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 seek_positions: Option<Vec<usize>> = stmt
4109 .where_
4110 .as_ref()
4111 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4112 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4113 let candidate_positions: Vec<usize> = match &seek_positions {
4114 Some(list) => list.clone(),
4115 None => (0..table.row_count()).collect(),
4116 };
4117 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4118 if loop_n.is_multiple_of(256) {
4122 cancel.check()?;
4123 }
4124 let Some(row) = table.rows().get(i) else {
4125 continue;
4126 };
4127 if let Some(w) = &stmt.where_ {
4128 let cond = eval::eval_expr(w, row, &ctx)?;
4129 if !matches!(cond, Value::Bool(true)) {
4130 continue;
4131 }
4132 }
4133 let mut new_vals = row.values.clone();
4134 for (pos, expr) in &targets {
4135 let v = eval::eval_expr(expr, row, &ctx)?;
4136 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4137 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4138 new_vals[*pos] = coerced;
4139 }
4140 for (pos, src) in &on_update_overrides {
4143 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4144 new_vals[*pos] = v;
4145 }
4146 planned.push((i, new_vals));
4147 }
4148 planned.sort_by_key(|(i, _)| *i);
4153 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4157 .iter()
4158 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4159 .collect();
4160 let self_fks = table.schema().foreign_keys.clone();
4161 let _ = table;
4166 if !self_fks.is_empty() {
4170 let new_rows: Vec<Vec<Value>> = planned
4171 .iter()
4172 .map(|(_pos, new_vals)| new_vals.clone())
4173 .collect();
4174 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4175 }
4176 {
4180 let new_rows: Vec<Vec<Value>> = planned
4181 .iter()
4182 .map(|(_pos, new_vals)| new_vals.clone())
4183 .collect();
4184 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4185 }
4186 let child_plan =
4190 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4191 for step in &child_plan {
4193 apply_fk_child_step(self.active_catalog_mut(), step)?;
4194 }
4195 let table = self
4197 .active_catalog_mut()
4198 .get_mut(&stmt.table)
4199 .ok_or_else(|| {
4200 EngineError::Storage(StorageError::TableNotFound {
4201 name: stmt.table.clone(),
4202 })
4203 })?;
4204 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4214 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4216 for (pos, new_vals) in &planned {
4217 let old_row = table.rows()[*pos].clone();
4218 let mut new_row = Row::new(new_vals.clone());
4219 let mut skip = false;
4220 for (fd, filter) in &before_update_triggers {
4221 if !filter.is_empty()
4226 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4227 {
4228 continue;
4229 }
4230 let (outcome, deferred) = triggers::fire_row_trigger(
4231 fd,
4232 Some(new_row.clone()),
4233 Some(&old_row),
4234 &stmt.table,
4235 &schema_cols,
4236 &[],
4237 trigger_session_cfg.as_deref(),
4238 false,
4239 )
4240 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4241 deferred_embedded.extend(deferred);
4242 match outcome {
4243 triggers::TriggerOutcome::Row(r) => new_row = r,
4244 triggers::TriggerOutcome::Skip => {
4245 skip = true;
4246 break;
4247 }
4248 }
4249 }
4250 if !skip {
4251 applied_after_before.push((*pos, new_row, old_row));
4252 }
4253 }
4254 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4257 applied_after_before
4258 .iter()
4259 .map(|(_pos, new_row, _old)| new_row.values.clone())
4260 .collect()
4261 } else {
4262 Vec::new()
4263 };
4264 let affected = applied_after_before.len();
4265 for (pos, new_row, old_row) in applied_after_before {
4269 table.update_row(pos, new_row.values.clone())?;
4270 for (fd, filter) in &after_update_triggers {
4271 if !filter.is_empty()
4272 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4273 {
4274 continue;
4275 }
4276 let (_outcome, deferred) = triggers::fire_row_trigger(
4277 fd,
4278 Some(new_row.clone()),
4279 Some(&old_row),
4280 &stmt.table,
4281 &schema_cols,
4282 &[],
4283 trigger_session_cfg.as_deref(),
4284 true,
4285 )
4286 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4287 deferred_embedded.extend(deferred);
4288 }
4289 }
4290 let _ = table;
4291 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4293 if !self.in_transaction() && affected > 0 {
4295 self.statistics
4296 .record_modifications(&stmt.table, affected as u64);
4297 }
4298 if let Some(items) = &stmt.returning {
4300 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4301 }
4302 Ok(QueryResult::CommandOk {
4303 affected,
4304 modified_catalog: !self.in_transaction(),
4305 })
4306 }
4307
4308 fn exec_merge_cancel(
4339 &mut self,
4340 stmt: &spg_sql::ast::MergeStatement,
4341 cancel: CancelToken<'_>,
4342 ) -> Result<QueryResult, EngineError> {
4343 let target_alias = stmt
4344 .target_alias
4345 .clone()
4346 .unwrap_or_else(|| stmt.target.clone());
4347 let source_alias = stmt
4348 .source_alias
4349 .clone()
4350 .unwrap_or_else(|| stmt.source.clone());
4351 let (target_cols, target_rows_snapshot) = {
4352 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4353 EngineError::Storage(StorageError::TableNotFound {
4354 name: stmt.target.clone(),
4355 })
4356 })?;
4357 (
4358 t.schema().columns.clone(),
4359 t.rows().iter().cloned().collect::<Vec<Row>>(),
4360 )
4361 };
4362 let (source_cols, source_rows) = {
4363 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4364 EngineError::Storage(StorageError::TableNotFound {
4365 name: stmt.source.clone(),
4366 })
4367 })?;
4368 (
4369 s.schema().columns.clone(),
4370 s.rows().iter().cloned().collect::<Vec<Row>>(),
4371 )
4372 };
4373 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4375 for col in &target_cols {
4376 combined_schema.push(ColumnSchema::new(
4377 alloc::format!("{target_alias}.{}", col.name),
4378 col.ty,
4379 col.nullable,
4380 ));
4381 }
4382 for col in &source_cols {
4383 combined_schema.push(ColumnSchema::new(
4384 alloc::format!("{source_alias}.{}", col.name),
4385 col.ty,
4386 col.nullable,
4387 ));
4388 }
4389 let combined_ctx = EvalContext::new(&combined_schema, None);
4390 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4394 for col in &target_cols {
4395 source_only_schema.push(ColumnSchema::new(
4396 alloc::format!("{target_alias}.{}", col.name),
4397 col.ty,
4398 col.nullable,
4399 ));
4400 }
4401 for col in &source_cols {
4402 source_only_schema.push(ColumnSchema::new(
4403 alloc::format!("{source_alias}.{}", col.name),
4404 col.ty,
4405 col.nullable,
4406 ));
4407 }
4408 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4409 let target_arity = target_cols.len();
4410 let source_arity = source_cols.len();
4411
4412 let mut delete_indices: Vec<usize> = Vec::new();
4415 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4416 let mut inserts: Vec<Vec<Value>> = Vec::new();
4417 let mut affected: usize = 0;
4418
4419 for (src_idx, src_row) in source_rows.iter().enumerate() {
4420 if src_idx.is_multiple_of(256) {
4421 cancel.check()?;
4422 }
4423 let mut matched_targets: Vec<usize> = Vec::new();
4425 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4426 let mut combined_vals = t_row.values.clone();
4427 combined_vals.extend(src_row.values.iter().cloned());
4428 let combined_row = Row::new(combined_vals);
4429 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4430 if matches!(cond, Value::Bool(true)) {
4431 matched_targets.push(t_idx);
4432 }
4433 }
4434 let is_matched = !matched_targets.is_empty();
4435 let fired_clause = stmt.clauses.iter().find(|c| {
4441 let kind_ok = match c.matched {
4442 spg_sql::ast::MergeMatched::Matched => is_matched,
4443 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4444 };
4445 if !kind_ok {
4446 return false;
4447 }
4448 let Some(cond_expr) = &c.condition else {
4449 return true;
4450 };
4451 let row = if is_matched {
4452 let t = &target_rows_snapshot[matched_targets[0]];
4453 let mut vals = t.values.clone();
4454 vals.extend(src_row.values.iter().cloned());
4455 Row::new(vals)
4456 } else {
4457 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4458 vals.extend(src_row.values.iter().cloned());
4459 Row::new(vals)
4460 };
4461 let ctx_ref = if is_matched {
4462 &combined_ctx
4463 } else {
4464 &source_only_ctx
4465 };
4466 matches!(
4467 eval::eval_expr(cond_expr, &row, ctx_ref),
4468 Ok(Value::Bool(true))
4469 )
4470 });
4471 let Some(clause) = fired_clause else { continue };
4472 match &clause.action {
4473 spg_sql::ast::MergeAction::DoNothing => {}
4474 spg_sql::ast::MergeAction::Delete => {
4475 for &t_idx in &matched_targets {
4476 if !delete_indices.contains(&t_idx) {
4477 delete_indices.push(t_idx);
4478 affected += 1;
4479 }
4480 }
4481 }
4482 spg_sql::ast::MergeAction::Update { assignments } => {
4483 let mut planned_sets: Vec<(usize, &Expr)> =
4485 Vec::with_capacity(assignments.len());
4486 for (col, expr) in assignments {
4487 let pos =
4488 target_cols
4489 .iter()
4490 .position(|c| c.name == *col)
4491 .ok_or_else(|| {
4492 EngineError::Eval(EvalError::ColumnNotFound {
4493 name: col.clone(),
4494 })
4495 })?;
4496 planned_sets.push((pos, expr));
4497 }
4498 for &t_idx in &matched_targets {
4499 let t_row = &target_rows_snapshot[t_idx];
4500 let mut new_values = t_row.values.clone();
4501 let mut combined_vals = t_row.values.clone();
4502 combined_vals.extend(src_row.values.iter().cloned());
4503 let combined_row = Row::new(combined_vals);
4504 for (pos, expr) in &planned_sets {
4505 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4506 let coerced = coerce_value(
4507 raw,
4508 target_cols[*pos].ty,
4509 &target_cols[*pos].name,
4510 *pos,
4511 )?;
4512 new_values[*pos] = coerced;
4513 }
4514 updates.push((t_idx, new_values));
4515 affected += 1;
4516 }
4517 }
4518 spg_sql::ast::MergeAction::Insert { columns, values } => {
4519 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4521 vals.extend(src_row.values.iter().cloned());
4522 let synth_row = Row::new(vals);
4523 let mut new_row_values: Vec<Value> =
4524 (0..target_arity).map(|_| Value::Null).collect();
4525 for (col, expr) in columns.iter().zip(values.iter()) {
4526 let pos =
4527 target_cols
4528 .iter()
4529 .position(|c| c.name == *col)
4530 .ok_or_else(|| {
4531 EngineError::Eval(EvalError::ColumnNotFound {
4532 name: col.clone(),
4533 })
4534 })?;
4535 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4536 let coerced =
4537 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4538 new_row_values[pos] = coerced;
4539 }
4540 inserts.push(new_row_values);
4541 affected += 1;
4542 }
4543 }
4544 }
4545 let _ = source_arity; let table = self
4549 .active_catalog_mut()
4550 .get_mut(&stmt.target)
4551 .ok_or_else(|| {
4552 EngineError::Storage(StorageError::TableNotFound {
4553 name: stmt.target.clone(),
4554 })
4555 })?;
4556 for (idx, new_vals) in &updates {
4560 table
4561 .update_row(*idx, new_vals.clone())
4562 .map_err(EngineError::Storage)?;
4563 }
4564 if !delete_indices.is_empty() {
4565 table.delete_rows(&delete_indices);
4566 }
4567 for vals in inserts {
4568 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4569 }
4570 Ok(QueryResult::CommandOk {
4571 affected,
4572 modified_catalog: affected > 0,
4573 })
4574 }
4575
4576 fn exec_delete_cancel(
4577 &mut self,
4578 stmt: &spg_sql::ast::DeleteStatement,
4579 cancel: CancelToken<'_>,
4580 ) -> Result<QueryResult, EngineError> {
4581 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4585 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4586 let trigger_session_cfg: Option<String> = self
4587 .session_params
4588 .get("default_text_search_config")
4589 .cloned();
4590 let mut cold_shadow_count: usize = 0;
4598 if let Some(w) = &stmt.where_ {
4599 let schema_cols = self
4600 .active_catalog()
4601 .get(&stmt.table)
4602 .ok_or_else(|| {
4603 EngineError::Storage(StorageError::TableNotFound {
4604 name: stmt.table.clone(),
4605 })
4606 })?
4607 .schema()
4608 .columns
4609 .clone();
4610 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4611 && let Some(idx_name) = self
4612 .active_catalog()
4613 .get(&stmt.table)
4614 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4615 {
4616 cold_shadow_count = self
4617 .active_catalog_mut()
4618 .shadow_cold_row(&stmt.table, &idx_name, &key)
4619 .unwrap_or(0);
4620 }
4621 }
4622
4623 let ts_cfg: Option<String> = self
4629 .session_param("default_text_search_config")
4630 .map(String::from);
4631 let table = self
4632 .active_catalog_mut()
4633 .get_mut(&stmt.table)
4634 .ok_or_else(|| {
4635 EngineError::Storage(StorageError::TableNotFound {
4636 name: stmt.table.clone(),
4637 })
4638 })?;
4639 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4640 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4641 .with_default_text_search_config(ts_cfg.as_deref());
4642 let mut positions: Vec<usize> = Vec::new();
4643 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4647 let seek_positions: Option<Vec<usize>> = stmt
4653 .where_
4654 .as_ref()
4655 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4656 let candidate_positions: Vec<usize> = match seek_positions {
4657 Some(mut list) => {
4658 list.sort_unstable();
4659 list
4660 }
4661 None => (0..table.row_count()).collect(),
4662 };
4663 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4664 if loop_n.is_multiple_of(256) {
4665 cancel.check()?;
4666 }
4667 let Some(row) = table.rows().get(i) else {
4668 continue;
4669 };
4670 let keep = if let Some(w) = &stmt.where_ {
4671 let cond = eval::eval_expr(w, row, &ctx)?;
4672 !matches!(cond, Value::Bool(true))
4673 } else {
4674 false
4675 };
4676 if !keep {
4677 positions.push(i);
4678 to_delete_rows.push(row.values.clone());
4679 }
4680 }
4681 let _ = table;
4688 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4696 if !before_delete_triggers.is_empty() {
4697 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4698 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4699 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4700 let old_row = Row::new(old_vals.clone());
4701 let mut cancel_this = false;
4702 for fd in &before_delete_triggers {
4703 let (outcome, deferred) = triggers::fire_row_trigger(
4704 fd,
4705 None,
4706 Some(&old_row),
4707 &stmt.table,
4708 &schema_cols,
4709 &[],
4710 trigger_session_cfg.as_deref(),
4711 false,
4712 )
4713 .map_err(|e| {
4714 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4715 })?;
4716 deferred_embedded.extend(deferred);
4717 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4718 cancel_this = true;
4719 break;
4720 }
4721 }
4722 if !cancel_this {
4723 filtered_positions.push(*pos);
4724 filtered_old_rows.push(old_vals.clone());
4725 }
4726 }
4727 positions = filtered_positions;
4728 to_delete_rows = filtered_old_rows;
4729 }
4730 let cascade_plan = plan_fk_parent_deletions(
4731 self.active_catalog(),
4732 &stmt.table,
4733 &positions,
4734 &to_delete_rows,
4735 )?;
4736 for step in &cascade_plan {
4743 apply_fk_child_step(self.active_catalog_mut(), step)?;
4744 }
4745 let table = self
4747 .active_catalog_mut()
4748 .get_mut(&stmt.table)
4749 .ok_or_else(|| {
4750 EngineError::Storage(StorageError::TableNotFound {
4751 name: stmt.table.clone(),
4752 })
4753 })?;
4754 let affected = table.delete_rows(&positions) + cold_shadow_count;
4755 let _ = table;
4756 if !after_delete_triggers.is_empty() {
4761 for old_vals in &to_delete_rows {
4762 let old_row = Row::new(old_vals.clone());
4763 for fd in &after_delete_triggers {
4764 let (_outcome, deferred) = triggers::fire_row_trigger(
4765 fd,
4766 None,
4767 Some(&old_row),
4768 &stmt.table,
4769 &schema_cols,
4770 &[],
4771 trigger_session_cfg.as_deref(),
4772 true,
4773 )
4774 .map_err(|e| {
4775 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4776 })?;
4777 deferred_embedded.extend(deferred);
4778 }
4779 }
4780 }
4781 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4783 if !self.in_transaction() && affected > 0 {
4785 self.statistics
4786 .record_modifications(&stmt.table, affected as u64);
4787 }
4788 if let Some(items) = &stmt.returning {
4794 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4795 }
4796 Ok(QueryResult::CommandOk {
4797 affected,
4798 modified_catalog: !self.in_transaction(),
4799 })
4800 }
4801
4802 #[allow(clippy::format_push_string)]
4812 fn exec_explain(
4813 &self,
4814 e: &spg_sql::ast::ExplainStatement,
4815 cancel: CancelToken<'_>,
4816 ) -> Result<QueryResult, EngineError> {
4817 let mut lines = Vec::<String>::new();
4818 explain_select(&e.inner, self, 0, &mut lines);
4819 if e.suggest {
4820 let suggestions = build_index_suggestions(&e.inner, self);
4829 for s in suggestions {
4830 lines.push(s);
4831 }
4832 } else if e.analyze {
4833 let started = self.clock.map(|f| f());
4850 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4851 let elapsed_micros = match (self.clock, started) {
4852 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4853 _ => None,
4854 };
4855 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4856 rows.len()
4857 } else {
4858 0
4859 };
4860 annotate_explain_lines(&mut lines, row_count, self);
4861 let mut total = alloc::format!("Total: rows={row_count}");
4862 if let Some(us) = elapsed_micros {
4863 total.push_str(&alloc::format!(" elapsed={us}us"));
4864 }
4865 lines.push(total);
4866 }
4867 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4868 let rows: Vec<Row> = lines
4869 .into_iter()
4870 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4871 .collect();
4872 Ok(QueryResult::Rows { columns, rows })
4873 }
4874
4875 fn exec_show_tables(&self) -> QueryResult {
4876 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4877 let rows: Vec<Row> = self
4878 .active_catalog()
4879 .table_names()
4880 .into_iter()
4881 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4882 .collect();
4883 QueryResult::Rows { columns, rows }
4884 }
4885
4886 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4891 let t = self.active_catalog().get(name).ok_or_else(|| {
4892 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4893 })?;
4894 let cols: Vec<String> = t
4895 .schema()
4896 .columns
4897 .iter()
4898 .map(|c| {
4899 let ty = render_data_type(c.ty);
4900 let nullable = if c.nullable { "" } else { " NOT NULL" };
4901 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4902 })
4903 .collect();
4904 let mut body = cols.join(",\n");
4905 for uc in &t.schema().uniqueness_constraints {
4907 let col_names: Vec<String> = uc
4908 .columns
4909 .iter()
4910 .map(|&p| {
4911 t.schema().columns.get(p).map_or_else(
4912 || alloc::format!("col{p}"),
4913 |c| alloc::format!("`{}`", c.name),
4914 )
4915 })
4916 .collect();
4917 let kw = if uc.is_primary_key {
4918 "PRIMARY KEY"
4919 } else {
4920 "UNIQUE KEY"
4921 };
4922 body.push_str(",\n ");
4923 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4924 }
4925 for fk in &t.schema().foreign_keys {
4927 let local: Vec<String> = fk
4928 .local_columns
4929 .iter()
4930 .map(|&p| {
4931 t.schema().columns.get(p).map_or_else(
4932 || alloc::format!("col{p}"),
4933 |c| alloc::format!("`{}`", c.name),
4934 )
4935 })
4936 .collect();
4937 let parent_cols: Vec<String> =
4938 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4939 fk.parent_columns
4940 .iter()
4941 .map(|&p| {
4942 parent.schema().columns.get(p).map_or_else(
4943 || alloc::format!("col{p}"),
4944 |c| alloc::format!("`{}`", c.name),
4945 )
4946 })
4947 .collect()
4948 } else {
4949 fk.parent_columns
4950 .iter()
4951 .map(|p| alloc::format!("col{p}"))
4952 .collect()
4953 };
4954 body.push_str(",\n ");
4955 body.push_str(&alloc::format!(
4956 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
4957 local.join(", "),
4958 fk.parent_table,
4959 parent_cols.join(", ")
4960 ));
4961 }
4962 let ddl = alloc::format!(
4963 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
4964 name,
4965 body
4966 );
4967 let columns = alloc::vec![
4968 ColumnSchema::new("Table", DataType::Text, false),
4969 ColumnSchema::new("Create Table", DataType::Text, false),
4970 ];
4971 let rows = alloc::vec![Row::new(alloc::vec![
4972 Value::Text(name.into()),
4973 Value::Text(ddl),
4974 ])];
4975 Ok(QueryResult::Rows { columns, rows })
4976 }
4977
4978 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
4984 let t = self.active_catalog().get(name).ok_or_else(|| {
4985 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4986 })?;
4987 let columns = alloc::vec![
4988 ColumnSchema::new("Table", DataType::Text, false),
4989 ColumnSchema::new("Non_unique", DataType::Int, false),
4990 ColumnSchema::new("Key_name", DataType::Text, false),
4991 ColumnSchema::new("Seq_in_index", DataType::Int, false),
4992 ColumnSchema::new("Column_name", DataType::Text, false),
4993 ColumnSchema::new("Null", DataType::Text, false),
4994 ColumnSchema::new("Index_type", DataType::Text, false),
4995 ];
4996 let mut rows: Vec<Row> = Vec::new();
4997 for idx in t.indices() {
4998 let col = t
4999 .schema()
5000 .columns
5001 .get(idx.column_position)
5002 .map_or("?".into(), |c| c.name.clone());
5003 let nullable = t
5004 .schema()
5005 .columns
5006 .get(idx.column_position)
5007 .map_or(true, |c| c.nullable);
5008 rows.push(Row::new(alloc::vec![
5009 Value::Text(name.into()),
5010 Value::Int(i32::from(!idx.is_unique)),
5011 Value::Text(idx.name.clone()),
5012 Value::Int(1),
5013 Value::Text(col),
5014 Value::Text(if nullable {
5015 "YES".into()
5016 } else {
5017 String::new()
5018 }),
5019 Value::Text("BTREE".into()),
5020 ]));
5021 }
5022 Ok(QueryResult::Rows { columns, rows })
5023 }
5024
5025 fn exec_show_status(&self) -> QueryResult {
5029 let columns = alloc::vec![
5030 ColumnSchema::new("Variable_name", DataType::Text, false),
5031 ColumnSchema::new("Value", DataType::Text, false),
5032 ];
5033 let pairs: &[(&str, &str)] = &[
5034 ("Uptime", "0"),
5035 ("Threads_connected", "1"),
5036 ("Threads_running", "1"),
5037 ("Questions", "0"),
5038 ("Slow_queries", "0"),
5039 ("Opened_tables", "0"),
5040 ("Innodb_buffer_pool_pages_total", "0"),
5041 ];
5042 let rows: Vec<Row> = pairs
5043 .iter()
5044 .map(|(k, v)| {
5045 Row::new(alloc::vec![
5046 Value::Text((*k).into()),
5047 Value::Text((*v).into())
5048 ])
5049 })
5050 .collect();
5051 QueryResult::Rows { columns, rows }
5052 }
5053
5054 fn exec_show_variables(&self) -> QueryResult {
5057 let columns = alloc::vec![
5058 ColumnSchema::new("Variable_name", DataType::Text, false),
5059 ColumnSchema::new("Value", DataType::Text, false),
5060 ];
5061 let mut rows: Vec<Row> = Vec::new();
5062 let canonical: &[(&str, &str)] = &[
5063 ("version", "8.0.35-spg"),
5064 ("version_comment", "SPG dual-stack engine"),
5065 ("character_set_server", "utf8mb4"),
5066 ("collation_server", "utf8mb4_0900_ai_ci"),
5067 ("max_allowed_packet", "67108864"),
5068 ("autocommit", "ON"),
5069 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5070 ("time_zone", "SYSTEM"),
5071 ("transaction_isolation", "REPEATABLE-READ"),
5072 ];
5073 for &(k, v) in canonical {
5074 rows.push(Row::new(alloc::vec![
5075 Value::Text(k.into()),
5076 Value::Text(v.into()),
5077 ]));
5078 }
5079 for (k, v) in &self.session_params {
5081 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5082 rows.push(Row::new(alloc::vec![
5083 Value::Text(k.clone()),
5084 Value::Text(v.clone()),
5085 ]));
5086 }
5087 }
5088 QueryResult::Rows { columns, rows }
5089 }
5090
5091 fn exec_show_processlist(&self) -> QueryResult {
5096 let columns = alloc::vec![
5097 ColumnSchema::new("Id", DataType::Int, false),
5098 ColumnSchema::new("User", DataType::Text, false),
5099 ColumnSchema::new("Host", DataType::Text, false),
5100 ColumnSchema::new("db", DataType::Text, true),
5101 ColumnSchema::new("Command", DataType::Text, false),
5102 ColumnSchema::new("Time", DataType::Int, false),
5103 ColumnSchema::new("State", DataType::Text, true),
5104 ColumnSchema::new("Info", DataType::Text, true),
5105 ];
5106 let rows = alloc::vec![Row::new(alloc::vec![
5107 Value::Int(1),
5108 Value::Text("postgres".into()),
5109 Value::Text("localhost".into()),
5110 Value::Text("postgres".into()),
5111 Value::Text("Query".into()),
5112 Value::Int(0),
5113 Value::Text("executing".into()),
5114 Value::Text("SHOW PROCESSLIST".into()),
5115 ])];
5116 QueryResult::Rows { columns, rows }
5117 }
5118
5119 fn exec_show_databases(&self) -> QueryResult {
5126 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5127 let names = [
5128 "information_schema",
5129 "mysql",
5130 "performance_schema",
5131 "sys",
5132 "postgres",
5133 ];
5134 let rows: Vec<Row> = names
5135 .iter()
5136 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5137 .collect();
5138 QueryResult::Rows { columns, rows }
5139 }
5140
5141 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5144 let table =
5145 self.active_catalog()
5146 .get(table_name)
5147 .ok_or_else(|| StorageError::TableNotFound {
5148 name: table_name.into(),
5149 })?;
5150 let columns = alloc::vec![
5151 ColumnSchema::new("name", DataType::Text, false),
5152 ColumnSchema::new("type", DataType::Text, false),
5153 ColumnSchema::new("nullable", DataType::Bool, false),
5154 ];
5155 let rows: Vec<Row> = table
5156 .schema()
5157 .columns
5158 .iter()
5159 .map(|c| {
5160 Row::new(alloc::vec![
5161 Value::Text(c.name.clone()),
5162 Value::Text(alloc::format!("{}", c.ty)),
5163 Value::Bool(c.nullable),
5164 ])
5165 })
5166 .collect();
5167 Ok(QueryResult::Rows { columns, rows })
5168 }
5169
5170 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5171 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5172 if self.tx_catalogs.contains_key(&tx_id) {
5173 return Err(EngineError::TransactionAlreadyOpen);
5174 }
5175 self.tx_catalogs.insert(
5176 tx_id,
5177 TxState {
5178 catalog: self.catalog.clone(),
5179 savepoints: Vec::new(),
5180 },
5181 );
5182 Ok(QueryResult::CommandOk {
5183 affected: 0,
5184 modified_catalog: false,
5185 })
5186 }
5187
5188 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5189 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5190 let state = self
5191 .tx_catalogs
5192 .remove(&tx_id)
5193 .ok_or(EngineError::NoActiveTransaction)?;
5194 self.catalog = state.catalog;
5195 Ok(QueryResult::CommandOk {
5199 affected: 0,
5200 modified_catalog: true,
5201 })
5202 }
5203
5204 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5205 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5206 if self.tx_catalogs.remove(&tx_id).is_none() {
5207 return Err(EngineError::NoActiveTransaction);
5208 }
5209 Ok(QueryResult::CommandOk {
5211 affected: 0,
5212 modified_catalog: false,
5213 })
5214 }
5215
5216 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5217 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5218 let state = self
5219 .tx_catalogs
5220 .get_mut(&tx_id)
5221 .ok_or(EngineError::NoActiveTransaction)?;
5222 state.savepoints.retain(|(n, _)| n != &name);
5226 let snapshot = state.catalog.clone();
5227 state.savepoints.push((name, snapshot));
5228 Ok(QueryResult::CommandOk {
5229 affected: 0,
5230 modified_catalog: false,
5231 })
5232 }
5233
5234 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5235 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5236 let state = self
5237 .tx_catalogs
5238 .get_mut(&tx_id)
5239 .ok_or(EngineError::NoActiveTransaction)?;
5240 let pos = state
5241 .savepoints
5242 .iter()
5243 .rposition(|(n, _)| n == name)
5244 .ok_or_else(|| {
5245 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5246 })?;
5247 let snapshot = state.savepoints[pos].1.clone();
5251 state.savepoints.truncate(pos + 1);
5252 state.catalog = snapshot;
5253 Ok(QueryResult::CommandOk {
5254 affected: 0,
5255 modified_catalog: false,
5256 })
5257 }
5258
5259 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5260 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5261 let state = self
5262 .tx_catalogs
5263 .get_mut(&tx_id)
5264 .ok_or(EngineError::NoActiveTransaction)?;
5265 let pos = state
5266 .savepoints
5267 .iter()
5268 .rposition(|(n, _)| n == name)
5269 .ok_or_else(|| {
5270 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5271 })?;
5272 state.savepoints.truncate(pos);
5275 Ok(QueryResult::CommandOk {
5276 affected: 0,
5277 modified_catalog: false,
5278 })
5279 }
5280
5281 fn exec_alter_table(
5292 &mut self,
5293 s: spg_sql::ast::AlterTableStatement,
5294 ) -> Result<QueryResult, EngineError> {
5295 let table_name = s.name.clone();
5300 for target in s.targets {
5301 self.exec_alter_table_subaction(&table_name, target)?;
5302 }
5303 Ok(QueryResult::CommandOk {
5304 affected: 0,
5305 modified_catalog: !self.in_transaction(),
5306 })
5307 }
5308
5309 fn exec_alter_table_subaction(
5310 &mut self,
5311 table_name_outer: &str,
5312 target: spg_sql::ast::AlterTableTarget,
5313 ) -> Result<(), EngineError> {
5314 struct S<'a> {
5317 name: &'a str,
5318 }
5319 let s = S {
5320 name: table_name_outer,
5321 };
5322 match target {
5323 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5324 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5325 EngineError::Storage(StorageError::TableNotFound {
5326 name: s.name.into(),
5327 })
5328 })?;
5329 table.schema_mut().hot_tier_bytes = Some(n);
5330 }
5331 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5332 let cols_snapshot = self
5337 .active_catalog()
5338 .get(s.name)
5339 .ok_or_else(|| {
5340 EngineError::Storage(StorageError::TableNotFound {
5341 name: s.name.into(),
5342 })
5343 })?
5344 .schema()
5345 .columns
5346 .clone();
5347 let storage_fk =
5348 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5349 let existing_rows: Vec<Vec<Value>> = self
5352 .active_catalog()
5353 .get(s.name)
5354 .expect("checked above")
5355 .rows()
5356 .iter()
5357 .map(|r| r.values.clone())
5358 .collect();
5359 enforce_fk_inserts(
5360 self.active_catalog(),
5361 s.name,
5362 core::slice::from_ref(&storage_fk),
5363 &existing_rows,
5364 )?;
5365 let table = self
5367 .active_catalog_mut()
5368 .get_mut(s.name)
5369 .expect("checked above");
5370 if let Some(name) = &storage_fk.name
5371 && table
5372 .schema()
5373 .foreign_keys
5374 .iter()
5375 .any(|f| f.name.as_ref() == Some(name))
5376 {
5377 return Err(EngineError::Unsupported(alloc::format!(
5378 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5379 )));
5380 }
5381 table.schema_mut().foreign_keys.push(storage_fk);
5382 }
5383 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5384 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5385 EngineError::Storage(StorageError::TableNotFound {
5386 name: s.name.into(),
5387 })
5388 })?;
5389 let fks = &mut table.schema_mut().foreign_keys;
5390 let before = fks.len();
5391 fks.retain(|f| f.name.as_ref() != Some(&name));
5392 if fks.len() == before && !if_exists {
5393 return Err(EngineError::Unsupported(alloc::format!(
5394 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5395 s.name
5396 )));
5397 }
5398 }
5400 spg_sql::ast::AlterTableTarget::AddColumn {
5401 column,
5402 if_not_exists,
5403 } => {
5404 let clock = self.clock;
5409 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5410 EngineError::Storage(StorageError::TableNotFound {
5411 name: s.name.into(),
5412 })
5413 })?;
5414 if table
5415 .schema()
5416 .columns
5417 .iter()
5418 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5419 {
5420 if if_not_exists {
5421 return Ok(());
5422 }
5423 return Err(EngineError::Unsupported(alloc::format!(
5424 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5425 column.name,
5426 s.name
5427 )));
5428 }
5429 let col_name = column.name.clone();
5430 let nullable = column.nullable;
5431 let has_default = column.default.is_some() || column.auto_increment;
5432 let col_schema = column_def_to_schema(column)?;
5433 let row_count = table.row_count();
5434 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5441 resolve_column_default_free(&col_schema, clock)?
5442 } else if nullable || row_count == 0 {
5443 Value::Null
5444 } else {
5445 return Err(EngineError::Unsupported(alloc::format!(
5446 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5447 when the table has existing rows"
5448 )));
5449 };
5450 table.add_column(col_schema, fill_value);
5451 }
5452 spg_sql::ast::AlterTableTarget::AlterColumnType {
5453 column,
5454 new_type,
5455 using,
5456 } => {
5457 let new_data_type = column_type_to_data_type(new_type);
5463 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5464 EngineError::Storage(StorageError::TableNotFound {
5465 name: s.name.into(),
5466 })
5467 })?;
5468 let col_pos = table
5469 .schema()
5470 .columns
5471 .iter()
5472 .position(|c| c.name.eq_ignore_ascii_case(&column))
5473 .ok_or_else(|| {
5474 EngineError::Unsupported(alloc::format!(
5475 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5476 s.name
5477 ))
5478 })?;
5479 let schema_cols = table.schema().columns.clone();
5480 let ctx = eval::EvalContext::new(&schema_cols, None);
5481 let mut new_values: alloc::vec::Vec<Value> =
5482 alloc::vec::Vec::with_capacity(table.row_count());
5483 for row in table.rows().iter() {
5484 let raw = match &using {
5485 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5486 EngineError::Unsupported(alloc::format!(
5487 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5488 ))
5489 })?,
5490 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5491 };
5492 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5493 new_values.push(coerced);
5494 }
5495 table.schema_mut().columns[col_pos].ty = new_data_type;
5496 for (i, v) in new_values.into_iter().enumerate() {
5497 let mut row_values = table
5498 .rows()
5499 .get(i)
5500 .expect("bounds-checked above")
5501 .values
5502 .clone();
5503 row_values[col_pos] = v;
5504 table.update_row(i, row_values)?;
5505 }
5506 }
5507 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5508 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5514 EngineError::Storage(StorageError::TableNotFound {
5515 name: s.name.into(),
5516 })
5517 })?;
5518 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5519 match tc {
5520 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5521 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5522 let positions: Vec<usize> = columns
5523 .iter()
5524 .map(|c| {
5525 table
5526 .schema()
5527 .columns
5528 .iter()
5529 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5530 .ok_or_else(|| {
5531 EngineError::Unsupported(alloc::format!(
5532 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5533 s.name
5534 ))
5535 })
5536 })
5537 .collect::<Result<Vec<_>, _>>()?;
5538 let already = table
5542 .schema()
5543 .uniqueness_constraints
5544 .iter()
5545 .any(|u| u.columns == positions);
5546 if !already {
5547 table.schema_mut().uniqueness_constraints.push(
5548 spg_storage::UniquenessConstraint {
5549 is_primary_key: is_pk,
5550 columns: positions.clone(),
5551 nulls_not_distinct: false,
5552 },
5553 );
5554 if is_pk {
5556 for p in &positions {
5557 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5558 c.nullable = false;
5559 }
5560 }
5561 }
5562 let leading = &columns[0];
5565 let already_idx = table.indices().iter().any(|idx| {
5566 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5567 && table.schema().columns[idx.column_position].name == *leading
5568 });
5569 if !already_idx {
5570 let suffix = if is_pk { "pkey" } else { "key" };
5571 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5572 let _ = table.add_index(idx_name, leading);
5573 }
5574 }
5575 }
5576 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5577 table.schema_mut().checks.push(alloc::format!("{expr}"));
5578 }
5579 spg_sql::ast::TableConstraint::Index { name, columns } => {
5580 let leading = &columns[0];
5586 let already_idx = table.indices().iter().any(|idx| {
5587 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5588 && table.schema().columns[idx.column_position].name == *leading
5589 });
5590 if !already_idx {
5591 let idx_name = name
5592 .clone()
5593 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5594 let _ = table.add_index(idx_name, leading);
5595 }
5596 }
5597 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5598 for (k, col) in columns.iter().enumerate() {
5606 let already_idx = table.indices().iter().any(|idx| {
5607 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5608 && table.schema().columns[idx.column_position].name == *col
5609 });
5610 if already_idx {
5611 continue;
5612 }
5613 let idx_name = match (&name, columns.len(), k) {
5614 (Some(n), 1, _) => n.clone(),
5615 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5616 (None, _, _) => {
5617 alloc::format!("{}_{col}_ftidx", s.name)
5618 }
5619 };
5620 let _ = table.add_gin_fulltext_index(idx_name, col);
5621 }
5622 }
5623 }
5624 }
5625 spg_sql::ast::AlterTableTarget::DropColumn {
5626 column,
5627 if_exists,
5628 cascade,
5629 } => {
5630 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5637 EngineError::Storage(StorageError::TableNotFound {
5638 name: s.name.into(),
5639 })
5640 })?;
5641 let col_pos = match table
5642 .schema()
5643 .columns
5644 .iter()
5645 .position(|c| c.name.eq_ignore_ascii_case(&column))
5646 {
5647 Some(p) => p,
5648 None => {
5649 if if_exists {
5650 return Ok(());
5651 }
5652 return Err(EngineError::Unsupported(alloc::format!(
5653 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5654 s.name
5655 )));
5656 }
5657 };
5658 let dependent_fks: Vec<usize> = table
5661 .schema()
5662 .foreign_keys
5663 .iter()
5664 .enumerate()
5665 .filter_map(|(i, fk)| {
5666 if fk.local_columns.contains(&col_pos) {
5667 Some(i)
5668 } else {
5669 None
5670 }
5671 })
5672 .collect();
5673 if !dependent_fks.is_empty() && !cascade {
5674 return Err(EngineError::Unsupported(alloc::format!(
5675 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5676 use DROP COLUMN ... CASCADE to remove them"
5677 )));
5678 }
5679 if cascade {
5681 let mut sorted = dependent_fks.clone();
5683 sorted.sort();
5684 sorted.reverse();
5685 let fks = &mut table.schema_mut().foreign_keys;
5686 for i in sorted {
5687 fks.remove(i);
5688 }
5689 }
5690 table.drop_column(col_pos);
5693 }
5694 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5695 let table_name = s.name.to_string();
5703 let trigs = self.active_catalog_mut().triggers_mut();
5704 let mut touched = false;
5705 for t in trigs.iter_mut() {
5706 if !t.table.eq_ignore_ascii_case(&table_name) {
5707 continue;
5708 }
5709 match &which {
5710 spg_sql::ast::TriggerSelector::All => {
5711 t.enabled = enabled;
5712 touched = true;
5713 }
5714 spg_sql::ast::TriggerSelector::Named(name) => {
5715 if t.name.eq_ignore_ascii_case(name) {
5716 t.enabled = enabled;
5717 touched = true;
5718 }
5719 }
5720 }
5721 }
5722 if !touched {
5728 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5729 return Err(EngineError::Unsupported(alloc::format!(
5730 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5731 if enabled { "ENABLE" } else { "DISABLE" },
5732 )));
5733 }
5734 }
5735 }
5736 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5737 let old = s.name.to_string();
5744 self.active_catalog_mut()
5745 .rename_table(&old, &new)
5746 .map_err(EngineError::Storage)?;
5747 }
5748 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5749 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5763 EngineError::Storage(StorageError::TableNotFound {
5764 name: s.name.into(),
5765 })
5766 })?;
5767 let col_pos = table
5768 .schema()
5769 .columns
5770 .iter()
5771 .position(|c| c.name.eq_ignore_ascii_case(&old))
5772 .ok_or_else(|| {
5773 EngineError::Unsupported(alloc::format!(
5774 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5775 s.name
5776 ))
5777 })?;
5778 if table
5780 .schema()
5781 .columns
5782 .iter()
5783 .enumerate()
5784 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5785 {
5786 return Err(EngineError::Unsupported(alloc::format!(
5787 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5788 s.name
5789 )));
5790 }
5791 if old.eq_ignore_ascii_case(&new) {
5795 return Ok(());
5796 }
5797 table.rename_column(col_pos, &new);
5798 let n_cols = table.schema().columns.len();
5804 for i in 0..n_cols {
5805 let rt = table.schema().columns[i].runtime_default.clone();
5806 if let Some(src) = rt {
5807 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5808 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5809 }
5810 }
5811 let checks = table.schema().checks.clone();
5813 let mut new_checks = Vec::with_capacity(checks.len());
5814 for chk in checks {
5815 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5816 }
5817 table.schema_mut().checks = new_checks;
5818 let n_idx = table.indices().len();
5820 for i in 0..n_idx {
5821 let pred = table.indices()[i].partial_predicate.clone();
5822 if let Some(src) = pred {
5823 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5824 table.set_partial_predicate(i, Some(rewritten));
5828 }
5829 }
5830 let table_name = s.name.to_string();
5833 for trig in self.active_catalog_mut().triggers_mut() {
5834 if !trig.table.eq_ignore_ascii_case(&table_name) {
5835 continue;
5836 }
5837 for c in &mut trig.update_columns {
5838 if c.eq_ignore_ascii_case(&old) {
5839 *c = new.clone();
5840 }
5841 }
5842 }
5843 }
5844 }
5845 Ok(())
5846 }
5847
5848 fn exec_alter_index(
5849 &mut self,
5850 stmt: spg_sql::ast::AlterIndexStatement,
5851 ) -> Result<QueryResult, EngineError> {
5852 let spg_sql::ast::AlterIndexStatement {
5856 name: idx_name,
5857 target,
5858 } = stmt;
5859 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5863 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5864 return match renamed {
5865 Ok(()) => Ok(QueryResult::CommandOk {
5866 affected: 0,
5867 modified_catalog: !self.in_transaction(),
5868 }),
5869 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5870 Ok(QueryResult::CommandOk {
5871 affected: 0,
5872 modified_catalog: false,
5873 })
5874 }
5875 Err(e) => Err(EngineError::Storage(e)),
5876 };
5877 }
5878 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5879 unreachable!("Rename branch returned above");
5880 };
5881 let target = encoding.map(|e| match e {
5882 SqlVecEncoding::F32 => VecEncoding::F32,
5883 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5884 SqlVecEncoding::F16 => VecEncoding::F16,
5885 });
5886 let table_name = {
5891 let cat = self.active_catalog();
5892 let mut found: Option<String> = None;
5893 for tname in cat.table_names() {
5894 if let Some(t) = cat.get(&tname)
5895 && t.indices().iter().any(|i| i.name == idx_name)
5896 {
5897 found = Some(tname);
5898 break;
5899 }
5900 }
5901 found.ok_or_else(|| {
5902 EngineError::Storage(StorageError::IndexNotFound {
5903 name: idx_name.clone(),
5904 })
5905 })?
5906 };
5907 let table = self
5908 .active_catalog_mut()
5909 .get_mut(&table_name)
5910 .expect("table found above");
5911 table.rebuild_nsw_index(&idx_name, target)?;
5912 self.plan_cache.evict_referencing(&table_name);
5915 Ok(QueryResult::CommandOk {
5916 affected: 0,
5917 modified_catalog: !self.in_transaction(),
5918 })
5919 }
5920
5921 fn exec_create_index(
5922 &mut self,
5923 stmt: CreateIndexStatement,
5924 ) -> Result<QueryResult, EngineError> {
5925 let table = self
5926 .active_catalog_mut()
5927 .get_mut(&stmt.table)
5928 .ok_or_else(|| {
5929 EngineError::Storage(StorageError::TableNotFound {
5930 name: stmt.table.clone(),
5931 })
5932 })?;
5933 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
5935 return Ok(QueryResult::CommandOk {
5936 affected: 0,
5937 modified_catalog: false,
5938 });
5939 }
5940 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
5947 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
5951 Vec::new()
5952 } else {
5953 let schema = table.schema();
5954 stmt.included_columns
5955 .iter()
5956 .map(|c| {
5957 schema.column_position(c).ok_or_else(|| {
5958 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
5959 })
5960 })
5961 .collect::<Result<Vec<_>, _>>()?
5962 };
5963 match stmt.method {
5964 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
5965 IndexMethod::Hnsw => {
5966 if !included_positions.is_empty() {
5967 return Err(EngineError::Unsupported(
5968 "INCLUDE columns are not supported on HNSW indexes".into(),
5969 ));
5970 }
5971 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
5972 }
5973 IndexMethod::Brin => {
5975 if !included_positions.is_empty() {
5976 return Err(EngineError::Unsupported(
5977 "INCLUDE columns are not supported on BRIN indexes".into(),
5978 ));
5979 }
5980 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
5981 }
5982 IndexMethod::Gin => {
5990 if !included_positions.is_empty() {
5991 return Err(EngineError::Unsupported(
5992 "INCLUDE columns are not supported on GIN indexes".into(),
5993 ));
5994 }
5995 let col_pos = table
5996 .schema()
5997 .column_position(&stmt.column)
5998 .ok_or_else(|| {
5999 EngineError::Storage(StorageError::ColumnNotFound {
6000 column: stmt.column.clone(),
6001 })
6002 })?;
6003 let col_ty = table.schema().columns[col_pos].ty;
6004 let is_trgm = stmt
6010 .opclass
6011 .as_deref()
6012 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
6013 if is_trgm
6014 && matches!(
6015 col_ty,
6016 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
6017 )
6018 {
6019 table
6020 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
6021 .map_err(EngineError::Storage)?;
6022 } else if col_ty == spg_storage::DataType::TsVector {
6023 table
6024 .add_gin_index(stmt.name.clone(), &stmt.column)
6025 .map_err(EngineError::Storage)?;
6026 } else {
6027 table.add_index(stmt.name.clone(), &stmt.column)?;
6033 }
6034 }
6035 }
6036 if !included_positions.is_empty()
6037 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
6038 {
6039 idx.included_columns = included_positions;
6040 }
6041 if let Some(pred_expr) = &stmt.partial_predicate {
6049 let canonical = pred_expr.to_string();
6050 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6062 idx.partial_predicate = Some(canonical);
6063 }
6064 }
6065 if let Some(key_expr) = &stmt.expression {
6073 if matches!(
6074 stmt.method,
6075 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6076 ) {
6077 return Err(EngineError::Unsupported(
6078 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6079 ));
6080 }
6081 let canonical = key_expr.to_string();
6082 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6083 idx.expression = Some(canonical);
6084 }
6085 }
6086 if stmt.is_unique {
6095 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6096 for col_name in &stmt.extra_columns {
6097 let pos = table
6098 .schema()
6099 .columns
6100 .iter()
6101 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6102 .ok_or_else(|| {
6103 EngineError::Unsupported(alloc::format!(
6104 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6105 stmt.name,
6106 stmt.table
6107 ))
6108 })?;
6109 extra_positions.push(pos);
6110 }
6111 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6112 idx.is_unique = true;
6113 idx.extra_column_positions = extra_positions;
6114 }
6115 let snapshot_indices = table.indices().to_vec();
6120 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6121 table.rows().iter().cloned().collect();
6122 let snapshot_schema = table.schema().clone();
6123 let idx_ref = snapshot_indices
6124 .iter()
6125 .find(|i| i.name == stmt.name)
6126 .expect("just-added index");
6127 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6128 }
6129 self.plan_cache.evict_referencing(&table_name);
6132 Ok(QueryResult::CommandOk {
6133 affected: 0,
6134 modified_catalog: !self.in_transaction(),
6135 })
6136 }
6137
6138 fn reconcile_table_if_not_exists(
6147 &mut self,
6148 stmt: CreateTableStatement,
6149 ) -> Result<QueryResult, EngineError> {
6150 let table_name = stmt.name.clone();
6151 let clock = self.clock;
6152 let existing_col_names: alloc::collections::BTreeSet<String> = self
6153 .active_catalog()
6154 .get(&table_name)
6155 .expect("checked above")
6156 .schema()
6157 .columns
6158 .iter()
6159 .map(|c| c.name.to_ascii_lowercase())
6160 .collect();
6161 let row_count = self
6162 .active_catalog()
6163 .get(&table_name)
6164 .expect("checked above")
6165 .row_count();
6166 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6168 .columns
6169 .iter()
6170 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6171 .cloned()
6172 .collect();
6173 for col_def in new_columns {
6174 let col_name = col_def.name.clone();
6175 let nullable = col_def.nullable;
6176 let has_default = col_def.default.is_some() || col_def.auto_increment;
6177 let col_schema = column_def_to_schema(col_def)?;
6178 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6179 resolve_column_default_free(&col_schema, clock)?
6180 } else if nullable || row_count == 0 {
6181 Value::Null
6182 } else {
6183 return Err(EngineError::Unsupported(alloc::format!(
6184 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6185 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6186 )));
6187 };
6188 let table = self
6189 .active_catalog_mut()
6190 .get_mut(&table_name)
6191 .expect("checked above");
6192 table.add_column(col_schema, fill_value);
6193 }
6194 let table_cols_now = self
6198 .active_catalog()
6199 .get(&table_name)
6200 .expect("checked above")
6201 .schema()
6202 .columns
6203 .clone();
6204 for fk in stmt.foreign_keys {
6205 let all_resolved = fk.columns.iter().all(|c| {
6209 table_cols_now
6210 .iter()
6211 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6212 });
6213 if !all_resolved {
6214 continue;
6215 }
6216 let already_present = {
6217 let table = self
6218 .active_catalog()
6219 .get(&table_name)
6220 .expect("checked above");
6221 table.schema().foreign_keys.iter().any(|f| {
6222 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6223 && f.local_columns.len() == fk.columns.len()
6224 })
6225 };
6226 if already_present {
6227 continue;
6228 }
6229 let storage_fk =
6230 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6231 let table = self
6232 .active_catalog_mut()
6233 .get_mut(&table_name)
6234 .expect("checked above");
6235 table.schema_mut().foreign_keys.push(storage_fk);
6236 }
6237 Ok(QueryResult::CommandOk {
6238 affected: 0,
6239 modified_catalog: !self.in_transaction(),
6240 })
6241 }
6242
6243 fn exec_drop_table(
6245 &mut self,
6246 names: Vec<String>,
6247 if_exists: bool,
6248 ) -> Result<QueryResult, EngineError> {
6249 for name in names {
6250 let dropped = self.active_catalog_mut().drop_table(&name);
6251 if !dropped && !if_exists {
6252 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6253 }
6254 }
6255 Ok(QueryResult::CommandOk {
6256 affected: 0,
6257 modified_catalog: !self.in_transaction(),
6258 })
6259 }
6260
6261 fn exec_drop_index(
6263 &mut self,
6264 name: String,
6265 if_exists: bool,
6266 ) -> Result<QueryResult, EngineError> {
6267 let dropped = self.active_catalog_mut().drop_named_index(&name);
6268 if !dropped && !if_exists {
6269 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6270 }
6271 Ok(QueryResult::CommandOk {
6272 affected: 0,
6273 modified_catalog: !self.in_transaction(),
6274 })
6275 }
6276
6277 fn exec_create_table(
6278 &mut self,
6279 stmt: CreateTableStatement,
6280 ) -> Result<QueryResult, EngineError> {
6281 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6282 return Ok(QueryResult::CommandOk {
6301 affected: 0,
6302 modified_catalog: false,
6303 });
6304 }
6305 let table_name = stmt.name.clone();
6306 let inline_pk_columns: Vec<String> = stmt
6310 .columns
6311 .iter()
6312 .filter(|c| c.is_primary_key)
6313 .map(|c| c.name.clone())
6314 .collect();
6315 let cols = stmt
6321 .columns
6322 .into_iter()
6323 .map(column_def_to_schema)
6324 .collect::<Result<Vec<_>, _>>()?;
6325 let mut cols = cols;
6334 for col in cols.iter_mut() {
6335 let Some(name) = col.user_enum_type.take() else {
6336 continue;
6337 };
6338 let cat = self.active_catalog();
6339 if cat.enum_types().contains_key(&name) {
6340 col.user_enum_type = Some(name);
6341 continue;
6342 }
6343 if let Some(dom) = cat.domain_types().get(&name) {
6344 col.ty = dom.base_type;
6345 col.user_domain_type = Some(name);
6346 if !dom.nullable {
6347 col.nullable = false;
6348 }
6349 continue;
6350 }
6351 return Err(EngineError::Unsupported(alloc::format!(
6352 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6353 col.name,
6354 name
6355 )));
6356 }
6357 for tc in &stmt.table_constraints {
6358 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6359 for col_name in columns {
6360 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6361 col.nullable = false;
6362 }
6363 }
6364 }
6365 }
6366 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6373 Vec::with_capacity(stmt.foreign_keys.len());
6374 for fk in stmt.foreign_keys {
6375 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6382 if !self.foreign_key_checks
6383 && needs_parent
6384 && self.active_catalog().get(&fk.parent_table).is_none()
6385 {
6386 self.pending_foreign_keys.push((table_name.clone(), fk));
6387 continue;
6388 }
6389 fks.push(resolve_foreign_key(
6390 &table_name,
6391 &cols,
6392 fk,
6393 self.active_catalog(),
6394 )?);
6395 }
6396 let mut schema = TableSchema::new(table_name.clone(), cols);
6397 schema.foreign_keys = fks;
6398 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6402 let mut check_exprs: Vec<String> = Vec::new();
6403 for tc in &stmt.table_constraints {
6404 let (is_pk, names, nnd) = match tc {
6405 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6406 (true, columns.clone(), false)
6407 }
6408 spg_sql::ast::TableConstraint::Unique {
6409 columns,
6410 nulls_not_distinct,
6411 ..
6412 } => (false, columns.clone(), *nulls_not_distinct),
6413 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6414 check_exprs.push(alloc::format!("{expr}"));
6417 continue;
6418 }
6419 spg_sql::ast::TableConstraint::Index { .. } => continue,
6425 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6429 };
6430 let mut positions = Vec::with_capacity(names.len());
6431 for n in &names {
6432 let pos = schema
6433 .columns
6434 .iter()
6435 .position(|c| c.name == *n)
6436 .ok_or_else(|| {
6437 EngineError::Unsupported(alloc::format!(
6438 "table constraint references unknown column {n:?}"
6439 ))
6440 })?;
6441 positions.push(pos);
6442 }
6443 uc_storage.push(spg_storage::UniquenessConstraint {
6444 is_primary_key: is_pk,
6445 columns: positions,
6446 nulls_not_distinct: nnd,
6447 });
6448 }
6449 schema.uniqueness_constraints = uc_storage.clone();
6450 schema.checks = check_exprs;
6451 self.active_catalog_mut().create_table(schema)?;
6452 let table = self
6456 .active_catalog_mut()
6457 .get_mut(&table_name)
6458 .expect("just created");
6459 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6460 let idx_name = if inline_pk_columns.len() == 1 {
6461 alloc::format!("{table_name}_pkey")
6462 } else {
6463 alloc::format!("{table_name}_pkey_{i}")
6464 };
6465 if let Err(e) = table.add_index(idx_name, col_name) {
6466 return Err(EngineError::Storage(e));
6467 }
6468 }
6469 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6470 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6475 for (k, col) in columns.iter().enumerate() {
6476 let already = table.indices().iter().any(|idx| {
6477 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6478 && table.schema().columns[idx.column_position].name == *col
6479 });
6480 if already {
6481 continue;
6482 }
6483 let idx_name = match (name.as_ref(), columns.len(), k) {
6484 (Some(n), 1, _) => n.clone(),
6485 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6486 (None, _, _) => {
6487 alloc::format!("{table_name}_{col}_ftidx")
6488 }
6489 };
6490 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6491 return Err(EngineError::Storage(e));
6492 }
6493 }
6494 continue;
6495 }
6496 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6500 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6501 ("pkey", columns, None)
6502 }
6503 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6504 spg_sql::ast::TableConstraint::Index { name, columns } => {
6505 ("idx", columns, name.as_ref())
6506 }
6507 spg_sql::ast::TableConstraint::Check { .. } => continue,
6508 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6510 };
6511 let leading = &names[0];
6512 let already = table.indices().iter().any(|idx| {
6515 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6516 && table.schema().columns[idx.column_position].name == *leading
6517 });
6518 if already {
6519 continue;
6520 }
6521 let idx_name = if let Some(n) = explicit_name {
6522 n.clone()
6523 } else if names.len() == 1 {
6524 alloc::format!("{table_name}_{leading}_{suffix}")
6525 } else {
6526 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6527 };
6528 if let Err(e) = table.add_index(idx_name, leading) {
6529 return Err(EngineError::Storage(e));
6530 }
6531 }
6532 Ok(QueryResult::CommandOk {
6533 affected: 0,
6534 modified_catalog: !self.in_transaction(),
6535 })
6536 }
6537
6538 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6539 for tuple in &mut stmt.rows {
6547 for cell in tuple.iter_mut() {
6548 self.resolve_sequence_calls_in_expr(cell)?;
6549 }
6550 }
6551 if let Some(select) = stmt.select_source.clone() {
6556 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6557 let rows = match select_result {
6558 QueryResult::Rows { rows, .. } => rows,
6559 other => {
6560 return Err(EngineError::Unsupported(alloc::format!(
6561 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6562 )));
6563 }
6564 };
6565 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6566 for row in rows {
6567 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6568 for v in row.values {
6569 tuple.push(value_to_literal_expr_permissive(v)?);
6570 }
6571 materialised.push(tuple);
6572 }
6573 let recurse = InsertStatement {
6574 table: stmt.table,
6575 columns: stmt.columns,
6576 rows: materialised,
6577 select_source: None,
6578 on_conflict: stmt.on_conflict,
6579 returning: stmt.returning,
6580 };
6581 return self.exec_insert(recurse);
6582 }
6583 let clock = self.clock;
6587 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6593 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6594 let trigger_session_cfg: Option<alloc::string::String> = self
6595 .session_params
6596 .get("default_text_search_config")
6597 .cloned();
6598 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6604 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6605 EngineError::Storage(StorageError::TableNotFound {
6606 name: stmt.table.clone(),
6607 })
6608 })?;
6609 preview_table.schema().columns.clone()
6610 };
6611 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6612 pre_borrow_column_meta
6613 .iter()
6614 .enumerate()
6615 .filter_map(|(i, col)| {
6616 if let Some(inline) = &col.inline_enum_variants {
6621 return Some((i, inline.clone()));
6622 }
6623 col.user_enum_type.as_ref().and_then(|ename| {
6624 self.active_catalog()
6625 .enum_types()
6626 .get(ename)
6627 .map(|e| (i, e.labels.clone()))
6628 })
6629 })
6630 .collect();
6631 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6636 pre_borrow_column_meta
6637 .iter()
6638 .enumerate()
6639 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6640 .collect();
6641 let table = self
6642 .active_catalog_mut()
6643 .get_mut(&stmt.table)
6644 .ok_or_else(|| {
6645 EngineError::Storage(StorageError::TableNotFound {
6646 name: stmt.table.clone(),
6647 })
6648 })?;
6649 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6655 let schema_cols_len = column_meta.len();
6656 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6660 None => None, Some(cols) => {
6662 let mut map = alloc::vec![None; schema_cols_len];
6663 for (j, name) in cols.iter().enumerate() {
6664 let idx = column_meta
6665 .iter()
6666 .position(|c| c.name == *name)
6667 .ok_or_else(|| {
6668 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6669 })?;
6670 if map[idx].is_some() {
6671 return Err(EngineError::Storage(StorageError::ArityMismatch {
6672 expected: schema_cols_len,
6673 actual: cols.len(),
6674 }));
6675 }
6676 map[idx] = Some(j);
6677 }
6678 for (i, col) in column_meta.iter().enumerate() {
6682 if map[i].is_none()
6683 && !col.nullable
6684 && col.default.is_none()
6685 && col.runtime_default.is_none()
6686 && !col.auto_increment
6687 {
6688 return Err(EngineError::Storage(StorageError::NullInNotNull {
6689 column: col.name.clone(),
6690 }));
6691 }
6692 }
6693 Some(map)
6694 }
6695 };
6696 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6697 let fks = table.schema().foreign_keys.clone();
6703 let mut affected = 0usize;
6704 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6707 for tuple in stmt.rows {
6708 if tuple.len() != expected_tuple_len {
6709 return Err(EngineError::Storage(StorageError::ArityMismatch {
6710 expected: expected_tuple_len,
6711 actual: tuple.len(),
6712 }));
6713 }
6714 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6718 let raw_tuple: Vec<Value> = tuple
6720 .into_iter()
6721 .map(literal_expr_to_value)
6722 .collect::<Result<_, _>>()?;
6723 let mut out = Vec::with_capacity(schema_cols_len);
6724 for (i, col) in column_meta.iter().enumerate() {
6725 let mut raw = match map[i] {
6726 Some(j) => raw_tuple[j].clone(),
6727 None => resolve_column_default_free(col, clock)?,
6728 };
6729 if col.auto_increment && raw.is_null() {
6730 let next = table.next_auto_value(i).ok_or_else(|| {
6731 EngineError::Unsupported(alloc::format!(
6732 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6733 col.name
6734 ))
6735 })?;
6736 raw = Value::BigInt(next);
6737 }
6738 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6739 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6740 let coerced =
6741 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6742 check_unsigned_range(&coerced, col, i)?;
6743 out.push(coerced);
6744 }
6745 out
6746 } else {
6747 let mut out = Vec::with_capacity(schema_cols_len);
6749 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6750 let mut raw = literal_expr_to_value(expr)?;
6751 if col.auto_increment && raw.is_null() {
6752 let next = table.next_auto_value(i).ok_or_else(|| {
6753 EngineError::Unsupported(alloc::format!(
6754 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6755 col.name
6756 ))
6757 })?;
6758 raw = Value::BigInt(next);
6759 }
6760 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6761 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6762 let coerced =
6763 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6764 check_unsigned_range(&coerced, col, i)?;
6765 out.push(coerced);
6766 }
6767 out
6768 };
6769 all_values.push(values);
6770 }
6771 let uniqueness = table.schema().uniqueness_constraints.clone();
6776 let _ = table;
6777 if !fks.is_empty() {
6778 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6779 }
6780 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6782 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6784 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6791 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6798 let mut skipped_count = 0usize;
6799 if let Some(clause) = &stmt.on_conflict {
6800 let conflict_cols = resolve_on_conflict_columns(
6801 self.active_catalog(),
6802 &stmt.table,
6803 clause.target_columns.as_slice(),
6804 )?;
6805 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6806 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6807 for values in all_values {
6808 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6809 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6812 let collides_with_table = !has_null_key
6813 && on_conflict_keys_exist(
6814 self.active_catalog(),
6815 &stmt.table,
6816 &conflict_cols,
6817 &key_tuple,
6818 );
6819 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6820 let collides_with_batch =
6821 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6822 let collides = collides_with_table || collides_with_batch;
6823 match (&clause.action, collides) {
6824 (_, false) => {
6825 seen_keys.push(key_tuple_owned);
6826 kept.push(values);
6827 }
6828 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6829 skipped_count += 1;
6830 }
6831 (
6832 spg_sql::ast::OnConflictAction::Update {
6833 assignments,
6834 where_,
6835 },
6836 true,
6837 ) => {
6838 if !collides_with_table {
6839 skipped_count += 1;
6840 continue;
6841 }
6842 let target_pos = lookup_row_position_by_keys(
6843 self.active_catalog(),
6844 &stmt.table,
6845 &conflict_cols,
6846 &key_tuple,
6847 )
6848 .ok_or_else(|| {
6849 EngineError::Unsupported(
6850 "ON CONFLICT DO UPDATE: conflict detected but row \
6851 position could not be resolved (cold-tier row?)"
6852 .into(),
6853 )
6854 })?;
6855 let updated = apply_on_conflict_assignments(
6856 self.active_catalog(),
6857 &stmt.table,
6858 target_pos,
6859 &values,
6860 assignments,
6861 where_.as_ref(),
6862 )?;
6863 if let Some(new_row) = updated {
6864 pending_updates.push((target_pos, new_row));
6865 } else {
6866 skipped_count += 1;
6867 }
6868 }
6869 }
6870 }
6871 all_values = kept;
6872 }
6873 let table = self
6875 .active_catalog_mut()
6876 .get_mut(&stmt.table)
6877 .ok_or_else(|| {
6878 EngineError::Storage(StorageError::TableNotFound {
6879 name: stmt.table.clone(),
6880 })
6881 })?;
6882 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
6886 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
6890 'rowloop: for values in all_values {
6891 let mut row = Row::new(values);
6892 for fd in &before_insert_triggers {
6897 let (outcome, deferred) = triggers::fire_row_trigger(
6898 fd,
6899 Some(row.clone()),
6900 None,
6901 &stmt.table,
6902 &column_meta,
6903 &[],
6904 trigger_session_cfg.as_deref(),
6905 false,
6906 )
6907 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6908 deferred_embedded.extend(deferred);
6909 match outcome {
6910 triggers::TriggerOutcome::Row(r) => row = r,
6911 triggers::TriggerOutcome::Skip => continue 'rowloop,
6912 }
6913 }
6914 if stmt.returning.is_some() {
6915 returning_rows.push(row.values.clone());
6916 }
6917 let inserted = row.clone();
6920 table.insert(row)?;
6921 affected += 1;
6922 for fd in &after_insert_triggers {
6926 let (_outcome, deferred) = triggers::fire_row_trigger(
6927 fd,
6928 Some(inserted.clone()),
6929 None,
6930 &stmt.table,
6931 &column_meta,
6932 &[],
6933 trigger_session_cfg.as_deref(),
6934 true,
6935 )
6936 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6937 deferred_embedded.extend(deferred);
6938 }
6939 }
6940 for (pos, new_row) in pending_updates {
6944 if stmt.returning.is_some() {
6945 returning_rows.push(new_row.clone());
6946 }
6947 table.update_row(pos, new_row)?;
6948 affected += 1;
6949 }
6950 let _ = skipped_count;
6951 let _ = table;
6957 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
6958 if let Some(items) = &stmt.returning {
6962 return self.build_returning_rows(&stmt.table, items, returning_rows);
6963 }
6964 if !self.in_transaction() && affected > 0 {
6969 self.statistics
6970 .record_modifications(&stmt.table, affected as u64);
6971 }
6972 Ok(QueryResult::CommandOk {
6973 affected,
6974 modified_catalog: !self.in_transaction(),
6975 })
6976 }
6977
6978 fn exec_select_as_of_segment(
6991 &self,
6992 stmt: &SelectStatement,
6993 from: &spg_sql::ast::FromClause,
6994 segment_id: u32,
6995 ) -> Result<QueryResult, EngineError> {
6996 if !from.joins.is_empty()
6999 || stmt.group_by.is_some()
7000 || stmt.having.is_some()
7001 || !stmt.unions.is_empty()
7002 || !stmt.order_by.is_empty()
7003 || stmt.offset.is_some()
7004 || stmt.distinct
7005 || aggregate::uses_aggregate(stmt)
7006 {
7007 return Err(EngineError::Unsupported(
7008 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
7009 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
7010 .into(),
7011 ));
7012 }
7013 let table = self
7014 .active_catalog()
7015 .get(&from.primary.name)
7016 .ok_or_else(|| StorageError::TableNotFound {
7017 name: from.primary.name.clone(),
7018 })?;
7019 let schema = table.schema().clone();
7020 let schema_cols = &schema.columns;
7021 let alias = from
7022 .primary
7023 .alias
7024 .as_deref()
7025 .unwrap_or(from.primary.name.as_str());
7026 let ctx = EvalContext::new(schema_cols, Some(alias));
7027 let seg = self
7028 .active_catalog()
7029 .cold_segment(segment_id)
7030 .ok_or_else(|| {
7031 EngineError::Unsupported(alloc::format!(
7032 "AS OF SEGMENT: cold segment {segment_id} not registered"
7033 ))
7034 })?;
7035 let mut out_rows: Vec<Row> = Vec::new();
7036 let mut limit_remaining: Option<usize> =
7037 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
7038 for (_key, body) in seg.scan() {
7039 let (row, _consumed) =
7040 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
7041 if let Some(where_expr) = &stmt.where_ {
7042 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7043 if !matches!(cond, Value::Bool(true)) {
7044 continue;
7045 }
7046 }
7047 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7049 out_rows.push(projected);
7050 if let Some(rem) = limit_remaining.as_mut() {
7051 if *rem == 0 {
7052 out_rows.pop();
7053 break;
7054 }
7055 *rem -= 1;
7056 }
7057 }
7058 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7060 Ok(QueryResult::Rows {
7061 columns,
7062 rows: out_rows,
7063 })
7064 }
7065
7066 fn eval_expr_simple(
7071 &self,
7072 expr: &Expr,
7073 row: &Row,
7074 ctx: &EvalContext,
7075 ) -> Result<Value, EngineError> {
7076 let cancel = CancelToken::none();
7077 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7078 }
7079
7080 fn build_returning_rows(
7087 &self,
7088 table_name: &str,
7089 items: &[SelectItem],
7090 mutated_rows: Vec<Vec<Value>>,
7091 ) -> Result<QueryResult, EngineError> {
7092 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7093 EngineError::Storage(StorageError::TableNotFound {
7094 name: table_name.into(),
7095 })
7096 })?;
7097 let schema_cols = table.schema().columns.clone();
7098 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7099 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7100 for values in mutated_rows {
7101 let row = Row::new(values);
7102 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7103 out_rows.push(projected);
7104 }
7105 Ok(QueryResult::Rows {
7106 columns,
7107 rows: out_rows,
7108 })
7109 }
7110
7111 fn project_row_simple(
7115 &self,
7116 row: &Row,
7117 items: &[SelectItem],
7118 schema_cols: &[ColumnSchema],
7119 alias: &str,
7120 ) -> Result<Row, EngineError> {
7121 let ctx = EvalContext::new(schema_cols, Some(alias));
7122 let cancel = CancelToken::none();
7123 let mut out_vals = Vec::new();
7124 for item in items {
7125 match item {
7126 SelectItem::Wildcard => {
7127 out_vals.extend(row.values.iter().cloned());
7128 }
7129 SelectItem::Expr { expr, .. } => {
7130 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7131 out_vals.push(v);
7132 }
7133 }
7134 }
7135 Ok(Row::new(out_vals))
7136 }
7137
7138 fn derive_output_columns(
7143 &self,
7144 items: &[SelectItem],
7145 schema_cols: &[ColumnSchema],
7146 _alias: &str,
7147 ) -> Vec<ColumnSchema> {
7148 let mut out = Vec::new();
7149 for item in items {
7150 match item {
7151 SelectItem::Wildcard => {
7152 out.extend(schema_cols.iter().cloned());
7153 }
7154 SelectItem::Expr { alias, .. } => {
7155 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7156 out.push(ColumnSchema::new(name, DataType::Text, true));
7159 }
7160 }
7161 }
7162 out
7163 }
7164
7165 fn exec_select_cancel(
7166 &self,
7167 stmt: &SelectStatement,
7168 cancel: CancelToken<'_>,
7169 ) -> Result<QueryResult, EngineError> {
7170 cancel.check()?;
7171 if !self.active_catalog().views().is_empty() {
7178 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7179 return self.exec_select_cancel(&rewritten, cancel);
7180 }
7181 }
7182 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7191 return self.exec_select_with_meta_views(stmt, cancel);
7192 }
7193 if let Some(from) = &stmt.from
7202 && let Some(seg_id) = from.primary.as_of_segment
7203 {
7204 return self.exec_select_as_of_segment(stmt, from, seg_id);
7205 }
7206 if let Some(from) = &stmt.from
7210 && from.joins.is_empty()
7211 && stmt.where_.is_none()
7212 && stmt.group_by.is_none()
7213 && stmt.having.is_none()
7214 && stmt.unions.is_empty()
7215 && stmt.order_by.is_empty()
7216 && stmt.limit.is_none()
7217 && stmt.offset.is_none()
7218 && !stmt.distinct
7219 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7220 {
7221 let lower = from.primary.name.to_ascii_lowercase();
7222 match lower.as_str() {
7223 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7224 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7226 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7227 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7228 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7229 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7230 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7231 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7232 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7233 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7234 _ => {}
7235 }
7236 }
7237 if !stmt.ctes.is_empty() {
7245 return self.exec_with_ctes(stmt, cancel);
7246 }
7247 let mut stmt_owned;
7254 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7255 stmt_owned = stmt.clone();
7256 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7257 &stmt_owned
7258 } else {
7259 stmt
7260 };
7261 if stmt_ref.unions.is_empty() {
7262 return self.exec_bare_select_cancel(stmt_ref, cancel);
7263 }
7264 let mut head = stmt_ref.clone();
7269 head.unions = Vec::new();
7270 head.order_by = Vec::new();
7271 head.limit = None;
7272 let QueryResult::Rows { columns, mut rows } =
7273 self.exec_bare_select_cancel(&head, cancel)?
7274 else {
7275 unreachable!("bare SELECT cannot return CommandOk")
7276 };
7277 for (kind, peer) in &stmt_ref.unions {
7278 let QueryResult::Rows {
7279 columns: peer_cols,
7280 rows: peer_rows,
7281 } = self.exec_bare_select_cancel(peer, cancel)?
7282 else {
7283 unreachable!("bare SELECT cannot return CommandOk")
7284 };
7285 if peer_cols.len() != columns.len() {
7286 return Err(EngineError::Unsupported(alloc::format!(
7287 "UNION arity mismatch: head has {} columns, peer has {}",
7288 columns.len(),
7289 peer_cols.len()
7290 )));
7291 }
7292 rows.extend(peer_rows);
7293 if matches!(kind, UnionKind::Distinct) {
7294 rows = dedup_rows(rows);
7295 }
7296 }
7297 if !stmt.order_by.is_empty() {
7300 let synth_ctx = EvalContext::new(&columns, None);
7301 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7302 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7303 for r in rows {
7304 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7305 tagged.push((keys, r));
7306 }
7307 sort_by_keys(&mut tagged, &descs);
7308 rows = tagged.into_iter().map(|(_, r)| r).collect();
7309 }
7310 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7311 Ok(QueryResult::Rows { columns, rows })
7312 }
7313
7314 #[allow(clippy::too_many_lines)]
7315 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7323 &self,
7324 stmt: &SelectStatement,
7325 primary: &TableRef,
7326 cancel: CancelToken<'_>,
7327 ) -> Result<QueryResult, EngineError> {
7328 let expr = primary
7329 .unnest_expr
7330 .as_deref()
7331 .expect("caller guards unnest_expr.is_some()");
7332 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7335 let ctx = EvalContext::new(&empty_schema, None);
7336 let dummy_row = Row::new(alloc::vec::Vec::new());
7337 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7340 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7341 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7342 Value::TextArray(items) => {
7343 let rows = items
7344 .into_iter()
7345 .map(|item| {
7346 Row::new(alloc::vec![match item {
7347 Some(s) => Value::Text(s),
7348 None => Value::Null,
7349 }])
7350 })
7351 .collect();
7352 (DataType::Text, rows)
7353 }
7354 Value::IntArray(items) => {
7355 let rows = items
7356 .into_iter()
7357 .map(|item| {
7358 Row::new(alloc::vec![match item {
7359 Some(n) => Value::Int(n),
7360 None => Value::Null,
7361 }])
7362 })
7363 .collect();
7364 (DataType::Int, rows)
7365 }
7366 Value::BigIntArray(items) => {
7367 let rows = items
7368 .into_iter()
7369 .map(|item| {
7370 Row::new(alloc::vec![match item {
7371 Some(n) => Value::BigInt(n),
7372 None => Value::Null,
7373 }])
7374 })
7375 .collect();
7376 (DataType::BigInt, rows)
7377 }
7378 other => {
7379 return Err(EngineError::Unsupported(alloc::format!(
7380 "unnest() expects an array argument, got {:?}",
7381 other.data_type()
7382 )));
7383 }
7384 };
7385 let alias = primary
7386 .alias
7387 .clone()
7388 .unwrap_or_else(|| "unnest".to_string());
7389 let col_name = primary
7395 .unnest_column_aliases
7396 .first()
7397 .cloned()
7398 .unwrap_or_else(|| alias.clone());
7399 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7400 let schema_cols = alloc::vec![col_schema.clone()];
7401 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7402 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7404 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7405 for row in rows {
7406 cancel.check()?;
7407 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7408 if matches!(v, Value::Bool(true)) {
7409 out.push(row);
7410 }
7411 }
7412 out
7413 } else {
7414 rows
7415 };
7416 if aggregate::uses_aggregate(stmt) {
7422 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7423 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7424 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7425 return Ok(QueryResult::Rows {
7426 columns: agg.columns,
7427 rows: agg.rows,
7428 });
7429 }
7430 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7432 let mut projected_rows: alloc::vec::Vec<Row> =
7433 alloc::vec::Vec::with_capacity(filtered.len());
7434 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7443 if let Some(srf_idx) = srf_position {
7444 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7445 .expect("checked by is_top_level_unnest above");
7446 for row in &filtered {
7447 let arr_val =
7448 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7449 let elements = array_value_to_elements(&arr_val)?;
7450 for elem in elements {
7454 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7455 for (i, p) in projection.iter().enumerate() {
7456 if i == srf_idx {
7457 vals.push(elem.clone());
7458 } else {
7459 vals.push(
7460 eval::eval_expr(&p.expr, row, &scan_ctx)
7461 .map_err(EngineError::Eval)?,
7462 );
7463 }
7464 }
7465 projected_rows.push(Row::new(vals));
7466 }
7467 }
7468 } else {
7469 for row in &filtered {
7470 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7471 for p in &projection {
7472 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7473 }
7474 projected_rows.push(Row::new(vals));
7475 }
7476 }
7477 let columns: alloc::vec::Vec<ColumnSchema> = projection
7480 .iter()
7481 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7482 .collect();
7483 if !stmt.order_by.is_empty() {
7486 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7487 .iter()
7488 .enumerate()
7489 .map(|(i, r)| -> Result<_, EngineError> {
7490 let keys: Result<Vec<Value>, EngineError> = stmt
7491 .order_by
7492 .iter()
7493 .map(|ob| {
7494 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7495 })
7496 .collect();
7497 Ok((i, keys?))
7498 })
7499 .collect::<Result<_, _>>()?;
7500 indexed.sort_by(|a, b| {
7501 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7502 let mut cmp = value_cmp(ka, kb);
7503 if stmt.order_by[idx].desc {
7504 cmp = cmp.reverse();
7505 }
7506 if cmp != core::cmp::Ordering::Equal {
7507 return cmp;
7508 }
7509 }
7510 core::cmp::Ordering::Equal
7511 });
7512 projected_rows = indexed
7513 .into_iter()
7514 .map(|(i, _)| projected_rows[i].clone())
7515 .collect();
7516 }
7517 if let Some(offset) = stmt.offset_literal() {
7519 let off = (offset as usize).min(projected_rows.len());
7520 projected_rows.drain(..off);
7521 }
7522 if let Some(limit) = stmt.limit_literal() {
7523 projected_rows.truncate(limit as usize);
7524 }
7525 Ok(QueryResult::Rows {
7526 columns,
7527 rows: projected_rows,
7528 })
7529 }
7530
7531 fn exec_select_generate_series(
7542 &self,
7543 stmt: &SelectStatement,
7544 primary: &TableRef,
7545 cancel: CancelToken<'_>,
7546 ) -> Result<QueryResult, EngineError> {
7547 let args = primary
7548 .generate_series_args
7549 .as_ref()
7550 .expect("caller guards generate_series_args.is_some()");
7551 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7552 let ctx = EvalContext::new(&empty_schema, None);
7553 let dummy_row = Row::new(alloc::vec::Vec::new());
7554 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7555 for a in args {
7556 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7557 }
7558 let (elem_dtype, rows) = match arg_values.as_slice() {
7562 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7563 let interval_step = match step {
7564 Value::Interval { .. } => step.clone(),
7565 other => {
7566 return Err(EngineError::Unsupported(alloc::format!(
7567 "generate_series(timestamp, timestamp, …): \
7568 step must be INTERVAL, got {:?}",
7569 other.data_type()
7570 )));
7571 }
7572 };
7573 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7574 (DataType::Timestamp, rows)
7575 }
7576 [start, stop, step]
7577 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7578 {
7579 let s = value_to_i64(start);
7580 let e = value_to_i64(stop);
7581 let st = value_to_i64(step);
7582 let rows = generate_series_integers(s, e, st, &cancel)?;
7583 (DataType::BigInt, rows)
7584 }
7585 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7586 let s = value_to_i64(start);
7587 let e = value_to_i64(stop);
7588 let rows = generate_series_integers(s, e, 1, &cancel)?;
7589 (DataType::BigInt, rows)
7590 }
7591 _ => {
7592 return Err(EngineError::Unsupported(alloc::format!(
7593 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7594 argument shapes; got {:?}",
7595 arg_values
7596 .iter()
7597 .map(|v| v.data_type())
7598 .collect::<alloc::vec::Vec<_>>()
7599 )));
7600 }
7601 };
7602 let alias = primary
7603 .alias
7604 .clone()
7605 .unwrap_or_else(|| "generate_series".to_string());
7606 let col_name = alias.clone();
7607 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7608 let schema_cols = alloc::vec![col_schema.clone()];
7609 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7610 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7612 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7613 for row in rows {
7614 cancel.check()?;
7615 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7616 if matches!(v, Value::Bool(true)) {
7617 out.push(row);
7618 }
7619 }
7620 out
7621 } else {
7622 rows
7623 };
7624 if aggregate::uses_aggregate(stmt) {
7634 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7635 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7636 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7637 return Ok(QueryResult::Rows {
7638 columns: agg.columns,
7639 rows: agg.rows,
7640 });
7641 }
7642 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7644 let mut projected_rows: alloc::vec::Vec<Row> =
7645 alloc::vec::Vec::with_capacity(filtered.len());
7646 for row in &filtered {
7647 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7648 for p in &projection {
7649 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7650 }
7651 projected_rows.push(Row::new(vals));
7652 }
7653 let columns: alloc::vec::Vec<ColumnSchema> = projection
7654 .iter()
7655 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7656 .collect();
7657 if !stmt.order_by.is_empty() {
7659 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7660 .iter()
7661 .enumerate()
7662 .map(|(i, r)| -> Result<_, EngineError> {
7663 let keys: Result<Vec<Value>, EngineError> = stmt
7664 .order_by
7665 .iter()
7666 .map(|ob| {
7667 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7668 })
7669 .collect();
7670 Ok((i, keys?))
7671 })
7672 .collect::<Result<_, _>>()?;
7673 indexed.sort_by(|a, b| {
7674 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7675 let mut cmp = value_cmp(ka, kb);
7676 if stmt.order_by[idx].desc {
7677 cmp = cmp.reverse();
7678 }
7679 if cmp != core::cmp::Ordering::Equal {
7680 return cmp;
7681 }
7682 }
7683 core::cmp::Ordering::Equal
7684 });
7685 projected_rows = indexed
7686 .into_iter()
7687 .map(|(i, _)| projected_rows[i].clone())
7688 .collect();
7689 }
7690 if let Some(offset) = stmt.offset_literal() {
7691 let off = (offset as usize).min(projected_rows.len());
7692 projected_rows.drain(..off);
7693 }
7694 if let Some(limit) = stmt.limit_literal() {
7695 projected_rows.truncate(limit as usize);
7696 }
7697 Ok(QueryResult::Rows {
7698 columns,
7699 rows: projected_rows,
7700 })
7701 }
7702
7703 fn exec_bare_select_cancel(
7704 &self,
7705 stmt: &SelectStatement,
7706 cancel: CancelToken<'_>,
7707 ) -> Result<QueryResult, EngineError> {
7708 check_with_ties_requires_order_by(stmt)?;
7713 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7721 return self.exec_select_with_meta_views(stmt, cancel);
7722 }
7723 if select_has_window(stmt) {
7728 return self.exec_select_with_window(stmt, cancel);
7729 }
7730 let Some(from) = &stmt.from else {
7735 let empty_schema: Vec<ColumnSchema> = Vec::new();
7736 let ctx = self.ev_ctx(&empty_schema, None);
7737 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7738 let dummy_row = Row::new(Vec::new());
7739 let mut values = Vec::with_capacity(projection.len());
7740 for p in &projection {
7741 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7742 }
7743 let columns: Vec<ColumnSchema> = projection
7744 .into_iter()
7745 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7746 .collect();
7747 return Ok(QueryResult::Rows {
7748 columns,
7749 rows: alloc::vec![Row::new(values)],
7750 });
7751 };
7752 if !from.joins.is_empty() {
7756 return self.exec_joined_select(stmt, from);
7757 }
7758 if from.primary.unnest_expr.is_some() {
7765 return self.exec_select_unnest(stmt, &from.primary, cancel);
7766 }
7767 if from.primary.generate_series_args.is_some() {
7773 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7774 }
7775 let primary = &from.primary;
7776 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7777 StorageError::TableNotFound {
7778 name: primary.name.clone(),
7779 }
7780 })?;
7781 let schema_cols = &table.schema().columns;
7782 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7785 let ctx = self.ev_ctx(schema_cols, Some(alias));
7786
7787 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7792 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7793 }
7794
7795 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7803 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7806 .or_else(|| {
7807 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7813 })
7814 .or_else(|| {
7815 try_trgm_seek(w, schema_cols, table, alias)
7821 })
7822 });
7823
7824 if aggregate::uses_aggregate(stmt) {
7827 let mut filtered: Vec<&Row> = Vec::new();
7828 let mut memo = memoize::MemoizeCache::new();
7832 if let Some(rows) = &indexed_rows {
7833 for cow in rows {
7834 let row = cow.as_ref();
7835 if let Some(where_expr) = &stmt.where_ {
7836 let cond = self.eval_expr_with_correlated(
7837 where_expr,
7838 row,
7839 &ctx,
7840 cancel,
7841 Some(&mut memo),
7842 )?;
7843 if !matches!(cond, Value::Bool(true)) {
7844 continue;
7845 }
7846 }
7847 filtered.push(row);
7848 }
7849 } else {
7850 for i in 0..table.row_count() {
7851 let row = &table.rows()[i];
7852 if let Some(where_expr) = &stmt.where_ {
7853 let cond = self.eval_expr_with_correlated(
7854 where_expr,
7855 row,
7856 &ctx,
7857 cancel,
7858 Some(&mut memo),
7859 )?;
7860 if !matches!(cond, Value::Bool(true)) {
7861 continue;
7862 }
7863 }
7864 filtered.push(row);
7865 }
7866 }
7867 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7868 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7869 return Ok(QueryResult::Rows {
7870 columns: agg.columns,
7871 rows: agg.rows,
7872 });
7873 }
7874
7875 let projection = build_projection(&stmt.items, schema_cols, alias)?;
7876 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7884
7885 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
7888 let mut memo = memoize::MemoizeCache::new();
7890 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
7893 if loop_idx.is_multiple_of(256) {
7894 cancel.check()?;
7895 }
7896 if let Some(where_expr) = &stmt.where_ {
7897 let cond =
7898 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
7899 if !matches!(cond, Value::Bool(true)) {
7900 return Ok(());
7901 }
7902 }
7903 let order_keys = if stmt.order_by.is_empty() {
7904 Vec::new()
7905 } else {
7906 build_order_keys(&stmt.order_by, row, &ctx)?
7907 };
7908 if let Some(srf_idx) = srf_position {
7909 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7910 .expect("checked by is_top_level_unnest above");
7911 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
7912 let elements = array_value_to_elements(&arr_val)?;
7913 for elem in elements {
7914 let mut values = Vec::with_capacity(projection.len());
7915 for (i, p) in projection.iter().enumerate() {
7916 if i == srf_idx {
7917 values.push(elem.clone());
7918 } else {
7919 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7920 }
7921 }
7922 tagged.push((order_keys.clone(), Row::new(values)));
7923 }
7924 } else {
7925 let mut values = Vec::with_capacity(projection.len());
7926 for p in &projection {
7927 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7928 }
7929 tagged.push((order_keys, Row::new(values)));
7930 }
7931 Ok(())
7932 };
7933 if let Some(rows) = &indexed_rows {
7934 for (loop_idx, cow) in rows.iter().enumerate() {
7935 process_row(cow.as_ref(), loop_idx)?;
7936 }
7937 } else {
7938 for i in 0..table.row_count() {
7939 process_row(&table.rows()[i], i)?;
7940 }
7941 }
7942
7943 if !stmt.order_by.is_empty() {
7944 let keep = if stmt.distinct || stmt.limit_with_ties {
7952 None
7953 } else {
7954 stmt.limit_literal()
7955 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
7956 };
7957 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7958 partial_sort_tagged(&mut tagged, keep, &descs);
7959 }
7960
7961 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
7971 apply_offset_and_limit_tagged(
7972 &mut tagged,
7973 stmt.offset_literal(),
7974 stmt.limit_literal(),
7975 true,
7976 );
7977 tagged.into_iter().map(|(_, r)| r).collect()
7978 } else {
7979 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
7980 if stmt.distinct {
7981 output_rows = dedup_rows(output_rows);
7982 }
7983 apply_offset_and_limit(
7984 &mut output_rows,
7985 stmt.offset_literal(),
7986 stmt.limit_literal(),
7987 );
7988 output_rows
7989 };
7990
7991 let columns: Vec<ColumnSchema> = projection
7992 .into_iter()
7993 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7994 .collect();
7995
7996 Ok(QueryResult::Rows {
7997 columns,
7998 rows: output_rows,
7999 })
8000 }
8001
8002 #[allow(clippy::too_many_lines)]
8009 fn materialise_table_ref(
8017 &self,
8018 tref: &TableRef,
8019 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8020 if let Some(expr) = tref.unnest_expr.as_deref() {
8021 let empty_schema: Vec<ColumnSchema> = Vec::new();
8022 let ctx = EvalContext::new(&empty_schema, None);
8023 let dummy_row = Row::new(Vec::new());
8024 let (elem_dtype, rows) =
8025 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8026 Value::Null => (DataType::Text, Vec::new()),
8027 Value::TextArray(items) => (
8028 DataType::Text,
8029 items
8030 .into_iter()
8031 .map(|item| {
8032 Row::new(alloc::vec![match item {
8033 Some(s) => Value::Text(s),
8034 None => Value::Null,
8035 }])
8036 })
8037 .collect(),
8038 ),
8039 Value::IntArray(items) => (
8040 DataType::Int,
8041 items
8042 .into_iter()
8043 .map(|item| {
8044 Row::new(alloc::vec![match item {
8045 Some(n) => Value::Int(n),
8046 None => Value::Null,
8047 }])
8048 })
8049 .collect(),
8050 ),
8051 Value::BigIntArray(items) => (
8052 DataType::BigInt,
8053 items
8054 .into_iter()
8055 .map(|item| {
8056 Row::new(alloc::vec![match item {
8057 Some(n) => Value::BigInt(n),
8058 None => Value::Null,
8059 }])
8060 })
8061 .collect(),
8062 ),
8063 other => {
8064 return Err(EngineError::Unsupported(alloc::format!(
8065 "unnest() expects an array argument, got {:?}",
8066 other.data_type()
8067 )));
8068 }
8069 };
8070 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8071 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8072 return Ok((
8073 rows,
8074 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8075 ));
8076 }
8077 let table =
8078 self.active_catalog()
8079 .get(&tref.name)
8080 .ok_or_else(|| StorageError::TableNotFound {
8081 name: tref.name.clone(),
8082 })?;
8083 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8084 let cols = table.schema().columns.clone();
8085 Ok((rows, cols))
8086 }
8087
8088 fn build_joined_filtered_rows(
8100 &self,
8101 from: &FromClause,
8102 where_: Option<&Expr>,
8103 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8104 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8105 let primary_alias = from
8106 .primary
8107 .alias
8108 .as_deref()
8109 .unwrap_or(from.primary.name.as_str())
8110 .to_string();
8111 #[allow(clippy::type_complexity)]
8118 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8119 for j in &from.joins {
8120 let a = j
8121 .table
8122 .alias
8123 .as_deref()
8124 .unwrap_or(j.table.name.as_str())
8125 .to_string();
8126 if let Some(inner_box) = &j.table.lateral_subquery {
8127 let schema = self.lateral_probe_schema(inner_box)?;
8132 joined.push(JoinedPeer {
8133 eager_rows: None,
8134 cols: schema,
8135 alias: a,
8136 kind: j.kind,
8137 on: j.on.as_ref(),
8138 lateral: Some(inner_box.as_ref()),
8139 });
8140 } else {
8141 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8142 joined.push(JoinedPeer {
8143 eager_rows: Some(rows),
8144 cols,
8145 alias: a,
8146 kind: j.kind,
8147 on: j.on.as_ref(),
8148 lateral: None,
8149 });
8150 }
8151 }
8152 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8153 for col in &primary_cols {
8154 combined_schema.push(ColumnSchema::new(
8155 alloc::format!("{primary_alias}.{}", col.name),
8156 col.ty,
8157 col.nullable,
8158 ));
8159 }
8160 for peer in &joined {
8161 for col in &peer.cols {
8162 combined_schema.push(ColumnSchema::new(
8163 alloc::format!("{}.{}", peer.alias, col.name),
8164 col.ty,
8165 col.nullable,
8166 ));
8167 }
8168 }
8169 let ctx = EvalContext::new(&combined_schema, None);
8170 let mut working: Vec<Row> = primary_rows;
8171 let mut consumed_cols = primary_cols.len();
8174 for peer in &joined {
8175 let right_arity = peer.cols.len();
8176 let mut next: Vec<Row> = Vec::new();
8177 for left in &working {
8178 let mut left_matched = false;
8179 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8180 Some(inner) => {
8181 let outer_schema = &combined_schema[..consumed_cols];
8185 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8186 alloc::borrow::Cow::Owned(rows)
8187 }
8188 None => {
8189 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8190 alloc::borrow::Cow::Borrowed(r.as_slice())
8191 }
8192 };
8193 for right in per_left_rrows.as_ref() {
8194 let mut combined_vals = left.values.clone();
8195 combined_vals.extend(right.values.iter().cloned());
8196 let combined = Row::new(combined_vals);
8197 let keep = if let Some(on_expr) = peer.on {
8198 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8199 matches!(cond, Value::Bool(true))
8200 } else {
8201 true
8202 };
8203 if keep {
8204 next.push(combined);
8205 left_matched = true;
8206 }
8207 }
8208 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8209 let mut combined_vals = left.values.clone();
8210 for _ in 0..right_arity {
8211 combined_vals.push(Value::Null);
8212 }
8213 next.push(Row::new(combined_vals));
8214 }
8215 }
8216 working = next;
8217 consumed_cols += right_arity;
8218 debug_assert!(consumed_cols <= combined_schema.len());
8219 }
8220 let mut filtered: Vec<Row> = Vec::new();
8221 for row in working {
8222 if let Some(where_expr) = where_ {
8223 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8224 if !matches!(cond, Value::Bool(true)) {
8225 continue;
8226 }
8227 }
8228 filtered.push(row);
8229 }
8230 Ok((combined_schema, filtered))
8231 }
8232
8233 fn lateral_probe_schema(
8239 &self,
8240 inner: &SelectStatement,
8241 ) -> Result<Vec<ColumnSchema>, EngineError> {
8242 match self.execute_readonly_select_for_lateral_probe(inner) {
8252 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8253 _ => {
8259 let mut out: Vec<ColumnSchema> = Vec::new();
8260 for (i, item) in inner.items.iter().enumerate() {
8261 let name = match item {
8262 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8263 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8264 SelectItem::Wildcard => alloc::format!("col{i}"),
8265 };
8266 out.push(ColumnSchema::new(name, DataType::Text, true));
8267 }
8268 Ok(out)
8269 }
8270 }
8271 }
8272
8273 fn execute_readonly_select_for_lateral_probe(
8279 &self,
8280 inner: &SelectStatement,
8281 ) -> Result<QueryResult, EngineError> {
8282 self.exec_bare_select_cancel(inner, CancelToken::none())
8283 }
8284
8285 fn materialise_lateral_for_outer(
8291 &self,
8292 inner: &SelectStatement,
8293 outer_schema: &[ColumnSchema],
8294 outer_row: &Row,
8295 ) -> Result<Vec<Row>, EngineError> {
8296 let mut substituted = inner.clone();
8297 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8298 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8299 match result {
8300 QueryResult::Rows { rows, .. } => Ok(rows),
8301 _ => Err(EngineError::Unsupported(
8302 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8303 )),
8304 }
8305 }
8306
8307 fn exec_joined_select(
8308 &self,
8309 stmt: &SelectStatement,
8310 from: &FromClause,
8311 ) -> Result<QueryResult, EngineError> {
8312 let (combined_schema, filtered) =
8320 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8321 let ctx = EvalContext::new(&combined_schema, None);
8322 if aggregate::uses_aggregate(stmt) {
8325 let refs: Vec<&Row> = filtered.iter().collect();
8326 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8327 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8328 return Ok(QueryResult::Rows {
8329 columns: agg.columns,
8330 rows: agg.rows,
8331 });
8332 }
8333
8334 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8335 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8336 for row in &filtered {
8337 let mut values = Vec::with_capacity(projection.len());
8338 for p in &projection {
8339 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8340 }
8341 let order_keys = if stmt.order_by.is_empty() {
8342 Vec::new()
8343 } else {
8344 build_order_keys(&stmt.order_by, row, &ctx)?
8345 };
8346 tagged.push((order_keys, Row::new(values)));
8347 }
8348 if !stmt.order_by.is_empty() {
8349 let keep = if stmt.distinct {
8350 None
8351 } else {
8352 stmt.limit_literal()
8353 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8354 };
8355 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8356 partial_sort_tagged(&mut tagged, keep, &descs);
8357 }
8358 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8359 if stmt.distinct {
8360 output_rows = dedup_rows(output_rows);
8361 }
8362 apply_offset_and_limit(
8363 &mut output_rows,
8364 stmt.offset_literal(),
8365 stmt.limit_literal(),
8366 );
8367 let columns: Vec<ColumnSchema> = projection
8368 .into_iter()
8369 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8370 .collect();
8371 Ok(QueryResult::Rows {
8372 columns,
8373 rows: output_rows,
8374 })
8375 }
8376}
8377
8378#[derive(Debug, Clone)]
8381struct ProjectedItem {
8382 expr: Expr,
8383 output_name: String,
8384 ty: DataType,
8385 nullable: bool,
8386}
8387
8388fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8394 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8395 for r in rows {
8396 if !out.iter().any(|seen| seen == &r) {
8397 out.push(r);
8398 }
8399 }
8400 out
8401}
8402
8403fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8407 match v {
8408 Value::Null => Ok(f64::INFINITY),
8409 Value::SmallInt(n) => Ok(f64::from(*n)),
8410 Value::Int(n) => Ok(f64::from(*n)),
8411 Value::Date(d) => Ok(f64::from(*d)),
8412 #[allow(clippy::cast_precision_loss)]
8413 Value::Timestamp(t) => Ok(*t as f64),
8414 #[allow(clippy::cast_precision_loss)]
8417 Value::Time(us) => Ok(*us as f64),
8418 Value::Year(y) => Ok(f64::from(*y)),
8422 #[allow(clippy::cast_precision_loss)]
8427 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8428 #[allow(clippy::cast_precision_loss)]
8430 Value::Money(c) => Ok(*c as f64),
8431 Value::Range { .. } => Err(EngineError::Unsupported(
8434 "ORDER BY of a range value is not supported in v7.17.0".into(),
8435 )),
8436 Value::Hstore(_) => Err(EngineError::Unsupported(
8438 "ORDER BY of a hstore value is not supported".into(),
8439 )),
8440 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8442 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8443 ),
8444 #[allow(clippy::cast_precision_loss)]
8445 Value::Numeric { scaled, scale } => {
8446 let mut divisor = 1.0_f64;
8452 for _ in 0..*scale {
8453 divisor *= 10.0;
8454 }
8455 Ok((*scaled as f64) / divisor)
8456 }
8457 #[allow(clippy::cast_precision_loss)]
8458 Value::BigInt(n) => Ok(*n as f64),
8459 Value::Float(x) => Ok(*x),
8460 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8461 Value::Text(s) => {
8462 let mut key: u64 = 0;
8466 for &b in s.as_bytes().iter().take(8) {
8467 key = (key << 8) | u64::from(b);
8468 }
8469 #[allow(clippy::cast_precision_loss)]
8470 Ok(key as f64)
8471 }
8472 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8473 Err(EngineError::Unsupported(
8474 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8475 ))
8476 }
8477 Value::Interval { .. } => Err(EngineError::Unsupported(
8478 "ORDER BY of an INTERVAL is not supported in v2.11 \
8479 (months vs micros has no single canonical ordering)"
8480 .into(),
8481 )),
8482 Value::Json(_) => Err(EngineError::Unsupported(
8483 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8484 )),
8485 _ => Err(EngineError::Unsupported(
8489 "ORDER BY of this value type is not supported".into(),
8490 )),
8491 }
8492}
8493
8494fn try_nsw_knn(
8508 stmt: &SelectStatement,
8509 table: &Table,
8510 schema_cols: &[ColumnSchema],
8511 table_alias: &str,
8512) -> Option<Vec<usize>> {
8513 if stmt.distinct {
8514 return None;
8515 }
8516 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8517 if limit == 0 {
8518 return None;
8519 }
8520 if stmt.order_by.len() != 1 {
8524 return None;
8525 }
8526 let order = &stmt.order_by[0];
8527 if order.desc {
8531 return None;
8532 }
8533 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8534 return None;
8535 };
8536 let metric = match op {
8537 BinOp::L2Distance => spg_storage::NswMetric::L2,
8538 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8539 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8540 _ => return None,
8541 };
8542 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8544 (lhs.as_ref(), rhs.as_ref())
8545 else {
8546 return None;
8547 };
8548 if let Some(q) = &col.qualifier
8549 && q != table_alias
8550 {
8551 return None;
8552 }
8553 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8554 let query = literal_to_vector(literal)?;
8555 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8556 if let Some(where_expr) = &stmt.where_ {
8557 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8561 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8562 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8563 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8564 for i in candidates {
8565 let row = &table.rows()[i];
8566 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8567 if matches!(cond, Value::Bool(true)) {
8568 kept.push(i);
8569 if kept.len() >= limit {
8570 break;
8571 }
8572 }
8573 }
8574 Some(kept)
8575 } else {
8576 Some(spg_storage::nsw_query(
8577 table, &idx.name, &query, limit, metric,
8578 ))
8579 }
8580}
8581
8582const NSW_OVER_FETCH_FLOOR: usize = 32;
8586
8587fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8590 match e {
8591 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8592 Expr::Cast { expr, .. } => literal_to_vector(expr),
8593 _ => None,
8594 }
8595}
8596
8597fn materialise_in_order(
8601 stmt: &SelectStatement,
8602 table: &Table,
8603 schema_cols: &[ColumnSchema],
8604 table_alias: &str,
8605 ordered_rows: &[usize],
8606) -> Result<QueryResult, EngineError> {
8607 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8608 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8609 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8610 for &i in ordered_rows {
8611 let row = &table.rows()[i];
8612 let mut values = Vec::with_capacity(projection.len());
8613 for p in &projection {
8614 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8615 }
8616 output_rows.push(Row::new(values));
8617 }
8618 apply_offset_and_limit(
8619 &mut output_rows,
8620 stmt.offset_literal(),
8621 stmt.limit_literal(),
8622 );
8623 let columns: Vec<ColumnSchema> = projection
8624 .into_iter()
8625 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8626 .collect();
8627 Ok(QueryResult::Rows {
8628 columns,
8629 rows: output_rows,
8630 })
8631}
8632
8633fn try_index_seek_positions(
8646 where_expr: &Expr,
8647 schema_cols: &[ColumnSchema],
8648 table: &Table,
8649 table_alias: &str,
8650) -> Option<Vec<usize>> {
8651 if let Expr::Binary {
8652 lhs,
8653 op: BinOp::And,
8654 rhs,
8655 } = where_expr
8656 {
8657 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8658 return Some(p);
8659 }
8660 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8661 }
8662 let Expr::Binary {
8663 lhs,
8664 op: BinOp::Eq,
8665 rhs,
8666 } = where_expr
8667 else {
8668 return None;
8669 };
8670 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8671 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8672 let idx = table.index_on(col_pos)?;
8673 let key = IndexKey::from_value(&value)?;
8674 let locators = idx.lookup_eq(&key);
8675 let mut out = Vec::with_capacity(locators.len());
8676 for loc in locators {
8677 match *loc {
8678 spg_storage::RowLocator::Hot(i) => out.push(i),
8679 spg_storage::RowLocator::Cold { .. } => return None,
8680 }
8681 }
8682 Some(out)
8683}
8684
8685fn try_index_seek<'a>(
8686 where_expr: &Expr,
8687 schema_cols: &[ColumnSchema],
8688 catalog: &'a Catalog,
8689 table: &'a Table,
8690 table_alias: &str,
8691) -> Option<Vec<Cow<'a, Row>>> {
8692 if let Expr::Binary {
8699 lhs,
8700 op: BinOp::And,
8701 rhs,
8702 } = where_expr
8703 {
8704 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8707 return Some(rows);
8708 }
8709 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8710 }
8711 let Expr::Binary {
8712 lhs,
8713 op: BinOp::Eq,
8714 rhs,
8715 } = where_expr
8716 else {
8717 return None;
8718 };
8719 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8720 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8721 let idx = table.index_on(col_pos)?;
8722 let key = IndexKey::from_value(&value)?;
8723 let locators = idx.lookup_eq(&key);
8724 let table_name = table.schema().name.as_str();
8725 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8733 for loc in locators {
8734 match *loc {
8735 spg_storage::RowLocator::Hot(i) => {
8736 if let Some(row) = table.rows().get(i) {
8737 out.push(Cow::Borrowed(row));
8738 }
8739 }
8740 spg_storage::RowLocator::Cold { segment_id, .. } => {
8741 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8742 out.push(Cow::Owned(row));
8743 }
8744 }
8745 }
8746 }
8747 Some(out)
8748}
8749
8750fn try_gin_seek<'a>(
8769 where_expr: &Expr,
8770 schema_cols: &[ColumnSchema],
8771 catalog: &'a Catalog,
8772 table: &'a Table,
8773 table_alias: &str,
8774 ctx: &eval::EvalContext<'_>,
8775) -> Option<Vec<Cow<'a, Row>>> {
8776 if let Expr::Binary {
8777 lhs,
8778 op: BinOp::And,
8779 rhs,
8780 } = where_expr
8781 {
8782 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8783 return Some(rows);
8784 }
8785 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8786 }
8787 if let Expr::Binary {
8796 lhs,
8797 op: BinOp::Or,
8798 rhs,
8799 } = where_expr
8800 {
8801 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8802 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8803 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8804 out.extend(left);
8805 out.extend(right);
8806 return Some(out);
8807 }
8808 let Expr::Binary {
8809 lhs,
8810 op: BinOp::TsMatch,
8811 rhs,
8812 } = where_expr
8813 else {
8814 return None;
8815 };
8816 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8821 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8822 let idx = table
8829 .indices()
8830 .iter()
8831 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8832 let candidates = gin_query_candidates(idx, &query)?;
8833 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8835 for loc in candidates {
8836 match loc {
8837 spg_storage::RowLocator::Hot(i) => {
8838 if let Some(row) = table.rows().get(i) {
8839 out.push(Cow::Borrowed(row));
8840 }
8841 }
8842 spg_storage::RowLocator::Cold { .. } => {}
8849 }
8850 }
8851 Some(out)
8852}
8853
8854fn try_trgm_seek<'a>(
8870 where_expr: &Expr,
8871 schema_cols: &[ColumnSchema],
8872 table: &'a Table,
8873 table_alias: &str,
8874) -> Option<Vec<Cow<'a, Row>>> {
8875 if let Expr::Binary {
8876 lhs,
8877 op: BinOp::And,
8878 rhs,
8879 } = where_expr
8880 {
8881 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
8882 return Some(rows);
8883 }
8884 return try_trgm_seek(rhs, schema_cols, table, table_alias);
8885 }
8886 let Expr::Like { expr, pattern, .. } = where_expr else {
8892 return None;
8893 };
8894 let Expr::Column(c) = expr.as_ref() else {
8896 return None;
8897 };
8898 if let Some(q) = &c.qualifier
8899 && q != table_alias
8900 {
8901 return None;
8902 }
8903 let col_pos = schema_cols
8904 .iter()
8905 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
8906 let idx = table
8908 .indices()
8909 .iter()
8910 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
8911 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
8915 return None;
8916 };
8917 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
8918 let mut iter = trigrams.iter();
8921 let first = iter.next()?;
8922 let mut acc: Vec<spg_storage::RowLocator> = {
8923 let mut v = idx.gin_trgm_lookup(first).to_vec();
8924 v.sort_by_key(locator_sort_key);
8925 v.dedup_by_key(|l| locator_sort_key(l));
8926 v
8927 };
8928 for tri in iter {
8929 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
8930 next.sort_by_key(locator_sort_key);
8931 next.dedup_by_key(|l| locator_sort_key(l));
8932 let mut merged: Vec<spg_storage::RowLocator> =
8934 Vec::with_capacity(acc.len().min(next.len()));
8935 let (mut i, mut j) = (0usize, 0usize);
8936 while i < acc.len() && j < next.len() {
8937 let lk = locator_sort_key(&acc[i]);
8938 let rk = locator_sort_key(&next[j]);
8939 match lk.cmp(&rk) {
8940 core::cmp::Ordering::Less => i += 1,
8941 core::cmp::Ordering::Greater => j += 1,
8942 core::cmp::Ordering::Equal => {
8943 merged.push(acc[i]);
8944 i += 1;
8945 j += 1;
8946 }
8947 }
8948 }
8949 acc = merged;
8950 if acc.is_empty() {
8951 break;
8952 }
8953 }
8954 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
8955 for loc in acc {
8956 if let spg_storage::RowLocator::Hot(i) = loc
8957 && let Some(row) = table.rows().get(i)
8958 {
8959 out.push(Cow::Borrowed(row));
8960 }
8961 }
8963 Some(out)
8964}
8965
8966fn resolve_gin_col_query(
8972 col_side: &Expr,
8973 query_side: &Expr,
8974 schema_cols: &[ColumnSchema],
8975 table_alias: &str,
8976 ctx: &eval::EvalContext<'_>,
8977) -> Option<(usize, spg_storage::TsQueryAst)> {
8978 let column = match col_side {
8983 Expr::Column(c) => c,
8984 Expr::FunctionCall { name, args }
8985 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
8986 {
8987 if let Expr::Column(c) = args.last().unwrap() {
8991 c
8992 } else {
8993 return None;
8994 }
8995 }
8996 _ => return None,
8997 };
8998 let c = column;
8999 if let Some(q) = &c.qualifier
9000 && q != table_alias
9001 {
9002 return None;
9003 }
9004 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9005 let empty_row = Row::new(Vec::new());
9009 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9010 let Value::TsQuery(q) = v else { return None };
9011 Some((pos, q))
9012}
9013
9014fn gin_query_candidates(
9025 idx: &spg_storage::Index,
9026 query: &spg_storage::TsQueryAst,
9027) -> Option<Vec<spg_storage::RowLocator>> {
9028 use spg_storage::TsQueryAst;
9029 match query {
9030 TsQueryAst::Term { word, .. } => {
9031 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9032 v.sort_by_key(locator_sort_key);
9033 v.dedup_by_key(|l| locator_sort_key(l));
9034 Some(v)
9035 }
9036 TsQueryAst::And(l, r) => {
9037 let mut left = gin_query_candidates(idx, l)?;
9038 let mut right = gin_query_candidates(idx, r)?;
9039 left.sort_by_key(locator_sort_key);
9040 right.sort_by_key(locator_sort_key);
9041 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9043 let (mut i, mut j) = (0usize, 0usize);
9044 while i < left.len() && j < right.len() {
9045 let lk = locator_sort_key(&left[i]);
9046 let rk = locator_sort_key(&right[j]);
9047 match lk.cmp(&rk) {
9048 core::cmp::Ordering::Less => i += 1,
9049 core::cmp::Ordering::Greater => j += 1,
9050 core::cmp::Ordering::Equal => {
9051 out.push(left[i]);
9052 i += 1;
9053 j += 1;
9054 }
9055 }
9056 }
9057 Some(out)
9058 }
9059 TsQueryAst::Or(l, r) => {
9060 let mut out = gin_query_candidates(idx, l)?;
9061 out.extend(gin_query_candidates(idx, r)?);
9062 out.sort_by_key(locator_sort_key);
9063 out.dedup_by_key(|l| locator_sort_key(l));
9064 Some(out)
9065 }
9066 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9071 }
9072}
9073
9074fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9079 match *l {
9080 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9081 spg_storage::RowLocator::Cold {
9082 segment_id,
9083 page_offset,
9084 } => (1, u64::from(segment_id), u64::from(page_offset)),
9085 }
9086}
9087
9088fn try_pk_predicate(
9100 where_expr: &Expr,
9101 schema_cols: &[ColumnSchema],
9102 table_alias: &str,
9103) -> Option<(usize, IndexKey)> {
9104 let Expr::Binary {
9105 lhs,
9106 op: BinOp::Eq,
9107 rhs,
9108 } = where_expr
9109 else {
9110 return None;
9111 };
9112 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9113 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9114 let key = IndexKey::from_value(&value)?;
9115 Some((col_pos, key))
9116}
9117
9118fn resolve_col_literal_pair(
9119 col_side: &Expr,
9120 lit_side: &Expr,
9121 schema_cols: &[ColumnSchema],
9122 table_alias: &str,
9123) -> Option<(usize, Value)> {
9124 let Expr::Column(c) = col_side else {
9125 return None;
9126 };
9127 if let Some(q) = &c.qualifier
9128 && q != table_alias
9129 {
9130 return None;
9131 }
9132 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9133 let Expr::Literal(l) = lit_side else {
9134 return None;
9135 };
9136 let v = match l {
9137 Literal::Integer(n) => {
9138 if let Ok(small) = i32::try_from(*n) {
9139 Value::Int(small)
9140 } else {
9141 Value::BigInt(*n)
9142 }
9143 }
9144 Literal::Float(x) => Value::Float(*x),
9145 Literal::String(s) => Value::Text(s.clone()),
9146 Literal::Bool(b) => Value::Bool(*b),
9147 Literal::Null => Value::Null,
9148 Literal::Vector(_) | Literal::Interval { .. } => return None,
9151 };
9152 Some((pos, v))
9153}
9154
9155fn resolve_projection_column<'a>(
9160 c: &ColumnName,
9161 schema_cols: &'a [ColumnSchema],
9162 table_alias: &str,
9163) -> Result<&'a ColumnSchema, EngineError> {
9164 if let Some(q) = &c.qualifier {
9165 let composite = alloc::format!("{q}.{name}", name = c.name);
9166 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9167 return Ok(s);
9168 }
9169 if q == table_alias
9172 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9173 {
9174 return Ok(s);
9175 }
9176 let prefix = alloc::format!("{q}.");
9180 let qualifier_known =
9181 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9182 if !qualifier_known {
9183 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9184 qualifier: q.clone(),
9185 }));
9186 }
9187 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9188 name: c.name.clone(),
9189 }));
9190 }
9191 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9192 return Ok(s);
9193 }
9194 let suffix = alloc::format!(".{name}", name = c.name);
9195 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9196 let first = matches.next();
9197 let extra = matches.next();
9198 match (first, extra) {
9199 (Some(s), None) => Ok(s),
9200 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9201 detail: alloc::format!("ambiguous column reference: {}", c.name),
9202 })),
9203 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9204 name: c.name.clone(),
9205 })),
9206 }
9207}
9208
9209fn build_projection(
9210 items: &[SelectItem],
9211 schema_cols: &[ColumnSchema],
9212 table_alias: &str,
9213) -> Result<Vec<ProjectedItem>, EngineError> {
9214 let mut out = Vec::new();
9215 for item in items {
9216 match item {
9217 SelectItem::Wildcard => {
9218 for col in schema_cols {
9219 out.push(ProjectedItem {
9220 expr: Expr::Column(ColumnName {
9221 qualifier: None,
9222 name: col.name.clone(),
9223 }),
9224 output_name: col.name.clone(),
9225 ty: col.ty,
9226 nullable: col.nullable,
9227 });
9228 }
9229 }
9230 SelectItem::Expr { expr, alias } => {
9231 if let Expr::Column(c) = expr {
9238 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9239 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9240 out.push(ProjectedItem {
9241 expr: expr.clone(),
9242 output_name,
9243 ty: sch.ty,
9244 nullable: sch.nullable,
9245 });
9246 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9247 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9248 out.push(ProjectedItem {
9249 expr: expr.clone(),
9250 output_name,
9251 ty: shape.ty,
9252 nullable: shape.nullable,
9253 });
9254 } else {
9255 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9256 out.push(ProjectedItem {
9257 expr: expr.clone(),
9258 output_name,
9259 ty: DataType::Text,
9260 nullable: true,
9261 });
9262 }
9263 }
9264 }
9265 }
9266 Ok(out)
9267}
9268
9269fn numeric_from_integer(
9273 n: i128,
9274 precision: u8,
9275 scale: u8,
9276 col_name: &str,
9277) -> Result<Value, EngineError> {
9278 let factor = pow10_i128(scale);
9279 let scaled = n.checked_mul(factor).ok_or_else(|| {
9280 EngineError::Unsupported(alloc::format!(
9281 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9282 ))
9283 })?;
9284 check_precision(scaled, precision, col_name)?;
9285 Ok(Value::Numeric { scaled, scale })
9286}
9287
9288#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9291fn numeric_from_float(
9292 x: f64,
9293 precision: u8,
9294 scale: u8,
9295 col_name: &str,
9296) -> Result<Value, EngineError> {
9297 if !x.is_finite() {
9298 return Err(EngineError::Unsupported(alloc::format!(
9299 "cannot store non-finite float in NUMERIC column `{col_name}`"
9300 )));
9301 }
9302 let mut factor = 1.0_f64;
9303 for _ in 0..scale {
9304 factor *= 10.0;
9305 }
9306 let shifted = x * factor;
9311 let biased = if shifted >= 0.0 {
9312 shifted + 0.5
9313 } else {
9314 shifted - 0.5
9315 };
9316 if !(-1e38..=1e38).contains(&biased) {
9319 return Err(EngineError::Unsupported(alloc::format!(
9320 "value {x} overflows NUMERIC range for column `{col_name}`"
9321 )));
9322 }
9323 let scaled = biased as i128;
9324 check_precision(scaled, precision, col_name)?;
9325 Ok(Value::Numeric { scaled, scale })
9326}
9327
9328fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9335 let s = s.trim();
9336 if s.is_empty() {
9337 return None;
9338 }
9339 let (negative, rest) = match s.as_bytes()[0] {
9340 b'-' => (true, &s[1..]),
9341 b'+' => (false, &s[1..]),
9342 _ => (false, s),
9343 };
9344 if rest.is_empty() {
9345 return None;
9346 }
9347 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9351 return None;
9352 }
9353 let (int_part, frac_part) = match rest.find('.') {
9354 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9355 None => (rest, ""),
9356 };
9357 if int_part.is_empty() && frac_part.is_empty() {
9358 return None;
9359 }
9360 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9361 return None;
9362 }
9363 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9364 return None;
9365 }
9366 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9367 if scale_u32 > u32::from(u8::MAX) {
9368 return None;
9369 }
9370 let scale = scale_u32 as u8;
9371 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9372 if negative {
9373 digits.push('-');
9374 }
9375 digits.push_str(int_part);
9376 digits.push_str(frac_part);
9377 let digits = if digits == "-" {
9379 return None;
9380 } else if digits.is_empty() {
9381 "0"
9382 } else {
9383 digits.as_str()
9384 };
9385 let mantissa: i128 = digits.parse().ok()?;
9386 Some((mantissa, scale))
9387}
9388
9389fn numeric_rescale(
9392 scaled: i128,
9393 src_scale: u8,
9394 precision: u8,
9395 dst_scale: u8,
9396 col_name: &str,
9397) -> Result<Value, EngineError> {
9398 let new_scaled = if dst_scale >= src_scale {
9399 let bump = pow10_i128(dst_scale - src_scale);
9400 scaled.checked_mul(bump).ok_or_else(|| {
9401 EngineError::Unsupported(alloc::format!(
9402 "overflow rescaling NUMERIC for column `{col_name}`"
9403 ))
9404 })?
9405 } else {
9406 let drop = pow10_i128(src_scale - dst_scale);
9407 let half = drop / 2;
9408 if scaled >= 0 {
9409 (scaled + half) / drop
9410 } else {
9411 (scaled - half) / drop
9412 }
9413 };
9414 check_precision(new_scaled, precision, col_name)?;
9415 Ok(Value::Numeric {
9416 scaled: new_scaled,
9417 scale: dst_scale,
9418 })
9419}
9420
9421const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9424 if scale == 0 {
9425 return scaled;
9426 }
9427 let factor = pow10_i128_const(scale);
9428 scaled / factor
9429}
9430
9431fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9435 if precision == 0 {
9436 return Ok(());
9437 }
9438 let limit = pow10_i128(precision);
9439 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9440 return Err(EngineError::Unsupported(alloc::format!(
9441 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9442 )));
9443 }
9444 Ok(())
9445}
9446
9447const fn pow10_i128_const(p: u8) -> i128 {
9448 let mut acc: i128 = 1;
9449 let mut i = 0;
9450 while i < p {
9451 acc *= 10;
9452 i += 1;
9453 }
9454 acc
9455}
9456
9457fn pow10_i128(p: u8) -> i128 {
9458 pow10_i128_const(p)
9459}
9460
9461impl Engine {
9476 #[allow(
9487 clippy::too_many_lines,
9488 clippy::type_complexity,
9489 clippy::needless_range_loop
9490 )] fn exec_select_with_window(
9492 &self,
9493 stmt: &SelectStatement,
9494 cancel: CancelToken<'_>,
9495 ) -> Result<QueryResult, EngineError> {
9496 let from = stmt.from.as_ref().ok_or_else(|| {
9497 EngineError::Unsupported("window functions require a FROM clause".into())
9498 })?;
9499 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9508 let filtered: Vec<Row>;
9509 if from.joins.is_empty() {
9510 let primary = &from.primary;
9511 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9512 StorageError::TableNotFound {
9513 name: primary.name.clone(),
9514 }
9515 })?;
9516 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9517 schema_cols_owned = table.schema().columns.clone();
9518 alias_opt = Some(alias);
9519 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9524 let mut owned: Vec<Row> = Vec::new();
9525 for (i, row) in table.rows().iter().enumerate() {
9526 if i.is_multiple_of(256) {
9527 cancel.check()?;
9528 }
9529 if let Some(w) = &stmt.where_ {
9530 let cond = eval::eval_expr(w, row, &ctx)?;
9531 if !matches!(cond, Value::Bool(true)) {
9532 continue;
9533 }
9534 }
9535 owned.push(row.clone());
9536 }
9537 filtered = owned;
9538 } else {
9539 let (combined_schema, rows) =
9540 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9541 schema_cols_owned = combined_schema;
9542 alias_opt = None;
9543 filtered = rows;
9544 }
9545 let schema_cols = &schema_cols_owned;
9546 let ctx = self.ev_ctx(schema_cols, alias_opt);
9547 let alias = alias_opt.unwrap_or("");
9548 let n_rows = filtered.len();
9549 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9553
9554 let mut window_nodes: Vec<Expr> = Vec::new();
9556 for item in &stmt.items {
9557 if let SelectItem::Expr { expr, .. } = item {
9558 collect_window_nodes(expr, &mut window_nodes);
9559 }
9560 }
9561
9562 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9565 for wnode in &window_nodes {
9566 let Expr::WindowFunction {
9567 name,
9568 args,
9569 partition_by,
9570 order_by,
9571 frame,
9572 null_treatment,
9573 } = wnode
9574 else {
9575 unreachable!("collect_window_nodes pushes only WindowFunction");
9576 };
9577 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9579 Vec::with_capacity(n_rows);
9580 for (i, row) in filtered.iter().enumerate() {
9581 let pkey: Vec<Value> = partition_by
9582 .iter()
9583 .map(|p| eval::eval_expr(p, row, &ctx))
9584 .collect::<Result<_, _>>()?;
9585 let okey: Vec<(Value, bool)> = order_by
9586 .iter()
9587 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9588 .collect::<Result<_, _>>()?;
9589 indexed.push((pkey, okey, i));
9590 }
9591 indexed.sort_by(|a, b| {
9594 let p_cmp = partition_key_cmp(&a.0, &b.0);
9595 if p_cmp != core::cmp::Ordering::Equal {
9596 return p_cmp;
9597 }
9598 order_key_cmp(&a.1, &b.1)
9599 });
9600 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9602 let mut p_start = 0;
9603 while p_start < indexed.len() {
9604 let mut p_end = p_start + 1;
9605 while p_end < indexed.len()
9606 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9607 == core::cmp::Ordering::Equal
9608 {
9609 p_end += 1;
9610 }
9611 compute_window_partition(
9613 name,
9614 args,
9615 !order_by.is_empty(),
9616 frame.as_ref(),
9617 *null_treatment,
9618 &indexed[p_start..p_end],
9619 &filtered_refs,
9620 &ctx,
9621 &mut out_vals,
9622 )?;
9623 p_start = p_end;
9624 }
9625 win_vals.push(out_vals);
9626 }
9627
9628 let mut ext_cols = schema_cols.clone();
9630 for i in 0..window_nodes.len() {
9631 ext_cols.push(ColumnSchema::new(
9632 alloc::format!("__win_{i}"),
9633 DataType::Text, true,
9635 ));
9636 }
9637 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9639 for i in 0..n_rows {
9640 let mut values = filtered[i].values.clone();
9641 for w in 0..window_nodes.len() {
9642 values.push(win_vals[w][i].clone());
9643 }
9644 ext_rows.push(Row::new(values));
9645 }
9646 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9648 for item in &stmt.items {
9649 let new_item = match item {
9650 SelectItem::Wildcard => SelectItem::Wildcard,
9651 SelectItem::Expr { expr, alias } => {
9652 let mut e = expr.clone();
9653 rewrite_window_to_columns(&mut e, &window_nodes);
9654 SelectItem::Expr {
9655 expr: e,
9656 alias: alias.clone(),
9657 }
9658 }
9659 };
9660 rewritten_items.push(new_item);
9661 }
9662
9663 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9669 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9670 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9671 for (i, row) in ext_rows.iter().enumerate() {
9672 if i.is_multiple_of(256) {
9673 cancel.check()?;
9674 }
9675 let mut values = Vec::with_capacity(projection.len());
9676 for p in &projection {
9677 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9678 }
9679 let order_keys = if stmt.order_by.is_empty() {
9680 Vec::new()
9681 } else {
9682 let mut keys = Vec::with_capacity(stmt.order_by.len());
9683 for o in &stmt.order_by {
9684 let mut e = o.expr.clone();
9685 rewrite_window_to_columns(&mut e, &window_nodes);
9686 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9687 keys.push(value_to_order_key(&key)?);
9688 }
9689 keys
9690 };
9691 tagged.push((order_keys, Row::new(values)));
9692 }
9693 if !stmt.order_by.is_empty() {
9695 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9696 sort_by_keys(&mut tagged, &descs);
9697 }
9698 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9699 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9700 let final_cols: Vec<ColumnSchema> = projection
9701 .into_iter()
9702 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9703 .collect();
9704 Ok(QueryResult::Rows {
9705 columns: final_cols,
9706 rows: out_rows,
9707 })
9708 }
9709
9710 fn exec_select_with_meta_views(
9727 &self,
9728 stmt: &SelectStatement,
9729 cancel: CancelToken<'_>,
9730 ) -> Result<QueryResult, EngineError> {
9731 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9732 collect_meta_view_names(stmt, &mut needed);
9733 let mut catalog = self.active_catalog().clone();
9734 for view in &needed {
9735 if catalog.get(view).is_some() {
9736 continue;
9737 }
9738 match view.as_str() {
9739 "__spg_info_columns" => {
9740 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9741 materialise_meta_view(&mut catalog, view, schema, rows)?;
9742 }
9743 "__spg_info_tables" => {
9744 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9745 materialise_meta_view(&mut catalog, view, schema, rows)?;
9746 }
9747 "__spg_pg_class" => {
9748 let (schema, rows) = synth_pg_class(self.active_catalog());
9749 materialise_meta_view(&mut catalog, view, schema, rows)?;
9750 }
9751 "__spg_pg_attribute" => {
9752 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9753 materialise_meta_view(&mut catalog, view, schema, rows)?;
9754 }
9755 "__spg_pg_type" => {
9758 let (schema, rows) = synth_pg_type(self.active_catalog());
9759 materialise_meta_view(&mut catalog, view, schema, rows)?;
9760 }
9761 "__spg_pg_proc" => {
9764 let (schema, rows) = synth_pg_proc(self.active_catalog());
9765 materialise_meta_view(&mut catalog, view, schema, rows)?;
9766 }
9767 "__spg_pg_namespace" => {
9770 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9771 materialise_meta_view(&mut catalog, view, schema, rows)?;
9772 }
9773 "__spg_pg_indexes" => {
9776 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9777 materialise_meta_view(&mut catalog, view, schema, rows)?;
9778 }
9779 "__spg_pg_index" => {
9782 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9783 materialise_meta_view(&mut catalog, view, schema, rows)?;
9784 }
9785 "__spg_pg_constraint" => {
9788 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9789 materialise_meta_view(&mut catalog, view, schema, rows)?;
9790 }
9791 "__spg_pg_database" => {
9796 let (schema, rows) = synth_pg_database(self.active_catalog());
9797 materialise_meta_view(&mut catalog, view, schema, rows)?;
9798 }
9799 "__spg_pg_roles" | "__spg_pg_user" => {
9800 let (schema, rows) = synth_pg_roles(self);
9801 materialise_meta_view(&mut catalog, view, schema, rows)?;
9802 }
9803 "__spg_pg_views" => {
9807 let (schema, rows) = synth_pg_views(self.active_catalog());
9808 materialise_meta_view(&mut catalog, view, schema, rows)?;
9809 }
9810 "__spg_pg_matviews" => {
9814 let (schema, _) = synth_pg_views(self.active_catalog());
9815 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9816 }
9817 "__spg_pg_settings" => {
9819 let (schema, rows) = synth_pg_settings(self);
9820 materialise_meta_view(&mut catalog, view, schema, rows)?;
9821 }
9822 "__spg_info_key_column_usage" => {
9824 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9825 materialise_meta_view(&mut catalog, view, schema, rows)?;
9826 }
9827 "__spg_info_referential_constraints" => {
9829 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9830 materialise_meta_view(&mut catalog, view, schema, rows)?;
9831 }
9832 "__spg_info_statistics" => {
9834 let (schema, rows) = synth_info_statistics(self.active_catalog());
9835 materialise_meta_view(&mut catalog, view, schema, rows)?;
9836 }
9837 "__spg_info_routines" => {
9839 let (schema, rows) = synth_info_routines();
9840 materialise_meta_view(&mut catalog, view, schema, rows)?;
9841 }
9842 "__spg_mysql_user" => {
9844 let (schema, rows) = synth_mysql_user(self);
9845 materialise_meta_view(&mut catalog, view, schema, rows)?;
9846 }
9847 "__spg_mysql_db" => {
9848 let (schema, rows) = synth_mysql_db();
9849 materialise_meta_view(&mut catalog, view, schema, rows)?;
9850 }
9851 _ => {
9852 return Err(EngineError::Unsupported(alloc::format!(
9853 "meta view {view:?} is not yet materialisable; \
9854 v7.16.2 covers information_schema.columns / .tables \
9855 and pg_catalog.pg_class / pg_attribute; \
9856 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9857 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9858 pg_user / pg_views / pg_matviews / pg_settings"
9859 )));
9860 }
9861 }
9862 }
9863 let mut temp = Engine::restore(catalog);
9864 if let Some(c) = self.clock {
9865 temp = temp.with_clock(c);
9866 }
9867 if let Some(f) = self.salt_fn {
9868 temp = temp.with_salt_fn(f);
9869 }
9870 temp.meta_views_materialised = true;
9871 temp.exec_select_cancel(stmt, cancel)
9872 }
9873
9874 fn exec_with_ctes(
9875 &self,
9876 stmt: &SelectStatement,
9877 cancel: CancelToken<'_>,
9878 ) -> Result<QueryResult, EngineError> {
9879 cancel.check()?;
9880 let mut catalog = self.active_catalog().clone();
9881 for cte in &stmt.ctes {
9882 if catalog.get(&cte.name).is_some() {
9883 return Err(EngineError::Unsupported(alloc::format!(
9884 "CTE name {:?} shadows an existing table; rename the CTE",
9885 cte.name
9886 )));
9887 }
9888 let (columns, rows) = if cte.recursive {
9889 self.materialise_recursive_cte(cte, &catalog, cancel)?
9890 } else {
9891 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
9892 let QueryResult::Rows { columns, rows } = body_result else {
9893 return Err(EngineError::Unsupported(alloc::format!(
9894 "CTE {:?} body did not return rows",
9895 cte.name
9896 )));
9897 };
9898 (columns, rows)
9899 };
9900 let inferred = infer_column_types(&columns, &rows);
9905 let mut columns = inferred;
9906 if !cte.column_overrides.is_empty() {
9908 if cte.column_overrides.len() != columns.len() {
9909 return Err(EngineError::Unsupported(alloc::format!(
9910 "CTE {:?} column list has {} names but body returns {} columns",
9911 cte.name,
9912 cte.column_overrides.len(),
9913 columns.len()
9914 )));
9915 }
9916 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9917 col.name.clone_from(name);
9918 }
9919 }
9920 let schema = TableSchema::new(cte.name.clone(), columns);
9921 catalog.create_table(schema).map_err(EngineError::Storage)?;
9922 let table = catalog
9923 .get_mut(&cte.name)
9924 .expect("just-created CTE table must exist");
9925 for row in rows {
9926 table.insert(row).map_err(EngineError::Storage)?;
9927 }
9928 }
9929 let mut body = stmt.clone();
9932 body.ctes = Vec::new();
9933 let mut temp = Engine::restore(catalog);
9934 if let Some(c) = self.clock {
9935 temp = temp.with_clock(c);
9936 }
9937 if let Some(f) = self.salt_fn {
9938 temp = temp.with_salt_fn(f);
9939 }
9940 temp.exec_select_cancel(&body, cancel)
9941 }
9942
9943 #[allow(clippy::too_many_lines)]
9953 fn materialise_recursive_cte(
9954 &self,
9955 cte: &spg_sql::ast::Cte,
9956 base_catalog: &Catalog,
9957 cancel: CancelToken<'_>,
9958 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
9959 const MAX_TOTAL_ROWS: usize = 1_000_000;
9960 const MAX_ITERATIONS: usize = 100_000;
9961 cancel.check()?;
9962 if cte.body.unions.is_empty() {
9963 return Err(EngineError::Unsupported(alloc::format!(
9964 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
9965 cte.name
9966 )));
9967 }
9968 let mut anchor = cte.body.clone();
9970 let union_terms = core::mem::take(&mut anchor.unions);
9971 anchor.ctes = Vec::new();
9972 if select_refers_to(&anchor, &cte.name) {
9974 return Err(EngineError::Unsupported(alloc::format!(
9975 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
9976 cte.name
9977 )));
9978 }
9979 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
9980 let QueryResult::Rows {
9981 columns: anchor_cols,
9982 rows: anchor_rows,
9983 } = anchor_result
9984 else {
9985 return Err(EngineError::Unsupported(alloc::format!(
9986 "WITH RECURSIVE {:?}: anchor did not return rows",
9987 cte.name
9988 )));
9989 };
9990 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
9994 if !cte.column_overrides.is_empty() {
9995 if cte.column_overrides.len() != columns.len() {
9996 return Err(EngineError::Unsupported(alloc::format!(
9997 "CTE {:?} column list has {} names but anchor returns {} columns",
9998 cte.name,
9999 cte.column_overrides.len(),
10000 columns.len()
10001 )));
10002 }
10003 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10004 col.name.clone_from(name);
10005 }
10006 }
10007 let mut all_rows: Vec<Row> = anchor_rows.clone();
10008 let mut working_set: Vec<Row> = anchor_rows;
10009 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10010 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10013 if !all_union_all {
10014 for r in &all_rows {
10015 seen.insert(encode_row_key(r));
10016 }
10017 }
10018 for iter in 0..MAX_ITERATIONS {
10019 cancel.check()?;
10020 if working_set.is_empty() {
10021 break;
10022 }
10023 let mut iter_catalog = base_catalog.clone();
10025 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10026 iter_catalog
10027 .create_table(schema)
10028 .map_err(EngineError::Storage)?;
10029 {
10030 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10031 for row in &working_set {
10032 table.insert(row.clone()).map_err(EngineError::Storage)?;
10033 }
10034 }
10035 let mut iter_engine = Engine::restore(iter_catalog);
10036 if let Some(c) = self.clock {
10037 iter_engine = iter_engine.with_clock(c);
10038 }
10039 if let Some(f) = self.salt_fn {
10040 iter_engine = iter_engine.with_salt_fn(f);
10041 }
10042 let mut next_set: Vec<Row> = Vec::new();
10044 for (_, term) in &union_terms {
10045 let mut term = term.clone();
10046 term.ctes = Vec::new();
10047 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10048 let QueryResult::Rows {
10049 columns: rc,
10050 rows: rs,
10051 } = r
10052 else {
10053 return Err(EngineError::Unsupported(alloc::format!(
10054 "WITH RECURSIVE {:?}: recursive term did not return rows",
10055 cte.name
10056 )));
10057 };
10058 if rc.len() != columns.len() {
10059 return Err(EngineError::Unsupported(alloc::format!(
10060 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10061 cte.name,
10062 rc.len(),
10063 columns.len()
10064 )));
10065 }
10066 for row in rs {
10067 if !all_union_all {
10068 let key = encode_row_key(&row);
10069 if !seen.insert(key) {
10070 continue;
10071 }
10072 }
10073 next_set.push(row);
10074 }
10075 }
10076 if next_set.is_empty() {
10077 break;
10078 }
10079 all_rows.extend(next_set.iter().cloned());
10080 working_set = next_set;
10081 if all_rows.len() > MAX_TOTAL_ROWS {
10082 return Err(EngineError::Unsupported(alloc::format!(
10083 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10084 cte.name
10085 )));
10086 }
10087 if iter + 1 == MAX_ITERATIONS {
10088 return Err(EngineError::Unsupported(alloc::format!(
10089 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10090 cte.name
10091 )));
10092 }
10093 }
10094 Ok((columns, all_rows))
10095 }
10096
10097 fn resolve_select_subqueries(
10098 &self,
10099 stmt: &mut SelectStatement,
10100 cancel: CancelToken<'_>,
10101 ) -> Result<(), EngineError> {
10102 for item in &mut stmt.items {
10103 if let SelectItem::Expr { expr, .. } = item {
10104 self.resolve_expr_subqueries(expr, cancel)?;
10105 }
10106 }
10107 if let Some(w) = &mut stmt.where_ {
10108 self.resolve_expr_subqueries(w, cancel)?;
10109 }
10110 if let Some(gs) = &mut stmt.group_by {
10111 for g in gs {
10112 self.resolve_expr_subqueries(g, cancel)?;
10113 }
10114 }
10115 if let Some(h) = &mut stmt.having {
10116 self.resolve_expr_subqueries(h, cancel)?;
10117 }
10118 for o in &mut stmt.order_by {
10119 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10120 }
10121 for (_, peer) in &mut stmt.unions {
10122 self.resolve_select_subqueries(peer, cancel)?;
10123 }
10124 Ok(())
10125 }
10126
10127 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10129 &self,
10130 e: &mut Expr,
10131 cancel: CancelToken<'_>,
10132 ) -> Result<(), EngineError> {
10133 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10135 *e = replacement;
10136 return Ok(());
10137 }
10138 match e {
10139 Expr::Binary { lhs, rhs, .. } => {
10140 self.resolve_expr_subqueries(lhs, cancel)?;
10141 self.resolve_expr_subqueries(rhs, cancel)?;
10142 }
10143 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10144 self.resolve_expr_subqueries(expr, cancel)?;
10145 }
10146 Expr::FunctionCall { args, .. } => {
10147 for a in args {
10148 self.resolve_expr_subqueries(a, cancel)?;
10149 }
10150 }
10151 Expr::Like { expr, pattern, .. } => {
10152 self.resolve_expr_subqueries(expr, cancel)?;
10153 self.resolve_expr_subqueries(pattern, cancel)?;
10154 }
10155 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10156 Expr::WindowFunction {
10159 args,
10160 partition_by,
10161 order_by,
10162 ..
10163 } => {
10164 for a in args {
10165 self.resolve_expr_subqueries(a, cancel)?;
10166 }
10167 for p in partition_by {
10168 self.resolve_expr_subqueries(p, cancel)?;
10169 }
10170 for (e, _) in order_by {
10171 self.resolve_expr_subqueries(e, cancel)?;
10172 }
10173 }
10174 Expr::ScalarSubquery(_)
10178 | Expr::Exists { .. }
10179 | Expr::InSubquery { .. }
10180 | Expr::Literal(_)
10181 | Expr::Placeholder(_)
10182 | Expr::Column(_) => {}
10183 Expr::Array(items) => {
10185 for elem in items {
10186 self.resolve_expr_subqueries(elem, cancel)?;
10187 }
10188 }
10189 Expr::ArraySubscript { target, index } => {
10190 self.resolve_expr_subqueries(target, cancel)?;
10191 self.resolve_expr_subqueries(index, cancel)?;
10192 }
10193 Expr::AnyAll { expr, array, .. } => {
10194 self.resolve_expr_subqueries(expr, cancel)?;
10195 self.resolve_expr_subqueries(array, cancel)?;
10196 }
10197 Expr::Case {
10198 operand,
10199 branches,
10200 else_branch,
10201 } => {
10202 if let Some(o) = operand {
10203 self.resolve_expr_subqueries(o, cancel)?;
10204 }
10205 for (w, t) in branches {
10206 self.resolve_expr_subqueries(w, cancel)?;
10207 self.resolve_expr_subqueries(t, cancel)?;
10208 }
10209 if let Some(e) = else_branch {
10210 self.resolve_expr_subqueries(e, cancel)?;
10211 }
10212 }
10213 }
10214 Ok(())
10215 }
10216
10217 fn eval_expr_with_correlated(
10225 &self,
10226 expr: &Expr,
10227 row: &Row,
10228 ctx: &EvalContext<'_>,
10229 cancel: CancelToken<'_>,
10230 memo: Option<&mut memoize::MemoizeCache>,
10231 ) -> Result<Value, EngineError> {
10232 if !expr_has_subquery(expr) {
10233 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10234 }
10235 let mut e = expr.clone();
10236 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10237 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10238 }
10239
10240 fn resolve_correlated_in_expr(
10241 &self,
10242 e: &mut Expr,
10243 row: &Row,
10244 ctx: &EvalContext<'_>,
10245 cancel: CancelToken<'_>,
10246 mut memo: Option<&mut memoize::MemoizeCache>,
10247 ) -> Result<(), EngineError> {
10248 match e {
10249 Expr::ScalarSubquery(inner) => {
10250 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10255 subquery_repr: alloc::format!("{}", **inner),
10256 outer_values: row.values.clone(),
10257 });
10258 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10259 && let Some(cached) = cache.get(k)
10260 {
10261 *e = value_to_literal_expr(cached)?;
10262 return Ok(());
10263 }
10264 let mut s = (**inner).clone();
10265 substitute_outer_columns(&mut s, row, ctx);
10266 let r = self.exec_select_cancel(&s, cancel)?;
10267 let QueryResult::Rows { rows, .. } = r else {
10268 return Err(EngineError::Unsupported(
10269 "scalar subquery: inner did not return rows".into(),
10270 ));
10271 };
10272 let value = match rows.as_slice() {
10273 [] => Value::Null,
10274 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10275 _ => {
10276 return Err(EngineError::Unsupported(alloc::format!(
10277 "scalar subquery returned {} rows; expected 0 or 1",
10278 rows.len()
10279 )));
10280 }
10281 };
10282 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10283 cache.insert(k, value.clone());
10284 }
10285 *e = value_to_literal_expr(value)?;
10286 }
10287 Expr::Exists { subquery, negated } => {
10288 let mut s = (**subquery).clone();
10289 substitute_outer_columns(&mut s, row, ctx);
10290 let r = self.exec_select_cancel(&s, cancel)?;
10291 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10292 let bit = if *negated { !exists } else { exists };
10293 *e = Expr::Literal(Literal::Bool(bit));
10294 }
10295 Expr::InSubquery {
10296 expr: lhs,
10297 subquery,
10298 negated,
10299 } => {
10300 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10301 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10302 let mut s = (**subquery).clone();
10303 substitute_outer_columns(&mut s, row, ctx);
10304 let r = self.exec_select_cancel(&s, cancel)?;
10305 let QueryResult::Rows { columns, rows, .. } = r else {
10306 return Err(EngineError::Unsupported(
10307 "IN-subquery: inner did not return rows".into(),
10308 ));
10309 };
10310 if columns.len() != 1 {
10311 return Err(EngineError::Unsupported(alloc::format!(
10312 "IN-subquery must project exactly one column; got {}",
10313 columns.len()
10314 )));
10315 }
10316 let mut found = false;
10317 let mut any_null = false;
10318 for r0 in rows {
10319 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10320 if v.is_null() {
10321 any_null = true;
10322 continue;
10323 }
10324 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10325 found = true;
10326 break;
10327 }
10328 }
10329 let bit = if found {
10330 !*negated
10331 } else if any_null {
10332 return Err(EngineError::Unsupported(
10333 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10334 ));
10335 } else {
10336 *negated
10337 };
10338 *e = Expr::Literal(Literal::Bool(bit));
10339 }
10340 Expr::Binary { lhs, rhs, .. } => {
10341 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10342 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10343 }
10344 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10345 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10346 }
10347 Expr::Like { expr, pattern, .. } => {
10348 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10349 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10350 }
10351 Expr::FunctionCall { args, .. } => {
10352 for a in args {
10353 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10354 }
10355 }
10356 Expr::Extract { source, .. } => {
10357 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10358 }
10359 Expr::WindowFunction { .. }
10360 | Expr::Literal(_)
10361 | Expr::Placeholder(_)
10362 | Expr::Column(_) => {}
10363 Expr::Array(items) => {
10365 for elem in items {
10366 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10367 }
10368 }
10369 Expr::ArraySubscript { target, index } => {
10370 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10371 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10372 }
10373 Expr::AnyAll { expr, array, .. } => {
10374 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10375 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10376 }
10377 Expr::Case {
10378 operand,
10379 branches,
10380 else_branch,
10381 } => {
10382 if let Some(o) = operand {
10383 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10384 }
10385 for (w, t) in branches {
10386 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10387 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10388 }
10389 if let Some(e) = else_branch {
10390 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10391 }
10392 }
10393 }
10394 Ok(())
10395 }
10396
10397 fn subquery_replacement(
10398 &self,
10399 e: &Expr,
10400 cancel: CancelToken<'_>,
10401 ) -> Result<Option<Expr>, EngineError> {
10402 match e {
10403 Expr::ScalarSubquery(inner) => {
10404 let mut s = (**inner).clone();
10405 self.resolve_select_subqueries(&mut s, cancel)?;
10408 let r = match self.exec_bare_select_cancel(&s, cancel) {
10409 Ok(r) => r,
10410 Err(e) if is_correlation_error(&e) => return Ok(None),
10411 Err(e) => return Err(e),
10412 };
10413 let QueryResult::Rows { rows, .. } = r else {
10414 return Err(EngineError::Unsupported(
10415 "scalar subquery: inner statement did not return rows".into(),
10416 ));
10417 };
10418 let value = match rows.as_slice() {
10419 [] => Value::Null,
10420 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10421 _ => {
10422 return Err(EngineError::Unsupported(alloc::format!(
10423 "scalar subquery returned {} rows; expected 0 or 1",
10424 rows.len()
10425 )));
10426 }
10427 };
10428 Ok(Some(value_to_literal_expr(value)?))
10429 }
10430 Expr::Exists { subquery, negated } => {
10431 let mut s = (**subquery).clone();
10432 self.resolve_select_subqueries(&mut s, cancel)?;
10433 let r = match self.exec_bare_select_cancel(&s, cancel) {
10434 Ok(r) => r,
10435 Err(e) if is_correlation_error(&e) => return Ok(None),
10436 Err(e) => return Err(e),
10437 };
10438 let exists = match r {
10439 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10440 QueryResult::CommandOk { .. } => false,
10441 };
10442 let bit = if *negated { !exists } else { exists };
10443 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10444 }
10445 Expr::InSubquery {
10446 expr,
10447 subquery,
10448 negated,
10449 } => {
10450 let mut s = (**subquery).clone();
10451 self.resolve_select_subqueries(&mut s, cancel)?;
10452 let r = match self.exec_bare_select_cancel(&s, cancel) {
10453 Ok(r) => r,
10454 Err(e) if is_correlation_error(&e) => return Ok(None),
10455 Err(e) => return Err(e),
10456 };
10457 let QueryResult::Rows { columns, rows, .. } = r else {
10458 return Err(EngineError::Unsupported(
10459 "IN-subquery: inner statement did not return rows".into(),
10460 ));
10461 };
10462 if columns.len() != 1 {
10463 return Err(EngineError::Unsupported(alloc::format!(
10464 "IN-subquery must project exactly one column; got {}",
10465 columns.len()
10466 )));
10467 }
10468 let mut acc: Option<Expr> = None;
10471 for row in rows {
10472 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10473 let lit = value_to_literal_expr(v)?;
10474 let cmp = Expr::Binary {
10475 lhs: expr.clone(),
10476 op: BinOp::Eq,
10477 rhs: Box::new(lit),
10478 };
10479 acc = Some(match acc {
10480 None => cmp,
10481 Some(prev) => Expr::Binary {
10482 lhs: Box::new(prev),
10483 op: BinOp::Or,
10484 rhs: Box::new(cmp),
10485 },
10486 });
10487 }
10488 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10489 let final_expr = if *negated {
10490 Expr::Unary {
10491 op: UnOp::Not,
10492 expr: Box::new(combined),
10493 }
10494 } else {
10495 combined
10496 };
10497 Ok(Some(final_expr))
10498 }
10499 _ => Ok(None),
10500 }
10501 }
10502}
10503
10504fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10516 if let Some(from) = &stmt.from
10517 && from_refers_to(from, target)
10518 {
10519 return true;
10520 }
10521 for (_, peer) in &stmt.unions {
10522 if select_refers_to(peer, target) {
10523 return true;
10524 }
10525 }
10526 for item in &stmt.items {
10527 if let SelectItem::Expr { expr, .. } = item
10528 && expr_refers_to(expr, target)
10529 {
10530 return true;
10531 }
10532 }
10533 if let Some(w) = &stmt.where_
10534 && expr_refers_to(w, target)
10535 {
10536 return true;
10537 }
10538 false
10539}
10540
10541fn from_refers_to(from: &FromClause, target: &str) -> bool {
10542 if from.primary.name.eq_ignore_ascii_case(target) {
10543 return true;
10544 }
10545 from.joins
10546 .iter()
10547 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10548}
10549
10550fn expr_refers_to(e: &Expr, target: &str) -> bool {
10551 match e {
10552 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10553 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10554 select_refers_to(subquery, target)
10555 }
10556 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10557 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10558 expr_refers_to(expr, target)
10559 }
10560 Expr::Like { expr, pattern, .. } => {
10561 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10562 }
10563 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10564 Expr::Extract { source, .. } => expr_refers_to(source, target),
10565 Expr::WindowFunction {
10566 args,
10567 partition_by,
10568 order_by,
10569 ..
10570 } => {
10571 args.iter().any(|a| expr_refers_to(a, target))
10572 || partition_by.iter().any(|p| expr_refers_to(p, target))
10573 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10574 }
10575 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10576 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10577 Expr::ArraySubscript { target: t, index } => {
10578 expr_refers_to(t, target) || expr_refers_to(index, target)
10579 }
10580 Expr::AnyAll { expr, array, .. } => {
10581 expr_refers_to(expr, target) || expr_refers_to(array, target)
10582 }
10583 Expr::Case {
10584 operand,
10585 branches,
10586 else_branch,
10587 } => {
10588 operand
10589 .as_deref()
10590 .is_some_and(|o| expr_refers_to(o, target))
10591 || branches
10592 .iter()
10593 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10594 || else_branch
10595 .as_deref()
10596 .is_some_and(|e| expr_refers_to(e, target))
10597 }
10598 }
10599}
10600
10601fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10612 let s = match ty {
10613 DataType::Int => "integer",
10614 DataType::BigInt => "bigint",
10615 DataType::SmallInt => "smallint",
10616 DataType::Float => "double precision",
10617 DataType::Bool => "boolean",
10618 DataType::Text => "text",
10619 DataType::Varchar(_) => "character varying",
10620 DataType::Date => "date",
10621 DataType::Timestamp => "timestamp without time zone",
10622 DataType::Timestamptz => "timestamp with time zone",
10623 DataType::Json => "jsonb",
10624 DataType::Bytes => "bytea",
10625 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10626 DataType::TsVector => "tsvector",
10627 DataType::TsQuery => "tsquery",
10628 DataType::Vector { .. } => "USER-DEFINED",
10629 _ => "USER-DEFINED",
10632 };
10633 alloc::string::String::from(s)
10634}
10635
10636fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10643 let schema = alloc::vec![
10644 ColumnSchema::new("table_catalog", DataType::Text, false),
10645 ColumnSchema::new("table_schema", DataType::Text, false),
10646 ColumnSchema::new("table_name", DataType::Text, false),
10647 ColumnSchema::new("column_name", DataType::Text, false),
10648 ColumnSchema::new("ordinal_position", DataType::Int, false),
10649 ColumnSchema::new("is_nullable", DataType::Text, false),
10650 ColumnSchema::new("data_type", DataType::Text, false),
10651 ];
10652 let mut rows: Vec<Row> = Vec::new();
10653 for tname in cat.table_names() {
10654 let Some(t) = cat.get(&tname) else { continue };
10655 for (i, col) in t.schema().columns.iter().enumerate() {
10656 #[allow(clippy::cast_possible_wrap)]
10657 let ordinal = (i + 1) as i32;
10658 rows.push(Row::new(alloc::vec![
10659 Value::Text("spg".into()),
10660 Value::Text("public".into()),
10661 Value::Text(tname.clone()),
10662 Value::Text(col.name.clone()),
10663 Value::Int(ordinal),
10664 Value::Text(if col.nullable {
10665 "YES".into()
10666 } else {
10667 "NO".into()
10668 }),
10669 Value::Text(pg_data_type_text(col.ty)),
10670 ]));
10671 }
10672 }
10673 (schema, rows)
10674}
10675
10676fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10678 let schema = alloc::vec![
10679 ColumnSchema::new("table_catalog", DataType::Text, false),
10680 ColumnSchema::new("table_schema", DataType::Text, false),
10681 ColumnSchema::new("table_name", DataType::Text, false),
10682 ColumnSchema::new("table_type", DataType::Text, false),
10683 ];
10684 let mut rows: Vec<Row> = Vec::new();
10685 for tname in cat.table_names() {
10686 rows.push(Row::new(alloc::vec![
10687 Value::Text("spg".into()),
10688 Value::Text("public".into()),
10689 Value::Text(tname.clone()),
10690 Value::Text("BASE TABLE".into()),
10691 ]));
10692 }
10693 (schema, rows)
10694}
10695
10696fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10700 let schema = alloc::vec![
10701 ColumnSchema::new("relname", DataType::Text, false),
10702 ColumnSchema::new("relkind", DataType::Text, false),
10703 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10704 ];
10705 let mut rows: Vec<Row> = Vec::new();
10706 for tname in cat.table_names() {
10707 rows.push(Row::new(alloc::vec![
10708 Value::Text(tname.clone()),
10709 Value::Text("r".into()),
10710 Value::BigInt(2200), ]));
10712 }
10713 (schema, rows)
10714}
10715
10716fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10720 let schema = alloc::vec![
10721 ColumnSchema::new("attrelid", DataType::Text, false),
10722 ColumnSchema::new("attname", DataType::Text, false),
10723 ColumnSchema::new("attnum", DataType::Int, false),
10724 ColumnSchema::new("atttypid", DataType::Text, false),
10725 ColumnSchema::new("attnotnull", DataType::Bool, false),
10726 ];
10727 let mut rows: Vec<Row> = Vec::new();
10728 for tname in cat.table_names() {
10729 let Some(t) = cat.get(&tname) else { continue };
10730 for (i, col) in t.schema().columns.iter().enumerate() {
10731 #[allow(clippy::cast_possible_wrap)]
10732 let ordinal = (i + 1) as i32;
10733 rows.push(Row::new(alloc::vec![
10734 Value::Text(tname.clone()),
10735 Value::Text(col.name.clone()),
10736 Value::Int(ordinal),
10737 Value::Text(pg_data_type_text(col.ty)),
10738 Value::Bool(!col.nullable),
10739 ]));
10740 }
10741 }
10742 (schema, rows)
10743}
10744
10745fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10762 let schema = alloc::vec![
10763 ColumnSchema::new("oid", DataType::BigInt, false),
10764 ColumnSchema::new("typname", DataType::Text, false),
10765 ColumnSchema::new("typlen", DataType::SmallInt, false),
10766 ColumnSchema::new("typtype", DataType::Text, false),
10767 ColumnSchema::new("typcategory", DataType::Text, false),
10768 ColumnSchema::new("typelem", DataType::BigInt, false),
10769 ColumnSchema::new("typarray", DataType::BigInt, false),
10770 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10771 ];
10772 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10775 (16, "bool", 1, "b", "B", 0, 1000),
10777 (17, "bytea", -1, "b", "U", 0, 1001),
10778 (18, "char", 1, "b", "S", 0, 1002),
10779 (19, "name", 64, "b", "S", 0, 1003),
10780 (20, "int8", 8, "b", "N", 0, 1016),
10781 (21, "int2", 2, "b", "N", 0, 1005),
10782 (23, "int4", 4, "b", "N", 0, 1007),
10783 (24, "regproc", 4, "b", "N", 0, 1008),
10784 (25, "text", -1, "b", "S", 0, 1009),
10785 (26, "oid", 4, "b", "N", 0, 1028),
10786 (114, "json", -1, "b", "U", 0, 199),
10787 (142, "xml", -1, "b", "U", 0, 143),
10788 (700, "float4", 4, "b", "N", 0, 1021),
10789 (701, "float8", 8, "b", "N", 0, 1022),
10790 (650, "cidr", -1, "b", "I", 0, 651),
10791 (869, "inet", -1, "b", "I", 0, 1041),
10792 (829, "macaddr", 6, "b", "U", 0, 1040),
10793 (1042, "bpchar", -1, "b", "S", 0, 1014),
10794 (1043, "varchar", -1, "b", "S", 0, 1015),
10795 (1082, "date", 4, "b", "D", 0, 1182),
10796 (1083, "time", 8, "b", "D", 0, 1183),
10797 (1114, "timestamp", 8, "b", "D", 0, 1115),
10798 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10799 (1186, "interval", 16, "b", "T", 0, 1187),
10800 (1266, "timetz", 12, "b", "D", 0, 1270),
10801 (1700, "numeric", -1, "b", "N", 0, 1231),
10802 (790, "money", 8, "b", "N", 0, 791),
10803 (2950, "uuid", 16, "b", "U", 0, 2951),
10804 (3802, "jsonb", -1, "b", "U", 0, 3807),
10805 (3614, "tsvector", -1, "b", "U", 0, 3643),
10806 (3615, "tsquery", -1, "b", "U", 0, 3645),
10807 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10809 (3910, "tsrange", -1, "r", "R", 0, 3911),
10810 (3904, "int4range", -1, "r", "R", 0, 3905),
10811 (3926, "int8range", -1, "r", "R", 0, 3927),
10812 (3906, "numrange", -1, "r", "R", 0, 3907),
10813 (3912, "daterange", -1, "r", "R", 0, 3913),
10814 ];
10815 let arrays: &[(i64, &str, i64)] = &[
10818 (1000, "_bool", 16),
10819 (1001, "_bytea", 17),
10820 (1002, "_char", 18),
10821 (1003, "_name", 19),
10822 (1016, "_int8", 20),
10823 (1005, "_int2", 21),
10824 (1007, "_int4", 23),
10825 (1008, "_regproc", 24),
10826 (1009, "_text", 25),
10827 (1028, "_oid", 26),
10828 (199, "_json", 114),
10829 (143, "_xml", 142),
10830 (1021, "_float4", 700),
10831 (1022, "_float8", 701),
10832 (651, "_cidr", 650),
10833 (1041, "_inet", 869),
10834 (1040, "_macaddr", 829),
10835 (1014, "_bpchar", 1042),
10836 (1015, "_varchar", 1043),
10837 (1182, "_date", 1082),
10838 (1183, "_time", 1083),
10839 (1115, "_timestamp", 1114),
10840 (1185, "_timestamptz", 1184),
10841 (1187, "_interval", 1186),
10842 (1270, "_timetz", 1266),
10843 (1231, "_numeric", 1700),
10844 (791, "_money", 790),
10845 (2951, "_uuid", 2950),
10846 (3807, "_jsonb", 3802),
10847 (3643, "_tsvector", 3614),
10848 (3645, "_tsquery", 3615),
10849 ];
10850 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10851 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10852 rows.push(Row::new(alloc::vec![
10853 Value::BigInt(oid),
10854 Value::Text(name.into()),
10855 Value::SmallInt(len),
10856 Value::Text(ty.into()),
10857 Value::Text(cat.into()),
10858 Value::BigInt(elem),
10859 Value::BigInt(arr),
10860 Value::BigInt(2200),
10861 ]));
10862 }
10863 for &(oid, name, elem) in arrays {
10864 rows.push(Row::new(alloc::vec![
10865 Value::BigInt(oid),
10866 Value::Text(name.into()),
10867 Value::SmallInt(-1),
10868 Value::Text("b".into()),
10869 Value::Text("A".into()),
10870 Value::BigInt(elem),
10871 Value::BigInt(0),
10872 Value::BigInt(2200),
10873 ]));
10874 }
10875 (schema, rows)
10876}
10877
10878fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10892 let schema = alloc::vec![
10893 ColumnSchema::new("oid", DataType::BigInt, false),
10894 ColumnSchema::new("proname", DataType::Text, false),
10895 ColumnSchema::new("pronamespace", DataType::BigInt, false),
10896 ColumnSchema::new("prokind", DataType::Text, false),
10897 ColumnSchema::new("pronargs", DataType::Int, false),
10898 ColumnSchema::new("prorettype", DataType::BigInt, false),
10899 ];
10900 let funcs: &[(i64, &str, &str, i32, i64)] = &[
10903 (1318, "length", "f", 1, 23),
10905 (871, "upper", "f", 1, 25),
10906 (870, "lower", "f", 1, 25),
10907 (936, "substring", "f", 3, 25),
10908 (937, "substring", "f", 2, 25),
10909 (3055, "btrim", "f", 1, 25),
10910 (885, "btrim", "f", 2, 25),
10911 (3056, "ltrim", "f", 1, 25),
10912 (875, "ltrim", "f", 2, 25),
10913 (3057, "rtrim", "f", 1, 25),
10914 (876, "rtrim", "f", 2, 25),
10915 (1397, "abs", "f", 1, 23),
10916 (1396, "abs", "f", 1, 20),
10917 (1606, "round", "f", 1, 1700),
10918 (1707, "round", "f", 2, 1700),
10919 (2308, "ceil", "f", 1, 701),
10920 (2309, "ceiling", "f", 1, 701),
10921 (2310, "floor", "f", 1, 701),
10922 (1376, "sqrt", "f", 1, 701),
10923 (1369, "ln", "f", 1, 701),
10924 (1373, "exp", "f", 1, 701),
10925 (1368, "power", "f", 2, 701),
10926 (2228, "random", "f", 0, 701),
10927 (1299, "now", "f", 0, 1184),
10929 (1274, "current_timestamp", "f", 0, 1184),
10930 (1140, "current_date", "f", 0, 1082),
10931 (2050, "current_time", "f", 0, 1083),
10932 (1158, "date_trunc", "f", 2, 1184),
10933 (1171, "date_part", "f", 2, 701),
10934 (1172, "age", "f", 1, 1186),
10935 (936, "to_char", "f", 2, 25),
10936 (861, "current_database", "f", 0, 19),
10938 (745, "current_user", "f", 0, 19),
10939 (745, "session_user", "f", 0, 19),
10940 (1402, "current_schema", "f", 0, 19),
10941 (3058, "concat", "f", -1, 25),
10943 (3059, "concat_ws", "f", -1, 25),
10944 (3539, "format", "f", -1, 25),
10945 (2877, "pg_typeof", "f", 1, 2206),
10947 (3198, "json_build_object", "f", -1, 114),
10949 (3199, "jsonb_build_object", "f", -1, 3802),
10950 (3271, "json_build_array", "f", -1, 114),
10951 (3272, "jsonb_build_array", "f", -1, 3802),
10952 (3253, "gen_random_uuid", "f", 0, 2950),
10954 (3252, "uuid_generate_v4", "f", 0, 2950),
10955 (2147, "count", "a", 0, 20),
10957 (2803, "count", "a", -1, 20),
10958 (2116, "max", "a", 1, 23),
10959 (2132, "min", "a", 1, 23),
10960 (2108, "sum", "a", 1, 20),
10961 (2100, "avg", "a", 1, 1700),
10962 (2517, "string_agg", "a", 2, 25),
10963 (2747, "array_agg", "a", 1, 1009),
10964 (2517, "bool_and", "a", 1, 16),
10965 (2518, "bool_or", "a", 1, 16),
10966 (2519, "every", "a", 1, 16),
10967 (3100, "row_number", "w", 0, 20),
10969 (3101, "rank", "w", 0, 20),
10970 (3102, "dense_rank", "w", 0, 20),
10971 (3103, "percent_rank", "w", 0, 701),
10972 (3104, "cume_dist", "w", 0, 701),
10973 (3105, "lag", "w", -1, 2283),
10974 (3106, "lead", "w", -1, 2283),
10975 (3107, "first_value", "w", 1, 2283),
10976 (3108, "last_value", "w", 1, 2283),
10977 (3109, "nth_value", "w", 2, 2283),
10978 ];
10979 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
10980 for &(oid, name, kind, nargs, rettype) in funcs {
10981 rows.push(Row::new(alloc::vec![
10982 Value::BigInt(oid),
10983 Value::Text(name.into()),
10984 Value::BigInt(11),
10985 Value::Text(kind.into()),
10986 Value::Int(nargs),
10987 Value::BigInt(rettype),
10988 ]));
10989 }
10990 (schema, rows)
10991}
10992
10993fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
10999 let schema = alloc::vec![
11000 ColumnSchema::new("user", DataType::Text, false),
11001 ColumnSchema::new("host", DataType::Text, false),
11002 ColumnSchema::new("select_priv", DataType::Text, false),
11003 ];
11004 let mut rows: Vec<Row> = Vec::new();
11005 rows.push(Row::new(alloc::vec![
11006 Value::Text("root".into()),
11007 Value::Text("localhost".into()),
11008 Value::Text("Y".into()),
11009 ]));
11010 for (name, _) in engine.users.iter() {
11011 if name != "root" {
11012 rows.push(Row::new(alloc::vec![
11013 Value::Text(name.to_string()),
11014 Value::Text("%".into()),
11015 Value::Text("Y".into()),
11016 ]));
11017 }
11018 }
11019 (schema, rows)
11020}
11021
11022fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11027 let schema = alloc::vec![
11028 ColumnSchema::new("host", DataType::Text, false),
11029 ColumnSchema::new("db", DataType::Text, false),
11030 ColumnSchema::new("user", DataType::Text, false),
11031 ColumnSchema::new("select_priv", DataType::Text, false),
11032 ];
11033 let rows = alloc::vec![Row::new(alloc::vec![
11034 Value::Text("localhost".into()),
11035 Value::Text("postgres".into()),
11036 Value::Text("root".into()),
11037 Value::Text("Y".into()),
11038 ])];
11039 (schema, rows)
11040}
11041
11042fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11055 let schema = alloc::vec![
11056 ColumnSchema::new("constraint_name", DataType::Text, false),
11057 ColumnSchema::new("table_name", DataType::Text, false),
11058 ColumnSchema::new("column_name", DataType::Text, false),
11059 ColumnSchema::new("ordinal_position", DataType::Int, false),
11060 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11061 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11062 ];
11063 let mut rows: Vec<Row> = Vec::new();
11064 for tname in cat.table_names() {
11065 let Some(t) = cat.get(&tname) else { continue };
11066 let cols = &t.schema().columns;
11067 let col_name_at = |pos: usize| -> String {
11068 cols.get(pos)
11069 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11070 };
11071 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11073 let conname = fk
11074 .name
11075 .clone()
11076 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11077 for (i, (&local, &parent)) in fk
11078 .local_columns
11079 .iter()
11080 .zip(fk.parent_columns.iter())
11081 .enumerate()
11082 {
11083 let parent_name = cat
11084 .get(&fk.parent_table)
11085 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11086 .unwrap_or_else(|| alloc::format!("col{parent}"));
11087 #[allow(clippy::cast_possible_wrap)]
11088 let ordinal = (i + 1) as i32;
11089 rows.push(Row::new(alloc::vec![
11090 Value::Text(conname.clone()),
11091 Value::Text(tname.clone()),
11092 Value::Text(col_name_at(local)),
11093 Value::Int(ordinal),
11094 Value::Text(fk.parent_table.clone()),
11095 Value::Text(parent_name),
11096 ]));
11097 }
11098 }
11099 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11101 let conname = if uc.is_primary_key {
11102 alloc::format!("{}_pkey", tname)
11103 } else {
11104 alloc::format!("{}_uniq{ci}", tname)
11105 };
11106 for (i, &local) in uc.columns.iter().enumerate() {
11107 #[allow(clippy::cast_possible_wrap)]
11108 let ordinal = (i + 1) as i32;
11109 rows.push(Row::new(alloc::vec![
11110 Value::Text(conname.clone()),
11111 Value::Text(tname.clone()),
11112 Value::Text(col_name_at(local)),
11113 Value::Int(ordinal),
11114 Value::Text(String::new()),
11115 Value::Text(String::new()),
11116 ]));
11117 }
11118 }
11119 }
11120 (schema, rows)
11121}
11122
11123fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11126 let schema = alloc::vec![
11127 ColumnSchema::new("constraint_name", DataType::Text, false),
11128 ColumnSchema::new("table_name", DataType::Text, false),
11129 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11130 ColumnSchema::new("update_rule", DataType::Text, false),
11131 ColumnSchema::new("delete_rule", DataType::Text, false),
11132 ];
11133 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11134 match a {
11135 spg_storage::FkAction::Cascade => "CASCADE",
11136 spg_storage::FkAction::SetNull => "SET NULL",
11137 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11138 spg_storage::FkAction::Restrict => "RESTRICT",
11139 spg_storage::FkAction::NoAction => "NO ACTION",
11140 }
11141 }
11142 let mut rows: Vec<Row> = Vec::new();
11143 for tname in cat.table_names() {
11144 let Some(t) = cat.get(&tname) else { continue };
11145 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11146 let conname = fk
11147 .name
11148 .clone()
11149 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11150 rows.push(Row::new(alloc::vec![
11151 Value::Text(conname),
11152 Value::Text(tname.clone()),
11153 Value::Text(fk.parent_table.clone()),
11154 Value::Text(rule_name(fk.on_update).into()),
11155 Value::Text(rule_name(fk.on_delete).into()),
11156 ]));
11157 }
11158 }
11159 (schema, rows)
11160}
11161
11162fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11166 let schema = alloc::vec![
11167 ColumnSchema::new("table_name", DataType::Text, false),
11168 ColumnSchema::new("index_name", DataType::Text, false),
11169 ColumnSchema::new("column_name", DataType::Text, false),
11170 ColumnSchema::new("seq_in_index", DataType::Int, false),
11171 ColumnSchema::new("non_unique", DataType::Int, false),
11172 ColumnSchema::new("index_type", DataType::Text, false),
11173 ];
11174 let mut rows: Vec<Row> = Vec::new();
11175 for tname in cat.table_names() {
11176 let Some(t) = cat.get(&tname) else { continue };
11177 for idx in t.indices() {
11178 let col = t
11179 .schema()
11180 .columns
11181 .get(idx.column_position)
11182 .map_or("?".into(), |c| c.name.clone());
11183 rows.push(Row::new(alloc::vec![
11184 Value::Text(tname.clone()),
11185 Value::Text(idx.name.clone()),
11186 Value::Text(col),
11187 Value::Int(1),
11188 Value::Int(i32::from(!idx.is_unique)),
11189 Value::Text("BTREE".into()),
11190 ]));
11191 }
11192 }
11193 (schema, rows)
11194}
11195
11196fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11200 let schema = alloc::vec![
11201 ColumnSchema::new("routine_name", DataType::Text, false),
11202 ColumnSchema::new("routine_type", DataType::Text, false),
11203 ColumnSchema::new("data_type", DataType::Text, false),
11204 ];
11205 (schema, Vec::new())
11206}
11207
11208fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11223 let schema = alloc::vec![
11224 ColumnSchema::new("conname", DataType::Text, false),
11225 ColumnSchema::new("contype", DataType::Text, false),
11226 ColumnSchema::new("conrelid", DataType::Text, false),
11227 ColumnSchema::new("confrelid", DataType::Text, false),
11228 ColumnSchema::new("conkey", DataType::Text, false),
11229 ColumnSchema::new("confkey", DataType::Text, false),
11230 ];
11231 let mut rows: Vec<Row> = Vec::new();
11232 for tname in cat.table_names() {
11233 let Some(t) = cat.get(&tname) else { continue };
11234 let cols = &t.schema().columns;
11235 let col_name_at = |pos: usize| -> String {
11236 cols.get(pos)
11237 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11238 };
11239 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11241 let kind = if uc.is_primary_key { "p" } else { "u" };
11242 let conname = if uc.is_primary_key {
11243 alloc::format!("{}_pkey", tname)
11244 } else {
11245 alloc::format!("{}_uniq{ci}", tname)
11246 };
11247 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11248 rows.push(Row::new(alloc::vec![
11249 Value::Text(conname),
11250 Value::Text(kind.into()),
11251 Value::Text(tname.clone()),
11252 Value::Text(String::new()),
11253 Value::Text(conkey.join(",")),
11254 Value::Text(String::new()),
11255 ]));
11256 }
11257 for idx in t.indices() {
11262 if !idx.is_unique {
11263 continue;
11264 }
11265 let is_primary = idx.name.ends_with("_pkey");
11266 let conname = idx.name.clone();
11267 let kind = if is_primary { "p" } else { "u" };
11268 let col_name = col_name_at(idx.column_position);
11269 let already = t
11272 .schema()
11273 .uniqueness_constraints
11274 .iter()
11275 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11276 if already {
11277 continue;
11278 }
11279 rows.push(Row::new(alloc::vec![
11280 Value::Text(conname),
11281 Value::Text(kind.into()),
11282 Value::Text(tname.clone()),
11283 Value::Text(String::new()),
11284 Value::Text(col_name),
11285 Value::Text(String::new()),
11286 ]));
11287 }
11288 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11290 let conname = fk
11291 .name
11292 .clone()
11293 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11294 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11295 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11298 fk.parent_columns
11299 .iter()
11300 .map(|&p| {
11301 parent
11302 .schema()
11303 .columns
11304 .get(p)
11305 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11306 })
11307 .collect()
11308 } else {
11309 fk.parent_columns
11310 .iter()
11311 .map(|p| alloc::format!("col{p}"))
11312 .collect()
11313 };
11314 rows.push(Row::new(alloc::vec![
11315 Value::Text(conname),
11316 Value::Text("f".into()),
11317 Value::Text(tname.clone()),
11318 Value::Text(fk.parent_table.clone()),
11319 Value::Text(conkey.join(",")),
11320 Value::Text(confkey.join(",")),
11321 ]));
11322 }
11323 }
11324 (schema, rows)
11325}
11326
11327fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11332 let schema = alloc::vec![
11333 ColumnSchema::new("oid", DataType::BigInt, false),
11334 ColumnSchema::new("datname", DataType::Text, false),
11335 ColumnSchema::new("datdba", DataType::BigInt, false),
11336 ColumnSchema::new("encoding", DataType::Int, false),
11337 ColumnSchema::new("datcollate", DataType::Text, false),
11338 ];
11339 let rows = alloc::vec![Row::new(alloc::vec![
11340 Value::BigInt(16384),
11341 Value::Text("postgres".into()),
11342 Value::BigInt(10),
11343 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11345 ])];
11346 (schema, rows)
11347}
11348
11349fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11354 let schema = alloc::vec![
11355 ColumnSchema::new("oid", DataType::BigInt, false),
11356 ColumnSchema::new("rolname", DataType::Text, false),
11357 ColumnSchema::new("rolsuper", DataType::Bool, false),
11358 ColumnSchema::new("rolinherit", DataType::Bool, false),
11359 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11360 ];
11361 let mut rows: Vec<Row> = Vec::new();
11362 let oid: i64 = 10;
11363 for (i, (name, _)) in engine.users.iter().enumerate() {
11364 rows.push(Row::new(alloc::vec![
11365 Value::BigInt(oid + (i as i64) + 1),
11366 Value::Text(name.to_string()),
11367 Value::Bool(false),
11368 Value::Bool(true),
11369 Value::Bool(true),
11370 ]));
11371 }
11372 if !rows
11375 .iter()
11376 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11377 {
11378 rows.insert(
11379 0,
11380 Row::new(alloc::vec![
11381 Value::BigInt(10),
11382 Value::Text("postgres".into()),
11383 Value::Bool(true),
11384 Value::Bool(true),
11385 Value::Bool(true),
11386 ]),
11387 );
11388 }
11389 (schema, rows)
11390}
11391
11392fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11396 let schema = alloc::vec![
11397 ColumnSchema::new("schemaname", DataType::Text, false),
11398 ColumnSchema::new("viewname", DataType::Text, false),
11399 ColumnSchema::new("definition", DataType::Text, false),
11400 ];
11401 let mut rows: Vec<Row> = Vec::new();
11402 for (name, def) in cat.views() {
11403 rows.push(Row::new(alloc::vec![
11404 Value::Text("public".into()),
11405 Value::Text(name.clone()),
11406 Value::Text(def.body.clone()),
11407 ]));
11408 }
11409 (schema, rows)
11410}
11411
11412fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11418 let schema = alloc::vec![
11419 ColumnSchema::new("name", DataType::Text, false),
11420 ColumnSchema::new("setting", DataType::Text, false),
11421 ColumnSchema::new("category", DataType::Text, false),
11422 ];
11423 let mut rows: Vec<Row> = Vec::new();
11424 let defaults: &[(&str, &str, &str)] = &[
11426 ("server_version", "16.0 (spg)", "Preset Options"),
11427 ("server_encoding", "UTF8", "Client Connection Defaults"),
11428 ("client_encoding", "UTF8", "Client Connection Defaults"),
11429 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11430 ("TimeZone", "UTC", "Client Connection Defaults"),
11431 ("standard_conforming_strings", "on", "Compatibility"),
11432 ("integer_datetimes", "on", "Compatibility"),
11433 ("max_connections", "100", "Connections and Authentication"),
11434 ];
11435 for &(name, val, cat) in defaults {
11436 rows.push(Row::new(alloc::vec![
11437 Value::Text(name.into()),
11438 Value::Text(val.into()),
11439 Value::Text(cat.into()),
11440 ]));
11441 }
11442 for (k, v) in &engine.session_params {
11444 if !defaults
11445 .iter()
11446 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11447 {
11448 rows.push(Row::new(alloc::vec![
11449 Value::Text(k.clone()),
11450 Value::Text(v.clone()),
11451 Value::Text("Session".into()),
11452 ]));
11453 }
11454 }
11455 (schema, rows)
11456}
11457
11458fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11469 let schema = alloc::vec![
11470 ColumnSchema::new("schemaname", DataType::Text, false),
11471 ColumnSchema::new("tablename", DataType::Text, false),
11472 ColumnSchema::new("indexname", DataType::Text, false),
11473 ColumnSchema::new("indexdef", DataType::Text, false),
11474 ];
11475 let mut rows: Vec<Row> = Vec::new();
11476 for tname in cat.table_names() {
11477 let Some(t) = cat.get(&tname) else { continue };
11478 for idx in t.indices() {
11479 let col_name = t
11480 .schema()
11481 .columns
11482 .get(idx.column_position)
11483 .map_or("?".into(), |c| c.name.clone());
11484 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11485 let indexdef = alloc::format!(
11486 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11487 idx.name,
11488 tname,
11489 col_name
11490 );
11491 rows.push(Row::new(alloc::vec![
11492 Value::Text("public".into()),
11493 Value::Text(tname.clone()),
11494 Value::Text(idx.name.clone()),
11495 Value::Text(indexdef),
11496 ]));
11497 }
11498 }
11499 (schema, rows)
11500}
11501
11502fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11514 let schema = alloc::vec![
11515 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11516 ColumnSchema::new("indrelid", DataType::BigInt, false),
11517 ColumnSchema::new("indnatts", DataType::Int, false),
11518 ColumnSchema::new("indisunique", DataType::Bool, false),
11519 ColumnSchema::new("indisprimary", DataType::Bool, false),
11520 ];
11521 let mut rows: Vec<Row> = Vec::new();
11522 let mut idx_oid: i64 = 100_000;
11523 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11524 let Some(t) = cat.get(tname) else { continue };
11525 for idx in t.indices() {
11526 idx_oid += 1;
11527 #[allow(clippy::cast_possible_wrap)]
11528 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11529 let is_primary = idx.name.ends_with("_pkey");
11532 rows.push(Row::new(alloc::vec![
11533 Value::BigInt(idx_oid),
11534 Value::BigInt((table_idx + 1) as i64),
11535 Value::Int(nattrs),
11536 Value::Bool(idx.is_unique),
11537 Value::Bool(is_primary),
11538 ]));
11539 }
11540 }
11541 (schema, rows)
11542}
11543
11544fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11549 let schema = alloc::vec![
11550 ColumnSchema::new("oid", DataType::BigInt, false),
11551 ColumnSchema::new("nspname", DataType::Text, false),
11552 ColumnSchema::new("nspowner", DataType::BigInt, false),
11553 ];
11554 let rows = alloc::vec![
11555 Row::new(alloc::vec![
11556 Value::BigInt(11),
11557 Value::Text("pg_catalog".into()),
11558 Value::BigInt(10),
11559 ]),
11560 Row::new(alloc::vec![
11561 Value::BigInt(2200),
11562 Value::Text("public".into()),
11563 Value::BigInt(10),
11564 ]),
11565 Row::new(alloc::vec![
11566 Value::BigInt(13000),
11567 Value::Text("information_schema".into()),
11568 Value::BigInt(10),
11569 ]),
11570 ];
11571 (schema, rows)
11572}
11573
11574fn materialise_meta_view(
11577 catalog: &mut Catalog,
11578 name: &str,
11579 columns: Vec<ColumnSchema>,
11580 rows: Vec<Row>,
11581) -> Result<(), EngineError> {
11582 let schema = TableSchema::new(name.to_string(), columns);
11583 catalog.create_table(schema).map_err(EngineError::Storage)?;
11584 let table = catalog
11585 .get_mut(name)
11586 .expect("just-created meta view must exist");
11587 for row in rows {
11588 table.insert(row).map_err(EngineError::Storage)?;
11589 }
11590 Ok(())
11591}
11592
11593fn collect_view_refs(
11606 tref: &spg_sql::ast::TableRef,
11607 cat: &spg_storage::Catalog,
11608 into: &mut Vec<String>,
11609) {
11610 if cat.views().contains_key(&tref.name)
11611 && cat.get(&tref.name).is_none()
11612 && !into.iter().any(|n| n == &tref.name)
11613 {
11614 into.push(tref.name.clone());
11615 }
11616}
11617
11618fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11619 fn is_meta(name: &str) -> bool {
11620 name.starts_with("__spg_info_")
11621 || name.starts_with("__spg_pg_")
11622 || name.starts_with("__spg_mysql_")
11623 }
11624 if let Some(from) = &stmt.from {
11625 if is_meta(&from.primary.name) {
11626 return true;
11627 }
11628 for j in &from.joins {
11629 if is_meta(&j.table.name) {
11630 return true;
11631 }
11632 }
11633 }
11634 for cte in &stmt.ctes {
11635 if select_references_meta_view(&cte.body) {
11636 return true;
11637 }
11638 }
11639 false
11640}
11641
11642fn collect_meta_view_names(
11647 stmt: &SelectStatement,
11648 into: &mut alloc::collections::BTreeSet<String>,
11649) {
11650 fn is_meta(name: &str) -> bool {
11651 name.starts_with("__spg_info_")
11652 || name.starts_with("__spg_pg_")
11653 || name.starts_with("__spg_mysql_")
11654 }
11655 if let Some(from) = &stmt.from {
11656 if is_meta(&from.primary.name) {
11657 into.insert(from.primary.name.clone());
11658 }
11659 for j in &from.joins {
11660 if is_meta(&j.table.name) {
11661 into.insert(j.table.name.clone());
11662 }
11663 }
11664 }
11665 for cte in &stmt.ctes {
11666 collect_meta_view_names(&cte.body, into);
11667 }
11668}
11669
11670fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11671 let mut out = columns.to_vec();
11672 for (col_idx, col) in out.iter_mut().enumerate() {
11673 if col.ty != DataType::Text {
11674 continue;
11675 }
11676 let mut inferred: Option<DataType> = None;
11677 let mut all_null = true;
11678 for row in rows {
11679 let Some(v) = row.values.get(col_idx) else {
11680 continue;
11681 };
11682 let ty = match v {
11683 Value::Null => continue,
11684 Value::SmallInt(_) => DataType::SmallInt,
11685 Value::Int(_) => DataType::Int,
11686 Value::BigInt(_) => DataType::BigInt,
11687 Value::Float(_) => DataType::Float,
11688 Value::Bool(_) => DataType::Bool,
11689 Value::Vector(_) => DataType::Vector {
11690 dim: 0,
11691 encoding: VecEncoding::F32,
11692 },
11693 _ => DataType::Text,
11694 };
11695 all_null = false;
11696 inferred = Some(match inferred {
11697 None => ty,
11698 Some(prev) if prev == ty => prev,
11699 Some(_) => DataType::Text,
11700 });
11701 }
11702 if let Some(t) = inferred {
11703 col.ty = t;
11704 col.nullable = true;
11705 } else if all_null {
11706 col.nullable = true;
11707 }
11708 }
11709 out
11710}
11711
11712#[allow(clippy::too_many_lines, clippy::format_push_string)]
11717fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11734 use alloc::collections::BTreeSet;
11735 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11736 let mut out: Vec<String> = Vec::new();
11737 let cat = engine.active_catalog();
11738 let Some(from) = &stmt.from else {
11742 return out;
11743 };
11744 let mut tables: Vec<String> = Vec::new();
11745 tables.push(from.primary.name.clone());
11746 for j in &from.joins {
11747 tables.push(j.table.name.clone());
11748 }
11749 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11752 if let Some(w) = &stmt.where_ {
11753 collect_column_refs(w, &mut col_refs);
11754 }
11755 for j in &from.joins {
11756 if let Some(on) = &j.on {
11757 collect_column_refs(on, &mut col_refs);
11758 }
11759 }
11760 for cn in &col_refs {
11761 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11764 tables.iter().find(|t| t == &q).cloned()
11765 } else {
11766 tables.iter().find_map(|t| {
11767 cat.get(t).and_then(|tbl| {
11768 if tbl.schema().column_position(&cn.name).is_some() {
11769 Some(t.clone())
11770 } else {
11771 None
11772 }
11773 })
11774 })
11775 };
11776 let Some(owner) = owner else {
11777 continue;
11778 };
11779 let Some(tbl) = cat.get(&owner) else {
11780 continue;
11781 };
11782 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11783 continue;
11784 };
11785 let already_indexed = tbl.indices().iter().any(|i| {
11788 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11789 && i.column_position == col_pos
11790 && i.expression.is_none()
11791 && i.partial_predicate.is_none()
11792 });
11793 if already_indexed {
11794 continue;
11795 }
11796 if seen.insert((owner.clone(), cn.name.clone())) {
11797 out.push(alloc::format!(
11798 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11799 owner,
11800 cn.name,
11801 owner,
11802 cn.name
11803 ));
11804 }
11805 }
11806 out
11807}
11808
11809fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11812 match expr {
11813 Expr::Column(cn) => out.push(cn.clone()),
11814 Expr::FunctionCall { args, .. } => {
11815 for a in args {
11816 collect_column_refs(a, out);
11817 }
11818 }
11819 Expr::Binary { lhs, rhs, .. } => {
11820 collect_column_refs(lhs, out);
11821 collect_column_refs(rhs, out);
11822 }
11823 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11824 _ => {}
11825 }
11826}
11827
11828fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11829 let catalog = engine.active_catalog();
11830 let cold_ids = catalog.cold_segment_ids_global();
11831 let any_cold = !cold_ids.is_empty();
11832 let cold_ids_repr = if any_cold {
11833 let mut s = alloc::string::String::from("[");
11834 for (i, id) in cold_ids.iter().enumerate() {
11835 if i > 0 {
11836 s.push(',');
11837 }
11838 s.push_str(&alloc::format!("{id}"));
11839 }
11840 s.push(']');
11841 s
11842 } else {
11843 alloc::string::String::new()
11844 };
11845 for (idx, line) in lines.iter_mut().enumerate() {
11846 let trimmed = line.trim_start();
11847 let is_top_level = idx == 0;
11848 if is_top_level {
11849 line.push_str(&alloc::format!(" (rows={total_rows})"));
11850 continue;
11851 }
11852 if let Some(rest) = trimmed.strip_prefix("From: ") {
11853 let (name, scan_kind) = match rest.split_once(" [") {
11854 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
11855 None => (rest.trim(), ""),
11856 };
11857 let bare = name.split_whitespace().next().unwrap_or(name);
11858 let hot = catalog.get(bare).map(|t| t.rows().len());
11859 let annot = match (hot, scan_kind) {
11864 (Some(h), "full scan") => {
11865 let mut s = alloc::format!(" (hot_rows={h}");
11866 if any_cold {
11867 s.push_str(&alloc::format!(
11868 ", cold_tier=present, cold_segments={cold_ids_repr}"
11869 ));
11870 }
11871 s.push(')');
11872 s
11873 }
11874 (Some(h), "index seek") => {
11875 let mut s = alloc::format!(" (hot_rows≤{h}");
11876 if any_cold {
11877 s.push_str(&alloc::format!(
11878 ", cold_tier=present, cold_segments={cold_ids_repr}"
11879 ));
11880 }
11881 s.push(')');
11882 s
11883 }
11884 _ => " (rows=—)".to_string(),
11885 };
11886 line.push_str(&annot);
11887 continue;
11888 }
11889 line.push_str(" (rows=—)");
11891 }
11892}
11893
11894fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
11895 let pad = " ".repeat(depth);
11896 let top = if !stmt.ctes.is_empty() {
11898 if stmt.ctes.iter().any(|c| c.recursive) {
11899 "CTEScan (WITH RECURSIVE)"
11900 } else {
11901 "CTEScan (WITH)"
11902 }
11903 } else if !stmt.unions.is_empty() {
11904 "UnionScan"
11905 } else if select_has_window(stmt) {
11906 "WindowAgg"
11907 } else if aggregate::uses_aggregate(stmt) {
11908 "Aggregate"
11909 } else if stmt.distinct {
11910 "Distinct"
11911 } else if stmt.from.is_some() {
11912 "TableScan"
11913 } else {
11914 "Result"
11915 };
11916 out.push(alloc::format!("{pad}{top}"));
11917 let child = " ".repeat(depth + 1);
11918 for cte in &stmt.ctes {
11920 let head = if cte.recursive {
11921 alloc::format!("{child}CTE (recursive): {}", cte.name)
11922 } else {
11923 alloc::format!("{child}CTE: {}", cte.name)
11924 };
11925 out.push(head);
11926 explain_select(&cte.body, engine, depth + 2, out);
11927 }
11928 if let Some(from) = &stmt.from {
11930 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
11931 if let Some(alias) = &from.primary.alias {
11932 tag.push_str(&alloc::format!(" AS {alias}"));
11933 }
11934 if let Some(w) = &stmt.where_
11937 && let Some(table) = engine.active_catalog().get(&from.primary.name)
11938 {
11939 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
11940 let cols = &table.schema().columns;
11941 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
11942 tag.push_str(" [index seek]");
11943 } else {
11944 tag.push_str(" [full scan]");
11945 }
11946 } else {
11947 tag.push_str(" [full scan]");
11948 }
11949 out.push(tag);
11950 for j in &from.joins {
11951 let kind = match j.kind {
11952 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
11953 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
11954 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
11955 };
11956 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
11957 if let Some(alias) = &j.table.alias {
11958 s.push_str(&alloc::format!(" AS {alias}"));
11959 }
11960 if j.on.is_some() {
11961 s.push_str(" (ON …)");
11962 }
11963 out.push(s);
11964 }
11965 }
11966 if let Some(w) = &stmt.where_ {
11968 let mut s = alloc::format!("{child}Filter: {w}");
11969 if expr_has_subquery(w) {
11970 s.push_str(" [subquery]");
11971 }
11972 out.push(s);
11973 }
11974 if let Some(gs) = &stmt.group_by {
11975 let mut parts = Vec::new();
11976 for g in gs {
11977 parts.push(alloc::format!("{g}"));
11978 }
11979 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
11980 }
11981 if let Some(h) = &stmt.having {
11982 out.push(alloc::format!("{child}Having: {h}"));
11983 }
11984 for o in &stmt.order_by {
11985 let dir = if o.desc { "DESC" } else { "ASC" };
11986 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
11987 }
11988 if let Some(lim) = stmt.limit {
11989 out.push(alloc::format!("{child}Limit: {lim}"));
11990 }
11991 if let Some(off) = stmt.offset {
11992 out.push(alloc::format!("{child}Offset: {off}"));
11993 }
11994 if stmt
11996 .items
11997 .iter()
11998 .any(|it| matches!(it, SelectItem::Wildcard))
11999 {
12000 out.push(alloc::format!("{child}Project: *"));
12001 } else {
12002 out.push(alloc::format!(
12003 "{child}Project: {} item(s)",
12004 stmt.items.len()
12005 ));
12006 }
12007 for (kind, peer) in &stmt.unions {
12009 let label = match kind {
12010 UnionKind::All => "UNION ALL",
12011 UnionKind::Distinct => "UNION",
12012 };
12013 out.push(alloc::format!("{child}{label}"));
12014 explain_select(peer, engine, depth + 2, out);
12015 }
12016}
12017
12018fn is_correlation_error(e: &EngineError) -> bool {
12023 matches!(
12024 e,
12025 EngineError::Eval(
12026 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12027 )
12028 )
12029}
12030
12031struct JoinedPeer<'a> {
12042 eager_rows: Option<Vec<Row>>,
12043 cols: Vec<ColumnSchema>,
12044 alias: String,
12045 kind: JoinKind,
12046 on: Option<&'a Expr>,
12047 lateral: Option<&'a SelectStatement>,
12048}
12049
12050fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12057 match expr {
12058 Expr::Column(c) => c.name.clone(),
12060 Expr::FunctionCall { name, .. } => name.clone(),
12063 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12065 _ => alloc::format!("column{}", idx + 1),
12067 }
12068}
12069
12070fn substitute_outer_columns_multi(
12077 stmt: &mut SelectStatement,
12078 outer_row: &Row,
12079 outer_schema: &[ColumnSchema],
12080) {
12081 substitute_outer_in_select(stmt, outer_row, outer_schema);
12082}
12083
12084fn substitute_outer_in_select(
12085 stmt: &mut SelectStatement,
12086 outer_row: &Row,
12087 outer_schema: &[ColumnSchema],
12088) {
12089 for item in &mut stmt.items {
12090 if let SelectItem::Expr { expr, .. } = item {
12091 substitute_outer_in_expr(expr, outer_row, outer_schema);
12092 }
12093 }
12094 if let Some(w) = &mut stmt.where_ {
12095 substitute_outer_in_expr(w, outer_row, outer_schema);
12096 }
12097 if let Some(gs) = &mut stmt.group_by {
12098 for g in gs {
12099 substitute_outer_in_expr(g, outer_row, outer_schema);
12100 }
12101 }
12102 if let Some(h) = &mut stmt.having {
12103 substitute_outer_in_expr(h, outer_row, outer_schema);
12104 }
12105 for o in &mut stmt.order_by {
12106 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12107 }
12108 for (_, peer) in &mut stmt.unions {
12109 substitute_outer_in_select(peer, outer_row, outer_schema);
12110 }
12111}
12112
12113fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12114 if let Expr::Column(c) = e
12115 && let Some(qual) = &c.qualifier
12116 {
12117 let composite = alloc::format!("{qual}.{}", c.name);
12118 if let Some(idx) = outer_schema
12119 .iter()
12120 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12121 {
12122 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12123 if let Ok(lit) = value_to_literal_expr(v) {
12124 *e = lit;
12125 return;
12126 }
12127 }
12128 }
12129 match e {
12130 Expr::Binary { lhs, rhs, .. } => {
12131 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12132 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12133 }
12134 Expr::Unary { expr: inner, .. } => {
12135 substitute_outer_in_expr(inner, outer_row, outer_schema);
12136 }
12137 Expr::FunctionCall { args, .. } => {
12138 for a in args {
12139 substitute_outer_in_expr(a, outer_row, outer_schema);
12140 }
12141 }
12142 Expr::Cast { expr: inner, .. } => {
12143 substitute_outer_in_expr(inner, outer_row, outer_schema);
12144 }
12145 Expr::Case {
12146 operand,
12147 branches,
12148 else_branch,
12149 } => {
12150 if let Some(op) = operand {
12151 substitute_outer_in_expr(op, outer_row, outer_schema);
12152 }
12153 for (cond, val) in branches {
12154 substitute_outer_in_expr(cond, outer_row, outer_schema);
12155 substitute_outer_in_expr(val, outer_row, outer_schema);
12156 }
12157 if let Some(e) = else_branch {
12158 substitute_outer_in_expr(e, outer_row, outer_schema);
12159 }
12160 }
12161 _ => {}
12162 }
12163}
12164
12165fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12166 let Some(outer_alias) = ctx.table_alias else {
12167 return;
12168 };
12169 substitute_in_select(stmt, row, ctx, outer_alias);
12170}
12171
12172fn substitute_in_select(
12173 stmt: &mut SelectStatement,
12174 row: &Row,
12175 ctx: &EvalContext<'_>,
12176 outer_alias: &str,
12177) {
12178 for item in &mut stmt.items {
12179 if let SelectItem::Expr { expr, .. } = item {
12180 substitute_in_expr(expr, row, ctx, outer_alias);
12181 }
12182 }
12183 if let Some(w) = &mut stmt.where_ {
12184 substitute_in_expr(w, row, ctx, outer_alias);
12185 }
12186 if let Some(gs) = &mut stmt.group_by {
12187 for g in gs {
12188 substitute_in_expr(g, row, ctx, outer_alias);
12189 }
12190 }
12191 if let Some(h) = &mut stmt.having {
12192 substitute_in_expr(h, row, ctx, outer_alias);
12193 }
12194 for o in &mut stmt.order_by {
12195 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12196 }
12197 for (_, peer) in &mut stmt.unions {
12198 substitute_in_select(peer, row, ctx, outer_alias);
12199 }
12200}
12201
12202fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12203 if let Expr::Column(c) = e
12204 && let Some(qual) = &c.qualifier
12205 && qual.eq_ignore_ascii_case(outer_alias)
12206 {
12207 if let Some(idx) = ctx
12209 .columns
12210 .iter()
12211 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12212 {
12213 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12214 if let Ok(lit) = value_to_literal_expr(v) {
12215 *e = lit;
12216 return;
12217 }
12218 }
12219 }
12220 match e {
12221 Expr::Binary { lhs, rhs, .. } => {
12222 substitute_in_expr(lhs, row, ctx, outer_alias);
12223 substitute_in_expr(rhs, row, ctx, outer_alias);
12224 }
12225 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12226 substitute_in_expr(expr, row, ctx, outer_alias);
12227 }
12228 Expr::Like { expr, pattern, .. } => {
12229 substitute_in_expr(expr, row, ctx, outer_alias);
12230 substitute_in_expr(pattern, row, ctx, outer_alias);
12231 }
12232 Expr::FunctionCall { args, .. } => {
12233 for a in args {
12234 substitute_in_expr(a, row, ctx, outer_alias);
12235 }
12236 }
12237 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12238 Expr::WindowFunction {
12239 args,
12240 partition_by,
12241 order_by,
12242 ..
12243 } => {
12244 for a in args {
12245 substitute_in_expr(a, row, ctx, outer_alias);
12246 }
12247 for p in partition_by {
12248 substitute_in_expr(p, row, ctx, outer_alias);
12249 }
12250 for (o, _) in order_by {
12251 substitute_in_expr(o, row, ctx, outer_alias);
12252 }
12253 }
12254 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12255 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12256 substitute_in_select(subquery, row, ctx, outer_alias);
12257 }
12258 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12259 Expr::Array(items) => {
12260 for elem in items {
12261 substitute_in_expr(elem, row, ctx, outer_alias);
12262 }
12263 }
12264 Expr::ArraySubscript { target, index } => {
12265 substitute_in_expr(target, row, ctx, outer_alias);
12266 substitute_in_expr(index, row, ctx, outer_alias);
12267 }
12268 Expr::AnyAll { expr, array, .. } => {
12269 substitute_in_expr(expr, row, ctx, outer_alias);
12270 substitute_in_expr(array, row, ctx, outer_alias);
12271 }
12272 Expr::Case {
12273 operand,
12274 branches,
12275 else_branch,
12276 } => {
12277 if let Some(o) = operand {
12278 substitute_in_expr(o, row, ctx, outer_alias);
12279 }
12280 for (w, t) in branches {
12281 substitute_in_expr(w, row, ctx, outer_alias);
12282 substitute_in_expr(t, row, ctx, outer_alias);
12283 }
12284 if let Some(e) = else_branch {
12285 substitute_in_expr(e, row, ctx, outer_alias);
12286 }
12287 }
12288 }
12289}
12290
12291fn encode_row_key(row: &Row) -> Vec<u8> {
12295 let mut out = Vec::new();
12296 for v in &row.values {
12297 let s = alloc::format!("{v:?}|");
12298 out.extend_from_slice(s.as_bytes());
12299 }
12300 out
12301}
12302
12303fn select_has_window(stmt: &SelectStatement) -> bool {
12304 for item in &stmt.items {
12305 if let SelectItem::Expr { expr, .. } = item
12306 && expr_has_window(expr)
12307 {
12308 return true;
12309 }
12310 }
12311 false
12312}
12313
12314fn expr_has_window(e: &Expr) -> bool {
12315 match e {
12316 Expr::WindowFunction { .. } => true,
12317 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12318 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12319 expr_has_window(expr)
12320 }
12321 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12322 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12323 Expr::Extract { source, .. } => expr_has_window(source),
12324 Expr::ScalarSubquery(_)
12325 | Expr::Exists { .. }
12326 | Expr::InSubquery { .. }
12327 | Expr::Literal(_)
12328 | Expr::Placeholder(_)
12329 | Expr::Column(_) => false,
12330 Expr::Array(items) => items.iter().any(expr_has_window),
12331 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12332 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12333 Expr::Case {
12334 operand,
12335 branches,
12336 else_branch,
12337 } => {
12338 operand.as_deref().is_some_and(expr_has_window)
12339 || branches
12340 .iter()
12341 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12342 || else_branch.as_deref().is_some_and(expr_has_window)
12343 }
12344 }
12345}
12346
12347fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12348 if let Expr::WindowFunction { .. } = e {
12349 if !out.iter().any(|x| x == e) {
12354 out.push(e.clone());
12355 }
12356 return;
12357 }
12358 match e {
12359 Expr::WindowFunction { .. } => unreachable!(),
12361 Expr::Binary { lhs, rhs, .. } => {
12362 collect_window_nodes(lhs, out);
12363 collect_window_nodes(rhs, out);
12364 }
12365 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12366 collect_window_nodes(expr, out);
12367 }
12368 Expr::FunctionCall { args, .. } => {
12369 for a in args {
12370 collect_window_nodes(a, out);
12371 }
12372 }
12373 Expr::Like { expr, pattern, .. } => {
12374 collect_window_nodes(expr, out);
12375 collect_window_nodes(pattern, out);
12376 }
12377 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12378 _ => {}
12379 }
12380}
12381
12382fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12383 if let Expr::WindowFunction { .. } = e
12384 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12385 {
12386 *e = Expr::Column(spg_sql::ast::ColumnName {
12387 qualifier: None,
12388 name: alloc::format!("__win_{idx}"),
12389 });
12390 return;
12391 }
12392 match e {
12393 Expr::Binary { lhs, rhs, .. } => {
12394 rewrite_window_to_columns(lhs, window_nodes);
12395 rewrite_window_to_columns(rhs, window_nodes);
12396 }
12397 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12398 rewrite_window_to_columns(expr, window_nodes);
12399 }
12400 Expr::FunctionCall { args, .. } => {
12401 for a in args {
12402 rewrite_window_to_columns(a, window_nodes);
12403 }
12404 }
12405 Expr::Like { expr, pattern, .. } => {
12406 rewrite_window_to_columns(expr, window_nodes);
12407 rewrite_window_to_columns(pattern, window_nodes);
12408 }
12409 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12410 _ => {}
12411 }
12412}
12413
12414fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12418 for (x, y) in a.iter().zip(b.iter()) {
12419 let c = value_cmp(x, y);
12420 if c != core::cmp::Ordering::Equal {
12421 return c;
12422 }
12423 }
12424 a.len().cmp(&b.len())
12425}
12426
12427fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12428 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12429 let c = value_cmp(va, vb);
12430 let c = if *desc { c.reverse() } else { c };
12431 if c != core::cmp::Ordering::Equal {
12432 return c;
12433 }
12434 }
12435 a.len().cmp(&b.len())
12436}
12437
12438const fn value_is_integer(v: &Value) -> bool {
12444 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12445}
12446
12447const fn value_to_i64(v: &Value) -> i64 {
12451 match v {
12452 Value::SmallInt(n) => *n as i64,
12453 Value::Int(n) => *n as i64,
12454 Value::BigInt(n) => *n,
12455 _ => panic!("value_to_i64 called on non-integer Value"),
12456 }
12457}
12458
12459fn generate_series_integers(
12465 start: i64,
12466 stop: i64,
12467 step: i64,
12468 cancel: &CancelToken<'_>,
12469) -> Result<alloc::vec::Vec<Row>, EngineError> {
12470 if step == 0 {
12471 return Err(EngineError::Unsupported(
12472 "generate_series(): step argument cannot be zero".into(),
12473 ));
12474 }
12475 let mut out = alloc::vec::Vec::new();
12476 let mut cur = start;
12477 const MAX_ROWS: usize = 10_000_000;
12481 loop {
12482 cancel.check()?;
12483 if step > 0 && cur > stop {
12484 break;
12485 }
12486 if step < 0 && cur < stop {
12487 break;
12488 }
12489 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12490 if out.len() > MAX_ROWS {
12491 return Err(EngineError::Unsupported(alloc::format!(
12492 "generate_series(): exceeded {MAX_ROWS} rows; \
12493 narrow start/stop or use a larger step"
12494 )));
12495 }
12496 cur = match cur.checked_add(step) {
12497 Some(n) => n,
12498 None => break,
12499 };
12500 }
12501 Ok(out)
12502}
12503
12504fn generate_series_timestamps(
12509 start: i64,
12510 stop: i64,
12511 step: Value,
12512 cancel: &CancelToken<'_>,
12513) -> Result<alloc::vec::Vec<Row>, EngineError> {
12514 let (months, micros) = match &step {
12515 Value::Interval { months, micros } => (*months, *micros),
12516 _ => unreachable!("caller guards step.is_interval"),
12517 };
12518 if months == 0 && micros == 0 {
12519 return Err(EngineError::Unsupported(
12520 "generate_series(): INTERVAL step cannot be zero".into(),
12521 ));
12522 }
12523 let ascending = months > 0 || micros > 0;
12524 let mut out = alloc::vec::Vec::new();
12525 let mut cur = Value::Timestamp(start);
12526 const MAX_ROWS: usize = 10_000_000;
12527 loop {
12528 cancel.check()?;
12529 let cur_t = match cur {
12530 Value::Timestamp(t) => t,
12531 _ => unreachable!("loop invariant: cur is Timestamp"),
12532 };
12533 if ascending && cur_t > stop {
12534 break;
12535 }
12536 if !ascending && cur_t < stop {
12537 break;
12538 }
12539 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12540 if out.len() > MAX_ROWS {
12541 return Err(EngineError::Unsupported(alloc::format!(
12542 "generate_series(): exceeded {MAX_ROWS} rows; \
12543 narrow start/stop or use a larger step"
12544 )));
12545 }
12546 let next = eval::apply_binary_interval(
12547 spg_sql::ast::BinOp::Add,
12548 &cur,
12549 &Value::Interval { months, micros },
12550 )
12551 .map_err(EngineError::Eval)?;
12552 cur = match next {
12553 Some(v) => v,
12554 None => break,
12555 };
12556 }
12557 Ok(out)
12558}
12559
12560#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12562 use core::cmp::Ordering;
12563 match (a, b) {
12564 (Value::Null, Value::Null) => Ordering::Equal,
12565 (Value::Null, _) => Ordering::Less,
12566 (_, Value::Null) => Ordering::Greater,
12567 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12568 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12569 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12570 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12571 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12572 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12573 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12574 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12575 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12578 }
12579}
12580
12581#[allow(
12587 clippy::too_many_arguments,
12588 clippy::cast_possible_truncation,
12589 clippy::cast_possible_wrap,
12590 clippy::cast_precision_loss,
12591 clippy::cast_sign_loss,
12592 clippy::doc_markdown,
12593 clippy::too_many_lines,
12594 clippy::type_complexity,
12595 clippy::match_same_arms
12596)]
12597fn compute_window_partition(
12598 name: &str,
12599 args: &[Expr],
12600 ordered: bool,
12601 frame: Option<&WindowFrame>,
12602 null_treatment: spg_sql::ast::NullTreatment,
12603 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12604 filtered_rows: &[&Row],
12605 ctx: &EvalContext<'_>,
12606 out_vals: &mut [Value],
12607) -> Result<(), EngineError> {
12608 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12609 let lower = name.to_ascii_lowercase();
12610 match lower.as_str() {
12611 "row_number" => {
12612 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12613 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12614 }
12615 Ok(())
12616 }
12617 "rank" => {
12618 let mut prev_key: Option<&[(Value, bool)]> = None;
12619 let mut current_rank: i64 = 1;
12620 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12621 if let Some(p) = prev_key
12622 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12623 {
12624 current_rank = (i + 1) as i64;
12625 }
12626 if prev_key.is_none() {
12627 current_rank = 1;
12628 }
12629 out_vals[*idx] = Value::BigInt(current_rank);
12630 prev_key = Some(okey.as_slice());
12631 }
12632 Ok(())
12633 }
12634 "dense_rank" => {
12635 let mut prev_key: Option<&[(Value, bool)]> = None;
12636 let mut current_rank: i64 = 0;
12637 for (_, okey, idx) in slice {
12638 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12639 current_rank += 1;
12640 }
12641 out_vals[*idx] = Value::BigInt(current_rank);
12642 prev_key = Some(okey.as_slice());
12643 }
12644 Ok(())
12645 }
12646 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12647 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12650 slice.iter().map(|_| Value::Null).collect()
12651 } else {
12652 slice
12653 .iter()
12654 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12655 .collect::<Result<_, _>>()
12656 .map_err(EngineError::Eval)?
12657 };
12658 let eff = effective_frame(frame, ordered)?;
12662 #[allow(clippy::needless_range_loop)]
12663 for i in 0..slice.len() {
12664 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12665 let mut sum: f64 = 0.0;
12666 let mut count: i64 = 0;
12667 let mut min_v: Option<f64> = None;
12668 let mut max_v: Option<f64> = None;
12669 let mut row_count: i64 = 0;
12670 if lo <= hi {
12671 for j in lo..=hi {
12672 let v = &arg_values[j];
12673 match lower.as_str() {
12674 "count_star" => row_count += 1,
12675 "count" => {
12676 if !v.is_null() {
12677 count += 1;
12678 }
12679 }
12680 _ => {
12681 if let Some(x) = value_to_f64(v) {
12682 sum += x;
12683 count += 1;
12684 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12685 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12686 }
12687 }
12688 }
12689 }
12690 }
12691 let value = match lower.as_str() {
12692 "count_star" => Value::BigInt(row_count),
12693 "count" => Value::BigInt(count),
12694 "sum" => Value::Float(sum),
12695 "avg" => {
12696 if count == 0 {
12697 Value::Null
12698 } else {
12699 Value::Float(sum / count as f64)
12700 }
12701 }
12702 "min" => min_v.map_or(Value::Null, Value::Float),
12703 "max" => max_v.map_or(Value::Null, Value::Float),
12704 _ => unreachable!(),
12705 };
12706 let (_, _, idx) = &slice[i];
12707 out_vals[*idx] = value;
12708 }
12709 Ok(())
12710 }
12711 "lag" | "lead" => {
12712 if args.is_empty() {
12715 return Err(EngineError::Unsupported(alloc::format!(
12716 "{lower}() requires at least one argument"
12717 )));
12718 }
12719 let offset: i64 = if args.len() >= 2 {
12720 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12721 .map_err(EngineError::Eval)?;
12722 match v {
12723 Value::SmallInt(n) => i64::from(n),
12724 Value::Int(n) => i64::from(n),
12725 Value::BigInt(n) => n,
12726 _ => {
12727 return Err(EngineError::Unsupported(alloc::format!(
12728 "{lower}() offset must be integer"
12729 )));
12730 }
12731 }
12732 } else {
12733 1
12734 };
12735 let default: Value = if args.len() >= 3 {
12736 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12737 .map_err(EngineError::Eval)?
12738 } else {
12739 Value::Null
12740 };
12741 let values: Vec<Value> = slice
12742 .iter()
12743 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12744 .collect::<Result<_, _>>()
12745 .map_err(EngineError::Eval)?;
12746 let n = slice.len();
12747 for (i, (_, _, idx)) in slice.iter().enumerate() {
12748 let signed_offset = if lower == "lag" { -offset } else { offset };
12749 let v = if ignore_nulls {
12750 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12754 let needed: i64 = signed_offset.abs();
12755 if needed == 0 {
12756 values[i].clone()
12757 } else {
12758 let mut j: i64 = i as i64;
12759 let mut hits: i64 = 0;
12760 let mut found: Option<Value> = None;
12761 loop {
12762 j += step;
12763 if j < 0 || j >= n as i64 {
12764 break;
12765 }
12766 #[allow(clippy::cast_sign_loss)]
12767 let v = &values[j as usize];
12768 if !v.is_null() {
12769 hits += 1;
12770 if hits == needed {
12771 found = Some(v.clone());
12772 break;
12773 }
12774 }
12775 }
12776 found.unwrap_or_else(|| default.clone())
12777 }
12778 } else {
12779 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12780 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12781 default.clone()
12782 } else {
12783 #[allow(clippy::cast_sign_loss)]
12784 {
12785 values[target_signed as usize].clone()
12786 }
12787 }
12788 };
12789 out_vals[*idx] = v;
12790 }
12791 Ok(())
12792 }
12793 "first_value" | "last_value" | "nth_value" => {
12794 if args.is_empty() {
12795 return Err(EngineError::Unsupported(alloc::format!(
12796 "{lower}() requires at least one argument"
12797 )));
12798 }
12799 let values: Vec<Value> = slice
12800 .iter()
12801 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12802 .collect::<Result<_, _>>()
12803 .map_err(EngineError::Eval)?;
12804 let nth: usize = if lower == "nth_value" {
12805 if args.len() < 2 {
12806 return Err(EngineError::Unsupported(
12807 "nth_value() requires (expr, n)".into(),
12808 ));
12809 }
12810 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12811 .map_err(EngineError::Eval)?;
12812 let raw = match v {
12813 Value::SmallInt(n) => i64::from(n),
12814 Value::Int(n) => i64::from(n),
12815 Value::BigInt(n) => n,
12816 _ => {
12817 return Err(EngineError::Unsupported(
12818 "nth_value() n must be integer".into(),
12819 ));
12820 }
12821 };
12822 if raw < 1 {
12823 return Err(EngineError::Unsupported(
12824 "nth_value() n must be >= 1".into(),
12825 ));
12826 }
12827 #[allow(clippy::cast_sign_loss)]
12828 {
12829 raw as usize
12830 }
12831 } else {
12832 0
12833 };
12834 let eff = effective_frame(frame, ordered)?;
12835 for i in 0..slice.len() {
12836 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12837 let (_, _, idx) = &slice[i];
12838 let v = if lo > hi {
12839 Value::Null
12840 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
12841 if lower == "first_value" {
12844 (lo..=hi)
12845 .find_map(|j| {
12846 let v = &values[j];
12847 (!v.is_null()).then(|| v.clone())
12848 })
12849 .unwrap_or(Value::Null)
12850 } else {
12851 (lo..=hi)
12852 .rev()
12853 .find_map(|j| {
12854 let v = &values[j];
12855 (!v.is_null()).then(|| v.clone())
12856 })
12857 .unwrap_or(Value::Null)
12858 }
12859 } else {
12860 match lower.as_str() {
12861 "first_value" => values[lo].clone(),
12862 "last_value" => values[hi].clone(),
12863 "nth_value" => {
12864 let pos = lo + nth - 1;
12865 if pos > hi {
12866 Value::Null
12867 } else {
12868 values[pos].clone()
12869 }
12870 }
12871 _ => unreachable!(),
12872 }
12873 };
12874 out_vals[*idx] = v;
12875 }
12876 Ok(())
12877 }
12878 "ntile" => {
12879 if args.is_empty() {
12880 return Err(EngineError::Unsupported(
12881 "ntile(n) requires an integer argument".into(),
12882 ));
12883 }
12884 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
12885 .map_err(EngineError::Eval)?;
12886 let bucket_count: i64 = match v {
12887 Value::SmallInt(n) => i64::from(n),
12888 Value::Int(n) => i64::from(n),
12889 Value::BigInt(n) => n,
12890 _ => {
12891 return Err(EngineError::Unsupported(
12892 "ntile() argument must be integer".into(),
12893 ));
12894 }
12895 };
12896 if bucket_count < 1 {
12897 return Err(EngineError::Unsupported(
12898 "ntile() argument must be >= 1".into(),
12899 ));
12900 }
12901 #[allow(clippy::cast_sign_loss)]
12902 let buckets = bucket_count as usize;
12903 let n = slice.len();
12904 let base = n / buckets;
12907 let extras = n % buckets;
12908 let mut bucket: usize = 1;
12909 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
12910 let mut buckets_with_extra_remaining = extras;
12911 for (_, _, idx) in slice {
12912 if remaining_in_bucket == 0 {
12913 bucket += 1;
12914 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
12915 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
12916 base + 1
12917 } else {
12918 base
12919 };
12920 if remaining_in_bucket == 0 {
12923 remaining_in_bucket = 1;
12924 }
12925 }
12926 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
12927 remaining_in_bucket -= 1;
12928 }
12929 Ok(())
12930 }
12931 "percent_rank" => {
12932 let n = slice.len();
12935 let mut prev_key: Option<&[(Value, bool)]> = None;
12936 let mut current_rank: i64 = 1;
12937 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12938 if let Some(p) = prev_key
12939 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12940 {
12941 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
12942 }
12943 if prev_key.is_none() {
12944 current_rank = 1;
12945 }
12946 #[allow(clippy::cast_precision_loss)]
12947 let pr = if n <= 1 {
12948 0.0
12949 } else {
12950 (current_rank - 1) as f64 / (n - 1) as f64
12951 };
12952 out_vals[*idx] = Value::Float(pr);
12953 prev_key = Some(okey.as_slice());
12954 }
12955 Ok(())
12956 }
12957 "cume_dist" => {
12958 let n = slice.len();
12960 for i in 0..slice.len() {
12962 let peer_end = peer_group_end(slice, i);
12963 #[allow(clippy::cast_precision_loss)]
12964 let cd = (peer_end + 1) as f64 / n as f64;
12965 let (_, _, idx) = &slice[i];
12966 out_vals[*idx] = Value::Float(cd);
12967 }
12968 Ok(())
12969 }
12970 other => Err(EngineError::Unsupported(alloc::format!(
12971 "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)"
12972 ))),
12973 }
12974}
12975
12976fn effective_frame(
12983 frame: Option<&WindowFrame>,
12984 ordered: bool,
12985) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
12986 match frame {
12987 None => {
12988 if ordered {
12989 Ok((
12990 FrameKind::Range,
12991 FrameBound::UnboundedPreceding,
12992 FrameBound::CurrentRow,
12993 ))
12994 } else {
12995 Ok((
12996 FrameKind::Rows,
12997 FrameBound::UnboundedPreceding,
12998 FrameBound::UnboundedFollowing,
12999 ))
13000 }
13001 }
13002 Some(fr) => {
13003 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13004 if matches!(fr.start, FrameBound::UnboundedFollowing)
13006 || matches!(end, FrameBound::UnboundedPreceding)
13007 {
13008 return Err(EngineError::Unsupported(alloc::format!(
13009 "invalid frame: start={:?} end={:?}",
13010 fr.start,
13011 end
13012 )));
13013 }
13014 if fr.kind == FrameKind::Range
13019 && (matches!(
13020 fr.start,
13021 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13022 ) || matches!(
13023 end,
13024 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13025 ))
13026 {
13027 return Err(EngineError::Unsupported(
13028 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13029 ));
13030 }
13031 Ok((fr.kind, fr.start.clone(), end))
13032 }
13033 }
13034}
13035
13036#[allow(clippy::type_complexity)]
13040fn frame_bounds_for_row(
13041 eff: &(FrameKind, FrameBound, FrameBound),
13042 i: usize,
13043 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
13044) -> (usize, usize) {
13045 let (kind, start, end) = eff;
13046 let n = slice.len();
13047 let last = n.saturating_sub(1);
13048 let (mut lo, mut hi) = match kind {
13049 FrameKind::Rows => {
13050 let lo = match start {
13051 FrameBound::UnboundedPreceding => 0,
13052 FrameBound::OffsetPreceding(k) => {
13053 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13054 i.saturating_sub(k)
13055 }
13056 FrameBound::CurrentRow => i,
13057 FrameBound::OffsetFollowing(k) => {
13058 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13059 i.saturating_add(k).min(last)
13060 }
13061 FrameBound::UnboundedFollowing => last,
13062 };
13063 let hi = match end {
13064 FrameBound::UnboundedPreceding => 0,
13065 FrameBound::OffsetPreceding(k) => {
13066 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13067 i.saturating_sub(k)
13068 }
13069 FrameBound::CurrentRow => i,
13070 FrameBound::OffsetFollowing(k) => {
13071 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13072 i.saturating_add(k).min(last)
13073 }
13074 FrameBound::UnboundedFollowing => last,
13075 };
13076 (lo, hi)
13077 }
13078 FrameKind::Range => {
13079 let lo = match start {
13085 FrameBound::UnboundedPreceding => 0,
13086 FrameBound::CurrentRow => peer_group_start(slice, i),
13087 FrameBound::UnboundedFollowing => last,
13088 _ => unreachable!("offset bounds rejected for RANGE"),
13089 };
13090 let hi = match end {
13091 FrameBound::UnboundedPreceding => 0,
13092 FrameBound::CurrentRow => peer_group_end(slice, i),
13093 FrameBound::UnboundedFollowing => last,
13094 _ => unreachable!("offset bounds rejected for RANGE"),
13095 };
13096 (lo, hi)
13097 }
13098 };
13099 if hi >= n {
13100 hi = last;
13101 }
13102 if lo >= n {
13103 lo = last;
13104 }
13105 (lo, hi)
13106}
13107
13108#[allow(clippy::type_complexity)]
13112fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13113 let key = &slice[i].1;
13114 let mut j = i;
13115 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13116 j -= 1;
13117 }
13118 j
13119}
13120
13121#[allow(clippy::type_complexity)]
13124fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13125 let key = &slice[i].1;
13126 let mut j = i;
13127 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13128 j += 1;
13129 }
13130 j
13131}
13132
13133fn value_to_f64(v: &Value) -> Option<f64> {
13134 match v {
13135 Value::SmallInt(n) => Some(f64::from(*n)),
13136 Value::Int(n) => Some(f64::from(*n)),
13137 #[allow(clippy::cast_precision_loss)]
13138 Value::BigInt(n) => Some(*n as f64),
13139 Value::Float(x) => Some(*x),
13140 _ => None,
13141 }
13142}
13143
13144fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13148 let mut any = false;
13149 for item in &stmt.items {
13150 if let SelectItem::Expr { expr, .. } = item {
13151 any = any || expr_has_subquery(expr);
13152 }
13153 }
13154 if let Some(w) = &stmt.where_ {
13155 any = any || expr_has_subquery(w);
13156 }
13157 if let Some(h) = &stmt.having {
13158 any = any || expr_has_subquery(h);
13159 }
13160 for o in &stmt.order_by {
13161 any = any || expr_has_subquery(&o.expr);
13162 }
13163 for (_, peer) in &stmt.unions {
13164 any = any || expr_tree_has_subquery(peer);
13165 }
13166 any
13167}
13168
13169fn expr_has_subquery(e: &Expr) -> bool {
13170 match e {
13171 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13172 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13173 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13174 expr_has_subquery(expr)
13175 }
13176 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13177 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13178 Expr::Extract { source, .. } => expr_has_subquery(source),
13179 Expr::WindowFunction {
13180 args,
13181 partition_by,
13182 order_by,
13183 ..
13184 } => {
13185 args.iter().any(expr_has_subquery)
13186 || partition_by.iter().any(expr_has_subquery)
13187 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13188 }
13189 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13190 Expr::Array(items) => items.iter().any(expr_has_subquery),
13191 Expr::ArraySubscript { target, index } => {
13192 expr_has_subquery(target) || expr_has_subquery(index)
13193 }
13194 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13195 Expr::Case {
13196 operand,
13197 branches,
13198 else_branch,
13199 } => {
13200 operand.as_deref().is_some_and(expr_has_subquery)
13201 || branches
13202 .iter()
13203 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13204 || else_branch.as_deref().is_some_and(expr_has_subquery)
13205 }
13206 }
13207}
13208
13209fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13216 let lit = match v {
13217 Value::Null => Literal::Null,
13218 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13219 Value::Int(n) => Literal::Integer(i64::from(n)),
13220 Value::BigInt(n) => Literal::Integer(n),
13221 Value::Float(x) => Literal::Float(x),
13222 Value::Text(s) | Value::Json(s) => Literal::String(s),
13223 Value::Bool(b) => Literal::Bool(b),
13224 other => {
13225 return Err(EngineError::Unsupported(alloc::format!(
13226 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13227 other.data_type()
13228 )));
13229 }
13230 };
13231 Ok(Expr::Literal(lit))
13232}
13233
13234fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13240 let lit = match v {
13241 Value::Null => Literal::Null,
13242 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13243 Value::Int(n) => Literal::Integer(i64::from(n)),
13244 Value::BigInt(n) => Literal::Integer(n),
13245 Value::Float(x) => Literal::Float(x),
13246 Value::Text(s) | Value::Json(s) => Literal::String(s),
13247 Value::Bool(b) => Literal::Bool(b),
13248 Value::Vector(xs) => Literal::Vector(xs),
13249 Value::Date(days) => {
13253 let micros = (i64::from(days)) * 86_400_000_000;
13254 Literal::String(format_timestamp_micros_as_date(micros))
13255 }
13256 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13257 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13258 other => {
13259 return Err(EngineError::Unsupported(alloc::format!(
13260 "INSERT … SELECT cannot materialise value of type {:?}; \
13261 add an explicit CAST in the inner SELECT",
13262 other.data_type()
13263 )));
13264 }
13265 };
13266 Ok(Expr::Literal(lit))
13267}
13268
13269fn format_timestamp_micros(us: i64) -> String {
13270 let days = us.div_euclid(86_400_000_000);
13272 let intra_day = us.rem_euclid(86_400_000_000);
13273 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13274 let secs = intra_day / 1_000_000;
13275 let us_rem = intra_day % 1_000_000;
13276 let h = (secs / 3600) % 24;
13277 let m = (secs / 60) % 60;
13278 let s = secs % 60;
13279 if us_rem == 0 {
13280 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13281 } else {
13282 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13283 }
13284}
13285
13286fn format_timestamp_micros_as_date(us: i64) -> String {
13287 let days = us.div_euclid(86_400_000_000);
13290 let jdn = days + 2_440_588;
13292 let (y, mo, d) = jdn_to_ymd(jdn);
13293 alloc::format!("{y:04}-{mo:02}-{d:02}")
13294}
13295
13296fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13297 let l = jdn + 68569;
13299 let n = (4 * l) / 146_097;
13300 let l = l - (146_097 * n + 3) / 4;
13301 let i = (4000 * (l + 1)) / 1_461_001;
13302 let l = l - (1461 * i) / 4 + 31;
13303 let j = (80 * l) / 2447;
13304 let day = (l - (2447 * j) / 80) as u32;
13305 let l = j / 11;
13306 let month = (j + 2 - 12 * l) as u32;
13307 let year = 100 * (n - 49) + i + l;
13308 (year, month, day)
13309}
13310
13311fn format_numeric(scaled: i128, scale: u8) -> String {
13312 if scale == 0 {
13313 return alloc::format!("{scaled}");
13314 }
13315 let abs = scaled.unsigned_abs();
13316 let divisor = 10u128.pow(u32::from(scale));
13317 let whole = abs / divisor;
13318 let frac = abs % divisor;
13319 let sign = if scaled < 0 { "-" } else { "" };
13320 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13321}
13322
13323fn rewrite_column_in_source(
13347 src: &str,
13348 old: &str,
13349 new: &str,
13350) -> Result<alloc::string::String, EngineError> {
13351 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13352 EngineError::Unsupported(alloc::format!(
13353 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13354 failed to parse for rewrite ({e})"
13355 ))
13356 })?;
13357 rewrite_column_in_expr(&mut expr, old, new);
13358 Ok(alloc::format!("{expr}"))
13359}
13360
13361fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13369 match e {
13370 Expr::Column(c) => {
13371 if c.name.eq_ignore_ascii_case(old) {
13372 c.name = new.to_string();
13373 }
13374 }
13375 Expr::Binary { lhs, rhs, .. } => {
13376 rewrite_column_in_expr(lhs, old, new);
13377 rewrite_column_in_expr(rhs, old, new);
13378 }
13379 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13380 rewrite_column_in_expr(expr, old, new);
13381 }
13382 Expr::FunctionCall { args, .. } => {
13383 for a in args {
13384 rewrite_column_in_expr(a, old, new);
13385 }
13386 }
13387 Expr::Like { expr, pattern, .. } => {
13388 rewrite_column_in_expr(expr, old, new);
13389 rewrite_column_in_expr(pattern, old, new);
13390 }
13391 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13392 Expr::WindowFunction {
13393 args,
13394 partition_by,
13395 order_by,
13396 ..
13397 } => {
13398 for a in args {
13399 rewrite_column_in_expr(a, old, new);
13400 }
13401 for p in partition_by {
13402 rewrite_column_in_expr(p, old, new);
13403 }
13404 for (o, _) in order_by {
13405 rewrite_column_in_expr(o, old, new);
13406 }
13407 }
13408 Expr::Array(items) => {
13409 for elem in items {
13410 rewrite_column_in_expr(elem, old, new);
13411 }
13412 }
13413 Expr::ArraySubscript { target, index } => {
13414 rewrite_column_in_expr(target, old, new);
13415 rewrite_column_in_expr(index, old, new);
13416 }
13417 Expr::AnyAll { expr, array, .. } => {
13418 rewrite_column_in_expr(expr, old, new);
13419 rewrite_column_in_expr(array, old, new);
13420 }
13421 Expr::Case {
13422 operand,
13423 branches,
13424 else_branch,
13425 } => {
13426 if let Some(o) = operand {
13427 rewrite_column_in_expr(o, old, new);
13428 }
13429 for (w, t) in branches {
13430 rewrite_column_in_expr(w, old, new);
13431 rewrite_column_in_expr(t, old, new);
13432 }
13433 if let Some(e) = else_branch {
13434 rewrite_column_in_expr(e, old, new);
13435 }
13436 }
13437 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13441 Expr::Literal(_) | Expr::Placeholder(_) => {}
13442 }
13443}
13444
13445pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13453 match stmt {
13454 Statement::Select(s) => substitute_select(s, params)?,
13455 Statement::Insert(ins) => {
13456 for row in &mut ins.rows {
13457 for e in row {
13458 substitute_expr(e, params)?;
13459 }
13460 }
13461 }
13462 Statement::Update(u) => {
13463 for (_, e) in &mut u.assignments {
13464 substitute_expr(e, params)?;
13465 }
13466 if let Some(w) = &mut u.where_ {
13467 substitute_expr(w, params)?;
13468 }
13469 }
13470 Statement::Delete(d) => {
13471 if let Some(w) = &mut d.where_ {
13472 substitute_expr(w, params)?;
13473 }
13474 }
13475 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13476 _ => {}
13479 }
13480 Ok(())
13481}
13482
13483fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13484 for item in &mut s.items {
13485 if let SelectItem::Expr { expr, .. } = item {
13486 substitute_expr(expr, params)?;
13487 }
13488 }
13489 if let Some(w) = &mut s.where_ {
13490 substitute_expr(w, params)?;
13491 }
13492 if let Some(gs) = &mut s.group_by {
13493 for g in gs {
13494 substitute_expr(g, params)?;
13495 }
13496 }
13497 if let Some(h) = &mut s.having {
13498 substitute_expr(h, params)?;
13499 }
13500 for o in &mut s.order_by {
13501 substitute_expr(&mut o.expr, params)?;
13502 }
13503 for (_, peer) in &mut s.unions {
13504 substitute_select(peer, params)?;
13505 }
13506 if let Some(le) = s.limit {
13511 s.limit = Some(resolve_limit_placeholder(le, params)?);
13512 }
13513 if let Some(le) = s.offset {
13514 s.offset = Some(resolve_limit_placeholder(le, params)?);
13515 }
13516 Ok(())
13517}
13518
13519fn resolve_limit_placeholder(
13520 le: spg_sql::ast::LimitExpr,
13521 params: &[Value],
13522) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13523 use spg_sql::ast::LimitExpr;
13524 match le {
13525 LimitExpr::Literal(_) => Ok(le),
13526 LimitExpr::Placeholder(n) => {
13527 let idx = usize::from(n).saturating_sub(1);
13528 let v = params.get(idx).ok_or_else(|| {
13529 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13530 n,
13531 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13532 })
13533 })?;
13534 let int = match v {
13535 Value::SmallInt(x) => Some(i64::from(*x)),
13536 Value::Int(x) => Some(i64::from(*x)),
13537 Value::BigInt(x) => Some(*x),
13538 _ => None,
13539 }
13540 .ok_or_else(|| {
13541 EngineError::Unsupported(alloc::format!(
13542 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13543 ))
13544 })?;
13545 if int < 0 {
13546 return Err(EngineError::Unsupported(alloc::format!(
13547 "LIMIT/OFFSET ${n} bound to negative value {int}"
13548 )));
13549 }
13550 let bounded = u32::try_from(int).map_err(|_| {
13551 EngineError::Unsupported(alloc::format!(
13552 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13553 ))
13554 })?;
13555 Ok(LimitExpr::Literal(bounded))
13556 }
13557 }
13558}
13559
13560fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13561 if let Expr::Placeholder(n) = e {
13562 let idx = usize::from(*n).saturating_sub(1);
13563 let v = params.get(idx).ok_or_else(|| {
13564 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13565 n: *n,
13566 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13567 })
13568 })?;
13569 *e = Expr::Literal(value_to_literal(v.clone()));
13570 return Ok(());
13571 }
13572 match e {
13573 Expr::Binary { lhs, rhs, .. } => {
13574 substitute_expr(lhs, params)?;
13575 substitute_expr(rhs, params)?;
13576 }
13577 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13578 substitute_expr(expr, params)?;
13579 }
13580 Expr::FunctionCall { args, .. } => {
13581 for a in args {
13582 substitute_expr(a, params)?;
13583 }
13584 }
13585 Expr::Like { expr, pattern, .. } => {
13586 substitute_expr(expr, params)?;
13587 substitute_expr(pattern, params)?;
13588 }
13589 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13590 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13591 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13592 Expr::InSubquery { expr, subquery, .. } => {
13593 substitute_expr(expr, params)?;
13594 substitute_select(subquery, params)?;
13595 }
13596 Expr::WindowFunction {
13597 args,
13598 partition_by,
13599 order_by,
13600 ..
13601 } => {
13602 for a in args {
13603 substitute_expr(a, params)?;
13604 }
13605 for p in partition_by {
13606 substitute_expr(p, params)?;
13607 }
13608 for (e, _) in order_by {
13609 substitute_expr(e, params)?;
13610 }
13611 }
13612 Expr::Literal(_) | Expr::Column(_) => {}
13613 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13615 Expr::Array(items) => {
13616 for elem in items {
13617 substitute_expr(elem, params)?;
13618 }
13619 }
13620 Expr::ArraySubscript { target, index } => {
13621 substitute_expr(target, params)?;
13622 substitute_expr(index, params)?;
13623 }
13624 Expr::AnyAll { expr, array, .. } => {
13625 substitute_expr(expr, params)?;
13626 substitute_expr(array, params)?;
13627 }
13628 Expr::Case {
13629 operand,
13630 branches,
13631 else_branch,
13632 } => {
13633 if let Some(o) = operand {
13634 substitute_expr(o, params)?;
13635 }
13636 for (w, t) in branches {
13637 substitute_expr(w, params)?;
13638 substitute_expr(t, params)?;
13639 }
13640 if let Some(e) = else_branch {
13641 substitute_expr(e, params)?;
13642 }
13643 }
13644 }
13645 Ok(())
13646}
13647
13648fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13666 use core::cmp::Ordering;
13667 match (a, b) {
13668 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13669 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13670 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13671 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13672 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13673 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13674 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13675 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13676 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13677 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13678 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13679 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13680 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13681 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13682 (Value::SmallInt(n), Value::Float(x)) => {
13684 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13685 }
13686 (Value::Float(x), Value::SmallInt(n)) => {
13687 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13688 }
13689 (Value::Int(n), Value::Float(x)) => {
13690 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13691 }
13692 (Value::Float(x), Value::Int(n)) => {
13693 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13694 }
13695 (Value::BigInt(n), Value::Float(x)) => {
13696 #[allow(clippy::cast_precision_loss)]
13697 let nf = *n as f64;
13698 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13699 }
13700 (Value::Float(x), Value::BigInt(n)) => {
13701 #[allow(clippy::cast_precision_loss)]
13702 let nf = *n as f64;
13703 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13704 }
13705 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13708 }
13709}
13710
13711fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13718 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13719 out.push('[');
13720 for (i, b) in bounds.iter().enumerate() {
13721 if i > 0 {
13722 out.push_str(", ");
13723 }
13724 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13725 if needs_quote {
13726 out.push('"');
13727 for ch in b.chars() {
13728 if ch == '"' || ch == '\\' {
13729 out.push('\\');
13730 }
13731 out.push(ch);
13732 }
13733 out.push('"');
13734 } else {
13735 out.push_str(b);
13736 }
13737 }
13738 out.push(']');
13739 out
13740}
13741
13742pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13752 match v {
13753 Value::Null => "NULL".to_string(),
13754 Value::SmallInt(n) => alloc::format!("{n}"),
13755 Value::Int(n) => alloc::format!("{n}"),
13756 Value::BigInt(n) => alloc::format!("{n}"),
13757 Value::Float(x) => alloc::format!("{x:?}"),
13758 Value::Text(s) | Value::Json(s) => s.clone(),
13759 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13760 Value::Date(d) => eval::format_date(*d),
13761 Value::Timestamp(t) => eval::format_timestamp(*t),
13762 Value::Time(us) => eval::format_time(*us),
13764 Value::Year(y) => alloc::format!("{y:04}"),
13766 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13768 Value::Money(c) => eval::format_money(*c),
13770 v @ Value::Range { .. } => format_range_str(v),
13772 Value::Hstore(pairs) => format_hstore_str(pairs),
13774 Value::IntArray2D(rows) => format_int_2d_text(rows),
13776 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13777 Value::TextArray2D(rows) => format_text_2d_text(rows),
13778 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13779 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13780 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13781 alloc::format!("{v:?}")
13785 }
13786 _ => alloc::format!("{v:?}"),
13790 }
13791}
13792
13793const fn is_internal_table_name(_name: &str) -> bool {
13800 false
13801}
13802
13803fn value_to_literal(v: Value) -> Literal {
13804 match v {
13805 Value::Null => Literal::Null,
13806 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13807 Value::Int(n) => Literal::Integer(i64::from(n)),
13808 Value::BigInt(n) => Literal::Integer(n),
13809 Value::Float(x) => Literal::Float(x),
13810 Value::Text(s) | Value::Json(s) => Literal::String(s),
13811 Value::Bool(b) => Literal::Bool(b),
13812 Value::Vector(v) => Literal::Vector(v),
13813 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13814 Value::Date(d) => Literal::String(eval::format_date(d)),
13815 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
13816 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
13822 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
13827 Value::TextArray(items) => Literal::String(eval::format_text_array(&items)),
13832 Value::IntArray(items) => Literal::String(eval::format_int_array(&items)),
13833 Value::BigIntArray(items) => Literal::String(eval::format_bigint_array(&items)),
13834 Value::Interval { months, micros } => Literal::Interval {
13835 months,
13836 micros,
13837 text: eval::format_interval(months, micros),
13838 },
13839 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
13842 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
13843 v => Literal::String(alloc::format!("{v:?}")),
13847 }
13848}
13849
13850fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
13851 let Some(now) = now_micros else {
13852 return;
13853 };
13854 match stmt {
13855 Statement::Select(s) => rewrite_select_clock(s, now),
13856 Statement::Insert(ins) => {
13857 for row in &mut ins.rows {
13858 for e in row {
13859 rewrite_expr_clock(e, now);
13860 }
13861 }
13862 }
13863 _ => {}
13864 }
13865}
13866
13867fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
13868 for item in &mut s.items {
13869 if let SelectItem::Expr { expr, .. } = item {
13870 rewrite_expr_clock(expr, now);
13871 }
13872 }
13873 if let Some(w) = &mut s.where_ {
13874 rewrite_expr_clock(w, now);
13875 }
13876 if let Some(gs) = &mut s.group_by {
13877 for g in gs {
13878 rewrite_expr_clock(g, now);
13879 }
13880 }
13881 if let Some(h) = &mut s.having {
13882 rewrite_expr_clock(h, now);
13883 }
13884 for o in &mut s.order_by {
13885 rewrite_expr_clock(&mut o.expr, now);
13886 }
13887 for (_, peer) in &mut s.unions {
13888 rewrite_select_clock(peer, now);
13889 }
13890}
13891
13892fn rewrite_expr_clock(e: &mut Expr, now: i64) {
13900 if let Some(replacement) = clock_replacement_for(e, now) {
13904 *e = replacement;
13905 return;
13906 }
13907 match e {
13908 Expr::Binary { lhs, rhs, .. } => {
13909 rewrite_expr_clock(lhs, now);
13910 rewrite_expr_clock(rhs, now);
13911 }
13912 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13913 rewrite_expr_clock(expr, now);
13914 }
13915 Expr::FunctionCall { args, .. } => {
13916 for a in args {
13917 rewrite_expr_clock(a, now);
13918 }
13919 }
13920 Expr::Like { expr, pattern, .. } => {
13921 rewrite_expr_clock(expr, now);
13922 rewrite_expr_clock(pattern, now);
13923 }
13924 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
13925 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
13929 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
13930 Expr::InSubquery { expr, subquery, .. } => {
13931 rewrite_expr_clock(expr, now);
13932 rewrite_select_clock(subquery, now);
13933 }
13934 Expr::WindowFunction {
13937 args,
13938 partition_by,
13939 order_by,
13940 ..
13941 } => {
13942 for a in args {
13943 rewrite_expr_clock(a, now);
13944 }
13945 for p in partition_by {
13946 rewrite_expr_clock(p, now);
13947 }
13948 for (e, _) in order_by {
13949 rewrite_expr_clock(e, now);
13950 }
13951 }
13952 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
13953 Expr::Array(items) => {
13954 for elem in items {
13955 rewrite_expr_clock(elem, now);
13956 }
13957 }
13958 Expr::ArraySubscript { target, index } => {
13959 rewrite_expr_clock(target, now);
13960 rewrite_expr_clock(index, now);
13961 }
13962 Expr::AnyAll { expr, array, .. } => {
13963 rewrite_expr_clock(expr, now);
13964 rewrite_expr_clock(array, now);
13965 }
13966 Expr::Case {
13967 operand,
13968 branches,
13969 else_branch,
13970 } => {
13971 if let Some(o) = operand {
13972 rewrite_expr_clock(o, now);
13973 }
13974 for (w, t) in branches {
13975 rewrite_expr_clock(w, now);
13976 rewrite_expr_clock(t, now);
13977 }
13978 if let Some(e) = else_branch {
13979 rewrite_expr_clock(e, now);
13980 }
13981 }
13982 }
13983}
13984
13985fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
13992 let (kind, name) = match e {
13993 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
13994 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
13995 _ => return None,
13996 };
13997 enum ClockShape {
14005 Timestamp,
14006 Date,
14007 UnixSeconds,
14008 }
14009 let shape = match name.len() {
14010 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14011 Some(ClockShape::Timestamp)
14012 }
14013 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14014 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14015 Some(ClockShape::UnixSeconds)
14016 }
14017 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14018 _ => None,
14019 };
14020 let shape = shape?;
14021 let payload = match shape {
14022 ClockShape::Timestamp => now,
14023 ClockShape::Date => now.div_euclid(86_400_000_000),
14024 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14025 };
14026 let target = match shape {
14027 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14028 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14029 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14030 };
14031 Some(Expr::Cast {
14032 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14033 target,
14034 })
14035}
14036
14037#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14038enum ClockSite {
14039 Fn,
14040 BareIdent,
14041}
14042
14043fn expand_group_by_all(s: &mut SelectStatement) {
14054 if !s.group_by_all {
14055 for (_, peer) in &mut s.unions {
14056 expand_group_by_all(peer);
14057 }
14058 return;
14059 }
14060 let mut groups: Vec<Expr> = Vec::new();
14061 for item in &s.items {
14062 if let SelectItem::Expr { expr, .. } = item
14063 && !aggregate::contains_aggregate(expr)
14064 {
14065 groups.push(expr.clone());
14066 }
14067 }
14068 s.group_by = Some(groups);
14069 s.group_by_all = false;
14070 for (_, peer) in &mut s.unions {
14071 expand_group_by_all(peer);
14072 }
14073}
14074
14075fn resolve_order_by_position(s: &mut SelectStatement) {
14076 for order in &mut s.order_by {
14081 match &order.expr {
14082 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14083 if let Ok(idx_one_based) = usize::try_from(*n) {
14084 let idx = idx_one_based - 1;
14085 if idx < s.items.len()
14086 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14087 {
14088 order.expr = expr.clone();
14089 }
14090 }
14091 }
14092 Expr::Column(c) if c.qualifier.is_none() => {
14093 for item in &s.items {
14095 if let SelectItem::Expr {
14096 expr,
14097 alias: Some(a),
14098 } = item
14099 && a == &c.name
14100 {
14101 order.expr = expr.clone();
14102 break;
14103 }
14104 }
14105 }
14106 _ => {}
14107 }
14108 }
14109 for (_, peer) in &mut s.unions {
14110 resolve_order_by_position(peer);
14111 }
14112}
14113
14114fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14127 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14128 match keep {
14129 Some(k) if k < tagged.len() && k > 0 => {
14130 let pivot = k - 1;
14131 tagged.select_nth_unstable_by(pivot, cmp);
14132 tagged[..k].sort_by(cmp);
14133 tagged.truncate(k);
14134 }
14135 _ => {
14136 tagged.sort_by(cmp);
14137 }
14138 }
14139}
14140
14141fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14142 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14143}
14144
14145fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14149 use core::cmp::Ordering;
14150 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14151 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14152 let ord = if descs.get(i).copied().unwrap_or(false) {
14153 ord.reverse()
14154 } else {
14155 ord
14156 };
14157 if ord != Ordering::Equal {
14158 return ord;
14159 }
14160 }
14161 Ordering::Equal
14162}
14163
14164fn build_order_keys(
14167 order_by: &[OrderBy],
14168 row: &Row,
14169 ctx: &EvalContext,
14170) -> Result<Vec<f64>, EngineError> {
14171 let mut keys = Vec::with_capacity(order_by.len());
14172 for o in order_by {
14173 let v = eval::eval_expr(&o.expr, row, ctx)?;
14174 keys.push(value_to_order_key(&v)?);
14175 }
14176 Ok(keys)
14177}
14178
14179fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14183 if let Some(off) = offset {
14184 let off = off as usize;
14185 if off >= rows.len() {
14186 rows.clear();
14187 } else {
14188 rows.drain(..off);
14189 }
14190 }
14191 if let Some(n) = limit {
14192 rows.truncate(n as usize);
14193 }
14194}
14195
14196fn apply_offset_and_limit_tagged(
14207 tagged: &mut Vec<(Vec<f64>, Row)>,
14208 offset: Option<u32>,
14209 limit: Option<u32>,
14210 with_ties: bool,
14211) {
14212 if let Some(off) = offset {
14213 let off = off as usize;
14214 if off >= tagged.len() {
14215 tagged.clear();
14216 } else {
14217 tagged.drain(..off);
14218 }
14219 }
14220 if let Some(n) = limit {
14221 let n = n as usize;
14222 if with_ties && n > 0 && n < tagged.len() {
14223 let cutoff_key = tagged[n - 1].0.clone();
14224 let mut end = n;
14225 while end < tagged.len() && tagged[end].0 == cutoff_key {
14226 end += 1;
14227 }
14228 tagged.truncate(end);
14229 } else {
14230 tagged.truncate(n);
14231 }
14232 }
14233}
14234
14235fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14241 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14242 return Err(EngineError::Unsupported(alloc::string::String::from(
14243 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14244 )));
14245 }
14246 Ok(())
14247}
14248
14249fn resolve_foreign_key(
14263 local_table_name: &str,
14264 local_cols: &[ColumnSchema],
14265 fk: spg_sql::ast::ForeignKeyConstraint,
14266 catalog: &Catalog,
14267) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14268 let mut local_columns = Vec::with_capacity(fk.columns.len());
14270 for name in &fk.columns {
14271 let pos = local_cols
14272 .iter()
14273 .position(|c| c.name == *name)
14274 .ok_or_else(|| {
14275 EngineError::Unsupported(alloc::format!(
14276 "FOREIGN KEY references unknown local column {name:?}"
14277 ))
14278 })?;
14279 local_columns.push(pos);
14280 }
14281 let is_self_ref = fk.parent_table == local_table_name;
14285 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14286 (local_cols, local_table_name)
14287 } else {
14288 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14289 EngineError::Storage(StorageError::TableNotFound {
14290 name: fk.parent_table.clone(),
14291 })
14292 })?;
14293 (
14294 parent_table.schema().columns.as_slice(),
14295 fk.parent_table.as_str(),
14296 )
14297 };
14298 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14303 if fk.columns.len() != 1 {
14304 return Err(EngineError::Unsupported(
14305 "composite FOREIGN KEY without explicit parent column list is not supported \
14306 — list the parent columns explicitly"
14307 .into(),
14308 ));
14309 }
14310 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14312 .ok_or_else(|| {
14313 EngineError::Unsupported(alloc::format!(
14314 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14315 to default the FOREIGN KEY against"
14316 ))
14317 })?;
14318 alloc::vec![pos]
14319 } else {
14320 let mut out = Vec::with_capacity(fk.parent_columns.len());
14321 for name in &fk.parent_columns {
14322 let pos = parent_cols_for_lookup
14323 .iter()
14324 .position(|c| c.name == *name)
14325 .ok_or_else(|| {
14326 EngineError::Unsupported(alloc::format!(
14327 "FOREIGN KEY references unknown parent column \
14328 {name:?} on table {parent_table_str:?}"
14329 ))
14330 })?;
14331 out.push(pos);
14332 }
14333 out
14334 };
14335 if parent_columns.len() != local_columns.len() {
14336 return Err(EngineError::Unsupported(alloc::format!(
14337 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14338 local_columns.len(),
14339 parent_columns.len()
14340 )));
14341 }
14342 if !is_self_ref {
14352 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14353 let primary_parent_col = parent_columns[0];
14354 let has_btree = parent_table
14355 .schema()
14356 .columns
14357 .get(primary_parent_col)
14358 .is_some()
14359 && parent_table.indices().iter().any(|idx| {
14360 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14361 && idx.column_position == primary_parent_col
14362 && idx.partial_predicate.is_none()
14363 });
14364 if !has_btree {
14365 return Err(EngineError::Unsupported(alloc::format!(
14366 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14367 index — create one with `CREATE INDEX ... ON {} ({})` first",
14368 parent_table_str,
14369 parent_table_str,
14370 parent_table.schema().columns[primary_parent_col].name,
14371 )));
14372 }
14373 }
14374 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14375 let on_update = fk_action_sql_to_storage(fk.on_update);
14376 Ok(spg_storage::ForeignKeyConstraint {
14377 name: fk.name,
14378 local_columns,
14379 parent_table: fk.parent_table,
14380 parent_columns,
14381 on_delete,
14382 on_update,
14383 })
14384}
14385
14386fn pick_pk_index_column(
14392 catalog: &Catalog,
14393 parent_name: &str,
14394 is_self_ref: bool,
14395 local_cols: &[ColumnSchema],
14396) -> Option<usize> {
14397 if is_self_ref {
14398 let _ = local_cols;
14402 return Some(0);
14403 }
14404 let parent = catalog.get(parent_name)?;
14405 parent.indices().iter().find_map(|idx| {
14406 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14407 && idx.partial_predicate.is_none()
14408 && idx.included_columns.is_empty()
14409 && idx.expression.is_none()
14410 {
14411 Some(idx.column_position)
14412 } else {
14413 None
14414 }
14415 })
14416}
14417
14418fn resolve_on_conflict_columns(
14425 catalog: &Catalog,
14426 table_name: &str,
14427 target: &[String],
14428) -> Result<Vec<usize>, EngineError> {
14429 let table = catalog.get(table_name).ok_or_else(|| {
14430 EngineError::Storage(StorageError::TableNotFound {
14431 name: table_name.into(),
14432 })
14433 })?;
14434 if target.is_empty() {
14435 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14445 return Ok(uc.columns.clone());
14446 }
14447 let pos = table
14448 .indices()
14449 .iter()
14450 .find_map(|idx| {
14451 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14452 && idx.partial_predicate.is_none()
14453 && idx.included_columns.is_empty()
14454 && idx.expression.is_none()
14455 {
14456 Some(idx.column_position)
14457 } else {
14458 None
14459 }
14460 })
14461 .ok_or_else(|| {
14462 EngineError::Unsupported(alloc::format!(
14463 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14464 ))
14465 })?;
14466 return Ok(alloc::vec![pos]);
14467 }
14468 let mut out = Vec::with_capacity(target.len());
14469 for name in target {
14470 let pos = table
14471 .schema()
14472 .columns
14473 .iter()
14474 .position(|c| c.name == *name)
14475 .ok_or_else(|| {
14476 EngineError::Unsupported(alloc::format!(
14477 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14478 ))
14479 })?;
14480 out.push(pos);
14481 }
14482 Ok(out)
14483}
14484
14485fn on_conflict_key_exists(
14488 catalog: &Catalog,
14489 table_name: &str,
14490 column_pos: usize,
14491 key: &Value,
14492) -> bool {
14493 let Some(table) = catalog.get(table_name) else {
14494 return false;
14495 };
14496 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14497 return false;
14498 };
14499 table.indices().iter().any(|idx| {
14500 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14501 && idx.column_position == column_pos
14502 && idx.partial_predicate.is_none()
14503 && !idx.lookup_eq(&idx_key).is_empty()
14504 })
14505}
14506
14507fn lookup_row_position_by_keys(
14513 catalog: &Catalog,
14514 table_name: &str,
14515 column_positions: &[usize],
14516 key: &[&Value],
14517) -> Option<usize> {
14518 let table = catalog.get(table_name)?;
14519 table.rows().iter().position(|r| {
14520 column_positions
14521 .iter()
14522 .enumerate()
14523 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14524 })
14525}
14526
14527fn on_conflict_keys_exist(
14532 catalog: &Catalog,
14533 table_name: &str,
14534 column_positions: &[usize],
14535 key: &[&Value],
14536) -> bool {
14537 if column_positions.len() == 1 {
14538 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14539 }
14540 let Some(table) = catalog.get(table_name) else {
14541 return false;
14542 };
14543 table.rows().iter().any(|r| {
14544 column_positions
14545 .iter()
14546 .enumerate()
14547 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14548 })
14549}
14550
14551fn apply_on_conflict_assignments(
14564 catalog: &Catalog,
14565 table_name: &str,
14566 target_pos: usize,
14567 incoming: &[Value],
14568 assignments: &[(String, Expr)],
14569 where_: Option<&Expr>,
14570) -> Result<Option<Vec<Value>>, EngineError> {
14571 let table = catalog.get(table_name).ok_or_else(|| {
14572 EngineError::Storage(StorageError::TableNotFound {
14573 name: table_name.into(),
14574 })
14575 })?;
14576 let schema_cols = table.schema().columns.clone();
14577 let existing = table
14578 .rows()
14579 .get(target_pos)
14580 .ok_or_else(|| {
14581 EngineError::Unsupported(alloc::format!(
14582 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14583 ))
14584 })?
14585 .clone();
14586 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14587 if let Some(w) = where_ {
14589 let pred = w.clone();
14590 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14591 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14592 if !matches!(v, Value::Bool(true)) {
14593 return Ok(None);
14594 }
14595 }
14596 let mut new_values = existing.values.clone();
14597 for (col_name, expr) in assignments {
14598 let target_idx = schema_cols
14599 .iter()
14600 .position(|c| c.name == *col_name)
14601 .ok_or_else(|| {
14602 EngineError::Eval(EvalError::ColumnNotFound {
14603 name: col_name.clone(),
14604 })
14605 })?;
14606 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14607 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14608 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14609 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14610 new_values[target_idx] = coerced;
14611 }
14612 Ok(Some(new_values))
14613}
14614
14615fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14620 use spg_sql::ast::ColumnName;
14621 match expr {
14622 Expr::Column(ColumnName { qualifier, name })
14623 if qualifier
14624 .as_deref()
14625 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14626 {
14627 let pos = schema_cols.iter().position(|c| c.name == name);
14628 match pos {
14629 Some(p) => {
14630 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14631 value_to_literal_expr(v)
14632 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14633 }
14634 None => Expr::Column(ColumnName { qualifier, name }),
14635 }
14636 }
14637 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14638 op,
14639 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14640 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14641 },
14642 Expr::Unary { op, expr } => Expr::Unary {
14643 op,
14644 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14645 },
14646 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14647 name,
14648 args: args
14649 .into_iter()
14650 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14651 .collect(),
14652 },
14653 other => other,
14654 }
14655}
14656
14657fn enforce_uniqueness_inserts(
14680 catalog: &Catalog,
14681 child_table: &str,
14682 constraints: &[spg_storage::UniquenessConstraint],
14683 rows: &[Vec<Value>],
14684) -> Result<(), EngineError> {
14685 if constraints.is_empty() {
14686 return Ok(());
14687 }
14688 let table = catalog.get(child_table).ok_or_else(|| {
14689 EngineError::Storage(StorageError::TableNotFound {
14690 name: child_table.into(),
14691 })
14692 })?;
14693 let schema = table.schema();
14694 for uc in constraints {
14695 for (batch_idx, row_values) in rows.iter().enumerate() {
14696 let key: Vec<Value> = uc
14705 .columns
14706 .iter()
14707 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14708 .collect();
14709 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14710 if has_null && !uc.nulls_not_distinct {
14715 continue;
14716 }
14717 let collides_in_table = table.rows().iter().any(|prow| {
14719 uc.columns.iter().enumerate().all(|(i, &p)| {
14720 prow.values
14721 .get(p)
14722 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14723 })
14724 });
14725 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14727 uc.columns.iter().enumerate().all(|(i, &p)| {
14728 earlier
14729 .get(p)
14730 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14731 })
14732 });
14733 if collides_in_table || collides_in_batch {
14734 let kind = if uc.is_primary_key {
14735 "PRIMARY KEY"
14736 } else {
14737 "UNIQUE"
14738 };
14739 let col_names: Vec<String> = uc
14740 .columns
14741 .iter()
14742 .map(|&i| table.schema().columns[i].name.clone())
14743 .collect();
14744 return Err(EngineError::Unsupported(alloc::format!(
14745 "{kind} violation on {child_table:?} columns {col_names:?}: \
14746 row #{batch_idx} duplicates an existing key"
14747 )));
14748 }
14749 }
14750 }
14751 Ok(())
14752}
14753
14754fn collated_key_cell(
14761 v: &spg_storage::Value,
14762 column_position: usize,
14763 schema: &spg_storage::TableSchema,
14764) -> spg_storage::Value {
14765 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14766 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14767 spg_storage::Value::Text(s.to_ascii_lowercase())
14768 }
14769 _ => v.clone(),
14770 }
14771}
14772
14773fn predicate_truthy(v: &spg_storage::Value) -> bool {
14781 use spg_storage::Value as V;
14782 match v {
14783 V::Bool(b) => *b,
14784 V::Int(n) => *n != 0,
14785 V::BigInt(n) => *n != 0,
14786 V::SmallInt(n) => *n != 0,
14787 _ => false,
14788 }
14789}
14790
14791fn check_existing_unique_violation(
14796 idx: &spg_storage::Index,
14797 schema: &spg_storage::TableSchema,
14798 rows: &[spg_storage::Row],
14799) -> Result<(), EngineError> {
14800 let predicate_expr = match idx.partial_predicate.as_deref() {
14801 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14802 EngineError::Unsupported(alloc::format!(
14803 "stored partial predicate {s:?} failed to re-parse: {e:?}"
14804 ))
14805 })?),
14806 None => None,
14807 };
14808 let ctx = eval::EvalContext::new(&schema.columns, None);
14809 let key_positions = unique_key_positions(idx);
14810 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
14811 for row in rows {
14812 if let Some(expr) = &predicate_expr {
14813 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
14814 EngineError::Unsupported(alloc::format!(
14815 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
14816 ))
14817 })?;
14818 if !predicate_truthy(&v) {
14819 continue;
14820 }
14821 }
14822 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
14823 .iter()
14824 .map(|&p| {
14825 let v = row
14826 .values
14827 .get(p)
14828 .cloned()
14829 .unwrap_or(spg_storage::Value::Null);
14830 collated_key_cell(&v, p, schema)
14831 })
14832 .collect();
14833 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14834 continue;
14835 }
14836 if seen.iter().any(|other| *other == key) {
14837 return Err(EngineError::Unsupported(alloc::format!(
14838 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
14839 idx.name
14840 )));
14841 }
14842 seen.push(key);
14843 }
14844 Ok(())
14845}
14846
14847fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
14851 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
14852 out.push(idx.column_position);
14853 out.extend_from_slice(&idx.extra_column_positions);
14854 out
14855}
14856
14857fn enforce_unique_index_inserts(
14865 catalog: &Catalog,
14866 table_name: &str,
14867 rows: &[alloc::vec::Vec<spg_storage::Value>],
14868) -> Result<(), EngineError> {
14869 let table = catalog.get(table_name).ok_or_else(|| {
14870 EngineError::Storage(StorageError::TableNotFound {
14871 name: table_name.into(),
14872 })
14873 })?;
14874 let schema = table.schema();
14875 let ctx = eval::EvalContext::new(&schema.columns, None);
14876 for idx in table.indices() {
14877 if !idx.is_unique {
14878 continue;
14879 }
14880 let predicate_expr = match idx.partial_predicate.as_deref() {
14882 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14883 EngineError::Unsupported(alloc::format!(
14884 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
14885 idx.name
14886 ))
14887 })?),
14888 None => None,
14889 };
14890 let key_positions = unique_key_positions(idx);
14891 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
14892 key_positions
14896 .iter()
14897 .map(|&p| {
14898 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
14899 collated_key_cell(&v, p, schema)
14900 })
14901 .collect()
14902 };
14903 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
14907 let Some(expr) = &predicate_expr else {
14908 return Ok(true);
14909 };
14910 let tmp_row = spg_storage::Row {
14911 values: values.to_vec(),
14912 };
14913 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14914 EngineError::Unsupported(alloc::format!(
14915 "UNIQUE INDEX {:?} predicate eval: {e:?}",
14916 idx.name
14917 ))
14918 })?;
14919 Ok(predicate_truthy(&v))
14920 };
14921 for (batch_idx, row_values) in rows.iter().enumerate() {
14922 if !participates(row_values)? {
14923 continue;
14924 }
14925 let key = key_of(row_values);
14926 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14927 continue;
14928 }
14929 for prow in table.rows() {
14931 if !participates(&prow.values)? {
14932 continue;
14933 }
14934 if key_of(&prow.values) == key {
14935 return Err(EngineError::Unsupported(alloc::format!(
14936 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14937 row #{batch_idx} duplicates an existing key",
14938 idx.name
14939 )));
14940 }
14941 }
14942 for earlier in &rows[..batch_idx] {
14944 if !participates(earlier)? {
14945 continue;
14946 }
14947 if key_of(earlier) == key {
14948 return Err(EngineError::Unsupported(alloc::format!(
14949 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14950 row #{batch_idx} duplicates an earlier row in the same batch",
14951 idx.name
14952 )));
14953 }
14954 }
14955 }
14956 }
14957 Ok(())
14958}
14959
14960fn any_column_changed(
14968 filter_cols: &[String],
14969 schema_cols: &[ColumnSchema],
14970 old_row: &Row,
14971 new_row: &Row,
14972) -> bool {
14973 for col_name in filter_cols {
14974 let Some(pos) = schema_cols
14975 .iter()
14976 .position(|c| c.name.eq_ignore_ascii_case(col_name))
14977 else {
14978 continue;
14979 };
14980 let old_v = old_row.values.get(pos);
14981 let new_v = new_row.values.get(pos);
14982 if old_v != new_v {
14983 return true;
14984 }
14985 }
14986 false
14987}
14988
14989fn enforce_check_constraints(
14994 catalog: &Catalog,
14995 table_name: &str,
14996 rows: &[alloc::vec::Vec<spg_storage::Value>],
14997) -> Result<(), EngineError> {
14998 let table = catalog.get(table_name).ok_or_else(|| {
14999 EngineError::Storage(StorageError::TableNotFound {
15000 name: table_name.into(),
15001 })
15002 })?;
15003 let schema = table.schema();
15004 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15008 alloc::vec::Vec::new();
15009 for (idx, col) in schema.columns.iter().enumerate() {
15010 let Some(dname) = &col.user_domain_type else {
15011 continue;
15012 };
15013 let Some(dom) = catalog.domain_types().get(dname) else {
15014 continue;
15015 };
15016 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15017 alloc::vec::Vec::with_capacity(dom.checks.len());
15018 for src in &dom.checks {
15019 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15020 EngineError::Unsupported(alloc::format!(
15021 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15022 col.name
15023 ))
15024 })?;
15025 parsed_for_col.push(expr);
15026 }
15027 if !parsed_for_col.is_empty() {
15028 domain_checks_per_col.push((idx, parsed_for_col));
15029 }
15030 }
15031 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15032 return Ok(());
15033 }
15034 let ctx = eval::EvalContext::new(&schema.columns, None);
15035 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15036 for (i, src) in schema.checks.iter().enumerate() {
15037 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15038 EngineError::Unsupported(alloc::format!(
15039 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15040 ))
15041 })?;
15042 parsed.push((i, expr));
15043 }
15044 for (batch_idx, row_values) in rows.iter().enumerate() {
15045 let tmp_row = spg_storage::Row {
15046 values: row_values.clone(),
15047 };
15048 for (i, expr) in &parsed {
15049 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15050 EngineError::Unsupported(alloc::format!(
15051 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15052 ))
15053 })?;
15054 if matches!(v, spg_storage::Value::Bool(false)) {
15056 return Err(EngineError::Unsupported(alloc::format!(
15057 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15058 schema.checks[*i]
15059 )));
15060 }
15061 }
15062 for (col_idx, checks) in &domain_checks_per_col {
15068 let cell = row_values
15069 .get(*col_idx)
15070 .cloned()
15071 .unwrap_or(spg_storage::Value::Null);
15072 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15073 "value",
15074 schema.columns[*col_idx].ty,
15075 schema.columns[*col_idx].nullable,
15076 )];
15077 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15078 let synth_row = spg_storage::Row {
15079 values: alloc::vec![cell],
15080 };
15081 for (ci, expr) in checks.iter().enumerate() {
15082 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15083 EngineError::Unsupported(alloc::format!(
15084 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15085 schema.columns[*col_idx].name
15086 ))
15087 })?;
15088 if matches!(v, spg_storage::Value::Bool(false)) {
15089 return Err(EngineError::Unsupported(alloc::format!(
15090 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15091 schema.columns[*col_idx].name
15092 )));
15093 }
15094 }
15095 }
15096 }
15097 Ok(())
15098}
15099
15100fn enforce_fk_inserts(
15101 catalog: &Catalog,
15102 child_table: &str,
15103 fks: &[spg_storage::ForeignKeyConstraint],
15104 rows: &[Vec<Value>],
15105) -> Result<(), EngineError> {
15106 for fk in fks {
15107 let parent_is_self = fk.parent_table == child_table;
15108 let parent = if parent_is_self {
15109 catalog.get(child_table).ok_or_else(|| {
15112 EngineError::Storage(StorageError::TableNotFound {
15113 name: child_table.into(),
15114 })
15115 })?
15116 } else {
15117 catalog.get(&fk.parent_table).ok_or_else(|| {
15118 EngineError::Storage(StorageError::TableNotFound {
15119 name: fk.parent_table.clone(),
15120 })
15121 })?
15122 };
15123 for (batch_idx, row_values) in rows.iter().enumerate() {
15124 if fk.local_columns.len() == 1 {
15128 let v = &row_values[fk.local_columns[0]];
15129 if matches!(v, Value::Null) {
15130 continue;
15131 }
15132 let parent_col = fk.parent_columns[0];
15133 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15134 EngineError::Unsupported(alloc::format!(
15135 "FOREIGN KEY column value of type {:?} is not index-eligible",
15136 v.data_type()
15137 ))
15138 })?;
15139 let present_committed = parent.indices().iter().any(|idx| {
15140 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15141 && idx.column_position == parent_col
15142 && idx.partial_predicate.is_none()
15143 && !idx.lookup_eq(&key).is_empty()
15144 });
15145 let present_in_batch = parent_is_self
15149 && rows[..batch_idx]
15150 .iter()
15151 .any(|earlier| earlier.get(parent_col) == Some(v));
15152 if !(present_committed || present_in_batch) {
15153 return Err(EngineError::Unsupported(alloc::format!(
15154 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15155 fk.parent_table,
15156 parent
15157 .schema()
15158 .columns
15159 .get(parent_col)
15160 .map_or("?", |c| c.name.as_str()),
15161 v,
15162 )));
15163 }
15164 } else {
15165 if fk
15169 .local_columns
15170 .iter()
15171 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15172 {
15173 continue;
15174 }
15175 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15176 let parent_match_committed = parent.rows().iter().any(|prow| {
15177 fk.parent_columns
15178 .iter()
15179 .enumerate()
15180 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15181 });
15182 let parent_match_in_batch = parent_is_self
15183 && rows[..batch_idx].iter().any(|earlier| {
15184 fk.parent_columns
15185 .iter()
15186 .enumerate()
15187 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15188 });
15189 if !(parent_match_committed || parent_match_in_batch) {
15190 return Err(EngineError::Unsupported(alloc::format!(
15191 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15192 fk.parent_table,
15193 )));
15194 }
15195 }
15196 }
15197 }
15198 Ok(())
15199}
15200
15201#[derive(Debug, Clone)]
15205struct FkChildStep {
15206 child_table: String,
15207 action: FkChildAction,
15208}
15209
15210#[derive(Debug, Clone)]
15211enum FkChildAction {
15212 Delete { positions: Vec<usize> },
15214 SetNull {
15218 positions: Vec<usize>,
15219 columns: Vec<usize>,
15220 },
15221 SetDefault {
15225 positions: Vec<usize>,
15226 columns: Vec<usize>,
15227 defaults: Vec<Value>,
15228 },
15229}
15230
15231fn plan_fk_parent_deletions(
15247 catalog: &Catalog,
15248 parent_table_name: &str,
15249 to_delete_positions: &[usize],
15250 to_delete_rows: &[Vec<Value>],
15251) -> Result<Vec<FkChildStep>, EngineError> {
15252 use alloc::collections::{BTreeMap, BTreeSet};
15253 if to_delete_rows.is_empty() {
15254 return Ok(Vec::new());
15255 }
15256 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15257 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15259 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15260 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15261 for &p in to_delete_positions {
15262 visited.insert((parent_table_name.to_string(), p));
15263 }
15264 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15265 .iter()
15266 .map(|r| (parent_table_name.to_string(), r.clone()))
15267 .collect();
15268 while let Some((cur_parent, parent_row)) = work.pop() {
15269 for child_name in catalog.table_names() {
15270 let child = catalog
15271 .get(&child_name)
15272 .expect("table_names → catalog.get round-trip is total");
15273 for fk in &child.schema().foreign_keys {
15274 if fk.parent_table != cur_parent {
15275 continue;
15276 }
15277 let parent_key: Vec<&Value> = fk
15278 .parent_columns
15279 .iter()
15280 .map(|&pi| &parent_row[pi])
15281 .collect();
15282 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15283 continue;
15284 }
15285 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15286 if child_name == cur_parent
15287 && visited.contains(&(child_name.clone(), child_row_idx))
15288 {
15289 continue;
15290 }
15291 let matches_key = fk
15292 .local_columns
15293 .iter()
15294 .enumerate()
15295 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15296 if !matches_key {
15297 continue;
15298 }
15299 match fk.on_delete {
15300 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15301 return Err(EngineError::Unsupported(alloc::format!(
15302 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15303 restricted by FK from {child_name:?}.{:?}",
15304 fk.local_columns,
15305 )));
15306 }
15307 spg_storage::FkAction::Cascade => {
15308 if visited.insert((child_name.clone(), child_row_idx)) {
15309 delete_plan
15310 .entry(child_name.clone())
15311 .or_default()
15312 .insert(child_row_idx);
15313 work.push((child_name.clone(), child_row.values.clone()));
15314 }
15315 }
15316 spg_storage::FkAction::SetNull => {
15317 for &li in &fk.local_columns {
15319 let col = child.schema().columns.get(li).ok_or_else(|| {
15320 EngineError::Unsupported(alloc::format!(
15321 "FK local column {li} missing in {child_name:?}"
15322 ))
15323 })?;
15324 if !col.nullable {
15325 return Err(EngineError::Unsupported(alloc::format!(
15326 "FOREIGN KEY ON DELETE SET NULL: column \
15327 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15328 col.name,
15329 )));
15330 }
15331 }
15332 let entry = setnull_plan.entry(child_name.clone()).or_default();
15333 for &li in &fk.local_columns {
15334 entry.insert((child_row_idx, li));
15335 }
15336 }
15337 spg_storage::FkAction::SetDefault => {
15338 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15340 for &li in &fk.local_columns {
15341 let col = child.schema().columns.get(li).ok_or_else(|| {
15342 EngineError::Unsupported(alloc::format!(
15343 "FK local column {li} missing in {child_name:?}"
15344 ))
15345 })?;
15346 let default = col.default.clone().ok_or_else(|| {
15347 EngineError::Unsupported(alloc::format!(
15348 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15349 {child_name:?}.{:?} has no DEFAULT declared",
15350 col.name,
15351 ))
15352 })?;
15353 entry.insert((child_row_idx, li), default);
15354 }
15355 }
15356 }
15357 }
15358 }
15359 }
15360 }
15361 let mut steps: Vec<FkChildStep> = Vec::new();
15369 for (child_table, entries) in setnull_plan {
15370 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15371 steps.push(FkChildStep {
15372 child_table,
15373 action: FkChildAction::SetNull { positions, columns },
15374 });
15375 }
15376 for (child_table, entries) in setdefault_plan {
15377 let mut positions = Vec::with_capacity(entries.len());
15378 let mut columns = Vec::with_capacity(entries.len());
15379 let mut defaults = Vec::with_capacity(entries.len());
15380 for ((p, c), v) in entries {
15381 positions.push(p);
15382 columns.push(c);
15383 defaults.push(v);
15384 }
15385 steps.push(FkChildStep {
15386 child_table,
15387 action: FkChildAction::SetDefault {
15388 positions,
15389 columns,
15390 defaults,
15391 },
15392 });
15393 }
15394 for (child_table, positions) in delete_plan {
15395 steps.push(FkChildStep {
15396 child_table,
15397 action: FkChildAction::Delete {
15398 positions: positions.into_iter().collect(),
15399 },
15400 });
15401 }
15402 Ok(steps)
15403}
15404
15405fn plan_fk_parent_updates(
15422 catalog: &Catalog,
15423 parent_table_name: &str,
15424 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15425) -> Result<Vec<FkChildStep>, EngineError> {
15426 use alloc::collections::BTreeMap;
15427 if plan_with_old.is_empty() {
15428 return Ok(Vec::new());
15429 }
15430 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15435 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15436 BTreeMap::new();
15437 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15438 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15440
15441 for child_name in catalog.table_names() {
15442 let child = catalog
15443 .get(&child_name)
15444 .expect("table_names → catalog.get total");
15445 for fk in &child.schema().foreign_keys {
15446 if fk.parent_table != parent_table_name {
15447 continue;
15448 }
15449 for (_pos, old_row, new_row) in plan_with_old {
15450 let key_changed = fk
15452 .parent_columns
15453 .iter()
15454 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15455 if !key_changed {
15456 continue;
15457 }
15458 let old_key: Vec<&Value> =
15460 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15461 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15462 continue;
15464 }
15465 let new_key: Vec<&Value> =
15466 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15467 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15468 if child_name == parent_table_name
15471 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15472 {
15473 continue;
15474 }
15475 let matches_key = fk
15476 .local_columns
15477 .iter()
15478 .enumerate()
15479 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15480 if !matches_key {
15481 continue;
15482 }
15483 match fk.on_update {
15484 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15485 return Err(EngineError::Unsupported(alloc::format!(
15486 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15487 restricted by FK from {child_name:?}.{:?}",
15488 fk.local_columns,
15489 )));
15490 }
15491 spg_storage::FkAction::Cascade => {
15492 let entry = cascade_plan.entry(child_name.clone()).or_default();
15494 for (i, &li) in fk.local_columns.iter().enumerate() {
15495 entry.insert((child_row_idx, li), new_key[i].clone());
15496 }
15497 }
15498 spg_storage::FkAction::SetNull => {
15499 for &li in &fk.local_columns {
15500 let col = child.schema().columns.get(li).ok_or_else(|| {
15501 EngineError::Unsupported(alloc::format!(
15502 "FK local column {li} missing in {child_name:?}"
15503 ))
15504 })?;
15505 if !col.nullable {
15506 return Err(EngineError::Unsupported(alloc::format!(
15507 "FOREIGN KEY ON UPDATE SET NULL: column \
15508 {child_name:?}.{:?} is NOT NULL",
15509 col.name,
15510 )));
15511 }
15512 }
15513 let entry = setnull_plan.entry(child_name.clone()).or_default();
15514 for &li in &fk.local_columns {
15515 entry.insert((child_row_idx, li));
15516 }
15517 }
15518 spg_storage::FkAction::SetDefault => {
15519 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15520 for &li in &fk.local_columns {
15521 let col = child.schema().columns.get(li).ok_or_else(|| {
15522 EngineError::Unsupported(alloc::format!(
15523 "FK local column {li} missing in {child_name:?}"
15524 ))
15525 })?;
15526 let default = col.default.clone().ok_or_else(|| {
15527 EngineError::Unsupported(alloc::format!(
15528 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15529 {child_name:?}.{:?} has no DEFAULT",
15530 col.name,
15531 ))
15532 })?;
15533 entry.insert((child_row_idx, li), default);
15534 }
15535 }
15536 }
15537 }
15538 }
15539 }
15540 }
15541 let mut steps: Vec<FkChildStep> = Vec::new();
15544 for (child_table, entries) in cascade_plan {
15545 let mut positions = Vec::with_capacity(entries.len());
15546 let mut columns = Vec::with_capacity(entries.len());
15547 let mut defaults = Vec::with_capacity(entries.len());
15548 for ((p, c), v) in entries {
15549 positions.push(p);
15550 columns.push(c);
15551 defaults.push(v);
15552 }
15553 steps.push(FkChildStep {
15558 child_table,
15559 action: FkChildAction::SetDefault {
15560 positions,
15561 columns,
15562 defaults,
15563 },
15564 });
15565 }
15566 for (child_table, entries) in setnull_plan {
15567 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15568 steps.push(FkChildStep {
15569 child_table,
15570 action: FkChildAction::SetNull { positions, columns },
15571 });
15572 }
15573 for (child_table, entries) in setdefault_plan {
15574 let mut positions = Vec::with_capacity(entries.len());
15575 let mut columns = Vec::with_capacity(entries.len());
15576 let mut defaults = Vec::with_capacity(entries.len());
15577 for ((p, c), v) in entries {
15578 positions.push(p);
15579 columns.push(c);
15580 defaults.push(v);
15581 }
15582 steps.push(FkChildStep {
15583 child_table,
15584 action: FkChildAction::SetDefault {
15585 positions,
15586 columns,
15587 defaults,
15588 },
15589 });
15590 }
15591 let _ = delete_plan; Ok(steps)
15593}
15594
15595fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15599 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15600 EngineError::Storage(StorageError::TableNotFound {
15601 name: step.child_table.clone(),
15602 })
15603 })?;
15604 match &step.action {
15605 FkChildAction::Delete { positions } => {
15606 let _ = child.delete_rows(positions);
15607 }
15608 FkChildAction::SetNull { positions, columns } => {
15609 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15610 }
15611 FkChildAction::SetDefault {
15612 positions,
15613 columns,
15614 defaults,
15615 } => {
15616 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15617 }
15618 }
15619 Ok(())
15620}
15621
15622fn apply_per_cell_writes(
15628 child: &mut spg_storage::Table,
15629 positions: &[usize],
15630 columns: &[usize],
15631 mut value_for: impl FnMut(usize) -> Value,
15632) -> Result<(), EngineError> {
15633 use alloc::collections::BTreeMap;
15634 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15635 for i in 0..positions.len() {
15636 by_row
15637 .entry(positions[i])
15638 .or_default()
15639 .push((columns[i], value_for(i)));
15640 }
15641 for (pos, mutations) in by_row {
15642 let mut new_values = child.rows()[pos].values.clone();
15643 for (col, v) in mutations {
15644 if let Some(slot) = new_values.get_mut(col) {
15645 *slot = v;
15646 }
15647 }
15648 child
15649 .update_row(pos, new_values)
15650 .map_err(EngineError::Storage)?;
15651 }
15652 Ok(())
15653}
15654
15655fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15656 match a {
15657 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15658 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15659 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15660 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15661 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15662 }
15663}
15664
15665fn resolve_column_default_free(
15671 col: &ColumnSchema,
15672 clock_fn: Option<ClockFn>,
15673) -> Result<Value, EngineError> {
15674 if let Some(rt) = &col.runtime_default {
15675 return eval_runtime_default_free(rt, col.ty, clock_fn);
15676 }
15677 Ok(col.default.clone().unwrap_or(Value::Null))
15678}
15679
15680fn eval_runtime_default_free(
15681 rt: &str,
15682 ty: DataType,
15683 clock_fn: Option<ClockFn>,
15684) -> Result<Value, EngineError> {
15685 let s = rt.trim().to_ascii_lowercase();
15686 let with_no_parens = s.trim_end_matches("()");
15692 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15693 if with_no_parens.ends_with(')') {
15694 &with_no_parens[..open_idx]
15695 } else {
15696 with_no_parens
15697 }
15698 } else {
15699 with_no_parens
15700 };
15701 let now_us = match clock_fn {
15702 Some(f) => f(),
15703 None => 0,
15704 };
15705 let v = match canonical {
15706 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15707 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15708 "current_time" | "localtime" => Value::Timestamp(now_us),
15709 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15715 other => {
15716 return Err(EngineError::Unsupported(alloc::format!(
15717 "runtime DEFAULT expression {other:?} not supported \
15718 (v7.17.0 whitelist: now() / current_timestamp / \
15719 current_date / current_time / localtimestamp / \
15720 localtime / gen_random_uuid() / \
15721 uuid_generate_v4())"
15722 )));
15723 }
15724 };
15725 coerce_value(v, ty, "DEFAULT", 0)
15726}
15727
15728fn is_runtime_default_expr(expr: &Expr) -> bool {
15734 match expr {
15735 Expr::FunctionCall { .. } => true,
15736 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15737 _ => false,
15738 }
15739}
15740
15741fn canonicalize_set_value(
15754 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15755 col_idx: usize,
15756 col_name: &str,
15757 value: Value,
15758) -> Result<Value, EngineError> {
15759 let Some(variants) = lookup.get(&col_idx) else {
15760 return Ok(value);
15761 };
15762 match value {
15763 Value::Null => Ok(Value::Null),
15764 Value::Text(s) => {
15765 if s.is_empty() {
15766 return Ok(Value::Text(alloc::string::String::new()));
15767 }
15768 let mut present = alloc::vec![false; variants.len()];
15771 for raw in s.split(',') {
15772 let tok = raw.trim();
15773 if tok.is_empty() {
15774 continue;
15775 }
15776 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15777 EngineError::Unsupported(alloc::format!(
15778 "column {col_name:?}: invalid SET token {tok:?}; \
15779 allowed: {variants:?}"
15780 ))
15781 })?;
15782 present[idx] = true;
15783 }
15784 let mut out = alloc::string::String::new();
15786 let mut first = true;
15787 for (i, keep) in present.iter().enumerate() {
15788 if !keep {
15789 continue;
15790 }
15791 if !first {
15792 out.push(',');
15793 }
15794 first = false;
15795 out.push_str(&variants[i]);
15796 }
15797 Ok(Value::Text(out))
15798 }
15799 other => Err(EngineError::Unsupported(alloc::format!(
15800 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
15801 other.data_type()
15802 ))),
15803 }
15804}
15805
15806fn enforce_enum_label(
15807 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15808 col_idx: usize,
15809 col_name: &str,
15810 value: &Value,
15811) -> Result<(), EngineError> {
15812 if let Some(labels) = lookup.get(&col_idx) {
15813 match value {
15814 Value::Null => Ok(()),
15815 Value::Text(s) => {
15816 if labels.iter().any(|l| l == s) {
15817 Ok(())
15818 } else {
15819 Err(EngineError::Unsupported(alloc::format!(
15820 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
15821 )))
15822 }
15823 }
15824 other => Err(EngineError::Unsupported(alloc::format!(
15825 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
15826 other.data_type()
15827 ))),
15828 }
15829 } else {
15830 Ok(())
15831 }
15832}
15833
15834fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
15835 let ty = column_type_to_data_type(c.ty);
15836 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
15837 if let Some(name) = c.user_type_ref {
15844 schema.user_enum_type = Some(name);
15845 }
15846 if let Some(expr) = c.on_update_runtime {
15849 schema.on_update_runtime = Some(alloc::format!("{expr}"));
15850 }
15851 schema.collation = match c.collation {
15855 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
15856 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
15857 };
15858 schema.is_unsigned = c.is_unsigned;
15861 schema.inline_enum_variants = c.inline_enum_variants;
15865 schema.inline_set_variants = c.inline_set_variants;
15869 if let Some(default_expr) = c.default {
15870 if is_runtime_default_expr(&default_expr) {
15876 let display = alloc::format!("{default_expr}");
15877 schema = schema.with_runtime_default(display);
15878 } else {
15879 let raw = literal_expr_to_value(default_expr)?;
15880 let coerced = coerce_value(raw, ty, &c.name, 0)?;
15881 schema = schema.with_default(coerced);
15882 }
15883 }
15884 if c.auto_increment {
15885 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
15887 return Err(EngineError::Unsupported(alloc::format!(
15888 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
15889 )));
15890 }
15891 schema = schema.with_auto_increment();
15892 }
15893 Ok(schema)
15894}
15895
15896fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
15901 let s = s.trim();
15902 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
15903 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
15905 if cleaned.len() % 2 != 0 {
15906 return Err("odd-length hex literal");
15907 }
15908 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
15909 let cleaned_bytes = cleaned.as_bytes();
15910 for i in (0..cleaned_bytes.len()).step_by(2) {
15911 let hi = hex_nibble(cleaned_bytes[i])?;
15912 let lo = hex_nibble(cleaned_bytes[i + 1])?;
15913 out.push((hi << 4) | lo);
15914 }
15915 return Ok(out);
15916 }
15917 let bytes = s.as_bytes();
15920 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
15921 let mut i = 0;
15922 while i < bytes.len() {
15923 let b = bytes[i];
15924 if b == b'\\' && i + 1 < bytes.len() {
15925 let n = bytes[i + 1];
15926 if n == b'\\' {
15927 out.push(b'\\');
15928 i += 2;
15929 continue;
15930 }
15931 if n.is_ascii_digit()
15932 && i + 3 < bytes.len()
15933 && bytes[i + 2].is_ascii_digit()
15934 && bytes[i + 3].is_ascii_digit()
15935 {
15936 let oct = |x: u8| (x - b'0') as u32;
15937 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
15938 if v <= 0xFF {
15939 out.push(v as u8);
15940 i += 4;
15941 continue;
15942 }
15943 }
15944 }
15945 out.push(b);
15946 i += 1;
15947 }
15948 Ok(out)
15949}
15950
15951fn hex_nibble(b: u8) -> Result<u8, &'static str> {
15952 match b {
15953 b'0'..=b'9' => Ok(b - b'0'),
15954 b'a'..=b'f' => Ok(b - b'a' + 10),
15955 b'A'..=b'F' => Ok(b - b'A' + 10),
15956 _ => Err("invalid hex digit"),
15957 }
15958}
15959
15960fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
15974 let mut has_text = false;
15975 let mut has_bigint = false;
15976 let mut has_int = false;
15977 for v in &items {
15978 match v {
15979 Value::Null => {}
15980 Value::Text(_) | Value::Json(_) => has_text = true,
15981 Value::BigInt(_) => has_bigint = true,
15982 Value::Int(_) | Value::SmallInt(_) => has_int = true,
15983 _ => has_text = true,
15984 }
15985 }
15986 if has_text || (!has_bigint && !has_int) {
15987 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
15988 .into_iter()
15989 .map(|v| match v {
15990 Value::Null => None,
15991 Value::Text(s) | Value::Json(s) => Some(s),
15992 other => Some(alloc::format!("{other:?}")),
15993 })
15994 .collect();
15995 return Value::TextArray(out);
15996 }
15997 if has_bigint {
15998 let out: alloc::vec::Vec<Option<i64>> = items
15999 .into_iter()
16000 .map(|v| match v {
16001 Value::Null => None,
16002 Value::Int(n) => Some(i64::from(n)),
16003 Value::SmallInt(n) => Some(i64::from(n)),
16004 Value::BigInt(n) => Some(n),
16005 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16006 })
16007 .collect();
16008 return Value::BigIntArray(out);
16009 }
16010 let out: alloc::vec::Vec<Option<i32>> = items
16011 .into_iter()
16012 .map(|v| match v {
16013 Value::Null => None,
16014 Value::Int(n) => Some(n),
16015 Value::SmallInt(n) => Some(i32::from(n)),
16016 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16017 })
16018 .collect();
16019 Value::IntArray(out)
16020}
16021
16022fn decode_text_array_literal(
16023 s: &str,
16024) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16025 let trimmed = s.trim();
16026 let inner = trimmed
16027 .strip_prefix('{')
16028 .and_then(|x| x.strip_suffix('}'))
16029 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16030 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16031 if inner.trim().is_empty() {
16032 return Ok(out);
16033 }
16034 let bytes = inner.as_bytes();
16035 let mut i = 0;
16036 while i <= bytes.len() {
16037 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16039 i += 1;
16040 }
16041 if i < bytes.len() && bytes[i] == b'"' {
16043 i += 1; let mut buf = alloc::string::String::new();
16045 while i < bytes.len() && bytes[i] != b'"' {
16046 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16047 buf.push(bytes[i + 1] as char);
16048 i += 2;
16049 } else {
16050 buf.push(bytes[i] as char);
16051 i += 1;
16052 }
16053 }
16054 if i >= bytes.len() {
16055 return Err("unterminated quoted element");
16056 }
16057 i += 1; out.push(Some(buf));
16059 } else {
16060 let start = i;
16062 while i < bytes.len() && bytes[i] != b',' {
16063 i += 1;
16064 }
16065 let raw = inner[start..i].trim();
16066 if raw.eq_ignore_ascii_case("NULL") {
16067 out.push(None);
16068 } else {
16069 out.push(Some(alloc::string::ToString::to_string(raw)));
16070 }
16071 }
16072 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16074 i += 1;
16075 }
16076 if i >= bytes.len() {
16077 break;
16078 }
16079 if bytes[i] != b',' {
16080 return Err("expected ',' between TEXT[] elements");
16081 }
16082 i += 1;
16083 }
16084 Ok(out)
16085}
16086
16087fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16092 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16093 out.push('{');
16094 for (i, item) in items.iter().enumerate() {
16095 if i > 0 {
16096 out.push(',');
16097 }
16098 match item {
16099 None => out.push_str("NULL"),
16100 Some(s) => {
16101 let needs_quote = s.is_empty()
16102 || s.eq_ignore_ascii_case("NULL")
16103 || s.chars()
16104 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16105 if needs_quote {
16106 out.push('"');
16107 for c in s.chars() {
16108 if c == '"' || c == '\\' {
16109 out.push('\\');
16110 }
16111 out.push(c);
16112 }
16113 out.push('"');
16114 } else {
16115 out.push_str(s);
16116 }
16117 }
16118 }
16119 }
16120 out.push('}');
16121 out
16122}
16123
16124fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16128 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16129 out.push_str("\\x");
16130 for byte in b {
16131 let hi = byte >> 4;
16132 let lo = byte & 0x0F;
16133 out.push(hex_digit(hi));
16134 out.push(hex_digit(lo));
16135 }
16136 out
16137}
16138
16139const fn hex_digit(n: u8) -> char {
16140 match n {
16141 0..=9 => (b'0' + n) as char,
16142 10..=15 => (b'a' + n - 10) as char,
16143 _ => '?',
16144 }
16145}
16146
16147fn parse_hstore_str(
16159 s: &str,
16160) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16161 let bytes = s.as_bytes();
16162 let mut i = 0;
16163 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16164 let skip_ws = |bytes: &[u8], i: &mut usize| {
16165 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16166 *i += 1;
16167 }
16168 };
16169 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16170 if *i >= bytes.len() {
16171 return None;
16172 }
16173 if bytes[*i] == b'"' {
16174 *i += 1;
16175 let mut out = alloc::string::String::new();
16176 while *i < bytes.len() {
16177 match bytes[*i] {
16178 b'"' => {
16179 *i += 1;
16180 return Some(out);
16181 }
16182 b'\\' if *i + 1 < bytes.len() => {
16183 out.push(bytes[*i + 1] as char);
16184 *i += 2;
16185 }
16186 c => {
16187 out.push(c as char);
16188 *i += 1;
16189 }
16190 }
16191 }
16192 None
16193 } else {
16194 let start = *i;
16195 while *i < bytes.len()
16196 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16197 {
16198 *i += 1;
16199 }
16200 if *i == start {
16201 return None;
16202 }
16203 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16204 }
16205 };
16206 skip_ws(bytes, &mut i);
16207 while i < bytes.len() {
16208 let key = parse_token(bytes, &mut i)?;
16209 skip_ws(bytes, &mut i);
16210 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16211 return None;
16212 }
16213 i += 2;
16214 skip_ws(bytes, &mut i);
16215 let val_token = if i + 4 <= bytes.len()
16217 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16218 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16219 {
16220 i += 4;
16221 None
16222 } else {
16223 Some(parse_token(bytes, &mut i)?)
16224 };
16225 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16227 out[pos] = (key, val_token);
16228 } else {
16229 out.push((key, val_token));
16230 }
16231 skip_ws(bytes, &mut i);
16232 if i >= bytes.len() {
16233 break;
16234 }
16235 if bytes[i] == b',' {
16236 i += 1;
16237 skip_ws(bytes, &mut i);
16238 continue;
16239 }
16240 return None;
16241 }
16242 Some(out)
16243}
16244
16245fn format_hstore_str(
16249 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16250) -> alloc::string::String {
16251 let mut out = alloc::string::String::new();
16252 for (i, (k, v)) in pairs.iter().enumerate() {
16253 if i > 0 {
16254 out.push_str(", ");
16255 }
16256 out.push('"');
16257 out.push_str(k);
16258 out.push_str("\"=>");
16259 match v {
16260 None => out.push_str("NULL"),
16261 Some(val) => {
16262 out.push('"');
16263 out.push_str(val);
16264 out.push('"');
16265 }
16266 }
16267 }
16268 out
16269}
16270
16271pub fn format_hstore_text(
16274 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16275) -> alloc::string::String {
16276 format_hstore_str(pairs)
16277}
16278
16279fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16284 let s = s.trim();
16285 let outer = s
16286 .strip_prefix('{')
16287 .and_then(|x| x.strip_suffix('}'))
16288 .ok_or("missing outer '{...}' braces")?;
16289 let trimmed = outer.trim();
16290 if trimmed.is_empty() {
16291 return Ok(Vec::new());
16292 }
16293 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16294 let mut i = 0;
16295 let bytes = trimmed.as_bytes();
16296 while i < bytes.len() {
16297 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16298 i += 1;
16299 }
16300 if i >= bytes.len() {
16301 break;
16302 }
16303 if bytes[i] != b'{' {
16304 return Err("expected '{' opening a row");
16305 }
16306 i += 1;
16307 let row_start = i;
16308 let mut depth = 1;
16309 while i < bytes.len() && depth > 0 {
16310 match bytes[i] {
16311 b'{' => depth += 1,
16312 b'}' => depth -= 1,
16313 _ => {}
16314 }
16315 if depth > 0 {
16316 i += 1;
16317 }
16318 }
16319 if depth != 0 {
16320 return Err("unbalanced '{...}' in row");
16321 }
16322 let row_text = &trimmed[row_start..i];
16323 i += 1;
16324 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16325 Vec::new()
16326 } else {
16327 row_text.split(',').map(|t| t.trim().to_string()).collect()
16328 };
16329 rows.push(cells);
16330 }
16331 if let Some(first) = rows.first() {
16332 let cols = first.len();
16333 for r in &rows {
16334 if r.len() != cols {
16335 return Err("ragged 2D array (rows have different column counts)");
16336 }
16337 }
16338 }
16339 Ok(rows)
16340}
16341
16342fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16343 let raw = split_2d_literal(s)?;
16344 raw.into_iter()
16345 .map(|row| {
16346 row.into_iter()
16347 .map(|cell| {
16348 if cell.eq_ignore_ascii_case("NULL") {
16349 Ok(None)
16350 } else {
16351 cell.parse::<i32>()
16352 .map(Some)
16353 .map_err(|_| "invalid int element")
16354 }
16355 })
16356 .collect()
16357 })
16358 .collect()
16359}
16360
16361fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16362 let raw = split_2d_literal(s)?;
16363 raw.into_iter()
16364 .map(|row| {
16365 row.into_iter()
16366 .map(|cell| {
16367 if cell.eq_ignore_ascii_case("NULL") {
16368 Ok(None)
16369 } else {
16370 cell.parse::<i64>()
16371 .map(Some)
16372 .map_err(|_| "invalid bigint element")
16373 }
16374 })
16375 .collect()
16376 })
16377 .collect()
16378}
16379
16380fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16381 let raw = split_2d_literal(s)?;
16382 Ok(raw
16383 .into_iter()
16384 .map(|row| {
16385 row.into_iter()
16386 .map(|cell| {
16387 if cell.eq_ignore_ascii_case("NULL") {
16388 None
16389 } else {
16390 Some(cell.trim_matches('"').to_string())
16391 }
16392 })
16393 .collect()
16394 })
16395 .collect())
16396}
16397
16398fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16399 let mut out = alloc::string::String::from("{");
16400 for (i, row) in rows.iter().enumerate() {
16401 if i > 0 {
16402 out.push(',');
16403 }
16404 out.push('{');
16405 for (j, cell) in row.iter().enumerate() {
16406 if j > 0 {
16407 out.push(',');
16408 }
16409 match cell {
16410 None => out.push_str("NULL"),
16411 Some(n) => out.push_str(&alloc::format!("{n}")),
16412 }
16413 }
16414 out.push('}');
16415 }
16416 out.push('}');
16417 out
16418}
16419
16420fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16421 let mut out = alloc::string::String::from("{");
16422 for (i, row) in rows.iter().enumerate() {
16423 if i > 0 {
16424 out.push(',');
16425 }
16426 out.push('{');
16427 for (j, cell) in row.iter().enumerate() {
16428 if j > 0 {
16429 out.push(',');
16430 }
16431 match cell {
16432 None => out.push_str("NULL"),
16433 Some(n) => out.push_str(&alloc::format!("{n}")),
16434 }
16435 }
16436 out.push('}');
16437 }
16438 out.push('}');
16439 out
16440}
16441
16442fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16443 let mut out = alloc::string::String::from("{");
16444 for (i, row) in rows.iter().enumerate() {
16445 if i > 0 {
16446 out.push(',');
16447 }
16448 out.push('{');
16449 for (j, cell) in row.iter().enumerate() {
16450 if j > 0 {
16451 out.push(',');
16452 }
16453 match cell {
16454 None => out.push_str("NULL"),
16455 Some(s) => out.push_str(s),
16456 }
16457 }
16458 out.push('}');
16459 }
16460 out.push('}');
16461 out
16462}
16463
16464pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16467 format_int_2d_text(rows)
16468}
16469pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16470 format_bigint_2d_text(rows)
16471}
16472pub fn format_text_2d_text_pub(
16473 rows: &[Vec<Option<alloc::string::String>>],
16474) -> alloc::string::String {
16475 format_text_2d_text(rows)
16476}
16477
16478fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16483 let s = s.trim();
16484 if s.eq_ignore_ascii_case("empty") {
16485 return Some(Value::Range {
16486 kind,
16487 lower: None,
16488 upper: None,
16489 lower_inc: false,
16490 upper_inc: false,
16491 empty: true,
16492 });
16493 }
16494 let bytes = s.as_bytes();
16495 if bytes.len() < 3 {
16496 return None;
16497 }
16498 let lower_inc = match bytes[0] {
16499 b'[' => true,
16500 b'(' => false,
16501 _ => return None,
16502 };
16503 let upper_inc = match bytes[bytes.len() - 1] {
16504 b']' => true,
16505 b')' => false,
16506 _ => return None,
16507 };
16508 let inner = &s[1..s.len() - 1];
16509 let (lo_text, up_text) = inner.split_once(',')?;
16510 let lower = if lo_text.is_empty() {
16511 None
16512 } else {
16513 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16514 };
16515 let upper = if up_text.is_empty() {
16516 None
16517 } else {
16518 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16519 };
16520 Some(Value::Range {
16521 kind,
16522 lower,
16523 upper,
16524 lower_inc,
16525 upper_inc,
16526 empty: false,
16527 })
16528}
16529
16530fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16533 let text = text.trim().trim_matches('"');
16534 use spg_storage::RangeKind as K;
16535 match kind {
16536 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16537 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16538 K::Num => {
16539 let dot = text.find('.');
16542 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16543 let digits: alloc::string::String = text
16544 .chars()
16545 .filter(|c| *c == '-' || c.is_ascii_digit())
16546 .collect();
16547 let scaled: i128 = digits.parse().ok()?;
16548 Some(Value::Numeric { scaled, scale })
16549 }
16550 K::Ts | K::TsTz => {
16551 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16556 }
16557 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16558 }
16559}
16560
16561pub fn format_range_text(v: &Value) -> alloc::string::String {
16565 format_range_str(v)
16566}
16567
16568fn format_range_str(v: &Value) -> alloc::string::String {
16569 let Value::Range {
16570 lower,
16571 upper,
16572 lower_inc,
16573 upper_inc,
16574 empty,
16575 ..
16576 } = v
16577 else {
16578 return alloc::string::String::new();
16579 };
16580 if *empty {
16581 return "empty".into();
16582 }
16583 let mut out = alloc::string::String::new();
16584 out.push(if *lower_inc { '[' } else { '(' });
16585 if let Some(l) = lower {
16586 out.push_str(&format_range_element(l));
16587 }
16588 out.push(',');
16589 if let Some(u) = upper {
16590 out.push_str(&format_range_element(u));
16591 }
16592 out.push(if *upper_inc { ']' } else { ')' });
16593 out
16594}
16595
16596fn format_range_element(v: &Value) -> alloc::string::String {
16597 match v {
16598 Value::Int(n) => alloc::format!("{n}"),
16599 Value::BigInt(n) => alloc::format!("{n}"),
16600 Value::Date(d) => crate::eval::format_date(*d),
16601 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16602 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16603 other => alloc::format!("{other:?}"),
16604 }
16605}
16606
16607fn parse_money_str(s: &str) -> Option<i64> {
16618 let s = s.trim();
16619 let (neg, rest) = match s.strip_prefix('-') {
16620 Some(r) => (true, r.trim_start()),
16621 None => (false, s),
16622 };
16623 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16624 let (int_part, frac_part) = match rest.split_once('.') {
16625 Some((i, f)) => (i, Some(f)),
16626 None => (rest, None),
16627 };
16628 if int_part.is_empty() {
16629 return None;
16630 }
16631 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16633 for b in int_part.bytes() {
16634 match b {
16635 b',' => {}
16636 b'0'..=b'9' => int_digits.push(b as char),
16637 _ => return None,
16638 }
16639 }
16640 if int_digits.is_empty() {
16641 return None;
16642 }
16643 let dollars: i64 = int_digits.parse().ok()?;
16644 let cents: i64 = match frac_part {
16645 None => 0,
16646 Some(f) => {
16647 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16648 return None;
16649 }
16650 let padded = if f.len() == 1 {
16651 alloc::format!("{f}0")
16652 } else {
16653 f.to_string()
16654 };
16655 padded.parse().ok()?
16656 }
16657 };
16658 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16659 Some(if neg { -total } else { total })
16660}
16661
16662fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16673 let s = s.trim();
16674 let bytes = s.as_bytes();
16678 let sign_pos = bytes
16679 .iter()
16680 .enumerate()
16681 .rev()
16682 .find(|&(_, &b)| b == b'+' || b == b'-')
16683 .map(|(i, _)| i)?;
16684 if sign_pos == 0 {
16685 return None; }
16687 let time_part = &s[..sign_pos];
16688 let offset_part = &s[sign_pos..];
16689 let us = parse_time_str(time_part)?;
16690 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16691 let offset_body = &offset_part[1..];
16692 let (hh_str, mm_str) = match offset_body.split_once(':') {
16693 Some((h, m)) => (h, m),
16694 None => (offset_body, "0"),
16695 };
16696 let hh: i32 = hh_str.parse().ok()?;
16697 let mm: i32 = mm_str.parse().ok()?;
16698 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16699 return None;
16700 }
16701 let total = sign * (hh * 3600 + mm * 60);
16702 if total.abs() > 50_400 {
16703 return None;
16704 }
16705 Some((us, total))
16706}
16707
16708fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16713 if n == 0 || (1901..=2155).contains(&n) {
16714 return Ok(Value::Year(n as u16));
16717 }
16718 Err(EngineError::Eval(EvalError::TypeMismatch {
16719 detail: alloc::format!(
16720 "year value out of range: {n} (column `{col_name}`; \
16721 MySQL accepts 0 or 1901..=2155)"
16722 ),
16723 }))
16724}
16725
16726fn parse_time_str(s: &str) -> Option<i64> {
16738 let s = s.trim();
16739 let (hms, frac) = match s.split_once('.') {
16740 Some((h, f)) => (h, Some(f)),
16741 None => (s, None),
16742 };
16743 let mut parts = hms.split(':');
16744 let hh: u32 = parts.next()?.parse().ok()?;
16745 let mm: u32 = parts.next()?.parse().ok()?;
16746 let ss: u32 = parts.next()?.parse().ok()?;
16747 if parts.next().is_some() {
16748 return None;
16749 }
16750 if hh > 23 || mm > 59 || ss > 59 {
16751 return None;
16752 }
16753 let frac_us: i64 = match frac {
16754 None => 0,
16755 Some(f) => {
16756 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16757 return None;
16758 }
16759 let mut padded = alloc::string::String::with_capacity(6);
16761 padded.push_str(f);
16762 while padded.len() < 6 {
16763 padded.push('0');
16764 }
16765 padded.parse().ok()?
16766 }
16767 };
16768 Some(
16769 i64::from(hh) * 3_600_000_000
16770 + i64::from(mm) * 60_000_000
16771 + i64::from(ss) * 1_000_000
16772 + frac_us,
16773 )
16774}
16775
16776const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16777 match t {
16778 ColumnTypeName::SmallInt => DataType::SmallInt,
16779 ColumnTypeName::Int => DataType::Int,
16780 ColumnTypeName::BigInt => DataType::BigInt,
16781 ColumnTypeName::Float => DataType::Float,
16782 ColumnTypeName::Text => DataType::Text,
16783 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
16784 ColumnTypeName::Char(n) => DataType::Char(n),
16785 ColumnTypeName::Bool => DataType::Bool,
16786 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
16787 dim,
16788 encoding: match encoding {
16789 SqlVecEncoding::F32 => VecEncoding::F32,
16790 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
16791 SqlVecEncoding::F16 => VecEncoding::F16,
16792 },
16793 },
16794 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
16795 ColumnTypeName::Date => DataType::Date,
16796 ColumnTypeName::Timestamp => DataType::Timestamp,
16797 ColumnTypeName::Timestamptz => DataType::Timestamptz,
16798 ColumnTypeName::Json => DataType::Json,
16799 ColumnTypeName::Jsonb => DataType::Jsonb,
16800 ColumnTypeName::Bytes => DataType::Bytes,
16801 ColumnTypeName::TextArray => DataType::TextArray,
16802 ColumnTypeName::IntArray => DataType::IntArray,
16803 ColumnTypeName::BigIntArray => DataType::BigIntArray,
16804 ColumnTypeName::TsVector => DataType::TsVector,
16805 ColumnTypeName::TsQuery => DataType::TsQuery,
16806 ColumnTypeName::Uuid => DataType::Uuid,
16807 ColumnTypeName::Time => DataType::Time,
16808 ColumnTypeName::Year => DataType::Year,
16809 ColumnTypeName::TimeTz => DataType::TimeTz,
16810 ColumnTypeName::Money => DataType::Money,
16811 ColumnTypeName::Range(k) => DataType::Range(match k {
16812 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
16813 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
16814 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
16815 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
16816 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
16817 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
16818 }),
16819 ColumnTypeName::Hstore => DataType::Hstore,
16820 ColumnTypeName::IntArray2D => DataType::IntArray2D,
16821 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
16822 ColumnTypeName::TextArray2D => DataType::TextArray2D,
16823 }
16824}
16825
16826fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
16830 match expr {
16831 Expr::Literal(l) => Ok(literal_to_value(l)),
16832 Expr::Cast { expr, target } => {
16833 let inner_value = literal_expr_to_value(*expr)?;
16834 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
16835 }
16836 Expr::Unary {
16837 op: UnOp::Neg,
16838 expr,
16839 } => match *expr {
16840 Expr::Literal(Literal::Integer(n)) => {
16841 let neg = n.checked_neg().ok_or_else(|| {
16844 EngineError::Unsupported("integer literal overflow on negation".into())
16845 })?;
16846 Ok(int_value_for(neg))
16847 }
16848 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
16849 other => Err(EngineError::Unsupported(alloc::format!(
16850 "unary minus over non-literal expression: {other:?}"
16851 ))),
16852 },
16853 Expr::Array(items) => {
16861 let mut materialised: alloc::vec::Vec<Value> =
16862 alloc::vec::Vec::with_capacity(items.len());
16863 for elem in items {
16864 materialised.push(literal_expr_to_value(elem)?);
16865 }
16866 Ok(array_literal_widen(materialised))
16867 }
16868 other => {
16881 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
16882 let ctx = EvalContext::new(&empty_schema, None);
16883 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
16884 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
16885 }
16886 }
16887}
16888
16889fn literal_to_value(l: Literal) -> Value {
16890 match l {
16891 Literal::Integer(n) => int_value_for(n),
16892 Literal::Float(x) => Value::Float(x),
16893 Literal::String(s) => Value::Text(s),
16894 Literal::Bool(b) => Value::Bool(b),
16895 Literal::Null => Value::Null,
16896 Literal::Vector(v) => Value::Vector(v),
16897 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
16898 }
16899}
16900
16901fn int_value_for(n: i64) -> Value {
16905 if let Ok(small) = i32::try_from(n) {
16906 Value::Int(small)
16907 } else {
16908 Value::BigInt(n)
16909 }
16910}
16911
16912#[allow(clippy::too_many_lines)]
16918fn check_unsigned_range(
16923 v: &Value,
16924 schema: &ColumnSchema,
16925 position: usize,
16926) -> Result<(), EngineError> {
16927 if !schema.is_unsigned {
16928 return Ok(());
16929 }
16930 let n = match v {
16931 Value::SmallInt(x) => i64::from(*x),
16932 Value::Int(x) => i64::from(*x),
16933 Value::BigInt(x) => *x,
16934 _ => return Ok(()), };
16936 if n < 0 {
16937 return Err(EngineError::Unsupported(alloc::format!(
16938 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
16939 schema.name
16940 )));
16941 }
16942 Ok(())
16943}
16944
16945fn coerce_value(
16946 v: Value,
16947 expected: DataType,
16948 col_name: &str,
16949 position: usize,
16950) -> Result<Value, EngineError> {
16951 if v.is_null() {
16952 return Ok(Value::Null);
16953 }
16954 let actual = v.data_type().expect("non-null");
16955 if actual == expected {
16956 return Ok(v);
16957 }
16958 let coerced = match (v, expected) {
16959 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16960 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16961 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16962 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16963 i128::from(n),
16964 precision,
16965 scale,
16966 col_name,
16967 )?),
16968 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
16969 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16970 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16971 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16972 i128::from(n),
16973 precision,
16974 scale,
16975 col_name,
16976 )?),
16977 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
16978 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16979 #[allow(clippy::cast_precision_loss)]
16980 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
16981 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16982 i128::from(n),
16983 precision,
16984 scale,
16985 col_name,
16986 )?),
16987 (Value::Float(x), DataType::Numeric { precision, scale }) => {
16988 Some(numeric_from_float(x, precision, scale, col_name)?)
16989 }
16990 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17001 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17002 return Err(EngineError::Eval(EvalError::TypeMismatch {
17003 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17004 }));
17005 };
17006 Some(numeric_rescale(
17007 mantissa, src_scale, precision, scale, col_name,
17008 )?)
17009 }
17010 (Value::Text(s), DataType::Date) => {
17012 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17013 EngineError::Eval(EvalError::TypeMismatch {
17014 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17015 })
17016 })?;
17017 Some(Value::Date(d))
17018 }
17019 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17026 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17027 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17028 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17029 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17030 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17031 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17032 _ => None,
17033 },
17034 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17043 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17044 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17045 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17049 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17050 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17058 (Value::Text(s), DataType::Bytes) => {
17065 let bytes = decode_bytea_literal(&s).map_err(|e| {
17066 EngineError::Eval(EvalError::TypeMismatch {
17067 detail: alloc::format!(
17068 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17069 ),
17070 })
17071 })?;
17072 Some(Value::Bytes(bytes))
17073 }
17074 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17078 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17086 Some(b) => Some(Value::Uuid(b)),
17087 None => {
17088 return Err(EngineError::Eval(EvalError::TypeMismatch {
17089 detail: alloc::format!(
17090 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17091 ),
17092 }));
17093 }
17094 },
17095 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17100 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17106 Some(us) => Some(Value::Time(us)),
17107 None => {
17108 return Err(EngineError::Eval(EvalError::TypeMismatch {
17109 detail: alloc::format!(
17110 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17111 ),
17112 }));
17113 }
17114 },
17115 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17117 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17122 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17123 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17124 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17128 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17129 Err(_) => {
17130 return Err(EngineError::Eval(EvalError::TypeMismatch {
17131 detail: alloc::format!(
17132 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17133 ),
17134 }));
17135 }
17136 },
17137 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17139 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17143 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17144 None => {
17145 return Err(EngineError::Eval(EvalError::TypeMismatch {
17146 detail: alloc::format!(
17147 "invalid input syntax for type time with time zone: \
17148 {s:?} (column `{col_name}`)"
17149 ),
17150 }));
17151 }
17152 },
17153 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17155 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17156 }
17157 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17161 Some(c) => Some(Value::Money(c)),
17162 None => {
17163 return Err(EngineError::Eval(EvalError::TypeMismatch {
17164 detail: alloc::format!(
17165 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17166 ),
17167 }));
17168 }
17169 },
17170 (Value::SmallInt(n), DataType::Money) => {
17174 Some(Value::Money(i64::from(n).saturating_mul(100)))
17175 }
17176 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17177 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17178 (Value::Float(x), DataType::Money) => {
17179 let scaled = x * 100.0;
17182 let cents = if scaled >= 0.0 {
17183 (scaled + 0.5) as i64
17184 } else {
17185 (scaled - 0.5) as i64
17186 };
17187 Some(Value::Money(cents))
17188 }
17189 (Value::Numeric { scaled, scale }, DataType::Money) => {
17190 let cents = if scale == 2 {
17193 scaled
17194 } else if scale < 2 {
17195 let mult = 10_i128.pow(u32::from(2 - scale));
17196 scaled.saturating_mul(mult)
17197 } else {
17198 let div = 10_i128.pow(u32::from(scale - 2));
17199 let half = div / 2;
17200 let bias = if scaled >= 0 { half } else { -half };
17201 (scaled + bias) / div
17202 };
17203 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17204 }
17205 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17207 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17211 Some(v) => Some(v),
17212 None => {
17213 return Err(EngineError::Eval(EvalError::TypeMismatch {
17214 detail: alloc::format!(
17215 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17216 ),
17217 }));
17218 }
17219 },
17220 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17222 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17224 Some(pairs) => Some(Value::Hstore(pairs)),
17225 None => {
17226 return Err(EngineError::Eval(EvalError::TypeMismatch {
17227 detail: alloc::format!(
17228 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17229 ),
17230 }));
17231 }
17232 },
17233 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17235 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17238 Ok(m) => Some(Value::IntArray2D(m)),
17239 Err(e) => {
17240 return Err(EngineError::Eval(EvalError::TypeMismatch {
17241 detail: alloc::format!(
17242 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17243 ),
17244 }));
17245 }
17246 },
17247 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17248 Ok(m) => Some(Value::BigIntArray2D(m)),
17249 Err(e) => {
17250 return Err(EngineError::Eval(EvalError::TypeMismatch {
17251 detail: alloc::format!(
17252 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17253 ),
17254 }));
17255 }
17256 },
17257 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17258 Ok(m) => Some(Value::TextArray2D(m)),
17259 Err(e) => {
17260 return Err(EngineError::Eval(EvalError::TypeMismatch {
17261 detail: alloc::format!(
17262 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17263 ),
17264 }));
17265 }
17266 },
17267 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17269 (Value::BigIntArray2D(rows), DataType::Text) => {
17270 Some(Value::Text(format_bigint_2d_text(&rows)))
17271 }
17272 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17273 (Value::Text(s), DataType::TextArray) => {
17278 let arr = decode_text_array_literal(&s).map_err(|e| {
17279 EngineError::Eval(EvalError::TypeMismatch {
17280 detail: alloc::format!(
17281 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17282 ),
17283 })
17284 })?;
17285 Some(Value::TextArray(arr))
17286 }
17287 (Value::Text(s), DataType::IntArray) => {
17293 let arr = decode_text_array_literal(&s).map_err(|e| {
17294 EngineError::Eval(EvalError::TypeMismatch {
17295 detail: alloc::format!(
17296 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17297 ),
17298 })
17299 })?;
17300 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17301 for elem in arr {
17302 match elem {
17303 None => out.push(None),
17304 Some(t) => {
17305 let n: i32 = t.parse().map_err(|_| {
17306 EngineError::Eval(EvalError::TypeMismatch {
17307 detail: alloc::format!(
17308 "cannot parse {t:?} as INT element for `{col_name}`"
17309 ),
17310 })
17311 })?;
17312 out.push(Some(n));
17313 }
17314 }
17315 }
17316 Some(Value::IntArray(out))
17317 }
17318 (Value::Text(s), DataType::BigIntArray) => {
17319 let arr = decode_text_array_literal(&s).map_err(|e| {
17320 EngineError::Eval(EvalError::TypeMismatch {
17321 detail: alloc::format!(
17322 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17323 ),
17324 })
17325 })?;
17326 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17327 for elem in arr {
17328 match elem {
17329 None => out.push(None),
17330 Some(t) => {
17331 let n: i64 = t.parse().map_err(|_| {
17332 EngineError::Eval(EvalError::TypeMismatch {
17333 detail: alloc::format!(
17334 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17335 ),
17336 })
17337 })?;
17338 out.push(Some(n));
17339 }
17340 }
17341 }
17342 Some(Value::BigIntArray(out))
17343 }
17344 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17348 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17357 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17358 EngineError::Eval(EvalError::TypeMismatch {
17359 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17360 })
17361 })?;
17362 if parsed.len() != dim as usize {
17363 return Err(EngineError::Eval(EvalError::TypeMismatch {
17364 detail: alloc::format!(
17365 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17366 parsed.len()
17367 ),
17368 }));
17369 }
17370 Some(match encoding {
17371 VecEncoding::F32 => Value::Vector(parsed),
17372 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17373 VecEncoding::F16 => {
17374 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17375 }
17376 })
17377 }
17378 (Value::Text(s), DataType::TsVector) => {
17388 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17389 EngineError::Eval(EvalError::TypeMismatch {
17390 detail: alloc::format!(
17391 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17392 ),
17393 })
17394 })?;
17395 Some(Value::TsVector(lexs))
17396 }
17397 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17398 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17399 EngineError::Eval(EvalError::TypeMismatch {
17400 detail: alloc::format!(
17401 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17402 ),
17403 })
17404 })?;
17405 Some(Value::Timestamp(t))
17406 }
17407 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17410 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17411 }
17412 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17416 (Value::Timestamp(t), DataType::Date) => {
17417 let days = t.div_euclid(86_400_000_000);
17418 i32::try_from(days).ok().map(Value::Date)
17419 }
17420 (
17421 Value::Numeric {
17422 scaled,
17423 scale: src_scale,
17424 },
17425 DataType::Numeric { precision, scale },
17426 ) => Some(numeric_rescale(
17427 scaled, src_scale, precision, scale, col_name,
17428 )?),
17429 #[allow(clippy::cast_precision_loss)]
17430 (Value::Numeric { scaled, scale }, DataType::Float) => {
17431 let mut div = 1.0_f64;
17432 for _ in 0..scale {
17433 div *= 10.0;
17434 }
17435 Some(Value::Float((scaled as f64) / div))
17436 }
17437 (Value::Numeric { scaled, scale }, DataType::Int) => {
17438 let truncated = numeric_truncate_to_integer(scaled, scale);
17439 i32::try_from(truncated).ok().map(Value::Int)
17440 }
17441 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17442 let truncated = numeric_truncate_to_integer(scaled, scale);
17443 i64::try_from(truncated).ok().map(Value::BigInt)
17444 }
17445 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17446 let truncated = numeric_truncate_to_integer(scaled, scale);
17447 i16::try_from(truncated).ok().map(Value::SmallInt)
17448 }
17449 (Value::Text(s), DataType::Varchar(max)) => {
17451 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17452 Some(Value::Text(s))
17453 } else {
17454 return Err(EngineError::Unsupported(alloc::format!(
17455 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17456 {} chars",
17457 s.chars().count()
17458 )));
17459 }
17460 }
17461 (
17469 Value::Vector(v),
17470 DataType::Vector {
17471 dim,
17472 encoding: VecEncoding::Sq8,
17473 },
17474 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17475 (
17480 Value::Vector(v),
17481 DataType::Vector {
17482 dim,
17483 encoding: VecEncoding::F16,
17484 },
17485 ) if v.len() == dim as usize => Some(Value::HalfVector(
17486 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17487 )),
17488 (Value::Text(s), DataType::Char(size)) => {
17492 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17493 if len > size {
17494 return Err(EngineError::Unsupported(alloc::format!(
17495 "value for CHAR({size}) column `{col_name}` exceeds length: \
17496 {len} chars"
17497 )));
17498 }
17499 let need = (size - len) as usize;
17500 let mut padded = s;
17501 padded.reserve(need);
17502 for _ in 0..need {
17503 padded.push(' ');
17504 }
17505 Some(Value::Text(padded))
17506 }
17507 _ => None,
17508 };
17509 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17510 column: col_name.into(),
17511 expected,
17512 actual,
17513 position,
17514 }))
17515}
17516
17517fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17523 use core::fmt::Write;
17524 let mut out = alloc::string::String::from("(");
17525 for (i, a) in args.iter().enumerate() {
17526 if i > 0 {
17527 out.push_str(", ");
17528 }
17529 match a.mode {
17530 spg_sql::ast::FunctionArgMode::In => {}
17531 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17532 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17533 }
17534 if let Some(n) = &a.name {
17535 out.push_str(n);
17536 out.push(' ');
17537 }
17538 match &a.ty {
17539 spg_sql::ast::FunctionArgType::Typed(t) => {
17540 let _ = write!(out, "{t}");
17541 }
17542 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17543 }
17544 }
17545 out.push(')');
17546 out
17547}
17548
17549fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
17558 match expr {
17559 spg_sql::ast::Expr::FunctionCall { name, args } => {
17560 name.eq_ignore_ascii_case("unnest") && args.len() == 1
17561 }
17562 _ => false,
17563 }
17564}
17565
17566fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
17570 match expr {
17571 spg_sql::ast::Expr::FunctionCall { name, args }
17572 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
17573 {
17574 Some(&args[0])
17575 }
17576 _ => None,
17577 }
17578}
17579
17580fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
17585 match v {
17586 Value::Null => Ok(Vec::new()),
17587 Value::TextArray(items) => Ok(items
17588 .iter()
17589 .map(|opt| {
17590 opt.as_ref()
17591 .map(|s| Value::Text(s.clone()))
17592 .unwrap_or(Value::Null)
17593 })
17594 .collect()),
17595 Value::IntArray(items) => Ok(items
17596 .iter()
17597 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
17598 .collect()),
17599 Value::BigIntArray(items) => Ok(items
17600 .iter()
17601 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
17602 .collect()),
17603 other => Err(EngineError::Eval(EvalError::TypeMismatch {
17604 detail: alloc::format!(
17605 "unnest() expects an array argument, got {:?}",
17606 other.data_type()
17607 ),
17608 })),
17609 }
17610}
17611
17612#[cfg(test)]
17613mod tests {
17614 use super::*;
17615 use alloc::vec;
17616
17617 fn unwrap_command_ok(r: &QueryResult) -> usize {
17618 match r {
17619 QueryResult::CommandOk { affected, .. } => *affected,
17620 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17621 }
17622 }
17623
17624 #[test]
17625 fn update_seek_positions_engages_on_indexed_eq() {
17626 let mut e = Engine::new();
17627 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
17628 .unwrap();
17629 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
17630 for i in 0..100 {
17631 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
17632 .unwrap();
17633 }
17634 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
17635 .expect("parse");
17636 let Statement::Update(u) = stmt else {
17637 panic!("expected Update, got {stmt:?}");
17638 };
17639 let w = u.where_.as_ref().expect("where");
17640 let table = e.catalog().get("b").unwrap();
17641 let schema_cols = table.schema().columns.clone();
17642 let Expr::Binary { lhs, op, rhs } = w else {
17644 panic!("WHERE not Binary: {w:?}");
17645 };
17646 assert_eq!(*op, BinOp::Eq, "op not Eq");
17647 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
17648 assert!(
17649 pair.is_some(),
17650 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
17651 );
17652 let (col_pos, value) = pair.unwrap();
17653 assert!(
17654 table.index_on(col_pos).is_some(),
17655 "no index on col {col_pos}"
17656 );
17657 assert!(
17658 IndexKey::from_value(&value).is_some(),
17659 "IndexKey::from_value None for {value:?}"
17660 );
17661 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
17662 assert_eq!(positions, Some(vec![42]), "seek did not engage");
17663 }
17664
17665 #[test]
17666 fn create_table_registers_schema() {
17667 let mut e = Engine::new();
17668 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17669 .unwrap();
17670 assert_eq!(e.catalog().table_count(), 1);
17671 let t = e.catalog().get("foo").unwrap();
17672 assert_eq!(t.schema().columns.len(), 2);
17673 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17674 assert!(!t.schema().columns[0].nullable);
17675 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17676 }
17677
17678 #[test]
17679 fn create_table_vector_default_is_f32_encoded() {
17680 let mut e = Engine::new();
17681 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17682 let t = e.catalog().get("t").unwrap();
17683 assert_eq!(
17684 t.schema().columns[0].ty,
17685 DataType::Vector {
17686 dim: 8,
17687 encoding: VecEncoding::F32,
17688 },
17689 );
17690 }
17691
17692 #[test]
17693 fn create_table_vector_using_sq8_succeeds() {
17694 let mut e = Engine::new();
17698 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17699 let t = e.catalog().get("t").unwrap();
17700 assert_eq!(
17701 t.schema().columns[0].ty,
17702 DataType::Vector {
17703 dim: 8,
17704 encoding: VecEncoding::Sq8,
17705 },
17706 );
17707 }
17708
17709 #[test]
17710 fn insert_into_sq8_column_quantises_f32_payload() {
17711 let mut e = Engine::new();
17718 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17719 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17720 .unwrap();
17721 let t = e.catalog().get("t").unwrap();
17722 assert_eq!(t.rows().len(), 1);
17723 match &t.rows()[0].values[0] {
17724 Value::Sq8Vector(q) => {
17725 assert_eq!(q.bytes.len(), 4);
17726 assert!((q.min - 0.0).abs() < 1e-6);
17728 assert!((q.max - 1.0).abs() < 1e-6);
17729 }
17730 other => panic!("expected Sq8Vector cell, got {other:?}"),
17731 }
17732 }
17733
17734 #[test]
17735 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17736 let mut e = Engine::new();
17743 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17744 .unwrap();
17745 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17746 .unwrap();
17747 let t = e.catalog().get("t").unwrap();
17748 assert_eq!(t.rows().len(), 1);
17749 match &t.rows()[0].values[0] {
17750 Value::HalfVector(h) => {
17751 assert_eq!(h.dim(), 4);
17752 let back = h.to_f32_vec();
17753 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17754 for (g, e) in back.iter().zip(expected.iter()) {
17755 assert!(
17756 (g - e).abs() < 1e-6,
17757 "{g} vs {e} should be exact on f16 grid"
17758 );
17759 }
17760 }
17761 other => panic!("expected HalfVector cell, got {other:?}"),
17762 }
17763 }
17764
17765 #[test]
17766 fn alter_index_rebuild_in_place_succeeds() {
17767 let mut e = Engine::new();
17772 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17773 .unwrap();
17774 for i in 0..8_i32 {
17775 #[allow(clippy::cast_precision_loss)]
17776 let base = (i as f32) * 0.1;
17777 e.execute(&alloc::format!(
17778 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17779 b1 = base + 0.01,
17780 b2 = base + 0.02,
17781 ))
17782 .unwrap();
17783 }
17784 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17785 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
17786 assert_eq!(
17788 e.catalog().get("t").unwrap().schema().columns[1].ty,
17789 DataType::Vector {
17790 dim: 3,
17791 encoding: VecEncoding::F32,
17792 },
17793 );
17794 }
17795
17796 #[test]
17797 fn alter_index_rebuild_with_encoding_switches_cell_type() {
17798 let mut e = Engine::new();
17803 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
17804 .unwrap();
17805 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
17806 .unwrap();
17807 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17808 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
17809 .unwrap();
17810 let t = e.catalog().get("t").unwrap();
17811 assert_eq!(
17812 t.schema().columns[1].ty,
17813 DataType::Vector {
17814 dim: 4,
17815 encoding: VecEncoding::Sq8,
17816 },
17817 );
17818 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
17819 }
17820
17821 #[test]
17822 fn alter_index_rebuild_unknown_index_errors() {
17823 let mut e = Engine::new();
17824 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
17825 assert!(
17826 matches!(
17827 &err,
17828 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
17829 ),
17830 "got: {err}"
17831 );
17832 }
17833
17834 #[test]
17835 fn alter_index_rebuild_on_btree_index_errors() {
17836 let mut e = Engine::new();
17839 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17840 e.execute("INSERT INTO t VALUES (1)").unwrap();
17841 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
17842 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
17843 assert!(
17844 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
17845 "got: {err}"
17846 );
17847 }
17848
17849 #[test]
17850 fn prepared_insert_substitutes_placeholders() {
17851 let mut e = Engine::new();
17857 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17858 .unwrap();
17859 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
17860 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
17861 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
17862 .unwrap();
17863 }
17864 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
17866 let QueryResult::Rows { rows, .. } = rows_result else {
17867 panic!("expected Rows")
17868 };
17869 assert_eq!(rows.len(), 3);
17870 }
17871
17872 #[test]
17873 fn prepared_select_with_placeholder_filters_rows() {
17874 let mut e = Engine::new();
17875 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17876 .unwrap();
17877 for i in 0..10_i32 {
17878 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17879 .unwrap();
17880 }
17881 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17882 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
17883 else {
17884 panic!("expected Rows")
17885 };
17886 assert_eq!(rows.len(), 1);
17888 assert_eq!(rows[0].values[0], Value::Int(5));
17889 }
17890
17891 #[test]
17892 fn prepared_too_few_params_errors() {
17893 let mut e = Engine::new();
17894 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17895 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17896 let err = e.execute_prepared(stmt, &[]).unwrap_err();
17897 assert!(
17898 matches!(
17899 &err,
17900 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
17901 ),
17902 "got: {err}"
17903 );
17904 }
17905
17906 #[test]
17907 fn bytea_cast_round_trips_text_input() {
17908 let e = Engine::new();
17911 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
17912 let QueryResult::Rows { rows, .. } = r else {
17913 panic!("expected Rows")
17914 };
17915 assert_eq!(rows.len(), 1);
17916 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
17917 }
17918
17919 #[test]
17920 fn bytea_cast_pg_escape_hex_form() {
17921 let e = Engine::new();
17925 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
17926 let QueryResult::Rows { rows, .. } = r else {
17927 panic!("expected Rows")
17928 };
17929 assert_eq!(
17930 rows[0].values[0],
17931 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
17932 );
17933 }
17934
17935 #[test]
17936 fn bytea_cast_chains_through_octet_length() {
17937 let e = Engine::new();
17941 let r = e
17942 .execute_readonly("SELECT octet_length('hello'::bytea)")
17943 .unwrap();
17944 let QueryResult::Rows { rows, .. } = r else {
17945 panic!("expected Rows")
17946 };
17947 match &rows[0].values[0] {
17948 Value::Int(n) => assert_eq!(*n, 5),
17949 Value::BigInt(n) => assert_eq!(*n, 5),
17950 other => panic!("expected integer length, got {other:?}"),
17951 }
17952 }
17953
17954 #[test]
17955 fn readonly_prepared_on_snapshot_select_with_placeholder() {
17956 let mut e = Engine::new();
17962 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17963 .unwrap();
17964 for i in 0..10_i32 {
17965 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17966 .unwrap();
17967 }
17968 let snapshot = e.clone_snapshot();
17969 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17970 let QueryResult::Rows { rows, .. } =
17971 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
17972 .unwrap()
17973 else {
17974 panic!("expected Rows")
17975 };
17976 assert_eq!(rows.len(), 1);
17977 assert_eq!(rows[0].values[0], Value::Int(5));
17978 }
17979
17980 #[test]
17981 fn readonly_prepared_on_snapshot_rejects_writes() {
17982 let mut e = Engine::new();
17986 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17987 let snapshot = e.clone_snapshot();
17988 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17989 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
17990 .unwrap_err();
17991 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
17992 }
17993
17994 #[test]
17995 fn readonly_prepared_on_snapshot_frozen_view() {
17996 let mut e = Engine::new();
18002 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18003 e.execute("INSERT INTO t VALUES (1)").unwrap();
18004 let snapshot = e.clone_snapshot();
18005 e.execute("INSERT INTO t VALUES (2)").unwrap();
18006 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18007 let QueryResult::Rows { rows, .. } =
18008 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18009 .unwrap()
18010 else {
18011 panic!("expected Rows")
18012 };
18013 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18014 }
18015
18016 #[test]
18017 fn describe_prepared_on_snapshot_resolves_columns() {
18018 let mut e = Engine::new();
18023 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18024 .unwrap();
18025 let snapshot = e.clone_snapshot();
18026 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18027 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18028 assert_eq!(cols.len(), 2);
18029 assert_eq!(cols[0].name, "id");
18030 assert_eq!(cols[0].ty, DataType::Int);
18031 assert_eq!(cols[1].name, "name");
18032 assert_eq!(cols[1].ty, DataType::Text);
18033 }
18034
18035 #[test]
18036 fn insert_into_half_column_dim_mismatch_errors() {
18037 let mut e = Engine::new();
18038 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18039 .unwrap();
18040 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18041 assert!(matches!(
18042 &err,
18043 EngineError::Storage(StorageError::TypeMismatch { .. })
18044 ));
18045 }
18046
18047 #[test]
18048 fn insert_into_sq8_column_dim_mismatch_errors() {
18049 let mut e = Engine::new();
18054 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18055 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18056 assert!(
18057 matches!(
18058 &err,
18059 EngineError::Storage(StorageError::TypeMismatch { .. })
18060 ),
18061 "got: {err}",
18062 );
18063 }
18064
18065 #[test]
18066 fn create_table_duplicate_errors() {
18067 let mut e = Engine::new();
18068 e.execute("CREATE TABLE foo (a INT)").unwrap();
18069 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18070 assert!(matches!(
18071 err,
18072 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18073 ));
18074 }
18075
18076 #[test]
18077 fn insert_into_unknown_table_errors() {
18078 let mut e = Engine::new();
18079 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18080 assert!(matches!(
18081 err,
18082 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18083 ));
18084 }
18085
18086 #[test]
18087 fn insert_happy_path_reports_one_affected() {
18088 let mut e = Engine::new();
18089 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18090 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18091 assert_eq!(unwrap_command_ok(&r), 1);
18092 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18093 }
18094
18095 #[test]
18096 fn insert_arity_mismatch_propagates() {
18097 let mut e = Engine::new();
18098 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18099 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18100 assert!(matches!(
18101 err,
18102 EngineError::Storage(StorageError::ArityMismatch { .. })
18103 ));
18104 }
18105
18106 #[test]
18107 fn insert_negative_integer_via_unary_minus() {
18108 let mut e = Engine::new();
18109 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18110 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18111 let rows = e.catalog().get("foo").unwrap().rows();
18112 assert_eq!(rows[0].values[0], Value::Int(-7));
18113 }
18114
18115 #[test]
18116 fn insert_expression_evaluated_against_empty_context() {
18117 let mut e = Engine::new();
18122 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18123 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18124 let rows = e.catalog().get("foo").unwrap().rows();
18125 assert_eq!(rows[0].values[0], Value::Int(3));
18126 }
18127
18128 #[test]
18129 fn select_star_returns_all_rows_in_insertion_order() {
18130 let mut e = Engine::new();
18131 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18132 .unwrap();
18133 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18134 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18135 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18136
18137 let r = e.execute("SELECT * FROM foo").unwrap();
18138 let QueryResult::Rows { columns, rows } = r else {
18139 panic!("expected Rows")
18140 };
18141 assert_eq!(columns.len(), 2);
18142 assert_eq!(columns[0].name, "a");
18143 assert_eq!(rows.len(), 3);
18144 assert_eq!(
18145 rows[1].values,
18146 vec![Value::Int(2), Value::Text("two".into())]
18147 );
18148 }
18149
18150 #[test]
18151 fn select_star_on_empty_table_returns_zero_rows() {
18152 let mut e = Engine::new();
18153 e.execute("CREATE TABLE foo (a INT)").unwrap();
18154 let r = e.execute("SELECT * FROM foo").unwrap();
18155 match r {
18156 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18157 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18158 }
18159 }
18160
18161 fn make_three_row_users(e: &mut Engine) {
18164 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18165 .unwrap();
18166 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18167 .unwrap();
18168 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18169 .unwrap();
18170 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18171 .unwrap();
18172 }
18173
18174 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18175 match r {
18176 QueryResult::Rows { columns, rows } => (columns, rows),
18177 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18178 }
18179 }
18180
18181 #[test]
18182 fn where_filter_passes_only_true_rows() {
18183 let mut e = Engine::new();
18184 make_three_row_users(&mut e);
18185 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18186 let (_, rows) = unwrap_rows(r);
18187 assert_eq!(rows.len(), 2);
18188 assert_eq!(rows[0].values[0], Value::Int(2));
18189 assert_eq!(rows[1].values[0], Value::Int(3));
18190 }
18191
18192 #[test]
18193 fn where_with_null_result_filters_out_row() {
18194 let mut e = Engine::new();
18195 make_three_row_users(&mut e);
18196 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18198 let (_, rows) = unwrap_rows(r);
18199 assert_eq!(rows.len(), 1);
18200 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18201 }
18202
18203 #[test]
18204 fn projection_named_columns() {
18205 let mut e = Engine::new();
18206 make_three_row_users(&mut e);
18207 let r = e.execute("SELECT name, score FROM users").unwrap();
18208 let (cols, rows) = unwrap_rows(r);
18209 assert_eq!(cols.len(), 2);
18210 assert_eq!(cols[0].name, "name");
18211 assert_eq!(cols[1].name, "score");
18212 assert_eq!(rows.len(), 3);
18213 assert_eq!(
18214 rows[0].values,
18215 vec![Value::Text("alice".into()), Value::Int(90)]
18216 );
18217 }
18218
18219 #[test]
18220 fn projection_with_column_alias() {
18221 let mut e = Engine::new();
18222 make_three_row_users(&mut e);
18223 let r = e
18224 .execute("SELECT name AS who FROM users WHERE id = 1")
18225 .unwrap();
18226 let (cols, rows) = unwrap_rows(r);
18227 assert_eq!(cols[0].name, "who");
18228 assert_eq!(rows.len(), 1);
18229 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18230 }
18231
18232 #[test]
18233 fn qualified_column_with_table_alias_resolves() {
18234 let mut e = Engine::new();
18235 make_three_row_users(&mut e);
18236 let r = e
18237 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18238 .unwrap();
18239 let (cols, rows) = unwrap_rows(r);
18240 assert_eq!(cols.len(), 2);
18241 assert_eq!(rows.len(), 2);
18242 }
18243
18244 #[test]
18245 fn qualified_column_with_wrong_alias_errors() {
18246 let mut e = Engine::new();
18247 make_three_row_users(&mut e);
18248 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18249 assert!(matches!(
18250 err,
18251 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18252 ));
18253 }
18254
18255 #[test]
18256 fn select_unknown_column_errors_in_projection() {
18257 let mut e = Engine::new();
18258 make_three_row_users(&mut e);
18259 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18260 assert!(matches!(
18261 err,
18262 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18263 ));
18264 }
18265
18266 #[test]
18267 fn where_unknown_column_errors() {
18268 let mut e = Engine::new();
18269 make_three_row_users(&mut e);
18270 let err = e
18271 .execute("SELECT * FROM users WHERE ghost = 1")
18272 .unwrap_err();
18273 assert!(matches!(
18274 err,
18275 EngineError::Eval(EvalError::ColumnNotFound { .. })
18276 ));
18277 }
18278
18279 #[test]
18280 fn expression_projection_evaluates_and_renders() {
18281 let mut e = Engine::new();
18284 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18285 e.execute("INSERT INTO t VALUES (3)").unwrap();
18286 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18287 assert_eq!(rows.len(), 1);
18288 assert_eq!(rows[0].values[0], Value::Int(3));
18291 }
18292
18293 #[test]
18294 fn select_unknown_table_errors() {
18295 let mut e = Engine::new();
18296 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18297 assert!(matches!(
18298 err,
18299 EngineError::Storage(StorageError::TableNotFound { .. })
18300 ));
18301 }
18302
18303 #[test]
18304 fn invalid_sql_returns_parse_error() {
18305 let mut e = Engine::new();
18308 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18309 assert!(matches!(err, EngineError::Parse(_)));
18310 }
18311
18312 #[test]
18315 fn create_index_registers_on_table() {
18316 let mut e = Engine::new();
18317 make_three_row_users(&mut e);
18318 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18319 let t = e.catalog().get("users").unwrap();
18320 assert_eq!(t.indices().len(), 1);
18321 assert_eq!(t.indices()[0].name, "by_name");
18322 }
18323
18324 #[test]
18325 fn create_index_on_unknown_table_errors() {
18326 let mut e = Engine::new();
18327 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18328 assert!(matches!(
18329 err,
18330 EngineError::Storage(StorageError::TableNotFound { .. })
18331 ));
18332 }
18333
18334 #[test]
18335 fn create_index_on_unknown_column_errors() {
18336 let mut e = Engine::new();
18337 make_three_row_users(&mut e);
18338 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18339 assert!(matches!(
18340 err,
18341 EngineError::Storage(StorageError::ColumnNotFound { .. })
18342 ));
18343 }
18344
18345 #[test]
18346 fn select_eq_uses_index_returns_same_rows_as_scan() {
18347 let mut without = Engine::new();
18351 make_three_row_users(&mut without);
18352 let mut with = Engine::new();
18353 make_three_row_users(&mut with);
18354 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18355
18356 let q = "SELECT * FROM users WHERE id = 2";
18357 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18358 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18359 assert_eq!(no_idx_rows, idx_rows);
18360 assert_eq!(idx_rows.len(), 1);
18361 }
18362
18363 #[test]
18364 fn select_eq_with_no_matching_index_value_returns_empty() {
18365 let mut e = Engine::new();
18366 make_three_row_users(&mut e);
18367 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18368 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18369 assert_eq!(rows.len(), 0);
18370 }
18371
18372 #[test]
18375 fn begin_sets_in_transaction_flag() {
18376 let mut e = Engine::new();
18377 assert!(!e.in_transaction());
18378 e.execute("BEGIN").unwrap();
18379 assert!(e.in_transaction());
18380 }
18381
18382 #[test]
18383 fn double_begin_errors() {
18384 let mut e = Engine::new();
18385 e.execute("BEGIN").unwrap();
18386 let err = e.execute("BEGIN").unwrap_err();
18387 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18388 }
18389
18390 #[test]
18391 fn commit_without_begin_errors() {
18392 let mut e = Engine::new();
18393 let err = e.execute("COMMIT").unwrap_err();
18394 assert_eq!(err, EngineError::NoActiveTransaction);
18395 }
18396
18397 #[test]
18398 fn rollback_without_begin_errors() {
18399 let mut e = Engine::new();
18400 let err = e.execute("ROLLBACK").unwrap_err();
18401 assert_eq!(err, EngineError::NoActiveTransaction);
18402 }
18403
18404 #[test]
18405 fn commit_applies_shadow_to_committed_catalog() {
18406 let mut e = Engine::new();
18407 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18408 e.execute("BEGIN").unwrap();
18409 e.execute("INSERT INTO t VALUES (1)").unwrap();
18410 e.execute("INSERT INTO t VALUES (2)").unwrap();
18411 e.execute("COMMIT").unwrap();
18412 assert!(!e.in_transaction());
18413 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18414 }
18415
18416 #[test]
18417 fn rollback_discards_shadow() {
18418 let mut e = Engine::new();
18419 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18420 e.execute("BEGIN").unwrap();
18421 e.execute("INSERT INTO t VALUES (1)").unwrap();
18422 e.execute("INSERT INTO t VALUES (2)").unwrap();
18423 e.execute("ROLLBACK").unwrap();
18424 assert!(!e.in_transaction());
18425 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18426 }
18427
18428 #[test]
18429 fn select_during_tx_sees_uncommitted_writes_own_session() {
18430 let mut e = Engine::new();
18433 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18434 e.execute("BEGIN").unwrap();
18435 e.execute("INSERT INTO t VALUES (42)").unwrap();
18436 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18437 assert_eq!(rows.len(), 1);
18438 assert_eq!(rows[0].values[0], Value::Int(42));
18439 }
18440
18441 #[test]
18442 fn snapshot_with_no_users_is_bare_catalog_format() {
18443 let mut e = Engine::new();
18444 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18445 let bytes = e.snapshot();
18446 assert_eq!(
18447 &bytes[..8],
18448 b"SPGDB001",
18449 "must be the bare v3.x catalog magic"
18450 );
18451 let e2 = Engine::restore_envelope(&bytes).unwrap();
18452 assert!(e2.users().is_empty());
18453 assert_eq!(e2.catalog().table_count(), 1);
18454 }
18455
18456 #[test]
18457 fn snapshot_with_users_round_trips_both_via_envelope() {
18458 let mut e = Engine::new();
18459 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18460 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18461 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18462 .unwrap();
18463 let bytes = e.snapshot();
18464 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18465 let e2 = Engine::restore_envelope(&bytes).unwrap();
18466 assert_eq!(e2.users().len(), 2);
18467 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18468 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18469 assert_eq!(e2.verify_user("alice", "wrong"), None);
18470 assert_eq!(e2.catalog().table_count(), 1);
18471 }
18472
18473 #[test]
18474 fn ddl_inside_tx_also_rolled_back() {
18475 let mut e = Engine::new();
18476 e.execute("BEGIN").unwrap();
18477 e.execute("CREATE TABLE t (v INT)").unwrap();
18478 e.execute("SELECT * FROM t").unwrap();
18480 e.execute("ROLLBACK").unwrap();
18481 let err = e.execute("SELECT * FROM t").unwrap_err();
18483 assert!(matches!(
18484 err,
18485 EngineError::Storage(StorageError::TableNotFound { .. })
18486 ));
18487 }
18488
18489 #[test]
18492 fn create_publication_lands_in_catalog() {
18493 let mut e = Engine::new();
18494 assert!(e.publications().is_empty());
18495 e.execute("CREATE PUBLICATION pub_a").unwrap();
18496 assert_eq!(e.publications().len(), 1);
18497 assert!(e.publications().contains("pub_a"));
18498 }
18499
18500 #[test]
18501 fn create_publication_duplicate_errors() {
18502 let mut e = Engine::new();
18503 e.execute("CREATE PUBLICATION pub_a").unwrap();
18504 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18505 assert!(
18506 alloc::format!("{err:?}").contains("DuplicateName"),
18507 "got {err:?}"
18508 );
18509 }
18510
18511 #[test]
18512 fn drop_publication_silent_when_absent() {
18513 let mut e = Engine::new();
18514 let r = e.execute("DROP PUBLICATION nope").unwrap();
18517 match r {
18518 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18519 other => panic!("expected CommandOk, got {other:?}"),
18520 }
18521 }
18522
18523 #[test]
18524 fn drop_publication_present_reports_one_affected() {
18525 let mut e = Engine::new();
18526 e.execute("CREATE PUBLICATION pub_a").unwrap();
18527 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18528 match r {
18529 QueryResult::CommandOk {
18530 affected,
18531 modified_catalog,
18532 } => {
18533 assert_eq!(affected, 1);
18534 assert!(modified_catalog);
18535 }
18536 other => panic!("expected CommandOk, got {other:?}"),
18537 }
18538 assert!(e.publications().is_empty());
18539 }
18540
18541 #[test]
18542 fn publications_persist_across_snapshot_restore() {
18543 let mut e = Engine::new();
18548 e.execute("CREATE PUBLICATION pub_a").unwrap();
18549 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18550 .unwrap();
18551 let snap = e.snapshot();
18552 let e2 = Engine::restore_envelope(&snap).unwrap();
18553 assert_eq!(e2.publications().len(), 2);
18554 assert!(e2.publications().contains("pub_a"));
18555 assert!(e2.publications().contains("pub_b"));
18556 }
18557
18558 #[test]
18559 fn create_publication_allowed_inside_transaction() {
18560 let mut e = Engine::new();
18564 e.execute("BEGIN").unwrap();
18565 e.execute("CREATE PUBLICATION pub_a").unwrap();
18566 e.execute("COMMIT").unwrap();
18567 assert!(e.publications().contains("pub_a"));
18568 }
18569
18570 #[test]
18573 fn create_publication_for_table_list_lands_with_scope() {
18574 let mut e = Engine::new();
18575 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18576 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18577 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18578 .unwrap();
18579 let scope = e.publications().get("pub_a").cloned();
18580 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18581 panic!("expected ForTables scope, got {scope:?}")
18582 };
18583 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18584 }
18585
18586 #[test]
18587 fn create_publication_all_tables_except_lands_with_scope() {
18588 let mut e = Engine::new();
18589 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18590 .unwrap();
18591 let scope = e.publications().get("pub_a").cloned();
18592 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18593 panic!("expected AllTablesExcept scope, got {scope:?}")
18594 };
18595 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18596 }
18597
18598 #[test]
18599 fn show_publications_empty_returns_zero_rows() {
18600 let e = Engine::new();
18601 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18602 let QueryResult::Rows { rows, columns } = r else {
18603 panic!()
18604 };
18605 assert!(rows.is_empty());
18606 assert_eq!(columns.len(), 3);
18607 assert_eq!(columns[0].name, "name");
18608 assert_eq!(columns[1].name, "scope");
18609 assert_eq!(columns[2].name, "table_count");
18610 }
18611
18612 #[test]
18613 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18614 let mut e = Engine::new();
18615 e.execute("CREATE PUBLICATION z_pub").unwrap();
18616 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18617 .unwrap();
18618 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18619 .unwrap();
18620 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18621 let QueryResult::Rows { rows, .. } = r else {
18622 panic!()
18623 };
18624 assert_eq!(rows.len(), 3);
18625 let names: Vec<&str> = rows
18627 .iter()
18628 .map(|r| {
18629 if let Value::Text(s) = &r.values[0] {
18630 s.as_str()
18631 } else {
18632 panic!()
18633 }
18634 })
18635 .collect();
18636 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18637 match &rows[0].values[1] {
18639 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18640 other => panic!("expected Text, got {other:?}"),
18641 }
18642 assert_eq!(rows[0].values[2], Value::Int(2));
18643 match &rows[1].values[1] {
18645 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18646 other => panic!("expected Text, got {other:?}"),
18647 }
18648 assert_eq!(rows[1].values[2], Value::Int(1));
18649 match &rows[2].values[1] {
18651 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18652 other => panic!("expected Text, got {other:?}"),
18653 }
18654 assert_eq!(rows[2].values[2], Value::Null);
18655 }
18656
18657 #[test]
18658 fn for_list_scopes_persist_across_snapshot() {
18659 let mut e = Engine::new();
18662 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18663 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18664 .unwrap();
18665 let snap = e.snapshot();
18666 let e2 = Engine::restore_envelope(&snap).unwrap();
18667 assert_eq!(e2.publications().len(), 2);
18668 let p1 = e2.publications().get("p1").cloned();
18669 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18670 panic!("p1 scope lost: {p1:?}")
18671 };
18672 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18673 let p2 = e2.publications().get("p2").cloned();
18674 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18675 panic!("p2 scope lost: {p2:?}")
18676 };
18677 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18678 }
18679
18680 #[test]
18683 fn create_subscription_lands_in_catalog_with_defaults() {
18684 let mut e = Engine::new();
18685 e.execute(
18686 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18687 )
18688 .unwrap();
18689 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18690 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18691 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18692 assert!(s.enabled);
18693 assert_eq!(s.last_received_pos, 0);
18694 }
18695
18696 #[test]
18697 fn create_subscription_duplicate_name_errors() {
18698 let mut e = Engine::new();
18699 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18700 .unwrap();
18701 let err = e
18702 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18703 .unwrap_err();
18704 assert!(
18705 alloc::format!("{err:?}").contains("DuplicateName"),
18706 "got {err:?}"
18707 );
18708 }
18709
18710 #[test]
18711 fn drop_subscription_silent_when_absent() {
18712 let mut e = Engine::new();
18713 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18714 match r {
18715 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18716 other => panic!("expected CommandOk, got {other:?}"),
18717 }
18718 }
18719
18720 #[test]
18721 fn subscription_advance_updates_last_pos_monotone() {
18722 let mut e = Engine::new();
18723 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18724 .unwrap();
18725 assert!(e.subscription_advance("s", 100));
18726 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18727 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18729 assert!(e.subscription_advance("s", 200));
18730 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18731 assert!(!e.subscription_advance("missing", 1));
18732 }
18733
18734 #[test]
18735 fn show_subscriptions_returns_rows_ordered_by_name() {
18736 let mut e = Engine::new();
18737 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18738 .unwrap();
18739 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18740 .unwrap();
18741 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18742 let QueryResult::Rows { rows, columns } = r else {
18743 panic!()
18744 };
18745 assert_eq!(rows.len(), 2);
18746 assert_eq!(columns.len(), 5);
18747 assert_eq!(columns[0].name, "name");
18748 assert_eq!(columns[4].name, "last_received_pos");
18749 let names: Vec<&str> = rows
18751 .iter()
18752 .map(|r| {
18753 if let Value::Text(s) = &r.values[0] {
18754 s.as_str()
18755 } else {
18756 panic!()
18757 }
18758 })
18759 .collect();
18760 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18761 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18763 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18764 assert_eq!(rows[0].values[3], Value::Bool(true));
18765 assert_eq!(rows[0].values[4], Value::BigInt(0));
18766 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18768 }
18769
18770 #[test]
18771 fn subscriptions_persist_across_snapshot_envelope_v4() {
18772 let mut e = Engine::new();
18773 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18774 .unwrap();
18775 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18776 .unwrap();
18777 e.subscription_advance("s2", 42);
18778 let snap = e.snapshot();
18779 let e2 = Engine::restore_envelope(&snap).unwrap();
18780 assert_eq!(e2.subscriptions().len(), 2);
18781 let s1 = e2.subscriptions().get("s1").unwrap();
18782 assert_eq!(s1.conn_str, "h=A");
18783 assert_eq!(
18784 s1.publications,
18785 alloc::vec!["p1".to_string(), "p2".to_string()]
18786 );
18787 assert_eq!(s1.last_received_pos, 0);
18788 let s2 = e2.subscriptions().get("s2").unwrap();
18789 assert_eq!(s2.last_received_pos, 42);
18790 }
18791
18792 #[test]
18793 fn v3_envelope_loads_with_empty_subscriptions() {
18794 let mut e = Engine::new();
18798 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
18799 let catalog = e.catalog.serialize();
18800 let users = crate::users::serialize_users(&e.users);
18801 let pubs = e.publications.serialize();
18802 let mut buf = Vec::new();
18803 buf.extend_from_slice(b"SPGENV01");
18804 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18806 buf.extend_from_slice(&catalog);
18807 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18808 buf.extend_from_slice(&users);
18809 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18810 buf.extend_from_slice(&pubs);
18811 let crc = spg_crypto::crc32::crc32(&buf);
18812 buf.extend_from_slice(&crc.to_le_bytes());
18813
18814 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
18815 assert!(e2.subscriptions().is_empty());
18816 assert!(e2.publications().contains("pub_legacy"));
18817 }
18818
18819 #[test]
18820 fn create_subscription_allowed_inside_transaction() {
18821 let mut e = Engine::new();
18822 e.execute("BEGIN").unwrap();
18823 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18824 .unwrap();
18825 e.execute("COMMIT").unwrap();
18826 assert!(e.subscriptions().contains("s"));
18827 }
18828
18829 #[test]
18831 fn analyze_populates_histogram_bounds() {
18832 let mut e = Engine::new();
18833 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
18834 .unwrap();
18835 for i in 0..50 {
18836 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
18837 .unwrap();
18838 }
18839 e.execute("ANALYZE t").unwrap();
18840 let stats = e.statistics();
18841 let id_stats = stats.get("t", "id").unwrap();
18842 assert!(id_stats.histogram_bounds.len() >= 2);
18843 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
18844 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
18845 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
18846 assert_eq!(id_stats.n_distinct, 50);
18847 }
18848
18849 #[test]
18850 fn reanalyze_overwrites_prior_stats() {
18851 let mut e = Engine::new();
18852 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18853 for i in 0..10 {
18854 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18855 .unwrap();
18856 }
18857 e.execute("ANALYZE t").unwrap();
18858 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
18859 assert_eq!(n1, 10);
18860 for i in 10..30 {
18861 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18862 .unwrap();
18863 }
18864 e.execute("ANALYZE t").unwrap();
18865 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
18866 assert_eq!(n2, 30);
18867 }
18868
18869 #[test]
18870 fn analyze_unknown_table_errors() {
18871 let mut e = Engine::new();
18872 let err = e.execute("ANALYZE nonexistent").unwrap_err();
18873 assert!(matches!(
18874 err,
18875 EngineError::Storage(StorageError::TableNotFound { .. })
18876 ));
18877 }
18878
18879 #[test]
18880 fn bare_analyze_covers_all_user_tables() {
18881 let mut e = Engine::new();
18882 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18883 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
18884 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
18885 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
18886 let r = e.execute("ANALYZE").unwrap();
18887 match r {
18888 QueryResult::CommandOk {
18889 affected,
18890 modified_catalog,
18891 } => {
18892 assert_eq!(affected, 2);
18893 assert!(modified_catalog);
18894 }
18895 other => panic!("expected CommandOk, got {other:?}"),
18896 }
18897 assert!(e.statistics().get("t1", "id").is_some());
18898 assert!(e.statistics().get("t2", "name").is_some());
18899 }
18900
18901 #[test]
18902 fn select_from_spg_statistic_returns_rows_per_column() {
18903 let mut e = Engine::new();
18904 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18905 .unwrap();
18906 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
18907 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
18908 e.execute("ANALYZE t").unwrap();
18909 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
18910 let QueryResult::Rows { rows, columns } = r else {
18911 panic!()
18912 };
18913 assert_eq!(columns.len(), 6);
18915 assert_eq!(columns[0].name, "table_name");
18916 assert_eq!(columns[4].name, "histogram_bounds");
18917 assert_eq!(columns[5].name, "cold_row_count");
18918 assert_eq!(rows.len(), 2, "one row per column of t");
18919 match (&rows[0].values[0], &rows[0].values[1]) {
18921 (Value::Text(t), Value::Text(c)) => {
18922 assert_eq!(t, "t");
18923 assert_eq!(c, "id");
18925 }
18926 _ => panic!(),
18927 }
18928 }
18929
18930 #[test]
18931 fn analyze_skips_vector_columns() {
18932 let mut e = Engine::new();
18935 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18936 .unwrap();
18937 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
18938 e.execute("ANALYZE t").unwrap();
18939 assert!(e.statistics().get("t", "id").is_some());
18940 assert!(e.statistics().get("t", "v").is_none());
18941 }
18942
18943 #[test]
18944 fn statistics_persist_across_envelope_v5_round_trip() {
18945 let mut e = Engine::new();
18946 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18947 for i in 0..20 {
18948 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18949 .unwrap();
18950 }
18951 e.execute("ANALYZE").unwrap();
18952 let snap = e.snapshot();
18953 let e2 = Engine::restore_envelope(&snap).unwrap();
18954 let s = e2.statistics().get("t", "id").unwrap();
18955 assert_eq!(s.n_distinct, 20);
18956 }
18957
18958 #[test]
18961 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
18962 let mut e = Engine::new();
18966 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18967 for i in 0..9 {
18968 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18969 .unwrap();
18970 }
18971 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
18972 e.execute("INSERT INTO t VALUES (9)").unwrap();
18973 let needs = e.tables_needing_analyze();
18974 assert_eq!(needs, alloc::vec!["t".to_string()]);
18975 }
18976
18977 #[test]
18978 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
18979 let mut e = Engine::new();
18985 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18986 for i in 0..1000 {
18987 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18988 .unwrap();
18989 }
18990 e.execute("ANALYZE t").unwrap();
18991 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
18992 for i in 1000..1050 {
18993 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18994 .unwrap();
18995 }
18996 assert!(
18997 e.tables_needing_analyze().is_empty(),
18998 "50 inserts < threshold of ~105"
18999 );
19000 for i in 1050..1200 {
19001 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19002 .unwrap();
19003 }
19004 assert_eq!(
19005 e.tables_needing_analyze(),
19006 alloc::vec!["t".to_string()],
19007 "200 inserts > 0.1 × 1200 threshold"
19008 );
19009 }
19010
19011 #[test]
19012 fn auto_analyze_threshold_resets_after_analyze() {
19013 let mut e = Engine::new();
19014 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19015 for i in 0..200 {
19016 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19017 .unwrap();
19018 }
19019 assert!(!e.tables_needing_analyze().is_empty());
19020 e.execute("ANALYZE").unwrap();
19021 assert!(
19022 e.tables_needing_analyze().is_empty(),
19023 "ANALYZE must reset the counter"
19024 );
19025 }
19026
19027 #[test]
19028 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19029 let mut e = Engine::new();
19030 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19031 .unwrap();
19032 for i in 0..50 {
19033 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19034 .unwrap();
19035 }
19036 e.execute("ANALYZE t").unwrap();
19037 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19040 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19041 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19042 }
19043
19044 #[test]
19045 fn v4_envelope_loads_with_empty_statistics() {
19046 let mut e = Engine::new();
19050 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19051 .unwrap();
19052 let catalog = e.catalog.serialize();
19053 let users = crate::users::serialize_users(&e.users);
19054 let pubs = e.publications.serialize();
19055 let subs = e.subscriptions.serialize();
19056 let mut buf = Vec::new();
19057 buf.extend_from_slice(b"SPGENV01");
19058 buf.push(4u8);
19059 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19060 buf.extend_from_slice(&catalog);
19061 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19062 buf.extend_from_slice(&users);
19063 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19064 buf.extend_from_slice(&pubs);
19065 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19066 buf.extend_from_slice(&subs);
19067 let crc = spg_crypto::crc32::crc32(&buf);
19068 buf.extend_from_slice(&crc.to_le_bytes());
19069 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19070 assert!(e2.statistics().is_empty());
19071 }
19072
19073 #[test]
19074 fn v1_v2_envelope_loads_with_empty_publications() {
19075 let mut e = Engine::new();
19082 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19085 .unwrap();
19086
19087 let catalog = e.catalog.serialize();
19089 let users = crate::users::serialize_users(&e.users);
19090 let mut buf = Vec::new();
19091 buf.extend_from_slice(b"SPGENV01");
19092 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19094 buf.extend_from_slice(&catalog);
19095 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19096 buf.extend_from_slice(&users);
19097 let crc = spg_crypto::crc32::crc32(&buf);
19098 buf.extend_from_slice(&crc.to_le_bytes());
19099
19100 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19101 assert!(e2.publications().is_empty());
19102 }
19103}