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 restore(catalog: Catalog) -> Self {
913 Self {
914 catalog,
915 tx_catalogs: BTreeMap::new(),
916 current_tx: None,
917 next_tx_id: 1,
918 clock: None,
919 salt_fn: None,
920 max_query_rows: None,
921 users: UserStore::new(),
922 publications: publications::Publications::new(),
923 subscriptions: subscriptions::Subscriptions::new(),
924 statistics: statistics::Statistics::new(),
925 plan_cache: plan_cache::PlanCache::new(),
926 query_stats: query_stats::QueryStats::new(),
927 activity_provider: None,
928 audit_chain_provider: None,
929 audit_verifier: None,
930 slow_query_threshold_us: None,
931 slow_query_logger: None,
932 session_params: BTreeMap::new(),
933 trigger_recursion_depth: 0,
934 foreign_key_checks: true,
935 meta_views_materialised: false,
936 pending_foreign_keys: Vec::new(),
937 }
938 }
939
940 pub fn restore_envelope(buf: &[u8]) -> Result<Self, EngineError> {
947 match split_envelope(buf) {
948 EnvelopeParse::Pair {
949 catalog: catalog_bytes,
950 users: user_bytes,
951 publications: pub_bytes,
952 subscriptions: sub_bytes,
953 statistics: stats_bytes,
954 } => {
955 let catalog = Catalog::deserialize(catalog_bytes).map_err(EngineError::Storage)?;
956 let users = users::deserialize_users(user_bytes)
957 .map_err(|e| EngineError::Unsupported(alloc::format!("users restore: {e}")))?;
958 let publications = match pub_bytes {
959 Some(b) => publications::Publications::deserialize(b).map_err(|e| {
960 EngineError::Unsupported(alloc::format!("publications restore: {e:?}"))
961 })?,
962 None => publications::Publications::new(),
963 };
964 let subscriptions = match sub_bytes {
965 Some(b) => subscriptions::Subscriptions::deserialize(b).map_err(|e| {
966 EngineError::Unsupported(alloc::format!("subscriptions restore: {e:?}"))
967 })?,
968 None => subscriptions::Subscriptions::new(),
969 };
970 let statistics = match stats_bytes {
971 Some(b) => statistics::Statistics::deserialize(b).map_err(|e| {
972 EngineError::Unsupported(alloc::format!("statistics restore: {e:?}"))
973 })?,
974 None => statistics::Statistics::new(),
975 };
976 Ok(Self {
977 catalog,
978 tx_catalogs: BTreeMap::new(),
979 current_tx: None,
980 next_tx_id: 1,
981 clock: None,
982 salt_fn: None,
983 max_query_rows: None,
984 users,
985 publications,
986 subscriptions,
987 statistics,
988 plan_cache: plan_cache::PlanCache::new(),
989 query_stats: query_stats::QueryStats::new(),
990 activity_provider: None,
991 audit_chain_provider: None,
992 audit_verifier: None,
993 slow_query_threshold_us: None,
994 slow_query_logger: None,
995 session_params: BTreeMap::new(),
996 trigger_recursion_depth: 0,
997 foreign_key_checks: true,
998 meta_views_materialised: false,
999 pending_foreign_keys: Vec::new(),
1000 })
1001 }
1002 EnvelopeParse::CrcMismatch { expected, computed } => {
1003 Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1004 "snapshot envelope CRC32 mismatch (expected={expected:#010x}, computed={computed:#010x})"
1005 ))))
1006 }
1007 EnvelopeParse::Bare => {
1008 let catalog = Catalog::deserialize(buf).map_err(EngineError::Storage)?;
1009 Ok(Self::restore(catalog))
1010 }
1011 }
1012 }
1013
1014 pub const fn users(&self) -> &UserStore {
1015 &self.users
1016 }
1017
1018 pub fn create_user(
1022 &mut self,
1023 name: &str,
1024 password: &str,
1025 role: Role,
1026 salt: [u8; 16],
1027 ) -> Result<(), UserError> {
1028 self.users.create(name, password, role, salt)?;
1029 let scram_salt = self.salt_fn.map_or_else(
1035 || {
1036 let mut s = [0u8; users::SCRAM_SALT_LEN];
1037 let digest = spg_crypto::hash(name.as_bytes());
1038 s.copy_from_slice(&digest[16..32]);
1041 s
1042 },
1043 |f| f(),
1044 );
1045 self.users
1046 .enable_scram(name, password, scram_salt, users::SCRAM_DEFAULT_ITERS)?;
1047 Ok(())
1048 }
1049
1050 pub fn drop_user(&mut self, name: &str) -> Result<(), UserError> {
1051 self.users.drop(name)
1052 }
1053
1054 pub fn verify_user(&self, name: &str, password: &str) -> Option<Role> {
1055 self.users.verify(name, password)
1056 }
1057
1058 #[must_use]
1061 pub const fn with_clock(mut self, clock: ClockFn) -> Self {
1062 self.clock = Some(clock);
1063 self
1064 }
1065
1066 #[must_use]
1069 pub const fn with_salt_fn(mut self, f: SaltFn) -> Self {
1070 self.salt_fn = Some(f);
1071 self
1072 }
1073
1074 #[must_use]
1080 pub const fn with_max_query_rows(mut self, n: usize) -> Self {
1081 self.max_query_rows = Some(n);
1082 self
1083 }
1084
1085 pub const fn catalog(&self) -> &Catalog {
1089 &self.catalog
1090 }
1091
1092 pub fn snapshot(&self) -> Vec<u8> {
1100 if self.users.is_empty()
1101 && self.publications.is_empty()
1102 && self.subscriptions.is_empty()
1103 && self.statistics.is_empty()
1104 {
1105 self.catalog.serialize()
1106 } else {
1107 build_envelope(
1108 &self.catalog.serialize(),
1109 &users::serialize_users(&self.users),
1110 &self.publications.serialize(),
1111 &self.subscriptions.serialize(),
1112 &self.statistics.serialize(),
1113 )
1114 }
1115 }
1116
1117 pub fn in_transaction(&self) -> bool {
1122 !self.tx_catalogs.is_empty()
1123 }
1124
1125 pub fn alloc_tx_id(&mut self) -> TxId {
1134 let id = TxId(self.next_tx_id);
1135 self.next_tx_id = self.next_tx_id.saturating_add(1);
1136 id
1137 }
1138
1139 pub fn replace_catalog(&mut self, catalog: Catalog) {
1159 self.catalog = catalog;
1160 }
1161
1162 pub fn freeze_oldest_to_cold(
1170 &mut self,
1171 table_name: &str,
1172 index_name: &str,
1173 max_rows: usize,
1174 ) -> Result<spg_storage::FreezeReport, EngineError> {
1175 let report = self
1176 .active_catalog_mut()
1177 .freeze_oldest_to_cold(table_name, index_name, max_rows)
1178 .map_err(EngineError::Storage)?;
1179 if let Some(t) = self.active_catalog_mut().get_mut(table_name) {
1180 t.mark_cold_row_count_stale();
1181 }
1182 Ok(report)
1183 }
1184
1185 pub fn receive_cold_segment(
1199 &mut self,
1200 segment_id: u32,
1201 bytes: Vec<u8>,
1202 ) -> Result<(), EngineError> {
1203 let mut new_cat = self.catalog.clone();
1204 match new_cat.load_segment_bytes_at(segment_id, bytes) {
1205 Ok(()) => {
1206 self.replace_catalog(new_cat);
1207 Ok(())
1208 }
1209 Err(StorageError::Corrupt(msg)) if msg.contains("already occupied") => Ok(()),
1210 Err(e) => Err(EngineError::Storage(e)),
1211 }
1212 }
1213
1214 pub fn compact_cold_segments_with_target(
1228 &mut self,
1229 target_segment_bytes: u64,
1230 ) -> Result<Vec<(String, String, CompactReport)>, EngineError> {
1231 let table_names = self.active_catalog().table_names();
1232 let mut reports: Vec<(String, String, CompactReport)> = Vec::new();
1233 for tname in table_names {
1234 if is_internal_table_name(&tname) {
1235 continue;
1236 }
1237 let idx_names: Vec<String> = {
1238 let Some(t) = self.active_catalog().get(&tname) else {
1239 continue;
1240 };
1241 t.indices()
1242 .iter()
1243 .filter(|i| matches!(i.kind, IndexKind::BTree(_)))
1244 .map(|i| i.name.clone())
1245 .collect()
1246 };
1247 for iname in idx_names {
1248 let report = self
1249 .active_catalog_mut()
1250 .compact_cold_segments(&tname, &iname, target_segment_bytes)
1251 .map_err(EngineError::Storage)?;
1252 if report.merged_segment_id.is_some() {
1253 if let Some(t) = self.active_catalog_mut().get_mut(&tname) {
1254 t.mark_cold_row_count_stale();
1255 }
1256 reports.push((tname.clone(), iname, report));
1257 }
1258 }
1259 }
1260 Ok(reports)
1261 }
1262
1263 fn active_catalog(&self) -> &Catalog {
1264 match self.current_tx {
1265 Some(t) => self
1266 .tx_catalogs
1267 .get(&t)
1268 .map_or(&self.catalog, |s| &s.catalog),
1269 None => &self.catalog,
1270 }
1271 }
1272
1273 fn resolve_plpgsql_block_subqueries(
1295 &self,
1296 block: &mut spg_sql::ast::PlPgSqlBlock,
1297 cancel: CancelToken<'_>,
1298 ) -> Result<(), EngineError> {
1299 for d in &mut block.declarations {
1300 if let Some(e) = &mut d.default {
1301 self.resolve_expr_subqueries(e, cancel)?;
1302 }
1303 }
1304 self.resolve_plpgsql_stmts_subqueries(&mut block.statements, cancel)
1305 }
1306
1307 fn resolve_plpgsql_stmts_subqueries(
1308 &self,
1309 stmts: &mut [spg_sql::ast::PlPgSqlStmt],
1310 cancel: CancelToken<'_>,
1311 ) -> Result<(), EngineError> {
1312 use spg_sql::ast::PlPgSqlStmt;
1313 for stmt in stmts {
1314 match stmt {
1315 PlPgSqlStmt::Assign { value, .. } => {
1316 self.resolve_expr_subqueries(value, cancel)?;
1317 }
1318 PlPgSqlStmt::Return(spg_sql::ast::ReturnTarget::Expr(e)) => {
1319 self.resolve_expr_subqueries(e, cancel)?;
1320 }
1321 PlPgSqlStmt::Return(_) => {}
1322 PlPgSqlStmt::If {
1323 branches,
1324 else_branch,
1325 } => {
1326 for (cond, body) in branches.iter_mut() {
1327 self.resolve_expr_subqueries(cond, cancel)?;
1328 self.resolve_plpgsql_stmts_subqueries(body, cancel)?;
1329 }
1330 self.resolve_plpgsql_stmts_subqueries(else_branch, cancel)?;
1331 }
1332 PlPgSqlStmt::Raise { args, .. } => {
1333 for a in args {
1334 self.resolve_expr_subqueries(a, cancel)?;
1335 }
1336 }
1337 PlPgSqlStmt::EmbeddedSql(_) => {
1338 }
1342 PlPgSqlStmt::SelectInto { body, .. } => {
1343 self.resolve_select_subqueries(body, cancel)?;
1349 }
1350 }
1351 }
1352 Ok(())
1353 }
1354
1355 fn exec_do_block(
1356 &mut self,
1357 body: spg_sql::ast::PlPgSqlBlock,
1358 ) -> Result<QueryResult, EngineError> {
1359 let mut body = body;
1368 self.resolve_plpgsql_block_subqueries(&mut body, CancelToken::none())?;
1369 let dts = self
1370 .session_param("default_text_search_config")
1371 .map(String::from);
1372 let engine_cell = core::cell::RefCell::new(&mut *self);
1385 let resolver_fn =
1386 |stmt: &spg_sql::ast::Statement| -> Result<Value, triggers::TriggerError> {
1387 let mut eng = engine_cell.borrow_mut();
1388 let r = eng
1389 .execute_stmt_with_cancel(stmt.clone(), CancelToken::none())
1390 .map_err(|e| triggers::TriggerError::EvalFailed {
1391 function: "DO".into(),
1392 cause: eval::EvalError::TypeMismatch {
1393 detail: alloc::format!("SELECT … INTO failed: {e}"),
1394 },
1395 })?;
1396 match r {
1397 QueryResult::Rows { rows, .. } => match rows.into_iter().next() {
1398 Some(row) => Ok(row.values.into_iter().next().unwrap_or(Value::Null)),
1399 None => Ok(Value::Null),
1400 },
1401 _ => Err(triggers::TriggerError::EvalFailed {
1402 function: "DO".into(),
1403 cause: eval::EvalError::TypeMismatch {
1404 detail: "SELECT … INTO body must be a SELECT".into(),
1405 },
1406 }),
1407 }
1408 };
1409 let collected =
1410 triggers::execute_do_block_top_level(&body, dts.as_deref(), Some(&resolver_fn))
1411 .map_err(|e| {
1412 EngineError::Storage(StorageError::Corrupt(alloc::format!("DO: {e}")))
1413 })?;
1414 for stmt in collected {
1420 self.execute_stmt_with_cancel(stmt, CancelToken::none())?;
1424 }
1425 Ok(QueryResult::CommandOk {
1426 affected: 0,
1427 modified_catalog: !self.in_transaction(),
1428 })
1429 }
1430
1431 fn snapshot_row_triggers(
1432 &self,
1433 table: &str,
1434 event: &str,
1435 timing: &str,
1436 ) -> Vec<spg_storage::FunctionDef> {
1437 let cat = self.active_catalog();
1438 cat.triggers()
1439 .iter()
1440 .filter(|t| {
1441 t.enabled
1444 && t.table == table
1445 && t.timing.eq_ignore_ascii_case(timing)
1446 && t.for_each.eq_ignore_ascii_case("row")
1447 && t.events.iter().any(|e| e.eq_ignore_ascii_case(event))
1448 })
1449 .filter_map(|t| cat.functions().get(&t.function).cloned())
1450 .collect()
1451 }
1452
1453 fn snapshot_update_row_triggers(
1458 &self,
1459 table: &str,
1460 timing: &str,
1461 ) -> Vec<(spg_storage::FunctionDef, Vec<String>)> {
1462 let cat = self.active_catalog();
1463 cat.triggers()
1464 .iter()
1465 .filter(|t| {
1466 t.enabled
1468 && t.table == table
1469 && t.timing.eq_ignore_ascii_case(timing)
1470 && t.for_each.eq_ignore_ascii_case("row")
1471 && t.events.iter().any(|e| e.eq_ignore_ascii_case("UPDATE"))
1472 })
1473 .filter_map(|t| {
1474 cat.functions()
1475 .get(&t.function)
1476 .cloned()
1477 .map(|fd| (fd, t.update_columns.clone()))
1478 })
1479 .collect()
1480 }
1481
1482 fn execute_deferred_trigger_stmts(
1491 &mut self,
1492 deferred: Vec<triggers::DeferredEmbeddedStmt>,
1493 cancel: CancelToken<'_>,
1494 ) -> Result<(), EngineError> {
1495 for d in deferred {
1496 if self.trigger_recursion_depth >= MAX_TRIGGER_RECURSION {
1497 return Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1498 "trigger embedded SQL recursion depth {} exceeded (trigger function \
1499 {:?} would push past the {} cap — check for trigger cycles)",
1500 self.trigger_recursion_depth,
1501 d.function,
1502 MAX_TRIGGER_RECURSION,
1503 ))));
1504 }
1505 self.trigger_recursion_depth += 1;
1506 let res = self.execute_stmt_with_cancel(d.stmt, cancel);
1507 self.trigger_recursion_depth -= 1;
1508 res?;
1509 }
1510 Ok(())
1511 }
1512
1513 fn active_catalog_mut(&mut self) -> &mut Catalog {
1514 let tx = self.current_tx;
1515 match tx {
1516 Some(t) => match self.tx_catalogs.get_mut(&t) {
1517 Some(s) => &mut s.catalog,
1518 None => &mut self.catalog,
1519 },
1520 None => &mut self.catalog,
1521 }
1522 }
1523
1524 pub fn execute_readonly(&self, sql: &str) -> Result<QueryResult, EngineError> {
1536 self.execute_readonly_with_cancel(sql, CancelToken::none())
1537 }
1538
1539 pub fn execute_readonly_with_cancel(
1545 &self,
1546 sql: &str,
1547 cancel: CancelToken<'_>,
1548 ) -> Result<QueryResult, EngineError> {
1549 cancel.check()?;
1550 let mut stmt = parser::parse_statement(sql)?;
1551 let now_micros = self.clock.map(|f| f());
1552 rewrite_clock_calls(&mut stmt, now_micros);
1553 if let Statement::Select(s) = &mut stmt {
1554 resolve_order_by_position(s);
1555 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1557 }
1558 let result = match stmt {
1559 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1560 Statement::ShowTables => Ok(self.exec_show_tables()),
1561 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1562 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1563 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1564 Statement::ShowStatus => Ok(self.exec_show_status()),
1565 Statement::ShowVariables => Ok(self.exec_show_variables()),
1566 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1567 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1568 Statement::ShowUsers => Ok(self.exec_show_users()),
1569 Statement::ShowPublications => Ok(self.exec_show_publications()),
1570 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1571 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1572 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1573 )),
1574 Statement::Explain(e) => self.exec_explain(&e, cancel),
1575 _ => Err(EngineError::WriteRequired),
1576 };
1577 self.enforce_row_limit(result)
1578 }
1579
1580 fn enforce_row_limit(
1584 &self,
1585 result: Result<QueryResult, EngineError>,
1586 ) -> Result<QueryResult, EngineError> {
1587 if let (Ok(QueryResult::Rows { rows, .. }), Some(cap)) = (&result, self.max_query_rows)
1588 && rows.len() > cap
1589 {
1590 return Err(EngineError::RowLimitExceeded(cap));
1591 }
1592 result
1593 }
1594
1595 pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError> {
1596 self.execute_in_with_cancel(sql, IMPLICIT_TX, CancelToken::none())
1597 }
1598
1599 pub fn execute_with_cancel(
1604 &mut self,
1605 sql: &str,
1606 cancel: CancelToken<'_>,
1607 ) -> Result<QueryResult, EngineError> {
1608 self.execute_in_with_cancel(sql, IMPLICIT_TX, cancel)
1609 }
1610
1611 pub fn execute_in(&mut self, sql: &str, tx_id: TxId) -> Result<QueryResult, EngineError> {
1618 self.execute_in_with_cancel(sql, tx_id, CancelToken::none())
1619 }
1620
1621 pub fn execute_in_with_cancel(
1627 &mut self,
1628 sql: &str,
1629 tx_id: TxId,
1630 cancel: CancelToken<'_>,
1631 ) -> Result<QueryResult, EngineError> {
1632 let saved = self.current_tx;
1633 self.current_tx = Some(tx_id);
1634 let result = self.execute_inner_with_cancel(sql, cancel);
1635 self.current_tx = saved;
1636 result
1637 }
1638
1639 pub fn prepare(&self, sql: &str) -> Result<Statement, ParseError> {
1651 let mut stmt = parser::parse_statement(sql)?;
1652 let now_micros = self.clock.map(|f| f());
1653 rewrite_clock_calls(&mut stmt, now_micros);
1654 if let Statement::Select(s) = &mut stmt {
1655 expand_group_by_all(s);
1659 resolve_order_by_position(s);
1660 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1663 }
1664 Ok(stmt)
1665 }
1666
1667 pub fn prepare_cached(&mut self, sql: &str) -> Result<Statement, ParseError> {
1679 let current_version = self.statistics.version();
1682 if let Some(plan) = self.plan_cache.get(sql) {
1683 if plan.statistics_version == current_version {
1684 return Ok(plan.stmt.clone());
1685 }
1686 }
1688 self.plan_cache.evict(sql);
1689 let stmt = self.prepare(sql)?;
1690 let source_tables = plan_cache::collect_source_tables(&stmt);
1691 let plan = plan_cache::PreparedPlan {
1692 stmt: stmt.clone(),
1693 statistics_version: current_version,
1694 source_tables,
1695 describe_columns: alloc::vec::Vec::new(),
1696 };
1697 self.plan_cache.insert(String::from(sql), plan);
1698 Ok(stmt)
1699 }
1700
1701 pub fn plan_cache(&self) -> &plan_cache::PlanCache {
1703 &self.plan_cache
1704 }
1705
1706 pub fn plan_cache_mut(&mut self) -> &mut plan_cache::PlanCache {
1708 &mut self.plan_cache
1709 }
1710
1711 pub fn describe_prepared(&self, stmt: &Statement) -> (Vec<u32>, Vec<ColumnSchema>) {
1717 describe::describe_prepared(stmt, self.active_catalog())
1718 }
1719
1720 pub fn execute_prepared(
1730 &mut self,
1731 stmt: Statement,
1732 params: &[Value],
1733 ) -> Result<QueryResult, EngineError> {
1734 self.execute_prepared_with_cancel(stmt, params, CancelToken::none())
1735 }
1736
1737 pub fn execute_prepared_with_cancel(
1742 &mut self,
1743 mut stmt: Statement,
1744 params: &[Value],
1745 cancel: CancelToken<'_>,
1746 ) -> Result<QueryResult, EngineError> {
1747 substitute_placeholders(&mut stmt, params)?;
1748 let saved = self.current_tx;
1759 self.current_tx = Some(IMPLICIT_TX);
1760 let result = self.execute_stmt_with_cancel(stmt, cancel);
1761 self.current_tx = saved;
1762 result
1763 }
1764
1765 fn execute_inner_with_cancel(
1766 &mut self,
1767 sql: &str,
1768 cancel: CancelToken<'_>,
1769 ) -> Result<QueryResult, EngineError> {
1770 cancel.check()?;
1771 let stmt = self.prepare(sql)?;
1772 let start_us = self.clock.map(|f| f());
1776 let result = self.execute_stmt_with_cancel(stmt, cancel);
1777 if let (Some(t0), Ok(_)) = (start_us, &result) {
1778 let now = self.clock.map_or(t0, |f| f());
1779 let elapsed = now.saturating_sub(t0).max(0) as u64;
1780 self.query_stats.record(sql, elapsed, now as u64);
1781 if let (Some(threshold), Some(logger)) =
1784 (self.slow_query_threshold_us, self.slow_query_logger)
1785 && elapsed >= threshold
1786 {
1787 logger(sql, elapsed);
1788 }
1789 }
1790 result
1791 }
1792
1793 fn execute_stmt_with_cancel(
1794 &mut self,
1795 stmt: Statement,
1796 cancel: CancelToken<'_>,
1797 ) -> Result<QueryResult, EngineError> {
1798 cancel.check()?;
1799 let mut stmt = stmt;
1813 self.pre_resolve_sequence_calls_in_statement(&mut stmt)?;
1822 let result = match stmt {
1823 Statement::CreateTable(s) => self.exec_create_table(s),
1824 Statement::CreateExtension(_) => Ok(QueryResult::CommandOk {
1828 affected: 0,
1829 modified_catalog: false,
1830 }),
1831 Statement::DoBlock(body) => self.exec_do_block(body),
1841 Statement::Empty => Ok(QueryResult::CommandOk {
1845 affected: 0,
1846 modified_catalog: false,
1847 }),
1848 Statement::DropTable { names, if_exists } => self.exec_drop_table(names, if_exists),
1849 Statement::DropIndex { name, if_exists } => self.exec_drop_index(name, if_exists),
1850 Statement::CreateIndex(s) => self.exec_create_index(s),
1851 Statement::Insert(s) => self.exec_insert(s),
1852 Statement::Update(s) => self.exec_update_cancel(&s, cancel),
1853 Statement::Delete(s) => self.exec_delete_cancel(&s, cancel),
1854 Statement::Merge(s) => self.exec_merge_cancel(&s, cancel),
1855 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1856 Statement::Begin => self.exec_begin(),
1857 Statement::Commit => self.exec_commit(),
1858 Statement::Rollback => self.exec_rollback(),
1859 Statement::Savepoint(name) => self.exec_savepoint(name),
1860 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
1861 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
1862 Statement::ShowTables => Ok(self.exec_show_tables()),
1863 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1864 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1865 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1866 Statement::ShowStatus => Ok(self.exec_show_status()),
1867 Statement::ShowVariables => Ok(self.exec_show_variables()),
1868 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1869 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1870 Statement::ShowUsers => Ok(self.exec_show_users()),
1871 Statement::ShowPublications => Ok(self.exec_show_publications()),
1872 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1873 Statement::CreateUser(s) => self.exec_create_user(&s),
1874 Statement::DropUser(name) => self.exec_drop_user(&name),
1875 Statement::Explain(e) => self.exec_explain(&e, cancel),
1876 Statement::AlterIndex(s) => self.exec_alter_index(s),
1877 Statement::AlterTable(s) => self.exec_alter_table(s),
1878 Statement::CreatePublication(s) => self.exec_create_publication(s),
1879 Statement::DropPublication(name) => self.exec_drop_publication(&name),
1880 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
1881 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
1882 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1889 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1890 )),
1891 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
1893 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
1895 Statement::SetParameter { name, value } => {
1900 self.set_session_param(name, value);
1901 Ok(QueryResult::CommandOk {
1902 affected: 0,
1903 modified_catalog: false,
1904 })
1905 }
1906 Statement::SetParameterList(pairs) => {
1912 for (name, value) in pairs {
1913 self.set_session_param(name, value);
1914 }
1915 Ok(QueryResult::CommandOk {
1916 affected: 0,
1917 modified_catalog: false,
1918 })
1919 }
1920 Statement::CreateFunction(s) => self.exec_create_function(s),
1924 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
1925 Statement::DropTrigger {
1926 name,
1927 table,
1928 if_exists,
1929 } => self.exec_drop_trigger(&name, &table, if_exists),
1930 Statement::DropFunction { name, if_exists } => {
1931 self.exec_drop_function(&name, if_exists)
1932 }
1933 Statement::CreateSequence(s) => self.exec_create_sequence(s),
1934 Statement::AlterSequence(s) => self.exec_alter_sequence(s),
1935 Statement::DropSequence { names, if_exists } => {
1936 self.exec_drop_sequence(&names, if_exists)
1937 }
1938 Statement::CreateView(s) => self.exec_create_view(s),
1939 Statement::DropView { names, if_exists } => self.exec_drop_view(&names, if_exists),
1940 Statement::CreateMaterializedView(s) => self.exec_create_materialized_view(s),
1941 Statement::RefreshMaterializedView { name, with_data } => {
1942 self.exec_refresh_materialized_view(&name, with_data)
1943 }
1944 Statement::DropMaterializedView { names, if_exists } => {
1945 self.exec_drop_materialized_view(&names, if_exists)
1946 }
1947 Statement::CreateType(s) => self.exec_create_type(s),
1948 Statement::DropType { names, if_exists } => self.exec_drop_type(&names, if_exists),
1949 Statement::CreateDomain(s) => self.exec_create_domain(s),
1950 Statement::DropDomain { names, if_exists } => self.exec_drop_domain(&names, if_exists),
1951 Statement::CreateSchema {
1952 name,
1953 if_not_exists,
1954 } => self.exec_create_schema(name, if_not_exists),
1955 Statement::DropSchema { names, if_exists } => self.exec_drop_schema(&names, if_exists),
1956 Statement::ResetParameter(target) => {
1957 match target {
1958 None => self.session_params.clear(),
1959 Some(name) => {
1960 self.session_params.remove(&name.to_ascii_lowercase());
1961 }
1962 }
1963 Ok(QueryResult::CommandOk {
1964 affected: 0,
1965 modified_catalog: false,
1966 })
1967 }
1968 };
1969 self.enforce_row_limit(result)
1970 }
1971
1972 fn exec_create_publication(
1980 &mut self,
1981 s: CreatePublicationStatement,
1982 ) -> Result<QueryResult, EngineError> {
1983 self.publications
1989 .create(s.name, s.scope)
1990 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
1991 Ok(QueryResult::CommandOk {
1992 affected: 1,
1993 modified_catalog: true,
1994 })
1995 }
1996
1997 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2002 let removed = self.publications.drop(name);
2003 Ok(QueryResult::CommandOk {
2004 affected: usize::from(removed),
2005 modified_catalog: removed,
2006 })
2007 }
2008
2009 pub const fn publications(&self) -> &publications::Publications {
2014 &self.publications
2015 }
2016
2017 fn exec_create_subscription(
2022 &mut self,
2023 s: CreateSubscriptionStatement,
2024 ) -> Result<QueryResult, EngineError> {
2025 let sub = subscriptions::Subscription {
2029 conn_str: s.conn_str,
2030 publications: s.publications,
2031 enabled: true,
2032 last_received_pos: 0,
2033 };
2034 self.subscriptions
2035 .create(s.name, sub)
2036 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
2037 Ok(QueryResult::CommandOk {
2038 affected: 1,
2039 modified_catalog: true,
2040 })
2041 }
2042
2043 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2051 let removed = self.subscriptions.drop(name);
2052 Ok(QueryResult::CommandOk {
2053 affected: usize::from(removed),
2054 modified_catalog: removed,
2055 })
2056 }
2057
2058 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
2063 &self.subscriptions
2064 }
2065
2066 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
2072 self.subscriptions.update_last_received_pos(name, pos)
2073 }
2074
2075 fn exec_show_subscriptions(&self) -> QueryResult {
2081 let columns = alloc::vec![
2082 ColumnSchema::new("name", DataType::Text, false),
2083 ColumnSchema::new("conn_str", DataType::Text, false),
2084 ColumnSchema::new("publications", DataType::Text, false),
2085 ColumnSchema::new("enabled", DataType::Bool, false),
2086 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2087 ];
2088 let rows: Vec<Row> = self
2089 .subscriptions
2090 .iter()
2091 .map(|(name, sub)| {
2092 Row::new(alloc::vec![
2093 Value::Text(name.clone()),
2094 Value::Text(sub.conn_str.clone()),
2095 Value::Text(sub.publications.join(", ")),
2096 Value::Bool(sub.enabled),
2097 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2098 ])
2099 })
2100 .collect();
2101 QueryResult::Rows { columns, rows }
2102 }
2103
2104 fn exec_spg_statistic(&self) -> QueryResult {
2109 let columns = alloc::vec![
2110 ColumnSchema::new("table_name", DataType::Text, false),
2111 ColumnSchema::new("column_name", DataType::Text, false),
2112 ColumnSchema::new("null_frac", DataType::Float, false),
2113 ColumnSchema::new("n_distinct", DataType::BigInt, false),
2114 ColumnSchema::new("histogram_bounds", DataType::Text, false),
2115 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
2120 ];
2121 let rows: Vec<Row> = self
2122 .statistics
2123 .iter()
2124 .map(|((t, c), s)| {
2125 let cold = self
2126 .catalog
2127 .get(t)
2128 .map_or(0, |table| table.cold_row_count());
2129 Row::new(alloc::vec![
2130 Value::Text(t.clone()),
2131 Value::Text(c.clone()),
2132 Value::Float(f64::from(s.null_frac)),
2133 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
2134 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
2135 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
2136 ])
2137 })
2138 .collect();
2139 QueryResult::Rows { columns, rows }
2140 }
2141
2142 fn exec_spg_stat_replication(&self) -> QueryResult {
2149 let columns = alloc::vec![
2150 ColumnSchema::new("name", DataType::Text, false),
2151 ColumnSchema::new("conn_str", DataType::Text, false),
2152 ColumnSchema::new("publications", DataType::Text, false),
2153 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2154 ColumnSchema::new("enabled", DataType::Bool, false),
2155 ];
2156 let rows: Vec<Row> = self
2157 .subscriptions
2158 .iter()
2159 .map(|(name, sub)| {
2160 Row::new(alloc::vec![
2161 Value::Text(name.clone()),
2162 Value::Text(sub.conn_str.clone()),
2163 Value::Text(sub.publications.join(",")),
2164 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2165 Value::Bool(sub.enabled),
2166 ])
2167 })
2168 .collect();
2169 QueryResult::Rows { columns, rows }
2170 }
2171
2172 fn exec_spg_stat_segment(&self) -> QueryResult {
2184 let columns = alloc::vec![
2185 ColumnSchema::new("segment_id", DataType::BigInt, false),
2186 ColumnSchema::new("table_name", DataType::Text, false),
2187 ColumnSchema::new("num_rows", DataType::BigInt, false),
2188 ColumnSchema::new("num_pages", DataType::BigInt, false),
2189 ColumnSchema::new("total_bytes", DataType::BigInt, false),
2190 ];
2191 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
2197 for tname in self.catalog.table_names() {
2198 if is_internal_table_name(&tname) {
2199 continue;
2200 }
2201 let Some(t) = self.catalog.get(&tname) else {
2202 continue;
2203 };
2204 for idx in t.indices() {
2205 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
2206 for (_, locs) in map.iter() {
2207 for loc in locs {
2208 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
2209 segment_owners
2210 .entry(*segment_id)
2211 .or_insert_with(|| tname.clone());
2212 }
2213 }
2214 }
2215 }
2216 }
2217 }
2218 let rows: Vec<Row> = self
2219 .catalog
2220 .cold_segment_ids_global()
2221 .iter()
2222 .filter_map(|&id| {
2223 let seg = self.catalog.cold_segment(id)?;
2224 let meta = seg.meta();
2225 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
2226 Some(Row::new(alloc::vec![
2227 Value::BigInt(i64::from(id)),
2228 Value::Text(owner),
2229 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
2230 Value::BigInt(i64::from(meta.num_pages)),
2231 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
2232 ]))
2233 })
2234 .collect();
2235 QueryResult::Rows { columns, rows }
2236 }
2237
2238 fn exec_spg_stat_query(&self) -> QueryResult {
2244 let columns = alloc::vec![
2245 ColumnSchema::new("sql", DataType::Text, false),
2246 ColumnSchema::new("exec_count", DataType::BigInt, false),
2247 ColumnSchema::new("total_us", DataType::BigInt, false),
2248 ColumnSchema::new("mean_us", DataType::BigInt, false),
2249 ColumnSchema::new("max_us", DataType::BigInt, false),
2250 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
2251 ];
2252 let rows: Vec<Row> = self
2253 .query_stats
2254 .snapshot()
2255 .into_iter()
2256 .map(|(sql, s)| {
2257 let mean = if s.exec_count == 0 {
2258 0
2259 } else {
2260 s.total_us / s.exec_count
2261 };
2262 Row::new(alloc::vec![
2263 Value::Text(sql),
2264 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
2265 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
2266 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
2267 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
2268 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
2269 ])
2270 })
2271 .collect();
2272 QueryResult::Rows { columns, rows }
2273 }
2274
2275 #[must_use]
2280 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
2281 self.activity_provider = Some(f);
2282 self
2283 }
2284
2285 #[must_use]
2287 pub const fn with_audit_providers(
2288 mut self,
2289 chain: AuditChainProvider,
2290 verify: AuditVerifier,
2291 ) -> Self {
2292 self.audit_chain_provider = Some(chain);
2293 self.audit_verifier = Some(verify);
2294 self
2295 }
2296
2297 #[must_use]
2302 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
2303 self.slow_query_threshold_us = Some(threshold_us);
2304 self.slow_query_logger = Some(logger);
2305 self
2306 }
2307
2308 pub fn set_plan_cache_max(&mut self, n: usize) {
2312 self.plan_cache.set_max_entries(n);
2313 }
2314
2315 fn exec_spg_stat_activity(&self) -> QueryResult {
2320 let columns = alloc::vec![
2321 ColumnSchema::new("pid", DataType::Int, false),
2322 ColumnSchema::new("user", DataType::Text, false),
2323 ColumnSchema::new("started_at_us", DataType::BigInt, false),
2324 ColumnSchema::new("current_sql", DataType::Text, false),
2325 ColumnSchema::new("wait_event", DataType::Text, false),
2326 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
2327 ColumnSchema::new("in_transaction", DataType::Bool, false),
2328 ColumnSchema::new("application_name", DataType::Text, false),
2329 ];
2330 let rows: Vec<Row> = self
2331 .activity_provider
2332 .map(|f| f())
2333 .unwrap_or_default()
2334 .into_iter()
2335 .map(|r| {
2336 Row::new(alloc::vec![
2337 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
2338 Value::Text(r.user),
2339 Value::BigInt(r.started_at_us),
2340 Value::Text(r.current_sql),
2341 Value::Text(r.wait_event),
2342 Value::BigInt(r.elapsed_us),
2343 Value::Bool(r.in_transaction),
2344 Value::Text(r.application_name),
2345 ])
2346 })
2347 .collect();
2348 QueryResult::Rows { columns, rows }
2349 }
2350
2351 fn exec_spg_table_ddl(&self) -> QueryResult {
2355 let columns = alloc::vec![
2356 ColumnSchema::new("table_name", DataType::Text, false),
2357 ColumnSchema::new("ddl", DataType::Text, false),
2358 ];
2359 let rows: Vec<Row> = self
2360 .catalog
2361 .table_names()
2362 .into_iter()
2363 .filter(|n| !is_internal_table_name(n))
2364 .filter_map(|name| {
2365 let table = self.catalog.get(&name)?;
2366 let ddl = render_create_table(&name, &table.schema().columns);
2367 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
2368 })
2369 .collect();
2370 QueryResult::Rows { columns, rows }
2371 }
2372
2373 fn exec_spg_role_ddl(&self) -> QueryResult {
2377 let columns = alloc::vec![
2378 ColumnSchema::new("role_name", DataType::Text, false),
2379 ColumnSchema::new("ddl", DataType::Text, false),
2380 ];
2381 let rows: Vec<Row> = self
2382 .users
2383 .iter()
2384 .map(|(name, rec)| {
2385 let ddl = alloc::format!(
2386 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2387 rec.role.as_str(),
2388 );
2389 Row::new(alloc::vec![
2390 Value::Text(String::from(name)),
2391 Value::Text(ddl)
2392 ])
2393 })
2394 .collect();
2395 QueryResult::Rows { columns, rows }
2396 }
2397
2398 fn exec_spg_database_ddl(&self) -> QueryResult {
2404 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2405 let mut out = String::new();
2406 for (name, rec) in self.users.iter() {
2407 out.push_str(&alloc::format!(
2408 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2409 rec.role.as_str(),
2410 ));
2411 }
2412 for name in self.catalog.table_names() {
2413 if is_internal_table_name(&name) {
2414 continue;
2415 }
2416 if let Some(table) = self.catalog.get(&name) {
2417 out.push_str(&render_create_table(&name, &table.schema().columns));
2418 out.push_str(";\n");
2419 }
2420 }
2421 QueryResult::Rows {
2422 columns,
2423 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2424 }
2425 }
2426
2427 fn exec_spg_audit_chain(&self) -> QueryResult {
2431 let columns = alloc::vec![
2432 ColumnSchema::new("seq", DataType::BigInt, false),
2433 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2434 ColumnSchema::new("prev_hash", DataType::Text, false),
2435 ColumnSchema::new("entry_hash", DataType::Text, false),
2436 ColumnSchema::new("sql", DataType::Text, false),
2437 ];
2438 let rows: Vec<Row> = self
2439 .audit_chain_provider
2440 .map(|f| f())
2441 .unwrap_or_default()
2442 .into_iter()
2443 .map(|r| {
2444 Row::new(alloc::vec![
2445 Value::BigInt(r.seq),
2446 Value::BigInt(r.ts_ms),
2447 Value::Text(r.prev_hash_hex),
2448 Value::Text(r.entry_hash_hex),
2449 Value::Text(r.sql),
2450 ])
2451 })
2452 .collect();
2453 QueryResult::Rows { columns, rows }
2454 }
2455
2456 fn exec_spg_audit_verify(&self) -> QueryResult {
2462 let columns = alloc::vec![
2463 ColumnSchema::new("verified_count", DataType::BigInt, false),
2464 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2465 ];
2466 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2467 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2468 QueryResult::Rows {
2469 columns,
2470 rows: alloc::vec![row],
2471 }
2472 }
2473
2474 pub fn query_stats(&self) -> &query_stats::QueryStats {
2476 &self.query_stats
2477 }
2478
2479 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2481 &mut self.query_stats
2482 }
2483
2484 pub const fn statistics(&self) -> &statistics::Statistics {
2488 &self.statistics
2489 }
2490
2491 pub fn tables_needing_analyze(&self) -> Vec<String> {
2504 const MIN_ROWS: u64 = 100;
2505 let mut out = Vec::new();
2506 for name in self.catalog.table_names() {
2507 if is_internal_table_name(&name) {
2508 continue;
2509 }
2510 let Some(table) = self.catalog.get(&name) else {
2511 continue;
2512 };
2513 let row_count = table.rows().len() as u64;
2514 let modified = self.statistics.modified_since_last_analyze(&name);
2515 let base = row_count.max(MIN_ROWS);
2520 let threshold = base.saturating_add(9) / 10;
2521 if modified >= threshold {
2522 out.push(name);
2523 }
2524 }
2525 out
2526 }
2527
2528 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2539 let names: Vec<String> = if let Some(name) = target {
2540 if self.catalog.get(name).is_none() {
2542 return Err(EngineError::Storage(StorageError::TableNotFound {
2543 name: name.to_string(),
2544 }));
2545 }
2546 alloc::vec![name.to_string()]
2547 } else {
2548 self.catalog
2549 .table_names()
2550 .into_iter()
2551 .filter(|n| !is_internal_table_name(n))
2552 .collect()
2553 };
2554 let mut analysed = 0usize;
2555 for table_name in &names {
2556 self.analyze_one_table(table_name)?;
2557 analysed += 1;
2558 }
2559 if analysed > 0 {
2565 self.statistics.bump_version();
2566 if target.is_some() {
2567 for t in &names {
2568 self.plan_cache.evict_referencing(t);
2569 }
2570 } else {
2571 self.plan_cache.clear();
2572 }
2573 }
2574 Ok(QueryResult::CommandOk {
2575 affected: analysed,
2576 modified_catalog: true,
2577 })
2578 }
2579
2580 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2593 let normalised = match value {
2594 spg_sql::ast::SetValue::String(s) => s,
2595 spg_sql::ast::SetValue::Ident(s) => s,
2596 spg_sql::ast::SetValue::Number(s) => s,
2597 spg_sql::ast::SetValue::Default => String::new(),
2598 };
2599 let key = name.to_ascii_lowercase();
2600 let value_off = matches!(
2611 normalised.to_ascii_lowercase().as_str(),
2612 "0" | "off" | "false"
2613 );
2614 let value_on = matches!(
2615 normalised.to_ascii_lowercase().as_str(),
2616 "1" | "on" | "true"
2617 );
2618 if key == "foreign_key_checks"
2619 || key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
2620 {
2621 if value_off || key == "session_replication_role" {
2622 self.foreign_key_checks = false;
2623 } else if value_on
2624 || (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
2625 {
2626 self.foreign_key_checks = true;
2627 let _ = self.drain_pending_foreign_keys();
2631 }
2632 }
2633 self.session_params.insert(key, normalised);
2634 }
2635
2636 fn drain_pending_foreign_keys(&mut self) -> Result<(), EngineError> {
2643 let pending = core::mem::take(&mut self.pending_foreign_keys);
2644 for (child, fk) in pending {
2645 let cols_snapshot = match self.active_catalog().get(&child) {
2649 Some(t) => t.schema().columns.clone(),
2650 None => continue,
2651 };
2652 let storage_fk =
2653 resolve_foreign_key(&child, &cols_snapshot, fk, self.active_catalog())?;
2654 let table = self
2655 .active_catalog_mut()
2656 .get_mut(&child)
2657 .expect("checked above");
2658 table.schema_mut().foreign_keys.push(storage_fk);
2659 }
2660 Ok(())
2661 }
2662
2663 #[must_use]
2667 pub fn session_param(&self, name: &str) -> Option<&str> {
2668 self.session_params
2669 .get(&name.to_ascii_lowercase())
2670 .map(String::as_str)
2671 }
2672
2673 fn ev_ctx<'a>(
2678 &'a self,
2679 columns: &'a [ColumnSchema],
2680 alias: Option<&'a str>,
2681 ) -> EvalContext<'a> {
2682 EvalContext::new(columns, alias)
2683 .with_default_text_search_config(self.session_param("default_text_search_config"))
2684 }
2685
2686 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2690 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2691 let reports = self.compact_cold_segments_with_target(target)?;
2692 let columns = alloc::vec![
2693 ColumnSchema::new("table_name", DataType::Text, false),
2694 ColumnSchema::new("index_name", DataType::Text, false),
2695 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2696 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2697 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2698 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2699 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2700 ];
2701 let rows: Vec<Row> = reports
2702 .into_iter()
2703 .map(|(tname, iname, report)| {
2704 Row::new(alloc::vec![
2705 Value::Text(tname),
2706 Value::Text(iname),
2707 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2708 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2709 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2710 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2711 Value::BigInt(
2712 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2713 ),
2714 ])
2715 })
2716 .collect();
2717 Ok(QueryResult::Rows { columns, rows })
2718 }
2719
2720 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2725 let table = self.catalog.get(table_name).ok_or_else(|| {
2726 EngineError::Storage(StorageError::TableNotFound {
2727 name: table_name.to_string(),
2728 })
2729 })?;
2730 let schema = table.schema().clone();
2731 let row_count = table.rows().len();
2732 self.statistics.clear_table(table_name);
2737 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2738 if matches!(col_schema.ty, DataType::Vector { .. }) {
2741 continue;
2742 }
2743 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2744 let mut nulls: u64 = 0;
2745 for row in table.rows() {
2746 match row.values.get(col_pos) {
2747 Some(Value::Null) | None => nulls += 1,
2748 Some(v) => non_null_values.push(v.clone()),
2749 }
2750 }
2751 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2756 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2757 let null_frac = if row_count == 0 {
2758 0.0
2759 } else {
2760 #[allow(clippy::cast_precision_loss)]
2761 let f = nulls as f32 / row_count as f32;
2762 f
2763 };
2764 let n_distinct = statistics::estimate_n_distinct(&non_null);
2765 let histogram_bounds = statistics::build_histogram(&non_null);
2766 self.statistics.set(
2767 table_name.to_string(),
2768 col_schema.name.clone(),
2769 statistics::ColumnStats {
2770 null_frac,
2771 n_distinct,
2772 histogram_bounds,
2773 },
2774 );
2775 }
2776 self.statistics.reset_modified(table_name);
2777 let cold_count = {
2783 let table = self
2784 .active_catalog()
2785 .get(table_name)
2786 .expect("table still present");
2787 table.count_cold_locators()
2788 };
2789 let table_mut = self
2790 .active_catalog_mut()
2791 .get_mut(table_name)
2792 .expect("table still present");
2793 table_mut.set_cold_row_count(cold_count);
2794 Ok(())
2795 }
2796
2797 fn exec_show_publications(&self) -> QueryResult {
2809 let columns = alloc::vec![
2810 ColumnSchema::new("name", DataType::Text, false),
2811 ColumnSchema::new("scope", DataType::Text, false),
2812 ColumnSchema::new("table_count", DataType::Int, true),
2813 ];
2814 let rows: Vec<Row> = self
2815 .publications
2816 .iter()
2817 .map(|(name, scope)| {
2818 let (scope_str, count_val) = match scope {
2819 spg_sql::ast::PublicationScope::AllTables => {
2820 ("FOR ALL TABLES".to_string(), Value::Null)
2821 }
2822 spg_sql::ast::PublicationScope::ForTables(ts) => (
2823 alloc::format!("FOR TABLE {}", ts.join(", ")),
2824 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2825 ),
2826 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2827 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
2828 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2829 ),
2830 };
2831 Row::new(alloc::vec![
2832 Value::Text(name.clone()),
2833 Value::Text(scope_str),
2834 count_val,
2835 ])
2836 })
2837 .collect();
2838 QueryResult::Rows { columns, rows }
2839 }
2840
2841 fn exec_show_users(&self) -> QueryResult {
2843 let columns = alloc::vec![
2844 ColumnSchema::new("name", DataType::Text, false),
2845 ColumnSchema::new("role", DataType::Text, false),
2846 ];
2847 let rows: Vec<Row> = self
2848 .users
2849 .iter()
2850 .map(|(name, rec)| {
2851 Row::new(alloc::vec![
2852 Value::Text(name.to_string()),
2853 Value::Text(rec.role.as_str().to_string()),
2854 ])
2855 })
2856 .collect();
2857 QueryResult::Rows { columns, rows }
2858 }
2859
2860 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
2861 if self.in_transaction() {
2862 return Err(EngineError::Unsupported(
2863 "CREATE USER is not allowed inside a transaction".into(),
2864 ));
2865 }
2866 let role = users::Role::parse(&s.role).ok_or_else(|| {
2867 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
2868 })?;
2869 let salt = self.salt_fn.map_or_else(
2873 || {
2874 let mut s_bytes = [0u8; 16];
2875 let digest = spg_crypto::hash(s.name.as_bytes());
2876 s_bytes.copy_from_slice(&digest[..16]);
2877 s_bytes
2878 },
2879 |f| f(),
2880 );
2881 self.users
2882 .create(&s.name, &s.password, role, salt)
2883 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
2884 Ok(QueryResult::CommandOk {
2885 affected: 1,
2886 modified_catalog: true,
2887 })
2888 }
2889
2890 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2891 if self.in_transaction() {
2892 return Err(EngineError::Unsupported(
2893 "DROP USER is not allowed inside a transaction".into(),
2894 ));
2895 }
2896 self.users
2897 .drop(name)
2898 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
2899 Ok(QueryResult::CommandOk {
2900 affected: 1,
2901 modified_catalog: true,
2902 })
2903 }
2904
2905 fn exec_create_function(
2911 &mut self,
2912 s: spg_sql::ast::CreateFunctionStatement,
2913 ) -> Result<QueryResult, EngineError> {
2914 let args_repr = render_function_args(&s.args);
2915 let returns = match &s.returns {
2916 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
2917 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
2918 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
2919 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
2920 };
2921 let body_text = match &s.body {
2922 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
2923 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
2924 };
2925 let def = spg_storage::FunctionDef {
2926 name: s.name.clone(),
2927 args_repr,
2928 returns,
2929 language: s.language.clone(),
2930 body: body_text,
2931 };
2932 self.active_catalog_mut()
2933 .create_function(def, s.or_replace)
2934 .map_err(EngineError::Storage)?;
2935 Ok(QueryResult::CommandOk {
2936 affected: 0,
2937 modified_catalog: true,
2938 })
2939 }
2940
2941 fn exec_create_trigger(
2946 &mut self,
2947 s: spg_sql::ast::CreateTriggerStatement,
2948 ) -> Result<QueryResult, EngineError> {
2949 let timing = match s.timing {
2950 spg_sql::ast::TriggerTiming::Before => "BEFORE",
2951 spg_sql::ast::TriggerTiming::After => "AFTER",
2952 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
2953 };
2954 let events: Vec<alloc::string::String> = s
2955 .events
2956 .iter()
2957 .map(|e| match e {
2958 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
2959 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
2960 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
2961 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
2962 })
2963 .collect();
2964 let for_each = match s.for_each {
2965 spg_sql::ast::TriggerForEach::Row => "ROW",
2966 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
2967 };
2968 let def = spg_storage::TriggerDef {
2969 name: s.name.clone(),
2970 table: s.table.clone(),
2971 timing: alloc::string::String::from(timing),
2972 events,
2973 for_each: alloc::string::String::from(for_each),
2974 function: s.function.clone(),
2975 update_columns: s.update_columns.clone(),
2976 enabled: true,
2979 };
2980 self.active_catalog_mut()
2981 .create_trigger(def, s.or_replace)
2982 .map_err(EngineError::Storage)?;
2983 Ok(QueryResult::CommandOk {
2984 affected: 0,
2985 modified_catalog: true,
2986 })
2987 }
2988
2989 fn exec_drop_trigger(
2990 &mut self,
2991 name: &str,
2992 table: &str,
2993 if_exists: bool,
2994 ) -> Result<QueryResult, EngineError> {
2995 let removed = self.active_catalog_mut().drop_trigger(name, table);
2996 if !removed && !if_exists {
2997 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
2998 alloc::format!("trigger {name:?} on {table:?} does not exist"),
2999 )));
3000 }
3001 Ok(QueryResult::CommandOk {
3002 affected: usize::from(removed),
3003 modified_catalog: removed,
3004 })
3005 }
3006
3007 fn exec_drop_function(
3008 &mut self,
3009 name: &str,
3010 if_exists: bool,
3011 ) -> Result<QueryResult, EngineError> {
3012 let removed = self.active_catalog_mut().drop_function(name);
3013 if !removed && !if_exists {
3014 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3015 alloc::format!("function {name:?} does not exist"),
3016 )));
3017 }
3018 Ok(QueryResult::CommandOk {
3019 affected: usize::from(removed),
3020 modified_catalog: removed,
3021 })
3022 }
3023
3024 fn exec_create_sequence(
3028 &mut self,
3029 s: spg_sql::ast::CreateSequenceStatement,
3030 ) -> Result<QueryResult, EngineError> {
3031 use spg_sql::ast::{SeqBound, SequenceDataType as AstDt};
3032 use spg_storage::{SequenceDataType, SequenceDef};
3033 let dt = match s.data_type {
3034 None => SequenceDataType::BigInt,
3035 Some(AstDt::SmallInt) => SequenceDataType::SmallInt,
3036 Some(AstDt::Int) => SequenceDataType::Int,
3037 Some(AstDt::BigInt) => SequenceDataType::BigInt,
3038 };
3039 let increment = s.options.increment.unwrap_or(1);
3040 if increment == 0 {
3041 return Err(EngineError::Unsupported(
3042 "INCREMENT must not be zero".into(),
3043 ));
3044 }
3045 let (def_min, def_max) = dt.default_bounds(increment > 0);
3046 let min_value = match s.options.min_value {
3047 None | Some(SeqBound::NoBound) => def_min,
3048 Some(SeqBound::Value(n)) => n,
3049 };
3050 let max_value = match s.options.max_value {
3051 None | Some(SeqBound::NoBound) => def_max,
3052 Some(SeqBound::Value(n)) => n,
3053 };
3054 if min_value > max_value {
3055 return Err(EngineError::Unsupported(alloc::format!(
3056 "MINVALUE ({min_value}) must be <= MAXVALUE ({max_value})"
3057 )));
3058 }
3059 let start = s
3060 .options
3061 .start
3062 .unwrap_or(if increment > 0 { min_value } else { max_value });
3063 if start < min_value || start > max_value {
3064 return Err(EngineError::Unsupported(alloc::format!(
3065 "START WITH ({start}) is outside MINVALUE..MAXVALUE ({min_value}..{max_value})"
3066 )));
3067 }
3068 let cache = s.options.cache.unwrap_or(1);
3069 if cache < 1 {
3070 return Err(EngineError::Unsupported("CACHE must be >= 1".into()));
3071 }
3072 let cycle = s.options.cycle.unwrap_or(false);
3073 let owned_by = match s.options.owned_by {
3074 None | Some(spg_sql::ast::SequenceOwnedBy::None) => None,
3075 Some(spg_sql::ast::SequenceOwnedBy::Column { table, column }) => Some((table, column)),
3076 };
3077 let def = SequenceDef {
3078 name: s.name.clone(),
3079 data_type: dt,
3080 start,
3081 increment,
3082 min_value,
3083 max_value,
3084 cache,
3085 cycle,
3086 owned_by,
3087 last_value: start,
3088 is_called: false,
3089 };
3090 self.active_catalog_mut()
3091 .create_sequence(def, s.if_not_exists)
3092 .map_err(EngineError::Storage)?;
3093 Ok(QueryResult::CommandOk {
3094 affected: 0,
3095 modified_catalog: !self.in_transaction(),
3096 })
3097 }
3098
3099 fn exec_alter_sequence(
3102 &mut self,
3103 s: spg_sql::ast::AlterSequenceStatement,
3104 ) -> Result<QueryResult, EngineError> {
3105 use spg_sql::ast::SeqBound;
3106 let cat = self.active_catalog_mut();
3107 if !cat.sequences().contains_key(&s.name) {
3108 if s.if_exists {
3109 return Ok(QueryResult::CommandOk {
3110 affected: 0,
3111 modified_catalog: false,
3112 });
3113 }
3114 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3115 alloc::format!("sequence {:?} does not exist", s.name),
3116 )));
3117 }
3118 let min_value = match s.options.min_value {
3119 None => None,
3120 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3122 };
3123 let max_value = match s.options.max_value {
3124 None => None,
3125 Some(SeqBound::NoBound) => None,
3126 Some(SeqBound::Value(n)) => Some(n),
3127 };
3128 let owned_by = s.options.owned_by.map(|ob| match ob {
3129 spg_sql::ast::SequenceOwnedBy::None => None,
3130 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3131 });
3132 cat.alter_sequence(
3133 &s.name,
3134 s.options.increment,
3135 min_value,
3136 max_value,
3137 s.options.start,
3138 s.options.restart,
3139 s.options.cache,
3140 s.options.cycle,
3141 owned_by,
3142 )
3143 .map_err(EngineError::Storage)?;
3144 Ok(QueryResult::CommandOk {
3145 affected: 0,
3146 modified_catalog: !self.in_transaction(),
3147 })
3148 }
3149
3150 fn pre_resolve_sequence_calls_in_statement(
3155 &mut self,
3156 stmt: &mut Statement,
3157 ) -> Result<(), EngineError> {
3158 match stmt {
3159 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3160 Statement::Insert(s) => {
3161 for tuple in &mut s.rows {
3162 for cell in tuple.iter_mut() {
3163 self.resolve_sequence_calls_in_expr(cell)?;
3164 }
3165 }
3166 Ok(())
3167 }
3168 Statement::Update(s) => {
3169 for (_col, expr) in &mut s.assignments {
3170 self.resolve_sequence_calls_in_expr(expr)?;
3171 }
3172 if let Some(w) = &mut s.where_ {
3173 self.resolve_sequence_calls_in_expr(w)?;
3174 }
3175 Ok(())
3176 }
3177 Statement::Delete(s) => {
3178 if let Some(w) = &mut s.where_ {
3179 self.resolve_sequence_calls_in_expr(w)?;
3180 }
3181 Ok(())
3182 }
3183 _ => Ok(()),
3184 }
3185 }
3186
3187 fn pre_resolve_sequence_calls_in_select(
3188 &mut self,
3189 s: &mut spg_sql::ast::SelectStatement,
3190 ) -> Result<(), EngineError> {
3191 for item in &mut s.items {
3192 match item {
3193 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3194 self.resolve_sequence_calls_in_expr(expr)?;
3195 }
3196 spg_sql::ast::SelectItem::Wildcard => {}
3197 }
3198 }
3199 if let Some(w) = &mut s.where_ {
3200 self.resolve_sequence_calls_in_expr(w)?;
3201 }
3202 Ok(())
3203 }
3204
3205 #[allow(clippy::too_many_lines)]
3213 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3214 match expr {
3215 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3216 Expr::FunctionCall { name, args } => {
3217 for a in args.iter_mut() {
3221 self.resolve_sequence_calls_in_expr(a)?;
3222 }
3223 let lc = name.to_ascii_lowercase();
3224 if lc == "nextval" || lc == "currval" || lc == "setval" {
3225 let v = self.eval_sequence_call(&lc, args)?;
3226 *expr = Expr::Literal(value_to_literal(v));
3227 }
3228 Ok(())
3229 }
3230 Expr::Binary { lhs, rhs, .. } => {
3231 self.resolve_sequence_calls_in_expr(lhs)?;
3232 self.resolve_sequence_calls_in_expr(rhs)
3233 }
3234 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3235 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3236 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3237 Expr::Like { expr, pattern, .. } => {
3238 self.resolve_sequence_calls_in_expr(expr)?;
3239 self.resolve_sequence_calls_in_expr(pattern)
3240 }
3241 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3242 Expr::Array(items) => {
3243 for it in items.iter_mut() {
3244 self.resolve_sequence_calls_in_expr(it)?;
3245 }
3246 Ok(())
3247 }
3248 _ => Ok(()),
3253 }
3254 }
3255
3256 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3260 if args.is_empty() {
3261 return Err(EngineError::Unsupported(alloc::format!(
3262 "{op}() takes at least one argument"
3263 )));
3264 }
3265 let seq_name = match &args[0] {
3266 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3267 let trimmed = s
3273 .strip_prefix("public.")
3274 .or_else(|| s.strip_prefix("pg_catalog."))
3275 .unwrap_or(s);
3276 trimmed.to_string()
3277 }
3278 Expr::Cast { expr, .. } => {
3283 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3284 let trimmed = s
3285 .strip_prefix("public.")
3286 .or_else(|| s.strip_prefix("pg_catalog."))
3287 .unwrap_or(s);
3288 trimmed.to_string()
3289 } else {
3290 return Err(EngineError::Unsupported(alloc::format!(
3291 "{op}() first argument must be a literal sequence name"
3292 )));
3293 }
3294 }
3295 other => {
3296 return Err(EngineError::Unsupported(alloc::format!(
3297 "{op}() first argument must be a literal sequence name, got {other:?}"
3298 )));
3299 }
3300 };
3301 match op {
3302 "nextval" => {
3303 let v = self
3304 .active_catalog_mut()
3305 .sequence_next_value(&seq_name)
3306 .map_err(EngineError::Storage)?;
3307 Ok(Value::BigInt(v))
3308 }
3309 "currval" => {
3310 let v = self
3311 .active_catalog()
3312 .sequence_current_value(&seq_name)
3313 .map_err(EngineError::Storage)?;
3314 Ok(Value::BigInt(v))
3315 }
3316 "setval" => {
3317 if args.len() < 2 || args.len() > 3 {
3318 return Err(EngineError::Unsupported(alloc::format!(
3319 "setval() takes 2 or 3 arguments, got {}",
3320 args.len()
3321 )));
3322 }
3323 let value = match &args[1] {
3324 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3325 other => {
3326 return Err(EngineError::Unsupported(alloc::format!(
3327 "setval() value argument must be a literal integer, got {other:?}"
3328 )));
3329 }
3330 };
3331 let is_called = if args.len() == 3 {
3332 match &args[2] {
3333 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3334 other => {
3335 return Err(EngineError::Unsupported(alloc::format!(
3336 "setval() is_called argument must be a literal BOOL, got {other:?}"
3337 )));
3338 }
3339 }
3340 } else {
3341 true
3342 };
3343 let v = self
3344 .active_catalog_mut()
3345 .sequence_set_value(&seq_name, value, is_called)
3346 .map_err(EngineError::Storage)?;
3347 Ok(Value::BigInt(v))
3348 }
3349 other => Err(EngineError::Unsupported(alloc::format!(
3350 "unknown sequence op {other:?}"
3351 ))),
3352 }
3353 }
3354
3355 fn expand_views_in_select(
3364 &self,
3365 stmt: &SelectStatement,
3366 ) -> Result<Option<SelectStatement>, EngineError> {
3367 let cat = self.active_catalog();
3368 let mut referenced: Vec<String> = Vec::new();
3369 if let Some(from) = &stmt.from {
3370 collect_view_refs(&from.primary, cat, &mut referenced);
3371 for j in &from.joins {
3372 collect_view_refs(&j.table, cat, &mut referenced);
3373 }
3374 }
3375 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3378 if referenced.is_empty() {
3379 return Ok(None);
3380 }
3381 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3382 for name in &referenced {
3383 let view = cat.views().get(name).ok_or_else(|| {
3384 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3385 "view {name:?} disappeared mid-expansion"
3386 )))
3387 })?;
3388 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3389 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3390 })?;
3391 let Statement::Select(body) = parsed else {
3392 return Err(EngineError::Unsupported(alloc::format!(
3393 "view {name:?} body is not a SELECT (catalog corruption)"
3394 )));
3395 };
3396 new_ctes.push(spg_sql::ast::Cte {
3397 name: name.clone(),
3398 body,
3399 recursive: false,
3400 column_overrides: view.columns.clone(),
3401 });
3402 }
3403 let mut out = stmt.clone();
3404 new_ctes.extend(out.ctes);
3406 out.ctes = new_ctes;
3407 Ok(Some(out))
3408 }
3409
3410 fn exec_create_view(
3414 &mut self,
3415 s: spg_sql::ast::CreateViewStatement,
3416 ) -> Result<QueryResult, EngineError> {
3417 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3421 let def = spg_storage::ViewDef {
3422 name: s.name.clone(),
3423 columns: s.columns,
3424 body: body_repr,
3425 };
3426 self.active_catalog_mut()
3427 .create_view(def, s.or_replace, s.if_not_exists)
3428 .map_err(EngineError::Storage)?;
3429 Ok(QueryResult::CommandOk {
3430 affected: 0,
3431 modified_catalog: !self.in_transaction(),
3432 })
3433 }
3434
3435 fn exec_create_type(
3440 &mut self,
3441 s: spg_sql::ast::CreateTypeStatement,
3442 ) -> Result<QueryResult, EngineError> {
3443 let cat = self.active_catalog();
3446 if cat.get(&s.name).is_some() {
3447 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3448 alloc::format!("type {:?} would shadow an existing table", s.name),
3449 )));
3450 }
3451 if cat.sequences().contains_key(&s.name) {
3452 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3453 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3454 )));
3455 }
3456 if cat.views().contains_key(&s.name) {
3457 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3458 alloc::format!("type {:?} would shadow an existing view", s.name),
3459 )));
3460 }
3461 let def = match s.kind {
3462 spg_sql::ast::TypeKind::Enum { labels } => {
3463 if labels.is_empty() {
3464 return Err(EngineError::Unsupported(
3465 "CREATE TYPE … AS ENUM requires at least one label".into(),
3466 ));
3467 }
3468 for i in 0..labels.len() {
3470 for j in (i + 1)..labels.len() {
3471 if labels[i] == labels[j] {
3472 return Err(EngineError::Unsupported(alloc::format!(
3473 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3474 s.name,
3475 labels[i]
3476 )));
3477 }
3478 }
3479 }
3480 spg_storage::EnumDef {
3481 name: s.name.clone(),
3482 labels,
3483 }
3484 }
3485 };
3486 self.active_catalog_mut()
3487 .create_enum_type(def)
3488 .map_err(EngineError::Storage)?;
3489 Ok(QueryResult::CommandOk {
3490 affected: 0,
3491 modified_catalog: !self.in_transaction(),
3492 })
3493 }
3494
3495 fn exec_create_domain(
3500 &mut self,
3501 s: spg_sql::ast::CreateDomainStatement,
3502 ) -> Result<QueryResult, EngineError> {
3503 let cat = self.active_catalog();
3504 if cat.domain_types().contains_key(&s.name) {
3505 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3506 alloc::format!("domain {:?} already exists", s.name),
3507 )));
3508 }
3509 if cat.get(&s.name).is_some()
3510 || cat.sequences().contains_key(&s.name)
3511 || cat.views().contains_key(&s.name)
3512 || cat.enum_types().contains_key(&s.name)
3513 {
3514 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3515 alloc::format!("domain {:?} would shadow an existing object", s.name),
3516 )));
3517 }
3518 let base_type = column_type_to_data_type(s.base_type);
3519 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3520 let checks = s
3521 .checks
3522 .iter()
3523 .map(|e| alloc::format!("{e}"))
3524 .collect::<Vec<_>>();
3525 let def = spg_storage::DomainDef {
3526 name: s.name.clone(),
3527 base_type,
3528 nullable: !s.not_null,
3529 default,
3530 checks,
3531 };
3532 self.active_catalog_mut()
3533 .create_domain_type(def)
3534 .map_err(EngineError::Storage)?;
3535 Ok(QueryResult::CommandOk {
3536 affected: 0,
3537 modified_catalog: !self.in_transaction(),
3538 })
3539 }
3540
3541 fn exec_drop_domain(
3543 &mut self,
3544 names: &[String],
3545 if_exists: bool,
3546 ) -> Result<QueryResult, EngineError> {
3547 let mut removed = 0usize;
3548 for name in names {
3549 let was_present = self.active_catalog_mut().drop_domain_type(name);
3550 if was_present {
3551 removed += 1;
3552 } else if !if_exists {
3553 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3554 alloc::format!("domain {name:?} does not exist"),
3555 )));
3556 }
3557 }
3558 Ok(QueryResult::CommandOk {
3559 affected: removed,
3560 modified_catalog: removed > 0 && !self.in_transaction(),
3561 })
3562 }
3563
3564 fn exec_create_schema(
3570 &mut self,
3571 name: String,
3572 if_not_exists: bool,
3573 ) -> Result<QueryResult, EngineError> {
3574 self.active_catalog_mut()
3575 .create_schema(name, if_not_exists)
3576 .map_err(EngineError::Storage)?;
3577 Ok(QueryResult::CommandOk {
3578 affected: 0,
3579 modified_catalog: !self.in_transaction(),
3580 })
3581 }
3582
3583 fn exec_drop_schema(
3587 &mut self,
3588 names: &[String],
3589 if_exists: bool,
3590 ) -> Result<QueryResult, EngineError> {
3591 let mut removed = 0usize;
3592 for name in names {
3593 let was_present = self
3594 .active_catalog_mut()
3595 .drop_schema(name)
3596 .map_err(EngineError::Storage)?;
3597 if was_present {
3598 removed += 1;
3599 } else if !if_exists {
3600 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3601 alloc::format!("schema {name:?} does not exist"),
3602 )));
3603 }
3604 }
3605 Ok(QueryResult::CommandOk {
3606 affected: removed,
3607 modified_catalog: removed > 0 && !self.in_transaction(),
3608 })
3609 }
3610
3611 fn exec_drop_type(
3616 &mut self,
3617 names: &[String],
3618 if_exists: bool,
3619 ) -> Result<QueryResult, EngineError> {
3620 let mut removed = 0usize;
3621 for name in names {
3622 let was_present = self.active_catalog_mut().drop_enum_type(name);
3623 if was_present {
3624 removed += 1;
3625 } else if !if_exists {
3626 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3627 alloc::format!("type {name:?} does not exist"),
3628 )));
3629 }
3630 }
3631 Ok(QueryResult::CommandOk {
3632 affected: removed,
3633 modified_catalog: removed > 0 && !self.in_transaction(),
3634 })
3635 }
3636
3637 fn exec_create_materialized_view(
3642 &mut self,
3643 s: spg_sql::ast::CreateMaterializedViewStatement,
3644 ) -> Result<QueryResult, EngineError> {
3645 let cat = self.active_catalog();
3647 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3648 if s.if_not_exists {
3649 return Ok(QueryResult::CommandOk {
3650 affected: 0,
3651 modified_catalog: false,
3652 });
3653 }
3654 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3655 alloc::format!("materialized view {:?} already exists", s.name),
3656 )));
3657 }
3658 if cat.views().contains_key(&s.name) {
3659 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3660 alloc::format!(
3661 "materialized view {:?} would shadow an existing view",
3662 s.name
3663 ),
3664 )));
3665 }
3666 if cat.sequences().contains_key(&s.name) {
3667 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3668 alloc::format!(
3669 "materialized view {:?} would shadow an existing sequence",
3670 s.name
3671 ),
3672 )));
3673 }
3674 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3676 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3681 let (mut cols, rows) = match result {
3682 QueryResult::Rows { columns, rows } => (columns, rows),
3683 other => {
3684 return Err(EngineError::Unsupported(alloc::format!(
3685 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3686 )));
3687 }
3688 };
3689 if !s.columns.is_empty() {
3691 if s.columns.len() != cols.len() {
3692 return Err(EngineError::Unsupported(alloc::format!(
3693 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3694 s.name,
3695 s.columns.len(),
3696 cols.len()
3697 )));
3698 }
3699 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3700 c.name.clone_from(name);
3701 }
3702 }
3703 cols = infer_column_types(&cols, &rows);
3706 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3707 let cat = self.active_catalog_mut();
3708 cat.create_table(schema).map_err(EngineError::Storage)?;
3709 if s.with_data {
3710 let table = cat
3711 .get_mut(&s.name)
3712 .expect("just-created materialized-view backing table must exist");
3713 for row in rows {
3714 table.insert(row).map_err(EngineError::Storage)?;
3715 }
3716 }
3717 cat.register_materialized_view(s.name.clone(), body_repr);
3718 Ok(QueryResult::CommandOk {
3719 affected: 0,
3720 modified_catalog: !self.in_transaction(),
3721 })
3722 }
3723
3724 fn exec_refresh_materialized_view(
3728 &mut self,
3729 name: &str,
3730 with_data: bool,
3731 ) -> Result<QueryResult, EngineError> {
3732 let source = self
3733 .active_catalog()
3734 .materialized_views()
3735 .get(name)
3736 .cloned()
3737 .ok_or_else(|| {
3738 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3739 "materialized view {name:?} does not exist"
3740 )))
3741 })?;
3742 {
3745 let cat = self.active_catalog_mut();
3746 let table = cat.get_mut(name).ok_or_else(|| {
3747 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3748 "materialized view {name:?} backing table missing"
3749 )))
3750 })?;
3751 table.truncate();
3752 }
3753 if !with_data {
3754 return Ok(QueryResult::CommandOk {
3755 affected: 0,
3756 modified_catalog: !self.in_transaction(),
3757 });
3758 }
3759 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
3760 EngineError::Unsupported(alloc::format!(
3761 "materialized view {name:?} body re-parse failed: {e}"
3762 ))
3763 })?;
3764 let Statement::Select(body) = parsed else {
3765 return Err(EngineError::Unsupported(alloc::format!(
3766 "materialized view {name:?} body is not a SELECT (catalog corruption)"
3767 )));
3768 };
3769 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
3770 QueryResult::Rows { rows, .. } => rows,
3771 other => {
3772 return Err(EngineError::Unsupported(alloc::format!(
3773 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
3774 )));
3775 }
3776 };
3777 let cat = self.active_catalog_mut();
3778 let table = cat.get_mut(name).expect("backing table verified above");
3779 let affected = rows.len();
3780 for row in rows {
3781 table.insert(row).map_err(EngineError::Storage)?;
3782 }
3783 Ok(QueryResult::CommandOk {
3784 affected,
3785 modified_catalog: !self.in_transaction(),
3786 })
3787 }
3788
3789 fn exec_drop_materialized_view(
3792 &mut self,
3793 names: &[String],
3794 if_exists: bool,
3795 ) -> Result<QueryResult, EngineError> {
3796 let mut removed = 0usize;
3797 for name in names {
3798 let was_present = self
3799 .active_catalog_mut()
3800 .drop_materialized_view_source(name);
3801 if was_present {
3802 self.active_catalog_mut().drop_table(name);
3804 removed += 1;
3805 } else if !if_exists {
3806 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3807 alloc::format!("materialized view {name:?} does not exist"),
3808 )));
3809 }
3810 }
3811 Ok(QueryResult::CommandOk {
3812 affected: removed,
3813 modified_catalog: removed > 0 && !self.in_transaction(),
3814 })
3815 }
3816
3817 fn exec_drop_view(
3819 &mut self,
3820 names: &[String],
3821 if_exists: bool,
3822 ) -> Result<QueryResult, EngineError> {
3823 let mut removed = 0usize;
3824 for name in names {
3825 let was_present = self.active_catalog_mut().drop_view(name);
3826 if !was_present && !if_exists {
3827 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3828 alloc::format!("view {name:?} does not exist"),
3829 )));
3830 }
3831 if was_present {
3832 removed += 1;
3833 }
3834 }
3835 Ok(QueryResult::CommandOk {
3836 affected: removed,
3837 modified_catalog: removed > 0 && !self.in_transaction(),
3838 })
3839 }
3840
3841 fn exec_drop_sequence(
3843 &mut self,
3844 names: &[String],
3845 if_exists: bool,
3846 ) -> Result<QueryResult, EngineError> {
3847 let mut removed = 0usize;
3848 for name in names {
3849 let was_present = self.active_catalog_mut().drop_sequence(name);
3850 if !was_present && !if_exists {
3851 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3852 alloc::format!("sequence {name:?} does not exist"),
3853 )));
3854 }
3855 if was_present {
3856 removed += 1;
3857 }
3858 }
3859 Ok(QueryResult::CommandOk {
3860 affected: removed,
3861 modified_catalog: removed > 0 && !self.in_transaction(),
3862 })
3863 }
3864
3865 fn exec_update_cancel(
3872 &mut self,
3873 stmt: &spg_sql::ast::UpdateStatement,
3874 cancel: CancelToken<'_>,
3875 ) -> Result<QueryResult, EngineError> {
3876 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
3885 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
3886 let trigger_session_cfg: Option<String> = self
3887 .session_params
3888 .get("default_text_search_config")
3889 .cloned();
3890 if let Some(w) = &stmt.where_ {
3898 let schema_cols = self
3899 .active_catalog()
3900 .get(&stmt.table)
3901 .ok_or_else(|| {
3902 EngineError::Storage(StorageError::TableNotFound {
3903 name: stmt.table.clone(),
3904 })
3905 })?
3906 .schema()
3907 .columns
3908 .clone();
3909 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
3910 && let Some(idx_name) = self
3911 .active_catalog()
3912 .get(&stmt.table)
3913 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
3914 {
3915 let _ = self
3919 .active_catalog_mut()
3920 .promote_cold_row(&stmt.table, &idx_name, &key);
3921 }
3922 }
3923
3924 let ts_cfg: Option<String> = self
3927 .session_param("default_text_search_config")
3928 .map(String::from);
3929 let clock_for_on_update = self.clock;
3933 let table = self
3934 .active_catalog_mut()
3935 .get_mut(&stmt.table)
3936 .ok_or_else(|| {
3937 EngineError::Storage(StorageError::TableNotFound {
3938 name: stmt.table.clone(),
3939 })
3940 })?;
3941 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
3942 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
3946 for (col, expr) in &stmt.assignments {
3947 let pos = schema_cols
3948 .iter()
3949 .position(|c| c.name == *col)
3950 .ok_or_else(|| {
3951 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
3952 })?;
3953 targets.push((pos, expr));
3954 }
3955 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
3963 for (i, col) in schema_cols.iter().enumerate() {
3964 if targets.iter().any(|(p, _)| *p == i) {
3965 continue;
3966 }
3967 if let Some(src) = &col.on_update_runtime {
3968 on_update_overrides.push((i, src.clone()));
3969 }
3970 }
3971 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
3972 .with_default_text_search_config(ts_cfg.as_deref());
3973 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
3979 for (i, row) in table.rows().iter().enumerate() {
3980 if i.is_multiple_of(256) {
3984 cancel.check()?;
3985 }
3986 if let Some(w) = &stmt.where_ {
3987 let cond = eval::eval_expr(w, row, &ctx)?;
3988 if !matches!(cond, Value::Bool(true)) {
3989 continue;
3990 }
3991 }
3992 let mut new_vals = row.values.clone();
3993 for (pos, expr) in &targets {
3994 let v = eval::eval_expr(expr, row, &ctx)?;
3995 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
3996 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
3997 new_vals[*pos] = coerced;
3998 }
3999 for (pos, src) in &on_update_overrides {
4002 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4003 new_vals[*pos] = v;
4004 }
4005 planned.push((i, new_vals));
4006 }
4007 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4011 .iter()
4012 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4013 .collect();
4014 let self_fks = table.schema().foreign_keys.clone();
4015 let _ = table;
4020 if !self_fks.is_empty() {
4024 let new_rows: Vec<Vec<Value>> = planned
4025 .iter()
4026 .map(|(_pos, new_vals)| new_vals.clone())
4027 .collect();
4028 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4029 }
4030 {
4034 let new_rows: Vec<Vec<Value>> = planned
4035 .iter()
4036 .map(|(_pos, new_vals)| new_vals.clone())
4037 .collect();
4038 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4039 }
4040 let child_plan =
4044 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4045 for step in &child_plan {
4047 apply_fk_child_step(self.active_catalog_mut(), step)?;
4048 }
4049 let table = self
4051 .active_catalog_mut()
4052 .get_mut(&stmt.table)
4053 .ok_or_else(|| {
4054 EngineError::Storage(StorageError::TableNotFound {
4055 name: stmt.table.clone(),
4056 })
4057 })?;
4058 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4068 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4070 for (pos, new_vals) in &planned {
4071 let old_row = table.rows()[*pos].clone();
4072 let mut new_row = Row::new(new_vals.clone());
4073 let mut skip = false;
4074 for (fd, filter) in &before_update_triggers {
4075 if !filter.is_empty()
4080 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4081 {
4082 continue;
4083 }
4084 let (outcome, deferred) = triggers::fire_row_trigger(
4085 fd,
4086 Some(new_row.clone()),
4087 Some(&old_row),
4088 &stmt.table,
4089 &schema_cols,
4090 &[],
4091 trigger_session_cfg.as_deref(),
4092 false,
4093 )
4094 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4095 deferred_embedded.extend(deferred);
4096 match outcome {
4097 triggers::TriggerOutcome::Row(r) => new_row = r,
4098 triggers::TriggerOutcome::Skip => {
4099 skip = true;
4100 break;
4101 }
4102 }
4103 }
4104 if !skip {
4105 applied_after_before.push((*pos, new_row, old_row));
4106 }
4107 }
4108 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4111 applied_after_before
4112 .iter()
4113 .map(|(_pos, new_row, _old)| new_row.values.clone())
4114 .collect()
4115 } else {
4116 Vec::new()
4117 };
4118 let affected = applied_after_before.len();
4119 for (pos, new_row, old_row) in applied_after_before {
4123 table.update_row(pos, new_row.values.clone())?;
4124 for (fd, filter) in &after_update_triggers {
4125 if !filter.is_empty()
4126 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4127 {
4128 continue;
4129 }
4130 let (_outcome, deferred) = triggers::fire_row_trigger(
4131 fd,
4132 Some(new_row.clone()),
4133 Some(&old_row),
4134 &stmt.table,
4135 &schema_cols,
4136 &[],
4137 trigger_session_cfg.as_deref(),
4138 true,
4139 )
4140 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4141 deferred_embedded.extend(deferred);
4142 }
4143 }
4144 let _ = table;
4145 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4147 if !self.in_transaction() && affected > 0 {
4149 self.statistics
4150 .record_modifications(&stmt.table, affected as u64);
4151 }
4152 if let Some(items) = &stmt.returning {
4154 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4155 }
4156 Ok(QueryResult::CommandOk {
4157 affected,
4158 modified_catalog: !self.in_transaction(),
4159 })
4160 }
4161
4162 fn exec_merge_cancel(
4193 &mut self,
4194 stmt: &spg_sql::ast::MergeStatement,
4195 cancel: CancelToken<'_>,
4196 ) -> Result<QueryResult, EngineError> {
4197 let target_alias = stmt
4198 .target_alias
4199 .clone()
4200 .unwrap_or_else(|| stmt.target.clone());
4201 let source_alias = stmt
4202 .source_alias
4203 .clone()
4204 .unwrap_or_else(|| stmt.source.clone());
4205 let (target_cols, target_rows_snapshot) = {
4206 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4207 EngineError::Storage(StorageError::TableNotFound {
4208 name: stmt.target.clone(),
4209 })
4210 })?;
4211 (
4212 t.schema().columns.clone(),
4213 t.rows().iter().cloned().collect::<Vec<Row>>(),
4214 )
4215 };
4216 let (source_cols, source_rows) = {
4217 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4218 EngineError::Storage(StorageError::TableNotFound {
4219 name: stmt.source.clone(),
4220 })
4221 })?;
4222 (
4223 s.schema().columns.clone(),
4224 s.rows().iter().cloned().collect::<Vec<Row>>(),
4225 )
4226 };
4227 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4229 for col in &target_cols {
4230 combined_schema.push(ColumnSchema::new(
4231 alloc::format!("{target_alias}.{}", col.name),
4232 col.ty,
4233 col.nullable,
4234 ));
4235 }
4236 for col in &source_cols {
4237 combined_schema.push(ColumnSchema::new(
4238 alloc::format!("{source_alias}.{}", col.name),
4239 col.ty,
4240 col.nullable,
4241 ));
4242 }
4243 let combined_ctx = EvalContext::new(&combined_schema, None);
4244 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4248 for col in &target_cols {
4249 source_only_schema.push(ColumnSchema::new(
4250 alloc::format!("{target_alias}.{}", col.name),
4251 col.ty,
4252 col.nullable,
4253 ));
4254 }
4255 for col in &source_cols {
4256 source_only_schema.push(ColumnSchema::new(
4257 alloc::format!("{source_alias}.{}", col.name),
4258 col.ty,
4259 col.nullable,
4260 ));
4261 }
4262 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4263 let target_arity = target_cols.len();
4264 let source_arity = source_cols.len();
4265
4266 let mut delete_indices: Vec<usize> = Vec::new();
4269 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4270 let mut inserts: Vec<Vec<Value>> = Vec::new();
4271 let mut affected: usize = 0;
4272
4273 for (src_idx, src_row) in source_rows.iter().enumerate() {
4274 if src_idx.is_multiple_of(256) {
4275 cancel.check()?;
4276 }
4277 let mut matched_targets: Vec<usize> = Vec::new();
4279 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4280 let mut combined_vals = t_row.values.clone();
4281 combined_vals.extend(src_row.values.iter().cloned());
4282 let combined_row = Row::new(combined_vals);
4283 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4284 if matches!(cond, Value::Bool(true)) {
4285 matched_targets.push(t_idx);
4286 }
4287 }
4288 let is_matched = !matched_targets.is_empty();
4289 let fired_clause = stmt.clauses.iter().find(|c| {
4295 let kind_ok = match c.matched {
4296 spg_sql::ast::MergeMatched::Matched => is_matched,
4297 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4298 };
4299 if !kind_ok {
4300 return false;
4301 }
4302 let Some(cond_expr) = &c.condition else {
4303 return true;
4304 };
4305 let row = if is_matched {
4306 let t = &target_rows_snapshot[matched_targets[0]];
4307 let mut vals = t.values.clone();
4308 vals.extend(src_row.values.iter().cloned());
4309 Row::new(vals)
4310 } else {
4311 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4312 vals.extend(src_row.values.iter().cloned());
4313 Row::new(vals)
4314 };
4315 let ctx_ref = if is_matched {
4316 &combined_ctx
4317 } else {
4318 &source_only_ctx
4319 };
4320 matches!(
4321 eval::eval_expr(cond_expr, &row, ctx_ref),
4322 Ok(Value::Bool(true))
4323 )
4324 });
4325 let Some(clause) = fired_clause else { continue };
4326 match &clause.action {
4327 spg_sql::ast::MergeAction::DoNothing => {}
4328 spg_sql::ast::MergeAction::Delete => {
4329 for &t_idx in &matched_targets {
4330 if !delete_indices.contains(&t_idx) {
4331 delete_indices.push(t_idx);
4332 affected += 1;
4333 }
4334 }
4335 }
4336 spg_sql::ast::MergeAction::Update { assignments } => {
4337 let mut planned_sets: Vec<(usize, &Expr)> =
4339 Vec::with_capacity(assignments.len());
4340 for (col, expr) in assignments {
4341 let pos =
4342 target_cols
4343 .iter()
4344 .position(|c| c.name == *col)
4345 .ok_or_else(|| {
4346 EngineError::Eval(EvalError::ColumnNotFound {
4347 name: col.clone(),
4348 })
4349 })?;
4350 planned_sets.push((pos, expr));
4351 }
4352 for &t_idx in &matched_targets {
4353 let t_row = &target_rows_snapshot[t_idx];
4354 let mut new_values = t_row.values.clone();
4355 let mut combined_vals = t_row.values.clone();
4356 combined_vals.extend(src_row.values.iter().cloned());
4357 let combined_row = Row::new(combined_vals);
4358 for (pos, expr) in &planned_sets {
4359 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4360 let coerced = coerce_value(
4361 raw,
4362 target_cols[*pos].ty,
4363 &target_cols[*pos].name,
4364 *pos,
4365 )?;
4366 new_values[*pos] = coerced;
4367 }
4368 updates.push((t_idx, new_values));
4369 affected += 1;
4370 }
4371 }
4372 spg_sql::ast::MergeAction::Insert { columns, values } => {
4373 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4375 vals.extend(src_row.values.iter().cloned());
4376 let synth_row = Row::new(vals);
4377 let mut new_row_values: Vec<Value> =
4378 (0..target_arity).map(|_| Value::Null).collect();
4379 for (col, expr) in columns.iter().zip(values.iter()) {
4380 let pos =
4381 target_cols
4382 .iter()
4383 .position(|c| c.name == *col)
4384 .ok_or_else(|| {
4385 EngineError::Eval(EvalError::ColumnNotFound {
4386 name: col.clone(),
4387 })
4388 })?;
4389 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4390 let coerced =
4391 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4392 new_row_values[pos] = coerced;
4393 }
4394 inserts.push(new_row_values);
4395 affected += 1;
4396 }
4397 }
4398 }
4399 let _ = source_arity; let table = self
4403 .active_catalog_mut()
4404 .get_mut(&stmt.target)
4405 .ok_or_else(|| {
4406 EngineError::Storage(StorageError::TableNotFound {
4407 name: stmt.target.clone(),
4408 })
4409 })?;
4410 for (idx, new_vals) in &updates {
4414 table
4415 .update_row(*idx, new_vals.clone())
4416 .map_err(EngineError::Storage)?;
4417 }
4418 if !delete_indices.is_empty() {
4419 table.delete_rows(&delete_indices);
4420 }
4421 for vals in inserts {
4422 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4423 }
4424 Ok(QueryResult::CommandOk {
4425 affected,
4426 modified_catalog: affected > 0,
4427 })
4428 }
4429
4430 fn exec_delete_cancel(
4431 &mut self,
4432 stmt: &spg_sql::ast::DeleteStatement,
4433 cancel: CancelToken<'_>,
4434 ) -> Result<QueryResult, EngineError> {
4435 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4439 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4440 let trigger_session_cfg: Option<String> = self
4441 .session_params
4442 .get("default_text_search_config")
4443 .cloned();
4444 let mut cold_shadow_count: usize = 0;
4452 if let Some(w) = &stmt.where_ {
4453 let schema_cols = self
4454 .active_catalog()
4455 .get(&stmt.table)
4456 .ok_or_else(|| {
4457 EngineError::Storage(StorageError::TableNotFound {
4458 name: stmt.table.clone(),
4459 })
4460 })?
4461 .schema()
4462 .columns
4463 .clone();
4464 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4465 && let Some(idx_name) = self
4466 .active_catalog()
4467 .get(&stmt.table)
4468 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4469 {
4470 cold_shadow_count = self
4471 .active_catalog_mut()
4472 .shadow_cold_row(&stmt.table, &idx_name, &key)
4473 .unwrap_or(0);
4474 }
4475 }
4476
4477 let ts_cfg: Option<String> = self
4483 .session_param("default_text_search_config")
4484 .map(String::from);
4485 let table = self
4486 .active_catalog_mut()
4487 .get_mut(&stmt.table)
4488 .ok_or_else(|| {
4489 EngineError::Storage(StorageError::TableNotFound {
4490 name: stmt.table.clone(),
4491 })
4492 })?;
4493 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4494 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4495 .with_default_text_search_config(ts_cfg.as_deref());
4496 let mut positions: Vec<usize> = Vec::new();
4497 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4501 for (i, row) in table.rows().iter().enumerate() {
4502 if i.is_multiple_of(256) {
4503 cancel.check()?;
4504 }
4505 let keep = if let Some(w) = &stmt.where_ {
4506 let cond = eval::eval_expr(w, row, &ctx)?;
4507 !matches!(cond, Value::Bool(true))
4508 } else {
4509 false
4510 };
4511 if !keep {
4512 positions.push(i);
4513 to_delete_rows.push(row.values.clone());
4514 }
4515 }
4516 let _ = table;
4523 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4531 if !before_delete_triggers.is_empty() {
4532 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4533 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4534 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4535 let old_row = Row::new(old_vals.clone());
4536 let mut cancel_this = false;
4537 for fd in &before_delete_triggers {
4538 let (outcome, deferred) = triggers::fire_row_trigger(
4539 fd,
4540 None,
4541 Some(&old_row),
4542 &stmt.table,
4543 &schema_cols,
4544 &[],
4545 trigger_session_cfg.as_deref(),
4546 false,
4547 )
4548 .map_err(|e| {
4549 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4550 })?;
4551 deferred_embedded.extend(deferred);
4552 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4553 cancel_this = true;
4554 break;
4555 }
4556 }
4557 if !cancel_this {
4558 filtered_positions.push(*pos);
4559 filtered_old_rows.push(old_vals.clone());
4560 }
4561 }
4562 positions = filtered_positions;
4563 to_delete_rows = filtered_old_rows;
4564 }
4565 let cascade_plan = plan_fk_parent_deletions(
4566 self.active_catalog(),
4567 &stmt.table,
4568 &positions,
4569 &to_delete_rows,
4570 )?;
4571 for step in &cascade_plan {
4578 apply_fk_child_step(self.active_catalog_mut(), step)?;
4579 }
4580 let table = self
4582 .active_catalog_mut()
4583 .get_mut(&stmt.table)
4584 .ok_or_else(|| {
4585 EngineError::Storage(StorageError::TableNotFound {
4586 name: stmt.table.clone(),
4587 })
4588 })?;
4589 let affected = table.delete_rows(&positions) + cold_shadow_count;
4590 let _ = table;
4591 if !after_delete_triggers.is_empty() {
4596 for old_vals in &to_delete_rows {
4597 let old_row = Row::new(old_vals.clone());
4598 for fd in &after_delete_triggers {
4599 let (_outcome, deferred) = triggers::fire_row_trigger(
4600 fd,
4601 None,
4602 Some(&old_row),
4603 &stmt.table,
4604 &schema_cols,
4605 &[],
4606 trigger_session_cfg.as_deref(),
4607 true,
4608 )
4609 .map_err(|e| {
4610 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4611 })?;
4612 deferred_embedded.extend(deferred);
4613 }
4614 }
4615 }
4616 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4618 if !self.in_transaction() && affected > 0 {
4620 self.statistics
4621 .record_modifications(&stmt.table, affected as u64);
4622 }
4623 if let Some(items) = &stmt.returning {
4629 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4630 }
4631 Ok(QueryResult::CommandOk {
4632 affected,
4633 modified_catalog: !self.in_transaction(),
4634 })
4635 }
4636
4637 #[allow(clippy::format_push_string)]
4647 fn exec_explain(
4648 &self,
4649 e: &spg_sql::ast::ExplainStatement,
4650 cancel: CancelToken<'_>,
4651 ) -> Result<QueryResult, EngineError> {
4652 let mut lines = Vec::<String>::new();
4653 explain_select(&e.inner, self, 0, &mut lines);
4654 if e.suggest {
4655 let suggestions = build_index_suggestions(&e.inner, self);
4664 for s in suggestions {
4665 lines.push(s);
4666 }
4667 } else if e.analyze {
4668 let started = self.clock.map(|f| f());
4685 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4686 let elapsed_micros = match (self.clock, started) {
4687 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4688 _ => None,
4689 };
4690 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4691 rows.len()
4692 } else {
4693 0
4694 };
4695 annotate_explain_lines(&mut lines, row_count, self);
4696 let mut total = alloc::format!("Total: rows={row_count}");
4697 if let Some(us) = elapsed_micros {
4698 total.push_str(&alloc::format!(" elapsed={us}us"));
4699 }
4700 lines.push(total);
4701 }
4702 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4703 let rows: Vec<Row> = lines
4704 .into_iter()
4705 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4706 .collect();
4707 Ok(QueryResult::Rows { columns, rows })
4708 }
4709
4710 fn exec_show_tables(&self) -> QueryResult {
4711 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4712 let rows: Vec<Row> = self
4713 .active_catalog()
4714 .table_names()
4715 .into_iter()
4716 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4717 .collect();
4718 QueryResult::Rows { columns, rows }
4719 }
4720
4721 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4726 let t = self.active_catalog().get(name).ok_or_else(|| {
4727 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4728 })?;
4729 let cols: Vec<String> = t
4730 .schema()
4731 .columns
4732 .iter()
4733 .map(|c| {
4734 let ty = render_data_type(c.ty);
4735 let nullable = if c.nullable { "" } else { " NOT NULL" };
4736 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4737 })
4738 .collect();
4739 let mut body = cols.join(",\n");
4740 for uc in &t.schema().uniqueness_constraints {
4742 let col_names: Vec<String> = uc
4743 .columns
4744 .iter()
4745 .map(|&p| {
4746 t.schema().columns.get(p).map_or_else(
4747 || alloc::format!("col{p}"),
4748 |c| alloc::format!("`{}`", c.name),
4749 )
4750 })
4751 .collect();
4752 let kw = if uc.is_primary_key {
4753 "PRIMARY KEY"
4754 } else {
4755 "UNIQUE KEY"
4756 };
4757 body.push_str(",\n ");
4758 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4759 }
4760 for fk in &t.schema().foreign_keys {
4762 let local: Vec<String> = fk
4763 .local_columns
4764 .iter()
4765 .map(|&p| {
4766 t.schema().columns.get(p).map_or_else(
4767 || alloc::format!("col{p}"),
4768 |c| alloc::format!("`{}`", c.name),
4769 )
4770 })
4771 .collect();
4772 let parent_cols: Vec<String> =
4773 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4774 fk.parent_columns
4775 .iter()
4776 .map(|&p| {
4777 parent.schema().columns.get(p).map_or_else(
4778 || alloc::format!("col{p}"),
4779 |c| alloc::format!("`{}`", c.name),
4780 )
4781 })
4782 .collect()
4783 } else {
4784 fk.parent_columns
4785 .iter()
4786 .map(|p| alloc::format!("col{p}"))
4787 .collect()
4788 };
4789 body.push_str(",\n ");
4790 body.push_str(&alloc::format!(
4791 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
4792 local.join(", "),
4793 fk.parent_table,
4794 parent_cols.join(", ")
4795 ));
4796 }
4797 let ddl = alloc::format!(
4798 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
4799 name,
4800 body
4801 );
4802 let columns = alloc::vec![
4803 ColumnSchema::new("Table", DataType::Text, false),
4804 ColumnSchema::new("Create Table", DataType::Text, false),
4805 ];
4806 let rows = alloc::vec![Row::new(alloc::vec![
4807 Value::Text(name.into()),
4808 Value::Text(ddl),
4809 ])];
4810 Ok(QueryResult::Rows { columns, rows })
4811 }
4812
4813 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
4819 let t = self.active_catalog().get(name).ok_or_else(|| {
4820 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4821 })?;
4822 let columns = alloc::vec![
4823 ColumnSchema::new("Table", DataType::Text, false),
4824 ColumnSchema::new("Non_unique", DataType::Int, false),
4825 ColumnSchema::new("Key_name", DataType::Text, false),
4826 ColumnSchema::new("Seq_in_index", DataType::Int, false),
4827 ColumnSchema::new("Column_name", DataType::Text, false),
4828 ColumnSchema::new("Null", DataType::Text, false),
4829 ColumnSchema::new("Index_type", DataType::Text, false),
4830 ];
4831 let mut rows: Vec<Row> = Vec::new();
4832 for idx in t.indices() {
4833 let col = t
4834 .schema()
4835 .columns
4836 .get(idx.column_position)
4837 .map_or("?".into(), |c| c.name.clone());
4838 let nullable = t
4839 .schema()
4840 .columns
4841 .get(idx.column_position)
4842 .map_or(true, |c| c.nullable);
4843 rows.push(Row::new(alloc::vec![
4844 Value::Text(name.into()),
4845 Value::Int(i32::from(!idx.is_unique)),
4846 Value::Text(idx.name.clone()),
4847 Value::Int(1),
4848 Value::Text(col),
4849 Value::Text(if nullable {
4850 "YES".into()
4851 } else {
4852 String::new()
4853 }),
4854 Value::Text("BTREE".into()),
4855 ]));
4856 }
4857 Ok(QueryResult::Rows { columns, rows })
4858 }
4859
4860 fn exec_show_status(&self) -> QueryResult {
4864 let columns = alloc::vec![
4865 ColumnSchema::new("Variable_name", DataType::Text, false),
4866 ColumnSchema::new("Value", DataType::Text, false),
4867 ];
4868 let pairs: &[(&str, &str)] = &[
4869 ("Uptime", "0"),
4870 ("Threads_connected", "1"),
4871 ("Threads_running", "1"),
4872 ("Questions", "0"),
4873 ("Slow_queries", "0"),
4874 ("Opened_tables", "0"),
4875 ("Innodb_buffer_pool_pages_total", "0"),
4876 ];
4877 let rows: Vec<Row> = pairs
4878 .iter()
4879 .map(|(k, v)| {
4880 Row::new(alloc::vec![
4881 Value::Text((*k).into()),
4882 Value::Text((*v).into())
4883 ])
4884 })
4885 .collect();
4886 QueryResult::Rows { columns, rows }
4887 }
4888
4889 fn exec_show_variables(&self) -> QueryResult {
4892 let columns = alloc::vec![
4893 ColumnSchema::new("Variable_name", DataType::Text, false),
4894 ColumnSchema::new("Value", DataType::Text, false),
4895 ];
4896 let mut rows: Vec<Row> = Vec::new();
4897 let canonical: &[(&str, &str)] = &[
4898 ("version", "8.0.35-spg"),
4899 ("version_comment", "SPG dual-stack engine"),
4900 ("character_set_server", "utf8mb4"),
4901 ("collation_server", "utf8mb4_0900_ai_ci"),
4902 ("max_allowed_packet", "67108864"),
4903 ("autocommit", "ON"),
4904 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
4905 ("time_zone", "SYSTEM"),
4906 ("transaction_isolation", "REPEATABLE-READ"),
4907 ];
4908 for &(k, v) in canonical {
4909 rows.push(Row::new(alloc::vec![
4910 Value::Text(k.into()),
4911 Value::Text(v.into()),
4912 ]));
4913 }
4914 for (k, v) in &self.session_params {
4916 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
4917 rows.push(Row::new(alloc::vec![
4918 Value::Text(k.clone()),
4919 Value::Text(v.clone()),
4920 ]));
4921 }
4922 }
4923 QueryResult::Rows { columns, rows }
4924 }
4925
4926 fn exec_show_processlist(&self) -> QueryResult {
4931 let columns = alloc::vec![
4932 ColumnSchema::new("Id", DataType::Int, false),
4933 ColumnSchema::new("User", DataType::Text, false),
4934 ColumnSchema::new("Host", DataType::Text, false),
4935 ColumnSchema::new("db", DataType::Text, true),
4936 ColumnSchema::new("Command", DataType::Text, false),
4937 ColumnSchema::new("Time", DataType::Int, false),
4938 ColumnSchema::new("State", DataType::Text, true),
4939 ColumnSchema::new("Info", DataType::Text, true),
4940 ];
4941 let rows = alloc::vec![Row::new(alloc::vec![
4942 Value::Int(1),
4943 Value::Text("postgres".into()),
4944 Value::Text("localhost".into()),
4945 Value::Text("postgres".into()),
4946 Value::Text("Query".into()),
4947 Value::Int(0),
4948 Value::Text("executing".into()),
4949 Value::Text("SHOW PROCESSLIST".into()),
4950 ])];
4951 QueryResult::Rows { columns, rows }
4952 }
4953
4954 fn exec_show_databases(&self) -> QueryResult {
4961 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
4962 let names = [
4963 "information_schema",
4964 "mysql",
4965 "performance_schema",
4966 "sys",
4967 "postgres",
4968 ];
4969 let rows: Vec<Row> = names
4970 .iter()
4971 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
4972 .collect();
4973 QueryResult::Rows { columns, rows }
4974 }
4975
4976 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
4979 let table =
4980 self.active_catalog()
4981 .get(table_name)
4982 .ok_or_else(|| StorageError::TableNotFound {
4983 name: table_name.into(),
4984 })?;
4985 let columns = alloc::vec![
4986 ColumnSchema::new("name", DataType::Text, false),
4987 ColumnSchema::new("type", DataType::Text, false),
4988 ColumnSchema::new("nullable", DataType::Bool, false),
4989 ];
4990 let rows: Vec<Row> = table
4991 .schema()
4992 .columns
4993 .iter()
4994 .map(|c| {
4995 Row::new(alloc::vec![
4996 Value::Text(c.name.clone()),
4997 Value::Text(alloc::format!("{}", c.ty)),
4998 Value::Bool(c.nullable),
4999 ])
5000 })
5001 .collect();
5002 Ok(QueryResult::Rows { columns, rows })
5003 }
5004
5005 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5006 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5007 if self.tx_catalogs.contains_key(&tx_id) {
5008 return Err(EngineError::TransactionAlreadyOpen);
5009 }
5010 self.tx_catalogs.insert(
5011 tx_id,
5012 TxState {
5013 catalog: self.catalog.clone(),
5014 savepoints: Vec::new(),
5015 },
5016 );
5017 Ok(QueryResult::CommandOk {
5018 affected: 0,
5019 modified_catalog: false,
5020 })
5021 }
5022
5023 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5024 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5025 let state = self
5026 .tx_catalogs
5027 .remove(&tx_id)
5028 .ok_or(EngineError::NoActiveTransaction)?;
5029 self.catalog = state.catalog;
5030 Ok(QueryResult::CommandOk {
5034 affected: 0,
5035 modified_catalog: true,
5036 })
5037 }
5038
5039 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5040 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5041 if self.tx_catalogs.remove(&tx_id).is_none() {
5042 return Err(EngineError::NoActiveTransaction);
5043 }
5044 Ok(QueryResult::CommandOk {
5046 affected: 0,
5047 modified_catalog: false,
5048 })
5049 }
5050
5051 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5052 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5053 let state = self
5054 .tx_catalogs
5055 .get_mut(&tx_id)
5056 .ok_or(EngineError::NoActiveTransaction)?;
5057 state.savepoints.retain(|(n, _)| n != &name);
5061 let snapshot = state.catalog.clone();
5062 state.savepoints.push((name, snapshot));
5063 Ok(QueryResult::CommandOk {
5064 affected: 0,
5065 modified_catalog: false,
5066 })
5067 }
5068
5069 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5070 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5071 let state = self
5072 .tx_catalogs
5073 .get_mut(&tx_id)
5074 .ok_or(EngineError::NoActiveTransaction)?;
5075 let pos = state
5076 .savepoints
5077 .iter()
5078 .rposition(|(n, _)| n == name)
5079 .ok_or_else(|| {
5080 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5081 })?;
5082 let snapshot = state.savepoints[pos].1.clone();
5086 state.savepoints.truncate(pos + 1);
5087 state.catalog = snapshot;
5088 Ok(QueryResult::CommandOk {
5089 affected: 0,
5090 modified_catalog: false,
5091 })
5092 }
5093
5094 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5095 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5096 let state = self
5097 .tx_catalogs
5098 .get_mut(&tx_id)
5099 .ok_or(EngineError::NoActiveTransaction)?;
5100 let pos = state
5101 .savepoints
5102 .iter()
5103 .rposition(|(n, _)| n == name)
5104 .ok_or_else(|| {
5105 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5106 })?;
5107 state.savepoints.truncate(pos);
5110 Ok(QueryResult::CommandOk {
5111 affected: 0,
5112 modified_catalog: false,
5113 })
5114 }
5115
5116 fn exec_alter_table(
5127 &mut self,
5128 s: spg_sql::ast::AlterTableStatement,
5129 ) -> Result<QueryResult, EngineError> {
5130 let table_name = s.name.clone();
5135 for target in s.targets {
5136 self.exec_alter_table_subaction(&table_name, target)?;
5137 }
5138 Ok(QueryResult::CommandOk {
5139 affected: 0,
5140 modified_catalog: !self.in_transaction(),
5141 })
5142 }
5143
5144 fn exec_alter_table_subaction(
5145 &mut self,
5146 table_name_outer: &str,
5147 target: spg_sql::ast::AlterTableTarget,
5148 ) -> Result<(), EngineError> {
5149 struct S<'a> {
5152 name: &'a str,
5153 }
5154 let s = S {
5155 name: table_name_outer,
5156 };
5157 match target {
5158 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5159 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5160 EngineError::Storage(StorageError::TableNotFound {
5161 name: s.name.into(),
5162 })
5163 })?;
5164 table.schema_mut().hot_tier_bytes = Some(n);
5165 }
5166 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5167 let cols_snapshot = self
5172 .active_catalog()
5173 .get(s.name)
5174 .ok_or_else(|| {
5175 EngineError::Storage(StorageError::TableNotFound {
5176 name: s.name.into(),
5177 })
5178 })?
5179 .schema()
5180 .columns
5181 .clone();
5182 let storage_fk =
5183 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5184 let existing_rows: Vec<Vec<Value>> = self
5187 .active_catalog()
5188 .get(s.name)
5189 .expect("checked above")
5190 .rows()
5191 .iter()
5192 .map(|r| r.values.clone())
5193 .collect();
5194 enforce_fk_inserts(
5195 self.active_catalog(),
5196 s.name,
5197 core::slice::from_ref(&storage_fk),
5198 &existing_rows,
5199 )?;
5200 let table = self
5202 .active_catalog_mut()
5203 .get_mut(s.name)
5204 .expect("checked above");
5205 if let Some(name) = &storage_fk.name
5206 && table
5207 .schema()
5208 .foreign_keys
5209 .iter()
5210 .any(|f| f.name.as_ref() == Some(name))
5211 {
5212 return Err(EngineError::Unsupported(alloc::format!(
5213 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5214 )));
5215 }
5216 table.schema_mut().foreign_keys.push(storage_fk);
5217 }
5218 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5219 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5220 EngineError::Storage(StorageError::TableNotFound {
5221 name: s.name.into(),
5222 })
5223 })?;
5224 let fks = &mut table.schema_mut().foreign_keys;
5225 let before = fks.len();
5226 fks.retain(|f| f.name.as_ref() != Some(&name));
5227 if fks.len() == before && !if_exists {
5228 return Err(EngineError::Unsupported(alloc::format!(
5229 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5230 s.name
5231 )));
5232 }
5233 }
5235 spg_sql::ast::AlterTableTarget::AddColumn {
5236 column,
5237 if_not_exists,
5238 } => {
5239 let clock = self.clock;
5244 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5245 EngineError::Storage(StorageError::TableNotFound {
5246 name: s.name.into(),
5247 })
5248 })?;
5249 if table
5250 .schema()
5251 .columns
5252 .iter()
5253 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5254 {
5255 if if_not_exists {
5256 return Ok(());
5257 }
5258 return Err(EngineError::Unsupported(alloc::format!(
5259 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5260 column.name,
5261 s.name
5262 )));
5263 }
5264 let col_name = column.name.clone();
5265 let nullable = column.nullable;
5266 let has_default = column.default.is_some() || column.auto_increment;
5267 let col_schema = column_def_to_schema(column)?;
5268 let row_count = table.row_count();
5269 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5276 resolve_column_default_free(&col_schema, clock)?
5277 } else if nullable || row_count == 0 {
5278 Value::Null
5279 } else {
5280 return Err(EngineError::Unsupported(alloc::format!(
5281 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5282 when the table has existing rows"
5283 )));
5284 };
5285 table.add_column(col_schema, fill_value);
5286 }
5287 spg_sql::ast::AlterTableTarget::AlterColumnType {
5288 column,
5289 new_type,
5290 using,
5291 } => {
5292 let new_data_type = column_type_to_data_type(new_type);
5298 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5299 EngineError::Storage(StorageError::TableNotFound {
5300 name: s.name.into(),
5301 })
5302 })?;
5303 let col_pos = table
5304 .schema()
5305 .columns
5306 .iter()
5307 .position(|c| c.name.eq_ignore_ascii_case(&column))
5308 .ok_or_else(|| {
5309 EngineError::Unsupported(alloc::format!(
5310 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5311 s.name
5312 ))
5313 })?;
5314 let schema_cols = table.schema().columns.clone();
5315 let ctx = eval::EvalContext::new(&schema_cols, None);
5316 let mut new_values: alloc::vec::Vec<Value> =
5317 alloc::vec::Vec::with_capacity(table.row_count());
5318 for row in table.rows().iter() {
5319 let raw = match &using {
5320 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5321 EngineError::Unsupported(alloc::format!(
5322 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5323 ))
5324 })?,
5325 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5326 };
5327 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5328 new_values.push(coerced);
5329 }
5330 table.schema_mut().columns[col_pos].ty = new_data_type;
5331 for (i, v) in new_values.into_iter().enumerate() {
5332 let mut row_values = table
5333 .rows()
5334 .get(i)
5335 .expect("bounds-checked above")
5336 .values
5337 .clone();
5338 row_values[col_pos] = v;
5339 table.update_row(i, row_values)?;
5340 }
5341 }
5342 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5343 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5349 EngineError::Storage(StorageError::TableNotFound {
5350 name: s.name.into(),
5351 })
5352 })?;
5353 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5354 match tc {
5355 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5356 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5357 let positions: Vec<usize> = columns
5358 .iter()
5359 .map(|c| {
5360 table
5361 .schema()
5362 .columns
5363 .iter()
5364 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5365 .ok_or_else(|| {
5366 EngineError::Unsupported(alloc::format!(
5367 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5368 s.name
5369 ))
5370 })
5371 })
5372 .collect::<Result<Vec<_>, _>>()?;
5373 let already = table
5377 .schema()
5378 .uniqueness_constraints
5379 .iter()
5380 .any(|u| u.columns == positions);
5381 if !already {
5382 table.schema_mut().uniqueness_constraints.push(
5383 spg_storage::UniquenessConstraint {
5384 is_primary_key: is_pk,
5385 columns: positions.clone(),
5386 nulls_not_distinct: false,
5387 },
5388 );
5389 if is_pk {
5391 for p in &positions {
5392 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5393 c.nullable = false;
5394 }
5395 }
5396 }
5397 let leading = &columns[0];
5400 let already_idx = table.indices().iter().any(|idx| {
5401 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5402 && table.schema().columns[idx.column_position].name == *leading
5403 });
5404 if !already_idx {
5405 let suffix = if is_pk { "pkey" } else { "key" };
5406 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5407 let _ = table.add_index(idx_name, leading);
5408 }
5409 }
5410 }
5411 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5412 table.schema_mut().checks.push(alloc::format!("{expr}"));
5413 }
5414 spg_sql::ast::TableConstraint::Index { name, columns } => {
5415 let leading = &columns[0];
5421 let already_idx = table.indices().iter().any(|idx| {
5422 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5423 && table.schema().columns[idx.column_position].name == *leading
5424 });
5425 if !already_idx {
5426 let idx_name = name
5427 .clone()
5428 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5429 let _ = table.add_index(idx_name, leading);
5430 }
5431 }
5432 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5433 for (k, col) in columns.iter().enumerate() {
5441 let already_idx = table.indices().iter().any(|idx| {
5442 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5443 && table.schema().columns[idx.column_position].name == *col
5444 });
5445 if already_idx {
5446 continue;
5447 }
5448 let idx_name = match (&name, columns.len(), k) {
5449 (Some(n), 1, _) => n.clone(),
5450 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5451 (None, _, _) => {
5452 alloc::format!("{}_{col}_ftidx", s.name)
5453 }
5454 };
5455 let _ = table.add_gin_fulltext_index(idx_name, col);
5456 }
5457 }
5458 }
5459 }
5460 spg_sql::ast::AlterTableTarget::DropColumn {
5461 column,
5462 if_exists,
5463 cascade,
5464 } => {
5465 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5472 EngineError::Storage(StorageError::TableNotFound {
5473 name: s.name.into(),
5474 })
5475 })?;
5476 let col_pos = match table
5477 .schema()
5478 .columns
5479 .iter()
5480 .position(|c| c.name.eq_ignore_ascii_case(&column))
5481 {
5482 Some(p) => p,
5483 None => {
5484 if if_exists {
5485 return Ok(());
5486 }
5487 return Err(EngineError::Unsupported(alloc::format!(
5488 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5489 s.name
5490 )));
5491 }
5492 };
5493 let dependent_fks: Vec<usize> = table
5496 .schema()
5497 .foreign_keys
5498 .iter()
5499 .enumerate()
5500 .filter_map(|(i, fk)| {
5501 if fk.local_columns.contains(&col_pos) {
5502 Some(i)
5503 } else {
5504 None
5505 }
5506 })
5507 .collect();
5508 if !dependent_fks.is_empty() && !cascade {
5509 return Err(EngineError::Unsupported(alloc::format!(
5510 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5511 use DROP COLUMN ... CASCADE to remove them"
5512 )));
5513 }
5514 if cascade {
5516 let mut sorted = dependent_fks.clone();
5518 sorted.sort();
5519 sorted.reverse();
5520 let fks = &mut table.schema_mut().foreign_keys;
5521 for i in sorted {
5522 fks.remove(i);
5523 }
5524 }
5525 table.drop_column(col_pos);
5528 }
5529 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5530 let table_name = s.name.to_string();
5538 let trigs = self.active_catalog_mut().triggers_mut();
5539 let mut touched = false;
5540 for t in trigs.iter_mut() {
5541 if !t.table.eq_ignore_ascii_case(&table_name) {
5542 continue;
5543 }
5544 match &which {
5545 spg_sql::ast::TriggerSelector::All => {
5546 t.enabled = enabled;
5547 touched = true;
5548 }
5549 spg_sql::ast::TriggerSelector::Named(name) => {
5550 if t.name.eq_ignore_ascii_case(name) {
5551 t.enabled = enabled;
5552 touched = true;
5553 }
5554 }
5555 }
5556 }
5557 if !touched {
5563 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5564 return Err(EngineError::Unsupported(alloc::format!(
5565 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5566 if enabled { "ENABLE" } else { "DISABLE" },
5567 )));
5568 }
5569 }
5570 }
5571 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5572 let old = s.name.to_string();
5579 self.active_catalog_mut()
5580 .rename_table(&old, &new)
5581 .map_err(EngineError::Storage)?;
5582 }
5583 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5584 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5598 EngineError::Storage(StorageError::TableNotFound {
5599 name: s.name.into(),
5600 })
5601 })?;
5602 let col_pos = table
5603 .schema()
5604 .columns
5605 .iter()
5606 .position(|c| c.name.eq_ignore_ascii_case(&old))
5607 .ok_or_else(|| {
5608 EngineError::Unsupported(alloc::format!(
5609 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5610 s.name
5611 ))
5612 })?;
5613 if table
5615 .schema()
5616 .columns
5617 .iter()
5618 .enumerate()
5619 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5620 {
5621 return Err(EngineError::Unsupported(alloc::format!(
5622 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5623 s.name
5624 )));
5625 }
5626 if old.eq_ignore_ascii_case(&new) {
5630 return Ok(());
5631 }
5632 table.rename_column(col_pos, &new);
5633 let n_cols = table.schema().columns.len();
5639 for i in 0..n_cols {
5640 let rt = table.schema().columns[i].runtime_default.clone();
5641 if let Some(src) = rt {
5642 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5643 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5644 }
5645 }
5646 let checks = table.schema().checks.clone();
5648 let mut new_checks = Vec::with_capacity(checks.len());
5649 for chk in checks {
5650 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5651 }
5652 table.schema_mut().checks = new_checks;
5653 let n_idx = table.indices().len();
5655 for i in 0..n_idx {
5656 let pred = table.indices()[i].partial_predicate.clone();
5657 if let Some(src) = pred {
5658 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5659 table.set_partial_predicate(i, Some(rewritten));
5663 }
5664 }
5665 let table_name = s.name.to_string();
5668 for trig in self.active_catalog_mut().triggers_mut() {
5669 if !trig.table.eq_ignore_ascii_case(&table_name) {
5670 continue;
5671 }
5672 for c in &mut trig.update_columns {
5673 if c.eq_ignore_ascii_case(&old) {
5674 *c = new.clone();
5675 }
5676 }
5677 }
5678 }
5679 }
5680 Ok(())
5681 }
5682
5683 fn exec_alter_index(
5684 &mut self,
5685 stmt: spg_sql::ast::AlterIndexStatement,
5686 ) -> Result<QueryResult, EngineError> {
5687 let spg_sql::ast::AlterIndexStatement {
5691 name: idx_name,
5692 target,
5693 } = stmt;
5694 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5698 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5699 return match renamed {
5700 Ok(()) => Ok(QueryResult::CommandOk {
5701 affected: 0,
5702 modified_catalog: !self.in_transaction(),
5703 }),
5704 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5705 Ok(QueryResult::CommandOk {
5706 affected: 0,
5707 modified_catalog: false,
5708 })
5709 }
5710 Err(e) => Err(EngineError::Storage(e)),
5711 };
5712 }
5713 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5714 unreachable!("Rename branch returned above");
5715 };
5716 let target = encoding.map(|e| match e {
5717 SqlVecEncoding::F32 => VecEncoding::F32,
5718 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5719 SqlVecEncoding::F16 => VecEncoding::F16,
5720 });
5721 let table_name = {
5726 let cat = self.active_catalog();
5727 let mut found: Option<String> = None;
5728 for tname in cat.table_names() {
5729 if let Some(t) = cat.get(&tname)
5730 && t.indices().iter().any(|i| i.name == idx_name)
5731 {
5732 found = Some(tname);
5733 break;
5734 }
5735 }
5736 found.ok_or_else(|| {
5737 EngineError::Storage(StorageError::IndexNotFound {
5738 name: idx_name.clone(),
5739 })
5740 })?
5741 };
5742 let table = self
5743 .active_catalog_mut()
5744 .get_mut(&table_name)
5745 .expect("table found above");
5746 table.rebuild_nsw_index(&idx_name, target)?;
5747 self.plan_cache.evict_referencing(&table_name);
5750 Ok(QueryResult::CommandOk {
5751 affected: 0,
5752 modified_catalog: !self.in_transaction(),
5753 })
5754 }
5755
5756 fn exec_create_index(
5757 &mut self,
5758 stmt: CreateIndexStatement,
5759 ) -> Result<QueryResult, EngineError> {
5760 let table = self
5761 .active_catalog_mut()
5762 .get_mut(&stmt.table)
5763 .ok_or_else(|| {
5764 EngineError::Storage(StorageError::TableNotFound {
5765 name: stmt.table.clone(),
5766 })
5767 })?;
5768 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
5770 return Ok(QueryResult::CommandOk {
5771 affected: 0,
5772 modified_catalog: false,
5773 });
5774 }
5775 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
5782 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
5786 Vec::new()
5787 } else {
5788 let schema = table.schema();
5789 stmt.included_columns
5790 .iter()
5791 .map(|c| {
5792 schema.column_position(c).ok_or_else(|| {
5793 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
5794 })
5795 })
5796 .collect::<Result<Vec<_>, _>>()?
5797 };
5798 match stmt.method {
5799 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
5800 IndexMethod::Hnsw => {
5801 if !included_positions.is_empty() {
5802 return Err(EngineError::Unsupported(
5803 "INCLUDE columns are not supported on HNSW indexes".into(),
5804 ));
5805 }
5806 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
5807 }
5808 IndexMethod::Brin => {
5810 if !included_positions.is_empty() {
5811 return Err(EngineError::Unsupported(
5812 "INCLUDE columns are not supported on BRIN indexes".into(),
5813 ));
5814 }
5815 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
5816 }
5817 IndexMethod::Gin => {
5825 if !included_positions.is_empty() {
5826 return Err(EngineError::Unsupported(
5827 "INCLUDE columns are not supported on GIN indexes".into(),
5828 ));
5829 }
5830 let col_pos = table
5831 .schema()
5832 .column_position(&stmt.column)
5833 .ok_or_else(|| {
5834 EngineError::Storage(StorageError::ColumnNotFound {
5835 column: stmt.column.clone(),
5836 })
5837 })?;
5838 let col_ty = table.schema().columns[col_pos].ty;
5839 let is_trgm = stmt
5845 .opclass
5846 .as_deref()
5847 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
5848 if is_trgm
5849 && matches!(
5850 col_ty,
5851 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
5852 )
5853 {
5854 table
5855 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
5856 .map_err(EngineError::Storage)?;
5857 } else if col_ty == spg_storage::DataType::TsVector {
5858 table
5859 .add_gin_index(stmt.name.clone(), &stmt.column)
5860 .map_err(EngineError::Storage)?;
5861 } else {
5862 table.add_index(stmt.name.clone(), &stmt.column)?;
5868 }
5869 }
5870 }
5871 if !included_positions.is_empty()
5872 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
5873 {
5874 idx.included_columns = included_positions;
5875 }
5876 if let Some(pred_expr) = &stmt.partial_predicate {
5884 let canonical = pred_expr.to_string();
5885 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
5897 idx.partial_predicate = Some(canonical);
5898 }
5899 }
5900 if let Some(key_expr) = &stmt.expression {
5908 if matches!(
5909 stmt.method,
5910 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
5911 ) {
5912 return Err(EngineError::Unsupported(
5913 "Expression keys are not supported on HNSW or BRIN indexes".into(),
5914 ));
5915 }
5916 let canonical = key_expr.to_string();
5917 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
5918 idx.expression = Some(canonical);
5919 }
5920 }
5921 if stmt.is_unique {
5930 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
5931 for col_name in &stmt.extra_columns {
5932 let pos = table
5933 .schema()
5934 .columns
5935 .iter()
5936 .position(|c| c.name.eq_ignore_ascii_case(col_name))
5937 .ok_or_else(|| {
5938 EngineError::Unsupported(alloc::format!(
5939 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
5940 stmt.name,
5941 stmt.table
5942 ))
5943 })?;
5944 extra_positions.push(pos);
5945 }
5946 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
5947 idx.is_unique = true;
5948 idx.extra_column_positions = extra_positions;
5949 }
5950 let snapshot_indices = table.indices().to_vec();
5955 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
5956 table.rows().iter().cloned().collect();
5957 let snapshot_schema = table.schema().clone();
5958 let idx_ref = snapshot_indices
5959 .iter()
5960 .find(|i| i.name == stmt.name)
5961 .expect("just-added index");
5962 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
5963 }
5964 self.plan_cache.evict_referencing(&table_name);
5967 Ok(QueryResult::CommandOk {
5968 affected: 0,
5969 modified_catalog: !self.in_transaction(),
5970 })
5971 }
5972
5973 fn reconcile_table_if_not_exists(
5982 &mut self,
5983 stmt: CreateTableStatement,
5984 ) -> Result<QueryResult, EngineError> {
5985 let table_name = stmt.name.clone();
5986 let clock = self.clock;
5987 let existing_col_names: alloc::collections::BTreeSet<String> = self
5988 .active_catalog()
5989 .get(&table_name)
5990 .expect("checked above")
5991 .schema()
5992 .columns
5993 .iter()
5994 .map(|c| c.name.to_ascii_lowercase())
5995 .collect();
5996 let row_count = self
5997 .active_catalog()
5998 .get(&table_name)
5999 .expect("checked above")
6000 .row_count();
6001 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6003 .columns
6004 .iter()
6005 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6006 .cloned()
6007 .collect();
6008 for col_def in new_columns {
6009 let col_name = col_def.name.clone();
6010 let nullable = col_def.nullable;
6011 let has_default = col_def.default.is_some() || col_def.auto_increment;
6012 let col_schema = column_def_to_schema(col_def)?;
6013 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6014 resolve_column_default_free(&col_schema, clock)?
6015 } else if nullable || row_count == 0 {
6016 Value::Null
6017 } else {
6018 return Err(EngineError::Unsupported(alloc::format!(
6019 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6020 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6021 )));
6022 };
6023 let table = self
6024 .active_catalog_mut()
6025 .get_mut(&table_name)
6026 .expect("checked above");
6027 table.add_column(col_schema, fill_value);
6028 }
6029 let table_cols_now = self
6033 .active_catalog()
6034 .get(&table_name)
6035 .expect("checked above")
6036 .schema()
6037 .columns
6038 .clone();
6039 for fk in stmt.foreign_keys {
6040 let all_resolved = fk.columns.iter().all(|c| {
6044 table_cols_now
6045 .iter()
6046 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6047 });
6048 if !all_resolved {
6049 continue;
6050 }
6051 let already_present = {
6052 let table = self
6053 .active_catalog()
6054 .get(&table_name)
6055 .expect("checked above");
6056 table.schema().foreign_keys.iter().any(|f| {
6057 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6058 && f.local_columns.len() == fk.columns.len()
6059 })
6060 };
6061 if already_present {
6062 continue;
6063 }
6064 let storage_fk =
6065 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6066 let table = self
6067 .active_catalog_mut()
6068 .get_mut(&table_name)
6069 .expect("checked above");
6070 table.schema_mut().foreign_keys.push(storage_fk);
6071 }
6072 Ok(QueryResult::CommandOk {
6073 affected: 0,
6074 modified_catalog: !self.in_transaction(),
6075 })
6076 }
6077
6078 fn exec_drop_table(
6080 &mut self,
6081 names: Vec<String>,
6082 if_exists: bool,
6083 ) -> Result<QueryResult, EngineError> {
6084 for name in names {
6085 let dropped = self.active_catalog_mut().drop_table(&name);
6086 if !dropped && !if_exists {
6087 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6088 }
6089 }
6090 Ok(QueryResult::CommandOk {
6091 affected: 0,
6092 modified_catalog: !self.in_transaction(),
6093 })
6094 }
6095
6096 fn exec_drop_index(
6098 &mut self,
6099 name: String,
6100 if_exists: bool,
6101 ) -> Result<QueryResult, EngineError> {
6102 let dropped = self.active_catalog_mut().drop_named_index(&name);
6103 if !dropped && !if_exists {
6104 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6105 }
6106 Ok(QueryResult::CommandOk {
6107 affected: 0,
6108 modified_catalog: !self.in_transaction(),
6109 })
6110 }
6111
6112 fn exec_create_table(
6113 &mut self,
6114 stmt: CreateTableStatement,
6115 ) -> Result<QueryResult, EngineError> {
6116 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6117 return Ok(QueryResult::CommandOk {
6136 affected: 0,
6137 modified_catalog: false,
6138 });
6139 }
6140 let table_name = stmt.name.clone();
6141 let inline_pk_columns: Vec<String> = stmt
6145 .columns
6146 .iter()
6147 .filter(|c| c.is_primary_key)
6148 .map(|c| c.name.clone())
6149 .collect();
6150 let cols = stmt
6156 .columns
6157 .into_iter()
6158 .map(column_def_to_schema)
6159 .collect::<Result<Vec<_>, _>>()?;
6160 let mut cols = cols;
6169 for col in cols.iter_mut() {
6170 let Some(name) = col.user_enum_type.take() else {
6171 continue;
6172 };
6173 let cat = self.active_catalog();
6174 if cat.enum_types().contains_key(&name) {
6175 col.user_enum_type = Some(name);
6176 continue;
6177 }
6178 if let Some(dom) = cat.domain_types().get(&name) {
6179 col.ty = dom.base_type;
6180 col.user_domain_type = Some(name);
6181 if !dom.nullable {
6182 col.nullable = false;
6183 }
6184 continue;
6185 }
6186 return Err(EngineError::Unsupported(alloc::format!(
6187 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6188 col.name,
6189 name
6190 )));
6191 }
6192 for tc in &stmt.table_constraints {
6193 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6194 for col_name in columns {
6195 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6196 col.nullable = false;
6197 }
6198 }
6199 }
6200 }
6201 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6208 Vec::with_capacity(stmt.foreign_keys.len());
6209 for fk in stmt.foreign_keys {
6210 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6217 if !self.foreign_key_checks
6218 && needs_parent
6219 && self.active_catalog().get(&fk.parent_table).is_none()
6220 {
6221 self.pending_foreign_keys.push((table_name.clone(), fk));
6222 continue;
6223 }
6224 fks.push(resolve_foreign_key(
6225 &table_name,
6226 &cols,
6227 fk,
6228 self.active_catalog(),
6229 )?);
6230 }
6231 let mut schema = TableSchema::new(table_name.clone(), cols);
6232 schema.foreign_keys = fks;
6233 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6237 let mut check_exprs: Vec<String> = Vec::new();
6238 for tc in &stmt.table_constraints {
6239 let (is_pk, names, nnd) = match tc {
6240 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6241 (true, columns.clone(), false)
6242 }
6243 spg_sql::ast::TableConstraint::Unique {
6244 columns,
6245 nulls_not_distinct,
6246 ..
6247 } => (false, columns.clone(), *nulls_not_distinct),
6248 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6249 check_exprs.push(alloc::format!("{expr}"));
6252 continue;
6253 }
6254 spg_sql::ast::TableConstraint::Index { .. } => continue,
6260 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6264 };
6265 let mut positions = Vec::with_capacity(names.len());
6266 for n in &names {
6267 let pos = schema
6268 .columns
6269 .iter()
6270 .position(|c| c.name == *n)
6271 .ok_or_else(|| {
6272 EngineError::Unsupported(alloc::format!(
6273 "table constraint references unknown column {n:?}"
6274 ))
6275 })?;
6276 positions.push(pos);
6277 }
6278 uc_storage.push(spg_storage::UniquenessConstraint {
6279 is_primary_key: is_pk,
6280 columns: positions,
6281 nulls_not_distinct: nnd,
6282 });
6283 }
6284 schema.uniqueness_constraints = uc_storage.clone();
6285 schema.checks = check_exprs;
6286 self.active_catalog_mut().create_table(schema)?;
6287 let table = self
6291 .active_catalog_mut()
6292 .get_mut(&table_name)
6293 .expect("just created");
6294 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6295 let idx_name = if inline_pk_columns.len() == 1 {
6296 alloc::format!("{table_name}_pkey")
6297 } else {
6298 alloc::format!("{table_name}_pkey_{i}")
6299 };
6300 if let Err(e) = table.add_index(idx_name, col_name) {
6301 return Err(EngineError::Storage(e));
6302 }
6303 }
6304 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6305 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6310 for (k, col) in columns.iter().enumerate() {
6311 let already = table.indices().iter().any(|idx| {
6312 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6313 && table.schema().columns[idx.column_position].name == *col
6314 });
6315 if already {
6316 continue;
6317 }
6318 let idx_name = match (name.as_ref(), columns.len(), k) {
6319 (Some(n), 1, _) => n.clone(),
6320 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6321 (None, _, _) => {
6322 alloc::format!("{table_name}_{col}_ftidx")
6323 }
6324 };
6325 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6326 return Err(EngineError::Storage(e));
6327 }
6328 }
6329 continue;
6330 }
6331 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6335 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6336 ("pkey", columns, None)
6337 }
6338 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6339 spg_sql::ast::TableConstraint::Index { name, columns } => {
6340 ("idx", columns, name.as_ref())
6341 }
6342 spg_sql::ast::TableConstraint::Check { .. } => continue,
6343 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6345 };
6346 let leading = &names[0];
6347 let already = table.indices().iter().any(|idx| {
6350 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6351 && table.schema().columns[idx.column_position].name == *leading
6352 });
6353 if already {
6354 continue;
6355 }
6356 let idx_name = if let Some(n) = explicit_name {
6357 n.clone()
6358 } else if names.len() == 1 {
6359 alloc::format!("{table_name}_{leading}_{suffix}")
6360 } else {
6361 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6362 };
6363 if let Err(e) = table.add_index(idx_name, leading) {
6364 return Err(EngineError::Storage(e));
6365 }
6366 }
6367 Ok(QueryResult::CommandOk {
6368 affected: 0,
6369 modified_catalog: !self.in_transaction(),
6370 })
6371 }
6372
6373 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6374 for tuple in &mut stmt.rows {
6382 for cell in tuple.iter_mut() {
6383 self.resolve_sequence_calls_in_expr(cell)?;
6384 }
6385 }
6386 if let Some(select) = stmt.select_source.clone() {
6391 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6392 let rows = match select_result {
6393 QueryResult::Rows { rows, .. } => rows,
6394 other => {
6395 return Err(EngineError::Unsupported(alloc::format!(
6396 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6397 )));
6398 }
6399 };
6400 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6401 for row in rows {
6402 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6403 for v in row.values {
6404 tuple.push(value_to_literal_expr_permissive(v)?);
6405 }
6406 materialised.push(tuple);
6407 }
6408 let recurse = InsertStatement {
6409 table: stmt.table,
6410 columns: stmt.columns,
6411 rows: materialised,
6412 select_source: None,
6413 on_conflict: stmt.on_conflict,
6414 returning: stmt.returning,
6415 };
6416 return self.exec_insert(recurse);
6417 }
6418 let clock = self.clock;
6422 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6428 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6429 let trigger_session_cfg: Option<alloc::string::String> = self
6430 .session_params
6431 .get("default_text_search_config")
6432 .cloned();
6433 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6439 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6440 EngineError::Storage(StorageError::TableNotFound {
6441 name: stmt.table.clone(),
6442 })
6443 })?;
6444 preview_table.schema().columns.clone()
6445 };
6446 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6447 pre_borrow_column_meta
6448 .iter()
6449 .enumerate()
6450 .filter_map(|(i, col)| {
6451 if let Some(inline) = &col.inline_enum_variants {
6456 return Some((i, inline.clone()));
6457 }
6458 col.user_enum_type.as_ref().and_then(|ename| {
6459 self.active_catalog()
6460 .enum_types()
6461 .get(ename)
6462 .map(|e| (i, e.labels.clone()))
6463 })
6464 })
6465 .collect();
6466 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6471 pre_borrow_column_meta
6472 .iter()
6473 .enumerate()
6474 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6475 .collect();
6476 let table = self
6477 .active_catalog_mut()
6478 .get_mut(&stmt.table)
6479 .ok_or_else(|| {
6480 EngineError::Storage(StorageError::TableNotFound {
6481 name: stmt.table.clone(),
6482 })
6483 })?;
6484 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6490 let schema_cols_len = column_meta.len();
6491 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6495 None => None, Some(cols) => {
6497 let mut map = alloc::vec![None; schema_cols_len];
6498 for (j, name) in cols.iter().enumerate() {
6499 let idx = column_meta
6500 .iter()
6501 .position(|c| c.name == *name)
6502 .ok_or_else(|| {
6503 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6504 })?;
6505 if map[idx].is_some() {
6506 return Err(EngineError::Storage(StorageError::ArityMismatch {
6507 expected: schema_cols_len,
6508 actual: cols.len(),
6509 }));
6510 }
6511 map[idx] = Some(j);
6512 }
6513 for (i, col) in column_meta.iter().enumerate() {
6517 if map[i].is_none()
6518 && !col.nullable
6519 && col.default.is_none()
6520 && col.runtime_default.is_none()
6521 && !col.auto_increment
6522 {
6523 return Err(EngineError::Storage(StorageError::NullInNotNull {
6524 column: col.name.clone(),
6525 }));
6526 }
6527 }
6528 Some(map)
6529 }
6530 };
6531 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6532 let fks = table.schema().foreign_keys.clone();
6538 let mut affected = 0usize;
6539 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6542 for tuple in stmt.rows {
6543 if tuple.len() != expected_tuple_len {
6544 return Err(EngineError::Storage(StorageError::ArityMismatch {
6545 expected: expected_tuple_len,
6546 actual: tuple.len(),
6547 }));
6548 }
6549 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6553 let raw_tuple: Vec<Value> = tuple
6555 .into_iter()
6556 .map(literal_expr_to_value)
6557 .collect::<Result<_, _>>()?;
6558 let mut out = Vec::with_capacity(schema_cols_len);
6559 for (i, col) in column_meta.iter().enumerate() {
6560 let mut raw = match map[i] {
6561 Some(j) => raw_tuple[j].clone(),
6562 None => resolve_column_default_free(col, clock)?,
6563 };
6564 if col.auto_increment && raw.is_null() {
6565 let next = table.next_auto_value(i).ok_or_else(|| {
6566 EngineError::Unsupported(alloc::format!(
6567 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6568 col.name
6569 ))
6570 })?;
6571 raw = Value::BigInt(next);
6572 }
6573 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6574 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6575 let coerced =
6576 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6577 check_unsigned_range(&coerced, col, i)?;
6578 out.push(coerced);
6579 }
6580 out
6581 } else {
6582 let mut out = Vec::with_capacity(schema_cols_len);
6584 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6585 let mut raw = literal_expr_to_value(expr)?;
6586 if col.auto_increment && raw.is_null() {
6587 let next = table.next_auto_value(i).ok_or_else(|| {
6588 EngineError::Unsupported(alloc::format!(
6589 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6590 col.name
6591 ))
6592 })?;
6593 raw = Value::BigInt(next);
6594 }
6595 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6596 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6597 let coerced =
6598 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6599 check_unsigned_range(&coerced, col, i)?;
6600 out.push(coerced);
6601 }
6602 out
6603 };
6604 all_values.push(values);
6605 }
6606 let uniqueness = table.schema().uniqueness_constraints.clone();
6611 let _ = table;
6612 if !fks.is_empty() {
6613 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6614 }
6615 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6617 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6619 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6626 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6633 let mut skipped_count = 0usize;
6634 if let Some(clause) = &stmt.on_conflict {
6635 let conflict_cols = resolve_on_conflict_columns(
6636 self.active_catalog(),
6637 &stmt.table,
6638 clause.target_columns.as_slice(),
6639 )?;
6640 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6641 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6642 for values in all_values {
6643 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6644 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6647 let collides_with_table = !has_null_key
6648 && on_conflict_keys_exist(
6649 self.active_catalog(),
6650 &stmt.table,
6651 &conflict_cols,
6652 &key_tuple,
6653 );
6654 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6655 let collides_with_batch =
6656 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6657 let collides = collides_with_table || collides_with_batch;
6658 match (&clause.action, collides) {
6659 (_, false) => {
6660 seen_keys.push(key_tuple_owned);
6661 kept.push(values);
6662 }
6663 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6664 skipped_count += 1;
6665 }
6666 (
6667 spg_sql::ast::OnConflictAction::Update {
6668 assignments,
6669 where_,
6670 },
6671 true,
6672 ) => {
6673 if !collides_with_table {
6674 skipped_count += 1;
6675 continue;
6676 }
6677 let target_pos = lookup_row_position_by_keys(
6678 self.active_catalog(),
6679 &stmt.table,
6680 &conflict_cols,
6681 &key_tuple,
6682 )
6683 .ok_or_else(|| {
6684 EngineError::Unsupported(
6685 "ON CONFLICT DO UPDATE: conflict detected but row \
6686 position could not be resolved (cold-tier row?)"
6687 .into(),
6688 )
6689 })?;
6690 let updated = apply_on_conflict_assignments(
6691 self.active_catalog(),
6692 &stmt.table,
6693 target_pos,
6694 &values,
6695 assignments,
6696 where_.as_ref(),
6697 )?;
6698 if let Some(new_row) = updated {
6699 pending_updates.push((target_pos, new_row));
6700 } else {
6701 skipped_count += 1;
6702 }
6703 }
6704 }
6705 }
6706 all_values = kept;
6707 }
6708 let table = self
6710 .active_catalog_mut()
6711 .get_mut(&stmt.table)
6712 .ok_or_else(|| {
6713 EngineError::Storage(StorageError::TableNotFound {
6714 name: stmt.table.clone(),
6715 })
6716 })?;
6717 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
6721 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
6725 'rowloop: for values in all_values {
6726 let mut row = Row::new(values);
6727 for fd in &before_insert_triggers {
6732 let (outcome, deferred) = triggers::fire_row_trigger(
6733 fd,
6734 Some(row.clone()),
6735 None,
6736 &stmt.table,
6737 &column_meta,
6738 &[],
6739 trigger_session_cfg.as_deref(),
6740 false,
6741 )
6742 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6743 deferred_embedded.extend(deferred);
6744 match outcome {
6745 triggers::TriggerOutcome::Row(r) => row = r,
6746 triggers::TriggerOutcome::Skip => continue 'rowloop,
6747 }
6748 }
6749 if stmt.returning.is_some() {
6750 returning_rows.push(row.values.clone());
6751 }
6752 let inserted = row.clone();
6755 table.insert(row)?;
6756 affected += 1;
6757 for fd in &after_insert_triggers {
6761 let (_outcome, deferred) = triggers::fire_row_trigger(
6762 fd,
6763 Some(inserted.clone()),
6764 None,
6765 &stmt.table,
6766 &column_meta,
6767 &[],
6768 trigger_session_cfg.as_deref(),
6769 true,
6770 )
6771 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6772 deferred_embedded.extend(deferred);
6773 }
6774 }
6775 for (pos, new_row) in pending_updates {
6779 if stmt.returning.is_some() {
6780 returning_rows.push(new_row.clone());
6781 }
6782 table.update_row(pos, new_row)?;
6783 affected += 1;
6784 }
6785 let _ = skipped_count;
6786 let _ = table;
6792 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
6793 if let Some(items) = &stmt.returning {
6797 return self.build_returning_rows(&stmt.table, items, returning_rows);
6798 }
6799 if !self.in_transaction() && affected > 0 {
6804 self.statistics
6805 .record_modifications(&stmt.table, affected as u64);
6806 }
6807 Ok(QueryResult::CommandOk {
6808 affected,
6809 modified_catalog: !self.in_transaction(),
6810 })
6811 }
6812
6813 fn exec_select_as_of_segment(
6826 &self,
6827 stmt: &SelectStatement,
6828 from: &spg_sql::ast::FromClause,
6829 segment_id: u32,
6830 ) -> Result<QueryResult, EngineError> {
6831 if !from.joins.is_empty()
6834 || stmt.group_by.is_some()
6835 || stmt.having.is_some()
6836 || !stmt.unions.is_empty()
6837 || !stmt.order_by.is_empty()
6838 || stmt.offset.is_some()
6839 || stmt.distinct
6840 || aggregate::uses_aggregate(stmt)
6841 {
6842 return Err(EngineError::Unsupported(
6843 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
6844 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
6845 .into(),
6846 ));
6847 }
6848 let table = self
6849 .active_catalog()
6850 .get(&from.primary.name)
6851 .ok_or_else(|| StorageError::TableNotFound {
6852 name: from.primary.name.clone(),
6853 })?;
6854 let schema = table.schema().clone();
6855 let schema_cols = &schema.columns;
6856 let alias = from
6857 .primary
6858 .alias
6859 .as_deref()
6860 .unwrap_or(from.primary.name.as_str());
6861 let ctx = EvalContext::new(schema_cols, Some(alias));
6862 let seg = self
6863 .active_catalog()
6864 .cold_segment(segment_id)
6865 .ok_or_else(|| {
6866 EngineError::Unsupported(alloc::format!(
6867 "AS OF SEGMENT: cold segment {segment_id} not registered"
6868 ))
6869 })?;
6870 let mut out_rows: Vec<Row> = Vec::new();
6871 let mut limit_remaining: Option<usize> =
6872 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
6873 for (_key, body) in seg.scan() {
6874 let (row, _consumed) =
6875 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
6876 if let Some(where_expr) = &stmt.where_ {
6877 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
6878 if !matches!(cond, Value::Bool(true)) {
6879 continue;
6880 }
6881 }
6882 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
6884 out_rows.push(projected);
6885 if let Some(rem) = limit_remaining.as_mut() {
6886 if *rem == 0 {
6887 out_rows.pop();
6888 break;
6889 }
6890 *rem -= 1;
6891 }
6892 }
6893 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
6895 Ok(QueryResult::Rows {
6896 columns,
6897 rows: out_rows,
6898 })
6899 }
6900
6901 fn eval_expr_simple(
6906 &self,
6907 expr: &Expr,
6908 row: &Row,
6909 ctx: &EvalContext,
6910 ) -> Result<Value, EngineError> {
6911 let cancel = CancelToken::none();
6912 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
6913 }
6914
6915 fn build_returning_rows(
6922 &self,
6923 table_name: &str,
6924 items: &[SelectItem],
6925 mutated_rows: Vec<Vec<Value>>,
6926 ) -> Result<QueryResult, EngineError> {
6927 let table = self.active_catalog().get(table_name).ok_or_else(|| {
6928 EngineError::Storage(StorageError::TableNotFound {
6929 name: table_name.into(),
6930 })
6931 })?;
6932 let schema_cols = table.schema().columns.clone();
6933 let columns = self.derive_output_columns(items, &schema_cols, table_name);
6934 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
6935 for values in mutated_rows {
6936 let row = Row::new(values);
6937 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
6938 out_rows.push(projected);
6939 }
6940 Ok(QueryResult::Rows {
6941 columns,
6942 rows: out_rows,
6943 })
6944 }
6945
6946 fn project_row_simple(
6950 &self,
6951 row: &Row,
6952 items: &[SelectItem],
6953 schema_cols: &[ColumnSchema],
6954 alias: &str,
6955 ) -> Result<Row, EngineError> {
6956 let ctx = EvalContext::new(schema_cols, Some(alias));
6957 let cancel = CancelToken::none();
6958 let mut out_vals = Vec::new();
6959 for item in items {
6960 match item {
6961 SelectItem::Wildcard => {
6962 out_vals.extend(row.values.iter().cloned());
6963 }
6964 SelectItem::Expr { expr, .. } => {
6965 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
6966 out_vals.push(v);
6967 }
6968 }
6969 }
6970 Ok(Row::new(out_vals))
6971 }
6972
6973 fn derive_output_columns(
6978 &self,
6979 items: &[SelectItem],
6980 schema_cols: &[ColumnSchema],
6981 _alias: &str,
6982 ) -> Vec<ColumnSchema> {
6983 let mut out = Vec::new();
6984 for item in items {
6985 match item {
6986 SelectItem::Wildcard => {
6987 out.extend(schema_cols.iter().cloned());
6988 }
6989 SelectItem::Expr { alias, .. } => {
6990 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
6991 out.push(ColumnSchema::new(name, DataType::Text, true));
6994 }
6995 }
6996 }
6997 out
6998 }
6999
7000 fn exec_select_cancel(
7001 &self,
7002 stmt: &SelectStatement,
7003 cancel: CancelToken<'_>,
7004 ) -> Result<QueryResult, EngineError> {
7005 cancel.check()?;
7006 if !self.active_catalog().views().is_empty() {
7013 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7014 return self.exec_select_cancel(&rewritten, cancel);
7015 }
7016 }
7017 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7026 return self.exec_select_with_meta_views(stmt, cancel);
7027 }
7028 if let Some(from) = &stmt.from
7037 && let Some(seg_id) = from.primary.as_of_segment
7038 {
7039 return self.exec_select_as_of_segment(stmt, from, seg_id);
7040 }
7041 if let Some(from) = &stmt.from
7045 && from.joins.is_empty()
7046 && stmt.where_.is_none()
7047 && stmt.group_by.is_none()
7048 && stmt.having.is_none()
7049 && stmt.unions.is_empty()
7050 && stmt.order_by.is_empty()
7051 && stmt.limit.is_none()
7052 && stmt.offset.is_none()
7053 && !stmt.distinct
7054 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7055 {
7056 let lower = from.primary.name.to_ascii_lowercase();
7057 match lower.as_str() {
7058 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7059 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7061 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7062 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7063 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7064 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7065 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7066 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7067 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7068 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7069 _ => {}
7070 }
7071 }
7072 if !stmt.ctes.is_empty() {
7080 return self.exec_with_ctes(stmt, cancel);
7081 }
7082 let mut stmt_owned;
7089 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7090 stmt_owned = stmt.clone();
7091 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7092 &stmt_owned
7093 } else {
7094 stmt
7095 };
7096 if stmt_ref.unions.is_empty() {
7097 return self.exec_bare_select_cancel(stmt_ref, cancel);
7098 }
7099 let mut head = stmt_ref.clone();
7104 head.unions = Vec::new();
7105 head.order_by = Vec::new();
7106 head.limit = None;
7107 let QueryResult::Rows { columns, mut rows } =
7108 self.exec_bare_select_cancel(&head, cancel)?
7109 else {
7110 unreachable!("bare SELECT cannot return CommandOk")
7111 };
7112 for (kind, peer) in &stmt_ref.unions {
7113 let QueryResult::Rows {
7114 columns: peer_cols,
7115 rows: peer_rows,
7116 } = self.exec_bare_select_cancel(peer, cancel)?
7117 else {
7118 unreachable!("bare SELECT cannot return CommandOk")
7119 };
7120 if peer_cols.len() != columns.len() {
7121 return Err(EngineError::Unsupported(alloc::format!(
7122 "UNION arity mismatch: head has {} columns, peer has {}",
7123 columns.len(),
7124 peer_cols.len()
7125 )));
7126 }
7127 rows.extend(peer_rows);
7128 if matches!(kind, UnionKind::Distinct) {
7129 rows = dedup_rows(rows);
7130 }
7131 }
7132 if !stmt.order_by.is_empty() {
7135 let synth_ctx = EvalContext::new(&columns, None);
7136 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7137 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7138 for r in rows {
7139 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7140 tagged.push((keys, r));
7141 }
7142 sort_by_keys(&mut tagged, &descs);
7143 rows = tagged.into_iter().map(|(_, r)| r).collect();
7144 }
7145 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7146 Ok(QueryResult::Rows { columns, rows })
7147 }
7148
7149 #[allow(clippy::too_many_lines)]
7150 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7158 &self,
7159 stmt: &SelectStatement,
7160 primary: &TableRef,
7161 cancel: CancelToken<'_>,
7162 ) -> Result<QueryResult, EngineError> {
7163 let expr = primary
7164 .unnest_expr
7165 .as_deref()
7166 .expect("caller guards unnest_expr.is_some()");
7167 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7170 let ctx = EvalContext::new(&empty_schema, None);
7171 let dummy_row = Row::new(alloc::vec::Vec::new());
7172 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7175 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7176 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7177 Value::TextArray(items) => {
7178 let rows = items
7179 .into_iter()
7180 .map(|item| {
7181 Row::new(alloc::vec![match item {
7182 Some(s) => Value::Text(s),
7183 None => Value::Null,
7184 }])
7185 })
7186 .collect();
7187 (DataType::Text, rows)
7188 }
7189 Value::IntArray(items) => {
7190 let rows = items
7191 .into_iter()
7192 .map(|item| {
7193 Row::new(alloc::vec![match item {
7194 Some(n) => Value::Int(n),
7195 None => Value::Null,
7196 }])
7197 })
7198 .collect();
7199 (DataType::Int, rows)
7200 }
7201 Value::BigIntArray(items) => {
7202 let rows = items
7203 .into_iter()
7204 .map(|item| {
7205 Row::new(alloc::vec![match item {
7206 Some(n) => Value::BigInt(n),
7207 None => Value::Null,
7208 }])
7209 })
7210 .collect();
7211 (DataType::BigInt, rows)
7212 }
7213 other => {
7214 return Err(EngineError::Unsupported(alloc::format!(
7215 "unnest() expects an array argument, got {:?}",
7216 other.data_type()
7217 )));
7218 }
7219 };
7220 let alias = primary
7221 .alias
7222 .clone()
7223 .unwrap_or_else(|| "unnest".to_string());
7224 let col_name = primary
7230 .unnest_column_aliases
7231 .first()
7232 .cloned()
7233 .unwrap_or_else(|| alias.clone());
7234 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7235 let schema_cols = alloc::vec![col_schema.clone()];
7236 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7237 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7239 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7240 for row in rows {
7241 cancel.check()?;
7242 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7243 if matches!(v, Value::Bool(true)) {
7244 out.push(row);
7245 }
7246 }
7247 out
7248 } else {
7249 rows
7250 };
7251 if aggregate::uses_aggregate(stmt) {
7257 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7258 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7259 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7260 return Ok(QueryResult::Rows {
7261 columns: agg.columns,
7262 rows: agg.rows,
7263 });
7264 }
7265 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7267 let mut projected_rows: alloc::vec::Vec<Row> =
7268 alloc::vec::Vec::with_capacity(filtered.len());
7269 for row in &filtered {
7270 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7271 for p in &projection {
7272 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7273 }
7274 projected_rows.push(Row::new(vals));
7275 }
7276 let columns: alloc::vec::Vec<ColumnSchema> = projection
7279 .iter()
7280 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7281 .collect();
7282 if !stmt.order_by.is_empty() {
7285 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7286 .iter()
7287 .enumerate()
7288 .map(|(i, r)| -> Result<_, EngineError> {
7289 let keys: Result<Vec<Value>, EngineError> = stmt
7290 .order_by
7291 .iter()
7292 .map(|ob| {
7293 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7294 })
7295 .collect();
7296 Ok((i, keys?))
7297 })
7298 .collect::<Result<_, _>>()?;
7299 indexed.sort_by(|a, b| {
7300 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7301 let mut cmp = value_cmp(ka, kb);
7302 if stmt.order_by[idx].desc {
7303 cmp = cmp.reverse();
7304 }
7305 if cmp != core::cmp::Ordering::Equal {
7306 return cmp;
7307 }
7308 }
7309 core::cmp::Ordering::Equal
7310 });
7311 projected_rows = indexed
7312 .into_iter()
7313 .map(|(i, _)| projected_rows[i].clone())
7314 .collect();
7315 }
7316 if let Some(offset) = stmt.offset_literal() {
7318 let off = (offset as usize).min(projected_rows.len());
7319 projected_rows.drain(..off);
7320 }
7321 if let Some(limit) = stmt.limit_literal() {
7322 projected_rows.truncate(limit as usize);
7323 }
7324 Ok(QueryResult::Rows {
7325 columns,
7326 rows: projected_rows,
7327 })
7328 }
7329
7330 fn exec_select_generate_series(
7341 &self,
7342 stmt: &SelectStatement,
7343 primary: &TableRef,
7344 cancel: CancelToken<'_>,
7345 ) -> Result<QueryResult, EngineError> {
7346 let args = primary
7347 .generate_series_args
7348 .as_ref()
7349 .expect("caller guards generate_series_args.is_some()");
7350 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7351 let ctx = EvalContext::new(&empty_schema, None);
7352 let dummy_row = Row::new(alloc::vec::Vec::new());
7353 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7354 for a in args {
7355 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7356 }
7357 let (elem_dtype, rows) = match arg_values.as_slice() {
7361 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7362 let interval_step = match step {
7363 Value::Interval { .. } => step.clone(),
7364 other => {
7365 return Err(EngineError::Unsupported(alloc::format!(
7366 "generate_series(timestamp, timestamp, …): \
7367 step must be INTERVAL, got {:?}",
7368 other.data_type()
7369 )));
7370 }
7371 };
7372 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7373 (DataType::Timestamp, rows)
7374 }
7375 [start, stop, step]
7376 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7377 {
7378 let s = value_to_i64(start);
7379 let e = value_to_i64(stop);
7380 let st = value_to_i64(step);
7381 let rows = generate_series_integers(s, e, st, &cancel)?;
7382 (DataType::BigInt, rows)
7383 }
7384 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7385 let s = value_to_i64(start);
7386 let e = value_to_i64(stop);
7387 let rows = generate_series_integers(s, e, 1, &cancel)?;
7388 (DataType::BigInt, rows)
7389 }
7390 _ => {
7391 return Err(EngineError::Unsupported(alloc::format!(
7392 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7393 argument shapes; got {:?}",
7394 arg_values
7395 .iter()
7396 .map(|v| v.data_type())
7397 .collect::<alloc::vec::Vec<_>>()
7398 )));
7399 }
7400 };
7401 let alias = primary
7402 .alias
7403 .clone()
7404 .unwrap_or_else(|| "generate_series".to_string());
7405 let col_name = alias.clone();
7406 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7407 let schema_cols = alloc::vec![col_schema.clone()];
7408 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7409 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7411 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7412 for row in rows {
7413 cancel.check()?;
7414 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7415 if matches!(v, Value::Bool(true)) {
7416 out.push(row);
7417 }
7418 }
7419 out
7420 } else {
7421 rows
7422 };
7423 if aggregate::uses_aggregate(stmt) {
7433 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7434 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7435 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7436 return Ok(QueryResult::Rows {
7437 columns: agg.columns,
7438 rows: agg.rows,
7439 });
7440 }
7441 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7443 let mut projected_rows: alloc::vec::Vec<Row> =
7444 alloc::vec::Vec::with_capacity(filtered.len());
7445 for row in &filtered {
7446 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7447 for p in &projection {
7448 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7449 }
7450 projected_rows.push(Row::new(vals));
7451 }
7452 let columns: alloc::vec::Vec<ColumnSchema> = projection
7453 .iter()
7454 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7455 .collect();
7456 if !stmt.order_by.is_empty() {
7458 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7459 .iter()
7460 .enumerate()
7461 .map(|(i, r)| -> Result<_, EngineError> {
7462 let keys: Result<Vec<Value>, EngineError> = stmt
7463 .order_by
7464 .iter()
7465 .map(|ob| {
7466 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7467 })
7468 .collect();
7469 Ok((i, keys?))
7470 })
7471 .collect::<Result<_, _>>()?;
7472 indexed.sort_by(|a, b| {
7473 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7474 let mut cmp = value_cmp(ka, kb);
7475 if stmt.order_by[idx].desc {
7476 cmp = cmp.reverse();
7477 }
7478 if cmp != core::cmp::Ordering::Equal {
7479 return cmp;
7480 }
7481 }
7482 core::cmp::Ordering::Equal
7483 });
7484 projected_rows = indexed
7485 .into_iter()
7486 .map(|(i, _)| projected_rows[i].clone())
7487 .collect();
7488 }
7489 if let Some(offset) = stmt.offset_literal() {
7490 let off = (offset as usize).min(projected_rows.len());
7491 projected_rows.drain(..off);
7492 }
7493 if let Some(limit) = stmt.limit_literal() {
7494 projected_rows.truncate(limit as usize);
7495 }
7496 Ok(QueryResult::Rows {
7497 columns,
7498 rows: projected_rows,
7499 })
7500 }
7501
7502 fn exec_bare_select_cancel(
7503 &self,
7504 stmt: &SelectStatement,
7505 cancel: CancelToken<'_>,
7506 ) -> Result<QueryResult, EngineError> {
7507 check_with_ties_requires_order_by(stmt)?;
7512 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7520 return self.exec_select_with_meta_views(stmt, cancel);
7521 }
7522 if select_has_window(stmt) {
7527 return self.exec_select_with_window(stmt, cancel);
7528 }
7529 let Some(from) = &stmt.from else {
7534 let empty_schema: Vec<ColumnSchema> = Vec::new();
7535 let ctx = self.ev_ctx(&empty_schema, None);
7536 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7537 let dummy_row = Row::new(Vec::new());
7538 let mut values = Vec::with_capacity(projection.len());
7539 for p in &projection {
7540 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7541 }
7542 let columns: Vec<ColumnSchema> = projection
7543 .into_iter()
7544 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7545 .collect();
7546 return Ok(QueryResult::Rows {
7547 columns,
7548 rows: alloc::vec![Row::new(values)],
7549 });
7550 };
7551 if !from.joins.is_empty() {
7555 return self.exec_joined_select(stmt, from);
7556 }
7557 if from.primary.unnest_expr.is_some() {
7564 return self.exec_select_unnest(stmt, &from.primary, cancel);
7565 }
7566 if from.primary.generate_series_args.is_some() {
7572 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7573 }
7574 let primary = &from.primary;
7575 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7576 StorageError::TableNotFound {
7577 name: primary.name.clone(),
7578 }
7579 })?;
7580 let schema_cols = &table.schema().columns;
7581 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7584 let ctx = self.ev_ctx(schema_cols, Some(alias));
7585
7586 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7591 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7592 }
7593
7594 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7602 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7605 .or_else(|| {
7606 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7612 })
7613 .or_else(|| {
7614 try_trgm_seek(w, schema_cols, table, alias)
7620 })
7621 });
7622
7623 if aggregate::uses_aggregate(stmt) {
7626 let mut filtered: Vec<&Row> = Vec::new();
7627 let mut memo = memoize::MemoizeCache::new();
7631 if let Some(rows) = &indexed_rows {
7632 for cow in rows {
7633 let row = cow.as_ref();
7634 if let Some(where_expr) = &stmt.where_ {
7635 let cond = self.eval_expr_with_correlated(
7636 where_expr,
7637 row,
7638 &ctx,
7639 cancel,
7640 Some(&mut memo),
7641 )?;
7642 if !matches!(cond, Value::Bool(true)) {
7643 continue;
7644 }
7645 }
7646 filtered.push(row);
7647 }
7648 } else {
7649 for i in 0..table.row_count() {
7650 let row = &table.rows()[i];
7651 if let Some(where_expr) = &stmt.where_ {
7652 let cond = self.eval_expr_with_correlated(
7653 where_expr,
7654 row,
7655 &ctx,
7656 cancel,
7657 Some(&mut memo),
7658 )?;
7659 if !matches!(cond, Value::Bool(true)) {
7660 continue;
7661 }
7662 }
7663 filtered.push(row);
7664 }
7665 }
7666 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7667 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7668 return Ok(QueryResult::Rows {
7669 columns: agg.columns,
7670 rows: agg.rows,
7671 });
7672 }
7673
7674 let projection = build_projection(&stmt.items, schema_cols, alias)?;
7675
7676 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
7679 let mut memo = memoize::MemoizeCache::new();
7681 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
7684 if loop_idx.is_multiple_of(256) {
7685 cancel.check()?;
7686 }
7687 if let Some(where_expr) = &stmt.where_ {
7688 let cond =
7689 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
7690 if !matches!(cond, Value::Bool(true)) {
7691 return Ok(());
7692 }
7693 }
7694 let mut values = Vec::with_capacity(projection.len());
7695 for p in &projection {
7696 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7697 }
7698 let order_keys = if stmt.order_by.is_empty() {
7699 Vec::new()
7700 } else {
7701 build_order_keys(&stmt.order_by, row, &ctx)?
7702 };
7703 tagged.push((order_keys, Row::new(values)));
7704 Ok(())
7705 };
7706 if let Some(rows) = &indexed_rows {
7707 for (loop_idx, cow) in rows.iter().enumerate() {
7708 process_row(cow.as_ref(), loop_idx)?;
7709 }
7710 } else {
7711 for i in 0..table.row_count() {
7712 process_row(&table.rows()[i], i)?;
7713 }
7714 }
7715
7716 if !stmt.order_by.is_empty() {
7717 let keep = if stmt.distinct || stmt.limit_with_ties {
7725 None
7726 } else {
7727 stmt.limit_literal()
7728 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
7729 };
7730 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7731 partial_sort_tagged(&mut tagged, keep, &descs);
7732 }
7733
7734 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
7744 apply_offset_and_limit_tagged(
7745 &mut tagged,
7746 stmt.offset_literal(),
7747 stmt.limit_literal(),
7748 true,
7749 );
7750 tagged.into_iter().map(|(_, r)| r).collect()
7751 } else {
7752 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
7753 if stmt.distinct {
7754 output_rows = dedup_rows(output_rows);
7755 }
7756 apply_offset_and_limit(
7757 &mut output_rows,
7758 stmt.offset_literal(),
7759 stmt.limit_literal(),
7760 );
7761 output_rows
7762 };
7763
7764 let columns: Vec<ColumnSchema> = projection
7765 .into_iter()
7766 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7767 .collect();
7768
7769 Ok(QueryResult::Rows {
7770 columns,
7771 rows: output_rows,
7772 })
7773 }
7774
7775 #[allow(clippy::too_many_lines)]
7782 fn materialise_table_ref(
7790 &self,
7791 tref: &TableRef,
7792 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
7793 if let Some(expr) = tref.unnest_expr.as_deref() {
7794 let empty_schema: Vec<ColumnSchema> = Vec::new();
7795 let ctx = EvalContext::new(&empty_schema, None);
7796 let dummy_row = Row::new(Vec::new());
7797 let (elem_dtype, rows) =
7798 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7799 Value::Null => (DataType::Text, Vec::new()),
7800 Value::TextArray(items) => (
7801 DataType::Text,
7802 items
7803 .into_iter()
7804 .map(|item| {
7805 Row::new(alloc::vec![match item {
7806 Some(s) => Value::Text(s),
7807 None => Value::Null,
7808 }])
7809 })
7810 .collect(),
7811 ),
7812 Value::IntArray(items) => (
7813 DataType::Int,
7814 items
7815 .into_iter()
7816 .map(|item| {
7817 Row::new(alloc::vec![match item {
7818 Some(n) => Value::Int(n),
7819 None => Value::Null,
7820 }])
7821 })
7822 .collect(),
7823 ),
7824 Value::BigIntArray(items) => (
7825 DataType::BigInt,
7826 items
7827 .into_iter()
7828 .map(|item| {
7829 Row::new(alloc::vec![match item {
7830 Some(n) => Value::BigInt(n),
7831 None => Value::Null,
7832 }])
7833 })
7834 .collect(),
7835 ),
7836 other => {
7837 return Err(EngineError::Unsupported(alloc::format!(
7838 "unnest() expects an array argument, got {:?}",
7839 other.data_type()
7840 )));
7841 }
7842 };
7843 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
7844 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
7845 return Ok((
7846 rows,
7847 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
7848 ));
7849 }
7850 let table =
7851 self.active_catalog()
7852 .get(&tref.name)
7853 .ok_or_else(|| StorageError::TableNotFound {
7854 name: tref.name.clone(),
7855 })?;
7856 let rows: Vec<Row> = table.rows().iter().cloned().collect();
7857 let cols = table.schema().columns.clone();
7858 Ok((rows, cols))
7859 }
7860
7861 fn build_joined_filtered_rows(
7873 &self,
7874 from: &FromClause,
7875 where_: Option<&Expr>,
7876 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
7877 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
7878 let primary_alias = from
7879 .primary
7880 .alias
7881 .as_deref()
7882 .unwrap_or(from.primary.name.as_str())
7883 .to_string();
7884 #[allow(clippy::type_complexity)]
7891 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
7892 for j in &from.joins {
7893 let a = j
7894 .table
7895 .alias
7896 .as_deref()
7897 .unwrap_or(j.table.name.as_str())
7898 .to_string();
7899 if let Some(inner_box) = &j.table.lateral_subquery {
7900 let schema = self.lateral_probe_schema(inner_box)?;
7905 joined.push(JoinedPeer {
7906 eager_rows: None,
7907 cols: schema,
7908 alias: a,
7909 kind: j.kind,
7910 on: j.on.as_ref(),
7911 lateral: Some(inner_box.as_ref()),
7912 });
7913 } else {
7914 let (rows, cols) = self.materialise_table_ref(&j.table)?;
7915 joined.push(JoinedPeer {
7916 eager_rows: Some(rows),
7917 cols,
7918 alias: a,
7919 kind: j.kind,
7920 on: j.on.as_ref(),
7921 lateral: None,
7922 });
7923 }
7924 }
7925 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
7926 for col in &primary_cols {
7927 combined_schema.push(ColumnSchema::new(
7928 alloc::format!("{primary_alias}.{}", col.name),
7929 col.ty,
7930 col.nullable,
7931 ));
7932 }
7933 for peer in &joined {
7934 for col in &peer.cols {
7935 combined_schema.push(ColumnSchema::new(
7936 alloc::format!("{}.{}", peer.alias, col.name),
7937 col.ty,
7938 col.nullable,
7939 ));
7940 }
7941 }
7942 let ctx = EvalContext::new(&combined_schema, None);
7943 let mut working: Vec<Row> = primary_rows;
7944 let mut consumed_cols = primary_cols.len();
7947 for peer in &joined {
7948 let right_arity = peer.cols.len();
7949 let mut next: Vec<Row> = Vec::new();
7950 for left in &working {
7951 let mut left_matched = false;
7952 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
7953 Some(inner) => {
7954 let outer_schema = &combined_schema[..consumed_cols];
7958 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
7959 alloc::borrow::Cow::Owned(rows)
7960 }
7961 None => {
7962 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
7963 alloc::borrow::Cow::Borrowed(r.as_slice())
7964 }
7965 };
7966 for right in per_left_rrows.as_ref() {
7967 let mut combined_vals = left.values.clone();
7968 combined_vals.extend(right.values.iter().cloned());
7969 let combined = Row::new(combined_vals);
7970 let keep = if let Some(on_expr) = peer.on {
7971 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
7972 matches!(cond, Value::Bool(true))
7973 } else {
7974 true
7975 };
7976 if keep {
7977 next.push(combined);
7978 left_matched = true;
7979 }
7980 }
7981 if !left_matched && matches!(peer.kind, JoinKind::Left) {
7982 let mut combined_vals = left.values.clone();
7983 for _ in 0..right_arity {
7984 combined_vals.push(Value::Null);
7985 }
7986 next.push(Row::new(combined_vals));
7987 }
7988 }
7989 working = next;
7990 consumed_cols += right_arity;
7991 debug_assert!(consumed_cols <= combined_schema.len());
7992 }
7993 let mut filtered: Vec<Row> = Vec::new();
7994 for row in working {
7995 if let Some(where_expr) = where_ {
7996 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
7997 if !matches!(cond, Value::Bool(true)) {
7998 continue;
7999 }
8000 }
8001 filtered.push(row);
8002 }
8003 Ok((combined_schema, filtered))
8004 }
8005
8006 fn lateral_probe_schema(
8012 &self,
8013 inner: &SelectStatement,
8014 ) -> Result<Vec<ColumnSchema>, EngineError> {
8015 match self.execute_readonly_select_for_lateral_probe(inner) {
8025 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8026 _ => {
8032 let mut out: Vec<ColumnSchema> = Vec::new();
8033 for (i, item) in inner.items.iter().enumerate() {
8034 let name = match item {
8035 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8036 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8037 SelectItem::Wildcard => alloc::format!("col{i}"),
8038 };
8039 out.push(ColumnSchema::new(name, DataType::Text, true));
8040 }
8041 Ok(out)
8042 }
8043 }
8044 }
8045
8046 fn execute_readonly_select_for_lateral_probe(
8052 &self,
8053 inner: &SelectStatement,
8054 ) -> Result<QueryResult, EngineError> {
8055 self.exec_bare_select_cancel(inner, CancelToken::none())
8056 }
8057
8058 fn materialise_lateral_for_outer(
8064 &self,
8065 inner: &SelectStatement,
8066 outer_schema: &[ColumnSchema],
8067 outer_row: &Row,
8068 ) -> Result<Vec<Row>, EngineError> {
8069 let mut substituted = inner.clone();
8070 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8071 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8072 match result {
8073 QueryResult::Rows { rows, .. } => Ok(rows),
8074 _ => Err(EngineError::Unsupported(
8075 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8076 )),
8077 }
8078 }
8079
8080 fn exec_joined_select(
8081 &self,
8082 stmt: &SelectStatement,
8083 from: &FromClause,
8084 ) -> Result<QueryResult, EngineError> {
8085 let (combined_schema, filtered) =
8093 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8094 let ctx = EvalContext::new(&combined_schema, None);
8095 if aggregate::uses_aggregate(stmt) {
8098 let refs: Vec<&Row> = filtered.iter().collect();
8099 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8100 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8101 return Ok(QueryResult::Rows {
8102 columns: agg.columns,
8103 rows: agg.rows,
8104 });
8105 }
8106
8107 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8108 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8109 for row in &filtered {
8110 let mut values = Vec::with_capacity(projection.len());
8111 for p in &projection {
8112 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8113 }
8114 let order_keys = if stmt.order_by.is_empty() {
8115 Vec::new()
8116 } else {
8117 build_order_keys(&stmt.order_by, row, &ctx)?
8118 };
8119 tagged.push((order_keys, Row::new(values)));
8120 }
8121 if !stmt.order_by.is_empty() {
8122 let keep = if stmt.distinct {
8123 None
8124 } else {
8125 stmt.limit_literal()
8126 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8127 };
8128 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8129 partial_sort_tagged(&mut tagged, keep, &descs);
8130 }
8131 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8132 if stmt.distinct {
8133 output_rows = dedup_rows(output_rows);
8134 }
8135 apply_offset_and_limit(
8136 &mut output_rows,
8137 stmt.offset_literal(),
8138 stmt.limit_literal(),
8139 );
8140 let columns: Vec<ColumnSchema> = projection
8141 .into_iter()
8142 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8143 .collect();
8144 Ok(QueryResult::Rows {
8145 columns,
8146 rows: output_rows,
8147 })
8148 }
8149}
8150
8151#[derive(Debug, Clone)]
8154struct ProjectedItem {
8155 expr: Expr,
8156 output_name: String,
8157 ty: DataType,
8158 nullable: bool,
8159}
8160
8161fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8167 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8168 for r in rows {
8169 if !out.iter().any(|seen| seen == &r) {
8170 out.push(r);
8171 }
8172 }
8173 out
8174}
8175
8176fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8180 match v {
8181 Value::Null => Ok(f64::INFINITY),
8182 Value::SmallInt(n) => Ok(f64::from(*n)),
8183 Value::Int(n) => Ok(f64::from(*n)),
8184 Value::Date(d) => Ok(f64::from(*d)),
8185 #[allow(clippy::cast_precision_loss)]
8186 Value::Timestamp(t) => Ok(*t as f64),
8187 #[allow(clippy::cast_precision_loss)]
8190 Value::Time(us) => Ok(*us as f64),
8191 Value::Year(y) => Ok(f64::from(*y)),
8195 #[allow(clippy::cast_precision_loss)]
8200 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8201 #[allow(clippy::cast_precision_loss)]
8203 Value::Money(c) => Ok(*c as f64),
8204 Value::Range { .. } => Err(EngineError::Unsupported(
8207 "ORDER BY of a range value is not supported in v7.17.0".into(),
8208 )),
8209 Value::Hstore(_) => Err(EngineError::Unsupported(
8211 "ORDER BY of a hstore value is not supported".into(),
8212 )),
8213 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8215 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8216 ),
8217 #[allow(clippy::cast_precision_loss)]
8218 Value::Numeric { scaled, scale } => {
8219 let mut divisor = 1.0_f64;
8225 for _ in 0..*scale {
8226 divisor *= 10.0;
8227 }
8228 Ok((*scaled as f64) / divisor)
8229 }
8230 #[allow(clippy::cast_precision_loss)]
8231 Value::BigInt(n) => Ok(*n as f64),
8232 Value::Float(x) => Ok(*x),
8233 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8234 Value::Text(s) => {
8235 let mut key: u64 = 0;
8239 for &b in s.as_bytes().iter().take(8) {
8240 key = (key << 8) | u64::from(b);
8241 }
8242 #[allow(clippy::cast_precision_loss)]
8243 Ok(key as f64)
8244 }
8245 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8246 Err(EngineError::Unsupported(
8247 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8248 ))
8249 }
8250 Value::Interval { .. } => Err(EngineError::Unsupported(
8251 "ORDER BY of an INTERVAL is not supported in v2.11 \
8252 (months vs micros has no single canonical ordering)"
8253 .into(),
8254 )),
8255 Value::Json(_) => Err(EngineError::Unsupported(
8256 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8257 )),
8258 _ => Err(EngineError::Unsupported(
8262 "ORDER BY of this value type is not supported".into(),
8263 )),
8264 }
8265}
8266
8267fn try_nsw_knn(
8281 stmt: &SelectStatement,
8282 table: &Table,
8283 schema_cols: &[ColumnSchema],
8284 table_alias: &str,
8285) -> Option<Vec<usize>> {
8286 if stmt.distinct {
8287 return None;
8288 }
8289 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8290 if limit == 0 {
8291 return None;
8292 }
8293 if stmt.order_by.len() != 1 {
8297 return None;
8298 }
8299 let order = &stmt.order_by[0];
8300 if order.desc {
8304 return None;
8305 }
8306 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8307 return None;
8308 };
8309 let metric = match op {
8310 BinOp::L2Distance => spg_storage::NswMetric::L2,
8311 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8312 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8313 _ => return None,
8314 };
8315 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8317 (lhs.as_ref(), rhs.as_ref())
8318 else {
8319 return None;
8320 };
8321 if let Some(q) = &col.qualifier
8322 && q != table_alias
8323 {
8324 return None;
8325 }
8326 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8327 let query = literal_to_vector(literal)?;
8328 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8329 if let Some(where_expr) = &stmt.where_ {
8330 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8334 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8335 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8336 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8337 for i in candidates {
8338 let row = &table.rows()[i];
8339 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8340 if matches!(cond, Value::Bool(true)) {
8341 kept.push(i);
8342 if kept.len() >= limit {
8343 break;
8344 }
8345 }
8346 }
8347 Some(kept)
8348 } else {
8349 Some(spg_storage::nsw_query(
8350 table, &idx.name, &query, limit, metric,
8351 ))
8352 }
8353}
8354
8355const NSW_OVER_FETCH_FLOOR: usize = 32;
8359
8360fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8363 match e {
8364 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8365 Expr::Cast { expr, .. } => literal_to_vector(expr),
8366 _ => None,
8367 }
8368}
8369
8370fn materialise_in_order(
8374 stmt: &SelectStatement,
8375 table: &Table,
8376 schema_cols: &[ColumnSchema],
8377 table_alias: &str,
8378 ordered_rows: &[usize],
8379) -> Result<QueryResult, EngineError> {
8380 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8381 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8382 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8383 for &i in ordered_rows {
8384 let row = &table.rows()[i];
8385 let mut values = Vec::with_capacity(projection.len());
8386 for p in &projection {
8387 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8388 }
8389 output_rows.push(Row::new(values));
8390 }
8391 apply_offset_and_limit(
8392 &mut output_rows,
8393 stmt.offset_literal(),
8394 stmt.limit_literal(),
8395 );
8396 let columns: Vec<ColumnSchema> = projection
8397 .into_iter()
8398 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8399 .collect();
8400 Ok(QueryResult::Rows {
8401 columns,
8402 rows: output_rows,
8403 })
8404}
8405
8406fn try_index_seek<'a>(
8407 where_expr: &Expr,
8408 schema_cols: &[ColumnSchema],
8409 catalog: &'a Catalog,
8410 table: &'a Table,
8411 table_alias: &str,
8412) -> Option<Vec<Cow<'a, Row>>> {
8413 if let Expr::Binary {
8420 lhs,
8421 op: BinOp::And,
8422 rhs,
8423 } = where_expr
8424 {
8425 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8428 return Some(rows);
8429 }
8430 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8431 }
8432 let Expr::Binary {
8433 lhs,
8434 op: BinOp::Eq,
8435 rhs,
8436 } = where_expr
8437 else {
8438 return None;
8439 };
8440 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8441 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8442 let idx = table.index_on(col_pos)?;
8443 let key = IndexKey::from_value(&value)?;
8444 let locators = idx.lookup_eq(&key);
8445 let table_name = table.schema().name.as_str();
8446 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8454 for loc in locators {
8455 match *loc {
8456 spg_storage::RowLocator::Hot(i) => {
8457 if let Some(row) = table.rows().get(i) {
8458 out.push(Cow::Borrowed(row));
8459 }
8460 }
8461 spg_storage::RowLocator::Cold { segment_id, .. } => {
8462 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8463 out.push(Cow::Owned(row));
8464 }
8465 }
8466 }
8467 }
8468 Some(out)
8469}
8470
8471fn try_gin_seek<'a>(
8490 where_expr: &Expr,
8491 schema_cols: &[ColumnSchema],
8492 catalog: &'a Catalog,
8493 table: &'a Table,
8494 table_alias: &str,
8495 ctx: &eval::EvalContext<'_>,
8496) -> Option<Vec<Cow<'a, Row>>> {
8497 if let Expr::Binary {
8498 lhs,
8499 op: BinOp::And,
8500 rhs,
8501 } = where_expr
8502 {
8503 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8504 return Some(rows);
8505 }
8506 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8507 }
8508 if let Expr::Binary {
8517 lhs,
8518 op: BinOp::Or,
8519 rhs,
8520 } = where_expr
8521 {
8522 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8523 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8524 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8525 out.extend(left);
8526 out.extend(right);
8527 return Some(out);
8528 }
8529 let Expr::Binary {
8530 lhs,
8531 op: BinOp::TsMatch,
8532 rhs,
8533 } = where_expr
8534 else {
8535 return None;
8536 };
8537 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8542 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8543 let idx = table
8550 .indices()
8551 .iter()
8552 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8553 let candidates = gin_query_candidates(idx, &query)?;
8554 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8556 for loc in candidates {
8557 match loc {
8558 spg_storage::RowLocator::Hot(i) => {
8559 if let Some(row) = table.rows().get(i) {
8560 out.push(Cow::Borrowed(row));
8561 }
8562 }
8563 spg_storage::RowLocator::Cold { .. } => {}
8570 }
8571 }
8572 Some(out)
8573}
8574
8575fn try_trgm_seek<'a>(
8591 where_expr: &Expr,
8592 schema_cols: &[ColumnSchema],
8593 table: &'a Table,
8594 table_alias: &str,
8595) -> Option<Vec<Cow<'a, Row>>> {
8596 if let Expr::Binary {
8597 lhs,
8598 op: BinOp::And,
8599 rhs,
8600 } = where_expr
8601 {
8602 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
8603 return Some(rows);
8604 }
8605 return try_trgm_seek(rhs, schema_cols, table, table_alias);
8606 }
8607 let Expr::Like { expr, pattern, .. } = where_expr else {
8613 return None;
8614 };
8615 let Expr::Column(c) = expr.as_ref() else {
8617 return None;
8618 };
8619 if let Some(q) = &c.qualifier
8620 && q != table_alias
8621 {
8622 return None;
8623 }
8624 let col_pos = schema_cols
8625 .iter()
8626 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
8627 let idx = table
8629 .indices()
8630 .iter()
8631 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
8632 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
8636 return None;
8637 };
8638 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
8639 let mut iter = trigrams.iter();
8642 let first = iter.next()?;
8643 let mut acc: Vec<spg_storage::RowLocator> = {
8644 let mut v = idx.gin_trgm_lookup(first).to_vec();
8645 v.sort_by_key(locator_sort_key);
8646 v.dedup_by_key(|l| locator_sort_key(l));
8647 v
8648 };
8649 for tri in iter {
8650 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
8651 next.sort_by_key(locator_sort_key);
8652 next.dedup_by_key(|l| locator_sort_key(l));
8653 let mut merged: Vec<spg_storage::RowLocator> =
8655 Vec::with_capacity(acc.len().min(next.len()));
8656 let (mut i, mut j) = (0usize, 0usize);
8657 while i < acc.len() && j < next.len() {
8658 let lk = locator_sort_key(&acc[i]);
8659 let rk = locator_sort_key(&next[j]);
8660 match lk.cmp(&rk) {
8661 core::cmp::Ordering::Less => i += 1,
8662 core::cmp::Ordering::Greater => j += 1,
8663 core::cmp::Ordering::Equal => {
8664 merged.push(acc[i]);
8665 i += 1;
8666 j += 1;
8667 }
8668 }
8669 }
8670 acc = merged;
8671 if acc.is_empty() {
8672 break;
8673 }
8674 }
8675 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
8676 for loc in acc {
8677 if let spg_storage::RowLocator::Hot(i) = loc
8678 && let Some(row) = table.rows().get(i)
8679 {
8680 out.push(Cow::Borrowed(row));
8681 }
8682 }
8684 Some(out)
8685}
8686
8687fn resolve_gin_col_query(
8693 col_side: &Expr,
8694 query_side: &Expr,
8695 schema_cols: &[ColumnSchema],
8696 table_alias: &str,
8697 ctx: &eval::EvalContext<'_>,
8698) -> Option<(usize, spg_storage::TsQueryAst)> {
8699 let column = match col_side {
8704 Expr::Column(c) => c,
8705 Expr::FunctionCall { name, args }
8706 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
8707 {
8708 if let Expr::Column(c) = args.last().unwrap() {
8712 c
8713 } else {
8714 return None;
8715 }
8716 }
8717 _ => return None,
8718 };
8719 let c = column;
8720 if let Some(q) = &c.qualifier
8721 && q != table_alias
8722 {
8723 return None;
8724 }
8725 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
8726 let empty_row = Row::new(Vec::new());
8730 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
8731 let Value::TsQuery(q) = v else { return None };
8732 Some((pos, q))
8733}
8734
8735fn gin_query_candidates(
8746 idx: &spg_storage::Index,
8747 query: &spg_storage::TsQueryAst,
8748) -> Option<Vec<spg_storage::RowLocator>> {
8749 use spg_storage::TsQueryAst;
8750 match query {
8751 TsQueryAst::Term { word, .. } => {
8752 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
8753 v.sort_by_key(locator_sort_key);
8754 v.dedup_by_key(|l| locator_sort_key(l));
8755 Some(v)
8756 }
8757 TsQueryAst::And(l, r) => {
8758 let mut left = gin_query_candidates(idx, l)?;
8759 let mut right = gin_query_candidates(idx, r)?;
8760 left.sort_by_key(locator_sort_key);
8761 right.sort_by_key(locator_sort_key);
8762 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
8764 let (mut i, mut j) = (0usize, 0usize);
8765 while i < left.len() && j < right.len() {
8766 let lk = locator_sort_key(&left[i]);
8767 let rk = locator_sort_key(&right[j]);
8768 match lk.cmp(&rk) {
8769 core::cmp::Ordering::Less => i += 1,
8770 core::cmp::Ordering::Greater => j += 1,
8771 core::cmp::Ordering::Equal => {
8772 out.push(left[i]);
8773 i += 1;
8774 j += 1;
8775 }
8776 }
8777 }
8778 Some(out)
8779 }
8780 TsQueryAst::Or(l, r) => {
8781 let mut out = gin_query_candidates(idx, l)?;
8782 out.extend(gin_query_candidates(idx, r)?);
8783 out.sort_by_key(locator_sort_key);
8784 out.dedup_by_key(|l| locator_sort_key(l));
8785 Some(out)
8786 }
8787 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
8792 }
8793}
8794
8795fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
8800 match *l {
8801 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
8802 spg_storage::RowLocator::Cold {
8803 segment_id,
8804 page_offset,
8805 } => (1, u64::from(segment_id), u64::from(page_offset)),
8806 }
8807}
8808
8809fn try_pk_predicate(
8821 where_expr: &Expr,
8822 schema_cols: &[ColumnSchema],
8823 table_alias: &str,
8824) -> Option<(usize, IndexKey)> {
8825 let Expr::Binary {
8826 lhs,
8827 op: BinOp::Eq,
8828 rhs,
8829 } = where_expr
8830 else {
8831 return None;
8832 };
8833 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8834 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8835 let key = IndexKey::from_value(&value)?;
8836 Some((col_pos, key))
8837}
8838
8839fn resolve_col_literal_pair(
8840 col_side: &Expr,
8841 lit_side: &Expr,
8842 schema_cols: &[ColumnSchema],
8843 table_alias: &str,
8844) -> Option<(usize, Value)> {
8845 let Expr::Column(c) = col_side else {
8846 return None;
8847 };
8848 if let Some(q) = &c.qualifier
8849 && q != table_alias
8850 {
8851 return None;
8852 }
8853 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
8854 let Expr::Literal(l) = lit_side else {
8855 return None;
8856 };
8857 let v = match l {
8858 Literal::Integer(n) => {
8859 if let Ok(small) = i32::try_from(*n) {
8860 Value::Int(small)
8861 } else {
8862 Value::BigInt(*n)
8863 }
8864 }
8865 Literal::Float(x) => Value::Float(*x),
8866 Literal::String(s) => Value::Text(s.clone()),
8867 Literal::Bool(b) => Value::Bool(*b),
8868 Literal::Null => Value::Null,
8869 Literal::Vector(_) | Literal::Interval { .. } => return None,
8872 };
8873 Some((pos, v))
8874}
8875
8876fn resolve_projection_column<'a>(
8881 c: &ColumnName,
8882 schema_cols: &'a [ColumnSchema],
8883 table_alias: &str,
8884) -> Result<&'a ColumnSchema, EngineError> {
8885 if let Some(q) = &c.qualifier {
8886 let composite = alloc::format!("{q}.{name}", name = c.name);
8887 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
8888 return Ok(s);
8889 }
8890 if q == table_alias
8893 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
8894 {
8895 return Ok(s);
8896 }
8897 let prefix = alloc::format!("{q}.");
8901 let qualifier_known =
8902 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
8903 if !qualifier_known {
8904 return Err(EngineError::Eval(EvalError::UnknownQualifier {
8905 qualifier: q.clone(),
8906 }));
8907 }
8908 return Err(EngineError::Eval(EvalError::ColumnNotFound {
8909 name: c.name.clone(),
8910 }));
8911 }
8912 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
8913 return Ok(s);
8914 }
8915 let suffix = alloc::format!(".{name}", name = c.name);
8916 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
8917 let first = matches.next();
8918 let extra = matches.next();
8919 match (first, extra) {
8920 (Some(s), None) => Ok(s),
8921 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
8922 detail: alloc::format!("ambiguous column reference: {}", c.name),
8923 })),
8924 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
8925 name: c.name.clone(),
8926 })),
8927 }
8928}
8929
8930fn build_projection(
8931 items: &[SelectItem],
8932 schema_cols: &[ColumnSchema],
8933 table_alias: &str,
8934) -> Result<Vec<ProjectedItem>, EngineError> {
8935 let mut out = Vec::new();
8936 for item in items {
8937 match item {
8938 SelectItem::Wildcard => {
8939 for col in schema_cols {
8940 out.push(ProjectedItem {
8941 expr: Expr::Column(ColumnName {
8942 qualifier: None,
8943 name: col.name.clone(),
8944 }),
8945 output_name: col.name.clone(),
8946 ty: col.ty,
8947 nullable: col.nullable,
8948 });
8949 }
8950 }
8951 SelectItem::Expr { expr, alias } => {
8952 if let Expr::Column(c) = expr {
8959 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
8960 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
8961 out.push(ProjectedItem {
8962 expr: expr.clone(),
8963 output_name,
8964 ty: sch.ty,
8965 nullable: sch.nullable,
8966 });
8967 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
8968 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
8969 out.push(ProjectedItem {
8970 expr: expr.clone(),
8971 output_name,
8972 ty: shape.ty,
8973 nullable: shape.nullable,
8974 });
8975 } else {
8976 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
8977 out.push(ProjectedItem {
8978 expr: expr.clone(),
8979 output_name,
8980 ty: DataType::Text,
8981 nullable: true,
8982 });
8983 }
8984 }
8985 }
8986 }
8987 Ok(out)
8988}
8989
8990fn numeric_from_integer(
8994 n: i128,
8995 precision: u8,
8996 scale: u8,
8997 col_name: &str,
8998) -> Result<Value, EngineError> {
8999 let factor = pow10_i128(scale);
9000 let scaled = n.checked_mul(factor).ok_or_else(|| {
9001 EngineError::Unsupported(alloc::format!(
9002 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9003 ))
9004 })?;
9005 check_precision(scaled, precision, col_name)?;
9006 Ok(Value::Numeric { scaled, scale })
9007}
9008
9009#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9012fn numeric_from_float(
9013 x: f64,
9014 precision: u8,
9015 scale: u8,
9016 col_name: &str,
9017) -> Result<Value, EngineError> {
9018 if !x.is_finite() {
9019 return Err(EngineError::Unsupported(alloc::format!(
9020 "cannot store non-finite float in NUMERIC column `{col_name}`"
9021 )));
9022 }
9023 let mut factor = 1.0_f64;
9024 for _ in 0..scale {
9025 factor *= 10.0;
9026 }
9027 let shifted = x * factor;
9032 let biased = if shifted >= 0.0 {
9033 shifted + 0.5
9034 } else {
9035 shifted - 0.5
9036 };
9037 if !(-1e38..=1e38).contains(&biased) {
9040 return Err(EngineError::Unsupported(alloc::format!(
9041 "value {x} overflows NUMERIC range for column `{col_name}`"
9042 )));
9043 }
9044 let scaled = biased as i128;
9045 check_precision(scaled, precision, col_name)?;
9046 Ok(Value::Numeric { scaled, scale })
9047}
9048
9049fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9056 let s = s.trim();
9057 if s.is_empty() {
9058 return None;
9059 }
9060 let (negative, rest) = match s.as_bytes()[0] {
9061 b'-' => (true, &s[1..]),
9062 b'+' => (false, &s[1..]),
9063 _ => (false, s),
9064 };
9065 if rest.is_empty() {
9066 return None;
9067 }
9068 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9072 return None;
9073 }
9074 let (int_part, frac_part) = match rest.find('.') {
9075 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9076 None => (rest, ""),
9077 };
9078 if int_part.is_empty() && frac_part.is_empty() {
9079 return None;
9080 }
9081 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9082 return None;
9083 }
9084 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9085 return None;
9086 }
9087 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9088 if scale_u32 > u32::from(u8::MAX) {
9089 return None;
9090 }
9091 let scale = scale_u32 as u8;
9092 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9093 if negative {
9094 digits.push('-');
9095 }
9096 digits.push_str(int_part);
9097 digits.push_str(frac_part);
9098 let digits = if digits == "-" {
9100 return None;
9101 } else if digits.is_empty() {
9102 "0"
9103 } else {
9104 digits.as_str()
9105 };
9106 let mantissa: i128 = digits.parse().ok()?;
9107 Some((mantissa, scale))
9108}
9109
9110fn numeric_rescale(
9113 scaled: i128,
9114 src_scale: u8,
9115 precision: u8,
9116 dst_scale: u8,
9117 col_name: &str,
9118) -> Result<Value, EngineError> {
9119 let new_scaled = if dst_scale >= src_scale {
9120 let bump = pow10_i128(dst_scale - src_scale);
9121 scaled.checked_mul(bump).ok_or_else(|| {
9122 EngineError::Unsupported(alloc::format!(
9123 "overflow rescaling NUMERIC for column `{col_name}`"
9124 ))
9125 })?
9126 } else {
9127 let drop = pow10_i128(src_scale - dst_scale);
9128 let half = drop / 2;
9129 if scaled >= 0 {
9130 (scaled + half) / drop
9131 } else {
9132 (scaled - half) / drop
9133 }
9134 };
9135 check_precision(new_scaled, precision, col_name)?;
9136 Ok(Value::Numeric {
9137 scaled: new_scaled,
9138 scale: dst_scale,
9139 })
9140}
9141
9142const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9145 if scale == 0 {
9146 return scaled;
9147 }
9148 let factor = pow10_i128_const(scale);
9149 scaled / factor
9150}
9151
9152fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9156 if precision == 0 {
9157 return Ok(());
9158 }
9159 let limit = pow10_i128(precision);
9160 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9161 return Err(EngineError::Unsupported(alloc::format!(
9162 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9163 )));
9164 }
9165 Ok(())
9166}
9167
9168const fn pow10_i128_const(p: u8) -> i128 {
9169 let mut acc: i128 = 1;
9170 let mut i = 0;
9171 while i < p {
9172 acc *= 10;
9173 i += 1;
9174 }
9175 acc
9176}
9177
9178fn pow10_i128(p: u8) -> i128 {
9179 pow10_i128_const(p)
9180}
9181
9182impl Engine {
9197 #[allow(
9208 clippy::too_many_lines,
9209 clippy::type_complexity,
9210 clippy::needless_range_loop
9211 )] fn exec_select_with_window(
9213 &self,
9214 stmt: &SelectStatement,
9215 cancel: CancelToken<'_>,
9216 ) -> Result<QueryResult, EngineError> {
9217 let from = stmt.from.as_ref().ok_or_else(|| {
9218 EngineError::Unsupported("window functions require a FROM clause".into())
9219 })?;
9220 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9229 let filtered: Vec<Row>;
9230 if from.joins.is_empty() {
9231 let primary = &from.primary;
9232 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9233 StorageError::TableNotFound {
9234 name: primary.name.clone(),
9235 }
9236 })?;
9237 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9238 schema_cols_owned = table.schema().columns.clone();
9239 alias_opt = Some(alias);
9240 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9245 let mut owned: Vec<Row> = Vec::new();
9246 for (i, row) in table.rows().iter().enumerate() {
9247 if i.is_multiple_of(256) {
9248 cancel.check()?;
9249 }
9250 if let Some(w) = &stmt.where_ {
9251 let cond = eval::eval_expr(w, row, &ctx)?;
9252 if !matches!(cond, Value::Bool(true)) {
9253 continue;
9254 }
9255 }
9256 owned.push(row.clone());
9257 }
9258 filtered = owned;
9259 } else {
9260 let (combined_schema, rows) =
9261 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9262 schema_cols_owned = combined_schema;
9263 alias_opt = None;
9264 filtered = rows;
9265 }
9266 let schema_cols = &schema_cols_owned;
9267 let ctx = self.ev_ctx(schema_cols, alias_opt);
9268 let alias = alias_opt.unwrap_or("");
9269 let n_rows = filtered.len();
9270 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9274
9275 let mut window_nodes: Vec<Expr> = Vec::new();
9277 for item in &stmt.items {
9278 if let SelectItem::Expr { expr, .. } = item {
9279 collect_window_nodes(expr, &mut window_nodes);
9280 }
9281 }
9282
9283 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9286 for wnode in &window_nodes {
9287 let Expr::WindowFunction {
9288 name,
9289 args,
9290 partition_by,
9291 order_by,
9292 frame,
9293 null_treatment,
9294 } = wnode
9295 else {
9296 unreachable!("collect_window_nodes pushes only WindowFunction");
9297 };
9298 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9300 Vec::with_capacity(n_rows);
9301 for (i, row) in filtered.iter().enumerate() {
9302 let pkey: Vec<Value> = partition_by
9303 .iter()
9304 .map(|p| eval::eval_expr(p, row, &ctx))
9305 .collect::<Result<_, _>>()?;
9306 let okey: Vec<(Value, bool)> = order_by
9307 .iter()
9308 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9309 .collect::<Result<_, _>>()?;
9310 indexed.push((pkey, okey, i));
9311 }
9312 indexed.sort_by(|a, b| {
9315 let p_cmp = partition_key_cmp(&a.0, &b.0);
9316 if p_cmp != core::cmp::Ordering::Equal {
9317 return p_cmp;
9318 }
9319 order_key_cmp(&a.1, &b.1)
9320 });
9321 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9323 let mut p_start = 0;
9324 while p_start < indexed.len() {
9325 let mut p_end = p_start + 1;
9326 while p_end < indexed.len()
9327 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9328 == core::cmp::Ordering::Equal
9329 {
9330 p_end += 1;
9331 }
9332 compute_window_partition(
9334 name,
9335 args,
9336 !order_by.is_empty(),
9337 frame.as_ref(),
9338 *null_treatment,
9339 &indexed[p_start..p_end],
9340 &filtered_refs,
9341 &ctx,
9342 &mut out_vals,
9343 )?;
9344 p_start = p_end;
9345 }
9346 win_vals.push(out_vals);
9347 }
9348
9349 let mut ext_cols = schema_cols.clone();
9351 for i in 0..window_nodes.len() {
9352 ext_cols.push(ColumnSchema::new(
9353 alloc::format!("__win_{i}"),
9354 DataType::Text, true,
9356 ));
9357 }
9358 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9360 for i in 0..n_rows {
9361 let mut values = filtered[i].values.clone();
9362 for w in 0..window_nodes.len() {
9363 values.push(win_vals[w][i].clone());
9364 }
9365 ext_rows.push(Row::new(values));
9366 }
9367 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9369 for item in &stmt.items {
9370 let new_item = match item {
9371 SelectItem::Wildcard => SelectItem::Wildcard,
9372 SelectItem::Expr { expr, alias } => {
9373 let mut e = expr.clone();
9374 rewrite_window_to_columns(&mut e, &window_nodes);
9375 SelectItem::Expr {
9376 expr: e,
9377 alias: alias.clone(),
9378 }
9379 }
9380 };
9381 rewritten_items.push(new_item);
9382 }
9383
9384 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9390 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9391 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9392 for (i, row) in ext_rows.iter().enumerate() {
9393 if i.is_multiple_of(256) {
9394 cancel.check()?;
9395 }
9396 let mut values = Vec::with_capacity(projection.len());
9397 for p in &projection {
9398 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9399 }
9400 let order_keys = if stmt.order_by.is_empty() {
9401 Vec::new()
9402 } else {
9403 let mut keys = Vec::with_capacity(stmt.order_by.len());
9404 for o in &stmt.order_by {
9405 let mut e = o.expr.clone();
9406 rewrite_window_to_columns(&mut e, &window_nodes);
9407 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9408 keys.push(value_to_order_key(&key)?);
9409 }
9410 keys
9411 };
9412 tagged.push((order_keys, Row::new(values)));
9413 }
9414 if !stmt.order_by.is_empty() {
9416 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9417 sort_by_keys(&mut tagged, &descs);
9418 }
9419 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9420 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9421 let final_cols: Vec<ColumnSchema> = projection
9422 .into_iter()
9423 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9424 .collect();
9425 Ok(QueryResult::Rows {
9426 columns: final_cols,
9427 rows: out_rows,
9428 })
9429 }
9430
9431 fn exec_select_with_meta_views(
9448 &self,
9449 stmt: &SelectStatement,
9450 cancel: CancelToken<'_>,
9451 ) -> Result<QueryResult, EngineError> {
9452 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9453 collect_meta_view_names(stmt, &mut needed);
9454 let mut catalog = self.active_catalog().clone();
9455 for view in &needed {
9456 if catalog.get(view).is_some() {
9457 continue;
9458 }
9459 match view.as_str() {
9460 "__spg_info_columns" => {
9461 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9462 materialise_meta_view(&mut catalog, view, schema, rows)?;
9463 }
9464 "__spg_info_tables" => {
9465 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9466 materialise_meta_view(&mut catalog, view, schema, rows)?;
9467 }
9468 "__spg_pg_class" => {
9469 let (schema, rows) = synth_pg_class(self.active_catalog());
9470 materialise_meta_view(&mut catalog, view, schema, rows)?;
9471 }
9472 "__spg_pg_attribute" => {
9473 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9474 materialise_meta_view(&mut catalog, view, schema, rows)?;
9475 }
9476 "__spg_pg_type" => {
9479 let (schema, rows) = synth_pg_type(self.active_catalog());
9480 materialise_meta_view(&mut catalog, view, schema, rows)?;
9481 }
9482 "__spg_pg_proc" => {
9485 let (schema, rows) = synth_pg_proc(self.active_catalog());
9486 materialise_meta_view(&mut catalog, view, schema, rows)?;
9487 }
9488 "__spg_pg_namespace" => {
9491 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9492 materialise_meta_view(&mut catalog, view, schema, rows)?;
9493 }
9494 "__spg_pg_indexes" => {
9497 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9498 materialise_meta_view(&mut catalog, view, schema, rows)?;
9499 }
9500 "__spg_pg_index" => {
9503 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9504 materialise_meta_view(&mut catalog, view, schema, rows)?;
9505 }
9506 "__spg_pg_constraint" => {
9509 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9510 materialise_meta_view(&mut catalog, view, schema, rows)?;
9511 }
9512 "__spg_pg_database" => {
9517 let (schema, rows) = synth_pg_database(self.active_catalog());
9518 materialise_meta_view(&mut catalog, view, schema, rows)?;
9519 }
9520 "__spg_pg_roles" | "__spg_pg_user" => {
9521 let (schema, rows) = synth_pg_roles(self);
9522 materialise_meta_view(&mut catalog, view, schema, rows)?;
9523 }
9524 "__spg_pg_views" => {
9528 let (schema, rows) = synth_pg_views(self.active_catalog());
9529 materialise_meta_view(&mut catalog, view, schema, rows)?;
9530 }
9531 "__spg_pg_matviews" => {
9535 let (schema, _) = synth_pg_views(self.active_catalog());
9536 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9537 }
9538 "__spg_pg_settings" => {
9540 let (schema, rows) = synth_pg_settings(self);
9541 materialise_meta_view(&mut catalog, view, schema, rows)?;
9542 }
9543 "__spg_info_key_column_usage" => {
9545 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9546 materialise_meta_view(&mut catalog, view, schema, rows)?;
9547 }
9548 "__spg_info_referential_constraints" => {
9550 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9551 materialise_meta_view(&mut catalog, view, schema, rows)?;
9552 }
9553 "__spg_info_statistics" => {
9555 let (schema, rows) = synth_info_statistics(self.active_catalog());
9556 materialise_meta_view(&mut catalog, view, schema, rows)?;
9557 }
9558 "__spg_info_routines" => {
9560 let (schema, rows) = synth_info_routines();
9561 materialise_meta_view(&mut catalog, view, schema, rows)?;
9562 }
9563 "__spg_mysql_user" => {
9565 let (schema, rows) = synth_mysql_user(self);
9566 materialise_meta_view(&mut catalog, view, schema, rows)?;
9567 }
9568 "__spg_mysql_db" => {
9569 let (schema, rows) = synth_mysql_db();
9570 materialise_meta_view(&mut catalog, view, schema, rows)?;
9571 }
9572 _ => {
9573 return Err(EngineError::Unsupported(alloc::format!(
9574 "meta view {view:?} is not yet materialisable; \
9575 v7.16.2 covers information_schema.columns / .tables \
9576 and pg_catalog.pg_class / pg_attribute; \
9577 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9578 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9579 pg_user / pg_views / pg_matviews / pg_settings"
9580 )));
9581 }
9582 }
9583 }
9584 let mut temp = Engine::restore(catalog);
9585 if let Some(c) = self.clock {
9586 temp = temp.with_clock(c);
9587 }
9588 if let Some(f) = self.salt_fn {
9589 temp = temp.with_salt_fn(f);
9590 }
9591 temp.meta_views_materialised = true;
9592 temp.exec_select_cancel(stmt, cancel)
9593 }
9594
9595 fn exec_with_ctes(
9596 &self,
9597 stmt: &SelectStatement,
9598 cancel: CancelToken<'_>,
9599 ) -> Result<QueryResult, EngineError> {
9600 cancel.check()?;
9601 let mut catalog = self.active_catalog().clone();
9602 for cte in &stmt.ctes {
9603 if catalog.get(&cte.name).is_some() {
9604 return Err(EngineError::Unsupported(alloc::format!(
9605 "CTE name {:?} shadows an existing table; rename the CTE",
9606 cte.name
9607 )));
9608 }
9609 let (columns, rows) = if cte.recursive {
9610 self.materialise_recursive_cte(cte, &catalog, cancel)?
9611 } else {
9612 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
9613 let QueryResult::Rows { columns, rows } = body_result else {
9614 return Err(EngineError::Unsupported(alloc::format!(
9615 "CTE {:?} body did not return rows",
9616 cte.name
9617 )));
9618 };
9619 (columns, rows)
9620 };
9621 let inferred = infer_column_types(&columns, &rows);
9626 let mut columns = inferred;
9627 if !cte.column_overrides.is_empty() {
9629 if cte.column_overrides.len() != columns.len() {
9630 return Err(EngineError::Unsupported(alloc::format!(
9631 "CTE {:?} column list has {} names but body returns {} columns",
9632 cte.name,
9633 cte.column_overrides.len(),
9634 columns.len()
9635 )));
9636 }
9637 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9638 col.name.clone_from(name);
9639 }
9640 }
9641 let schema = TableSchema::new(cte.name.clone(), columns);
9642 catalog.create_table(schema).map_err(EngineError::Storage)?;
9643 let table = catalog
9644 .get_mut(&cte.name)
9645 .expect("just-created CTE table must exist");
9646 for row in rows {
9647 table.insert(row).map_err(EngineError::Storage)?;
9648 }
9649 }
9650 let mut body = stmt.clone();
9653 body.ctes = Vec::new();
9654 let mut temp = Engine::restore(catalog);
9655 if let Some(c) = self.clock {
9656 temp = temp.with_clock(c);
9657 }
9658 if let Some(f) = self.salt_fn {
9659 temp = temp.with_salt_fn(f);
9660 }
9661 temp.exec_select_cancel(&body, cancel)
9662 }
9663
9664 #[allow(clippy::too_many_lines)]
9674 fn materialise_recursive_cte(
9675 &self,
9676 cte: &spg_sql::ast::Cte,
9677 base_catalog: &Catalog,
9678 cancel: CancelToken<'_>,
9679 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
9680 const MAX_TOTAL_ROWS: usize = 1_000_000;
9681 const MAX_ITERATIONS: usize = 100_000;
9682 cancel.check()?;
9683 if cte.body.unions.is_empty() {
9684 return Err(EngineError::Unsupported(alloc::format!(
9685 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
9686 cte.name
9687 )));
9688 }
9689 let mut anchor = cte.body.clone();
9691 let union_terms = core::mem::take(&mut anchor.unions);
9692 anchor.ctes = Vec::new();
9693 if select_refers_to(&anchor, &cte.name) {
9695 return Err(EngineError::Unsupported(alloc::format!(
9696 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
9697 cte.name
9698 )));
9699 }
9700 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
9701 let QueryResult::Rows {
9702 columns: anchor_cols,
9703 rows: anchor_rows,
9704 } = anchor_result
9705 else {
9706 return Err(EngineError::Unsupported(alloc::format!(
9707 "WITH RECURSIVE {:?}: anchor did not return rows",
9708 cte.name
9709 )));
9710 };
9711 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
9715 if !cte.column_overrides.is_empty() {
9716 if cte.column_overrides.len() != columns.len() {
9717 return Err(EngineError::Unsupported(alloc::format!(
9718 "CTE {:?} column list has {} names but anchor returns {} columns",
9719 cte.name,
9720 cte.column_overrides.len(),
9721 columns.len()
9722 )));
9723 }
9724 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9725 col.name.clone_from(name);
9726 }
9727 }
9728 let mut all_rows: Vec<Row> = anchor_rows.clone();
9729 let mut working_set: Vec<Row> = anchor_rows;
9730 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
9731 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
9734 if !all_union_all {
9735 for r in &all_rows {
9736 seen.insert(encode_row_key(r));
9737 }
9738 }
9739 for iter in 0..MAX_ITERATIONS {
9740 cancel.check()?;
9741 if working_set.is_empty() {
9742 break;
9743 }
9744 let mut iter_catalog = base_catalog.clone();
9746 let schema = TableSchema::new(cte.name.clone(), columns.clone());
9747 iter_catalog
9748 .create_table(schema)
9749 .map_err(EngineError::Storage)?;
9750 {
9751 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
9752 for row in &working_set {
9753 table.insert(row.clone()).map_err(EngineError::Storage)?;
9754 }
9755 }
9756 let mut iter_engine = Engine::restore(iter_catalog);
9757 if let Some(c) = self.clock {
9758 iter_engine = iter_engine.with_clock(c);
9759 }
9760 if let Some(f) = self.salt_fn {
9761 iter_engine = iter_engine.with_salt_fn(f);
9762 }
9763 let mut next_set: Vec<Row> = Vec::new();
9765 for (_, term) in &union_terms {
9766 let mut term = term.clone();
9767 term.ctes = Vec::new();
9768 let r = iter_engine.exec_select_cancel(&term, cancel)?;
9769 let QueryResult::Rows {
9770 columns: rc,
9771 rows: rs,
9772 } = r
9773 else {
9774 return Err(EngineError::Unsupported(alloc::format!(
9775 "WITH RECURSIVE {:?}: recursive term did not return rows",
9776 cte.name
9777 )));
9778 };
9779 if rc.len() != columns.len() {
9780 return Err(EngineError::Unsupported(alloc::format!(
9781 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
9782 cte.name,
9783 rc.len(),
9784 columns.len()
9785 )));
9786 }
9787 for row in rs {
9788 if !all_union_all {
9789 let key = encode_row_key(&row);
9790 if !seen.insert(key) {
9791 continue;
9792 }
9793 }
9794 next_set.push(row);
9795 }
9796 }
9797 if next_set.is_empty() {
9798 break;
9799 }
9800 all_rows.extend(next_set.iter().cloned());
9801 working_set = next_set;
9802 if all_rows.len() > MAX_TOTAL_ROWS {
9803 return Err(EngineError::Unsupported(alloc::format!(
9804 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
9805 cte.name
9806 )));
9807 }
9808 if iter + 1 == MAX_ITERATIONS {
9809 return Err(EngineError::Unsupported(alloc::format!(
9810 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
9811 cte.name
9812 )));
9813 }
9814 }
9815 Ok((columns, all_rows))
9816 }
9817
9818 fn resolve_select_subqueries(
9819 &self,
9820 stmt: &mut SelectStatement,
9821 cancel: CancelToken<'_>,
9822 ) -> Result<(), EngineError> {
9823 for item in &mut stmt.items {
9824 if let SelectItem::Expr { expr, .. } = item {
9825 self.resolve_expr_subqueries(expr, cancel)?;
9826 }
9827 }
9828 if let Some(w) = &mut stmt.where_ {
9829 self.resolve_expr_subqueries(w, cancel)?;
9830 }
9831 if let Some(gs) = &mut stmt.group_by {
9832 for g in gs {
9833 self.resolve_expr_subqueries(g, cancel)?;
9834 }
9835 }
9836 if let Some(h) = &mut stmt.having {
9837 self.resolve_expr_subqueries(h, cancel)?;
9838 }
9839 for o in &mut stmt.order_by {
9840 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
9841 }
9842 for (_, peer) in &mut stmt.unions {
9843 self.resolve_select_subqueries(peer, cancel)?;
9844 }
9845 Ok(())
9846 }
9847
9848 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
9850 &self,
9851 e: &mut Expr,
9852 cancel: CancelToken<'_>,
9853 ) -> Result<(), EngineError> {
9854 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
9856 *e = replacement;
9857 return Ok(());
9858 }
9859 match e {
9860 Expr::Binary { lhs, rhs, .. } => {
9861 self.resolve_expr_subqueries(lhs, cancel)?;
9862 self.resolve_expr_subqueries(rhs, cancel)?;
9863 }
9864 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
9865 self.resolve_expr_subqueries(expr, cancel)?;
9866 }
9867 Expr::FunctionCall { args, .. } => {
9868 for a in args {
9869 self.resolve_expr_subqueries(a, cancel)?;
9870 }
9871 }
9872 Expr::Like { expr, pattern, .. } => {
9873 self.resolve_expr_subqueries(expr, cancel)?;
9874 self.resolve_expr_subqueries(pattern, cancel)?;
9875 }
9876 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
9877 Expr::WindowFunction {
9880 args,
9881 partition_by,
9882 order_by,
9883 ..
9884 } => {
9885 for a in args {
9886 self.resolve_expr_subqueries(a, cancel)?;
9887 }
9888 for p in partition_by {
9889 self.resolve_expr_subqueries(p, cancel)?;
9890 }
9891 for (e, _) in order_by {
9892 self.resolve_expr_subqueries(e, cancel)?;
9893 }
9894 }
9895 Expr::ScalarSubquery(_)
9899 | Expr::Exists { .. }
9900 | Expr::InSubquery { .. }
9901 | Expr::Literal(_)
9902 | Expr::Placeholder(_)
9903 | Expr::Column(_) => {}
9904 Expr::Array(items) => {
9906 for elem in items {
9907 self.resolve_expr_subqueries(elem, cancel)?;
9908 }
9909 }
9910 Expr::ArraySubscript { target, index } => {
9911 self.resolve_expr_subqueries(target, cancel)?;
9912 self.resolve_expr_subqueries(index, cancel)?;
9913 }
9914 Expr::AnyAll { expr, array, .. } => {
9915 self.resolve_expr_subqueries(expr, cancel)?;
9916 self.resolve_expr_subqueries(array, cancel)?;
9917 }
9918 Expr::Case {
9919 operand,
9920 branches,
9921 else_branch,
9922 } => {
9923 if let Some(o) = operand {
9924 self.resolve_expr_subqueries(o, cancel)?;
9925 }
9926 for (w, t) in branches {
9927 self.resolve_expr_subqueries(w, cancel)?;
9928 self.resolve_expr_subqueries(t, cancel)?;
9929 }
9930 if let Some(e) = else_branch {
9931 self.resolve_expr_subqueries(e, cancel)?;
9932 }
9933 }
9934 }
9935 Ok(())
9936 }
9937
9938 fn eval_expr_with_correlated(
9946 &self,
9947 expr: &Expr,
9948 row: &Row,
9949 ctx: &EvalContext<'_>,
9950 cancel: CancelToken<'_>,
9951 memo: Option<&mut memoize::MemoizeCache>,
9952 ) -> Result<Value, EngineError> {
9953 if !expr_has_subquery(expr) {
9954 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
9955 }
9956 let mut e = expr.clone();
9957 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
9958 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
9959 }
9960
9961 fn resolve_correlated_in_expr(
9962 &self,
9963 e: &mut Expr,
9964 row: &Row,
9965 ctx: &EvalContext<'_>,
9966 cancel: CancelToken<'_>,
9967 mut memo: Option<&mut memoize::MemoizeCache>,
9968 ) -> Result<(), EngineError> {
9969 match e {
9970 Expr::ScalarSubquery(inner) => {
9971 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
9976 subquery_repr: alloc::format!("{}", **inner),
9977 outer_values: row.values.clone(),
9978 });
9979 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
9980 && let Some(cached) = cache.get(k)
9981 {
9982 *e = value_to_literal_expr(cached)?;
9983 return Ok(());
9984 }
9985 let mut s = (**inner).clone();
9986 substitute_outer_columns(&mut s, row, ctx);
9987 let r = self.exec_select_cancel(&s, cancel)?;
9988 let QueryResult::Rows { rows, .. } = r else {
9989 return Err(EngineError::Unsupported(
9990 "scalar subquery: inner did not return rows".into(),
9991 ));
9992 };
9993 let value = match rows.as_slice() {
9994 [] => Value::Null,
9995 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
9996 _ => {
9997 return Err(EngineError::Unsupported(alloc::format!(
9998 "scalar subquery returned {} rows; expected 0 or 1",
9999 rows.len()
10000 )));
10001 }
10002 };
10003 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10004 cache.insert(k, value.clone());
10005 }
10006 *e = value_to_literal_expr(value)?;
10007 }
10008 Expr::Exists { subquery, negated } => {
10009 let mut s = (**subquery).clone();
10010 substitute_outer_columns(&mut s, row, ctx);
10011 let r = self.exec_select_cancel(&s, cancel)?;
10012 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10013 let bit = if *negated { !exists } else { exists };
10014 *e = Expr::Literal(Literal::Bool(bit));
10015 }
10016 Expr::InSubquery {
10017 expr: lhs,
10018 subquery,
10019 negated,
10020 } => {
10021 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10022 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10023 let mut s = (**subquery).clone();
10024 substitute_outer_columns(&mut s, row, ctx);
10025 let r = self.exec_select_cancel(&s, cancel)?;
10026 let QueryResult::Rows { columns, rows, .. } = r else {
10027 return Err(EngineError::Unsupported(
10028 "IN-subquery: inner did not return rows".into(),
10029 ));
10030 };
10031 if columns.len() != 1 {
10032 return Err(EngineError::Unsupported(alloc::format!(
10033 "IN-subquery must project exactly one column; got {}",
10034 columns.len()
10035 )));
10036 }
10037 let mut found = false;
10038 let mut any_null = false;
10039 for r0 in rows {
10040 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10041 if v.is_null() {
10042 any_null = true;
10043 continue;
10044 }
10045 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10046 found = true;
10047 break;
10048 }
10049 }
10050 let bit = if found {
10051 !*negated
10052 } else if any_null {
10053 return Err(EngineError::Unsupported(
10054 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10055 ));
10056 } else {
10057 *negated
10058 };
10059 *e = Expr::Literal(Literal::Bool(bit));
10060 }
10061 Expr::Binary { lhs, rhs, .. } => {
10062 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10063 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10064 }
10065 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10066 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10067 }
10068 Expr::Like { expr, pattern, .. } => {
10069 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10070 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10071 }
10072 Expr::FunctionCall { args, .. } => {
10073 for a in args {
10074 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10075 }
10076 }
10077 Expr::Extract { source, .. } => {
10078 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10079 }
10080 Expr::WindowFunction { .. }
10081 | Expr::Literal(_)
10082 | Expr::Placeholder(_)
10083 | Expr::Column(_) => {}
10084 Expr::Array(items) => {
10086 for elem in items {
10087 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10088 }
10089 }
10090 Expr::ArraySubscript { target, index } => {
10091 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10092 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10093 }
10094 Expr::AnyAll { expr, array, .. } => {
10095 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10096 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10097 }
10098 Expr::Case {
10099 operand,
10100 branches,
10101 else_branch,
10102 } => {
10103 if let Some(o) = operand {
10104 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10105 }
10106 for (w, t) in branches {
10107 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10108 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10109 }
10110 if let Some(e) = else_branch {
10111 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10112 }
10113 }
10114 }
10115 Ok(())
10116 }
10117
10118 fn subquery_replacement(
10119 &self,
10120 e: &Expr,
10121 cancel: CancelToken<'_>,
10122 ) -> Result<Option<Expr>, EngineError> {
10123 match e {
10124 Expr::ScalarSubquery(inner) => {
10125 let mut s = (**inner).clone();
10126 self.resolve_select_subqueries(&mut s, cancel)?;
10129 let r = match self.exec_bare_select_cancel(&s, cancel) {
10130 Ok(r) => r,
10131 Err(e) if is_correlation_error(&e) => return Ok(None),
10132 Err(e) => return Err(e),
10133 };
10134 let QueryResult::Rows { rows, .. } = r else {
10135 return Err(EngineError::Unsupported(
10136 "scalar subquery: inner statement did not return rows".into(),
10137 ));
10138 };
10139 let value = match rows.as_slice() {
10140 [] => Value::Null,
10141 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10142 _ => {
10143 return Err(EngineError::Unsupported(alloc::format!(
10144 "scalar subquery returned {} rows; expected 0 or 1",
10145 rows.len()
10146 )));
10147 }
10148 };
10149 Ok(Some(value_to_literal_expr(value)?))
10150 }
10151 Expr::Exists { subquery, negated } => {
10152 let mut s = (**subquery).clone();
10153 self.resolve_select_subqueries(&mut s, cancel)?;
10154 let r = match self.exec_bare_select_cancel(&s, cancel) {
10155 Ok(r) => r,
10156 Err(e) if is_correlation_error(&e) => return Ok(None),
10157 Err(e) => return Err(e),
10158 };
10159 let exists = match r {
10160 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10161 QueryResult::CommandOk { .. } => false,
10162 };
10163 let bit = if *negated { !exists } else { exists };
10164 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10165 }
10166 Expr::InSubquery {
10167 expr,
10168 subquery,
10169 negated,
10170 } => {
10171 let mut s = (**subquery).clone();
10172 self.resolve_select_subqueries(&mut s, cancel)?;
10173 let r = match self.exec_bare_select_cancel(&s, cancel) {
10174 Ok(r) => r,
10175 Err(e) if is_correlation_error(&e) => return Ok(None),
10176 Err(e) => return Err(e),
10177 };
10178 let QueryResult::Rows { columns, rows, .. } = r else {
10179 return Err(EngineError::Unsupported(
10180 "IN-subquery: inner statement did not return rows".into(),
10181 ));
10182 };
10183 if columns.len() != 1 {
10184 return Err(EngineError::Unsupported(alloc::format!(
10185 "IN-subquery must project exactly one column; got {}",
10186 columns.len()
10187 )));
10188 }
10189 let mut acc: Option<Expr> = None;
10192 for row in rows {
10193 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10194 let lit = value_to_literal_expr(v)?;
10195 let cmp = Expr::Binary {
10196 lhs: expr.clone(),
10197 op: BinOp::Eq,
10198 rhs: Box::new(lit),
10199 };
10200 acc = Some(match acc {
10201 None => cmp,
10202 Some(prev) => Expr::Binary {
10203 lhs: Box::new(prev),
10204 op: BinOp::Or,
10205 rhs: Box::new(cmp),
10206 },
10207 });
10208 }
10209 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10210 let final_expr = if *negated {
10211 Expr::Unary {
10212 op: UnOp::Not,
10213 expr: Box::new(combined),
10214 }
10215 } else {
10216 combined
10217 };
10218 Ok(Some(final_expr))
10219 }
10220 _ => Ok(None),
10221 }
10222 }
10223}
10224
10225fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10237 if let Some(from) = &stmt.from
10238 && from_refers_to(from, target)
10239 {
10240 return true;
10241 }
10242 for (_, peer) in &stmt.unions {
10243 if select_refers_to(peer, target) {
10244 return true;
10245 }
10246 }
10247 for item in &stmt.items {
10248 if let SelectItem::Expr { expr, .. } = item
10249 && expr_refers_to(expr, target)
10250 {
10251 return true;
10252 }
10253 }
10254 if let Some(w) = &stmt.where_
10255 && expr_refers_to(w, target)
10256 {
10257 return true;
10258 }
10259 false
10260}
10261
10262fn from_refers_to(from: &FromClause, target: &str) -> bool {
10263 if from.primary.name.eq_ignore_ascii_case(target) {
10264 return true;
10265 }
10266 from.joins
10267 .iter()
10268 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10269}
10270
10271fn expr_refers_to(e: &Expr, target: &str) -> bool {
10272 match e {
10273 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10274 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10275 select_refers_to(subquery, target)
10276 }
10277 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10278 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10279 expr_refers_to(expr, target)
10280 }
10281 Expr::Like { expr, pattern, .. } => {
10282 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10283 }
10284 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10285 Expr::Extract { source, .. } => expr_refers_to(source, target),
10286 Expr::WindowFunction {
10287 args,
10288 partition_by,
10289 order_by,
10290 ..
10291 } => {
10292 args.iter().any(|a| expr_refers_to(a, target))
10293 || partition_by.iter().any(|p| expr_refers_to(p, target))
10294 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10295 }
10296 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10297 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10298 Expr::ArraySubscript { target: t, index } => {
10299 expr_refers_to(t, target) || expr_refers_to(index, target)
10300 }
10301 Expr::AnyAll { expr, array, .. } => {
10302 expr_refers_to(expr, target) || expr_refers_to(array, target)
10303 }
10304 Expr::Case {
10305 operand,
10306 branches,
10307 else_branch,
10308 } => {
10309 operand
10310 .as_deref()
10311 .is_some_and(|o| expr_refers_to(o, target))
10312 || branches
10313 .iter()
10314 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10315 || else_branch
10316 .as_deref()
10317 .is_some_and(|e| expr_refers_to(e, target))
10318 }
10319 }
10320}
10321
10322fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10333 let s = match ty {
10334 DataType::Int => "integer",
10335 DataType::BigInt => "bigint",
10336 DataType::SmallInt => "smallint",
10337 DataType::Float => "double precision",
10338 DataType::Bool => "boolean",
10339 DataType::Text => "text",
10340 DataType::Varchar(_) => "character varying",
10341 DataType::Date => "date",
10342 DataType::Timestamp => "timestamp without time zone",
10343 DataType::Timestamptz => "timestamp with time zone",
10344 DataType::Json => "jsonb",
10345 DataType::Bytes => "bytea",
10346 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10347 DataType::TsVector => "tsvector",
10348 DataType::TsQuery => "tsquery",
10349 DataType::Vector { .. } => "USER-DEFINED",
10350 _ => "USER-DEFINED",
10353 };
10354 alloc::string::String::from(s)
10355}
10356
10357fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10364 let schema = alloc::vec![
10365 ColumnSchema::new("table_catalog", DataType::Text, false),
10366 ColumnSchema::new("table_schema", DataType::Text, false),
10367 ColumnSchema::new("table_name", DataType::Text, false),
10368 ColumnSchema::new("column_name", DataType::Text, false),
10369 ColumnSchema::new("ordinal_position", DataType::Int, false),
10370 ColumnSchema::new("is_nullable", DataType::Text, false),
10371 ColumnSchema::new("data_type", DataType::Text, false),
10372 ];
10373 let mut rows: Vec<Row> = Vec::new();
10374 for tname in cat.table_names() {
10375 let Some(t) = cat.get(&tname) else { continue };
10376 for (i, col) in t.schema().columns.iter().enumerate() {
10377 #[allow(clippy::cast_possible_wrap)]
10378 let ordinal = (i + 1) as i32;
10379 rows.push(Row::new(alloc::vec![
10380 Value::Text("spg".into()),
10381 Value::Text("public".into()),
10382 Value::Text(tname.clone()),
10383 Value::Text(col.name.clone()),
10384 Value::Int(ordinal),
10385 Value::Text(if col.nullable {
10386 "YES".into()
10387 } else {
10388 "NO".into()
10389 }),
10390 Value::Text(pg_data_type_text(col.ty)),
10391 ]));
10392 }
10393 }
10394 (schema, rows)
10395}
10396
10397fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10399 let schema = alloc::vec![
10400 ColumnSchema::new("table_catalog", DataType::Text, false),
10401 ColumnSchema::new("table_schema", DataType::Text, false),
10402 ColumnSchema::new("table_name", DataType::Text, false),
10403 ColumnSchema::new("table_type", DataType::Text, false),
10404 ];
10405 let mut rows: Vec<Row> = Vec::new();
10406 for tname in cat.table_names() {
10407 rows.push(Row::new(alloc::vec![
10408 Value::Text("spg".into()),
10409 Value::Text("public".into()),
10410 Value::Text(tname.clone()),
10411 Value::Text("BASE TABLE".into()),
10412 ]));
10413 }
10414 (schema, rows)
10415}
10416
10417fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10421 let schema = alloc::vec![
10422 ColumnSchema::new("relname", DataType::Text, false),
10423 ColumnSchema::new("relkind", DataType::Text, false),
10424 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10425 ];
10426 let mut rows: Vec<Row> = Vec::new();
10427 for tname in cat.table_names() {
10428 rows.push(Row::new(alloc::vec![
10429 Value::Text(tname.clone()),
10430 Value::Text("r".into()),
10431 Value::BigInt(2200), ]));
10433 }
10434 (schema, rows)
10435}
10436
10437fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10441 let schema = alloc::vec![
10442 ColumnSchema::new("attrelid", DataType::Text, false),
10443 ColumnSchema::new("attname", DataType::Text, false),
10444 ColumnSchema::new("attnum", DataType::Int, false),
10445 ColumnSchema::new("atttypid", DataType::Text, false),
10446 ColumnSchema::new("attnotnull", DataType::Bool, false),
10447 ];
10448 let mut rows: Vec<Row> = Vec::new();
10449 for tname in cat.table_names() {
10450 let Some(t) = cat.get(&tname) else { continue };
10451 for (i, col) in t.schema().columns.iter().enumerate() {
10452 #[allow(clippy::cast_possible_wrap)]
10453 let ordinal = (i + 1) as i32;
10454 rows.push(Row::new(alloc::vec![
10455 Value::Text(tname.clone()),
10456 Value::Text(col.name.clone()),
10457 Value::Int(ordinal),
10458 Value::Text(pg_data_type_text(col.ty)),
10459 Value::Bool(!col.nullable),
10460 ]));
10461 }
10462 }
10463 (schema, rows)
10464}
10465
10466fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10483 let schema = alloc::vec![
10484 ColumnSchema::new("oid", DataType::BigInt, false),
10485 ColumnSchema::new("typname", DataType::Text, false),
10486 ColumnSchema::new("typlen", DataType::SmallInt, false),
10487 ColumnSchema::new("typtype", DataType::Text, false),
10488 ColumnSchema::new("typcategory", DataType::Text, false),
10489 ColumnSchema::new("typelem", DataType::BigInt, false),
10490 ColumnSchema::new("typarray", DataType::BigInt, false),
10491 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10492 ];
10493 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10496 (16, "bool", 1, "b", "B", 0, 1000),
10498 (17, "bytea", -1, "b", "U", 0, 1001),
10499 (18, "char", 1, "b", "S", 0, 1002),
10500 (19, "name", 64, "b", "S", 0, 1003),
10501 (20, "int8", 8, "b", "N", 0, 1016),
10502 (21, "int2", 2, "b", "N", 0, 1005),
10503 (23, "int4", 4, "b", "N", 0, 1007),
10504 (24, "regproc", 4, "b", "N", 0, 1008),
10505 (25, "text", -1, "b", "S", 0, 1009),
10506 (26, "oid", 4, "b", "N", 0, 1028),
10507 (114, "json", -1, "b", "U", 0, 199),
10508 (142, "xml", -1, "b", "U", 0, 143),
10509 (700, "float4", 4, "b", "N", 0, 1021),
10510 (701, "float8", 8, "b", "N", 0, 1022),
10511 (650, "cidr", -1, "b", "I", 0, 651),
10512 (869, "inet", -1, "b", "I", 0, 1041),
10513 (829, "macaddr", 6, "b", "U", 0, 1040),
10514 (1042, "bpchar", -1, "b", "S", 0, 1014),
10515 (1043, "varchar", -1, "b", "S", 0, 1015),
10516 (1082, "date", 4, "b", "D", 0, 1182),
10517 (1083, "time", 8, "b", "D", 0, 1183),
10518 (1114, "timestamp", 8, "b", "D", 0, 1115),
10519 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10520 (1186, "interval", 16, "b", "T", 0, 1187),
10521 (1266, "timetz", 12, "b", "D", 0, 1270),
10522 (1700, "numeric", -1, "b", "N", 0, 1231),
10523 (790, "money", 8, "b", "N", 0, 791),
10524 (2950, "uuid", 16, "b", "U", 0, 2951),
10525 (3802, "jsonb", -1, "b", "U", 0, 3807),
10526 (3614, "tsvector", -1, "b", "U", 0, 3643),
10527 (3615, "tsquery", -1, "b", "U", 0, 3645),
10528 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10530 (3910, "tsrange", -1, "r", "R", 0, 3911),
10531 (3904, "int4range", -1, "r", "R", 0, 3905),
10532 (3926, "int8range", -1, "r", "R", 0, 3927),
10533 (3906, "numrange", -1, "r", "R", 0, 3907),
10534 (3912, "daterange", -1, "r", "R", 0, 3913),
10535 ];
10536 let arrays: &[(i64, &str, i64)] = &[
10539 (1000, "_bool", 16),
10540 (1001, "_bytea", 17),
10541 (1002, "_char", 18),
10542 (1003, "_name", 19),
10543 (1016, "_int8", 20),
10544 (1005, "_int2", 21),
10545 (1007, "_int4", 23),
10546 (1008, "_regproc", 24),
10547 (1009, "_text", 25),
10548 (1028, "_oid", 26),
10549 (199, "_json", 114),
10550 (143, "_xml", 142),
10551 (1021, "_float4", 700),
10552 (1022, "_float8", 701),
10553 (651, "_cidr", 650),
10554 (1041, "_inet", 869),
10555 (1040, "_macaddr", 829),
10556 (1014, "_bpchar", 1042),
10557 (1015, "_varchar", 1043),
10558 (1182, "_date", 1082),
10559 (1183, "_time", 1083),
10560 (1115, "_timestamp", 1114),
10561 (1185, "_timestamptz", 1184),
10562 (1187, "_interval", 1186),
10563 (1270, "_timetz", 1266),
10564 (1231, "_numeric", 1700),
10565 (791, "_money", 790),
10566 (2951, "_uuid", 2950),
10567 (3807, "_jsonb", 3802),
10568 (3643, "_tsvector", 3614),
10569 (3645, "_tsquery", 3615),
10570 ];
10571 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10572 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10573 rows.push(Row::new(alloc::vec![
10574 Value::BigInt(oid),
10575 Value::Text(name.into()),
10576 Value::SmallInt(len),
10577 Value::Text(ty.into()),
10578 Value::Text(cat.into()),
10579 Value::BigInt(elem),
10580 Value::BigInt(arr),
10581 Value::BigInt(2200),
10582 ]));
10583 }
10584 for &(oid, name, elem) in arrays {
10585 rows.push(Row::new(alloc::vec![
10586 Value::BigInt(oid),
10587 Value::Text(name.into()),
10588 Value::SmallInt(-1),
10589 Value::Text("b".into()),
10590 Value::Text("A".into()),
10591 Value::BigInt(elem),
10592 Value::BigInt(0),
10593 Value::BigInt(2200),
10594 ]));
10595 }
10596 (schema, rows)
10597}
10598
10599fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10613 let schema = alloc::vec![
10614 ColumnSchema::new("oid", DataType::BigInt, false),
10615 ColumnSchema::new("proname", DataType::Text, false),
10616 ColumnSchema::new("pronamespace", DataType::BigInt, false),
10617 ColumnSchema::new("prokind", DataType::Text, false),
10618 ColumnSchema::new("pronargs", DataType::Int, false),
10619 ColumnSchema::new("prorettype", DataType::BigInt, false),
10620 ];
10621 let funcs: &[(i64, &str, &str, i32, i64)] = &[
10624 (1318, "length", "f", 1, 23),
10626 (871, "upper", "f", 1, 25),
10627 (870, "lower", "f", 1, 25),
10628 (936, "substring", "f", 3, 25),
10629 (937, "substring", "f", 2, 25),
10630 (3055, "btrim", "f", 1, 25),
10631 (885, "btrim", "f", 2, 25),
10632 (3056, "ltrim", "f", 1, 25),
10633 (875, "ltrim", "f", 2, 25),
10634 (3057, "rtrim", "f", 1, 25),
10635 (876, "rtrim", "f", 2, 25),
10636 (1397, "abs", "f", 1, 23),
10637 (1396, "abs", "f", 1, 20),
10638 (1606, "round", "f", 1, 1700),
10639 (1707, "round", "f", 2, 1700),
10640 (2308, "ceil", "f", 1, 701),
10641 (2309, "ceiling", "f", 1, 701),
10642 (2310, "floor", "f", 1, 701),
10643 (1376, "sqrt", "f", 1, 701),
10644 (1369, "ln", "f", 1, 701),
10645 (1373, "exp", "f", 1, 701),
10646 (1368, "power", "f", 2, 701),
10647 (2228, "random", "f", 0, 701),
10648 (1299, "now", "f", 0, 1184),
10650 (1274, "current_timestamp", "f", 0, 1184),
10651 (1140, "current_date", "f", 0, 1082),
10652 (2050, "current_time", "f", 0, 1083),
10653 (1158, "date_trunc", "f", 2, 1184),
10654 (1171, "date_part", "f", 2, 701),
10655 (1172, "age", "f", 1, 1186),
10656 (936, "to_char", "f", 2, 25),
10657 (861, "current_database", "f", 0, 19),
10659 (745, "current_user", "f", 0, 19),
10660 (745, "session_user", "f", 0, 19),
10661 (1402, "current_schema", "f", 0, 19),
10662 (3058, "concat", "f", -1, 25),
10664 (3059, "concat_ws", "f", -1, 25),
10665 (3539, "format", "f", -1, 25),
10666 (2877, "pg_typeof", "f", 1, 2206),
10668 (3198, "json_build_object", "f", -1, 114),
10670 (3199, "jsonb_build_object", "f", -1, 3802),
10671 (3271, "json_build_array", "f", -1, 114),
10672 (3272, "jsonb_build_array", "f", -1, 3802),
10673 (3253, "gen_random_uuid", "f", 0, 2950),
10675 (3252, "uuid_generate_v4", "f", 0, 2950),
10676 (2147, "count", "a", 0, 20),
10678 (2803, "count", "a", -1, 20),
10679 (2116, "max", "a", 1, 23),
10680 (2132, "min", "a", 1, 23),
10681 (2108, "sum", "a", 1, 20),
10682 (2100, "avg", "a", 1, 1700),
10683 (2517, "string_agg", "a", 2, 25),
10684 (2747, "array_agg", "a", 1, 1009),
10685 (2517, "bool_and", "a", 1, 16),
10686 (2518, "bool_or", "a", 1, 16),
10687 (2519, "every", "a", 1, 16),
10688 (3100, "row_number", "w", 0, 20),
10690 (3101, "rank", "w", 0, 20),
10691 (3102, "dense_rank", "w", 0, 20),
10692 (3103, "percent_rank", "w", 0, 701),
10693 (3104, "cume_dist", "w", 0, 701),
10694 (3105, "lag", "w", -1, 2283),
10695 (3106, "lead", "w", -1, 2283),
10696 (3107, "first_value", "w", 1, 2283),
10697 (3108, "last_value", "w", 1, 2283),
10698 (3109, "nth_value", "w", 2, 2283),
10699 ];
10700 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
10701 for &(oid, name, kind, nargs, rettype) in funcs {
10702 rows.push(Row::new(alloc::vec![
10703 Value::BigInt(oid),
10704 Value::Text(name.into()),
10705 Value::BigInt(11),
10706 Value::Text(kind.into()),
10707 Value::Int(nargs),
10708 Value::BigInt(rettype),
10709 ]));
10710 }
10711 (schema, rows)
10712}
10713
10714fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
10720 let schema = alloc::vec![
10721 ColumnSchema::new("user", DataType::Text, false),
10722 ColumnSchema::new("host", DataType::Text, false),
10723 ColumnSchema::new("select_priv", DataType::Text, false),
10724 ];
10725 let mut rows: Vec<Row> = Vec::new();
10726 rows.push(Row::new(alloc::vec![
10727 Value::Text("root".into()),
10728 Value::Text("localhost".into()),
10729 Value::Text("Y".into()),
10730 ]));
10731 for (name, _) in engine.users.iter() {
10732 if name != "root" {
10733 rows.push(Row::new(alloc::vec![
10734 Value::Text(name.to_string()),
10735 Value::Text("%".into()),
10736 Value::Text("Y".into()),
10737 ]));
10738 }
10739 }
10740 (schema, rows)
10741}
10742
10743fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
10748 let schema = alloc::vec![
10749 ColumnSchema::new("host", DataType::Text, false),
10750 ColumnSchema::new("db", DataType::Text, false),
10751 ColumnSchema::new("user", DataType::Text, false),
10752 ColumnSchema::new("select_priv", DataType::Text, false),
10753 ];
10754 let rows = alloc::vec![Row::new(alloc::vec![
10755 Value::Text("localhost".into()),
10756 Value::Text("postgres".into()),
10757 Value::Text("root".into()),
10758 Value::Text("Y".into()),
10759 ])];
10760 (schema, rows)
10761}
10762
10763fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10776 let schema = alloc::vec![
10777 ColumnSchema::new("constraint_name", DataType::Text, false),
10778 ColumnSchema::new("table_name", DataType::Text, false),
10779 ColumnSchema::new("column_name", DataType::Text, false),
10780 ColumnSchema::new("ordinal_position", DataType::Int, false),
10781 ColumnSchema::new("referenced_table_name", DataType::Text, false),
10782 ColumnSchema::new("referenced_column_name", DataType::Text, false),
10783 ];
10784 let mut rows: Vec<Row> = Vec::new();
10785 for tname in cat.table_names() {
10786 let Some(t) = cat.get(&tname) else { continue };
10787 let cols = &t.schema().columns;
10788 let col_name_at = |pos: usize| -> String {
10789 cols.get(pos)
10790 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
10791 };
10792 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
10794 let conname = fk
10795 .name
10796 .clone()
10797 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
10798 for (i, (&local, &parent)) in fk
10799 .local_columns
10800 .iter()
10801 .zip(fk.parent_columns.iter())
10802 .enumerate()
10803 {
10804 let parent_name = cat
10805 .get(&fk.parent_table)
10806 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
10807 .unwrap_or_else(|| alloc::format!("col{parent}"));
10808 #[allow(clippy::cast_possible_wrap)]
10809 let ordinal = (i + 1) as i32;
10810 rows.push(Row::new(alloc::vec![
10811 Value::Text(conname.clone()),
10812 Value::Text(tname.clone()),
10813 Value::Text(col_name_at(local)),
10814 Value::Int(ordinal),
10815 Value::Text(fk.parent_table.clone()),
10816 Value::Text(parent_name),
10817 ]));
10818 }
10819 }
10820 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
10822 let conname = if uc.is_primary_key {
10823 alloc::format!("{}_pkey", tname)
10824 } else {
10825 alloc::format!("{}_uniq{ci}", tname)
10826 };
10827 for (i, &local) in uc.columns.iter().enumerate() {
10828 #[allow(clippy::cast_possible_wrap)]
10829 let ordinal = (i + 1) as i32;
10830 rows.push(Row::new(alloc::vec![
10831 Value::Text(conname.clone()),
10832 Value::Text(tname.clone()),
10833 Value::Text(col_name_at(local)),
10834 Value::Int(ordinal),
10835 Value::Text(String::new()),
10836 Value::Text(String::new()),
10837 ]));
10838 }
10839 }
10840 }
10841 (schema, rows)
10842}
10843
10844fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10847 let schema = alloc::vec![
10848 ColumnSchema::new("constraint_name", DataType::Text, false),
10849 ColumnSchema::new("table_name", DataType::Text, false),
10850 ColumnSchema::new("referenced_table_name", DataType::Text, false),
10851 ColumnSchema::new("update_rule", DataType::Text, false),
10852 ColumnSchema::new("delete_rule", DataType::Text, false),
10853 ];
10854 fn rule_name(a: spg_storage::FkAction) -> &'static str {
10855 match a {
10856 spg_storage::FkAction::Cascade => "CASCADE",
10857 spg_storage::FkAction::SetNull => "SET NULL",
10858 spg_storage::FkAction::SetDefault => "SET DEFAULT",
10859 spg_storage::FkAction::Restrict => "RESTRICT",
10860 spg_storage::FkAction::NoAction => "NO ACTION",
10861 }
10862 }
10863 let mut rows: Vec<Row> = Vec::new();
10864 for tname in cat.table_names() {
10865 let Some(t) = cat.get(&tname) else { continue };
10866 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
10867 let conname = fk
10868 .name
10869 .clone()
10870 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
10871 rows.push(Row::new(alloc::vec![
10872 Value::Text(conname),
10873 Value::Text(tname.clone()),
10874 Value::Text(fk.parent_table.clone()),
10875 Value::Text(rule_name(fk.on_update).into()),
10876 Value::Text(rule_name(fk.on_delete).into()),
10877 ]));
10878 }
10879 }
10880 (schema, rows)
10881}
10882
10883fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10887 let schema = alloc::vec![
10888 ColumnSchema::new("table_name", DataType::Text, false),
10889 ColumnSchema::new("index_name", DataType::Text, false),
10890 ColumnSchema::new("column_name", DataType::Text, false),
10891 ColumnSchema::new("seq_in_index", DataType::Int, false),
10892 ColumnSchema::new("non_unique", DataType::Int, false),
10893 ColumnSchema::new("index_type", DataType::Text, false),
10894 ];
10895 let mut rows: Vec<Row> = Vec::new();
10896 for tname in cat.table_names() {
10897 let Some(t) = cat.get(&tname) else { continue };
10898 for idx in t.indices() {
10899 let col = t
10900 .schema()
10901 .columns
10902 .get(idx.column_position)
10903 .map_or("?".into(), |c| c.name.clone());
10904 rows.push(Row::new(alloc::vec![
10905 Value::Text(tname.clone()),
10906 Value::Text(idx.name.clone()),
10907 Value::Text(col),
10908 Value::Int(1),
10909 Value::Int(i32::from(!idx.is_unique)),
10910 Value::Text("BTREE".into()),
10911 ]));
10912 }
10913 }
10914 (schema, rows)
10915}
10916
10917fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
10921 let schema = alloc::vec![
10922 ColumnSchema::new("routine_name", DataType::Text, false),
10923 ColumnSchema::new("routine_type", DataType::Text, false),
10924 ColumnSchema::new("data_type", DataType::Text, false),
10925 ];
10926 (schema, Vec::new())
10927}
10928
10929fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10944 let schema = alloc::vec![
10945 ColumnSchema::new("conname", DataType::Text, false),
10946 ColumnSchema::new("contype", DataType::Text, false),
10947 ColumnSchema::new("conrelid", DataType::Text, false),
10948 ColumnSchema::new("confrelid", DataType::Text, false),
10949 ColumnSchema::new("conkey", DataType::Text, false),
10950 ColumnSchema::new("confkey", DataType::Text, false),
10951 ];
10952 let mut rows: Vec<Row> = Vec::new();
10953 for tname in cat.table_names() {
10954 let Some(t) = cat.get(&tname) else { continue };
10955 let cols = &t.schema().columns;
10956 let col_name_at = |pos: usize| -> String {
10957 cols.get(pos)
10958 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
10959 };
10960 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
10962 let kind = if uc.is_primary_key { "p" } else { "u" };
10963 let conname = if uc.is_primary_key {
10964 alloc::format!("{}_pkey", tname)
10965 } else {
10966 alloc::format!("{}_uniq{ci}", tname)
10967 };
10968 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
10969 rows.push(Row::new(alloc::vec![
10970 Value::Text(conname),
10971 Value::Text(kind.into()),
10972 Value::Text(tname.clone()),
10973 Value::Text(String::new()),
10974 Value::Text(conkey.join(",")),
10975 Value::Text(String::new()),
10976 ]));
10977 }
10978 for idx in t.indices() {
10983 if !idx.is_unique {
10984 continue;
10985 }
10986 let is_primary = idx.name.ends_with("_pkey");
10987 let conname = idx.name.clone();
10988 let kind = if is_primary { "p" } else { "u" };
10989 let col_name = col_name_at(idx.column_position);
10990 let already = t
10993 .schema()
10994 .uniqueness_constraints
10995 .iter()
10996 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
10997 if already {
10998 continue;
10999 }
11000 rows.push(Row::new(alloc::vec![
11001 Value::Text(conname),
11002 Value::Text(kind.into()),
11003 Value::Text(tname.clone()),
11004 Value::Text(String::new()),
11005 Value::Text(col_name),
11006 Value::Text(String::new()),
11007 ]));
11008 }
11009 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11011 let conname = fk
11012 .name
11013 .clone()
11014 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11015 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11016 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11019 fk.parent_columns
11020 .iter()
11021 .map(|&p| {
11022 parent
11023 .schema()
11024 .columns
11025 .get(p)
11026 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11027 })
11028 .collect()
11029 } else {
11030 fk.parent_columns
11031 .iter()
11032 .map(|p| alloc::format!("col{p}"))
11033 .collect()
11034 };
11035 rows.push(Row::new(alloc::vec![
11036 Value::Text(conname),
11037 Value::Text("f".into()),
11038 Value::Text(tname.clone()),
11039 Value::Text(fk.parent_table.clone()),
11040 Value::Text(conkey.join(",")),
11041 Value::Text(confkey.join(",")),
11042 ]));
11043 }
11044 }
11045 (schema, rows)
11046}
11047
11048fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11053 let schema = alloc::vec![
11054 ColumnSchema::new("oid", DataType::BigInt, false),
11055 ColumnSchema::new("datname", DataType::Text, false),
11056 ColumnSchema::new("datdba", DataType::BigInt, false),
11057 ColumnSchema::new("encoding", DataType::Int, false),
11058 ColumnSchema::new("datcollate", DataType::Text, false),
11059 ];
11060 let rows = alloc::vec![Row::new(alloc::vec![
11061 Value::BigInt(16384),
11062 Value::Text("postgres".into()),
11063 Value::BigInt(10),
11064 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11066 ])];
11067 (schema, rows)
11068}
11069
11070fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11075 let schema = alloc::vec![
11076 ColumnSchema::new("oid", DataType::BigInt, false),
11077 ColumnSchema::new("rolname", DataType::Text, false),
11078 ColumnSchema::new("rolsuper", DataType::Bool, false),
11079 ColumnSchema::new("rolinherit", DataType::Bool, false),
11080 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11081 ];
11082 let mut rows: Vec<Row> = Vec::new();
11083 let oid: i64 = 10;
11084 for (i, (name, _)) in engine.users.iter().enumerate() {
11085 rows.push(Row::new(alloc::vec![
11086 Value::BigInt(oid + (i as i64) + 1),
11087 Value::Text(name.to_string()),
11088 Value::Bool(false),
11089 Value::Bool(true),
11090 Value::Bool(true),
11091 ]));
11092 }
11093 if !rows
11096 .iter()
11097 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11098 {
11099 rows.insert(
11100 0,
11101 Row::new(alloc::vec![
11102 Value::BigInt(10),
11103 Value::Text("postgres".into()),
11104 Value::Bool(true),
11105 Value::Bool(true),
11106 Value::Bool(true),
11107 ]),
11108 );
11109 }
11110 (schema, rows)
11111}
11112
11113fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11117 let schema = alloc::vec![
11118 ColumnSchema::new("schemaname", DataType::Text, false),
11119 ColumnSchema::new("viewname", DataType::Text, false),
11120 ColumnSchema::new("definition", DataType::Text, false),
11121 ];
11122 let mut rows: Vec<Row> = Vec::new();
11123 for (name, def) in cat.views() {
11124 rows.push(Row::new(alloc::vec![
11125 Value::Text("public".into()),
11126 Value::Text(name.clone()),
11127 Value::Text(def.body.clone()),
11128 ]));
11129 }
11130 (schema, rows)
11131}
11132
11133fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11139 let schema = alloc::vec![
11140 ColumnSchema::new("name", DataType::Text, false),
11141 ColumnSchema::new("setting", DataType::Text, false),
11142 ColumnSchema::new("category", DataType::Text, false),
11143 ];
11144 let mut rows: Vec<Row> = Vec::new();
11145 let defaults: &[(&str, &str, &str)] = &[
11147 ("server_version", "16.0 (spg)", "Preset Options"),
11148 ("server_encoding", "UTF8", "Client Connection Defaults"),
11149 ("client_encoding", "UTF8", "Client Connection Defaults"),
11150 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11151 ("TimeZone", "UTC", "Client Connection Defaults"),
11152 ("standard_conforming_strings", "on", "Compatibility"),
11153 ("integer_datetimes", "on", "Compatibility"),
11154 ("max_connections", "100", "Connections and Authentication"),
11155 ];
11156 for &(name, val, cat) in defaults {
11157 rows.push(Row::new(alloc::vec![
11158 Value::Text(name.into()),
11159 Value::Text(val.into()),
11160 Value::Text(cat.into()),
11161 ]));
11162 }
11163 for (k, v) in &engine.session_params {
11165 if !defaults
11166 .iter()
11167 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11168 {
11169 rows.push(Row::new(alloc::vec![
11170 Value::Text(k.clone()),
11171 Value::Text(v.clone()),
11172 Value::Text("Session".into()),
11173 ]));
11174 }
11175 }
11176 (schema, rows)
11177}
11178
11179fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11190 let schema = alloc::vec![
11191 ColumnSchema::new("schemaname", DataType::Text, false),
11192 ColumnSchema::new("tablename", DataType::Text, false),
11193 ColumnSchema::new("indexname", DataType::Text, false),
11194 ColumnSchema::new("indexdef", DataType::Text, false),
11195 ];
11196 let mut rows: Vec<Row> = Vec::new();
11197 for tname in cat.table_names() {
11198 let Some(t) = cat.get(&tname) else { continue };
11199 for idx in t.indices() {
11200 let col_name = t
11201 .schema()
11202 .columns
11203 .get(idx.column_position)
11204 .map_or("?".into(), |c| c.name.clone());
11205 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11206 let indexdef = alloc::format!(
11207 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11208 idx.name,
11209 tname,
11210 col_name
11211 );
11212 rows.push(Row::new(alloc::vec![
11213 Value::Text("public".into()),
11214 Value::Text(tname.clone()),
11215 Value::Text(idx.name.clone()),
11216 Value::Text(indexdef),
11217 ]));
11218 }
11219 }
11220 (schema, rows)
11221}
11222
11223fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11235 let schema = alloc::vec![
11236 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11237 ColumnSchema::new("indrelid", DataType::BigInt, false),
11238 ColumnSchema::new("indnatts", DataType::Int, false),
11239 ColumnSchema::new("indisunique", DataType::Bool, false),
11240 ColumnSchema::new("indisprimary", DataType::Bool, false),
11241 ];
11242 let mut rows: Vec<Row> = Vec::new();
11243 let mut idx_oid: i64 = 100_000;
11244 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11245 let Some(t) = cat.get(tname) else { continue };
11246 for idx in t.indices() {
11247 idx_oid += 1;
11248 #[allow(clippy::cast_possible_wrap)]
11249 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11250 let is_primary = idx.name.ends_with("_pkey");
11253 rows.push(Row::new(alloc::vec![
11254 Value::BigInt(idx_oid),
11255 Value::BigInt((table_idx + 1) as i64),
11256 Value::Int(nattrs),
11257 Value::Bool(idx.is_unique),
11258 Value::Bool(is_primary),
11259 ]));
11260 }
11261 }
11262 (schema, rows)
11263}
11264
11265fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11270 let schema = alloc::vec![
11271 ColumnSchema::new("oid", DataType::BigInt, false),
11272 ColumnSchema::new("nspname", DataType::Text, false),
11273 ColumnSchema::new("nspowner", DataType::BigInt, false),
11274 ];
11275 let rows = alloc::vec![
11276 Row::new(alloc::vec![
11277 Value::BigInt(11),
11278 Value::Text("pg_catalog".into()),
11279 Value::BigInt(10),
11280 ]),
11281 Row::new(alloc::vec![
11282 Value::BigInt(2200),
11283 Value::Text("public".into()),
11284 Value::BigInt(10),
11285 ]),
11286 Row::new(alloc::vec![
11287 Value::BigInt(13000),
11288 Value::Text("information_schema".into()),
11289 Value::BigInt(10),
11290 ]),
11291 ];
11292 (schema, rows)
11293}
11294
11295fn materialise_meta_view(
11298 catalog: &mut Catalog,
11299 name: &str,
11300 columns: Vec<ColumnSchema>,
11301 rows: Vec<Row>,
11302) -> Result<(), EngineError> {
11303 let schema = TableSchema::new(name.to_string(), columns);
11304 catalog.create_table(schema).map_err(EngineError::Storage)?;
11305 let table = catalog
11306 .get_mut(name)
11307 .expect("just-created meta view must exist");
11308 for row in rows {
11309 table.insert(row).map_err(EngineError::Storage)?;
11310 }
11311 Ok(())
11312}
11313
11314fn collect_view_refs(
11327 tref: &spg_sql::ast::TableRef,
11328 cat: &spg_storage::Catalog,
11329 into: &mut Vec<String>,
11330) {
11331 if cat.views().contains_key(&tref.name)
11332 && cat.get(&tref.name).is_none()
11333 && !into.iter().any(|n| n == &tref.name)
11334 {
11335 into.push(tref.name.clone());
11336 }
11337}
11338
11339fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11340 fn is_meta(name: &str) -> bool {
11341 name.starts_with("__spg_info_")
11342 || name.starts_with("__spg_pg_")
11343 || name.starts_with("__spg_mysql_")
11344 }
11345 if let Some(from) = &stmt.from {
11346 if is_meta(&from.primary.name) {
11347 return true;
11348 }
11349 for j in &from.joins {
11350 if is_meta(&j.table.name) {
11351 return true;
11352 }
11353 }
11354 }
11355 for cte in &stmt.ctes {
11356 if select_references_meta_view(&cte.body) {
11357 return true;
11358 }
11359 }
11360 false
11361}
11362
11363fn collect_meta_view_names(
11368 stmt: &SelectStatement,
11369 into: &mut alloc::collections::BTreeSet<String>,
11370) {
11371 fn is_meta(name: &str) -> bool {
11372 name.starts_with("__spg_info_")
11373 || name.starts_with("__spg_pg_")
11374 || name.starts_with("__spg_mysql_")
11375 }
11376 if let Some(from) = &stmt.from {
11377 if is_meta(&from.primary.name) {
11378 into.insert(from.primary.name.clone());
11379 }
11380 for j in &from.joins {
11381 if is_meta(&j.table.name) {
11382 into.insert(j.table.name.clone());
11383 }
11384 }
11385 }
11386 for cte in &stmt.ctes {
11387 collect_meta_view_names(&cte.body, into);
11388 }
11389}
11390
11391fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11392 let mut out = columns.to_vec();
11393 for (col_idx, col) in out.iter_mut().enumerate() {
11394 if col.ty != DataType::Text {
11395 continue;
11396 }
11397 let mut inferred: Option<DataType> = None;
11398 let mut all_null = true;
11399 for row in rows {
11400 let Some(v) = row.values.get(col_idx) else {
11401 continue;
11402 };
11403 let ty = match v {
11404 Value::Null => continue,
11405 Value::SmallInt(_) => DataType::SmallInt,
11406 Value::Int(_) => DataType::Int,
11407 Value::BigInt(_) => DataType::BigInt,
11408 Value::Float(_) => DataType::Float,
11409 Value::Bool(_) => DataType::Bool,
11410 Value::Vector(_) => DataType::Vector {
11411 dim: 0,
11412 encoding: VecEncoding::F32,
11413 },
11414 _ => DataType::Text,
11415 };
11416 all_null = false;
11417 inferred = Some(match inferred {
11418 None => ty,
11419 Some(prev) if prev == ty => prev,
11420 Some(_) => DataType::Text,
11421 });
11422 }
11423 if let Some(t) = inferred {
11424 col.ty = t;
11425 col.nullable = true;
11426 } else if all_null {
11427 col.nullable = true;
11428 }
11429 }
11430 out
11431}
11432
11433#[allow(clippy::too_many_lines, clippy::format_push_string)]
11438fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11455 use alloc::collections::BTreeSet;
11456 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11457 let mut out: Vec<String> = Vec::new();
11458 let cat = engine.active_catalog();
11459 let Some(from) = &stmt.from else {
11463 return out;
11464 };
11465 let mut tables: Vec<String> = Vec::new();
11466 tables.push(from.primary.name.clone());
11467 for j in &from.joins {
11468 tables.push(j.table.name.clone());
11469 }
11470 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11473 if let Some(w) = &stmt.where_ {
11474 collect_column_refs(w, &mut col_refs);
11475 }
11476 for j in &from.joins {
11477 if let Some(on) = &j.on {
11478 collect_column_refs(on, &mut col_refs);
11479 }
11480 }
11481 for cn in &col_refs {
11482 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11485 tables.iter().find(|t| t == &q).cloned()
11486 } else {
11487 tables.iter().find_map(|t| {
11488 cat.get(t).and_then(|tbl| {
11489 if tbl.schema().column_position(&cn.name).is_some() {
11490 Some(t.clone())
11491 } else {
11492 None
11493 }
11494 })
11495 })
11496 };
11497 let Some(owner) = owner else {
11498 continue;
11499 };
11500 let Some(tbl) = cat.get(&owner) else {
11501 continue;
11502 };
11503 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11504 continue;
11505 };
11506 let already_indexed = tbl.indices().iter().any(|i| {
11509 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11510 && i.column_position == col_pos
11511 && i.expression.is_none()
11512 && i.partial_predicate.is_none()
11513 });
11514 if already_indexed {
11515 continue;
11516 }
11517 if seen.insert((owner.clone(), cn.name.clone())) {
11518 out.push(alloc::format!(
11519 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11520 owner,
11521 cn.name,
11522 owner,
11523 cn.name
11524 ));
11525 }
11526 }
11527 out
11528}
11529
11530fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11533 match expr {
11534 Expr::Column(cn) => out.push(cn.clone()),
11535 Expr::FunctionCall { args, .. } => {
11536 for a in args {
11537 collect_column_refs(a, out);
11538 }
11539 }
11540 Expr::Binary { lhs, rhs, .. } => {
11541 collect_column_refs(lhs, out);
11542 collect_column_refs(rhs, out);
11543 }
11544 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11545 _ => {}
11546 }
11547}
11548
11549fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11550 let catalog = engine.active_catalog();
11551 let cold_ids = catalog.cold_segment_ids_global();
11552 let any_cold = !cold_ids.is_empty();
11553 let cold_ids_repr = if any_cold {
11554 let mut s = alloc::string::String::from("[");
11555 for (i, id) in cold_ids.iter().enumerate() {
11556 if i > 0 {
11557 s.push(',');
11558 }
11559 s.push_str(&alloc::format!("{id}"));
11560 }
11561 s.push(']');
11562 s
11563 } else {
11564 alloc::string::String::new()
11565 };
11566 for (idx, line) in lines.iter_mut().enumerate() {
11567 let trimmed = line.trim_start();
11568 let is_top_level = idx == 0;
11569 if is_top_level {
11570 line.push_str(&alloc::format!(" (rows={total_rows})"));
11571 continue;
11572 }
11573 if let Some(rest) = trimmed.strip_prefix("From: ") {
11574 let (name, scan_kind) = match rest.split_once(" [") {
11575 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
11576 None => (rest.trim(), ""),
11577 };
11578 let bare = name.split_whitespace().next().unwrap_or(name);
11579 let hot = catalog.get(bare).map(|t| t.rows().len());
11580 let annot = match (hot, scan_kind) {
11585 (Some(h), "full scan") => {
11586 let mut s = alloc::format!(" (hot_rows={h}");
11587 if any_cold {
11588 s.push_str(&alloc::format!(
11589 ", cold_tier=present, cold_segments={cold_ids_repr}"
11590 ));
11591 }
11592 s.push(')');
11593 s
11594 }
11595 (Some(h), "index seek") => {
11596 let mut s = alloc::format!(" (hot_rows≤{h}");
11597 if any_cold {
11598 s.push_str(&alloc::format!(
11599 ", cold_tier=present, cold_segments={cold_ids_repr}"
11600 ));
11601 }
11602 s.push(')');
11603 s
11604 }
11605 _ => " (rows=—)".to_string(),
11606 };
11607 line.push_str(&annot);
11608 continue;
11609 }
11610 line.push_str(" (rows=—)");
11612 }
11613}
11614
11615fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
11616 let pad = " ".repeat(depth);
11617 let top = if !stmt.ctes.is_empty() {
11619 if stmt.ctes.iter().any(|c| c.recursive) {
11620 "CTEScan (WITH RECURSIVE)"
11621 } else {
11622 "CTEScan (WITH)"
11623 }
11624 } else if !stmt.unions.is_empty() {
11625 "UnionScan"
11626 } else if select_has_window(stmt) {
11627 "WindowAgg"
11628 } else if aggregate::uses_aggregate(stmt) {
11629 "Aggregate"
11630 } else if stmt.distinct {
11631 "Distinct"
11632 } else if stmt.from.is_some() {
11633 "TableScan"
11634 } else {
11635 "Result"
11636 };
11637 out.push(alloc::format!("{pad}{top}"));
11638 let child = " ".repeat(depth + 1);
11639 for cte in &stmt.ctes {
11641 let head = if cte.recursive {
11642 alloc::format!("{child}CTE (recursive): {}", cte.name)
11643 } else {
11644 alloc::format!("{child}CTE: {}", cte.name)
11645 };
11646 out.push(head);
11647 explain_select(&cte.body, engine, depth + 2, out);
11648 }
11649 if let Some(from) = &stmt.from {
11651 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
11652 if let Some(alias) = &from.primary.alias {
11653 tag.push_str(&alloc::format!(" AS {alias}"));
11654 }
11655 if let Some(w) = &stmt.where_
11658 && let Some(table) = engine.active_catalog().get(&from.primary.name)
11659 {
11660 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
11661 let cols = &table.schema().columns;
11662 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
11663 tag.push_str(" [index seek]");
11664 } else {
11665 tag.push_str(" [full scan]");
11666 }
11667 } else {
11668 tag.push_str(" [full scan]");
11669 }
11670 out.push(tag);
11671 for j in &from.joins {
11672 let kind = match j.kind {
11673 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
11674 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
11675 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
11676 };
11677 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
11678 if let Some(alias) = &j.table.alias {
11679 s.push_str(&alloc::format!(" AS {alias}"));
11680 }
11681 if j.on.is_some() {
11682 s.push_str(" (ON …)");
11683 }
11684 out.push(s);
11685 }
11686 }
11687 if let Some(w) = &stmt.where_ {
11689 let mut s = alloc::format!("{child}Filter: {w}");
11690 if expr_has_subquery(w) {
11691 s.push_str(" [subquery]");
11692 }
11693 out.push(s);
11694 }
11695 if let Some(gs) = &stmt.group_by {
11696 let mut parts = Vec::new();
11697 for g in gs {
11698 parts.push(alloc::format!("{g}"));
11699 }
11700 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
11701 }
11702 if let Some(h) = &stmt.having {
11703 out.push(alloc::format!("{child}Having: {h}"));
11704 }
11705 for o in &stmt.order_by {
11706 let dir = if o.desc { "DESC" } else { "ASC" };
11707 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
11708 }
11709 if let Some(lim) = stmt.limit {
11710 out.push(alloc::format!("{child}Limit: {lim}"));
11711 }
11712 if let Some(off) = stmt.offset {
11713 out.push(alloc::format!("{child}Offset: {off}"));
11714 }
11715 if stmt
11717 .items
11718 .iter()
11719 .any(|it| matches!(it, SelectItem::Wildcard))
11720 {
11721 out.push(alloc::format!("{child}Project: *"));
11722 } else {
11723 out.push(alloc::format!(
11724 "{child}Project: {} item(s)",
11725 stmt.items.len()
11726 ));
11727 }
11728 for (kind, peer) in &stmt.unions {
11730 let label = match kind {
11731 UnionKind::All => "UNION ALL",
11732 UnionKind::Distinct => "UNION",
11733 };
11734 out.push(alloc::format!("{child}{label}"));
11735 explain_select(peer, engine, depth + 2, out);
11736 }
11737}
11738
11739fn is_correlation_error(e: &EngineError) -> bool {
11744 matches!(
11745 e,
11746 EngineError::Eval(
11747 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
11748 )
11749 )
11750}
11751
11752struct JoinedPeer<'a> {
11763 eager_rows: Option<Vec<Row>>,
11764 cols: Vec<ColumnSchema>,
11765 alias: String,
11766 kind: JoinKind,
11767 on: Option<&'a Expr>,
11768 lateral: Option<&'a SelectStatement>,
11769}
11770
11771fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
11778 match expr {
11779 Expr::Column(c) => c.name.clone(),
11781 Expr::FunctionCall { name, .. } => name.clone(),
11784 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
11786 _ => alloc::format!("column{}", idx + 1),
11788 }
11789}
11790
11791fn substitute_outer_columns_multi(
11798 stmt: &mut SelectStatement,
11799 outer_row: &Row,
11800 outer_schema: &[ColumnSchema],
11801) {
11802 substitute_outer_in_select(stmt, outer_row, outer_schema);
11803}
11804
11805fn substitute_outer_in_select(
11806 stmt: &mut SelectStatement,
11807 outer_row: &Row,
11808 outer_schema: &[ColumnSchema],
11809) {
11810 for item in &mut stmt.items {
11811 if let SelectItem::Expr { expr, .. } = item {
11812 substitute_outer_in_expr(expr, outer_row, outer_schema);
11813 }
11814 }
11815 if let Some(w) = &mut stmt.where_ {
11816 substitute_outer_in_expr(w, outer_row, outer_schema);
11817 }
11818 if let Some(gs) = &mut stmt.group_by {
11819 for g in gs {
11820 substitute_outer_in_expr(g, outer_row, outer_schema);
11821 }
11822 }
11823 if let Some(h) = &mut stmt.having {
11824 substitute_outer_in_expr(h, outer_row, outer_schema);
11825 }
11826 for o in &mut stmt.order_by {
11827 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
11828 }
11829 for (_, peer) in &mut stmt.unions {
11830 substitute_outer_in_select(peer, outer_row, outer_schema);
11831 }
11832}
11833
11834fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
11835 if let Expr::Column(c) = e
11836 && let Some(qual) = &c.qualifier
11837 {
11838 let composite = alloc::format!("{qual}.{}", c.name);
11839 if let Some(idx) = outer_schema
11840 .iter()
11841 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
11842 {
11843 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
11844 if let Ok(lit) = value_to_literal_expr(v) {
11845 *e = lit;
11846 return;
11847 }
11848 }
11849 }
11850 match e {
11851 Expr::Binary { lhs, rhs, .. } => {
11852 substitute_outer_in_expr(lhs, outer_row, outer_schema);
11853 substitute_outer_in_expr(rhs, outer_row, outer_schema);
11854 }
11855 Expr::Unary { expr: inner, .. } => {
11856 substitute_outer_in_expr(inner, outer_row, outer_schema);
11857 }
11858 Expr::FunctionCall { args, .. } => {
11859 for a in args {
11860 substitute_outer_in_expr(a, outer_row, outer_schema);
11861 }
11862 }
11863 Expr::Cast { expr: inner, .. } => {
11864 substitute_outer_in_expr(inner, outer_row, outer_schema);
11865 }
11866 Expr::Case {
11867 operand,
11868 branches,
11869 else_branch,
11870 } => {
11871 if let Some(op) = operand {
11872 substitute_outer_in_expr(op, outer_row, outer_schema);
11873 }
11874 for (cond, val) in branches {
11875 substitute_outer_in_expr(cond, outer_row, outer_schema);
11876 substitute_outer_in_expr(val, outer_row, outer_schema);
11877 }
11878 if let Some(e) = else_branch {
11879 substitute_outer_in_expr(e, outer_row, outer_schema);
11880 }
11881 }
11882 _ => {}
11883 }
11884}
11885
11886fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
11887 let Some(outer_alias) = ctx.table_alias else {
11888 return;
11889 };
11890 substitute_in_select(stmt, row, ctx, outer_alias);
11891}
11892
11893fn substitute_in_select(
11894 stmt: &mut SelectStatement,
11895 row: &Row,
11896 ctx: &EvalContext<'_>,
11897 outer_alias: &str,
11898) {
11899 for item in &mut stmt.items {
11900 if let SelectItem::Expr { expr, .. } = item {
11901 substitute_in_expr(expr, row, ctx, outer_alias);
11902 }
11903 }
11904 if let Some(w) = &mut stmt.where_ {
11905 substitute_in_expr(w, row, ctx, outer_alias);
11906 }
11907 if let Some(gs) = &mut stmt.group_by {
11908 for g in gs {
11909 substitute_in_expr(g, row, ctx, outer_alias);
11910 }
11911 }
11912 if let Some(h) = &mut stmt.having {
11913 substitute_in_expr(h, row, ctx, outer_alias);
11914 }
11915 for o in &mut stmt.order_by {
11916 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
11917 }
11918 for (_, peer) in &mut stmt.unions {
11919 substitute_in_select(peer, row, ctx, outer_alias);
11920 }
11921}
11922
11923fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
11924 if let Expr::Column(c) = e
11925 && let Some(qual) = &c.qualifier
11926 && qual.eq_ignore_ascii_case(outer_alias)
11927 {
11928 if let Some(idx) = ctx
11930 .columns
11931 .iter()
11932 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
11933 {
11934 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
11935 if let Ok(lit) = value_to_literal_expr(v) {
11936 *e = lit;
11937 return;
11938 }
11939 }
11940 }
11941 match e {
11942 Expr::Binary { lhs, rhs, .. } => {
11943 substitute_in_expr(lhs, row, ctx, outer_alias);
11944 substitute_in_expr(rhs, row, ctx, outer_alias);
11945 }
11946 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11947 substitute_in_expr(expr, row, ctx, outer_alias);
11948 }
11949 Expr::Like { expr, pattern, .. } => {
11950 substitute_in_expr(expr, row, ctx, outer_alias);
11951 substitute_in_expr(pattern, row, ctx, outer_alias);
11952 }
11953 Expr::FunctionCall { args, .. } => {
11954 for a in args {
11955 substitute_in_expr(a, row, ctx, outer_alias);
11956 }
11957 }
11958 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
11959 Expr::WindowFunction {
11960 args,
11961 partition_by,
11962 order_by,
11963 ..
11964 } => {
11965 for a in args {
11966 substitute_in_expr(a, row, ctx, outer_alias);
11967 }
11968 for p in partition_by {
11969 substitute_in_expr(p, row, ctx, outer_alias);
11970 }
11971 for (o, _) in order_by {
11972 substitute_in_expr(o, row, ctx, outer_alias);
11973 }
11974 }
11975 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
11976 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
11977 substitute_in_select(subquery, row, ctx, outer_alias);
11978 }
11979 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
11980 Expr::Array(items) => {
11981 for elem in items {
11982 substitute_in_expr(elem, row, ctx, outer_alias);
11983 }
11984 }
11985 Expr::ArraySubscript { target, index } => {
11986 substitute_in_expr(target, row, ctx, outer_alias);
11987 substitute_in_expr(index, row, ctx, outer_alias);
11988 }
11989 Expr::AnyAll { expr, array, .. } => {
11990 substitute_in_expr(expr, row, ctx, outer_alias);
11991 substitute_in_expr(array, row, ctx, outer_alias);
11992 }
11993 Expr::Case {
11994 operand,
11995 branches,
11996 else_branch,
11997 } => {
11998 if let Some(o) = operand {
11999 substitute_in_expr(o, row, ctx, outer_alias);
12000 }
12001 for (w, t) in branches {
12002 substitute_in_expr(w, row, ctx, outer_alias);
12003 substitute_in_expr(t, row, ctx, outer_alias);
12004 }
12005 if let Some(e) = else_branch {
12006 substitute_in_expr(e, row, ctx, outer_alias);
12007 }
12008 }
12009 }
12010}
12011
12012fn encode_row_key(row: &Row) -> Vec<u8> {
12016 let mut out = Vec::new();
12017 for v in &row.values {
12018 let s = alloc::format!("{v:?}|");
12019 out.extend_from_slice(s.as_bytes());
12020 }
12021 out
12022}
12023
12024fn select_has_window(stmt: &SelectStatement) -> bool {
12025 for item in &stmt.items {
12026 if let SelectItem::Expr { expr, .. } = item
12027 && expr_has_window(expr)
12028 {
12029 return true;
12030 }
12031 }
12032 false
12033}
12034
12035fn expr_has_window(e: &Expr) -> bool {
12036 match e {
12037 Expr::WindowFunction { .. } => true,
12038 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12039 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12040 expr_has_window(expr)
12041 }
12042 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12043 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12044 Expr::Extract { source, .. } => expr_has_window(source),
12045 Expr::ScalarSubquery(_)
12046 | Expr::Exists { .. }
12047 | Expr::InSubquery { .. }
12048 | Expr::Literal(_)
12049 | Expr::Placeholder(_)
12050 | Expr::Column(_) => false,
12051 Expr::Array(items) => items.iter().any(expr_has_window),
12052 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12053 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12054 Expr::Case {
12055 operand,
12056 branches,
12057 else_branch,
12058 } => {
12059 operand.as_deref().is_some_and(expr_has_window)
12060 || branches
12061 .iter()
12062 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12063 || else_branch.as_deref().is_some_and(expr_has_window)
12064 }
12065 }
12066}
12067
12068fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12069 if let Expr::WindowFunction { .. } = e {
12070 if !out.iter().any(|x| x == e) {
12075 out.push(e.clone());
12076 }
12077 return;
12078 }
12079 match e {
12080 Expr::WindowFunction { .. } => unreachable!(),
12082 Expr::Binary { lhs, rhs, .. } => {
12083 collect_window_nodes(lhs, out);
12084 collect_window_nodes(rhs, out);
12085 }
12086 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12087 collect_window_nodes(expr, out);
12088 }
12089 Expr::FunctionCall { args, .. } => {
12090 for a in args {
12091 collect_window_nodes(a, out);
12092 }
12093 }
12094 Expr::Like { expr, pattern, .. } => {
12095 collect_window_nodes(expr, out);
12096 collect_window_nodes(pattern, out);
12097 }
12098 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12099 _ => {}
12100 }
12101}
12102
12103fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12104 if let Expr::WindowFunction { .. } = e
12105 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12106 {
12107 *e = Expr::Column(spg_sql::ast::ColumnName {
12108 qualifier: None,
12109 name: alloc::format!("__win_{idx}"),
12110 });
12111 return;
12112 }
12113 match e {
12114 Expr::Binary { lhs, rhs, .. } => {
12115 rewrite_window_to_columns(lhs, window_nodes);
12116 rewrite_window_to_columns(rhs, window_nodes);
12117 }
12118 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12119 rewrite_window_to_columns(expr, window_nodes);
12120 }
12121 Expr::FunctionCall { args, .. } => {
12122 for a in args {
12123 rewrite_window_to_columns(a, window_nodes);
12124 }
12125 }
12126 Expr::Like { expr, pattern, .. } => {
12127 rewrite_window_to_columns(expr, window_nodes);
12128 rewrite_window_to_columns(pattern, window_nodes);
12129 }
12130 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12131 _ => {}
12132 }
12133}
12134
12135fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12139 for (x, y) in a.iter().zip(b.iter()) {
12140 let c = value_cmp(x, y);
12141 if c != core::cmp::Ordering::Equal {
12142 return c;
12143 }
12144 }
12145 a.len().cmp(&b.len())
12146}
12147
12148fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12149 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12150 let c = value_cmp(va, vb);
12151 let c = if *desc { c.reverse() } else { c };
12152 if c != core::cmp::Ordering::Equal {
12153 return c;
12154 }
12155 }
12156 a.len().cmp(&b.len())
12157}
12158
12159const fn value_is_integer(v: &Value) -> bool {
12165 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12166}
12167
12168const fn value_to_i64(v: &Value) -> i64 {
12172 match v {
12173 Value::SmallInt(n) => *n as i64,
12174 Value::Int(n) => *n as i64,
12175 Value::BigInt(n) => *n,
12176 _ => panic!("value_to_i64 called on non-integer Value"),
12177 }
12178}
12179
12180fn generate_series_integers(
12186 start: i64,
12187 stop: i64,
12188 step: i64,
12189 cancel: &CancelToken<'_>,
12190) -> Result<alloc::vec::Vec<Row>, EngineError> {
12191 if step == 0 {
12192 return Err(EngineError::Unsupported(
12193 "generate_series(): step argument cannot be zero".into(),
12194 ));
12195 }
12196 let mut out = alloc::vec::Vec::new();
12197 let mut cur = start;
12198 const MAX_ROWS: usize = 10_000_000;
12202 loop {
12203 cancel.check()?;
12204 if step > 0 && cur > stop {
12205 break;
12206 }
12207 if step < 0 && cur < stop {
12208 break;
12209 }
12210 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12211 if out.len() > MAX_ROWS {
12212 return Err(EngineError::Unsupported(alloc::format!(
12213 "generate_series(): exceeded {MAX_ROWS} rows; \
12214 narrow start/stop or use a larger step"
12215 )));
12216 }
12217 cur = match cur.checked_add(step) {
12218 Some(n) => n,
12219 None => break,
12220 };
12221 }
12222 Ok(out)
12223}
12224
12225fn generate_series_timestamps(
12230 start: i64,
12231 stop: i64,
12232 step: Value,
12233 cancel: &CancelToken<'_>,
12234) -> Result<alloc::vec::Vec<Row>, EngineError> {
12235 let (months, micros) = match &step {
12236 Value::Interval { months, micros } => (*months, *micros),
12237 _ => unreachable!("caller guards step.is_interval"),
12238 };
12239 if months == 0 && micros == 0 {
12240 return Err(EngineError::Unsupported(
12241 "generate_series(): INTERVAL step cannot be zero".into(),
12242 ));
12243 }
12244 let ascending = months > 0 || micros > 0;
12245 let mut out = alloc::vec::Vec::new();
12246 let mut cur = Value::Timestamp(start);
12247 const MAX_ROWS: usize = 10_000_000;
12248 loop {
12249 cancel.check()?;
12250 let cur_t = match cur {
12251 Value::Timestamp(t) => t,
12252 _ => unreachable!("loop invariant: cur is Timestamp"),
12253 };
12254 if ascending && cur_t > stop {
12255 break;
12256 }
12257 if !ascending && cur_t < stop {
12258 break;
12259 }
12260 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12261 if out.len() > MAX_ROWS {
12262 return Err(EngineError::Unsupported(alloc::format!(
12263 "generate_series(): exceeded {MAX_ROWS} rows; \
12264 narrow start/stop or use a larger step"
12265 )));
12266 }
12267 let next = eval::apply_binary_interval(
12268 spg_sql::ast::BinOp::Add,
12269 &cur,
12270 &Value::Interval { months, micros },
12271 )
12272 .map_err(EngineError::Eval)?;
12273 cur = match next {
12274 Some(v) => v,
12275 None => break,
12276 };
12277 }
12278 Ok(out)
12279}
12280
12281#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12283 use core::cmp::Ordering;
12284 match (a, b) {
12285 (Value::Null, Value::Null) => Ordering::Equal,
12286 (Value::Null, _) => Ordering::Less,
12287 (_, Value::Null) => Ordering::Greater,
12288 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12289 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12290 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12291 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12292 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12293 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12294 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12295 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12296 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12299 }
12300}
12301
12302#[allow(
12308 clippy::too_many_arguments,
12309 clippy::cast_possible_truncation,
12310 clippy::cast_possible_wrap,
12311 clippy::cast_precision_loss,
12312 clippy::cast_sign_loss,
12313 clippy::doc_markdown,
12314 clippy::too_many_lines,
12315 clippy::type_complexity,
12316 clippy::match_same_arms
12317)]
12318fn compute_window_partition(
12319 name: &str,
12320 args: &[Expr],
12321 ordered: bool,
12322 frame: Option<&WindowFrame>,
12323 null_treatment: spg_sql::ast::NullTreatment,
12324 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12325 filtered_rows: &[&Row],
12326 ctx: &EvalContext<'_>,
12327 out_vals: &mut [Value],
12328) -> Result<(), EngineError> {
12329 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12330 let lower = name.to_ascii_lowercase();
12331 match lower.as_str() {
12332 "row_number" => {
12333 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12334 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12335 }
12336 Ok(())
12337 }
12338 "rank" => {
12339 let mut prev_key: Option<&[(Value, bool)]> = None;
12340 let mut current_rank: i64 = 1;
12341 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12342 if let Some(p) = prev_key
12343 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12344 {
12345 current_rank = (i + 1) as i64;
12346 }
12347 if prev_key.is_none() {
12348 current_rank = 1;
12349 }
12350 out_vals[*idx] = Value::BigInt(current_rank);
12351 prev_key = Some(okey.as_slice());
12352 }
12353 Ok(())
12354 }
12355 "dense_rank" => {
12356 let mut prev_key: Option<&[(Value, bool)]> = None;
12357 let mut current_rank: i64 = 0;
12358 for (_, okey, idx) in slice {
12359 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12360 current_rank += 1;
12361 }
12362 out_vals[*idx] = Value::BigInt(current_rank);
12363 prev_key = Some(okey.as_slice());
12364 }
12365 Ok(())
12366 }
12367 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12368 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12371 slice.iter().map(|_| Value::Null).collect()
12372 } else {
12373 slice
12374 .iter()
12375 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12376 .collect::<Result<_, _>>()
12377 .map_err(EngineError::Eval)?
12378 };
12379 let eff = effective_frame(frame, ordered)?;
12383 #[allow(clippy::needless_range_loop)]
12384 for i in 0..slice.len() {
12385 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12386 let mut sum: f64 = 0.0;
12387 let mut count: i64 = 0;
12388 let mut min_v: Option<f64> = None;
12389 let mut max_v: Option<f64> = None;
12390 let mut row_count: i64 = 0;
12391 if lo <= hi {
12392 for j in lo..=hi {
12393 let v = &arg_values[j];
12394 match lower.as_str() {
12395 "count_star" => row_count += 1,
12396 "count" => {
12397 if !v.is_null() {
12398 count += 1;
12399 }
12400 }
12401 _ => {
12402 if let Some(x) = value_to_f64(v) {
12403 sum += x;
12404 count += 1;
12405 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12406 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12407 }
12408 }
12409 }
12410 }
12411 }
12412 let value = match lower.as_str() {
12413 "count_star" => Value::BigInt(row_count),
12414 "count" => Value::BigInt(count),
12415 "sum" => Value::Float(sum),
12416 "avg" => {
12417 if count == 0 {
12418 Value::Null
12419 } else {
12420 Value::Float(sum / count as f64)
12421 }
12422 }
12423 "min" => min_v.map_or(Value::Null, Value::Float),
12424 "max" => max_v.map_or(Value::Null, Value::Float),
12425 _ => unreachable!(),
12426 };
12427 let (_, _, idx) = &slice[i];
12428 out_vals[*idx] = value;
12429 }
12430 Ok(())
12431 }
12432 "lag" | "lead" => {
12433 if args.is_empty() {
12436 return Err(EngineError::Unsupported(alloc::format!(
12437 "{lower}() requires at least one argument"
12438 )));
12439 }
12440 let offset: i64 = if args.len() >= 2 {
12441 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12442 .map_err(EngineError::Eval)?;
12443 match v {
12444 Value::SmallInt(n) => i64::from(n),
12445 Value::Int(n) => i64::from(n),
12446 Value::BigInt(n) => n,
12447 _ => {
12448 return Err(EngineError::Unsupported(alloc::format!(
12449 "{lower}() offset must be integer"
12450 )));
12451 }
12452 }
12453 } else {
12454 1
12455 };
12456 let default: Value = if args.len() >= 3 {
12457 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12458 .map_err(EngineError::Eval)?
12459 } else {
12460 Value::Null
12461 };
12462 let values: Vec<Value> = slice
12463 .iter()
12464 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12465 .collect::<Result<_, _>>()
12466 .map_err(EngineError::Eval)?;
12467 let n = slice.len();
12468 for (i, (_, _, idx)) in slice.iter().enumerate() {
12469 let signed_offset = if lower == "lag" { -offset } else { offset };
12470 let v = if ignore_nulls {
12471 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12475 let needed: i64 = signed_offset.abs();
12476 if needed == 0 {
12477 values[i].clone()
12478 } else {
12479 let mut j: i64 = i as i64;
12480 let mut hits: i64 = 0;
12481 let mut found: Option<Value> = None;
12482 loop {
12483 j += step;
12484 if j < 0 || j >= n as i64 {
12485 break;
12486 }
12487 #[allow(clippy::cast_sign_loss)]
12488 let v = &values[j as usize];
12489 if !v.is_null() {
12490 hits += 1;
12491 if hits == needed {
12492 found = Some(v.clone());
12493 break;
12494 }
12495 }
12496 }
12497 found.unwrap_or_else(|| default.clone())
12498 }
12499 } else {
12500 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12501 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12502 default.clone()
12503 } else {
12504 #[allow(clippy::cast_sign_loss)]
12505 {
12506 values[target_signed as usize].clone()
12507 }
12508 }
12509 };
12510 out_vals[*idx] = v;
12511 }
12512 Ok(())
12513 }
12514 "first_value" | "last_value" | "nth_value" => {
12515 if args.is_empty() {
12516 return Err(EngineError::Unsupported(alloc::format!(
12517 "{lower}() requires at least one argument"
12518 )));
12519 }
12520 let values: Vec<Value> = slice
12521 .iter()
12522 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12523 .collect::<Result<_, _>>()
12524 .map_err(EngineError::Eval)?;
12525 let nth: usize = if lower == "nth_value" {
12526 if args.len() < 2 {
12527 return Err(EngineError::Unsupported(
12528 "nth_value() requires (expr, n)".into(),
12529 ));
12530 }
12531 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12532 .map_err(EngineError::Eval)?;
12533 let raw = match v {
12534 Value::SmallInt(n) => i64::from(n),
12535 Value::Int(n) => i64::from(n),
12536 Value::BigInt(n) => n,
12537 _ => {
12538 return Err(EngineError::Unsupported(
12539 "nth_value() n must be integer".into(),
12540 ));
12541 }
12542 };
12543 if raw < 1 {
12544 return Err(EngineError::Unsupported(
12545 "nth_value() n must be >= 1".into(),
12546 ));
12547 }
12548 #[allow(clippy::cast_sign_loss)]
12549 {
12550 raw as usize
12551 }
12552 } else {
12553 0
12554 };
12555 let eff = effective_frame(frame, ordered)?;
12556 for i in 0..slice.len() {
12557 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12558 let (_, _, idx) = &slice[i];
12559 let v = if lo > hi {
12560 Value::Null
12561 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
12562 if lower == "first_value" {
12565 (lo..=hi)
12566 .find_map(|j| {
12567 let v = &values[j];
12568 (!v.is_null()).then(|| v.clone())
12569 })
12570 .unwrap_or(Value::Null)
12571 } else {
12572 (lo..=hi)
12573 .rev()
12574 .find_map(|j| {
12575 let v = &values[j];
12576 (!v.is_null()).then(|| v.clone())
12577 })
12578 .unwrap_or(Value::Null)
12579 }
12580 } else {
12581 match lower.as_str() {
12582 "first_value" => values[lo].clone(),
12583 "last_value" => values[hi].clone(),
12584 "nth_value" => {
12585 let pos = lo + nth - 1;
12586 if pos > hi {
12587 Value::Null
12588 } else {
12589 values[pos].clone()
12590 }
12591 }
12592 _ => unreachable!(),
12593 }
12594 };
12595 out_vals[*idx] = v;
12596 }
12597 Ok(())
12598 }
12599 "ntile" => {
12600 if args.is_empty() {
12601 return Err(EngineError::Unsupported(
12602 "ntile(n) requires an integer argument".into(),
12603 ));
12604 }
12605 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
12606 .map_err(EngineError::Eval)?;
12607 let bucket_count: i64 = match v {
12608 Value::SmallInt(n) => i64::from(n),
12609 Value::Int(n) => i64::from(n),
12610 Value::BigInt(n) => n,
12611 _ => {
12612 return Err(EngineError::Unsupported(
12613 "ntile() argument must be integer".into(),
12614 ));
12615 }
12616 };
12617 if bucket_count < 1 {
12618 return Err(EngineError::Unsupported(
12619 "ntile() argument must be >= 1".into(),
12620 ));
12621 }
12622 #[allow(clippy::cast_sign_loss)]
12623 let buckets = bucket_count as usize;
12624 let n = slice.len();
12625 let base = n / buckets;
12628 let extras = n % buckets;
12629 let mut bucket: usize = 1;
12630 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
12631 let mut buckets_with_extra_remaining = extras;
12632 for (_, _, idx) in slice {
12633 if remaining_in_bucket == 0 {
12634 bucket += 1;
12635 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
12636 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
12637 base + 1
12638 } else {
12639 base
12640 };
12641 if remaining_in_bucket == 0 {
12644 remaining_in_bucket = 1;
12645 }
12646 }
12647 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
12648 remaining_in_bucket -= 1;
12649 }
12650 Ok(())
12651 }
12652 "percent_rank" => {
12653 let n = slice.len();
12656 let mut prev_key: Option<&[(Value, bool)]> = None;
12657 let mut current_rank: i64 = 1;
12658 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12659 if let Some(p) = prev_key
12660 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12661 {
12662 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
12663 }
12664 if prev_key.is_none() {
12665 current_rank = 1;
12666 }
12667 #[allow(clippy::cast_precision_loss)]
12668 let pr = if n <= 1 {
12669 0.0
12670 } else {
12671 (current_rank - 1) as f64 / (n - 1) as f64
12672 };
12673 out_vals[*idx] = Value::Float(pr);
12674 prev_key = Some(okey.as_slice());
12675 }
12676 Ok(())
12677 }
12678 "cume_dist" => {
12679 let n = slice.len();
12681 for i in 0..slice.len() {
12683 let peer_end = peer_group_end(slice, i);
12684 #[allow(clippy::cast_precision_loss)]
12685 let cd = (peer_end + 1) as f64 / n as f64;
12686 let (_, _, idx) = &slice[i];
12687 out_vals[*idx] = Value::Float(cd);
12688 }
12689 Ok(())
12690 }
12691 other => Err(EngineError::Unsupported(alloc::format!(
12692 "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)"
12693 ))),
12694 }
12695}
12696
12697fn effective_frame(
12704 frame: Option<&WindowFrame>,
12705 ordered: bool,
12706) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
12707 match frame {
12708 None => {
12709 if ordered {
12710 Ok((
12711 FrameKind::Range,
12712 FrameBound::UnboundedPreceding,
12713 FrameBound::CurrentRow,
12714 ))
12715 } else {
12716 Ok((
12717 FrameKind::Rows,
12718 FrameBound::UnboundedPreceding,
12719 FrameBound::UnboundedFollowing,
12720 ))
12721 }
12722 }
12723 Some(fr) => {
12724 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
12725 if matches!(fr.start, FrameBound::UnboundedFollowing)
12727 || matches!(end, FrameBound::UnboundedPreceding)
12728 {
12729 return Err(EngineError::Unsupported(alloc::format!(
12730 "invalid frame: start={:?} end={:?}",
12731 fr.start,
12732 end
12733 )));
12734 }
12735 if fr.kind == FrameKind::Range
12740 && (matches!(
12741 fr.start,
12742 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12743 ) || matches!(
12744 end,
12745 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12746 ))
12747 {
12748 return Err(EngineError::Unsupported(
12749 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
12750 ));
12751 }
12752 Ok((fr.kind, fr.start.clone(), end))
12753 }
12754 }
12755}
12756
12757#[allow(clippy::type_complexity)]
12761fn frame_bounds_for_row(
12762 eff: &(FrameKind, FrameBound, FrameBound),
12763 i: usize,
12764 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12765) -> (usize, usize) {
12766 let (kind, start, end) = eff;
12767 let n = slice.len();
12768 let last = n.saturating_sub(1);
12769 let (mut lo, mut hi) = match kind {
12770 FrameKind::Rows => {
12771 let lo = match start {
12772 FrameBound::UnboundedPreceding => 0,
12773 FrameBound::OffsetPreceding(k) => {
12774 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12775 i.saturating_sub(k)
12776 }
12777 FrameBound::CurrentRow => i,
12778 FrameBound::OffsetFollowing(k) => {
12779 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12780 i.saturating_add(k).min(last)
12781 }
12782 FrameBound::UnboundedFollowing => last,
12783 };
12784 let hi = match end {
12785 FrameBound::UnboundedPreceding => 0,
12786 FrameBound::OffsetPreceding(k) => {
12787 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12788 i.saturating_sub(k)
12789 }
12790 FrameBound::CurrentRow => i,
12791 FrameBound::OffsetFollowing(k) => {
12792 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12793 i.saturating_add(k).min(last)
12794 }
12795 FrameBound::UnboundedFollowing => last,
12796 };
12797 (lo, hi)
12798 }
12799 FrameKind::Range => {
12800 let lo = match start {
12806 FrameBound::UnboundedPreceding => 0,
12807 FrameBound::CurrentRow => peer_group_start(slice, i),
12808 FrameBound::UnboundedFollowing => last,
12809 _ => unreachable!("offset bounds rejected for RANGE"),
12810 };
12811 let hi = match end {
12812 FrameBound::UnboundedPreceding => 0,
12813 FrameBound::CurrentRow => peer_group_end(slice, i),
12814 FrameBound::UnboundedFollowing => last,
12815 _ => unreachable!("offset bounds rejected for RANGE"),
12816 };
12817 (lo, hi)
12818 }
12819 };
12820 if hi >= n {
12821 hi = last;
12822 }
12823 if lo >= n {
12824 lo = last;
12825 }
12826 (lo, hi)
12827}
12828
12829#[allow(clippy::type_complexity)]
12833fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
12834 let key = &slice[i].1;
12835 let mut j = i;
12836 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
12837 j -= 1;
12838 }
12839 j
12840}
12841
12842#[allow(clippy::type_complexity)]
12845fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
12846 let key = &slice[i].1;
12847 let mut j = i;
12848 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
12849 j += 1;
12850 }
12851 j
12852}
12853
12854fn value_to_f64(v: &Value) -> Option<f64> {
12855 match v {
12856 Value::SmallInt(n) => Some(f64::from(*n)),
12857 Value::Int(n) => Some(f64::from(*n)),
12858 #[allow(clippy::cast_precision_loss)]
12859 Value::BigInt(n) => Some(*n as f64),
12860 Value::Float(x) => Some(*x),
12861 _ => None,
12862 }
12863}
12864
12865fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
12869 let mut any = false;
12870 for item in &stmt.items {
12871 if let SelectItem::Expr { expr, .. } = item {
12872 any = any || expr_has_subquery(expr);
12873 }
12874 }
12875 if let Some(w) = &stmt.where_ {
12876 any = any || expr_has_subquery(w);
12877 }
12878 if let Some(h) = &stmt.having {
12879 any = any || expr_has_subquery(h);
12880 }
12881 for o in &stmt.order_by {
12882 any = any || expr_has_subquery(&o.expr);
12883 }
12884 for (_, peer) in &stmt.unions {
12885 any = any || expr_tree_has_subquery(peer);
12886 }
12887 any
12888}
12889
12890fn expr_has_subquery(e: &Expr) -> bool {
12891 match e {
12892 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
12893 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
12894 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12895 expr_has_subquery(expr)
12896 }
12897 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
12898 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
12899 Expr::Extract { source, .. } => expr_has_subquery(source),
12900 Expr::WindowFunction {
12901 args,
12902 partition_by,
12903 order_by,
12904 ..
12905 } => {
12906 args.iter().any(expr_has_subquery)
12907 || partition_by.iter().any(expr_has_subquery)
12908 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
12909 }
12910 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
12911 Expr::Array(items) => items.iter().any(expr_has_subquery),
12912 Expr::ArraySubscript { target, index } => {
12913 expr_has_subquery(target) || expr_has_subquery(index)
12914 }
12915 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
12916 Expr::Case {
12917 operand,
12918 branches,
12919 else_branch,
12920 } => {
12921 operand.as_deref().is_some_and(expr_has_subquery)
12922 || branches
12923 .iter()
12924 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
12925 || else_branch.as_deref().is_some_and(expr_has_subquery)
12926 }
12927 }
12928}
12929
12930fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
12937 let lit = match v {
12938 Value::Null => Literal::Null,
12939 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
12940 Value::Int(n) => Literal::Integer(i64::from(n)),
12941 Value::BigInt(n) => Literal::Integer(n),
12942 Value::Float(x) => Literal::Float(x),
12943 Value::Text(s) | Value::Json(s) => Literal::String(s),
12944 Value::Bool(b) => Literal::Bool(b),
12945 other => {
12946 return Err(EngineError::Unsupported(alloc::format!(
12947 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
12948 other.data_type()
12949 )));
12950 }
12951 };
12952 Ok(Expr::Literal(lit))
12953}
12954
12955fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
12961 let lit = match v {
12962 Value::Null => Literal::Null,
12963 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
12964 Value::Int(n) => Literal::Integer(i64::from(n)),
12965 Value::BigInt(n) => Literal::Integer(n),
12966 Value::Float(x) => Literal::Float(x),
12967 Value::Text(s) | Value::Json(s) => Literal::String(s),
12968 Value::Bool(b) => Literal::Bool(b),
12969 Value::Vector(xs) => Literal::Vector(xs),
12970 Value::Date(days) => {
12974 let micros = (i64::from(days)) * 86_400_000_000;
12975 Literal::String(format_timestamp_micros_as_date(micros))
12976 }
12977 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
12978 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
12979 other => {
12980 return Err(EngineError::Unsupported(alloc::format!(
12981 "INSERT … SELECT cannot materialise value of type {:?}; \
12982 add an explicit CAST in the inner SELECT",
12983 other.data_type()
12984 )));
12985 }
12986 };
12987 Ok(Expr::Literal(lit))
12988}
12989
12990fn format_timestamp_micros(us: i64) -> String {
12991 let days = us.div_euclid(86_400_000_000);
12993 let intra_day = us.rem_euclid(86_400_000_000);
12994 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
12995 let secs = intra_day / 1_000_000;
12996 let us_rem = intra_day % 1_000_000;
12997 let h = (secs / 3600) % 24;
12998 let m = (secs / 60) % 60;
12999 let s = secs % 60;
13000 if us_rem == 0 {
13001 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13002 } else {
13003 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13004 }
13005}
13006
13007fn format_timestamp_micros_as_date(us: i64) -> String {
13008 let days = us.div_euclid(86_400_000_000);
13011 let jdn = days + 2_440_588;
13013 let (y, mo, d) = jdn_to_ymd(jdn);
13014 alloc::format!("{y:04}-{mo:02}-{d:02}")
13015}
13016
13017fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13018 let l = jdn + 68569;
13020 let n = (4 * l) / 146_097;
13021 let l = l - (146_097 * n + 3) / 4;
13022 let i = (4000 * (l + 1)) / 1_461_001;
13023 let l = l - (1461 * i) / 4 + 31;
13024 let j = (80 * l) / 2447;
13025 let day = (l - (2447 * j) / 80) as u32;
13026 let l = j / 11;
13027 let month = (j + 2 - 12 * l) as u32;
13028 let year = 100 * (n - 49) + i + l;
13029 (year, month, day)
13030}
13031
13032fn format_numeric(scaled: i128, scale: u8) -> String {
13033 if scale == 0 {
13034 return alloc::format!("{scaled}");
13035 }
13036 let abs = scaled.unsigned_abs();
13037 let divisor = 10u128.pow(u32::from(scale));
13038 let whole = abs / divisor;
13039 let frac = abs % divisor;
13040 let sign = if scaled < 0 { "-" } else { "" };
13041 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13042}
13043
13044fn rewrite_column_in_source(
13068 src: &str,
13069 old: &str,
13070 new: &str,
13071) -> Result<alloc::string::String, EngineError> {
13072 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13073 EngineError::Unsupported(alloc::format!(
13074 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13075 failed to parse for rewrite ({e})"
13076 ))
13077 })?;
13078 rewrite_column_in_expr(&mut expr, old, new);
13079 Ok(alloc::format!("{expr}"))
13080}
13081
13082fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13090 match e {
13091 Expr::Column(c) => {
13092 if c.name.eq_ignore_ascii_case(old) {
13093 c.name = new.to_string();
13094 }
13095 }
13096 Expr::Binary { lhs, rhs, .. } => {
13097 rewrite_column_in_expr(lhs, old, new);
13098 rewrite_column_in_expr(rhs, old, new);
13099 }
13100 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13101 rewrite_column_in_expr(expr, old, new);
13102 }
13103 Expr::FunctionCall { args, .. } => {
13104 for a in args {
13105 rewrite_column_in_expr(a, old, new);
13106 }
13107 }
13108 Expr::Like { expr, pattern, .. } => {
13109 rewrite_column_in_expr(expr, old, new);
13110 rewrite_column_in_expr(pattern, old, new);
13111 }
13112 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13113 Expr::WindowFunction {
13114 args,
13115 partition_by,
13116 order_by,
13117 ..
13118 } => {
13119 for a in args {
13120 rewrite_column_in_expr(a, old, new);
13121 }
13122 for p in partition_by {
13123 rewrite_column_in_expr(p, old, new);
13124 }
13125 for (o, _) in order_by {
13126 rewrite_column_in_expr(o, old, new);
13127 }
13128 }
13129 Expr::Array(items) => {
13130 for elem in items {
13131 rewrite_column_in_expr(elem, old, new);
13132 }
13133 }
13134 Expr::ArraySubscript { target, index } => {
13135 rewrite_column_in_expr(target, old, new);
13136 rewrite_column_in_expr(index, old, new);
13137 }
13138 Expr::AnyAll { expr, array, .. } => {
13139 rewrite_column_in_expr(expr, old, new);
13140 rewrite_column_in_expr(array, old, new);
13141 }
13142 Expr::Case {
13143 operand,
13144 branches,
13145 else_branch,
13146 } => {
13147 if let Some(o) = operand {
13148 rewrite_column_in_expr(o, old, new);
13149 }
13150 for (w, t) in branches {
13151 rewrite_column_in_expr(w, old, new);
13152 rewrite_column_in_expr(t, old, new);
13153 }
13154 if let Some(e) = else_branch {
13155 rewrite_column_in_expr(e, old, new);
13156 }
13157 }
13158 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13162 Expr::Literal(_) | Expr::Placeholder(_) => {}
13163 }
13164}
13165
13166pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13174 match stmt {
13175 Statement::Select(s) => substitute_select(s, params)?,
13176 Statement::Insert(ins) => {
13177 for row in &mut ins.rows {
13178 for e in row {
13179 substitute_expr(e, params)?;
13180 }
13181 }
13182 }
13183 Statement::Update(u) => {
13184 for (_, e) in &mut u.assignments {
13185 substitute_expr(e, params)?;
13186 }
13187 if let Some(w) = &mut u.where_ {
13188 substitute_expr(w, params)?;
13189 }
13190 }
13191 Statement::Delete(d) => {
13192 if let Some(w) = &mut d.where_ {
13193 substitute_expr(w, params)?;
13194 }
13195 }
13196 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13197 _ => {}
13200 }
13201 Ok(())
13202}
13203
13204fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13205 for item in &mut s.items {
13206 if let SelectItem::Expr { expr, .. } = item {
13207 substitute_expr(expr, params)?;
13208 }
13209 }
13210 if let Some(w) = &mut s.where_ {
13211 substitute_expr(w, params)?;
13212 }
13213 if let Some(gs) = &mut s.group_by {
13214 for g in gs {
13215 substitute_expr(g, params)?;
13216 }
13217 }
13218 if let Some(h) = &mut s.having {
13219 substitute_expr(h, params)?;
13220 }
13221 for o in &mut s.order_by {
13222 substitute_expr(&mut o.expr, params)?;
13223 }
13224 for (_, peer) in &mut s.unions {
13225 substitute_select(peer, params)?;
13226 }
13227 if let Some(le) = s.limit {
13232 s.limit = Some(resolve_limit_placeholder(le, params)?);
13233 }
13234 if let Some(le) = s.offset {
13235 s.offset = Some(resolve_limit_placeholder(le, params)?);
13236 }
13237 Ok(())
13238}
13239
13240fn resolve_limit_placeholder(
13241 le: spg_sql::ast::LimitExpr,
13242 params: &[Value],
13243) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13244 use spg_sql::ast::LimitExpr;
13245 match le {
13246 LimitExpr::Literal(_) => Ok(le),
13247 LimitExpr::Placeholder(n) => {
13248 let idx = usize::from(n).saturating_sub(1);
13249 let v = params.get(idx).ok_or_else(|| {
13250 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13251 n,
13252 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13253 })
13254 })?;
13255 let int = match v {
13256 Value::SmallInt(x) => Some(i64::from(*x)),
13257 Value::Int(x) => Some(i64::from(*x)),
13258 Value::BigInt(x) => Some(*x),
13259 _ => None,
13260 }
13261 .ok_or_else(|| {
13262 EngineError::Unsupported(alloc::format!(
13263 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13264 ))
13265 })?;
13266 if int < 0 {
13267 return Err(EngineError::Unsupported(alloc::format!(
13268 "LIMIT/OFFSET ${n} bound to negative value {int}"
13269 )));
13270 }
13271 let bounded = u32::try_from(int).map_err(|_| {
13272 EngineError::Unsupported(alloc::format!(
13273 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13274 ))
13275 })?;
13276 Ok(LimitExpr::Literal(bounded))
13277 }
13278 }
13279}
13280
13281fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13282 if let Expr::Placeholder(n) = e {
13283 let idx = usize::from(*n).saturating_sub(1);
13284 let v = params.get(idx).ok_or_else(|| {
13285 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13286 n: *n,
13287 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13288 })
13289 })?;
13290 *e = Expr::Literal(value_to_literal(v.clone()));
13291 return Ok(());
13292 }
13293 match e {
13294 Expr::Binary { lhs, rhs, .. } => {
13295 substitute_expr(lhs, params)?;
13296 substitute_expr(rhs, params)?;
13297 }
13298 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13299 substitute_expr(expr, params)?;
13300 }
13301 Expr::FunctionCall { args, .. } => {
13302 for a in args {
13303 substitute_expr(a, params)?;
13304 }
13305 }
13306 Expr::Like { expr, pattern, .. } => {
13307 substitute_expr(expr, params)?;
13308 substitute_expr(pattern, params)?;
13309 }
13310 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13311 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13312 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13313 Expr::InSubquery { expr, subquery, .. } => {
13314 substitute_expr(expr, params)?;
13315 substitute_select(subquery, params)?;
13316 }
13317 Expr::WindowFunction {
13318 args,
13319 partition_by,
13320 order_by,
13321 ..
13322 } => {
13323 for a in args {
13324 substitute_expr(a, params)?;
13325 }
13326 for p in partition_by {
13327 substitute_expr(p, params)?;
13328 }
13329 for (e, _) in order_by {
13330 substitute_expr(e, params)?;
13331 }
13332 }
13333 Expr::Literal(_) | Expr::Column(_) => {}
13334 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13336 Expr::Array(items) => {
13337 for elem in items {
13338 substitute_expr(elem, params)?;
13339 }
13340 }
13341 Expr::ArraySubscript { target, index } => {
13342 substitute_expr(target, params)?;
13343 substitute_expr(index, params)?;
13344 }
13345 Expr::AnyAll { expr, array, .. } => {
13346 substitute_expr(expr, params)?;
13347 substitute_expr(array, params)?;
13348 }
13349 Expr::Case {
13350 operand,
13351 branches,
13352 else_branch,
13353 } => {
13354 if let Some(o) = operand {
13355 substitute_expr(o, params)?;
13356 }
13357 for (w, t) in branches {
13358 substitute_expr(w, params)?;
13359 substitute_expr(t, params)?;
13360 }
13361 if let Some(e) = else_branch {
13362 substitute_expr(e, params)?;
13363 }
13364 }
13365 }
13366 Ok(())
13367}
13368
13369fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13387 use core::cmp::Ordering;
13388 match (a, b) {
13389 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13390 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13391 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13392 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13393 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13394 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13395 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13396 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13397 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13398 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13399 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13400 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13401 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13402 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13403 (Value::SmallInt(n), Value::Float(x)) => {
13405 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13406 }
13407 (Value::Float(x), Value::SmallInt(n)) => {
13408 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13409 }
13410 (Value::Int(n), Value::Float(x)) => {
13411 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13412 }
13413 (Value::Float(x), Value::Int(n)) => {
13414 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13415 }
13416 (Value::BigInt(n), Value::Float(x)) => {
13417 #[allow(clippy::cast_precision_loss)]
13418 let nf = *n as f64;
13419 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13420 }
13421 (Value::Float(x), Value::BigInt(n)) => {
13422 #[allow(clippy::cast_precision_loss)]
13423 let nf = *n as f64;
13424 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13425 }
13426 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13429 }
13430}
13431
13432fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13439 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13440 out.push('[');
13441 for (i, b) in bounds.iter().enumerate() {
13442 if i > 0 {
13443 out.push_str(", ");
13444 }
13445 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13446 if needs_quote {
13447 out.push('"');
13448 for ch in b.chars() {
13449 if ch == '"' || ch == '\\' {
13450 out.push('\\');
13451 }
13452 out.push(ch);
13453 }
13454 out.push('"');
13455 } else {
13456 out.push_str(b);
13457 }
13458 }
13459 out.push(']');
13460 out
13461}
13462
13463pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13473 match v {
13474 Value::Null => "NULL".to_string(),
13475 Value::SmallInt(n) => alloc::format!("{n}"),
13476 Value::Int(n) => alloc::format!("{n}"),
13477 Value::BigInt(n) => alloc::format!("{n}"),
13478 Value::Float(x) => alloc::format!("{x:?}"),
13479 Value::Text(s) | Value::Json(s) => s.clone(),
13480 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13481 Value::Date(d) => eval::format_date(*d),
13482 Value::Timestamp(t) => eval::format_timestamp(*t),
13483 Value::Time(us) => eval::format_time(*us),
13485 Value::Year(y) => alloc::format!("{y:04}"),
13487 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13489 Value::Money(c) => eval::format_money(*c),
13491 v @ Value::Range { .. } => format_range_str(v),
13493 Value::Hstore(pairs) => format_hstore_str(pairs),
13495 Value::IntArray2D(rows) => format_int_2d_text(rows),
13497 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13498 Value::TextArray2D(rows) => format_text_2d_text(rows),
13499 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13500 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13501 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13502 alloc::format!("{v:?}")
13506 }
13507 _ => alloc::format!("{v:?}"),
13511 }
13512}
13513
13514const fn is_internal_table_name(_name: &str) -> bool {
13521 false
13522}
13523
13524fn value_to_literal(v: Value) -> Literal {
13525 match v {
13526 Value::Null => Literal::Null,
13527 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13528 Value::Int(n) => Literal::Integer(i64::from(n)),
13529 Value::BigInt(n) => Literal::Integer(n),
13530 Value::Float(x) => Literal::Float(x),
13531 Value::Text(s) | Value::Json(s) => Literal::String(s),
13532 Value::Bool(b) => Literal::Bool(b),
13533 Value::Vector(v) => Literal::Vector(v),
13534 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13535 Value::Date(d) => Literal::String(eval::format_date(d)),
13536 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
13537 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
13543 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
13548 Value::TextArray(items) => Literal::String(eval::format_text_array(&items)),
13553 Value::IntArray(items) => Literal::String(eval::format_int_array(&items)),
13554 Value::BigIntArray(items) => Literal::String(eval::format_bigint_array(&items)),
13555 Value::Interval { months, micros } => Literal::Interval {
13556 months,
13557 micros,
13558 text: eval::format_interval(months, micros),
13559 },
13560 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
13563 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
13564 v => Literal::String(alloc::format!("{v:?}")),
13568 }
13569}
13570
13571fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
13572 let Some(now) = now_micros else {
13573 return;
13574 };
13575 match stmt {
13576 Statement::Select(s) => rewrite_select_clock(s, now),
13577 Statement::Insert(ins) => {
13578 for row in &mut ins.rows {
13579 for e in row {
13580 rewrite_expr_clock(e, now);
13581 }
13582 }
13583 }
13584 _ => {}
13585 }
13586}
13587
13588fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
13589 for item in &mut s.items {
13590 if let SelectItem::Expr { expr, .. } = item {
13591 rewrite_expr_clock(expr, now);
13592 }
13593 }
13594 if let Some(w) = &mut s.where_ {
13595 rewrite_expr_clock(w, now);
13596 }
13597 if let Some(gs) = &mut s.group_by {
13598 for g in gs {
13599 rewrite_expr_clock(g, now);
13600 }
13601 }
13602 if let Some(h) = &mut s.having {
13603 rewrite_expr_clock(h, now);
13604 }
13605 for o in &mut s.order_by {
13606 rewrite_expr_clock(&mut o.expr, now);
13607 }
13608 for (_, peer) in &mut s.unions {
13609 rewrite_select_clock(peer, now);
13610 }
13611}
13612
13613fn rewrite_expr_clock(e: &mut Expr, now: i64) {
13621 if let Some(replacement) = clock_replacement_for(e, now) {
13625 *e = replacement;
13626 return;
13627 }
13628 match e {
13629 Expr::Binary { lhs, rhs, .. } => {
13630 rewrite_expr_clock(lhs, now);
13631 rewrite_expr_clock(rhs, now);
13632 }
13633 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13634 rewrite_expr_clock(expr, now);
13635 }
13636 Expr::FunctionCall { args, .. } => {
13637 for a in args {
13638 rewrite_expr_clock(a, now);
13639 }
13640 }
13641 Expr::Like { expr, pattern, .. } => {
13642 rewrite_expr_clock(expr, now);
13643 rewrite_expr_clock(pattern, now);
13644 }
13645 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
13646 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
13650 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
13651 Expr::InSubquery { expr, subquery, .. } => {
13652 rewrite_expr_clock(expr, now);
13653 rewrite_select_clock(subquery, now);
13654 }
13655 Expr::WindowFunction {
13658 args,
13659 partition_by,
13660 order_by,
13661 ..
13662 } => {
13663 for a in args {
13664 rewrite_expr_clock(a, now);
13665 }
13666 for p in partition_by {
13667 rewrite_expr_clock(p, now);
13668 }
13669 for (e, _) in order_by {
13670 rewrite_expr_clock(e, now);
13671 }
13672 }
13673 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
13674 Expr::Array(items) => {
13675 for elem in items {
13676 rewrite_expr_clock(elem, now);
13677 }
13678 }
13679 Expr::ArraySubscript { target, index } => {
13680 rewrite_expr_clock(target, now);
13681 rewrite_expr_clock(index, now);
13682 }
13683 Expr::AnyAll { expr, array, .. } => {
13684 rewrite_expr_clock(expr, now);
13685 rewrite_expr_clock(array, now);
13686 }
13687 Expr::Case {
13688 operand,
13689 branches,
13690 else_branch,
13691 } => {
13692 if let Some(o) = operand {
13693 rewrite_expr_clock(o, now);
13694 }
13695 for (w, t) in branches {
13696 rewrite_expr_clock(w, now);
13697 rewrite_expr_clock(t, now);
13698 }
13699 if let Some(e) = else_branch {
13700 rewrite_expr_clock(e, now);
13701 }
13702 }
13703 }
13704}
13705
13706fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
13713 let (kind, name) = match e {
13714 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
13715 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
13716 _ => return None,
13717 };
13718 enum ClockShape {
13726 Timestamp,
13727 Date,
13728 UnixSeconds,
13729 }
13730 let shape = match name.len() {
13731 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
13732 Some(ClockShape::Timestamp)
13733 }
13734 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
13735 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
13736 Some(ClockShape::UnixSeconds)
13737 }
13738 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
13739 _ => None,
13740 };
13741 let shape = shape?;
13742 let payload = match shape {
13743 ClockShape::Timestamp => now,
13744 ClockShape::Date => now.div_euclid(86_400_000_000),
13745 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
13746 };
13747 let target = match shape {
13748 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
13749 ClockShape::Date => spg_sql::ast::CastTarget::Date,
13750 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
13751 };
13752 Some(Expr::Cast {
13753 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
13754 target,
13755 })
13756}
13757
13758#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13759enum ClockSite {
13760 Fn,
13761 BareIdent,
13762}
13763
13764fn expand_group_by_all(s: &mut SelectStatement) {
13775 if !s.group_by_all {
13776 for (_, peer) in &mut s.unions {
13777 expand_group_by_all(peer);
13778 }
13779 return;
13780 }
13781 let mut groups: Vec<Expr> = Vec::new();
13782 for item in &s.items {
13783 if let SelectItem::Expr { expr, .. } = item
13784 && !aggregate::contains_aggregate(expr)
13785 {
13786 groups.push(expr.clone());
13787 }
13788 }
13789 s.group_by = Some(groups);
13790 s.group_by_all = false;
13791 for (_, peer) in &mut s.unions {
13792 expand_group_by_all(peer);
13793 }
13794}
13795
13796fn resolve_order_by_position(s: &mut SelectStatement) {
13797 for order in &mut s.order_by {
13802 match &order.expr {
13803 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
13804 if let Ok(idx_one_based) = usize::try_from(*n) {
13805 let idx = idx_one_based - 1;
13806 if idx < s.items.len()
13807 && let SelectItem::Expr { expr, .. } = &s.items[idx]
13808 {
13809 order.expr = expr.clone();
13810 }
13811 }
13812 }
13813 Expr::Column(c) if c.qualifier.is_none() => {
13814 for item in &s.items {
13816 if let SelectItem::Expr {
13817 expr,
13818 alias: Some(a),
13819 } = item
13820 && a == &c.name
13821 {
13822 order.expr = expr.clone();
13823 break;
13824 }
13825 }
13826 }
13827 _ => {}
13828 }
13829 }
13830 for (_, peer) in &mut s.unions {
13831 resolve_order_by_position(peer);
13832 }
13833}
13834
13835fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
13848 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
13849 match keep {
13850 Some(k) if k < tagged.len() && k > 0 => {
13851 let pivot = k - 1;
13852 tagged.select_nth_unstable_by(pivot, cmp);
13853 tagged[..k].sort_by(cmp);
13854 tagged.truncate(k);
13855 }
13856 _ => {
13857 tagged.sort_by(cmp);
13858 }
13859 }
13860}
13861
13862fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
13863 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
13864}
13865
13866fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
13870 use core::cmp::Ordering;
13871 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
13872 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
13873 let ord = if descs.get(i).copied().unwrap_or(false) {
13874 ord.reverse()
13875 } else {
13876 ord
13877 };
13878 if ord != Ordering::Equal {
13879 return ord;
13880 }
13881 }
13882 Ordering::Equal
13883}
13884
13885fn build_order_keys(
13888 order_by: &[OrderBy],
13889 row: &Row,
13890 ctx: &EvalContext,
13891) -> Result<Vec<f64>, EngineError> {
13892 let mut keys = Vec::with_capacity(order_by.len());
13893 for o in order_by {
13894 let v = eval::eval_expr(&o.expr, row, ctx)?;
13895 keys.push(value_to_order_key(&v)?);
13896 }
13897 Ok(keys)
13898}
13899
13900fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
13904 if let Some(off) = offset {
13905 let off = off as usize;
13906 if off >= rows.len() {
13907 rows.clear();
13908 } else {
13909 rows.drain(..off);
13910 }
13911 }
13912 if let Some(n) = limit {
13913 rows.truncate(n as usize);
13914 }
13915}
13916
13917fn apply_offset_and_limit_tagged(
13928 tagged: &mut Vec<(Vec<f64>, Row)>,
13929 offset: Option<u32>,
13930 limit: Option<u32>,
13931 with_ties: bool,
13932) {
13933 if let Some(off) = offset {
13934 let off = off as usize;
13935 if off >= tagged.len() {
13936 tagged.clear();
13937 } else {
13938 tagged.drain(..off);
13939 }
13940 }
13941 if let Some(n) = limit {
13942 let n = n as usize;
13943 if with_ties && n > 0 && n < tagged.len() {
13944 let cutoff_key = tagged[n - 1].0.clone();
13945 let mut end = n;
13946 while end < tagged.len() && tagged[end].0 == cutoff_key {
13947 end += 1;
13948 }
13949 tagged.truncate(end);
13950 } else {
13951 tagged.truncate(n);
13952 }
13953 }
13954}
13955
13956fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
13962 if stmt.limit_with_ties && stmt.order_by.is_empty() {
13963 return Err(EngineError::Unsupported(alloc::string::String::from(
13964 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
13965 )));
13966 }
13967 Ok(())
13968}
13969
13970fn resolve_foreign_key(
13984 local_table_name: &str,
13985 local_cols: &[ColumnSchema],
13986 fk: spg_sql::ast::ForeignKeyConstraint,
13987 catalog: &Catalog,
13988) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
13989 let mut local_columns = Vec::with_capacity(fk.columns.len());
13991 for name in &fk.columns {
13992 let pos = local_cols
13993 .iter()
13994 .position(|c| c.name == *name)
13995 .ok_or_else(|| {
13996 EngineError::Unsupported(alloc::format!(
13997 "FOREIGN KEY references unknown local column {name:?}"
13998 ))
13999 })?;
14000 local_columns.push(pos);
14001 }
14002 let is_self_ref = fk.parent_table == local_table_name;
14006 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14007 (local_cols, local_table_name)
14008 } else {
14009 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14010 EngineError::Storage(StorageError::TableNotFound {
14011 name: fk.parent_table.clone(),
14012 })
14013 })?;
14014 (
14015 parent_table.schema().columns.as_slice(),
14016 fk.parent_table.as_str(),
14017 )
14018 };
14019 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14024 if fk.columns.len() != 1 {
14025 return Err(EngineError::Unsupported(
14026 "composite FOREIGN KEY without explicit parent column list is not supported \
14027 — list the parent columns explicitly"
14028 .into(),
14029 ));
14030 }
14031 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14033 .ok_or_else(|| {
14034 EngineError::Unsupported(alloc::format!(
14035 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14036 to default the FOREIGN KEY against"
14037 ))
14038 })?;
14039 alloc::vec![pos]
14040 } else {
14041 let mut out = Vec::with_capacity(fk.parent_columns.len());
14042 for name in &fk.parent_columns {
14043 let pos = parent_cols_for_lookup
14044 .iter()
14045 .position(|c| c.name == *name)
14046 .ok_or_else(|| {
14047 EngineError::Unsupported(alloc::format!(
14048 "FOREIGN KEY references unknown parent column \
14049 {name:?} on table {parent_table_str:?}"
14050 ))
14051 })?;
14052 out.push(pos);
14053 }
14054 out
14055 };
14056 if parent_columns.len() != local_columns.len() {
14057 return Err(EngineError::Unsupported(alloc::format!(
14058 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14059 local_columns.len(),
14060 parent_columns.len()
14061 )));
14062 }
14063 if !is_self_ref {
14073 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14074 let primary_parent_col = parent_columns[0];
14075 let has_btree = parent_table
14076 .schema()
14077 .columns
14078 .get(primary_parent_col)
14079 .is_some()
14080 && parent_table.indices().iter().any(|idx| {
14081 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14082 && idx.column_position == primary_parent_col
14083 && idx.partial_predicate.is_none()
14084 });
14085 if !has_btree {
14086 return Err(EngineError::Unsupported(alloc::format!(
14087 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14088 index — create one with `CREATE INDEX ... ON {} ({})` first",
14089 parent_table_str,
14090 parent_table_str,
14091 parent_table.schema().columns[primary_parent_col].name,
14092 )));
14093 }
14094 }
14095 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14096 let on_update = fk_action_sql_to_storage(fk.on_update);
14097 Ok(spg_storage::ForeignKeyConstraint {
14098 name: fk.name,
14099 local_columns,
14100 parent_table: fk.parent_table,
14101 parent_columns,
14102 on_delete,
14103 on_update,
14104 })
14105}
14106
14107fn pick_pk_index_column(
14113 catalog: &Catalog,
14114 parent_name: &str,
14115 is_self_ref: bool,
14116 local_cols: &[ColumnSchema],
14117) -> Option<usize> {
14118 if is_self_ref {
14119 let _ = local_cols;
14123 return Some(0);
14124 }
14125 let parent = catalog.get(parent_name)?;
14126 parent.indices().iter().find_map(|idx| {
14127 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14128 && idx.partial_predicate.is_none()
14129 && idx.included_columns.is_empty()
14130 && idx.expression.is_none()
14131 {
14132 Some(idx.column_position)
14133 } else {
14134 None
14135 }
14136 })
14137}
14138
14139fn resolve_on_conflict_columns(
14146 catalog: &Catalog,
14147 table_name: &str,
14148 target: &[String],
14149) -> Result<Vec<usize>, EngineError> {
14150 let table = catalog.get(table_name).ok_or_else(|| {
14151 EngineError::Storage(StorageError::TableNotFound {
14152 name: table_name.into(),
14153 })
14154 })?;
14155 if target.is_empty() {
14156 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14166 return Ok(uc.columns.clone());
14167 }
14168 let pos = table
14169 .indices()
14170 .iter()
14171 .find_map(|idx| {
14172 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14173 && idx.partial_predicate.is_none()
14174 && idx.included_columns.is_empty()
14175 && idx.expression.is_none()
14176 {
14177 Some(idx.column_position)
14178 } else {
14179 None
14180 }
14181 })
14182 .ok_or_else(|| {
14183 EngineError::Unsupported(alloc::format!(
14184 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14185 ))
14186 })?;
14187 return Ok(alloc::vec![pos]);
14188 }
14189 let mut out = Vec::with_capacity(target.len());
14190 for name in target {
14191 let pos = table
14192 .schema()
14193 .columns
14194 .iter()
14195 .position(|c| c.name == *name)
14196 .ok_or_else(|| {
14197 EngineError::Unsupported(alloc::format!(
14198 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14199 ))
14200 })?;
14201 out.push(pos);
14202 }
14203 Ok(out)
14204}
14205
14206fn on_conflict_key_exists(
14209 catalog: &Catalog,
14210 table_name: &str,
14211 column_pos: usize,
14212 key: &Value,
14213) -> bool {
14214 let Some(table) = catalog.get(table_name) else {
14215 return false;
14216 };
14217 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14218 return false;
14219 };
14220 table.indices().iter().any(|idx| {
14221 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14222 && idx.column_position == column_pos
14223 && idx.partial_predicate.is_none()
14224 && !idx.lookup_eq(&idx_key).is_empty()
14225 })
14226}
14227
14228fn lookup_row_position_by_keys(
14234 catalog: &Catalog,
14235 table_name: &str,
14236 column_positions: &[usize],
14237 key: &[&Value],
14238) -> Option<usize> {
14239 let table = catalog.get(table_name)?;
14240 table.rows().iter().position(|r| {
14241 column_positions
14242 .iter()
14243 .enumerate()
14244 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14245 })
14246}
14247
14248fn on_conflict_keys_exist(
14253 catalog: &Catalog,
14254 table_name: &str,
14255 column_positions: &[usize],
14256 key: &[&Value],
14257) -> bool {
14258 if column_positions.len() == 1 {
14259 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14260 }
14261 let Some(table) = catalog.get(table_name) else {
14262 return false;
14263 };
14264 table.rows().iter().any(|r| {
14265 column_positions
14266 .iter()
14267 .enumerate()
14268 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14269 })
14270}
14271
14272fn apply_on_conflict_assignments(
14285 catalog: &Catalog,
14286 table_name: &str,
14287 target_pos: usize,
14288 incoming: &[Value],
14289 assignments: &[(String, Expr)],
14290 where_: Option<&Expr>,
14291) -> Result<Option<Vec<Value>>, EngineError> {
14292 let table = catalog.get(table_name).ok_or_else(|| {
14293 EngineError::Storage(StorageError::TableNotFound {
14294 name: table_name.into(),
14295 })
14296 })?;
14297 let schema_cols = table.schema().columns.clone();
14298 let existing = table
14299 .rows()
14300 .get(target_pos)
14301 .ok_or_else(|| {
14302 EngineError::Unsupported(alloc::format!(
14303 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14304 ))
14305 })?
14306 .clone();
14307 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14308 if let Some(w) = where_ {
14310 let pred = w.clone();
14311 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14312 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14313 if !matches!(v, Value::Bool(true)) {
14314 return Ok(None);
14315 }
14316 }
14317 let mut new_values = existing.values.clone();
14318 for (col_name, expr) in assignments {
14319 let target_idx = schema_cols
14320 .iter()
14321 .position(|c| c.name == *col_name)
14322 .ok_or_else(|| {
14323 EngineError::Eval(EvalError::ColumnNotFound {
14324 name: col_name.clone(),
14325 })
14326 })?;
14327 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14328 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14329 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14330 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14331 new_values[target_idx] = coerced;
14332 }
14333 Ok(Some(new_values))
14334}
14335
14336fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14341 use spg_sql::ast::ColumnName;
14342 match expr {
14343 Expr::Column(ColumnName { qualifier, name })
14344 if qualifier
14345 .as_deref()
14346 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14347 {
14348 let pos = schema_cols.iter().position(|c| c.name == name);
14349 match pos {
14350 Some(p) => {
14351 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14352 value_to_literal_expr(v)
14353 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14354 }
14355 None => Expr::Column(ColumnName { qualifier, name }),
14356 }
14357 }
14358 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14359 op,
14360 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14361 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14362 },
14363 Expr::Unary { op, expr } => Expr::Unary {
14364 op,
14365 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14366 },
14367 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14368 name,
14369 args: args
14370 .into_iter()
14371 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14372 .collect(),
14373 },
14374 other => other,
14375 }
14376}
14377
14378fn enforce_uniqueness_inserts(
14401 catalog: &Catalog,
14402 child_table: &str,
14403 constraints: &[spg_storage::UniquenessConstraint],
14404 rows: &[Vec<Value>],
14405) -> Result<(), EngineError> {
14406 if constraints.is_empty() {
14407 return Ok(());
14408 }
14409 let table = catalog.get(child_table).ok_or_else(|| {
14410 EngineError::Storage(StorageError::TableNotFound {
14411 name: child_table.into(),
14412 })
14413 })?;
14414 let schema = table.schema();
14415 for uc in constraints {
14416 for (batch_idx, row_values) in rows.iter().enumerate() {
14417 let key: Vec<Value> = uc
14426 .columns
14427 .iter()
14428 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14429 .collect();
14430 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14431 if has_null && !uc.nulls_not_distinct {
14436 continue;
14437 }
14438 let collides_in_table = table.rows().iter().any(|prow| {
14440 uc.columns.iter().enumerate().all(|(i, &p)| {
14441 prow.values
14442 .get(p)
14443 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14444 })
14445 });
14446 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14448 uc.columns.iter().enumerate().all(|(i, &p)| {
14449 earlier
14450 .get(p)
14451 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14452 })
14453 });
14454 if collides_in_table || collides_in_batch {
14455 let kind = if uc.is_primary_key {
14456 "PRIMARY KEY"
14457 } else {
14458 "UNIQUE"
14459 };
14460 let col_names: Vec<String> = uc
14461 .columns
14462 .iter()
14463 .map(|&i| table.schema().columns[i].name.clone())
14464 .collect();
14465 return Err(EngineError::Unsupported(alloc::format!(
14466 "{kind} violation on {child_table:?} columns {col_names:?}: \
14467 row #{batch_idx} duplicates an existing key"
14468 )));
14469 }
14470 }
14471 }
14472 Ok(())
14473}
14474
14475fn collated_key_cell(
14482 v: &spg_storage::Value,
14483 column_position: usize,
14484 schema: &spg_storage::TableSchema,
14485) -> spg_storage::Value {
14486 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14487 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14488 spg_storage::Value::Text(s.to_ascii_lowercase())
14489 }
14490 _ => v.clone(),
14491 }
14492}
14493
14494fn predicate_truthy(v: &spg_storage::Value) -> bool {
14502 use spg_storage::Value as V;
14503 match v {
14504 V::Bool(b) => *b,
14505 V::Int(n) => *n != 0,
14506 V::BigInt(n) => *n != 0,
14507 V::SmallInt(n) => *n != 0,
14508 _ => false,
14509 }
14510}
14511
14512fn check_existing_unique_violation(
14517 idx: &spg_storage::Index,
14518 schema: &spg_storage::TableSchema,
14519 rows: &[spg_storage::Row],
14520) -> Result<(), EngineError> {
14521 let predicate_expr = match idx.partial_predicate.as_deref() {
14522 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14523 EngineError::Unsupported(alloc::format!(
14524 "stored partial predicate {s:?} failed to re-parse: {e:?}"
14525 ))
14526 })?),
14527 None => None,
14528 };
14529 let ctx = eval::EvalContext::new(&schema.columns, None);
14530 let key_positions = unique_key_positions(idx);
14531 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
14532 for row in rows {
14533 if let Some(expr) = &predicate_expr {
14534 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
14535 EngineError::Unsupported(alloc::format!(
14536 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
14537 ))
14538 })?;
14539 if !predicate_truthy(&v) {
14540 continue;
14541 }
14542 }
14543 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
14544 .iter()
14545 .map(|&p| {
14546 let v = row
14547 .values
14548 .get(p)
14549 .cloned()
14550 .unwrap_or(spg_storage::Value::Null);
14551 collated_key_cell(&v, p, schema)
14552 })
14553 .collect();
14554 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14555 continue;
14556 }
14557 if seen.iter().any(|other| *other == key) {
14558 return Err(EngineError::Unsupported(alloc::format!(
14559 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
14560 idx.name
14561 )));
14562 }
14563 seen.push(key);
14564 }
14565 Ok(())
14566}
14567
14568fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
14572 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
14573 out.push(idx.column_position);
14574 out.extend_from_slice(&idx.extra_column_positions);
14575 out
14576}
14577
14578fn enforce_unique_index_inserts(
14586 catalog: &Catalog,
14587 table_name: &str,
14588 rows: &[alloc::vec::Vec<spg_storage::Value>],
14589) -> Result<(), EngineError> {
14590 let table = catalog.get(table_name).ok_or_else(|| {
14591 EngineError::Storage(StorageError::TableNotFound {
14592 name: table_name.into(),
14593 })
14594 })?;
14595 let schema = table.schema();
14596 let ctx = eval::EvalContext::new(&schema.columns, None);
14597 for idx in table.indices() {
14598 if !idx.is_unique {
14599 continue;
14600 }
14601 let predicate_expr = match idx.partial_predicate.as_deref() {
14603 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14604 EngineError::Unsupported(alloc::format!(
14605 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
14606 idx.name
14607 ))
14608 })?),
14609 None => None,
14610 };
14611 let key_positions = unique_key_positions(idx);
14612 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
14613 key_positions
14617 .iter()
14618 .map(|&p| {
14619 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
14620 collated_key_cell(&v, p, schema)
14621 })
14622 .collect()
14623 };
14624 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
14628 let Some(expr) = &predicate_expr else {
14629 return Ok(true);
14630 };
14631 let tmp_row = spg_storage::Row {
14632 values: values.to_vec(),
14633 };
14634 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14635 EngineError::Unsupported(alloc::format!(
14636 "UNIQUE INDEX {:?} predicate eval: {e:?}",
14637 idx.name
14638 ))
14639 })?;
14640 Ok(predicate_truthy(&v))
14641 };
14642 for (batch_idx, row_values) in rows.iter().enumerate() {
14643 if !participates(row_values)? {
14644 continue;
14645 }
14646 let key = key_of(row_values);
14647 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14648 continue;
14649 }
14650 for prow in table.rows() {
14652 if !participates(&prow.values)? {
14653 continue;
14654 }
14655 if key_of(&prow.values) == key {
14656 return Err(EngineError::Unsupported(alloc::format!(
14657 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14658 row #{batch_idx} duplicates an existing key",
14659 idx.name
14660 )));
14661 }
14662 }
14663 for earlier in &rows[..batch_idx] {
14665 if !participates(earlier)? {
14666 continue;
14667 }
14668 if key_of(earlier) == key {
14669 return Err(EngineError::Unsupported(alloc::format!(
14670 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14671 row #{batch_idx} duplicates an earlier row in the same batch",
14672 idx.name
14673 )));
14674 }
14675 }
14676 }
14677 }
14678 Ok(())
14679}
14680
14681fn any_column_changed(
14689 filter_cols: &[String],
14690 schema_cols: &[ColumnSchema],
14691 old_row: &Row,
14692 new_row: &Row,
14693) -> bool {
14694 for col_name in filter_cols {
14695 let Some(pos) = schema_cols
14696 .iter()
14697 .position(|c| c.name.eq_ignore_ascii_case(col_name))
14698 else {
14699 continue;
14700 };
14701 let old_v = old_row.values.get(pos);
14702 let new_v = new_row.values.get(pos);
14703 if old_v != new_v {
14704 return true;
14705 }
14706 }
14707 false
14708}
14709
14710fn enforce_check_constraints(
14715 catalog: &Catalog,
14716 table_name: &str,
14717 rows: &[alloc::vec::Vec<spg_storage::Value>],
14718) -> Result<(), EngineError> {
14719 let table = catalog.get(table_name).ok_or_else(|| {
14720 EngineError::Storage(StorageError::TableNotFound {
14721 name: table_name.into(),
14722 })
14723 })?;
14724 let schema = table.schema();
14725 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
14729 alloc::vec::Vec::new();
14730 for (idx, col) in schema.columns.iter().enumerate() {
14731 let Some(dname) = &col.user_domain_type else {
14732 continue;
14733 };
14734 let Some(dom) = catalog.domain_types().get(dname) else {
14735 continue;
14736 };
14737 let mut parsed_for_col: alloc::vec::Vec<Expr> =
14738 alloc::vec::Vec::with_capacity(dom.checks.len());
14739 for src in &dom.checks {
14740 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14741 EngineError::Unsupported(alloc::format!(
14742 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
14743 col.name
14744 ))
14745 })?;
14746 parsed_for_col.push(expr);
14747 }
14748 if !parsed_for_col.is_empty() {
14749 domain_checks_per_col.push((idx, parsed_for_col));
14750 }
14751 }
14752 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
14753 return Ok(());
14754 }
14755 let ctx = eval::EvalContext::new(&schema.columns, None);
14756 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
14757 for (i, src) in schema.checks.iter().enumerate() {
14758 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14759 EngineError::Unsupported(alloc::format!(
14760 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
14761 ))
14762 })?;
14763 parsed.push((i, expr));
14764 }
14765 for (batch_idx, row_values) in rows.iter().enumerate() {
14766 let tmp_row = spg_storage::Row {
14767 values: row_values.clone(),
14768 };
14769 for (i, expr) in &parsed {
14770 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14771 EngineError::Unsupported(alloc::format!(
14772 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
14773 ))
14774 })?;
14775 if matches!(v, spg_storage::Value::Bool(false)) {
14777 return Err(EngineError::Unsupported(alloc::format!(
14778 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
14779 schema.checks[*i]
14780 )));
14781 }
14782 }
14783 for (col_idx, checks) in &domain_checks_per_col {
14789 let cell = row_values
14790 .get(*col_idx)
14791 .cloned()
14792 .unwrap_or(spg_storage::Value::Null);
14793 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
14794 "value",
14795 schema.columns[*col_idx].ty,
14796 schema.columns[*col_idx].nullable,
14797 )];
14798 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
14799 let synth_row = spg_storage::Row {
14800 values: alloc::vec![cell],
14801 };
14802 for (ci, expr) in checks.iter().enumerate() {
14803 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
14804 EngineError::Unsupported(alloc::format!(
14805 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
14806 schema.columns[*col_idx].name
14807 ))
14808 })?;
14809 if matches!(v, spg_storage::Value::Bool(false)) {
14810 return Err(EngineError::Unsupported(alloc::format!(
14811 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
14812 schema.columns[*col_idx].name
14813 )));
14814 }
14815 }
14816 }
14817 }
14818 Ok(())
14819}
14820
14821fn enforce_fk_inserts(
14822 catalog: &Catalog,
14823 child_table: &str,
14824 fks: &[spg_storage::ForeignKeyConstraint],
14825 rows: &[Vec<Value>],
14826) -> Result<(), EngineError> {
14827 for fk in fks {
14828 let parent_is_self = fk.parent_table == child_table;
14829 let parent = if parent_is_self {
14830 catalog.get(child_table).ok_or_else(|| {
14833 EngineError::Storage(StorageError::TableNotFound {
14834 name: child_table.into(),
14835 })
14836 })?
14837 } else {
14838 catalog.get(&fk.parent_table).ok_or_else(|| {
14839 EngineError::Storage(StorageError::TableNotFound {
14840 name: fk.parent_table.clone(),
14841 })
14842 })?
14843 };
14844 for (batch_idx, row_values) in rows.iter().enumerate() {
14845 if fk.local_columns.len() == 1 {
14849 let v = &row_values[fk.local_columns[0]];
14850 if matches!(v, Value::Null) {
14851 continue;
14852 }
14853 let parent_col = fk.parent_columns[0];
14854 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
14855 EngineError::Unsupported(alloc::format!(
14856 "FOREIGN KEY column value of type {:?} is not index-eligible",
14857 v.data_type()
14858 ))
14859 })?;
14860 let present_committed = parent.indices().iter().any(|idx| {
14861 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14862 && idx.column_position == parent_col
14863 && idx.partial_predicate.is_none()
14864 && !idx.lookup_eq(&key).is_empty()
14865 });
14866 let present_in_batch = parent_is_self
14870 && rows[..batch_idx]
14871 .iter()
14872 .any(|earlier| earlier.get(parent_col) == Some(v));
14873 if !(present_committed || present_in_batch) {
14874 return Err(EngineError::Unsupported(alloc::format!(
14875 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
14876 fk.parent_table,
14877 parent
14878 .schema()
14879 .columns
14880 .get(parent_col)
14881 .map_or("?", |c| c.name.as_str()),
14882 v,
14883 )));
14884 }
14885 } else {
14886 if fk
14890 .local_columns
14891 .iter()
14892 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
14893 {
14894 continue;
14895 }
14896 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
14897 let parent_match_committed = parent.rows().iter().any(|prow| {
14898 fk.parent_columns
14899 .iter()
14900 .enumerate()
14901 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
14902 });
14903 let parent_match_in_batch = parent_is_self
14904 && rows[..batch_idx].iter().any(|earlier| {
14905 fk.parent_columns
14906 .iter()
14907 .enumerate()
14908 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
14909 });
14910 if !(parent_match_committed || parent_match_in_batch) {
14911 return Err(EngineError::Unsupported(alloc::format!(
14912 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
14913 fk.parent_table,
14914 )));
14915 }
14916 }
14917 }
14918 }
14919 Ok(())
14920}
14921
14922#[derive(Debug, Clone)]
14926struct FkChildStep {
14927 child_table: String,
14928 action: FkChildAction,
14929}
14930
14931#[derive(Debug, Clone)]
14932enum FkChildAction {
14933 Delete { positions: Vec<usize> },
14935 SetNull {
14939 positions: Vec<usize>,
14940 columns: Vec<usize>,
14941 },
14942 SetDefault {
14946 positions: Vec<usize>,
14947 columns: Vec<usize>,
14948 defaults: Vec<Value>,
14949 },
14950}
14951
14952fn plan_fk_parent_deletions(
14968 catalog: &Catalog,
14969 parent_table_name: &str,
14970 to_delete_positions: &[usize],
14971 to_delete_rows: &[Vec<Value>],
14972) -> Result<Vec<FkChildStep>, EngineError> {
14973 use alloc::collections::{BTreeMap, BTreeSet};
14974 if to_delete_rows.is_empty() {
14975 return Ok(Vec::new());
14976 }
14977 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
14978 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
14980 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
14981 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
14982 for &p in to_delete_positions {
14983 visited.insert((parent_table_name.to_string(), p));
14984 }
14985 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
14986 .iter()
14987 .map(|r| (parent_table_name.to_string(), r.clone()))
14988 .collect();
14989 while let Some((cur_parent, parent_row)) = work.pop() {
14990 for child_name in catalog.table_names() {
14991 let child = catalog
14992 .get(&child_name)
14993 .expect("table_names → catalog.get round-trip is total");
14994 for fk in &child.schema().foreign_keys {
14995 if fk.parent_table != cur_parent {
14996 continue;
14997 }
14998 let parent_key: Vec<&Value> = fk
14999 .parent_columns
15000 .iter()
15001 .map(|&pi| &parent_row[pi])
15002 .collect();
15003 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15004 continue;
15005 }
15006 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15007 if child_name == cur_parent
15008 && visited.contains(&(child_name.clone(), child_row_idx))
15009 {
15010 continue;
15011 }
15012 let matches_key = fk
15013 .local_columns
15014 .iter()
15015 .enumerate()
15016 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15017 if !matches_key {
15018 continue;
15019 }
15020 match fk.on_delete {
15021 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15022 return Err(EngineError::Unsupported(alloc::format!(
15023 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15024 restricted by FK from {child_name:?}.{:?}",
15025 fk.local_columns,
15026 )));
15027 }
15028 spg_storage::FkAction::Cascade => {
15029 if visited.insert((child_name.clone(), child_row_idx)) {
15030 delete_plan
15031 .entry(child_name.clone())
15032 .or_default()
15033 .insert(child_row_idx);
15034 work.push((child_name.clone(), child_row.values.clone()));
15035 }
15036 }
15037 spg_storage::FkAction::SetNull => {
15038 for &li in &fk.local_columns {
15040 let col = child.schema().columns.get(li).ok_or_else(|| {
15041 EngineError::Unsupported(alloc::format!(
15042 "FK local column {li} missing in {child_name:?}"
15043 ))
15044 })?;
15045 if !col.nullable {
15046 return Err(EngineError::Unsupported(alloc::format!(
15047 "FOREIGN KEY ON DELETE SET NULL: column \
15048 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15049 col.name,
15050 )));
15051 }
15052 }
15053 let entry = setnull_plan.entry(child_name.clone()).or_default();
15054 for &li in &fk.local_columns {
15055 entry.insert((child_row_idx, li));
15056 }
15057 }
15058 spg_storage::FkAction::SetDefault => {
15059 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15061 for &li in &fk.local_columns {
15062 let col = child.schema().columns.get(li).ok_or_else(|| {
15063 EngineError::Unsupported(alloc::format!(
15064 "FK local column {li} missing in {child_name:?}"
15065 ))
15066 })?;
15067 let default = col.default.clone().ok_or_else(|| {
15068 EngineError::Unsupported(alloc::format!(
15069 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15070 {child_name:?}.{:?} has no DEFAULT declared",
15071 col.name,
15072 ))
15073 })?;
15074 entry.insert((child_row_idx, li), default);
15075 }
15076 }
15077 }
15078 }
15079 }
15080 }
15081 }
15082 let mut steps: Vec<FkChildStep> = Vec::new();
15090 for (child_table, entries) in setnull_plan {
15091 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15092 steps.push(FkChildStep {
15093 child_table,
15094 action: FkChildAction::SetNull { positions, columns },
15095 });
15096 }
15097 for (child_table, entries) in setdefault_plan {
15098 let mut positions = Vec::with_capacity(entries.len());
15099 let mut columns = Vec::with_capacity(entries.len());
15100 let mut defaults = Vec::with_capacity(entries.len());
15101 for ((p, c), v) in entries {
15102 positions.push(p);
15103 columns.push(c);
15104 defaults.push(v);
15105 }
15106 steps.push(FkChildStep {
15107 child_table,
15108 action: FkChildAction::SetDefault {
15109 positions,
15110 columns,
15111 defaults,
15112 },
15113 });
15114 }
15115 for (child_table, positions) in delete_plan {
15116 steps.push(FkChildStep {
15117 child_table,
15118 action: FkChildAction::Delete {
15119 positions: positions.into_iter().collect(),
15120 },
15121 });
15122 }
15123 Ok(steps)
15124}
15125
15126fn plan_fk_parent_updates(
15143 catalog: &Catalog,
15144 parent_table_name: &str,
15145 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15146) -> Result<Vec<FkChildStep>, EngineError> {
15147 use alloc::collections::BTreeMap;
15148 if plan_with_old.is_empty() {
15149 return Ok(Vec::new());
15150 }
15151 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15156 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15157 BTreeMap::new();
15158 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15159 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15161
15162 for child_name in catalog.table_names() {
15163 let child = catalog
15164 .get(&child_name)
15165 .expect("table_names → catalog.get total");
15166 for fk in &child.schema().foreign_keys {
15167 if fk.parent_table != parent_table_name {
15168 continue;
15169 }
15170 for (_pos, old_row, new_row) in plan_with_old {
15171 let key_changed = fk
15173 .parent_columns
15174 .iter()
15175 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15176 if !key_changed {
15177 continue;
15178 }
15179 let old_key: Vec<&Value> =
15181 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15182 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15183 continue;
15185 }
15186 let new_key: Vec<&Value> =
15187 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15188 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15189 if child_name == parent_table_name
15192 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15193 {
15194 continue;
15195 }
15196 let matches_key = fk
15197 .local_columns
15198 .iter()
15199 .enumerate()
15200 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15201 if !matches_key {
15202 continue;
15203 }
15204 match fk.on_update {
15205 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15206 return Err(EngineError::Unsupported(alloc::format!(
15207 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15208 restricted by FK from {child_name:?}.{:?}",
15209 fk.local_columns,
15210 )));
15211 }
15212 spg_storage::FkAction::Cascade => {
15213 let entry = cascade_plan.entry(child_name.clone()).or_default();
15215 for (i, &li) in fk.local_columns.iter().enumerate() {
15216 entry.insert((child_row_idx, li), new_key[i].clone());
15217 }
15218 }
15219 spg_storage::FkAction::SetNull => {
15220 for &li in &fk.local_columns {
15221 let col = child.schema().columns.get(li).ok_or_else(|| {
15222 EngineError::Unsupported(alloc::format!(
15223 "FK local column {li} missing in {child_name:?}"
15224 ))
15225 })?;
15226 if !col.nullable {
15227 return Err(EngineError::Unsupported(alloc::format!(
15228 "FOREIGN KEY ON UPDATE SET NULL: column \
15229 {child_name:?}.{:?} is NOT NULL",
15230 col.name,
15231 )));
15232 }
15233 }
15234 let entry = setnull_plan.entry(child_name.clone()).or_default();
15235 for &li in &fk.local_columns {
15236 entry.insert((child_row_idx, li));
15237 }
15238 }
15239 spg_storage::FkAction::SetDefault => {
15240 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15241 for &li in &fk.local_columns {
15242 let col = child.schema().columns.get(li).ok_or_else(|| {
15243 EngineError::Unsupported(alloc::format!(
15244 "FK local column {li} missing in {child_name:?}"
15245 ))
15246 })?;
15247 let default = col.default.clone().ok_or_else(|| {
15248 EngineError::Unsupported(alloc::format!(
15249 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15250 {child_name:?}.{:?} has no DEFAULT",
15251 col.name,
15252 ))
15253 })?;
15254 entry.insert((child_row_idx, li), default);
15255 }
15256 }
15257 }
15258 }
15259 }
15260 }
15261 }
15262 let mut steps: Vec<FkChildStep> = Vec::new();
15265 for (child_table, entries) in cascade_plan {
15266 let mut positions = Vec::with_capacity(entries.len());
15267 let mut columns = Vec::with_capacity(entries.len());
15268 let mut defaults = Vec::with_capacity(entries.len());
15269 for ((p, c), v) in entries {
15270 positions.push(p);
15271 columns.push(c);
15272 defaults.push(v);
15273 }
15274 steps.push(FkChildStep {
15279 child_table,
15280 action: FkChildAction::SetDefault {
15281 positions,
15282 columns,
15283 defaults,
15284 },
15285 });
15286 }
15287 for (child_table, entries) in setnull_plan {
15288 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15289 steps.push(FkChildStep {
15290 child_table,
15291 action: FkChildAction::SetNull { positions, columns },
15292 });
15293 }
15294 for (child_table, entries) in setdefault_plan {
15295 let mut positions = Vec::with_capacity(entries.len());
15296 let mut columns = Vec::with_capacity(entries.len());
15297 let mut defaults = Vec::with_capacity(entries.len());
15298 for ((p, c), v) in entries {
15299 positions.push(p);
15300 columns.push(c);
15301 defaults.push(v);
15302 }
15303 steps.push(FkChildStep {
15304 child_table,
15305 action: FkChildAction::SetDefault {
15306 positions,
15307 columns,
15308 defaults,
15309 },
15310 });
15311 }
15312 let _ = delete_plan; Ok(steps)
15314}
15315
15316fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15320 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15321 EngineError::Storage(StorageError::TableNotFound {
15322 name: step.child_table.clone(),
15323 })
15324 })?;
15325 match &step.action {
15326 FkChildAction::Delete { positions } => {
15327 let _ = child.delete_rows(positions);
15328 }
15329 FkChildAction::SetNull { positions, columns } => {
15330 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15331 }
15332 FkChildAction::SetDefault {
15333 positions,
15334 columns,
15335 defaults,
15336 } => {
15337 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15338 }
15339 }
15340 Ok(())
15341}
15342
15343fn apply_per_cell_writes(
15349 child: &mut spg_storage::Table,
15350 positions: &[usize],
15351 columns: &[usize],
15352 mut value_for: impl FnMut(usize) -> Value,
15353) -> Result<(), EngineError> {
15354 use alloc::collections::BTreeMap;
15355 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15356 for i in 0..positions.len() {
15357 by_row
15358 .entry(positions[i])
15359 .or_default()
15360 .push((columns[i], value_for(i)));
15361 }
15362 for (pos, mutations) in by_row {
15363 let mut new_values = child.rows()[pos].values.clone();
15364 for (col, v) in mutations {
15365 if let Some(slot) = new_values.get_mut(col) {
15366 *slot = v;
15367 }
15368 }
15369 child
15370 .update_row(pos, new_values)
15371 .map_err(EngineError::Storage)?;
15372 }
15373 Ok(())
15374}
15375
15376fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15377 match a {
15378 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15379 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15380 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15381 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15382 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15383 }
15384}
15385
15386fn resolve_column_default_free(
15392 col: &ColumnSchema,
15393 clock_fn: Option<ClockFn>,
15394) -> Result<Value, EngineError> {
15395 if let Some(rt) = &col.runtime_default {
15396 return eval_runtime_default_free(rt, col.ty, clock_fn);
15397 }
15398 Ok(col.default.clone().unwrap_or(Value::Null))
15399}
15400
15401fn eval_runtime_default_free(
15402 rt: &str,
15403 ty: DataType,
15404 clock_fn: Option<ClockFn>,
15405) -> Result<Value, EngineError> {
15406 let s = rt.trim().to_ascii_lowercase();
15407 let with_no_parens = s.trim_end_matches("()");
15413 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15414 if with_no_parens.ends_with(')') {
15415 &with_no_parens[..open_idx]
15416 } else {
15417 with_no_parens
15418 }
15419 } else {
15420 with_no_parens
15421 };
15422 let now_us = match clock_fn {
15423 Some(f) => f(),
15424 None => 0,
15425 };
15426 let v = match canonical {
15427 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15428 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15429 "current_time" | "localtime" => Value::Timestamp(now_us),
15430 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15436 other => {
15437 return Err(EngineError::Unsupported(alloc::format!(
15438 "runtime DEFAULT expression {other:?} not supported \
15439 (v7.17.0 whitelist: now() / current_timestamp / \
15440 current_date / current_time / localtimestamp / \
15441 localtime / gen_random_uuid() / \
15442 uuid_generate_v4())"
15443 )));
15444 }
15445 };
15446 coerce_value(v, ty, "DEFAULT", 0)
15447}
15448
15449fn is_runtime_default_expr(expr: &Expr) -> bool {
15455 match expr {
15456 Expr::FunctionCall { .. } => true,
15457 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15458 _ => false,
15459 }
15460}
15461
15462fn canonicalize_set_value(
15475 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15476 col_idx: usize,
15477 col_name: &str,
15478 value: Value,
15479) -> Result<Value, EngineError> {
15480 let Some(variants) = lookup.get(&col_idx) else {
15481 return Ok(value);
15482 };
15483 match value {
15484 Value::Null => Ok(Value::Null),
15485 Value::Text(s) => {
15486 if s.is_empty() {
15487 return Ok(Value::Text(alloc::string::String::new()));
15488 }
15489 let mut present = alloc::vec![false; variants.len()];
15492 for raw in s.split(',') {
15493 let tok = raw.trim();
15494 if tok.is_empty() {
15495 continue;
15496 }
15497 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15498 EngineError::Unsupported(alloc::format!(
15499 "column {col_name:?}: invalid SET token {tok:?}; \
15500 allowed: {variants:?}"
15501 ))
15502 })?;
15503 present[idx] = true;
15504 }
15505 let mut out = alloc::string::String::new();
15507 let mut first = true;
15508 for (i, keep) in present.iter().enumerate() {
15509 if !keep {
15510 continue;
15511 }
15512 if !first {
15513 out.push(',');
15514 }
15515 first = false;
15516 out.push_str(&variants[i]);
15517 }
15518 Ok(Value::Text(out))
15519 }
15520 other => Err(EngineError::Unsupported(alloc::format!(
15521 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
15522 other.data_type()
15523 ))),
15524 }
15525}
15526
15527fn enforce_enum_label(
15528 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15529 col_idx: usize,
15530 col_name: &str,
15531 value: &Value,
15532) -> Result<(), EngineError> {
15533 if let Some(labels) = lookup.get(&col_idx) {
15534 match value {
15535 Value::Null => Ok(()),
15536 Value::Text(s) => {
15537 if labels.iter().any(|l| l == s) {
15538 Ok(())
15539 } else {
15540 Err(EngineError::Unsupported(alloc::format!(
15541 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
15542 )))
15543 }
15544 }
15545 other => Err(EngineError::Unsupported(alloc::format!(
15546 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
15547 other.data_type()
15548 ))),
15549 }
15550 } else {
15551 Ok(())
15552 }
15553}
15554
15555fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
15556 let ty = column_type_to_data_type(c.ty);
15557 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
15558 if let Some(name) = c.user_type_ref {
15565 schema.user_enum_type = Some(name);
15566 }
15567 if let Some(expr) = c.on_update_runtime {
15570 schema.on_update_runtime = Some(alloc::format!("{expr}"));
15571 }
15572 schema.collation = match c.collation {
15576 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
15577 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
15578 };
15579 schema.is_unsigned = c.is_unsigned;
15582 schema.inline_enum_variants = c.inline_enum_variants;
15586 schema.inline_set_variants = c.inline_set_variants;
15590 if let Some(default_expr) = c.default {
15591 if is_runtime_default_expr(&default_expr) {
15597 let display = alloc::format!("{default_expr}");
15598 schema = schema.with_runtime_default(display);
15599 } else {
15600 let raw = literal_expr_to_value(default_expr)?;
15601 let coerced = coerce_value(raw, ty, &c.name, 0)?;
15602 schema = schema.with_default(coerced);
15603 }
15604 }
15605 if c.auto_increment {
15606 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
15608 return Err(EngineError::Unsupported(alloc::format!(
15609 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
15610 )));
15611 }
15612 schema = schema.with_auto_increment();
15613 }
15614 Ok(schema)
15615}
15616
15617fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
15622 let s = s.trim();
15623 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
15624 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
15626 if cleaned.len() % 2 != 0 {
15627 return Err("odd-length hex literal");
15628 }
15629 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
15630 let cleaned_bytes = cleaned.as_bytes();
15631 for i in (0..cleaned_bytes.len()).step_by(2) {
15632 let hi = hex_nibble(cleaned_bytes[i])?;
15633 let lo = hex_nibble(cleaned_bytes[i + 1])?;
15634 out.push((hi << 4) | lo);
15635 }
15636 return Ok(out);
15637 }
15638 let bytes = s.as_bytes();
15641 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
15642 let mut i = 0;
15643 while i < bytes.len() {
15644 let b = bytes[i];
15645 if b == b'\\' && i + 1 < bytes.len() {
15646 let n = bytes[i + 1];
15647 if n == b'\\' {
15648 out.push(b'\\');
15649 i += 2;
15650 continue;
15651 }
15652 if n.is_ascii_digit()
15653 && i + 3 < bytes.len()
15654 && bytes[i + 2].is_ascii_digit()
15655 && bytes[i + 3].is_ascii_digit()
15656 {
15657 let oct = |x: u8| (x - b'0') as u32;
15658 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
15659 if v <= 0xFF {
15660 out.push(v as u8);
15661 i += 4;
15662 continue;
15663 }
15664 }
15665 }
15666 out.push(b);
15667 i += 1;
15668 }
15669 Ok(out)
15670}
15671
15672fn hex_nibble(b: u8) -> Result<u8, &'static str> {
15673 match b {
15674 b'0'..=b'9' => Ok(b - b'0'),
15675 b'a'..=b'f' => Ok(b - b'a' + 10),
15676 b'A'..=b'F' => Ok(b - b'A' + 10),
15677 _ => Err("invalid hex digit"),
15678 }
15679}
15680
15681fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
15695 let mut has_text = false;
15696 let mut has_bigint = false;
15697 let mut has_int = false;
15698 for v in &items {
15699 match v {
15700 Value::Null => {}
15701 Value::Text(_) | Value::Json(_) => has_text = true,
15702 Value::BigInt(_) => has_bigint = true,
15703 Value::Int(_) | Value::SmallInt(_) => has_int = true,
15704 _ => has_text = true,
15705 }
15706 }
15707 if has_text || (!has_bigint && !has_int) {
15708 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
15709 .into_iter()
15710 .map(|v| match v {
15711 Value::Null => None,
15712 Value::Text(s) | Value::Json(s) => Some(s),
15713 other => Some(alloc::format!("{other:?}")),
15714 })
15715 .collect();
15716 return Value::TextArray(out);
15717 }
15718 if has_bigint {
15719 let out: alloc::vec::Vec<Option<i64>> = items
15720 .into_iter()
15721 .map(|v| match v {
15722 Value::Null => None,
15723 Value::Int(n) => Some(i64::from(n)),
15724 Value::SmallInt(n) => Some(i64::from(n)),
15725 Value::BigInt(n) => Some(n),
15726 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
15727 })
15728 .collect();
15729 return Value::BigIntArray(out);
15730 }
15731 let out: alloc::vec::Vec<Option<i32>> = items
15732 .into_iter()
15733 .map(|v| match v {
15734 Value::Null => None,
15735 Value::Int(n) => Some(n),
15736 Value::SmallInt(n) => Some(i32::from(n)),
15737 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
15738 })
15739 .collect();
15740 Value::IntArray(out)
15741}
15742
15743fn decode_text_array_literal(
15744 s: &str,
15745) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
15746 let trimmed = s.trim();
15747 let inner = trimmed
15748 .strip_prefix('{')
15749 .and_then(|x| x.strip_suffix('}'))
15750 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
15751 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
15752 if inner.trim().is_empty() {
15753 return Ok(out);
15754 }
15755 let bytes = inner.as_bytes();
15756 let mut i = 0;
15757 while i <= bytes.len() {
15758 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15760 i += 1;
15761 }
15762 if i < bytes.len() && bytes[i] == b'"' {
15764 i += 1; let mut buf = alloc::string::String::new();
15766 while i < bytes.len() && bytes[i] != b'"' {
15767 if bytes[i] == b'\\' && i + 1 < bytes.len() {
15768 buf.push(bytes[i + 1] as char);
15769 i += 2;
15770 } else {
15771 buf.push(bytes[i] as char);
15772 i += 1;
15773 }
15774 }
15775 if i >= bytes.len() {
15776 return Err("unterminated quoted element");
15777 }
15778 i += 1; out.push(Some(buf));
15780 } else {
15781 let start = i;
15783 while i < bytes.len() && bytes[i] != b',' {
15784 i += 1;
15785 }
15786 let raw = inner[start..i].trim();
15787 if raw.eq_ignore_ascii_case("NULL") {
15788 out.push(None);
15789 } else {
15790 out.push(Some(alloc::string::ToString::to_string(raw)));
15791 }
15792 }
15793 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15795 i += 1;
15796 }
15797 if i >= bytes.len() {
15798 break;
15799 }
15800 if bytes[i] != b',' {
15801 return Err("expected ',' between TEXT[] elements");
15802 }
15803 i += 1;
15804 }
15805 Ok(out)
15806}
15807
15808fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
15813 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
15814 out.push('{');
15815 for (i, item) in items.iter().enumerate() {
15816 if i > 0 {
15817 out.push(',');
15818 }
15819 match item {
15820 None => out.push_str("NULL"),
15821 Some(s) => {
15822 let needs_quote = s.is_empty()
15823 || s.eq_ignore_ascii_case("NULL")
15824 || s.chars()
15825 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
15826 if needs_quote {
15827 out.push('"');
15828 for c in s.chars() {
15829 if c == '"' || c == '\\' {
15830 out.push('\\');
15831 }
15832 out.push(c);
15833 }
15834 out.push('"');
15835 } else {
15836 out.push_str(s);
15837 }
15838 }
15839 }
15840 }
15841 out.push('}');
15842 out
15843}
15844
15845fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
15849 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
15850 out.push_str("\\x");
15851 for byte in b {
15852 let hi = byte >> 4;
15853 let lo = byte & 0x0F;
15854 out.push(hex_digit(hi));
15855 out.push(hex_digit(lo));
15856 }
15857 out
15858}
15859
15860const fn hex_digit(n: u8) -> char {
15861 match n {
15862 0..=9 => (b'0' + n) as char,
15863 10..=15 => (b'a' + n - 10) as char,
15864 _ => '?',
15865 }
15866}
15867
15868fn parse_hstore_str(
15880 s: &str,
15881) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
15882 let bytes = s.as_bytes();
15883 let mut i = 0;
15884 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
15885 let skip_ws = |bytes: &[u8], i: &mut usize| {
15886 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
15887 *i += 1;
15888 }
15889 };
15890 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
15891 if *i >= bytes.len() {
15892 return None;
15893 }
15894 if bytes[*i] == b'"' {
15895 *i += 1;
15896 let mut out = alloc::string::String::new();
15897 while *i < bytes.len() {
15898 match bytes[*i] {
15899 b'"' => {
15900 *i += 1;
15901 return Some(out);
15902 }
15903 b'\\' if *i + 1 < bytes.len() => {
15904 out.push(bytes[*i + 1] as char);
15905 *i += 2;
15906 }
15907 c => {
15908 out.push(c as char);
15909 *i += 1;
15910 }
15911 }
15912 }
15913 None
15914 } else {
15915 let start = *i;
15916 while *i < bytes.len()
15917 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
15918 {
15919 *i += 1;
15920 }
15921 if *i == start {
15922 return None;
15923 }
15924 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
15925 }
15926 };
15927 skip_ws(bytes, &mut i);
15928 while i < bytes.len() {
15929 let key = parse_token(bytes, &mut i)?;
15930 skip_ws(bytes, &mut i);
15931 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
15932 return None;
15933 }
15934 i += 2;
15935 skip_ws(bytes, &mut i);
15936 let val_token = if i + 4 <= bytes.len()
15938 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
15939 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
15940 {
15941 i += 4;
15942 None
15943 } else {
15944 Some(parse_token(bytes, &mut i)?)
15945 };
15946 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
15948 out[pos] = (key, val_token);
15949 } else {
15950 out.push((key, val_token));
15951 }
15952 skip_ws(bytes, &mut i);
15953 if i >= bytes.len() {
15954 break;
15955 }
15956 if bytes[i] == b',' {
15957 i += 1;
15958 skip_ws(bytes, &mut i);
15959 continue;
15960 }
15961 return None;
15962 }
15963 Some(out)
15964}
15965
15966fn format_hstore_str(
15970 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
15971) -> alloc::string::String {
15972 let mut out = alloc::string::String::new();
15973 for (i, (k, v)) in pairs.iter().enumerate() {
15974 if i > 0 {
15975 out.push_str(", ");
15976 }
15977 out.push('"');
15978 out.push_str(k);
15979 out.push_str("\"=>");
15980 match v {
15981 None => out.push_str("NULL"),
15982 Some(val) => {
15983 out.push('"');
15984 out.push_str(val);
15985 out.push('"');
15986 }
15987 }
15988 }
15989 out
15990}
15991
15992pub fn format_hstore_text(
15995 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
15996) -> alloc::string::String {
15997 format_hstore_str(pairs)
15998}
15999
16000fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16005 let s = s.trim();
16006 let outer = s
16007 .strip_prefix('{')
16008 .and_then(|x| x.strip_suffix('}'))
16009 .ok_or("missing outer '{...}' braces")?;
16010 let trimmed = outer.trim();
16011 if trimmed.is_empty() {
16012 return Ok(Vec::new());
16013 }
16014 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16015 let mut i = 0;
16016 let bytes = trimmed.as_bytes();
16017 while i < bytes.len() {
16018 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16019 i += 1;
16020 }
16021 if i >= bytes.len() {
16022 break;
16023 }
16024 if bytes[i] != b'{' {
16025 return Err("expected '{' opening a row");
16026 }
16027 i += 1;
16028 let row_start = i;
16029 let mut depth = 1;
16030 while i < bytes.len() && depth > 0 {
16031 match bytes[i] {
16032 b'{' => depth += 1,
16033 b'}' => depth -= 1,
16034 _ => {}
16035 }
16036 if depth > 0 {
16037 i += 1;
16038 }
16039 }
16040 if depth != 0 {
16041 return Err("unbalanced '{...}' in row");
16042 }
16043 let row_text = &trimmed[row_start..i];
16044 i += 1;
16045 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16046 Vec::new()
16047 } else {
16048 row_text.split(',').map(|t| t.trim().to_string()).collect()
16049 };
16050 rows.push(cells);
16051 }
16052 if let Some(first) = rows.first() {
16053 let cols = first.len();
16054 for r in &rows {
16055 if r.len() != cols {
16056 return Err("ragged 2D array (rows have different column counts)");
16057 }
16058 }
16059 }
16060 Ok(rows)
16061}
16062
16063fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16064 let raw = split_2d_literal(s)?;
16065 raw.into_iter()
16066 .map(|row| {
16067 row.into_iter()
16068 .map(|cell| {
16069 if cell.eq_ignore_ascii_case("NULL") {
16070 Ok(None)
16071 } else {
16072 cell.parse::<i32>()
16073 .map(Some)
16074 .map_err(|_| "invalid int element")
16075 }
16076 })
16077 .collect()
16078 })
16079 .collect()
16080}
16081
16082fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16083 let raw = split_2d_literal(s)?;
16084 raw.into_iter()
16085 .map(|row| {
16086 row.into_iter()
16087 .map(|cell| {
16088 if cell.eq_ignore_ascii_case("NULL") {
16089 Ok(None)
16090 } else {
16091 cell.parse::<i64>()
16092 .map(Some)
16093 .map_err(|_| "invalid bigint element")
16094 }
16095 })
16096 .collect()
16097 })
16098 .collect()
16099}
16100
16101fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16102 let raw = split_2d_literal(s)?;
16103 Ok(raw
16104 .into_iter()
16105 .map(|row| {
16106 row.into_iter()
16107 .map(|cell| {
16108 if cell.eq_ignore_ascii_case("NULL") {
16109 None
16110 } else {
16111 Some(cell.trim_matches('"').to_string())
16112 }
16113 })
16114 .collect()
16115 })
16116 .collect())
16117}
16118
16119fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16120 let mut out = alloc::string::String::from("{");
16121 for (i, row) in rows.iter().enumerate() {
16122 if i > 0 {
16123 out.push(',');
16124 }
16125 out.push('{');
16126 for (j, cell) in row.iter().enumerate() {
16127 if j > 0 {
16128 out.push(',');
16129 }
16130 match cell {
16131 None => out.push_str("NULL"),
16132 Some(n) => out.push_str(&alloc::format!("{n}")),
16133 }
16134 }
16135 out.push('}');
16136 }
16137 out.push('}');
16138 out
16139}
16140
16141fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16142 let mut out = alloc::string::String::from("{");
16143 for (i, row) in rows.iter().enumerate() {
16144 if i > 0 {
16145 out.push(',');
16146 }
16147 out.push('{');
16148 for (j, cell) in row.iter().enumerate() {
16149 if j > 0 {
16150 out.push(',');
16151 }
16152 match cell {
16153 None => out.push_str("NULL"),
16154 Some(n) => out.push_str(&alloc::format!("{n}")),
16155 }
16156 }
16157 out.push('}');
16158 }
16159 out.push('}');
16160 out
16161}
16162
16163fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16164 let mut out = alloc::string::String::from("{");
16165 for (i, row) in rows.iter().enumerate() {
16166 if i > 0 {
16167 out.push(',');
16168 }
16169 out.push('{');
16170 for (j, cell) in row.iter().enumerate() {
16171 if j > 0 {
16172 out.push(',');
16173 }
16174 match cell {
16175 None => out.push_str("NULL"),
16176 Some(s) => out.push_str(s),
16177 }
16178 }
16179 out.push('}');
16180 }
16181 out.push('}');
16182 out
16183}
16184
16185pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16188 format_int_2d_text(rows)
16189}
16190pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16191 format_bigint_2d_text(rows)
16192}
16193pub fn format_text_2d_text_pub(
16194 rows: &[Vec<Option<alloc::string::String>>],
16195) -> alloc::string::String {
16196 format_text_2d_text(rows)
16197}
16198
16199fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16204 let s = s.trim();
16205 if s.eq_ignore_ascii_case("empty") {
16206 return Some(Value::Range {
16207 kind,
16208 lower: None,
16209 upper: None,
16210 lower_inc: false,
16211 upper_inc: false,
16212 empty: true,
16213 });
16214 }
16215 let bytes = s.as_bytes();
16216 if bytes.len() < 3 {
16217 return None;
16218 }
16219 let lower_inc = match bytes[0] {
16220 b'[' => true,
16221 b'(' => false,
16222 _ => return None,
16223 };
16224 let upper_inc = match bytes[bytes.len() - 1] {
16225 b']' => true,
16226 b')' => false,
16227 _ => return None,
16228 };
16229 let inner = &s[1..s.len() - 1];
16230 let (lo_text, up_text) = inner.split_once(',')?;
16231 let lower = if lo_text.is_empty() {
16232 None
16233 } else {
16234 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16235 };
16236 let upper = if up_text.is_empty() {
16237 None
16238 } else {
16239 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16240 };
16241 Some(Value::Range {
16242 kind,
16243 lower,
16244 upper,
16245 lower_inc,
16246 upper_inc,
16247 empty: false,
16248 })
16249}
16250
16251fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16254 let text = text.trim().trim_matches('"');
16255 use spg_storage::RangeKind as K;
16256 match kind {
16257 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16258 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16259 K::Num => {
16260 let dot = text.find('.');
16263 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16264 let digits: alloc::string::String = text
16265 .chars()
16266 .filter(|c| *c == '-' || c.is_ascii_digit())
16267 .collect();
16268 let scaled: i128 = digits.parse().ok()?;
16269 Some(Value::Numeric { scaled, scale })
16270 }
16271 K::Ts | K::TsTz => {
16272 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16277 }
16278 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16279 }
16280}
16281
16282pub fn format_range_text(v: &Value) -> alloc::string::String {
16286 format_range_str(v)
16287}
16288
16289fn format_range_str(v: &Value) -> alloc::string::String {
16290 let Value::Range {
16291 lower,
16292 upper,
16293 lower_inc,
16294 upper_inc,
16295 empty,
16296 ..
16297 } = v
16298 else {
16299 return alloc::string::String::new();
16300 };
16301 if *empty {
16302 return "empty".into();
16303 }
16304 let mut out = alloc::string::String::new();
16305 out.push(if *lower_inc { '[' } else { '(' });
16306 if let Some(l) = lower {
16307 out.push_str(&format_range_element(l));
16308 }
16309 out.push(',');
16310 if let Some(u) = upper {
16311 out.push_str(&format_range_element(u));
16312 }
16313 out.push(if *upper_inc { ']' } else { ')' });
16314 out
16315}
16316
16317fn format_range_element(v: &Value) -> alloc::string::String {
16318 match v {
16319 Value::Int(n) => alloc::format!("{n}"),
16320 Value::BigInt(n) => alloc::format!("{n}"),
16321 Value::Date(d) => crate::eval::format_date(*d),
16322 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16323 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16324 other => alloc::format!("{other:?}"),
16325 }
16326}
16327
16328fn parse_money_str(s: &str) -> Option<i64> {
16339 let s = s.trim();
16340 let (neg, rest) = match s.strip_prefix('-') {
16341 Some(r) => (true, r.trim_start()),
16342 None => (false, s),
16343 };
16344 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16345 let (int_part, frac_part) = match rest.split_once('.') {
16346 Some((i, f)) => (i, Some(f)),
16347 None => (rest, None),
16348 };
16349 if int_part.is_empty() {
16350 return None;
16351 }
16352 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16354 for b in int_part.bytes() {
16355 match b {
16356 b',' => {}
16357 b'0'..=b'9' => int_digits.push(b as char),
16358 _ => return None,
16359 }
16360 }
16361 if int_digits.is_empty() {
16362 return None;
16363 }
16364 let dollars: i64 = int_digits.parse().ok()?;
16365 let cents: i64 = match frac_part {
16366 None => 0,
16367 Some(f) => {
16368 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16369 return None;
16370 }
16371 let padded = if f.len() == 1 {
16372 alloc::format!("{f}0")
16373 } else {
16374 f.to_string()
16375 };
16376 padded.parse().ok()?
16377 }
16378 };
16379 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16380 Some(if neg { -total } else { total })
16381}
16382
16383fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16394 let s = s.trim();
16395 let bytes = s.as_bytes();
16399 let sign_pos = bytes
16400 .iter()
16401 .enumerate()
16402 .rev()
16403 .find(|&(_, &b)| b == b'+' || b == b'-')
16404 .map(|(i, _)| i)?;
16405 if sign_pos == 0 {
16406 return None; }
16408 let time_part = &s[..sign_pos];
16409 let offset_part = &s[sign_pos..];
16410 let us = parse_time_str(time_part)?;
16411 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16412 let offset_body = &offset_part[1..];
16413 let (hh_str, mm_str) = match offset_body.split_once(':') {
16414 Some((h, m)) => (h, m),
16415 None => (offset_body, "0"),
16416 };
16417 let hh: i32 = hh_str.parse().ok()?;
16418 let mm: i32 = mm_str.parse().ok()?;
16419 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16420 return None;
16421 }
16422 let total = sign * (hh * 3600 + mm * 60);
16423 if total.abs() > 50_400 {
16424 return None;
16425 }
16426 Some((us, total))
16427}
16428
16429fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16434 if n == 0 || (1901..=2155).contains(&n) {
16435 return Ok(Value::Year(n as u16));
16438 }
16439 Err(EngineError::Eval(EvalError::TypeMismatch {
16440 detail: alloc::format!(
16441 "year value out of range: {n} (column `{col_name}`; \
16442 MySQL accepts 0 or 1901..=2155)"
16443 ),
16444 }))
16445}
16446
16447fn parse_time_str(s: &str) -> Option<i64> {
16459 let s = s.trim();
16460 let (hms, frac) = match s.split_once('.') {
16461 Some((h, f)) => (h, Some(f)),
16462 None => (s, None),
16463 };
16464 let mut parts = hms.split(':');
16465 let hh: u32 = parts.next()?.parse().ok()?;
16466 let mm: u32 = parts.next()?.parse().ok()?;
16467 let ss: u32 = parts.next()?.parse().ok()?;
16468 if parts.next().is_some() {
16469 return None;
16470 }
16471 if hh > 23 || mm > 59 || ss > 59 {
16472 return None;
16473 }
16474 let frac_us: i64 = match frac {
16475 None => 0,
16476 Some(f) => {
16477 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16478 return None;
16479 }
16480 let mut padded = alloc::string::String::with_capacity(6);
16482 padded.push_str(f);
16483 while padded.len() < 6 {
16484 padded.push('0');
16485 }
16486 padded.parse().ok()?
16487 }
16488 };
16489 Some(
16490 i64::from(hh) * 3_600_000_000
16491 + i64::from(mm) * 60_000_000
16492 + i64::from(ss) * 1_000_000
16493 + frac_us,
16494 )
16495}
16496
16497const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16498 match t {
16499 ColumnTypeName::SmallInt => DataType::SmallInt,
16500 ColumnTypeName::Int => DataType::Int,
16501 ColumnTypeName::BigInt => DataType::BigInt,
16502 ColumnTypeName::Float => DataType::Float,
16503 ColumnTypeName::Text => DataType::Text,
16504 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
16505 ColumnTypeName::Char(n) => DataType::Char(n),
16506 ColumnTypeName::Bool => DataType::Bool,
16507 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
16508 dim,
16509 encoding: match encoding {
16510 SqlVecEncoding::F32 => VecEncoding::F32,
16511 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
16512 SqlVecEncoding::F16 => VecEncoding::F16,
16513 },
16514 },
16515 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
16516 ColumnTypeName::Date => DataType::Date,
16517 ColumnTypeName::Timestamp => DataType::Timestamp,
16518 ColumnTypeName::Timestamptz => DataType::Timestamptz,
16519 ColumnTypeName::Json => DataType::Json,
16520 ColumnTypeName::Jsonb => DataType::Jsonb,
16521 ColumnTypeName::Bytes => DataType::Bytes,
16522 ColumnTypeName::TextArray => DataType::TextArray,
16523 ColumnTypeName::IntArray => DataType::IntArray,
16524 ColumnTypeName::BigIntArray => DataType::BigIntArray,
16525 ColumnTypeName::TsVector => DataType::TsVector,
16526 ColumnTypeName::TsQuery => DataType::TsQuery,
16527 ColumnTypeName::Uuid => DataType::Uuid,
16528 ColumnTypeName::Time => DataType::Time,
16529 ColumnTypeName::Year => DataType::Year,
16530 ColumnTypeName::TimeTz => DataType::TimeTz,
16531 ColumnTypeName::Money => DataType::Money,
16532 ColumnTypeName::Range(k) => DataType::Range(match k {
16533 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
16534 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
16535 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
16536 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
16537 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
16538 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
16539 }),
16540 ColumnTypeName::Hstore => DataType::Hstore,
16541 ColumnTypeName::IntArray2D => DataType::IntArray2D,
16542 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
16543 ColumnTypeName::TextArray2D => DataType::TextArray2D,
16544 }
16545}
16546
16547fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
16551 match expr {
16552 Expr::Literal(l) => Ok(literal_to_value(l)),
16553 Expr::Cast { expr, target } => {
16554 let inner_value = literal_expr_to_value(*expr)?;
16555 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
16556 }
16557 Expr::Unary {
16558 op: UnOp::Neg,
16559 expr,
16560 } => match *expr {
16561 Expr::Literal(Literal::Integer(n)) => {
16562 let neg = n.checked_neg().ok_or_else(|| {
16565 EngineError::Unsupported("integer literal overflow on negation".into())
16566 })?;
16567 Ok(int_value_for(neg))
16568 }
16569 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
16570 other => Err(EngineError::Unsupported(alloc::format!(
16571 "unary minus over non-literal expression: {other:?}"
16572 ))),
16573 },
16574 Expr::Array(items) => {
16582 let mut materialised: alloc::vec::Vec<Value> =
16583 alloc::vec::Vec::with_capacity(items.len());
16584 for elem in items {
16585 materialised.push(literal_expr_to_value(elem)?);
16586 }
16587 Ok(array_literal_widen(materialised))
16588 }
16589 other => {
16602 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
16603 let ctx = EvalContext::new(&empty_schema, None);
16604 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
16605 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
16606 }
16607 }
16608}
16609
16610fn literal_to_value(l: Literal) -> Value {
16611 match l {
16612 Literal::Integer(n) => int_value_for(n),
16613 Literal::Float(x) => Value::Float(x),
16614 Literal::String(s) => Value::Text(s),
16615 Literal::Bool(b) => Value::Bool(b),
16616 Literal::Null => Value::Null,
16617 Literal::Vector(v) => Value::Vector(v),
16618 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
16619 }
16620}
16621
16622fn int_value_for(n: i64) -> Value {
16626 if let Ok(small) = i32::try_from(n) {
16627 Value::Int(small)
16628 } else {
16629 Value::BigInt(n)
16630 }
16631}
16632
16633#[allow(clippy::too_many_lines)]
16639fn check_unsigned_range(
16644 v: &Value,
16645 schema: &ColumnSchema,
16646 position: usize,
16647) -> Result<(), EngineError> {
16648 if !schema.is_unsigned {
16649 return Ok(());
16650 }
16651 let n = match v {
16652 Value::SmallInt(x) => i64::from(*x),
16653 Value::Int(x) => i64::from(*x),
16654 Value::BigInt(x) => *x,
16655 _ => return Ok(()), };
16657 if n < 0 {
16658 return Err(EngineError::Unsupported(alloc::format!(
16659 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
16660 schema.name
16661 )));
16662 }
16663 Ok(())
16664}
16665
16666fn coerce_value(
16667 v: Value,
16668 expected: DataType,
16669 col_name: &str,
16670 position: usize,
16671) -> Result<Value, EngineError> {
16672 if v.is_null() {
16673 return Ok(Value::Null);
16674 }
16675 let actual = v.data_type().expect("non-null");
16676 if actual == expected {
16677 return Ok(v);
16678 }
16679 let coerced = match (v, expected) {
16680 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16681 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16682 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16683 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16684 i128::from(n),
16685 precision,
16686 scale,
16687 col_name,
16688 )?),
16689 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
16690 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16691 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16692 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16693 i128::from(n),
16694 precision,
16695 scale,
16696 col_name,
16697 )?),
16698 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
16699 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16700 #[allow(clippy::cast_precision_loss)]
16701 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
16702 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16703 i128::from(n),
16704 precision,
16705 scale,
16706 col_name,
16707 )?),
16708 (Value::Float(x), DataType::Numeric { precision, scale }) => {
16709 Some(numeric_from_float(x, precision, scale, col_name)?)
16710 }
16711 (Value::Text(s), DataType::Numeric { precision, scale }) => {
16722 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
16723 return Err(EngineError::Eval(EvalError::TypeMismatch {
16724 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
16725 }));
16726 };
16727 Some(numeric_rescale(
16728 mantissa, src_scale, precision, scale, col_name,
16729 )?)
16730 }
16731 (Value::Text(s), DataType::Date) => {
16733 let d = eval::parse_date_literal(&s).ok_or_else(|| {
16734 EngineError::Eval(EvalError::TypeMismatch {
16735 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
16736 })
16737 })?;
16738 Some(Value::Date(d))
16739 }
16740 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
16747 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
16748 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
16749 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
16750 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
16751 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
16752 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
16753 _ => None,
16754 },
16755 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16764 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16765 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16766 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
16770 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
16771 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
16779 (Value::Text(s), DataType::Bytes) => {
16786 let bytes = decode_bytea_literal(&s).map_err(|e| {
16787 EngineError::Eval(EvalError::TypeMismatch {
16788 detail: alloc::format!(
16789 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
16790 ),
16791 })
16792 })?;
16793 Some(Value::Bytes(bytes))
16794 }
16795 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
16799 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
16807 Some(b) => Some(Value::Uuid(b)),
16808 None => {
16809 return Err(EngineError::Eval(EvalError::TypeMismatch {
16810 detail: alloc::format!(
16811 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
16812 ),
16813 }));
16814 }
16815 },
16816 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
16821 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
16827 Some(us) => Some(Value::Time(us)),
16828 None => {
16829 return Err(EngineError::Eval(EvalError::TypeMismatch {
16830 detail: alloc::format!(
16831 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
16832 ),
16833 }));
16834 }
16835 },
16836 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
16838 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
16843 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
16844 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
16845 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
16849 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
16850 Err(_) => {
16851 return Err(EngineError::Eval(EvalError::TypeMismatch {
16852 detail: alloc::format!(
16853 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
16854 ),
16855 }));
16856 }
16857 },
16858 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
16860 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
16864 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
16865 None => {
16866 return Err(EngineError::Eval(EvalError::TypeMismatch {
16867 detail: alloc::format!(
16868 "invalid input syntax for type time with time zone: \
16869 {s:?} (column `{col_name}`)"
16870 ),
16871 }));
16872 }
16873 },
16874 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
16876 Some(Value::Text(eval::format_timetz(us, offset_secs)))
16877 }
16878 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
16882 Some(c) => Some(Value::Money(c)),
16883 None => {
16884 return Err(EngineError::Eval(EvalError::TypeMismatch {
16885 detail: alloc::format!(
16886 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
16887 ),
16888 }));
16889 }
16890 },
16891 (Value::SmallInt(n), DataType::Money) => {
16895 Some(Value::Money(i64::from(n).saturating_mul(100)))
16896 }
16897 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
16898 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
16899 (Value::Float(x), DataType::Money) => {
16900 let scaled = x * 100.0;
16903 let cents = if scaled >= 0.0 {
16904 (scaled + 0.5) as i64
16905 } else {
16906 (scaled - 0.5) as i64
16907 };
16908 Some(Value::Money(cents))
16909 }
16910 (Value::Numeric { scaled, scale }, DataType::Money) => {
16911 let cents = if scale == 2 {
16914 scaled
16915 } else if scale < 2 {
16916 let mult = 10_i128.pow(u32::from(2 - scale));
16917 scaled.saturating_mul(mult)
16918 } else {
16919 let div = 10_i128.pow(u32::from(scale - 2));
16920 let half = div / 2;
16921 let bias = if scaled >= 0 { half } else { -half };
16922 (scaled + bias) / div
16923 };
16924 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
16925 }
16926 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
16928 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
16932 Some(v) => Some(v),
16933 None => {
16934 return Err(EngineError::Eval(EvalError::TypeMismatch {
16935 detail: alloc::format!(
16936 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
16937 ),
16938 }));
16939 }
16940 },
16941 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
16943 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
16945 Some(pairs) => Some(Value::Hstore(pairs)),
16946 None => {
16947 return Err(EngineError::Eval(EvalError::TypeMismatch {
16948 detail: alloc::format!(
16949 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
16950 ),
16951 }));
16952 }
16953 },
16954 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
16956 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
16959 Ok(m) => Some(Value::IntArray2D(m)),
16960 Err(e) => {
16961 return Err(EngineError::Eval(EvalError::TypeMismatch {
16962 detail: alloc::format!(
16963 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
16964 ),
16965 }));
16966 }
16967 },
16968 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
16969 Ok(m) => Some(Value::BigIntArray2D(m)),
16970 Err(e) => {
16971 return Err(EngineError::Eval(EvalError::TypeMismatch {
16972 detail: alloc::format!(
16973 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
16974 ),
16975 }));
16976 }
16977 },
16978 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
16979 Ok(m) => Some(Value::TextArray2D(m)),
16980 Err(e) => {
16981 return Err(EngineError::Eval(EvalError::TypeMismatch {
16982 detail: alloc::format!(
16983 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
16984 ),
16985 }));
16986 }
16987 },
16988 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
16990 (Value::BigIntArray2D(rows), DataType::Text) => {
16991 Some(Value::Text(format_bigint_2d_text(&rows)))
16992 }
16993 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
16994 (Value::Text(s), DataType::TextArray) => {
16999 let arr = decode_text_array_literal(&s).map_err(|e| {
17000 EngineError::Eval(EvalError::TypeMismatch {
17001 detail: alloc::format!(
17002 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17003 ),
17004 })
17005 })?;
17006 Some(Value::TextArray(arr))
17007 }
17008 (Value::Text(s), DataType::IntArray) => {
17014 let arr = decode_text_array_literal(&s).map_err(|e| {
17015 EngineError::Eval(EvalError::TypeMismatch {
17016 detail: alloc::format!(
17017 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17018 ),
17019 })
17020 })?;
17021 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17022 for elem in arr {
17023 match elem {
17024 None => out.push(None),
17025 Some(t) => {
17026 let n: i32 = t.parse().map_err(|_| {
17027 EngineError::Eval(EvalError::TypeMismatch {
17028 detail: alloc::format!(
17029 "cannot parse {t:?} as INT element for `{col_name}`"
17030 ),
17031 })
17032 })?;
17033 out.push(Some(n));
17034 }
17035 }
17036 }
17037 Some(Value::IntArray(out))
17038 }
17039 (Value::Text(s), DataType::BigIntArray) => {
17040 let arr = decode_text_array_literal(&s).map_err(|e| {
17041 EngineError::Eval(EvalError::TypeMismatch {
17042 detail: alloc::format!(
17043 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17044 ),
17045 })
17046 })?;
17047 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17048 for elem in arr {
17049 match elem {
17050 None => out.push(None),
17051 Some(t) => {
17052 let n: i64 = t.parse().map_err(|_| {
17053 EngineError::Eval(EvalError::TypeMismatch {
17054 detail: alloc::format!(
17055 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17056 ),
17057 })
17058 })?;
17059 out.push(Some(n));
17060 }
17061 }
17062 }
17063 Some(Value::BigIntArray(out))
17064 }
17065 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17069 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17078 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17079 EngineError::Eval(EvalError::TypeMismatch {
17080 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17081 })
17082 })?;
17083 if parsed.len() != dim as usize {
17084 return Err(EngineError::Eval(EvalError::TypeMismatch {
17085 detail: alloc::format!(
17086 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17087 parsed.len()
17088 ),
17089 }));
17090 }
17091 Some(match encoding {
17092 VecEncoding::F32 => Value::Vector(parsed),
17093 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17094 VecEncoding::F16 => {
17095 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17096 }
17097 })
17098 }
17099 (Value::Text(s), DataType::TsVector) => {
17109 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17110 EngineError::Eval(EvalError::TypeMismatch {
17111 detail: alloc::format!(
17112 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17113 ),
17114 })
17115 })?;
17116 Some(Value::TsVector(lexs))
17117 }
17118 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17119 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17120 EngineError::Eval(EvalError::TypeMismatch {
17121 detail: alloc::format!(
17122 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17123 ),
17124 })
17125 })?;
17126 Some(Value::Timestamp(t))
17127 }
17128 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17131 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17132 }
17133 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17137 (Value::Timestamp(t), DataType::Date) => {
17138 let days = t.div_euclid(86_400_000_000);
17139 i32::try_from(days).ok().map(Value::Date)
17140 }
17141 (
17142 Value::Numeric {
17143 scaled,
17144 scale: src_scale,
17145 },
17146 DataType::Numeric { precision, scale },
17147 ) => Some(numeric_rescale(
17148 scaled, src_scale, precision, scale, col_name,
17149 )?),
17150 #[allow(clippy::cast_precision_loss)]
17151 (Value::Numeric { scaled, scale }, DataType::Float) => {
17152 let mut div = 1.0_f64;
17153 for _ in 0..scale {
17154 div *= 10.0;
17155 }
17156 Some(Value::Float((scaled as f64) / div))
17157 }
17158 (Value::Numeric { scaled, scale }, DataType::Int) => {
17159 let truncated = numeric_truncate_to_integer(scaled, scale);
17160 i32::try_from(truncated).ok().map(Value::Int)
17161 }
17162 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17163 let truncated = numeric_truncate_to_integer(scaled, scale);
17164 i64::try_from(truncated).ok().map(Value::BigInt)
17165 }
17166 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17167 let truncated = numeric_truncate_to_integer(scaled, scale);
17168 i16::try_from(truncated).ok().map(Value::SmallInt)
17169 }
17170 (Value::Text(s), DataType::Varchar(max)) => {
17172 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17173 Some(Value::Text(s))
17174 } else {
17175 return Err(EngineError::Unsupported(alloc::format!(
17176 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17177 {} chars",
17178 s.chars().count()
17179 )));
17180 }
17181 }
17182 (
17190 Value::Vector(v),
17191 DataType::Vector {
17192 dim,
17193 encoding: VecEncoding::Sq8,
17194 },
17195 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17196 (
17201 Value::Vector(v),
17202 DataType::Vector {
17203 dim,
17204 encoding: VecEncoding::F16,
17205 },
17206 ) if v.len() == dim as usize => Some(Value::HalfVector(
17207 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17208 )),
17209 (Value::Text(s), DataType::Char(size)) => {
17213 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17214 if len > size {
17215 return Err(EngineError::Unsupported(alloc::format!(
17216 "value for CHAR({size}) column `{col_name}` exceeds length: \
17217 {len} chars"
17218 )));
17219 }
17220 let need = (size - len) as usize;
17221 let mut padded = s;
17222 padded.reserve(need);
17223 for _ in 0..need {
17224 padded.push(' ');
17225 }
17226 Some(Value::Text(padded))
17227 }
17228 _ => None,
17229 };
17230 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17231 column: col_name.into(),
17232 expected,
17233 actual,
17234 position,
17235 }))
17236}
17237
17238fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17244 use core::fmt::Write;
17245 let mut out = alloc::string::String::from("(");
17246 for (i, a) in args.iter().enumerate() {
17247 if i > 0 {
17248 out.push_str(", ");
17249 }
17250 match a.mode {
17251 spg_sql::ast::FunctionArgMode::In => {}
17252 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17253 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17254 }
17255 if let Some(n) = &a.name {
17256 out.push_str(n);
17257 out.push(' ');
17258 }
17259 match &a.ty {
17260 spg_sql::ast::FunctionArgType::Typed(t) => {
17261 let _ = write!(out, "{t}");
17262 }
17263 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17264 }
17265 }
17266 out.push(')');
17267 out
17268}
17269
17270#[cfg(test)]
17271mod tests {
17272 use super::*;
17273 use alloc::vec;
17274
17275 fn unwrap_command_ok(r: &QueryResult) -> usize {
17276 match r {
17277 QueryResult::CommandOk { affected, .. } => *affected,
17278 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17279 }
17280 }
17281
17282 #[test]
17283 fn create_table_registers_schema() {
17284 let mut e = Engine::new();
17285 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17286 .unwrap();
17287 assert_eq!(e.catalog().table_count(), 1);
17288 let t = e.catalog().get("foo").unwrap();
17289 assert_eq!(t.schema().columns.len(), 2);
17290 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17291 assert!(!t.schema().columns[0].nullable);
17292 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17293 }
17294
17295 #[test]
17296 fn create_table_vector_default_is_f32_encoded() {
17297 let mut e = Engine::new();
17298 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17299 let t = e.catalog().get("t").unwrap();
17300 assert_eq!(
17301 t.schema().columns[0].ty,
17302 DataType::Vector {
17303 dim: 8,
17304 encoding: VecEncoding::F32,
17305 },
17306 );
17307 }
17308
17309 #[test]
17310 fn create_table_vector_using_sq8_succeeds() {
17311 let mut e = Engine::new();
17315 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17316 let t = e.catalog().get("t").unwrap();
17317 assert_eq!(
17318 t.schema().columns[0].ty,
17319 DataType::Vector {
17320 dim: 8,
17321 encoding: VecEncoding::Sq8,
17322 },
17323 );
17324 }
17325
17326 #[test]
17327 fn insert_into_sq8_column_quantises_f32_payload() {
17328 let mut e = Engine::new();
17335 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17336 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17337 .unwrap();
17338 let t = e.catalog().get("t").unwrap();
17339 assert_eq!(t.rows().len(), 1);
17340 match &t.rows()[0].values[0] {
17341 Value::Sq8Vector(q) => {
17342 assert_eq!(q.bytes.len(), 4);
17343 assert!((q.min - 0.0).abs() < 1e-6);
17345 assert!((q.max - 1.0).abs() < 1e-6);
17346 }
17347 other => panic!("expected Sq8Vector cell, got {other:?}"),
17348 }
17349 }
17350
17351 #[test]
17352 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17353 let mut e = Engine::new();
17360 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17361 .unwrap();
17362 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17363 .unwrap();
17364 let t = e.catalog().get("t").unwrap();
17365 assert_eq!(t.rows().len(), 1);
17366 match &t.rows()[0].values[0] {
17367 Value::HalfVector(h) => {
17368 assert_eq!(h.dim(), 4);
17369 let back = h.to_f32_vec();
17370 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17371 for (g, e) in back.iter().zip(expected.iter()) {
17372 assert!(
17373 (g - e).abs() < 1e-6,
17374 "{g} vs {e} should be exact on f16 grid"
17375 );
17376 }
17377 }
17378 other => panic!("expected HalfVector cell, got {other:?}"),
17379 }
17380 }
17381
17382 #[test]
17383 fn alter_index_rebuild_in_place_succeeds() {
17384 let mut e = Engine::new();
17389 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17390 .unwrap();
17391 for i in 0..8_i32 {
17392 #[allow(clippy::cast_precision_loss)]
17393 let base = (i as f32) * 0.1;
17394 e.execute(&alloc::format!(
17395 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17396 b1 = base + 0.01,
17397 b2 = base + 0.02,
17398 ))
17399 .unwrap();
17400 }
17401 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17402 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
17403 assert_eq!(
17405 e.catalog().get("t").unwrap().schema().columns[1].ty,
17406 DataType::Vector {
17407 dim: 3,
17408 encoding: VecEncoding::F32,
17409 },
17410 );
17411 }
17412
17413 #[test]
17414 fn alter_index_rebuild_with_encoding_switches_cell_type() {
17415 let mut e = Engine::new();
17420 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
17421 .unwrap();
17422 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
17423 .unwrap();
17424 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17425 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
17426 .unwrap();
17427 let t = e.catalog().get("t").unwrap();
17428 assert_eq!(
17429 t.schema().columns[1].ty,
17430 DataType::Vector {
17431 dim: 4,
17432 encoding: VecEncoding::Sq8,
17433 },
17434 );
17435 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
17436 }
17437
17438 #[test]
17439 fn alter_index_rebuild_unknown_index_errors() {
17440 let mut e = Engine::new();
17441 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
17442 assert!(
17443 matches!(
17444 &err,
17445 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
17446 ),
17447 "got: {err}"
17448 );
17449 }
17450
17451 #[test]
17452 fn alter_index_rebuild_on_btree_index_errors() {
17453 let mut e = Engine::new();
17456 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17457 e.execute("INSERT INTO t VALUES (1)").unwrap();
17458 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
17459 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
17460 assert!(
17461 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
17462 "got: {err}"
17463 );
17464 }
17465
17466 #[test]
17467 fn prepared_insert_substitutes_placeholders() {
17468 let mut e = Engine::new();
17474 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17475 .unwrap();
17476 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
17477 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
17478 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
17479 .unwrap();
17480 }
17481 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
17483 let QueryResult::Rows { rows, .. } = rows_result else {
17484 panic!("expected Rows")
17485 };
17486 assert_eq!(rows.len(), 3);
17487 }
17488
17489 #[test]
17490 fn prepared_select_with_placeholder_filters_rows() {
17491 let mut e = Engine::new();
17492 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17493 .unwrap();
17494 for i in 0..10_i32 {
17495 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17496 .unwrap();
17497 }
17498 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17499 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
17500 else {
17501 panic!("expected Rows")
17502 };
17503 assert_eq!(rows.len(), 1);
17505 assert_eq!(rows[0].values[0], Value::Int(5));
17506 }
17507
17508 #[test]
17509 fn prepared_too_few_params_errors() {
17510 let mut e = Engine::new();
17511 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17512 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17513 let err = e.execute_prepared(stmt, &[]).unwrap_err();
17514 assert!(
17515 matches!(
17516 &err,
17517 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
17518 ),
17519 "got: {err}"
17520 );
17521 }
17522
17523 #[test]
17524 fn insert_into_half_column_dim_mismatch_errors() {
17525 let mut e = Engine::new();
17526 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17527 .unwrap();
17528 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17529 assert!(matches!(
17530 &err,
17531 EngineError::Storage(StorageError::TypeMismatch { .. })
17532 ));
17533 }
17534
17535 #[test]
17536 fn insert_into_sq8_column_dim_mismatch_errors() {
17537 let mut e = Engine::new();
17542 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17543 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17544 assert!(
17545 matches!(
17546 &err,
17547 EngineError::Storage(StorageError::TypeMismatch { .. })
17548 ),
17549 "got: {err}",
17550 );
17551 }
17552
17553 #[test]
17554 fn create_table_duplicate_errors() {
17555 let mut e = Engine::new();
17556 e.execute("CREATE TABLE foo (a INT)").unwrap();
17557 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
17558 assert!(matches!(
17559 err,
17560 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
17561 ));
17562 }
17563
17564 #[test]
17565 fn insert_into_unknown_table_errors() {
17566 let mut e = Engine::new();
17567 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
17568 assert!(matches!(
17569 err,
17570 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
17571 ));
17572 }
17573
17574 #[test]
17575 fn insert_happy_path_reports_one_affected() {
17576 let mut e = Engine::new();
17577 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17578 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
17579 assert_eq!(unwrap_command_ok(&r), 1);
17580 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
17581 }
17582
17583 #[test]
17584 fn insert_arity_mismatch_propagates() {
17585 let mut e = Engine::new();
17586 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
17587 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
17588 assert!(matches!(
17589 err,
17590 EngineError::Storage(StorageError::ArityMismatch { .. })
17591 ));
17592 }
17593
17594 #[test]
17595 fn insert_negative_integer_via_unary_minus() {
17596 let mut e = Engine::new();
17597 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17598 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
17599 let rows = e.catalog().get("foo").unwrap().rows();
17600 assert_eq!(rows[0].values[0], Value::Int(-7));
17601 }
17602
17603 #[test]
17604 fn insert_expression_evaluated_against_empty_context() {
17605 let mut e = Engine::new();
17610 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17611 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
17612 let rows = e.catalog().get("foo").unwrap().rows();
17613 assert_eq!(rows[0].values[0], Value::Int(3));
17614 }
17615
17616 #[test]
17617 fn select_star_returns_all_rows_in_insertion_order() {
17618 let mut e = Engine::new();
17619 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
17620 .unwrap();
17621 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
17622 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
17623 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
17624
17625 let r = e.execute("SELECT * FROM foo").unwrap();
17626 let QueryResult::Rows { columns, rows } = r else {
17627 panic!("expected Rows")
17628 };
17629 assert_eq!(columns.len(), 2);
17630 assert_eq!(columns[0].name, "a");
17631 assert_eq!(rows.len(), 3);
17632 assert_eq!(
17633 rows[1].values,
17634 vec![Value::Int(2), Value::Text("two".into())]
17635 );
17636 }
17637
17638 #[test]
17639 fn select_star_on_empty_table_returns_zero_rows() {
17640 let mut e = Engine::new();
17641 e.execute("CREATE TABLE foo (a INT)").unwrap();
17642 let r = e.execute("SELECT * FROM foo").unwrap();
17643 match r {
17644 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
17645 QueryResult::CommandOk { .. } => panic!("expected Rows"),
17646 }
17647 }
17648
17649 fn make_three_row_users(e: &mut Engine) {
17652 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
17653 .unwrap();
17654 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
17655 .unwrap();
17656 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
17657 .unwrap();
17658 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
17659 .unwrap();
17660 }
17661
17662 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
17663 match r {
17664 QueryResult::Rows { columns, rows } => (columns, rows),
17665 QueryResult::CommandOk { .. } => panic!("expected Rows"),
17666 }
17667 }
17668
17669 #[test]
17670 fn where_filter_passes_only_true_rows() {
17671 let mut e = Engine::new();
17672 make_three_row_users(&mut e);
17673 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
17674 let (_, rows) = unwrap_rows(r);
17675 assert_eq!(rows.len(), 2);
17676 assert_eq!(rows[0].values[0], Value::Int(2));
17677 assert_eq!(rows[1].values[0], Value::Int(3));
17678 }
17679
17680 #[test]
17681 fn where_with_null_result_filters_out_row() {
17682 let mut e = Engine::new();
17683 make_three_row_users(&mut e);
17684 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
17686 let (_, rows) = unwrap_rows(r);
17687 assert_eq!(rows.len(), 1);
17688 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
17689 }
17690
17691 #[test]
17692 fn projection_named_columns() {
17693 let mut e = Engine::new();
17694 make_three_row_users(&mut e);
17695 let r = e.execute("SELECT name, score FROM users").unwrap();
17696 let (cols, rows) = unwrap_rows(r);
17697 assert_eq!(cols.len(), 2);
17698 assert_eq!(cols[0].name, "name");
17699 assert_eq!(cols[1].name, "score");
17700 assert_eq!(rows.len(), 3);
17701 assert_eq!(
17702 rows[0].values,
17703 vec![Value::Text("alice".into()), Value::Int(90)]
17704 );
17705 }
17706
17707 #[test]
17708 fn projection_with_column_alias() {
17709 let mut e = Engine::new();
17710 make_three_row_users(&mut e);
17711 let r = e
17712 .execute("SELECT name AS who FROM users WHERE id = 1")
17713 .unwrap();
17714 let (cols, rows) = unwrap_rows(r);
17715 assert_eq!(cols[0].name, "who");
17716 assert_eq!(rows.len(), 1);
17717 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
17718 }
17719
17720 #[test]
17721 fn qualified_column_with_table_alias_resolves() {
17722 let mut e = Engine::new();
17723 make_three_row_users(&mut e);
17724 let r = e
17725 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
17726 .unwrap();
17727 let (cols, rows) = unwrap_rows(r);
17728 assert_eq!(cols.len(), 2);
17729 assert_eq!(rows.len(), 2);
17730 }
17731
17732 #[test]
17733 fn qualified_column_with_wrong_alias_errors() {
17734 let mut e = Engine::new();
17735 make_three_row_users(&mut e);
17736 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
17737 assert!(matches!(
17738 err,
17739 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
17740 ));
17741 }
17742
17743 #[test]
17744 fn select_unknown_column_errors_in_projection() {
17745 let mut e = Engine::new();
17746 make_three_row_users(&mut e);
17747 let err = e.execute("SELECT ghost FROM users").unwrap_err();
17748 assert!(matches!(
17749 err,
17750 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
17751 ));
17752 }
17753
17754 #[test]
17755 fn where_unknown_column_errors() {
17756 let mut e = Engine::new();
17757 make_three_row_users(&mut e);
17758 let err = e
17759 .execute("SELECT * FROM users WHERE ghost = 1")
17760 .unwrap_err();
17761 assert!(matches!(
17762 err,
17763 EngineError::Eval(EvalError::ColumnNotFound { .. })
17764 ));
17765 }
17766
17767 #[test]
17768 fn expression_projection_evaluates_and_renders() {
17769 let mut e = Engine::new();
17772 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
17773 e.execute("INSERT INTO t VALUES (3)").unwrap();
17774 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
17775 assert_eq!(rows.len(), 1);
17776 assert_eq!(rows[0].values[0], Value::Int(3));
17779 }
17780
17781 #[test]
17782 fn select_unknown_table_errors() {
17783 let mut e = Engine::new();
17784 let err = e.execute("SELECT * FROM ghost").unwrap_err();
17785 assert!(matches!(
17786 err,
17787 EngineError::Storage(StorageError::TableNotFound { .. })
17788 ));
17789 }
17790
17791 #[test]
17792 fn invalid_sql_returns_parse_error() {
17793 let mut e = Engine::new();
17796 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
17797 assert!(matches!(err, EngineError::Parse(_)));
17798 }
17799
17800 #[test]
17803 fn create_index_registers_on_table() {
17804 let mut e = Engine::new();
17805 make_three_row_users(&mut e);
17806 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
17807 let t = e.catalog().get("users").unwrap();
17808 assert_eq!(t.indices().len(), 1);
17809 assert_eq!(t.indices()[0].name, "by_name");
17810 }
17811
17812 #[test]
17813 fn create_index_on_unknown_table_errors() {
17814 let mut e = Engine::new();
17815 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
17816 assert!(matches!(
17817 err,
17818 EngineError::Storage(StorageError::TableNotFound { .. })
17819 ));
17820 }
17821
17822 #[test]
17823 fn create_index_on_unknown_column_errors() {
17824 let mut e = Engine::new();
17825 make_three_row_users(&mut e);
17826 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
17827 assert!(matches!(
17828 err,
17829 EngineError::Storage(StorageError::ColumnNotFound { .. })
17830 ));
17831 }
17832
17833 #[test]
17834 fn select_eq_uses_index_returns_same_rows_as_scan() {
17835 let mut without = Engine::new();
17839 make_three_row_users(&mut without);
17840 let mut with = Engine::new();
17841 make_three_row_users(&mut with);
17842 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
17843
17844 let q = "SELECT * FROM users WHERE id = 2";
17845 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
17846 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
17847 assert_eq!(no_idx_rows, idx_rows);
17848 assert_eq!(idx_rows.len(), 1);
17849 }
17850
17851 #[test]
17852 fn select_eq_with_no_matching_index_value_returns_empty() {
17853 let mut e = Engine::new();
17854 make_three_row_users(&mut e);
17855 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
17856 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
17857 assert_eq!(rows.len(), 0);
17858 }
17859
17860 #[test]
17863 fn begin_sets_in_transaction_flag() {
17864 let mut e = Engine::new();
17865 assert!(!e.in_transaction());
17866 e.execute("BEGIN").unwrap();
17867 assert!(e.in_transaction());
17868 }
17869
17870 #[test]
17871 fn double_begin_errors() {
17872 let mut e = Engine::new();
17873 e.execute("BEGIN").unwrap();
17874 let err = e.execute("BEGIN").unwrap_err();
17875 assert_eq!(err, EngineError::TransactionAlreadyOpen);
17876 }
17877
17878 #[test]
17879 fn commit_without_begin_errors() {
17880 let mut e = Engine::new();
17881 let err = e.execute("COMMIT").unwrap_err();
17882 assert_eq!(err, EngineError::NoActiveTransaction);
17883 }
17884
17885 #[test]
17886 fn rollback_without_begin_errors() {
17887 let mut e = Engine::new();
17888 let err = e.execute("ROLLBACK").unwrap_err();
17889 assert_eq!(err, EngineError::NoActiveTransaction);
17890 }
17891
17892 #[test]
17893 fn commit_applies_shadow_to_committed_catalog() {
17894 let mut e = Engine::new();
17895 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
17896 e.execute("BEGIN").unwrap();
17897 e.execute("INSERT INTO t VALUES (1)").unwrap();
17898 e.execute("INSERT INTO t VALUES (2)").unwrap();
17899 e.execute("COMMIT").unwrap();
17900 assert!(!e.in_transaction());
17901 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
17902 }
17903
17904 #[test]
17905 fn rollback_discards_shadow() {
17906 let mut e = Engine::new();
17907 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
17908 e.execute("BEGIN").unwrap();
17909 e.execute("INSERT INTO t VALUES (1)").unwrap();
17910 e.execute("INSERT INTO t VALUES (2)").unwrap();
17911 e.execute("ROLLBACK").unwrap();
17912 assert!(!e.in_transaction());
17913 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
17914 }
17915
17916 #[test]
17917 fn select_during_tx_sees_uncommitted_writes_own_session() {
17918 let mut e = Engine::new();
17921 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
17922 e.execute("BEGIN").unwrap();
17923 e.execute("INSERT INTO t VALUES (42)").unwrap();
17924 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
17925 assert_eq!(rows.len(), 1);
17926 assert_eq!(rows[0].values[0], Value::Int(42));
17927 }
17928
17929 #[test]
17930 fn snapshot_with_no_users_is_bare_catalog_format() {
17931 let mut e = Engine::new();
17932 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17933 let bytes = e.snapshot();
17934 assert_eq!(
17935 &bytes[..8],
17936 b"SPGDB001",
17937 "must be the bare v3.x catalog magic"
17938 );
17939 let e2 = Engine::restore_envelope(&bytes).unwrap();
17940 assert!(e2.users().is_empty());
17941 assert_eq!(e2.catalog().table_count(), 1);
17942 }
17943
17944 #[test]
17945 fn snapshot_with_users_round_trips_both_via_envelope() {
17946 let mut e = Engine::new();
17947 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17948 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
17949 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
17950 .unwrap();
17951 let bytes = e.snapshot();
17952 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
17953 let e2 = Engine::restore_envelope(&bytes).unwrap();
17954 assert_eq!(e2.users().len(), 2);
17955 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
17956 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
17957 assert_eq!(e2.verify_user("alice", "wrong"), None);
17958 assert_eq!(e2.catalog().table_count(), 1);
17959 }
17960
17961 #[test]
17962 fn ddl_inside_tx_also_rolled_back() {
17963 let mut e = Engine::new();
17964 e.execute("BEGIN").unwrap();
17965 e.execute("CREATE TABLE t (v INT)").unwrap();
17966 e.execute("SELECT * FROM t").unwrap();
17968 e.execute("ROLLBACK").unwrap();
17969 let err = e.execute("SELECT * FROM t").unwrap_err();
17971 assert!(matches!(
17972 err,
17973 EngineError::Storage(StorageError::TableNotFound { .. })
17974 ));
17975 }
17976
17977 #[test]
17980 fn create_publication_lands_in_catalog() {
17981 let mut e = Engine::new();
17982 assert!(e.publications().is_empty());
17983 e.execute("CREATE PUBLICATION pub_a").unwrap();
17984 assert_eq!(e.publications().len(), 1);
17985 assert!(e.publications().contains("pub_a"));
17986 }
17987
17988 #[test]
17989 fn create_publication_duplicate_errors() {
17990 let mut e = Engine::new();
17991 e.execute("CREATE PUBLICATION pub_a").unwrap();
17992 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
17993 assert!(
17994 alloc::format!("{err:?}").contains("DuplicateName"),
17995 "got {err:?}"
17996 );
17997 }
17998
17999 #[test]
18000 fn drop_publication_silent_when_absent() {
18001 let mut e = Engine::new();
18002 let r = e.execute("DROP PUBLICATION nope").unwrap();
18005 match r {
18006 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18007 other => panic!("expected CommandOk, got {other:?}"),
18008 }
18009 }
18010
18011 #[test]
18012 fn drop_publication_present_reports_one_affected() {
18013 let mut e = Engine::new();
18014 e.execute("CREATE PUBLICATION pub_a").unwrap();
18015 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18016 match r {
18017 QueryResult::CommandOk {
18018 affected,
18019 modified_catalog,
18020 } => {
18021 assert_eq!(affected, 1);
18022 assert!(modified_catalog);
18023 }
18024 other => panic!("expected CommandOk, got {other:?}"),
18025 }
18026 assert!(e.publications().is_empty());
18027 }
18028
18029 #[test]
18030 fn publications_persist_across_snapshot_restore() {
18031 let mut e = Engine::new();
18036 e.execute("CREATE PUBLICATION pub_a").unwrap();
18037 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18038 .unwrap();
18039 let snap = e.snapshot();
18040 let e2 = Engine::restore_envelope(&snap).unwrap();
18041 assert_eq!(e2.publications().len(), 2);
18042 assert!(e2.publications().contains("pub_a"));
18043 assert!(e2.publications().contains("pub_b"));
18044 }
18045
18046 #[test]
18047 fn create_publication_allowed_inside_transaction() {
18048 let mut e = Engine::new();
18052 e.execute("BEGIN").unwrap();
18053 e.execute("CREATE PUBLICATION pub_a").unwrap();
18054 e.execute("COMMIT").unwrap();
18055 assert!(e.publications().contains("pub_a"));
18056 }
18057
18058 #[test]
18061 fn create_publication_for_table_list_lands_with_scope() {
18062 let mut e = Engine::new();
18063 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18064 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18065 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18066 .unwrap();
18067 let scope = e.publications().get("pub_a").cloned();
18068 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18069 panic!("expected ForTables scope, got {scope:?}")
18070 };
18071 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18072 }
18073
18074 #[test]
18075 fn create_publication_all_tables_except_lands_with_scope() {
18076 let mut e = Engine::new();
18077 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18078 .unwrap();
18079 let scope = e.publications().get("pub_a").cloned();
18080 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18081 panic!("expected AllTablesExcept scope, got {scope:?}")
18082 };
18083 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18084 }
18085
18086 #[test]
18087 fn show_publications_empty_returns_zero_rows() {
18088 let e = Engine::new();
18089 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18090 let QueryResult::Rows { rows, columns } = r else {
18091 panic!()
18092 };
18093 assert!(rows.is_empty());
18094 assert_eq!(columns.len(), 3);
18095 assert_eq!(columns[0].name, "name");
18096 assert_eq!(columns[1].name, "scope");
18097 assert_eq!(columns[2].name, "table_count");
18098 }
18099
18100 #[test]
18101 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18102 let mut e = Engine::new();
18103 e.execute("CREATE PUBLICATION z_pub").unwrap();
18104 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18105 .unwrap();
18106 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18107 .unwrap();
18108 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18109 let QueryResult::Rows { rows, .. } = r else {
18110 panic!()
18111 };
18112 assert_eq!(rows.len(), 3);
18113 let names: Vec<&str> = rows
18115 .iter()
18116 .map(|r| {
18117 if let Value::Text(s) = &r.values[0] {
18118 s.as_str()
18119 } else {
18120 panic!()
18121 }
18122 })
18123 .collect();
18124 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18125 match &rows[0].values[1] {
18127 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18128 other => panic!("expected Text, got {other:?}"),
18129 }
18130 assert_eq!(rows[0].values[2], Value::Int(2));
18131 match &rows[1].values[1] {
18133 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18134 other => panic!("expected Text, got {other:?}"),
18135 }
18136 assert_eq!(rows[1].values[2], Value::Int(1));
18137 match &rows[2].values[1] {
18139 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18140 other => panic!("expected Text, got {other:?}"),
18141 }
18142 assert_eq!(rows[2].values[2], Value::Null);
18143 }
18144
18145 #[test]
18146 fn for_list_scopes_persist_across_snapshot() {
18147 let mut e = Engine::new();
18150 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18151 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18152 .unwrap();
18153 let snap = e.snapshot();
18154 let e2 = Engine::restore_envelope(&snap).unwrap();
18155 assert_eq!(e2.publications().len(), 2);
18156 let p1 = e2.publications().get("p1").cloned();
18157 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18158 panic!("p1 scope lost: {p1:?}")
18159 };
18160 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18161 let p2 = e2.publications().get("p2").cloned();
18162 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18163 panic!("p2 scope lost: {p2:?}")
18164 };
18165 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18166 }
18167
18168 #[test]
18171 fn create_subscription_lands_in_catalog_with_defaults() {
18172 let mut e = Engine::new();
18173 e.execute(
18174 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18175 )
18176 .unwrap();
18177 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18178 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18179 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18180 assert!(s.enabled);
18181 assert_eq!(s.last_received_pos, 0);
18182 }
18183
18184 #[test]
18185 fn create_subscription_duplicate_name_errors() {
18186 let mut e = Engine::new();
18187 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18188 .unwrap();
18189 let err = e
18190 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18191 .unwrap_err();
18192 assert!(
18193 alloc::format!("{err:?}").contains("DuplicateName"),
18194 "got {err:?}"
18195 );
18196 }
18197
18198 #[test]
18199 fn drop_subscription_silent_when_absent() {
18200 let mut e = Engine::new();
18201 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18202 match r {
18203 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18204 other => panic!("expected CommandOk, got {other:?}"),
18205 }
18206 }
18207
18208 #[test]
18209 fn subscription_advance_updates_last_pos_monotone() {
18210 let mut e = Engine::new();
18211 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18212 .unwrap();
18213 assert!(e.subscription_advance("s", 100));
18214 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18215 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18217 assert!(e.subscription_advance("s", 200));
18218 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18219 assert!(!e.subscription_advance("missing", 1));
18220 }
18221
18222 #[test]
18223 fn show_subscriptions_returns_rows_ordered_by_name() {
18224 let mut e = Engine::new();
18225 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18226 .unwrap();
18227 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18228 .unwrap();
18229 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18230 let QueryResult::Rows { rows, columns } = r else {
18231 panic!()
18232 };
18233 assert_eq!(rows.len(), 2);
18234 assert_eq!(columns.len(), 5);
18235 assert_eq!(columns[0].name, "name");
18236 assert_eq!(columns[4].name, "last_received_pos");
18237 let names: Vec<&str> = rows
18239 .iter()
18240 .map(|r| {
18241 if let Value::Text(s) = &r.values[0] {
18242 s.as_str()
18243 } else {
18244 panic!()
18245 }
18246 })
18247 .collect();
18248 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18249 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18251 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18252 assert_eq!(rows[0].values[3], Value::Bool(true));
18253 assert_eq!(rows[0].values[4], Value::BigInt(0));
18254 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18256 }
18257
18258 #[test]
18259 fn subscriptions_persist_across_snapshot_envelope_v4() {
18260 let mut e = Engine::new();
18261 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18262 .unwrap();
18263 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18264 .unwrap();
18265 e.subscription_advance("s2", 42);
18266 let snap = e.snapshot();
18267 let e2 = Engine::restore_envelope(&snap).unwrap();
18268 assert_eq!(e2.subscriptions().len(), 2);
18269 let s1 = e2.subscriptions().get("s1").unwrap();
18270 assert_eq!(s1.conn_str, "h=A");
18271 assert_eq!(
18272 s1.publications,
18273 alloc::vec!["p1".to_string(), "p2".to_string()]
18274 );
18275 assert_eq!(s1.last_received_pos, 0);
18276 let s2 = e2.subscriptions().get("s2").unwrap();
18277 assert_eq!(s2.last_received_pos, 42);
18278 }
18279
18280 #[test]
18281 fn v3_envelope_loads_with_empty_subscriptions() {
18282 let mut e = Engine::new();
18286 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
18287 let catalog = e.catalog.serialize();
18288 let users = crate::users::serialize_users(&e.users);
18289 let pubs = e.publications.serialize();
18290 let mut buf = Vec::new();
18291 buf.extend_from_slice(b"SPGENV01");
18292 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18294 buf.extend_from_slice(&catalog);
18295 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18296 buf.extend_from_slice(&users);
18297 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18298 buf.extend_from_slice(&pubs);
18299 let crc = spg_crypto::crc32::crc32(&buf);
18300 buf.extend_from_slice(&crc.to_le_bytes());
18301
18302 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
18303 assert!(e2.subscriptions().is_empty());
18304 assert!(e2.publications().contains("pub_legacy"));
18305 }
18306
18307 #[test]
18308 fn create_subscription_allowed_inside_transaction() {
18309 let mut e = Engine::new();
18310 e.execute("BEGIN").unwrap();
18311 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18312 .unwrap();
18313 e.execute("COMMIT").unwrap();
18314 assert!(e.subscriptions().contains("s"));
18315 }
18316
18317 #[test]
18319 fn analyze_populates_histogram_bounds() {
18320 let mut e = Engine::new();
18321 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
18322 .unwrap();
18323 for i in 0..50 {
18324 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
18325 .unwrap();
18326 }
18327 e.execute("ANALYZE t").unwrap();
18328 let stats = e.statistics();
18329 let id_stats = stats.get("t", "id").unwrap();
18330 assert!(id_stats.histogram_bounds.len() >= 2);
18331 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
18332 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
18333 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
18334 assert_eq!(id_stats.n_distinct, 50);
18335 }
18336
18337 #[test]
18338 fn reanalyze_overwrites_prior_stats() {
18339 let mut e = Engine::new();
18340 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18341 for i in 0..10 {
18342 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18343 .unwrap();
18344 }
18345 e.execute("ANALYZE t").unwrap();
18346 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
18347 assert_eq!(n1, 10);
18348 for i in 10..30 {
18349 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18350 .unwrap();
18351 }
18352 e.execute("ANALYZE t").unwrap();
18353 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
18354 assert_eq!(n2, 30);
18355 }
18356
18357 #[test]
18358 fn analyze_unknown_table_errors() {
18359 let mut e = Engine::new();
18360 let err = e.execute("ANALYZE nonexistent").unwrap_err();
18361 assert!(matches!(
18362 err,
18363 EngineError::Storage(StorageError::TableNotFound { .. })
18364 ));
18365 }
18366
18367 #[test]
18368 fn bare_analyze_covers_all_user_tables() {
18369 let mut e = Engine::new();
18370 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18371 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
18372 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
18373 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
18374 let r = e.execute("ANALYZE").unwrap();
18375 match r {
18376 QueryResult::CommandOk {
18377 affected,
18378 modified_catalog,
18379 } => {
18380 assert_eq!(affected, 2);
18381 assert!(modified_catalog);
18382 }
18383 other => panic!("expected CommandOk, got {other:?}"),
18384 }
18385 assert!(e.statistics().get("t1", "id").is_some());
18386 assert!(e.statistics().get("t2", "name").is_some());
18387 }
18388
18389 #[test]
18390 fn select_from_spg_statistic_returns_rows_per_column() {
18391 let mut e = Engine::new();
18392 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18393 .unwrap();
18394 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
18395 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
18396 e.execute("ANALYZE t").unwrap();
18397 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
18398 let QueryResult::Rows { rows, columns } = r else {
18399 panic!()
18400 };
18401 assert_eq!(columns.len(), 6);
18403 assert_eq!(columns[0].name, "table_name");
18404 assert_eq!(columns[4].name, "histogram_bounds");
18405 assert_eq!(columns[5].name, "cold_row_count");
18406 assert_eq!(rows.len(), 2, "one row per column of t");
18407 match (&rows[0].values[0], &rows[0].values[1]) {
18409 (Value::Text(t), Value::Text(c)) => {
18410 assert_eq!(t, "t");
18411 assert_eq!(c, "id");
18413 }
18414 _ => panic!(),
18415 }
18416 }
18417
18418 #[test]
18419 fn analyze_skips_vector_columns() {
18420 let mut e = Engine::new();
18423 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18424 .unwrap();
18425 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
18426 e.execute("ANALYZE t").unwrap();
18427 assert!(e.statistics().get("t", "id").is_some());
18428 assert!(e.statistics().get("t", "v").is_none());
18429 }
18430
18431 #[test]
18432 fn statistics_persist_across_envelope_v5_round_trip() {
18433 let mut e = Engine::new();
18434 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18435 for i in 0..20 {
18436 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18437 .unwrap();
18438 }
18439 e.execute("ANALYZE").unwrap();
18440 let snap = e.snapshot();
18441 let e2 = Engine::restore_envelope(&snap).unwrap();
18442 let s = e2.statistics().get("t", "id").unwrap();
18443 assert_eq!(s.n_distinct, 20);
18444 }
18445
18446 #[test]
18449 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
18450 let mut e = Engine::new();
18454 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18455 for i in 0..9 {
18456 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18457 .unwrap();
18458 }
18459 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
18460 e.execute("INSERT INTO t VALUES (9)").unwrap();
18461 let needs = e.tables_needing_analyze();
18462 assert_eq!(needs, alloc::vec!["t".to_string()]);
18463 }
18464
18465 #[test]
18466 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
18467 let mut e = Engine::new();
18473 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18474 for i in 0..1000 {
18475 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18476 .unwrap();
18477 }
18478 e.execute("ANALYZE t").unwrap();
18479 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
18480 for i in 1000..1050 {
18481 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18482 .unwrap();
18483 }
18484 assert!(
18485 e.tables_needing_analyze().is_empty(),
18486 "50 inserts < threshold of ~105"
18487 );
18488 for i in 1050..1200 {
18489 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18490 .unwrap();
18491 }
18492 assert_eq!(
18493 e.tables_needing_analyze(),
18494 alloc::vec!["t".to_string()],
18495 "200 inserts > 0.1 × 1200 threshold"
18496 );
18497 }
18498
18499 #[test]
18500 fn auto_analyze_threshold_resets_after_analyze() {
18501 let mut e = Engine::new();
18502 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18503 for i in 0..200 {
18504 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18505 .unwrap();
18506 }
18507 assert!(!e.tables_needing_analyze().is_empty());
18508 e.execute("ANALYZE").unwrap();
18509 assert!(
18510 e.tables_needing_analyze().is_empty(),
18511 "ANALYZE must reset the counter"
18512 );
18513 }
18514
18515 #[test]
18516 fn auto_analyze_threshold_tracks_updates_and_deletes() {
18517 let mut e = Engine::new();
18518 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18519 .unwrap();
18520 for i in 0..50 {
18521 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
18522 .unwrap();
18523 }
18524 e.execute("ANALYZE t").unwrap();
18525 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
18528 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
18529 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
18530 }
18531
18532 #[test]
18533 fn v4_envelope_loads_with_empty_statistics() {
18534 let mut e = Engine::new();
18538 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18539 .unwrap();
18540 let catalog = e.catalog.serialize();
18541 let users = crate::users::serialize_users(&e.users);
18542 let pubs = e.publications.serialize();
18543 let subs = e.subscriptions.serialize();
18544 let mut buf = Vec::new();
18545 buf.extend_from_slice(b"SPGENV01");
18546 buf.push(4u8);
18547 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18548 buf.extend_from_slice(&catalog);
18549 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18550 buf.extend_from_slice(&users);
18551 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18552 buf.extend_from_slice(&pubs);
18553 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
18554 buf.extend_from_slice(&subs);
18555 let crc = spg_crypto::crc32::crc32(&buf);
18556 buf.extend_from_slice(&crc.to_le_bytes());
18557 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
18558 assert!(e2.statistics().is_empty());
18559 }
18560
18561 #[test]
18562 fn v1_v2_envelope_loads_with_empty_publications() {
18563 let mut e = Engine::new();
18570 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18573 .unwrap();
18574
18575 let catalog = e.catalog.serialize();
18577 let users = crate::users::serialize_users(&e.users);
18578 let mut buf = Vec::new();
18579 buf.extend_from_slice(b"SPGENV01");
18580 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18582 buf.extend_from_slice(&catalog);
18583 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18584 buf.extend_from_slice(&users);
18585 let crc = spg_crypto::crc32::crc32(&buf);
18586 buf.extend_from_slice(&crc.to_le_bytes());
18587
18588 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
18589 assert!(e2.publications().is_empty());
18590 }
18591}