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};
41use spg_sql::parser::{self, ParseError};
42use spg_storage::{
43 Catalog, ColumnSchema, CompactReport, DataType, IndexKey, IndexKind, Row, StorageError, Table,
44 TableSchema, Value, VecEncoding,
45};
46
47use crate::eval::{EvalContext, EvalError};
48
49#[derive(Debug, Clone, PartialEq)]
51#[non_exhaustive]
52pub enum QueryResult {
53 CommandOk {
62 affected: usize,
63 modified_catalog: bool,
64 },
65 Rows {
67 columns: Vec<ColumnSchema>,
68 rows: Vec<Row>,
69 },
70}
71
72#[derive(Debug, Clone, PartialEq)]
78#[non_exhaustive]
79pub enum EngineError {
80 Parse(ParseError),
81 Storage(StorageError),
82 Eval(EvalError),
83 Unsupported(String),
85 TransactionAlreadyOpen,
87 NoActiveTransaction,
89 WriteRequired,
94 RowLimitExceeded(usize),
97 Cancelled,
103}
104
105impl fmt::Display for EngineError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 Self::Parse(e) => write!(f, "parse: {e}"),
109 Self::Storage(e) => write!(f, "storage: {e}"),
110 Self::Eval(e) => write!(f, "eval: {e}"),
111 Self::Unsupported(s) => write!(f, "unsupported: {s}"),
112 Self::TransactionAlreadyOpen => f.write_str("a transaction is already open"),
113 Self::NoActiveTransaction => f.write_str("no active transaction"),
114 Self::WriteRequired => {
115 f.write_str("statement requires a write lock (use execute, not execute_readonly)")
116 }
117 Self::RowLimitExceeded(n) => {
118 write!(f, "query exceeded max_query_rows={n}")
119 }
120 Self::Cancelled => f.write_str("query cancelled (timeout or client request)"),
121 }
122 }
123}
124
125impl From<ParseError> for EngineError {
126 fn from(e: ParseError) -> Self {
127 Self::Parse(e)
128 }
129}
130impl From<StorageError> for EngineError {
131 fn from(e: StorageError) -> Self {
132 Self::Storage(e)
133 }
134}
135impl From<EvalError> for EngineError {
136 fn from(e: EvalError) -> Self {
137 Self::Eval(e)
138 }
139}
140
141pub type ClockFn = fn() -> i64;
150
151pub type SaltFn = fn() -> [u8; 16];
158
159#[derive(Debug, Clone, Copy)]
170pub struct CancelToken<'a> {
171 flag: Option<&'a core::sync::atomic::AtomicBool>,
172}
173
174impl<'a> CancelToken<'a> {
175 #[must_use]
176 pub const fn none() -> Self {
177 Self { flag: None }
178 }
179
180 #[must_use]
181 pub const fn from_flag(f: &'a core::sync::atomic::AtomicBool) -> Self {
182 Self { flag: Some(f) }
183 }
184
185 #[must_use]
186 pub fn is_cancelled(self) -> bool {
187 self.flag
188 .is_some_and(|f| f.load(core::sync::atomic::Ordering::Relaxed))
189 }
190
191 #[inline]
195 pub fn check(self) -> Result<(), EngineError> {
196 if self.is_cancelled() {
197 Err(EngineError::Cancelled)
198 } else {
199 Ok(())
200 }
201 }
202}
203
204const ENVELOPE_MAGIC: &[u8; 8] = b"SPGENV01";
262const ENVELOPE_VERSION_V1: u8 = 1;
263const ENVELOPE_VERSION_V2: u8 = 2;
264const ENVELOPE_VERSION_V3: u8 = 3;
265const ENVELOPE_VERSION_V4: u8 = 4;
266const ENVELOPE_VERSION_V5: u8 = 5;
267
268fn build_envelope(catalog: &[u8], users: &[u8], pubs: &[u8], subs: &[u8], stats: &[u8]) -> Vec<u8> {
269 let mut out = Vec::with_capacity(
270 8 + 1
271 + 4
272 + catalog.len()
273 + 4
274 + users.len()
275 + 4
276 + pubs.len()
277 + 4
278 + subs.len()
279 + 4
280 + stats.len()
281 + 4,
282 );
283 out.extend_from_slice(ENVELOPE_MAGIC);
284 out.push(ENVELOPE_VERSION_V5);
285 out.extend_from_slice(
286 &u32::try_from(catalog.len())
287 .expect("≤ 4G catalog")
288 .to_le_bytes(),
289 );
290 out.extend_from_slice(catalog);
291 out.extend_from_slice(
292 &u32::try_from(users.len())
293 .expect("≤ 4G users")
294 .to_le_bytes(),
295 );
296 out.extend_from_slice(users);
297 out.extend_from_slice(
298 &u32::try_from(pubs.len())
299 .expect("≤ 4G publications")
300 .to_le_bytes(),
301 );
302 out.extend_from_slice(pubs);
303 out.extend_from_slice(
304 &u32::try_from(subs.len())
305 .expect("≤ 4G subscriptions")
306 .to_le_bytes(),
307 );
308 out.extend_from_slice(subs);
309 out.extend_from_slice(
310 &u32::try_from(stats.len())
311 .expect("≤ 4G statistics")
312 .to_le_bytes(),
313 );
314 out.extend_from_slice(stats);
315 let crc = spg_crypto::crc32::crc32(&out);
316 out.extend_from_slice(&crc.to_le_bytes());
317 out
318}
319
320enum EnvelopeParse<'a> {
327 Bare,
328 Pair {
329 catalog: &'a [u8],
330 users: &'a [u8],
331 publications: Option<&'a [u8]>,
332 subscriptions: Option<&'a [u8]>,
333 statistics: Option<&'a [u8]>,
334 },
335 CrcMismatch {
336 expected: u32,
337 computed: u32,
338 },
339}
340
341fn split_envelope(buf: &[u8]) -> EnvelopeParse<'_> {
346 if buf.len() < 8 + 1 + 4 || &buf[..8] != ENVELOPE_MAGIC {
347 return EnvelopeParse::Bare;
348 }
349 let version = buf[8];
350 if !matches!(
351 version,
352 ENVELOPE_VERSION_V1
353 | ENVELOPE_VERSION_V2
354 | ENVELOPE_VERSION_V3
355 | ENVELOPE_VERSION_V4
356 | ENVELOPE_VERSION_V5
357 ) {
358 return EnvelopeParse::Bare;
359 }
360 let mut p = 9usize;
361 let Some(cat_len_bytes) = buf.get(p..p + 4) else {
362 return EnvelopeParse::Bare;
363 };
364 let Ok(cat_len_arr) = cat_len_bytes.try_into() else {
365 return EnvelopeParse::Bare;
366 };
367 let cat_len = u32::from_le_bytes(cat_len_arr) as usize;
368 p += 4;
369 if p + cat_len + 4 > buf.len() {
370 return EnvelopeParse::Bare;
371 }
372 let catalog = &buf[p..p + cat_len];
373 p += cat_len;
374 let Some(user_len_bytes) = buf.get(p..p + 4) else {
375 return EnvelopeParse::Bare;
376 };
377 let Ok(user_len_arr) = user_len_bytes.try_into() else {
378 return EnvelopeParse::Bare;
379 };
380 let user_len = u32::from_le_bytes(user_len_arr) as usize;
381 p += 4;
382 if p + user_len > buf.len() {
383 return EnvelopeParse::Bare;
384 }
385 let users = &buf[p..p + user_len];
386 p += user_len;
387 let publications = if matches!(
388 version,
389 ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
390 ) {
391 let Some(pubs_len_bytes) = buf.get(p..p + 4) else {
393 return EnvelopeParse::Bare;
394 };
395 let Ok(pubs_len_arr) = pubs_len_bytes.try_into() else {
396 return EnvelopeParse::Bare;
397 };
398 let pubs_len = u32::from_le_bytes(pubs_len_arr) as usize;
399 p += 4;
400 if p + pubs_len > buf.len() {
401 return EnvelopeParse::Bare;
402 }
403 let pubs_slice = &buf[p..p + pubs_len];
404 p += pubs_len;
405 Some(pubs_slice)
406 } else {
407 None
408 };
409 let subscriptions = if matches!(version, ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5) {
410 let Some(subs_len_bytes) = buf.get(p..p + 4) else {
412 return EnvelopeParse::Bare;
413 };
414 let Ok(subs_len_arr) = subs_len_bytes.try_into() else {
415 return EnvelopeParse::Bare;
416 };
417 let subs_len = u32::from_le_bytes(subs_len_arr) as usize;
418 p += 4;
419 if p + subs_len > buf.len() {
420 return EnvelopeParse::Bare;
421 }
422 let subs_slice = &buf[p..p + subs_len];
423 p += subs_len;
424 Some(subs_slice)
425 } else {
426 None
427 };
428 let statistics = if version == ENVELOPE_VERSION_V5 {
429 let Some(stats_len_bytes) = buf.get(p..p + 4) else {
431 return EnvelopeParse::Bare;
432 };
433 let Ok(stats_len_arr) = stats_len_bytes.try_into() else {
434 return EnvelopeParse::Bare;
435 };
436 let stats_len = u32::from_le_bytes(stats_len_arr) as usize;
437 p += 4;
438 if p + stats_len > buf.len() {
439 return EnvelopeParse::Bare;
440 }
441 let stats_slice = &buf[p..p + stats_len];
442 p += stats_len;
443 Some(stats_slice)
444 } else {
445 None
446 };
447 if matches!(
448 version,
449 ENVELOPE_VERSION_V2 | ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
450 ) {
451 if p + 4 != buf.len() {
452 return EnvelopeParse::Bare;
453 }
454 let Ok(crc_arr) = buf[p..p + 4].try_into() else {
455 return EnvelopeParse::Bare;
456 };
457 let expected = u32::from_le_bytes(crc_arr);
458 let computed = spg_crypto::crc32::crc32(&buf[..p]);
459 if expected != computed {
460 return EnvelopeParse::CrcMismatch { expected, computed };
461 }
462 } else if p != buf.len() {
463 return EnvelopeParse::Bare;
465 }
466 EnvelopeParse::Pair {
467 catalog,
468 users,
469 publications,
470 subscriptions,
471 statistics,
472 }
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
485pub struct TxId(pub u64);
486
487pub const IMPLICIT_TX: TxId = TxId(0);
490
491pub const COMPACTION_TARGET_DEFAULT_BYTES: u64 = 4 * 1024 * 1024;
497
498#[derive(Debug, Default, Clone)]
503struct TxState {
504 catalog: Catalog,
509 savepoints: Vec<(String, Catalog)>,
515}
516
517#[derive(Debug, Clone)]
531pub struct CatalogSnapshot {
532 catalog: Catalog,
533 statistics: statistics::Statistics,
534 clock: Option<ClockFn>,
535 max_query_rows: Option<usize>,
536}
537
538#[derive(Debug, Default)]
539pub struct Engine {
540 catalog: Catalog,
543 tx_catalogs: BTreeMap<TxId, TxState>,
548 current_tx: Option<TxId>,
553 next_tx_id: u64,
556 clock: Option<ClockFn>,
559 salt_fn: Option<SaltFn>,
563 max_query_rows: Option<usize>,
569 users: UserStore,
575 publications: publications::Publications,
579 subscriptions: subscriptions::Subscriptions,
583 statistics: statistics::Statistics,
587 plan_cache: plan_cache::PlanCache,
591 query_stats: query_stats::QueryStats,
595 activity_provider: Option<ActivityProvider>,
602 audit_chain_provider: Option<AuditChainProvider>,
607 audit_verifier: Option<AuditVerifier>,
608 slow_query_threshold_us: Option<u64>,
614 slow_query_logger: Option<SlowQueryLogger>,
615 session_params: BTreeMap<String, String>,
622 trigger_recursion_depth: u32,
630}
631
632const MAX_TRIGGER_RECURSION: u32 = 16;
636
637pub type SlowQueryLogger = fn(&str, u64);
641
642fn render_create_table(name: &str, columns: &[ColumnSchema]) -> String {
647 let mut out = alloc::format!("CREATE TABLE {name} (");
648 for (i, col) in columns.iter().enumerate() {
649 if i > 0 {
650 out.push_str(", ");
651 }
652 out.push_str(&col.name);
653 out.push(' ');
654 out.push_str(&render_data_type(col.ty));
655 if !col.nullable {
656 out.push_str(" NOT NULL");
657 }
658 if col.auto_increment {
659 out.push_str(" AUTO_INCREMENT");
660 }
661 }
662 out.push(')');
663 out
664}
665
666fn render_data_type(ty: DataType) -> String {
667 match ty {
668 DataType::SmallInt => "SMALLINT".into(),
669 DataType::Int => "INT".into(),
670 DataType::BigInt => "BIGINT".into(),
671 DataType::Float => "FLOAT".into(),
672 DataType::Text => "TEXT".into(),
673 DataType::Varchar(n) => alloc::format!("VARCHAR({n})"),
674 DataType::Char(n) => alloc::format!("CHAR({n})"),
675 DataType::Bool => "BOOL".into(),
676 DataType::Vector { dim, encoding } => match encoding {
677 spg_storage::VecEncoding::F32 => alloc::format!("VECTOR({dim})"),
678 spg_storage::VecEncoding::Sq8 => alloc::format!("VECTOR({dim}) USING SQ8"),
679 spg_storage::VecEncoding::F16 => alloc::format!("VECTOR({dim}) USING HALF"),
680 },
681 DataType::Numeric { precision, scale } => {
682 alloc::format!("NUMERIC({precision},{scale})")
683 }
684 DataType::Date => "DATE".into(),
685 DataType::Timestamp => "TIMESTAMP".into(),
686 DataType::Interval => "INTERVAL".into(),
687 DataType::Json => "JSON".into(),
688 DataType::Jsonb => "JSONB".into(),
689 DataType::Timestamptz => "TIMESTAMPTZ".into(),
690 DataType::Bytes => "BYTEA".into(),
691 DataType::TextArray => "TEXT[]".into(),
692 DataType::IntArray => "INT[]".into(),
693 DataType::BigIntArray => "BIGINT[]".into(),
694 DataType::TsVector => "TSVECTOR".into(),
695 DataType::TsQuery => "TSQUERY".into(),
696 }
697}
698
699#[derive(Debug, Clone)]
703pub struct ActivityRow {
704 pub pid: u32,
705 pub user: String,
706 pub started_at_us: i64,
707 pub current_sql: String,
708 pub wait_event: String,
709 pub elapsed_us: i64,
710 pub in_transaction: bool,
711}
712
713pub type ActivityProvider = fn() -> Vec<ActivityRow>;
716
717#[derive(Debug, Clone)]
720pub struct AuditRow {
721 pub seq: i64,
722 pub ts_ms: i64,
723 pub prev_hash_hex: String,
724 pub entry_hash_hex: String,
725 pub sql: String,
726}
727
728pub type AuditChainProvider = fn() -> Vec<AuditRow>;
733pub type AuditVerifier = fn() -> (i64, i64);
734
735impl Engine {
736 pub fn new() -> Self {
737 Self {
738 catalog: Catalog::new(),
739 tx_catalogs: BTreeMap::new(),
740 current_tx: None,
741 next_tx_id: 1,
742 clock: None,
743 salt_fn: None,
744 max_query_rows: None,
745 users: UserStore::new(),
746 publications: publications::Publications::new(),
747 subscriptions: subscriptions::Subscriptions::new(),
748 statistics: statistics::Statistics::new(),
749 plan_cache: plan_cache::PlanCache::new(),
750 query_stats: query_stats::QueryStats::new(),
751 activity_provider: None,
752 audit_chain_provider: None,
753 audit_verifier: None,
754 slow_query_threshold_us: None,
755 slow_query_logger: None,
756 session_params: BTreeMap::new(),
757 trigger_recursion_depth: 0,
758 }
759 }
760
761 #[must_use]
770 pub fn clone_snapshot(&self) -> CatalogSnapshot {
771 CatalogSnapshot {
772 catalog: self.active_catalog().clone(),
773 statistics: self.statistics.clone(),
774 clock: self.clock,
775 max_query_rows: self.max_query_rows,
776 }
777 }
778
779 pub fn execute_readonly_on_snapshot(
787 snapshot: &CatalogSnapshot,
788 sql: &str,
789 ) -> Result<QueryResult, EngineError> {
790 Self::execute_readonly_on_snapshot_with_cancel(snapshot, sql, CancelToken::none())
791 }
792
793 pub fn execute_readonly_on_snapshot_with_cancel(
800 snapshot: &CatalogSnapshot,
801 sql: &str,
802 cancel: CancelToken<'_>,
803 ) -> Result<QueryResult, EngineError> {
804 let transient = Engine {
805 catalog: snapshot.catalog.clone(),
806 statistics: snapshot.statistics.clone(),
807 clock: snapshot.clock,
808 max_query_rows: snapshot.max_query_rows,
809 ..Engine::default()
810 };
811 transient.execute_readonly_with_cancel(sql, cancel)
812 }
813
814 pub fn restore(catalog: Catalog) -> Self {
817 Self {
818 catalog,
819 tx_catalogs: BTreeMap::new(),
820 current_tx: None,
821 next_tx_id: 1,
822 clock: None,
823 salt_fn: None,
824 max_query_rows: None,
825 users: UserStore::new(),
826 publications: publications::Publications::new(),
827 subscriptions: subscriptions::Subscriptions::new(),
828 statistics: statistics::Statistics::new(),
829 plan_cache: plan_cache::PlanCache::new(),
830 query_stats: query_stats::QueryStats::new(),
831 activity_provider: None,
832 audit_chain_provider: None,
833 audit_verifier: None,
834 slow_query_threshold_us: None,
835 slow_query_logger: None,
836 session_params: BTreeMap::new(),
837 trigger_recursion_depth: 0,
838 }
839 }
840
841 pub fn restore_envelope(buf: &[u8]) -> Result<Self, EngineError> {
848 match split_envelope(buf) {
849 EnvelopeParse::Pair {
850 catalog: catalog_bytes,
851 users: user_bytes,
852 publications: pub_bytes,
853 subscriptions: sub_bytes,
854 statistics: stats_bytes,
855 } => {
856 let catalog = Catalog::deserialize(catalog_bytes).map_err(EngineError::Storage)?;
857 let users = users::deserialize_users(user_bytes)
858 .map_err(|e| EngineError::Unsupported(alloc::format!("users restore: {e}")))?;
859 let publications = match pub_bytes {
860 Some(b) => publications::Publications::deserialize(b).map_err(|e| {
861 EngineError::Unsupported(alloc::format!("publications restore: {e:?}"))
862 })?,
863 None => publications::Publications::new(),
864 };
865 let subscriptions = match sub_bytes {
866 Some(b) => subscriptions::Subscriptions::deserialize(b).map_err(|e| {
867 EngineError::Unsupported(alloc::format!("subscriptions restore: {e:?}"))
868 })?,
869 None => subscriptions::Subscriptions::new(),
870 };
871 let statistics = match stats_bytes {
872 Some(b) => statistics::Statistics::deserialize(b).map_err(|e| {
873 EngineError::Unsupported(alloc::format!("statistics restore: {e:?}"))
874 })?,
875 None => statistics::Statistics::new(),
876 };
877 Ok(Self {
878 catalog,
879 tx_catalogs: BTreeMap::new(),
880 current_tx: None,
881 next_tx_id: 1,
882 clock: None,
883 salt_fn: None,
884 max_query_rows: None,
885 users,
886 publications,
887 subscriptions,
888 statistics,
889 plan_cache: plan_cache::PlanCache::new(),
890 query_stats: query_stats::QueryStats::new(),
891 activity_provider: None,
892 audit_chain_provider: None,
893 audit_verifier: None,
894 slow_query_threshold_us: None,
895 slow_query_logger: None,
896 session_params: BTreeMap::new(),
897 trigger_recursion_depth: 0,
898 })
899 }
900 EnvelopeParse::CrcMismatch { expected, computed } => {
901 Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
902 "snapshot envelope CRC32 mismatch (expected={expected:#010x}, computed={computed:#010x})"
903 ))))
904 }
905 EnvelopeParse::Bare => {
906 let catalog = Catalog::deserialize(buf).map_err(EngineError::Storage)?;
907 Ok(Self::restore(catalog))
908 }
909 }
910 }
911
912 pub const fn users(&self) -> &UserStore {
913 &self.users
914 }
915
916 pub fn create_user(
920 &mut self,
921 name: &str,
922 password: &str,
923 role: Role,
924 salt: [u8; 16],
925 ) -> Result<(), UserError> {
926 self.users.create(name, password, role, salt)?;
927 let scram_salt = self.salt_fn.map_or_else(
933 || {
934 let mut s = [0u8; users::SCRAM_SALT_LEN];
935 let digest = spg_crypto::hash(name.as_bytes());
936 s.copy_from_slice(&digest[16..32]);
939 s
940 },
941 |f| f(),
942 );
943 self.users
944 .enable_scram(name, password, scram_salt, users::SCRAM_DEFAULT_ITERS)?;
945 Ok(())
946 }
947
948 pub fn drop_user(&mut self, name: &str) -> Result<(), UserError> {
949 self.users.drop(name)
950 }
951
952 pub fn verify_user(&self, name: &str, password: &str) -> Option<Role> {
953 self.users.verify(name, password)
954 }
955
956 #[must_use]
959 pub const fn with_clock(mut self, clock: ClockFn) -> Self {
960 self.clock = Some(clock);
961 self
962 }
963
964 #[must_use]
967 pub const fn with_salt_fn(mut self, f: SaltFn) -> Self {
968 self.salt_fn = Some(f);
969 self
970 }
971
972 #[must_use]
978 pub const fn with_max_query_rows(mut self, n: usize) -> Self {
979 self.max_query_rows = Some(n);
980 self
981 }
982
983 pub const fn catalog(&self) -> &Catalog {
987 &self.catalog
988 }
989
990 pub fn snapshot(&self) -> Vec<u8> {
998 if self.users.is_empty()
999 && self.publications.is_empty()
1000 && self.subscriptions.is_empty()
1001 && self.statistics.is_empty()
1002 {
1003 self.catalog.serialize()
1004 } else {
1005 build_envelope(
1006 &self.catalog.serialize(),
1007 &users::serialize_users(&self.users),
1008 &self.publications.serialize(),
1009 &self.subscriptions.serialize(),
1010 &self.statistics.serialize(),
1011 )
1012 }
1013 }
1014
1015 pub fn in_transaction(&self) -> bool {
1020 !self.tx_catalogs.is_empty()
1021 }
1022
1023 pub fn alloc_tx_id(&mut self) -> TxId {
1032 let id = TxId(self.next_tx_id);
1033 self.next_tx_id = self.next_tx_id.saturating_add(1);
1034 id
1035 }
1036
1037 pub fn replace_catalog(&mut self, catalog: Catalog) {
1057 self.catalog = catalog;
1058 }
1059
1060 pub fn freeze_oldest_to_cold(
1068 &mut self,
1069 table_name: &str,
1070 index_name: &str,
1071 max_rows: usize,
1072 ) -> Result<spg_storage::FreezeReport, EngineError> {
1073 let report = self
1074 .active_catalog_mut()
1075 .freeze_oldest_to_cold(table_name, index_name, max_rows)
1076 .map_err(EngineError::Storage)?;
1077 if let Some(t) = self.active_catalog_mut().get_mut(table_name) {
1078 t.mark_cold_row_count_stale();
1079 }
1080 Ok(report)
1081 }
1082
1083 pub fn receive_cold_segment(
1097 &mut self,
1098 segment_id: u32,
1099 bytes: Vec<u8>,
1100 ) -> Result<(), EngineError> {
1101 let mut new_cat = self.catalog.clone();
1102 match new_cat.load_segment_bytes_at(segment_id, bytes) {
1103 Ok(()) => {
1104 self.replace_catalog(new_cat);
1105 Ok(())
1106 }
1107 Err(StorageError::Corrupt(msg)) if msg.contains("already occupied") => Ok(()),
1108 Err(e) => Err(EngineError::Storage(e)),
1109 }
1110 }
1111
1112 pub fn compact_cold_segments_with_target(
1126 &mut self,
1127 target_segment_bytes: u64,
1128 ) -> Result<Vec<(String, String, CompactReport)>, EngineError> {
1129 let table_names = self.active_catalog().table_names();
1130 let mut reports: Vec<(String, String, CompactReport)> = Vec::new();
1131 for tname in table_names {
1132 if is_internal_table_name(&tname) {
1133 continue;
1134 }
1135 let idx_names: Vec<String> = {
1136 let Some(t) = self.active_catalog().get(&tname) else {
1137 continue;
1138 };
1139 t.indices()
1140 .iter()
1141 .filter(|i| matches!(i.kind, IndexKind::BTree(_)))
1142 .map(|i| i.name.clone())
1143 .collect()
1144 };
1145 for iname in idx_names {
1146 let report = self
1147 .active_catalog_mut()
1148 .compact_cold_segments(&tname, &iname, target_segment_bytes)
1149 .map_err(EngineError::Storage)?;
1150 if report.merged_segment_id.is_some() {
1151 if let Some(t) = self.active_catalog_mut().get_mut(&tname) {
1152 t.mark_cold_row_count_stale();
1153 }
1154 reports.push((tname.clone(), iname, report));
1155 }
1156 }
1157 }
1158 Ok(reports)
1159 }
1160
1161 fn active_catalog(&self) -> &Catalog {
1162 match self.current_tx {
1163 Some(t) => self
1164 .tx_catalogs
1165 .get(&t)
1166 .map_or(&self.catalog, |s| &s.catalog),
1167 None => &self.catalog,
1168 }
1169 }
1170
1171 fn snapshot_row_triggers(
1179 &self,
1180 table: &str,
1181 event: &str,
1182 timing: &str,
1183 ) -> Vec<spg_storage::FunctionDef> {
1184 let cat = self.active_catalog();
1185 cat.triggers()
1186 .iter()
1187 .filter(|t| {
1188 t.table == table
1189 && t.timing.eq_ignore_ascii_case(timing)
1190 && t.for_each.eq_ignore_ascii_case("row")
1191 && t.events.iter().any(|e| e.eq_ignore_ascii_case(event))
1192 })
1193 .filter_map(|t| cat.functions().get(&t.function).cloned())
1194 .collect()
1195 }
1196
1197 fn snapshot_update_row_triggers(
1202 &self,
1203 table: &str,
1204 timing: &str,
1205 ) -> Vec<(spg_storage::FunctionDef, Vec<String>)> {
1206 let cat = self.active_catalog();
1207 cat.triggers()
1208 .iter()
1209 .filter(|t| {
1210 t.table == table
1211 && t.timing.eq_ignore_ascii_case(timing)
1212 && t.for_each.eq_ignore_ascii_case("row")
1213 && t.events.iter().any(|e| e.eq_ignore_ascii_case("UPDATE"))
1214 })
1215 .filter_map(|t| {
1216 cat.functions()
1217 .get(&t.function)
1218 .cloned()
1219 .map(|fd| (fd, t.update_columns.clone()))
1220 })
1221 .collect()
1222 }
1223
1224 fn execute_deferred_trigger_stmts(
1233 &mut self,
1234 deferred: Vec<triggers::DeferredEmbeddedStmt>,
1235 cancel: CancelToken<'_>,
1236 ) -> Result<(), EngineError> {
1237 for d in deferred {
1238 if self.trigger_recursion_depth >= MAX_TRIGGER_RECURSION {
1239 return Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1240 "trigger embedded SQL recursion depth {} exceeded (trigger function \
1241 {:?} would push past the {} cap — check for trigger cycles)",
1242 self.trigger_recursion_depth,
1243 d.function,
1244 MAX_TRIGGER_RECURSION,
1245 ))));
1246 }
1247 self.trigger_recursion_depth += 1;
1248 let res = self.execute_stmt_with_cancel(d.stmt, cancel);
1249 self.trigger_recursion_depth -= 1;
1250 res?;
1251 }
1252 Ok(())
1253 }
1254
1255 fn active_catalog_mut(&mut self) -> &mut Catalog {
1256 let tx = self.current_tx;
1257 match tx {
1258 Some(t) => match self.tx_catalogs.get_mut(&t) {
1259 Some(s) => &mut s.catalog,
1260 None => &mut self.catalog,
1261 },
1262 None => &mut self.catalog,
1263 }
1264 }
1265
1266 pub fn execute_readonly(&self, sql: &str) -> Result<QueryResult, EngineError> {
1278 self.execute_readonly_with_cancel(sql, CancelToken::none())
1279 }
1280
1281 pub fn execute_readonly_with_cancel(
1287 &self,
1288 sql: &str,
1289 cancel: CancelToken<'_>,
1290 ) -> Result<QueryResult, EngineError> {
1291 cancel.check()?;
1292 let mut stmt = parser::parse_statement(sql)?;
1293 let now_micros = self.clock.map(|f| f());
1294 rewrite_clock_calls(&mut stmt, now_micros);
1295 if let Statement::Select(s) = &mut stmt {
1296 resolve_order_by_position(s);
1297 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1299 }
1300 let result = match stmt {
1301 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1302 Statement::ShowTables => Ok(self.exec_show_tables()),
1303 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1304 Statement::ShowUsers => Ok(self.exec_show_users()),
1305 Statement::ShowPublications => Ok(self.exec_show_publications()),
1306 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1307 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1308 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1309 )),
1310 Statement::Explain(e) => self.exec_explain(&e, cancel),
1311 _ => Err(EngineError::WriteRequired),
1312 };
1313 self.enforce_row_limit(result)
1314 }
1315
1316 fn enforce_row_limit(
1320 &self,
1321 result: Result<QueryResult, EngineError>,
1322 ) -> Result<QueryResult, EngineError> {
1323 if let (Ok(QueryResult::Rows { rows, .. }), Some(cap)) = (&result, self.max_query_rows)
1324 && rows.len() > cap
1325 {
1326 return Err(EngineError::RowLimitExceeded(cap));
1327 }
1328 result
1329 }
1330
1331 pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError> {
1332 self.execute_in_with_cancel(sql, IMPLICIT_TX, CancelToken::none())
1333 }
1334
1335 pub fn execute_with_cancel(
1340 &mut self,
1341 sql: &str,
1342 cancel: CancelToken<'_>,
1343 ) -> Result<QueryResult, EngineError> {
1344 self.execute_in_with_cancel(sql, IMPLICIT_TX, cancel)
1345 }
1346
1347 pub fn execute_in(&mut self, sql: &str, tx_id: TxId) -> Result<QueryResult, EngineError> {
1354 self.execute_in_with_cancel(sql, tx_id, CancelToken::none())
1355 }
1356
1357 pub fn execute_in_with_cancel(
1363 &mut self,
1364 sql: &str,
1365 tx_id: TxId,
1366 cancel: CancelToken<'_>,
1367 ) -> Result<QueryResult, EngineError> {
1368 let saved = self.current_tx;
1369 self.current_tx = Some(tx_id);
1370 let result = self.execute_inner_with_cancel(sql, cancel);
1371 self.current_tx = saved;
1372 result
1373 }
1374
1375 pub fn prepare(&self, sql: &str) -> Result<Statement, ParseError> {
1387 let mut stmt = parser::parse_statement(sql)?;
1388 let now_micros = self.clock.map(|f| f());
1389 rewrite_clock_calls(&mut stmt, now_micros);
1390 if let Statement::Select(s) = &mut stmt {
1391 expand_group_by_all(s);
1395 resolve_order_by_position(s);
1396 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1399 }
1400 Ok(stmt)
1401 }
1402
1403 pub fn prepare_cached(&mut self, sql: &str) -> Result<Statement, ParseError> {
1415 let current_version = self.statistics.version();
1418 if let Some(plan) = self.plan_cache.get(sql) {
1419 if plan.statistics_version == current_version {
1420 return Ok(plan.stmt.clone());
1421 }
1422 }
1424 self.plan_cache.evict(sql);
1425 let stmt = self.prepare(sql)?;
1426 let source_tables = plan_cache::collect_source_tables(&stmt);
1427 let plan = plan_cache::PreparedPlan {
1428 stmt: stmt.clone(),
1429 statistics_version: current_version,
1430 source_tables,
1431 describe_columns: alloc::vec::Vec::new(),
1432 };
1433 self.plan_cache.insert(String::from(sql), plan);
1434 Ok(stmt)
1435 }
1436
1437 pub fn plan_cache(&self) -> &plan_cache::PlanCache {
1439 &self.plan_cache
1440 }
1441
1442 pub fn plan_cache_mut(&mut self) -> &mut plan_cache::PlanCache {
1444 &mut self.plan_cache
1445 }
1446
1447 pub fn describe_prepared(&self, stmt: &Statement) -> (Vec<u32>, Vec<ColumnSchema>) {
1453 describe::describe_prepared(stmt, self.active_catalog())
1454 }
1455
1456 pub fn execute_prepared(
1466 &mut self,
1467 mut stmt: Statement,
1468 params: &[Value],
1469 ) -> Result<QueryResult, EngineError> {
1470 substitute_placeholders(&mut stmt, params)?;
1471 self.execute_stmt_with_cancel(stmt, CancelToken::none())
1472 }
1473
1474 fn execute_inner_with_cancel(
1475 &mut self,
1476 sql: &str,
1477 cancel: CancelToken<'_>,
1478 ) -> Result<QueryResult, EngineError> {
1479 cancel.check()?;
1480 let stmt = self.prepare(sql)?;
1481 let start_us = self.clock.map(|f| f());
1485 let result = self.execute_stmt_with_cancel(stmt, cancel);
1486 if let (Some(t0), Ok(_)) = (start_us, &result) {
1487 let now = self.clock.map_or(t0, |f| f());
1488 let elapsed = now.saturating_sub(t0).max(0) as u64;
1489 self.query_stats.record(sql, elapsed, now as u64);
1490 if let (Some(threshold), Some(logger)) =
1493 (self.slow_query_threshold_us, self.slow_query_logger)
1494 && elapsed >= threshold
1495 {
1496 logger(sql, elapsed);
1497 }
1498 }
1499 result
1500 }
1501
1502 fn execute_stmt_with_cancel(
1503 &mut self,
1504 stmt: Statement,
1505 cancel: CancelToken<'_>,
1506 ) -> Result<QueryResult, EngineError> {
1507 cancel.check()?;
1508 let result = match stmt {
1509 Statement::CreateTable(s) => self.exec_create_table(s),
1510 Statement::CreateExtension(_) => Ok(QueryResult::CommandOk {
1514 affected: 0,
1515 modified_catalog: false,
1516 }),
1517 Statement::DoBlock => Ok(QueryResult::CommandOk {
1520 affected: 0,
1521 modified_catalog: false,
1522 }),
1523 Statement::CreateIndex(s) => self.exec_create_index(s),
1524 Statement::Insert(s) => self.exec_insert(s),
1525 Statement::Update(s) => self.exec_update_cancel(&s, cancel),
1526 Statement::Delete(s) => self.exec_delete_cancel(&s, cancel),
1527 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1528 Statement::Begin => self.exec_begin(),
1529 Statement::Commit => self.exec_commit(),
1530 Statement::Rollback => self.exec_rollback(),
1531 Statement::Savepoint(name) => self.exec_savepoint(name),
1532 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
1533 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
1534 Statement::ShowTables => Ok(self.exec_show_tables()),
1535 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1536 Statement::ShowUsers => Ok(self.exec_show_users()),
1537 Statement::ShowPublications => Ok(self.exec_show_publications()),
1538 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1539 Statement::CreateUser(s) => self.exec_create_user(&s),
1540 Statement::DropUser(name) => self.exec_drop_user(&name),
1541 Statement::Explain(e) => self.exec_explain(&e, cancel),
1542 Statement::AlterIndex(s) => self.exec_alter_index(s),
1543 Statement::AlterTable(s) => self.exec_alter_table(s),
1544 Statement::CreatePublication(s) => self.exec_create_publication(s),
1545 Statement::DropPublication(name) => self.exec_drop_publication(&name),
1546 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
1547 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
1548 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1555 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1556 )),
1557 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
1559 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
1561 Statement::SetParameter { name, value } => {
1566 self.set_session_param(name, value);
1567 Ok(QueryResult::CommandOk {
1568 affected: 0,
1569 modified_catalog: false,
1570 })
1571 }
1572 Statement::CreateFunction(s) => self.exec_create_function(s),
1576 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
1577 Statement::DropTrigger {
1578 name,
1579 table,
1580 if_exists,
1581 } => self.exec_drop_trigger(&name, &table, if_exists),
1582 Statement::DropFunction { name, if_exists } => {
1583 self.exec_drop_function(&name, if_exists)
1584 }
1585 Statement::ResetParameter(target) => {
1586 match target {
1587 None => self.session_params.clear(),
1588 Some(name) => {
1589 self.session_params.remove(&name.to_ascii_lowercase());
1590 }
1591 }
1592 Ok(QueryResult::CommandOk {
1593 affected: 0,
1594 modified_catalog: false,
1595 })
1596 }
1597 };
1598 self.enforce_row_limit(result)
1599 }
1600
1601 fn exec_create_publication(
1609 &mut self,
1610 s: CreatePublicationStatement,
1611 ) -> Result<QueryResult, EngineError> {
1612 self.publications
1618 .create(s.name, s.scope)
1619 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
1620 Ok(QueryResult::CommandOk {
1621 affected: 1,
1622 modified_catalog: true,
1623 })
1624 }
1625
1626 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
1631 let removed = self.publications.drop(name);
1632 Ok(QueryResult::CommandOk {
1633 affected: usize::from(removed),
1634 modified_catalog: removed,
1635 })
1636 }
1637
1638 pub const fn publications(&self) -> &publications::Publications {
1643 &self.publications
1644 }
1645
1646 fn exec_create_subscription(
1651 &mut self,
1652 s: CreateSubscriptionStatement,
1653 ) -> Result<QueryResult, EngineError> {
1654 let sub = subscriptions::Subscription {
1658 conn_str: s.conn_str,
1659 publications: s.publications,
1660 enabled: true,
1661 last_received_pos: 0,
1662 };
1663 self.subscriptions
1664 .create(s.name, sub)
1665 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
1666 Ok(QueryResult::CommandOk {
1667 affected: 1,
1668 modified_catalog: true,
1669 })
1670 }
1671
1672 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
1680 let removed = self.subscriptions.drop(name);
1681 Ok(QueryResult::CommandOk {
1682 affected: usize::from(removed),
1683 modified_catalog: removed,
1684 })
1685 }
1686
1687 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
1692 &self.subscriptions
1693 }
1694
1695 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
1701 self.subscriptions.update_last_received_pos(name, pos)
1702 }
1703
1704 fn exec_show_subscriptions(&self) -> QueryResult {
1710 let columns = alloc::vec![
1711 ColumnSchema::new("name", DataType::Text, false),
1712 ColumnSchema::new("conn_str", DataType::Text, false),
1713 ColumnSchema::new("publications", DataType::Text, false),
1714 ColumnSchema::new("enabled", DataType::Bool, false),
1715 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
1716 ];
1717 let rows: Vec<Row> = self
1718 .subscriptions
1719 .iter()
1720 .map(|(name, sub)| {
1721 Row::new(alloc::vec![
1722 Value::Text(name.clone()),
1723 Value::Text(sub.conn_str.clone()),
1724 Value::Text(sub.publications.join(", ")),
1725 Value::Bool(sub.enabled),
1726 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
1727 ])
1728 })
1729 .collect();
1730 QueryResult::Rows { columns, rows }
1731 }
1732
1733 fn exec_spg_statistic(&self) -> QueryResult {
1738 let columns = alloc::vec![
1739 ColumnSchema::new("table_name", DataType::Text, false),
1740 ColumnSchema::new("column_name", DataType::Text, false),
1741 ColumnSchema::new("null_frac", DataType::Float, false),
1742 ColumnSchema::new("n_distinct", DataType::BigInt, false),
1743 ColumnSchema::new("histogram_bounds", DataType::Text, false),
1744 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
1749 ];
1750 let rows: Vec<Row> = self
1751 .statistics
1752 .iter()
1753 .map(|((t, c), s)| {
1754 let cold = self
1755 .catalog
1756 .get(t)
1757 .map_or(0, |table| table.cold_row_count());
1758 Row::new(alloc::vec![
1759 Value::Text(t.clone()),
1760 Value::Text(c.clone()),
1761 Value::Float(f64::from(s.null_frac)),
1762 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
1763 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
1764 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
1765 ])
1766 })
1767 .collect();
1768 QueryResult::Rows { columns, rows }
1769 }
1770
1771 fn exec_spg_stat_replication(&self) -> QueryResult {
1778 let columns = alloc::vec![
1779 ColumnSchema::new("name", DataType::Text, false),
1780 ColumnSchema::new("conn_str", DataType::Text, false),
1781 ColumnSchema::new("publications", DataType::Text, false),
1782 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
1783 ColumnSchema::new("enabled", DataType::Bool, false),
1784 ];
1785 let rows: Vec<Row> = self
1786 .subscriptions
1787 .iter()
1788 .map(|(name, sub)| {
1789 Row::new(alloc::vec![
1790 Value::Text(name.clone()),
1791 Value::Text(sub.conn_str.clone()),
1792 Value::Text(sub.publications.join(",")),
1793 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
1794 Value::Bool(sub.enabled),
1795 ])
1796 })
1797 .collect();
1798 QueryResult::Rows { columns, rows }
1799 }
1800
1801 fn exec_spg_stat_segment(&self) -> QueryResult {
1813 let columns = alloc::vec![
1814 ColumnSchema::new("segment_id", DataType::BigInt, false),
1815 ColumnSchema::new("table_name", DataType::Text, false),
1816 ColumnSchema::new("num_rows", DataType::BigInt, false),
1817 ColumnSchema::new("num_pages", DataType::BigInt, false),
1818 ColumnSchema::new("total_bytes", DataType::BigInt, false),
1819 ];
1820 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
1826 for tname in self.catalog.table_names() {
1827 if is_internal_table_name(&tname) {
1828 continue;
1829 }
1830 let Some(t) = self.catalog.get(&tname) else {
1831 continue;
1832 };
1833 for idx in t.indices() {
1834 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
1835 for (_, locs) in map.iter() {
1836 for loc in locs {
1837 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
1838 segment_owners
1839 .entry(*segment_id)
1840 .or_insert_with(|| tname.clone());
1841 }
1842 }
1843 }
1844 }
1845 }
1846 }
1847 let rows: Vec<Row> = self
1848 .catalog
1849 .cold_segment_ids_global()
1850 .iter()
1851 .filter_map(|&id| {
1852 let seg = self.catalog.cold_segment(id)?;
1853 let meta = seg.meta();
1854 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
1855 Some(Row::new(alloc::vec![
1856 Value::BigInt(i64::from(id)),
1857 Value::Text(owner),
1858 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
1859 Value::BigInt(i64::from(meta.num_pages)),
1860 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
1861 ]))
1862 })
1863 .collect();
1864 QueryResult::Rows { columns, rows }
1865 }
1866
1867 fn exec_spg_stat_query(&self) -> QueryResult {
1873 let columns = alloc::vec![
1874 ColumnSchema::new("sql", DataType::Text, false),
1875 ColumnSchema::new("exec_count", DataType::BigInt, false),
1876 ColumnSchema::new("total_us", DataType::BigInt, false),
1877 ColumnSchema::new("mean_us", DataType::BigInt, false),
1878 ColumnSchema::new("max_us", DataType::BigInt, false),
1879 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
1880 ];
1881 let rows: Vec<Row> = self
1882 .query_stats
1883 .snapshot()
1884 .into_iter()
1885 .map(|(sql, s)| {
1886 let mean = if s.exec_count == 0 {
1887 0
1888 } else {
1889 s.total_us / s.exec_count
1890 };
1891 Row::new(alloc::vec![
1892 Value::Text(sql),
1893 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
1894 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
1895 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
1896 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
1897 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
1898 ])
1899 })
1900 .collect();
1901 QueryResult::Rows { columns, rows }
1902 }
1903
1904 #[must_use]
1909 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
1910 self.activity_provider = Some(f);
1911 self
1912 }
1913
1914 #[must_use]
1916 pub const fn with_audit_providers(
1917 mut self,
1918 chain: AuditChainProvider,
1919 verify: AuditVerifier,
1920 ) -> Self {
1921 self.audit_chain_provider = Some(chain);
1922 self.audit_verifier = Some(verify);
1923 self
1924 }
1925
1926 #[must_use]
1931 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
1932 self.slow_query_threshold_us = Some(threshold_us);
1933 self.slow_query_logger = Some(logger);
1934 self
1935 }
1936
1937 pub fn set_plan_cache_max(&mut self, n: usize) {
1941 self.plan_cache.set_max_entries(n);
1942 }
1943
1944 fn exec_spg_stat_activity(&self) -> QueryResult {
1949 let columns = alloc::vec![
1950 ColumnSchema::new("pid", DataType::Int, false),
1951 ColumnSchema::new("user", DataType::Text, false),
1952 ColumnSchema::new("started_at_us", DataType::BigInt, false),
1953 ColumnSchema::new("current_sql", DataType::Text, false),
1954 ColumnSchema::new("wait_event", DataType::Text, false),
1955 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
1956 ColumnSchema::new("in_transaction", DataType::Bool, false),
1957 ];
1958 let rows: Vec<Row> = self
1959 .activity_provider
1960 .map(|f| f())
1961 .unwrap_or_default()
1962 .into_iter()
1963 .map(|r| {
1964 Row::new(alloc::vec![
1965 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
1966 Value::Text(r.user),
1967 Value::BigInt(r.started_at_us),
1968 Value::Text(r.current_sql),
1969 Value::Text(r.wait_event),
1970 Value::BigInt(r.elapsed_us),
1971 Value::Bool(r.in_transaction),
1972 ])
1973 })
1974 .collect();
1975 QueryResult::Rows { columns, rows }
1976 }
1977
1978 fn exec_spg_table_ddl(&self) -> QueryResult {
1982 let columns = alloc::vec![
1983 ColumnSchema::new("table_name", DataType::Text, false),
1984 ColumnSchema::new("ddl", DataType::Text, false),
1985 ];
1986 let rows: Vec<Row> = self
1987 .catalog
1988 .table_names()
1989 .into_iter()
1990 .filter(|n| !is_internal_table_name(n))
1991 .filter_map(|name| {
1992 let table = self.catalog.get(&name)?;
1993 let ddl = render_create_table(&name, &table.schema().columns);
1994 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
1995 })
1996 .collect();
1997 QueryResult::Rows { columns, rows }
1998 }
1999
2000 fn exec_spg_role_ddl(&self) -> QueryResult {
2004 let columns = alloc::vec![
2005 ColumnSchema::new("role_name", DataType::Text, false),
2006 ColumnSchema::new("ddl", DataType::Text, false),
2007 ];
2008 let rows: Vec<Row> = self
2009 .users
2010 .iter()
2011 .map(|(name, rec)| {
2012 let ddl = alloc::format!(
2013 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2014 rec.role.as_str(),
2015 );
2016 Row::new(alloc::vec![
2017 Value::Text(String::from(name)),
2018 Value::Text(ddl)
2019 ])
2020 })
2021 .collect();
2022 QueryResult::Rows { columns, rows }
2023 }
2024
2025 fn exec_spg_database_ddl(&self) -> QueryResult {
2031 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2032 let mut out = String::new();
2033 for (name, rec) in self.users.iter() {
2034 out.push_str(&alloc::format!(
2035 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2036 rec.role.as_str(),
2037 ));
2038 }
2039 for name in self.catalog.table_names() {
2040 if is_internal_table_name(&name) {
2041 continue;
2042 }
2043 if let Some(table) = self.catalog.get(&name) {
2044 out.push_str(&render_create_table(&name, &table.schema().columns));
2045 out.push_str(";\n");
2046 }
2047 }
2048 QueryResult::Rows {
2049 columns,
2050 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2051 }
2052 }
2053
2054 fn exec_spg_audit_chain(&self) -> QueryResult {
2058 let columns = alloc::vec![
2059 ColumnSchema::new("seq", DataType::BigInt, false),
2060 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2061 ColumnSchema::new("prev_hash", DataType::Text, false),
2062 ColumnSchema::new("entry_hash", DataType::Text, false),
2063 ColumnSchema::new("sql", DataType::Text, false),
2064 ];
2065 let rows: Vec<Row> = self
2066 .audit_chain_provider
2067 .map(|f| f())
2068 .unwrap_or_default()
2069 .into_iter()
2070 .map(|r| {
2071 Row::new(alloc::vec![
2072 Value::BigInt(r.seq),
2073 Value::BigInt(r.ts_ms),
2074 Value::Text(r.prev_hash_hex),
2075 Value::Text(r.entry_hash_hex),
2076 Value::Text(r.sql),
2077 ])
2078 })
2079 .collect();
2080 QueryResult::Rows { columns, rows }
2081 }
2082
2083 fn exec_spg_audit_verify(&self) -> QueryResult {
2089 let columns = alloc::vec![
2090 ColumnSchema::new("verified_count", DataType::BigInt, false),
2091 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2092 ];
2093 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2094 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2095 QueryResult::Rows {
2096 columns,
2097 rows: alloc::vec![row],
2098 }
2099 }
2100
2101 pub fn query_stats(&self) -> &query_stats::QueryStats {
2103 &self.query_stats
2104 }
2105
2106 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2108 &mut self.query_stats
2109 }
2110
2111 pub const fn statistics(&self) -> &statistics::Statistics {
2115 &self.statistics
2116 }
2117
2118 pub fn tables_needing_analyze(&self) -> Vec<String> {
2131 const MIN_ROWS: u64 = 100;
2132 let mut out = Vec::new();
2133 for name in self.catalog.table_names() {
2134 if is_internal_table_name(&name) {
2135 continue;
2136 }
2137 let Some(table) = self.catalog.get(&name) else {
2138 continue;
2139 };
2140 let row_count = table.rows().len() as u64;
2141 let modified = self.statistics.modified_since_last_analyze(&name);
2142 let base = row_count.max(MIN_ROWS);
2147 let threshold = base.saturating_add(9) / 10;
2148 if modified >= threshold {
2149 out.push(name);
2150 }
2151 }
2152 out
2153 }
2154
2155 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2166 let names: Vec<String> = if let Some(name) = target {
2167 if self.catalog.get(name).is_none() {
2169 return Err(EngineError::Storage(StorageError::TableNotFound {
2170 name: name.to_string(),
2171 }));
2172 }
2173 alloc::vec![name.to_string()]
2174 } else {
2175 self.catalog
2176 .table_names()
2177 .into_iter()
2178 .filter(|n| !is_internal_table_name(n))
2179 .collect()
2180 };
2181 let mut analysed = 0usize;
2182 for table_name in &names {
2183 self.analyze_one_table(table_name)?;
2184 analysed += 1;
2185 }
2186 if analysed > 0 {
2192 self.statistics.bump_version();
2193 if target.is_some() {
2194 for t in &names {
2195 self.plan_cache.evict_referencing(t);
2196 }
2197 } else {
2198 self.plan_cache.clear();
2199 }
2200 }
2201 Ok(QueryResult::CommandOk {
2202 affected: analysed,
2203 modified_catalog: true,
2204 })
2205 }
2206
2207 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2220 let normalised = match value {
2221 spg_sql::ast::SetValue::String(s) => s,
2222 spg_sql::ast::SetValue::Ident(s) => s,
2223 spg_sql::ast::SetValue::Number(s) => s,
2224 spg_sql::ast::SetValue::Default => String::new(),
2225 };
2226 self.session_params
2227 .insert(name.to_ascii_lowercase(), normalised);
2228 }
2229
2230 #[must_use]
2234 pub fn session_param(&self, name: &str) -> Option<&str> {
2235 self.session_params
2236 .get(&name.to_ascii_lowercase())
2237 .map(String::as_str)
2238 }
2239
2240 fn ev_ctx<'a>(
2245 &'a self,
2246 columns: &'a [ColumnSchema],
2247 alias: Option<&'a str>,
2248 ) -> EvalContext<'a> {
2249 EvalContext::new(columns, alias)
2250 .with_default_text_search_config(self.session_param("default_text_search_config"))
2251 }
2252
2253 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2257 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2258 let reports = self.compact_cold_segments_with_target(target)?;
2259 let columns = alloc::vec![
2260 ColumnSchema::new("table_name", DataType::Text, false),
2261 ColumnSchema::new("index_name", DataType::Text, false),
2262 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2263 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2264 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2265 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2266 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2267 ];
2268 let rows: Vec<Row> = reports
2269 .into_iter()
2270 .map(|(tname, iname, report)| {
2271 Row::new(alloc::vec![
2272 Value::Text(tname),
2273 Value::Text(iname),
2274 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2275 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2276 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2277 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2278 Value::BigInt(
2279 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2280 ),
2281 ])
2282 })
2283 .collect();
2284 Ok(QueryResult::Rows { columns, rows })
2285 }
2286
2287 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2292 let table = self.catalog.get(table_name).ok_or_else(|| {
2293 EngineError::Storage(StorageError::TableNotFound {
2294 name: table_name.to_string(),
2295 })
2296 })?;
2297 let schema = table.schema().clone();
2298 let row_count = table.rows().len();
2299 self.statistics.clear_table(table_name);
2304 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2305 if matches!(col_schema.ty, DataType::Vector { .. }) {
2308 continue;
2309 }
2310 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2311 let mut nulls: u64 = 0;
2312 for row in table.rows() {
2313 match row.values.get(col_pos) {
2314 Some(Value::Null) | None => nulls += 1,
2315 Some(v) => non_null_values.push(v.clone()),
2316 }
2317 }
2318 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2323 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2324 let null_frac = if row_count == 0 {
2325 0.0
2326 } else {
2327 #[allow(clippy::cast_precision_loss)]
2328 let f = nulls as f32 / row_count as f32;
2329 f
2330 };
2331 let n_distinct = statistics::estimate_n_distinct(&non_null);
2332 let histogram_bounds = statistics::build_histogram(&non_null);
2333 self.statistics.set(
2334 table_name.to_string(),
2335 col_schema.name.clone(),
2336 statistics::ColumnStats {
2337 null_frac,
2338 n_distinct,
2339 histogram_bounds,
2340 },
2341 );
2342 }
2343 self.statistics.reset_modified(table_name);
2344 let cold_count = {
2350 let table = self
2351 .active_catalog()
2352 .get(table_name)
2353 .expect("table still present");
2354 table.count_cold_locators()
2355 };
2356 let table_mut = self
2357 .active_catalog_mut()
2358 .get_mut(table_name)
2359 .expect("table still present");
2360 table_mut.set_cold_row_count(cold_count);
2361 Ok(())
2362 }
2363
2364 fn exec_show_publications(&self) -> QueryResult {
2376 let columns = alloc::vec![
2377 ColumnSchema::new("name", DataType::Text, false),
2378 ColumnSchema::new("scope", DataType::Text, false),
2379 ColumnSchema::new("table_count", DataType::Int, true),
2380 ];
2381 let rows: Vec<Row> = self
2382 .publications
2383 .iter()
2384 .map(|(name, scope)| {
2385 let (scope_str, count_val) = match scope {
2386 spg_sql::ast::PublicationScope::AllTables => {
2387 ("FOR ALL TABLES".to_string(), Value::Null)
2388 }
2389 spg_sql::ast::PublicationScope::ForTables(ts) => (
2390 alloc::format!("FOR TABLE {}", ts.join(", ")),
2391 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2392 ),
2393 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2394 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
2395 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2396 ),
2397 };
2398 Row::new(alloc::vec![
2399 Value::Text(name.clone()),
2400 Value::Text(scope_str),
2401 count_val,
2402 ])
2403 })
2404 .collect();
2405 QueryResult::Rows { columns, rows }
2406 }
2407
2408 fn exec_show_users(&self) -> QueryResult {
2410 let columns = alloc::vec![
2411 ColumnSchema::new("name", DataType::Text, false),
2412 ColumnSchema::new("role", DataType::Text, false),
2413 ];
2414 let rows: Vec<Row> = self
2415 .users
2416 .iter()
2417 .map(|(name, rec)| {
2418 Row::new(alloc::vec![
2419 Value::Text(name.to_string()),
2420 Value::Text(rec.role.as_str().to_string()),
2421 ])
2422 })
2423 .collect();
2424 QueryResult::Rows { columns, rows }
2425 }
2426
2427 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
2428 if self.in_transaction() {
2429 return Err(EngineError::Unsupported(
2430 "CREATE USER is not allowed inside a transaction".into(),
2431 ));
2432 }
2433 let role = users::Role::parse(&s.role).ok_or_else(|| {
2434 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
2435 })?;
2436 let salt = self.salt_fn.map_or_else(
2440 || {
2441 let mut s_bytes = [0u8; 16];
2442 let digest = spg_crypto::hash(s.name.as_bytes());
2443 s_bytes.copy_from_slice(&digest[..16]);
2444 s_bytes
2445 },
2446 |f| f(),
2447 );
2448 self.users
2449 .create(&s.name, &s.password, role, salt)
2450 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
2451 Ok(QueryResult::CommandOk {
2452 affected: 1,
2453 modified_catalog: true,
2454 })
2455 }
2456
2457 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2458 if self.in_transaction() {
2459 return Err(EngineError::Unsupported(
2460 "DROP USER is not allowed inside a transaction".into(),
2461 ));
2462 }
2463 self.users
2464 .drop(name)
2465 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
2466 Ok(QueryResult::CommandOk {
2467 affected: 1,
2468 modified_catalog: true,
2469 })
2470 }
2471
2472 fn exec_create_function(
2478 &mut self,
2479 s: spg_sql::ast::CreateFunctionStatement,
2480 ) -> Result<QueryResult, EngineError> {
2481 let args_repr = render_function_args(&s.args);
2482 let returns = match &s.returns {
2483 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
2484 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
2485 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
2486 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
2487 };
2488 let body_text = match &s.body {
2489 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
2490 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
2491 };
2492 let def = spg_storage::FunctionDef {
2493 name: s.name.clone(),
2494 args_repr,
2495 returns,
2496 language: s.language.clone(),
2497 body: body_text,
2498 };
2499 self.active_catalog_mut()
2500 .create_function(def, s.or_replace)
2501 .map_err(EngineError::Storage)?;
2502 Ok(QueryResult::CommandOk {
2503 affected: 0,
2504 modified_catalog: true,
2505 })
2506 }
2507
2508 fn exec_create_trigger(
2513 &mut self,
2514 s: spg_sql::ast::CreateTriggerStatement,
2515 ) -> Result<QueryResult, EngineError> {
2516 let timing = match s.timing {
2517 spg_sql::ast::TriggerTiming::Before => "BEFORE",
2518 spg_sql::ast::TriggerTiming::After => "AFTER",
2519 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
2520 };
2521 let events: Vec<alloc::string::String> = s
2522 .events
2523 .iter()
2524 .map(|e| match e {
2525 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
2526 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
2527 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
2528 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
2529 })
2530 .collect();
2531 let for_each = match s.for_each {
2532 spg_sql::ast::TriggerForEach::Row => "ROW",
2533 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
2534 };
2535 let def = spg_storage::TriggerDef {
2536 name: s.name.clone(),
2537 table: s.table.clone(),
2538 timing: alloc::string::String::from(timing),
2539 events,
2540 for_each: alloc::string::String::from(for_each),
2541 function: s.function.clone(),
2542 update_columns: s.update_columns.clone(),
2543 };
2544 self.active_catalog_mut()
2545 .create_trigger(def, s.or_replace)
2546 .map_err(EngineError::Storage)?;
2547 Ok(QueryResult::CommandOk {
2548 affected: 0,
2549 modified_catalog: true,
2550 })
2551 }
2552
2553 fn exec_drop_trigger(
2554 &mut self,
2555 name: &str,
2556 table: &str,
2557 if_exists: bool,
2558 ) -> Result<QueryResult, EngineError> {
2559 let removed = self.active_catalog_mut().drop_trigger(name, table);
2560 if !removed && !if_exists {
2561 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
2562 alloc::format!("trigger {name:?} on {table:?} does not exist"),
2563 )));
2564 }
2565 Ok(QueryResult::CommandOk {
2566 affected: usize::from(removed),
2567 modified_catalog: removed,
2568 })
2569 }
2570
2571 fn exec_drop_function(
2572 &mut self,
2573 name: &str,
2574 if_exists: bool,
2575 ) -> Result<QueryResult, EngineError> {
2576 let removed = self.active_catalog_mut().drop_function(name);
2577 if !removed && !if_exists {
2578 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
2579 alloc::format!("function {name:?} does not exist"),
2580 )));
2581 }
2582 Ok(QueryResult::CommandOk {
2583 affected: usize::from(removed),
2584 modified_catalog: removed,
2585 })
2586 }
2587
2588 fn exec_update_cancel(
2595 &mut self,
2596 stmt: &spg_sql::ast::UpdateStatement,
2597 cancel: CancelToken<'_>,
2598 ) -> Result<QueryResult, EngineError> {
2599 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
2608 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
2609 let trigger_session_cfg: Option<String> = self
2610 .session_params
2611 .get("default_text_search_config")
2612 .cloned();
2613 if let Some(w) = &stmt.where_ {
2621 let schema_cols = self
2622 .active_catalog()
2623 .get(&stmt.table)
2624 .ok_or_else(|| {
2625 EngineError::Storage(StorageError::TableNotFound {
2626 name: stmt.table.clone(),
2627 })
2628 })?
2629 .schema()
2630 .columns
2631 .clone();
2632 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
2633 && let Some(idx_name) = self
2634 .active_catalog()
2635 .get(&stmt.table)
2636 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
2637 {
2638 let _ = self
2642 .active_catalog_mut()
2643 .promote_cold_row(&stmt.table, &idx_name, &key);
2644 }
2645 }
2646
2647 let ts_cfg: Option<String> = self
2650 .session_param("default_text_search_config")
2651 .map(String::from);
2652 let table = self
2653 .active_catalog_mut()
2654 .get_mut(&stmt.table)
2655 .ok_or_else(|| {
2656 EngineError::Storage(StorageError::TableNotFound {
2657 name: stmt.table.clone(),
2658 })
2659 })?;
2660 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
2661 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
2665 for (col, expr) in &stmt.assignments {
2666 let pos = schema_cols
2667 .iter()
2668 .position(|c| c.name == *col)
2669 .ok_or_else(|| {
2670 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
2671 })?;
2672 targets.push((pos, expr));
2673 }
2674 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
2675 .with_default_text_search_config(ts_cfg.as_deref());
2676 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
2682 for (i, row) in table.rows().iter().enumerate() {
2683 if i.is_multiple_of(256) {
2687 cancel.check()?;
2688 }
2689 if let Some(w) = &stmt.where_ {
2690 let cond = eval::eval_expr(w, row, &ctx)?;
2691 if !matches!(cond, Value::Bool(true)) {
2692 continue;
2693 }
2694 }
2695 let mut new_vals = row.values.clone();
2696 for (pos, expr) in &targets {
2697 let v = eval::eval_expr(expr, row, &ctx)?;
2698 new_vals[*pos] =
2699 coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
2700 }
2701 planned.push((i, new_vals));
2702 }
2703 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
2707 .iter()
2708 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
2709 .collect();
2710 let self_fks = table.schema().foreign_keys.clone();
2711 let _ = table;
2716 if !self_fks.is_empty() {
2720 let new_rows: Vec<Vec<Value>> = planned
2721 .iter()
2722 .map(|(_pos, new_vals)| new_vals.clone())
2723 .collect();
2724 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
2725 }
2726 {
2730 let new_rows: Vec<Vec<Value>> = planned
2731 .iter()
2732 .map(|(_pos, new_vals)| new_vals.clone())
2733 .collect();
2734 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
2735 }
2736 let child_plan =
2740 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
2741 for step in &child_plan {
2743 apply_fk_child_step(self.active_catalog_mut(), step)?;
2744 }
2745 let table = self
2747 .active_catalog_mut()
2748 .get_mut(&stmt.table)
2749 .ok_or_else(|| {
2750 EngineError::Storage(StorageError::TableNotFound {
2751 name: stmt.table.clone(),
2752 })
2753 })?;
2754 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
2764 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
2766 for (pos, new_vals) in &planned {
2767 let old_row = table.rows()[*pos].clone();
2768 let mut new_row = Row::new(new_vals.clone());
2769 let mut skip = false;
2770 for (fd, filter) in &before_update_triggers {
2771 if !filter.is_empty()
2776 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
2777 {
2778 continue;
2779 }
2780 let (outcome, deferred) = triggers::fire_row_trigger(
2781 fd,
2782 Some(new_row.clone()),
2783 Some(&old_row),
2784 &stmt.table,
2785 &schema_cols,
2786 &[],
2787 trigger_session_cfg.as_deref(),
2788 false,
2789 )
2790 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
2791 deferred_embedded.extend(deferred);
2792 match outcome {
2793 triggers::TriggerOutcome::Row(r) => new_row = r,
2794 triggers::TriggerOutcome::Skip => {
2795 skip = true;
2796 break;
2797 }
2798 }
2799 }
2800 if !skip {
2801 applied_after_before.push((*pos, new_row, old_row));
2802 }
2803 }
2804 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
2807 applied_after_before
2808 .iter()
2809 .map(|(_pos, new_row, _old)| new_row.values.clone())
2810 .collect()
2811 } else {
2812 Vec::new()
2813 };
2814 let affected = applied_after_before.len();
2815 for (pos, new_row, old_row) in applied_after_before {
2819 table.update_row(pos, new_row.values.clone())?;
2820 for (fd, filter) in &after_update_triggers {
2821 if !filter.is_empty()
2822 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
2823 {
2824 continue;
2825 }
2826 let (_outcome, deferred) = triggers::fire_row_trigger(
2827 fd,
2828 Some(new_row.clone()),
2829 Some(&old_row),
2830 &stmt.table,
2831 &schema_cols,
2832 &[],
2833 trigger_session_cfg.as_deref(),
2834 true,
2835 )
2836 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
2837 deferred_embedded.extend(deferred);
2838 }
2839 }
2840 let _ = table;
2841 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
2843 if !self.in_transaction() && affected > 0 {
2845 self.statistics
2846 .record_modifications(&stmt.table, affected as u64);
2847 }
2848 if let Some(items) = &stmt.returning {
2850 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
2851 }
2852 Ok(QueryResult::CommandOk {
2853 affected,
2854 modified_catalog: !self.in_transaction(),
2855 })
2856 }
2857
2858 fn exec_delete_cancel(
2862 &mut self,
2863 stmt: &spg_sql::ast::DeleteStatement,
2864 cancel: CancelToken<'_>,
2865 ) -> Result<QueryResult, EngineError> {
2866 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
2870 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
2871 let trigger_session_cfg: Option<String> = self
2872 .session_params
2873 .get("default_text_search_config")
2874 .cloned();
2875 let mut cold_shadow_count: usize = 0;
2883 if let Some(w) = &stmt.where_ {
2884 let schema_cols = self
2885 .active_catalog()
2886 .get(&stmt.table)
2887 .ok_or_else(|| {
2888 EngineError::Storage(StorageError::TableNotFound {
2889 name: stmt.table.clone(),
2890 })
2891 })?
2892 .schema()
2893 .columns
2894 .clone();
2895 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
2896 && let Some(idx_name) = self
2897 .active_catalog()
2898 .get(&stmt.table)
2899 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
2900 {
2901 cold_shadow_count = self
2902 .active_catalog_mut()
2903 .shadow_cold_row(&stmt.table, &idx_name, &key)
2904 .unwrap_or(0);
2905 }
2906 }
2907
2908 let ts_cfg: Option<String> = self
2914 .session_param("default_text_search_config")
2915 .map(String::from);
2916 let table = self
2917 .active_catalog_mut()
2918 .get_mut(&stmt.table)
2919 .ok_or_else(|| {
2920 EngineError::Storage(StorageError::TableNotFound {
2921 name: stmt.table.clone(),
2922 })
2923 })?;
2924 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
2925 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
2926 .with_default_text_search_config(ts_cfg.as_deref());
2927 let mut positions: Vec<usize> = Vec::new();
2928 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
2932 for (i, row) in table.rows().iter().enumerate() {
2933 if i.is_multiple_of(256) {
2934 cancel.check()?;
2935 }
2936 let keep = if let Some(w) = &stmt.where_ {
2937 let cond = eval::eval_expr(w, row, &ctx)?;
2938 !matches!(cond, Value::Bool(true))
2939 } else {
2940 false
2941 };
2942 if !keep {
2943 positions.push(i);
2944 to_delete_rows.push(row.values.clone());
2945 }
2946 }
2947 let _ = table;
2954 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
2962 if !before_delete_triggers.is_empty() {
2963 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
2964 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
2965 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
2966 let old_row = Row::new(old_vals.clone());
2967 let mut cancel_this = false;
2968 for fd in &before_delete_triggers {
2969 let (outcome, deferred) = triggers::fire_row_trigger(
2970 fd,
2971 None,
2972 Some(&old_row),
2973 &stmt.table,
2974 &schema_cols,
2975 &[],
2976 trigger_session_cfg.as_deref(),
2977 false,
2978 )
2979 .map_err(|e| {
2980 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
2981 })?;
2982 deferred_embedded.extend(deferred);
2983 if matches!(outcome, triggers::TriggerOutcome::Skip) {
2984 cancel_this = true;
2985 break;
2986 }
2987 }
2988 if !cancel_this {
2989 filtered_positions.push(*pos);
2990 filtered_old_rows.push(old_vals.clone());
2991 }
2992 }
2993 positions = filtered_positions;
2994 to_delete_rows = filtered_old_rows;
2995 }
2996 let cascade_plan = plan_fk_parent_deletions(
2997 self.active_catalog(),
2998 &stmt.table,
2999 &positions,
3000 &to_delete_rows,
3001 )?;
3002 for step in &cascade_plan {
3009 apply_fk_child_step(self.active_catalog_mut(), step)?;
3010 }
3011 let table = self
3013 .active_catalog_mut()
3014 .get_mut(&stmt.table)
3015 .ok_or_else(|| {
3016 EngineError::Storage(StorageError::TableNotFound {
3017 name: stmt.table.clone(),
3018 })
3019 })?;
3020 let affected = table.delete_rows(&positions) + cold_shadow_count;
3021 let _ = table;
3022 if !after_delete_triggers.is_empty() {
3027 for old_vals in &to_delete_rows {
3028 let old_row = Row::new(old_vals.clone());
3029 for fd in &after_delete_triggers {
3030 let (_outcome, deferred) = triggers::fire_row_trigger(
3031 fd,
3032 None,
3033 Some(&old_row),
3034 &stmt.table,
3035 &schema_cols,
3036 &[],
3037 trigger_session_cfg.as_deref(),
3038 true,
3039 )
3040 .map_err(|e| {
3041 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
3042 })?;
3043 deferred_embedded.extend(deferred);
3044 }
3045 }
3046 }
3047 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
3049 if !self.in_transaction() && affected > 0 {
3051 self.statistics
3052 .record_modifications(&stmt.table, affected as u64);
3053 }
3054 if let Some(items) = &stmt.returning {
3060 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
3061 }
3062 Ok(QueryResult::CommandOk {
3063 affected,
3064 modified_catalog: !self.in_transaction(),
3065 })
3066 }
3067
3068 #[allow(clippy::format_push_string)]
3078 fn exec_explain(
3079 &self,
3080 e: &spg_sql::ast::ExplainStatement,
3081 cancel: CancelToken<'_>,
3082 ) -> Result<QueryResult, EngineError> {
3083 let mut lines = Vec::<String>::new();
3084 explain_select(&e.inner, self, 0, &mut lines);
3085 if e.suggest {
3086 let suggestions = build_index_suggestions(&e.inner, self);
3095 for s in suggestions {
3096 lines.push(s);
3097 }
3098 } else if e.analyze {
3099 let started = self.clock.map(|f| f());
3116 let exec = self.exec_select_cancel(&e.inner, cancel)?;
3117 let elapsed_micros = match (self.clock, started) {
3118 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
3119 _ => None,
3120 };
3121 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
3122 rows.len()
3123 } else {
3124 0
3125 };
3126 annotate_explain_lines(&mut lines, row_count, self);
3127 let mut total = alloc::format!("Total: rows={row_count}");
3128 if let Some(us) = elapsed_micros {
3129 total.push_str(&alloc::format!(" elapsed={us}us"));
3130 }
3131 lines.push(total);
3132 }
3133 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
3134 let rows: Vec<Row> = lines
3135 .into_iter()
3136 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
3137 .collect();
3138 Ok(QueryResult::Rows { columns, rows })
3139 }
3140
3141 fn exec_show_tables(&self) -> QueryResult {
3142 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
3143 let rows: Vec<Row> = self
3144 .active_catalog()
3145 .table_names()
3146 .into_iter()
3147 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
3148 .collect();
3149 QueryResult::Rows { columns, rows }
3150 }
3151
3152 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
3155 let table =
3156 self.active_catalog()
3157 .get(table_name)
3158 .ok_or_else(|| StorageError::TableNotFound {
3159 name: table_name.into(),
3160 })?;
3161 let columns = alloc::vec![
3162 ColumnSchema::new("name", DataType::Text, false),
3163 ColumnSchema::new("type", DataType::Text, false),
3164 ColumnSchema::new("nullable", DataType::Bool, false),
3165 ];
3166 let rows: Vec<Row> = table
3167 .schema()
3168 .columns
3169 .iter()
3170 .map(|c| {
3171 Row::new(alloc::vec![
3172 Value::Text(c.name.clone()),
3173 Value::Text(alloc::format!("{}", c.ty)),
3174 Value::Bool(c.nullable),
3175 ])
3176 })
3177 .collect();
3178 Ok(QueryResult::Rows { columns, rows })
3179 }
3180
3181 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
3182 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3183 if self.tx_catalogs.contains_key(&tx_id) {
3184 return Err(EngineError::TransactionAlreadyOpen);
3185 }
3186 self.tx_catalogs.insert(
3187 tx_id,
3188 TxState {
3189 catalog: self.catalog.clone(),
3190 savepoints: Vec::new(),
3191 },
3192 );
3193 Ok(QueryResult::CommandOk {
3194 affected: 0,
3195 modified_catalog: false,
3196 })
3197 }
3198
3199 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
3200 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3201 let state = self
3202 .tx_catalogs
3203 .remove(&tx_id)
3204 .ok_or(EngineError::NoActiveTransaction)?;
3205 self.catalog = state.catalog;
3206 Ok(QueryResult::CommandOk {
3210 affected: 0,
3211 modified_catalog: true,
3212 })
3213 }
3214
3215 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
3216 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3217 if self.tx_catalogs.remove(&tx_id).is_none() {
3218 return Err(EngineError::NoActiveTransaction);
3219 }
3220 Ok(QueryResult::CommandOk {
3222 affected: 0,
3223 modified_catalog: false,
3224 })
3225 }
3226
3227 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
3228 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3229 let state = self
3230 .tx_catalogs
3231 .get_mut(&tx_id)
3232 .ok_or(EngineError::NoActiveTransaction)?;
3233 state.savepoints.retain(|(n, _)| n != &name);
3237 let snapshot = state.catalog.clone();
3238 state.savepoints.push((name, snapshot));
3239 Ok(QueryResult::CommandOk {
3240 affected: 0,
3241 modified_catalog: false,
3242 })
3243 }
3244
3245 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3246 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3247 let state = self
3248 .tx_catalogs
3249 .get_mut(&tx_id)
3250 .ok_or(EngineError::NoActiveTransaction)?;
3251 let pos = state
3252 .savepoints
3253 .iter()
3254 .rposition(|(n, _)| n == name)
3255 .ok_or_else(|| {
3256 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
3257 })?;
3258 let snapshot = state.savepoints[pos].1.clone();
3262 state.savepoints.truncate(pos + 1);
3263 state.catalog = snapshot;
3264 Ok(QueryResult::CommandOk {
3265 affected: 0,
3266 modified_catalog: false,
3267 })
3268 }
3269
3270 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3271 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
3272 let state = self
3273 .tx_catalogs
3274 .get_mut(&tx_id)
3275 .ok_or(EngineError::NoActiveTransaction)?;
3276 let pos = state
3277 .savepoints
3278 .iter()
3279 .rposition(|(n, _)| n == name)
3280 .ok_or_else(|| {
3281 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
3282 })?;
3283 state.savepoints.truncate(pos);
3286 Ok(QueryResult::CommandOk {
3287 affected: 0,
3288 modified_catalog: false,
3289 })
3290 }
3291
3292 fn exec_alter_table(
3303 &mut self,
3304 s: spg_sql::ast::AlterTableStatement,
3305 ) -> Result<QueryResult, EngineError> {
3306 match s.target {
3307 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
3308 let table = self.active_catalog_mut().get_mut(&s.name).ok_or_else(|| {
3309 EngineError::Storage(StorageError::TableNotFound {
3310 name: s.name.clone(),
3311 })
3312 })?;
3313 table.schema_mut().hot_tier_bytes = Some(n);
3314 }
3315 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
3316 let cols_snapshot = self
3321 .active_catalog()
3322 .get(&s.name)
3323 .ok_or_else(|| {
3324 EngineError::Storage(StorageError::TableNotFound {
3325 name: s.name.clone(),
3326 })
3327 })?
3328 .schema()
3329 .columns
3330 .clone();
3331 let storage_fk =
3332 resolve_foreign_key(&s.name, &cols_snapshot, fk, self.active_catalog())?;
3333 let existing_rows: Vec<Vec<Value>> = self
3336 .active_catalog()
3337 .get(&s.name)
3338 .expect("checked above")
3339 .rows()
3340 .iter()
3341 .map(|r| r.values.clone())
3342 .collect();
3343 enforce_fk_inserts(
3344 self.active_catalog(),
3345 &s.name,
3346 core::slice::from_ref(&storage_fk),
3347 &existing_rows,
3348 )?;
3349 let table = self
3351 .active_catalog_mut()
3352 .get_mut(&s.name)
3353 .expect("checked above");
3354 if let Some(name) = &storage_fk.name
3355 && table
3356 .schema()
3357 .foreign_keys
3358 .iter()
3359 .any(|f| f.name.as_ref() == Some(name))
3360 {
3361 return Err(EngineError::Unsupported(alloc::format!(
3362 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
3363 )));
3364 }
3365 table.schema_mut().foreign_keys.push(storage_fk);
3366 }
3367 spg_sql::ast::AlterTableTarget::DropForeignKey(name) => {
3368 let table = self.active_catalog_mut().get_mut(&s.name).ok_or_else(|| {
3369 EngineError::Storage(StorageError::TableNotFound {
3370 name: s.name.clone(),
3371 })
3372 })?;
3373 let fks = &mut table.schema_mut().foreign_keys;
3374 let before = fks.len();
3375 fks.retain(|f| f.name.as_ref() != Some(&name));
3376 if fks.len() == before {
3377 return Err(EngineError::Unsupported(alloc::format!(
3378 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
3379 s.name
3380 )));
3381 }
3382 }
3383 spg_sql::ast::AlterTableTarget::AddColumn {
3384 column,
3385 if_not_exists,
3386 } => {
3387 let clock = self.clock;
3392 let table = self.active_catalog_mut().get_mut(&s.name).ok_or_else(|| {
3393 EngineError::Storage(StorageError::TableNotFound {
3394 name: s.name.clone(),
3395 })
3396 })?;
3397 if table
3398 .schema()
3399 .columns
3400 .iter()
3401 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
3402 {
3403 if if_not_exists {
3404 return Ok(QueryResult::CommandOk {
3405 affected: 0,
3406 modified_catalog: !self.in_transaction(),
3407 });
3408 }
3409 return Err(EngineError::Unsupported(alloc::format!(
3410 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
3411 column.name,
3412 s.name
3413 )));
3414 }
3415 let col_name = column.name.clone();
3416 let nullable = column.nullable;
3417 let has_default =
3418 column.default.is_some() || column.auto_increment;
3419 let col_schema = column_def_to_schema(column)?;
3420 let row_count = table.row_count();
3421 let fill_value: Value = if has_default
3428 || col_schema.runtime_default.is_some()
3429 {
3430 resolve_column_default_free(&col_schema, clock)?
3431 } else if nullable || row_count == 0 {
3432 Value::Null
3433 } else {
3434 return Err(EngineError::Unsupported(alloc::format!(
3435 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
3436 when the table has existing rows"
3437 )));
3438 };
3439 table.add_column(col_schema, fill_value);
3440 }
3441 spg_sql::ast::AlterTableTarget::AlterColumnType {
3442 column,
3443 new_type,
3444 using,
3445 } => {
3446 let new_data_type = column_type_to_data_type(new_type);
3452 let table = self.active_catalog_mut().get_mut(&s.name).ok_or_else(|| {
3453 EngineError::Storage(StorageError::TableNotFound {
3454 name: s.name.clone(),
3455 })
3456 })?;
3457 let col_pos = table
3458 .schema()
3459 .columns
3460 .iter()
3461 .position(|c| c.name.eq_ignore_ascii_case(&column))
3462 .ok_or_else(|| {
3463 EngineError::Unsupported(alloc::format!(
3464 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
3465 s.name
3466 ))
3467 })?;
3468 let schema_cols = table.schema().columns.clone();
3469 let ctx = eval::EvalContext::new(&schema_cols, None);
3470 let mut new_values: alloc::vec::Vec<Value> =
3471 alloc::vec::Vec::with_capacity(table.row_count());
3472 for row in table.rows().iter() {
3473 let raw = match &using {
3474 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
3475 EngineError::Unsupported(alloc::format!(
3476 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
3477 ))
3478 })?,
3479 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
3480 };
3481 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
3482 new_values.push(coerced);
3483 }
3484 table.schema_mut().columns[col_pos].ty = new_data_type;
3485 for (i, v) in new_values.into_iter().enumerate() {
3486 let mut row_values = table
3487 .rows()
3488 .get(i)
3489 .expect("bounds-checked above")
3490 .values
3491 .clone();
3492 row_values[col_pos] = v;
3493 table.update_row(i, row_values)?;
3494 }
3495 }
3496 }
3497 Ok(QueryResult::CommandOk {
3498 affected: 0,
3499 modified_catalog: !self.in_transaction(),
3500 })
3501 }
3502
3503 fn exec_alter_index(
3504 &mut self,
3505 stmt: spg_sql::ast::AlterIndexStatement,
3506 ) -> Result<QueryResult, EngineError> {
3507 let spg_sql::ast::AlterIndexStatement {
3511 name: idx_name,
3512 target,
3513 } = stmt;
3514 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target;
3515 let target = encoding.map(|e| match e {
3516 SqlVecEncoding::F32 => VecEncoding::F32,
3517 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
3518 SqlVecEncoding::F16 => VecEncoding::F16,
3519 });
3520 let table_name = {
3525 let cat = self.active_catalog();
3526 let mut found: Option<String> = None;
3527 for tname in cat.table_names() {
3528 if let Some(t) = cat.get(&tname)
3529 && t.indices().iter().any(|i| i.name == idx_name)
3530 {
3531 found = Some(tname);
3532 break;
3533 }
3534 }
3535 found.ok_or_else(|| {
3536 EngineError::Storage(StorageError::IndexNotFound {
3537 name: idx_name.clone(),
3538 })
3539 })?
3540 };
3541 let table = self
3542 .active_catalog_mut()
3543 .get_mut(&table_name)
3544 .expect("table found above");
3545 table.rebuild_nsw_index(&idx_name, target)?;
3546 self.plan_cache.evict_referencing(&table_name);
3549 Ok(QueryResult::CommandOk {
3550 affected: 0,
3551 modified_catalog: !self.in_transaction(),
3552 })
3553 }
3554
3555 fn exec_create_index(
3556 &mut self,
3557 stmt: CreateIndexStatement,
3558 ) -> Result<QueryResult, EngineError> {
3559 let table = self
3560 .active_catalog_mut()
3561 .get_mut(&stmt.table)
3562 .ok_or_else(|| {
3563 EngineError::Storage(StorageError::TableNotFound {
3564 name: stmt.table.clone(),
3565 })
3566 })?;
3567 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
3569 return Ok(QueryResult::CommandOk {
3570 affected: 0,
3571 modified_catalog: false,
3572 });
3573 }
3574 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
3581 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
3585 Vec::new()
3586 } else {
3587 let schema = table.schema();
3588 stmt.included_columns
3589 .iter()
3590 .map(|c| {
3591 schema.column_position(c).ok_or_else(|| {
3592 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
3593 })
3594 })
3595 .collect::<Result<Vec<_>, _>>()?
3596 };
3597 match stmt.method {
3598 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
3599 IndexMethod::Hnsw => {
3600 if !included_positions.is_empty() {
3601 return Err(EngineError::Unsupported(
3602 "INCLUDE columns are not supported on HNSW indexes".into(),
3603 ));
3604 }
3605 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
3606 }
3607 IndexMethod::Brin => {
3609 if !included_positions.is_empty() {
3610 return Err(EngineError::Unsupported(
3611 "INCLUDE columns are not supported on BRIN indexes".into(),
3612 ));
3613 }
3614 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
3615 }
3616 IndexMethod::Gin => {
3624 if !included_positions.is_empty() {
3625 return Err(EngineError::Unsupported(
3626 "INCLUDE columns are not supported on GIN indexes".into(),
3627 ));
3628 }
3629 let col_pos = table
3630 .schema()
3631 .column_position(&stmt.column)
3632 .ok_or_else(|| {
3633 EngineError::Storage(StorageError::ColumnNotFound {
3634 column: stmt.column.clone(),
3635 })
3636 })?;
3637 if table.schema().columns[col_pos].ty == spg_storage::DataType::TsVector {
3638 table
3639 .add_gin_index(stmt.name.clone(), &stmt.column)
3640 .map_err(EngineError::Storage)?;
3641 } else {
3642 table.add_index(stmt.name.clone(), &stmt.column)?;
3648 }
3649 }
3650 }
3651 if !included_positions.is_empty()
3652 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
3653 {
3654 idx.included_columns = included_positions;
3655 }
3656 if let Some(pred_expr) = &stmt.partial_predicate {
3664 let canonical = pred_expr.to_string();
3665 if matches!(
3666 stmt.method,
3667 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
3668 ) {
3669 return Err(EngineError::Unsupported(
3670 "WHERE predicates are not supported on HNSW or BRIN indexes".into(),
3671 ));
3672 }
3673 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
3674 idx.partial_predicate = Some(canonical);
3675 }
3676 }
3677 if let Some(key_expr) = &stmt.expression {
3685 if matches!(
3686 stmt.method,
3687 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
3688 ) {
3689 return Err(EngineError::Unsupported(
3690 "Expression keys are not supported on HNSW or BRIN indexes".into(),
3691 ));
3692 }
3693 let canonical = key_expr.to_string();
3694 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
3695 idx.expression = Some(canonical);
3696 }
3697 }
3698 if stmt.is_unique {
3707 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
3708 for col_name in &stmt.extra_columns {
3709 let pos = table
3710 .schema()
3711 .columns
3712 .iter()
3713 .position(|c| c.name.eq_ignore_ascii_case(col_name))
3714 .ok_or_else(|| {
3715 EngineError::Unsupported(alloc::format!(
3716 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
3717 stmt.name,
3718 stmt.table
3719 ))
3720 })?;
3721 extra_positions.push(pos);
3722 }
3723 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
3724 idx.is_unique = true;
3725 idx.extra_column_positions = extra_positions;
3726 }
3727 let snapshot_indices = table.indices().to_vec();
3732 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
3733 table.rows().iter().cloned().collect();
3734 let snapshot_schema = table.schema().clone();
3735 let idx_ref = snapshot_indices
3736 .iter()
3737 .find(|i| i.name == stmt.name)
3738 .expect("just-added index");
3739 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
3740 }
3741 self.plan_cache.evict_referencing(&table_name);
3744 Ok(QueryResult::CommandOk {
3745 affected: 0,
3746 modified_catalog: !self.in_transaction(),
3747 })
3748 }
3749
3750 fn exec_create_table(
3751 &mut self,
3752 stmt: CreateTableStatement,
3753 ) -> Result<QueryResult, EngineError> {
3754 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
3755 return Ok(QueryResult::CommandOk {
3756 affected: 0,
3757 modified_catalog: false,
3758 });
3759 }
3760 let table_name = stmt.name.clone();
3761 let inline_pk_columns: Vec<String> = stmt
3765 .columns
3766 .iter()
3767 .filter(|c| c.is_primary_key)
3768 .map(|c| c.name.clone())
3769 .collect();
3770 let cols = stmt
3776 .columns
3777 .into_iter()
3778 .map(column_def_to_schema)
3779 .collect::<Result<Vec<_>, _>>()?;
3780 let mut cols = cols;
3782 for tc in &stmt.table_constraints {
3783 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
3784 for col_name in columns {
3785 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
3786 col.nullable = false;
3787 }
3788 }
3789 }
3790 }
3791 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
3798 Vec::with_capacity(stmt.foreign_keys.len());
3799 for fk in stmt.foreign_keys {
3800 fks.push(resolve_foreign_key(
3801 &table_name,
3802 &cols,
3803 fk,
3804 self.active_catalog(),
3805 )?);
3806 }
3807 let mut schema = TableSchema::new(table_name.clone(), cols);
3808 schema.foreign_keys = fks;
3809 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
3813 let mut check_exprs: Vec<String> = Vec::new();
3814 for tc in &stmt.table_constraints {
3815 let (is_pk, names, nnd) = match tc {
3816 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
3817 (true, columns.clone(), false)
3818 }
3819 spg_sql::ast::TableConstraint::Unique {
3820 columns,
3821 nulls_not_distinct,
3822 ..
3823 } => (false, columns.clone(), *nulls_not_distinct),
3824 spg_sql::ast::TableConstraint::Check { expr, .. } => {
3825 check_exprs.push(alloc::format!("{expr}"));
3828 continue;
3829 }
3830 };
3831 let mut positions = Vec::with_capacity(names.len());
3832 for n in &names {
3833 let pos = schema
3834 .columns
3835 .iter()
3836 .position(|c| c.name == *n)
3837 .ok_or_else(|| {
3838 EngineError::Unsupported(alloc::format!(
3839 "table constraint references unknown column {n:?}"
3840 ))
3841 })?;
3842 positions.push(pos);
3843 }
3844 uc_storage.push(spg_storage::UniquenessConstraint {
3845 is_primary_key: is_pk,
3846 columns: positions,
3847 nulls_not_distinct: nnd,
3848 });
3849 }
3850 schema.uniqueness_constraints = uc_storage.clone();
3851 schema.checks = check_exprs;
3852 self.active_catalog_mut().create_table(schema)?;
3853 let table = self
3857 .active_catalog_mut()
3858 .get_mut(&table_name)
3859 .expect("just created");
3860 for (i, col_name) in inline_pk_columns.iter().enumerate() {
3861 let idx_name = if inline_pk_columns.len() == 1 {
3862 alloc::format!("{table_name}_pkey")
3863 } else {
3864 alloc::format!("{table_name}_pkey_{i}")
3865 };
3866 if let Err(e) = table.add_index(idx_name, col_name) {
3867 return Err(EngineError::Storage(e));
3868 }
3869 }
3870 for (i, tc) in stmt.table_constraints.iter().enumerate() {
3871 let (is_pk, names) = match tc {
3872 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => (true, columns),
3873 spg_sql::ast::TableConstraint::Unique { columns, .. } => (false, columns),
3874 spg_sql::ast::TableConstraint::Check { .. } => continue,
3875 };
3876 let leading = &names[0];
3877 let already = table.indices().iter().any(|idx| {
3880 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
3881 && table.schema().columns[idx.column_position].name == *leading
3882 });
3883 if already {
3884 continue;
3885 }
3886 let suffix = if is_pk { "pkey" } else { "key" };
3887 let idx_name = if names.len() == 1 {
3888 alloc::format!("{table_name}_{leading}_{suffix}")
3889 } else {
3890 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
3891 };
3892 if let Err(e) = table.add_index(idx_name, leading) {
3893 return Err(EngineError::Storage(e));
3894 }
3895 }
3896 Ok(QueryResult::CommandOk {
3897 affected: 0,
3898 modified_catalog: !self.in_transaction(),
3899 })
3900 }
3901
3902 fn exec_insert(&mut self, stmt: InsertStatement) -> Result<QueryResult, EngineError> {
3903 if let Some(select) = stmt.select_source.clone() {
3908 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
3909 let rows = match select_result {
3910 QueryResult::Rows { rows, .. } => rows,
3911 other => {
3912 return Err(EngineError::Unsupported(alloc::format!(
3913 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
3914 )));
3915 }
3916 };
3917 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
3918 for row in rows {
3919 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
3920 for v in row.values {
3921 tuple.push(value_to_literal_expr_permissive(v)?);
3922 }
3923 materialised.push(tuple);
3924 }
3925 let recurse = InsertStatement {
3926 table: stmt.table,
3927 columns: stmt.columns,
3928 rows: materialised,
3929 select_source: None,
3930 on_conflict: stmt.on_conflict,
3931 returning: stmt.returning,
3932 };
3933 return self.exec_insert(recurse);
3934 }
3935 let clock = self.clock;
3939 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
3945 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
3946 let trigger_session_cfg: Option<alloc::string::String> = self
3947 .session_params
3948 .get("default_text_search_config")
3949 .cloned();
3950 let table = self
3951 .active_catalog_mut()
3952 .get_mut(&stmt.table)
3953 .ok_or_else(|| {
3954 EngineError::Storage(StorageError::TableNotFound {
3955 name: stmt.table.clone(),
3956 })
3957 })?;
3958 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
3964 let schema_cols_len = column_meta.len();
3965 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
3969 None => None, Some(cols) => {
3971 let mut map = alloc::vec![None; schema_cols_len];
3972 for (j, name) in cols.iter().enumerate() {
3973 let idx = column_meta
3974 .iter()
3975 .position(|c| c.name == *name)
3976 .ok_or_else(|| {
3977 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
3978 })?;
3979 if map[idx].is_some() {
3980 return Err(EngineError::Storage(StorageError::ArityMismatch {
3981 expected: schema_cols_len,
3982 actual: cols.len(),
3983 }));
3984 }
3985 map[idx] = Some(j);
3986 }
3987 for (i, col) in column_meta.iter().enumerate() {
3991 if map[i].is_none()
3992 && !col.nullable
3993 && col.default.is_none()
3994 && col.runtime_default.is_none()
3995 && !col.auto_increment
3996 {
3997 return Err(EngineError::Storage(StorageError::NullInNotNull {
3998 column: col.name.clone(),
3999 }));
4000 }
4001 }
4002 Some(map)
4003 }
4004 };
4005 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
4006 let fks = table.schema().foreign_keys.clone();
4012 let mut affected = 0usize;
4013 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
4016 for tuple in stmt.rows {
4017 if tuple.len() != expected_tuple_len {
4018 return Err(EngineError::Storage(StorageError::ArityMismatch {
4019 expected: expected_tuple_len,
4020 actual: tuple.len(),
4021 }));
4022 }
4023 let values: Vec<Value> = if let Some(map) = &tuple_pos {
4027 let raw_tuple: Vec<Value> = tuple
4029 .into_iter()
4030 .map(literal_expr_to_value)
4031 .collect::<Result<_, _>>()?;
4032 let mut out = Vec::with_capacity(schema_cols_len);
4033 for (i, col) in column_meta.iter().enumerate() {
4034 let mut raw = match map[i] {
4035 Some(j) => raw_tuple[j].clone(),
4036 None => resolve_column_default_free(col, clock)?,
4037 };
4038 if col.auto_increment && raw.is_null() {
4039 let next = table.next_auto_value(i).ok_or_else(|| {
4040 EngineError::Unsupported(alloc::format!(
4041 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
4042 col.name
4043 ))
4044 })?;
4045 raw = Value::BigInt(next);
4046 }
4047 out.push(coerce_value(raw, col.ty, &col.name, i)?);
4048 }
4049 out
4050 } else {
4051 let mut out = Vec::with_capacity(schema_cols_len);
4053 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
4054 let mut raw = literal_expr_to_value(expr)?;
4055 if col.auto_increment && raw.is_null() {
4056 let next = table.next_auto_value(i).ok_or_else(|| {
4057 EngineError::Unsupported(alloc::format!(
4058 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
4059 col.name
4060 ))
4061 })?;
4062 raw = Value::BigInt(next);
4063 }
4064 out.push(coerce_value(raw, col.ty, &col.name, i)?);
4065 }
4066 out
4067 };
4068 all_values.push(values);
4069 }
4070 let uniqueness = table.schema().uniqueness_constraints.clone();
4075 let _ = table;
4076 if !fks.is_empty() {
4077 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
4078 }
4079 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
4081 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
4083 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
4090 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
4097 let mut skipped_count = 0usize;
4098 if let Some(clause) = &stmt.on_conflict {
4099 let conflict_cols = resolve_on_conflict_columns(
4100 self.active_catalog(),
4101 &stmt.table,
4102 clause.target_columns.as_slice(),
4103 )?;
4104 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
4105 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
4106 for values in all_values {
4107 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
4108 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
4111 let collides_with_table = !has_null_key
4112 && on_conflict_keys_exist(
4113 self.active_catalog(),
4114 &stmt.table,
4115 &conflict_cols,
4116 &key_tuple,
4117 );
4118 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
4119 let collides_with_batch =
4120 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
4121 let collides = collides_with_table || collides_with_batch;
4122 match (&clause.action, collides) {
4123 (_, false) => {
4124 seen_keys.push(key_tuple_owned);
4125 kept.push(values);
4126 }
4127 (spg_sql::ast::OnConflictAction::Nothing, true) => {
4128 skipped_count += 1;
4129 }
4130 (
4131 spg_sql::ast::OnConflictAction::Update {
4132 assignments,
4133 where_,
4134 },
4135 true,
4136 ) => {
4137 if !collides_with_table {
4138 skipped_count += 1;
4139 continue;
4140 }
4141 let target_pos = lookup_row_position_by_keys(
4142 self.active_catalog(),
4143 &stmt.table,
4144 &conflict_cols,
4145 &key_tuple,
4146 )
4147 .ok_or_else(|| {
4148 EngineError::Unsupported(
4149 "ON CONFLICT DO UPDATE: conflict detected but row \
4150 position could not be resolved (cold-tier row?)"
4151 .into(),
4152 )
4153 })?;
4154 let updated = apply_on_conflict_assignments(
4155 self.active_catalog(),
4156 &stmt.table,
4157 target_pos,
4158 &values,
4159 assignments,
4160 where_.as_ref(),
4161 )?;
4162 if let Some(new_row) = updated {
4163 pending_updates.push((target_pos, new_row));
4164 } else {
4165 skipped_count += 1;
4166 }
4167 }
4168 }
4169 }
4170 all_values = kept;
4171 }
4172 let table = self
4174 .active_catalog_mut()
4175 .get_mut(&stmt.table)
4176 .ok_or_else(|| {
4177 EngineError::Storage(StorageError::TableNotFound {
4178 name: stmt.table.clone(),
4179 })
4180 })?;
4181 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
4185 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4189 'rowloop: for values in all_values {
4190 let mut row = Row::new(values);
4191 for fd in &before_insert_triggers {
4196 let (outcome, deferred) = triggers::fire_row_trigger(
4197 fd,
4198 Some(row.clone()),
4199 None,
4200 &stmt.table,
4201 &column_meta,
4202 &[],
4203 trigger_session_cfg.as_deref(),
4204 false,
4205 )
4206 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4207 deferred_embedded.extend(deferred);
4208 match outcome {
4209 triggers::TriggerOutcome::Row(r) => row = r,
4210 triggers::TriggerOutcome::Skip => continue 'rowloop,
4211 }
4212 }
4213 if stmt.returning.is_some() {
4214 returning_rows.push(row.values.clone());
4215 }
4216 let inserted = row.clone();
4219 table.insert(row)?;
4220 affected += 1;
4221 for fd in &after_insert_triggers {
4225 let (_outcome, deferred) = triggers::fire_row_trigger(
4226 fd,
4227 Some(inserted.clone()),
4228 None,
4229 &stmt.table,
4230 &column_meta,
4231 &[],
4232 trigger_session_cfg.as_deref(),
4233 true,
4234 )
4235 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4236 deferred_embedded.extend(deferred);
4237 }
4238 }
4239 for (pos, new_row) in pending_updates {
4243 if stmt.returning.is_some() {
4244 returning_rows.push(new_row.clone());
4245 }
4246 table.update_row(pos, new_row)?;
4247 affected += 1;
4248 }
4249 let _ = skipped_count;
4250 let _ = table;
4256 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
4257 if let Some(items) = &stmt.returning {
4261 return self.build_returning_rows(&stmt.table, items, returning_rows);
4262 }
4263 if !self.in_transaction() && affected > 0 {
4268 self.statistics
4269 .record_modifications(&stmt.table, affected as u64);
4270 }
4271 Ok(QueryResult::CommandOk {
4272 affected,
4273 modified_catalog: !self.in_transaction(),
4274 })
4275 }
4276
4277 fn exec_select_as_of_segment(
4290 &self,
4291 stmt: &SelectStatement,
4292 from: &spg_sql::ast::FromClause,
4293 segment_id: u32,
4294 ) -> Result<QueryResult, EngineError> {
4295 if !from.joins.is_empty()
4298 || stmt.group_by.is_some()
4299 || stmt.having.is_some()
4300 || !stmt.unions.is_empty()
4301 || !stmt.order_by.is_empty()
4302 || stmt.offset.is_some()
4303 || stmt.distinct
4304 || aggregate::uses_aggregate(stmt)
4305 {
4306 return Err(EngineError::Unsupported(
4307 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
4308 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
4309 .into(),
4310 ));
4311 }
4312 let table = self
4313 .active_catalog()
4314 .get(&from.primary.name)
4315 .ok_or_else(|| StorageError::TableNotFound {
4316 name: from.primary.name.clone(),
4317 })?;
4318 let schema = table.schema().clone();
4319 let schema_cols = &schema.columns;
4320 let alias = from
4321 .primary
4322 .alias
4323 .as_deref()
4324 .unwrap_or(from.primary.name.as_str());
4325 let ctx = EvalContext::new(schema_cols, Some(alias));
4326 let seg = self
4327 .active_catalog()
4328 .cold_segment(segment_id)
4329 .ok_or_else(|| {
4330 EngineError::Unsupported(alloc::format!(
4331 "AS OF SEGMENT: cold segment {segment_id} not registered"
4332 ))
4333 })?;
4334 let mut out_rows: Vec<Row> = Vec::new();
4335 let mut limit_remaining: Option<usize> =
4336 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
4337 for (_key, body) in seg.scan() {
4338 let (row, _consumed) =
4339 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
4340 if let Some(where_expr) = &stmt.where_ {
4341 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
4342 if !matches!(cond, Value::Bool(true)) {
4343 continue;
4344 }
4345 }
4346 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
4348 out_rows.push(projected);
4349 if let Some(rem) = limit_remaining.as_mut() {
4350 if *rem == 0 {
4351 out_rows.pop();
4352 break;
4353 }
4354 *rem -= 1;
4355 }
4356 }
4357 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
4359 Ok(QueryResult::Rows {
4360 columns,
4361 rows: out_rows,
4362 })
4363 }
4364
4365 fn eval_expr_simple(
4370 &self,
4371 expr: &Expr,
4372 row: &Row,
4373 ctx: &EvalContext,
4374 ) -> Result<Value, EngineError> {
4375 let cancel = CancelToken::none();
4376 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
4377 }
4378
4379 fn build_returning_rows(
4386 &self,
4387 table_name: &str,
4388 items: &[SelectItem],
4389 mutated_rows: Vec<Vec<Value>>,
4390 ) -> Result<QueryResult, EngineError> {
4391 let table = self.active_catalog().get(table_name).ok_or_else(|| {
4392 EngineError::Storage(StorageError::TableNotFound {
4393 name: table_name.into(),
4394 })
4395 })?;
4396 let schema_cols = table.schema().columns.clone();
4397 let columns = self.derive_output_columns(items, &schema_cols, table_name);
4398 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
4399 for values in mutated_rows {
4400 let row = Row::new(values);
4401 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
4402 out_rows.push(projected);
4403 }
4404 Ok(QueryResult::Rows {
4405 columns,
4406 rows: out_rows,
4407 })
4408 }
4409
4410 fn project_row_simple(
4414 &self,
4415 row: &Row,
4416 items: &[SelectItem],
4417 schema_cols: &[ColumnSchema],
4418 alias: &str,
4419 ) -> Result<Row, EngineError> {
4420 let ctx = EvalContext::new(schema_cols, Some(alias));
4421 let cancel = CancelToken::none();
4422 let mut out_vals = Vec::new();
4423 for item in items {
4424 match item {
4425 SelectItem::Wildcard => {
4426 out_vals.extend(row.values.iter().cloned());
4427 }
4428 SelectItem::Expr { expr, .. } => {
4429 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
4430 out_vals.push(v);
4431 }
4432 }
4433 }
4434 Ok(Row::new(out_vals))
4435 }
4436
4437 fn derive_output_columns(
4442 &self,
4443 items: &[SelectItem],
4444 schema_cols: &[ColumnSchema],
4445 _alias: &str,
4446 ) -> Vec<ColumnSchema> {
4447 let mut out = Vec::new();
4448 for item in items {
4449 match item {
4450 SelectItem::Wildcard => {
4451 out.extend(schema_cols.iter().cloned());
4452 }
4453 SelectItem::Expr { alias, .. } => {
4454 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
4455 out.push(ColumnSchema::new(name, DataType::Text, true));
4458 }
4459 }
4460 }
4461 out
4462 }
4463
4464 fn exec_select_cancel(
4465 &self,
4466 stmt: &SelectStatement,
4467 cancel: CancelToken<'_>,
4468 ) -> Result<QueryResult, EngineError> {
4469 cancel.check()?;
4470 if let Some(from) = &stmt.from
4479 && let Some(seg_id) = from.primary.as_of_segment
4480 {
4481 return self.exec_select_as_of_segment(stmt, from, seg_id);
4482 }
4483 if let Some(from) = &stmt.from
4487 && from.joins.is_empty()
4488 && stmt.where_.is_none()
4489 && stmt.group_by.is_none()
4490 && stmt.having.is_none()
4491 && stmt.unions.is_empty()
4492 && stmt.order_by.is_empty()
4493 && stmt.limit.is_none()
4494 && stmt.offset.is_none()
4495 && !stmt.distinct
4496 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
4497 {
4498 let lower = from.primary.name.to_ascii_lowercase();
4499 match lower.as_str() {
4500 "spg_statistic" => return Ok(self.exec_spg_statistic()),
4501 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
4503 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
4504 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
4505 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
4506 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
4507 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
4508 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
4509 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
4510 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
4511 _ => {}
4512 }
4513 }
4514 if !stmt.ctes.is_empty() {
4522 return self.exec_with_ctes(stmt, cancel);
4523 }
4524 let mut stmt_owned;
4531 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
4532 stmt_owned = stmt.clone();
4533 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
4534 &stmt_owned
4535 } else {
4536 stmt
4537 };
4538 if stmt_ref.unions.is_empty() {
4539 return self.exec_bare_select_cancel(stmt_ref, cancel);
4540 }
4541 let mut head = stmt_ref.clone();
4546 head.unions = Vec::new();
4547 head.order_by = Vec::new();
4548 head.limit = None;
4549 let QueryResult::Rows { columns, mut rows } =
4550 self.exec_bare_select_cancel(&head, cancel)?
4551 else {
4552 unreachable!("bare SELECT cannot return CommandOk")
4553 };
4554 for (kind, peer) in &stmt_ref.unions {
4555 let QueryResult::Rows {
4556 columns: peer_cols,
4557 rows: peer_rows,
4558 } = self.exec_bare_select_cancel(peer, cancel)?
4559 else {
4560 unreachable!("bare SELECT cannot return CommandOk")
4561 };
4562 if peer_cols.len() != columns.len() {
4563 return Err(EngineError::Unsupported(alloc::format!(
4564 "UNION arity mismatch: head has {} columns, peer has {}",
4565 columns.len(),
4566 peer_cols.len()
4567 )));
4568 }
4569 rows.extend(peer_rows);
4570 if matches!(kind, UnionKind::Distinct) {
4571 rows = dedup_rows(rows);
4572 }
4573 }
4574 if !stmt.order_by.is_empty() {
4577 let synth_ctx = EvalContext::new(&columns, None);
4578 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
4579 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
4580 for r in rows {
4581 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
4582 tagged.push((keys, r));
4583 }
4584 sort_by_keys(&mut tagged, &descs);
4585 rows = tagged.into_iter().map(|(_, r)| r).collect();
4586 }
4587 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
4588 Ok(QueryResult::Rows { columns, rows })
4589 }
4590
4591 #[allow(clippy::too_many_lines)]
4592 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
4600 &self,
4601 stmt: &SelectStatement,
4602 primary: &TableRef,
4603 cancel: CancelToken<'_>,
4604 ) -> Result<QueryResult, EngineError> {
4605 let expr = primary
4606 .unnest_expr
4607 .as_deref()
4608 .expect("caller guards unnest_expr.is_some()");
4609 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
4612 let ctx = EvalContext::new(&empty_schema, None);
4613 let dummy_row = Row::new(alloc::vec::Vec::new());
4614 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
4617 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
4618 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
4619 Value::TextArray(items) => {
4620 let rows = items
4621 .into_iter()
4622 .map(|item| {
4623 Row::new(alloc::vec![match item {
4624 Some(s) => Value::Text(s),
4625 None => Value::Null,
4626 }])
4627 })
4628 .collect();
4629 (DataType::Text, rows)
4630 }
4631 Value::IntArray(items) => {
4632 let rows = items
4633 .into_iter()
4634 .map(|item| {
4635 Row::new(alloc::vec![match item {
4636 Some(n) => Value::Int(n),
4637 None => Value::Null,
4638 }])
4639 })
4640 .collect();
4641 (DataType::Int, rows)
4642 }
4643 Value::BigIntArray(items) => {
4644 let rows = items
4645 .into_iter()
4646 .map(|item| {
4647 Row::new(alloc::vec![match item {
4648 Some(n) => Value::BigInt(n),
4649 None => Value::Null,
4650 }])
4651 })
4652 .collect();
4653 (DataType::BigInt, rows)
4654 }
4655 other => {
4656 return Err(EngineError::Unsupported(alloc::format!(
4657 "unnest() expects an array argument, got {:?}",
4658 other.data_type()
4659 )));
4660 }
4661 };
4662 let alias = primary
4663 .alias
4664 .clone()
4665 .unwrap_or_else(|| "unnest".to_string());
4666 let col_schema = ColumnSchema::new(alias.clone(), elem_dtype, true);
4667 let schema_cols = alloc::vec![col_schema.clone()];
4668 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
4669 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
4671 let mut out = alloc::vec::Vec::with_capacity(rows.len());
4672 for row in rows {
4673 cancel.check()?;
4674 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
4675 if matches!(v, Value::Bool(true)) {
4676 out.push(row);
4677 }
4678 }
4679 out
4680 } else {
4681 rows
4682 };
4683 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
4685 let mut projected_rows: alloc::vec::Vec<Row> =
4686 alloc::vec::Vec::with_capacity(filtered.len());
4687 for row in &filtered {
4688 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
4689 for p in &projection {
4690 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
4691 }
4692 projected_rows.push(Row::new(vals));
4693 }
4694 let columns: alloc::vec::Vec<ColumnSchema> = projection
4697 .iter()
4698 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
4699 .collect();
4700 if !stmt.order_by.is_empty() {
4703 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
4704 .iter()
4705 .enumerate()
4706 .map(|(i, r)| -> Result<_, EngineError> {
4707 let keys: Result<Vec<Value>, EngineError> = stmt
4708 .order_by
4709 .iter()
4710 .map(|ob| {
4711 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
4712 })
4713 .collect();
4714 Ok((i, keys?))
4715 })
4716 .collect::<Result<_, _>>()?;
4717 indexed.sort_by(|a, b| {
4718 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
4719 let mut cmp = value_cmp(ka, kb);
4720 if stmt.order_by[idx].desc {
4721 cmp = cmp.reverse();
4722 }
4723 if cmp != core::cmp::Ordering::Equal {
4724 return cmp;
4725 }
4726 }
4727 core::cmp::Ordering::Equal
4728 });
4729 projected_rows = indexed
4730 .into_iter()
4731 .map(|(i, _)| projected_rows[i].clone())
4732 .collect();
4733 }
4734 if let Some(offset) = stmt.offset_literal() {
4736 let off = (offset as usize).min(projected_rows.len());
4737 projected_rows.drain(..off);
4738 }
4739 if let Some(limit) = stmt.limit_literal() {
4740 projected_rows.truncate(limit as usize);
4741 }
4742 Ok(QueryResult::Rows {
4743 columns,
4744 rows: projected_rows,
4745 })
4746 }
4747
4748 fn exec_bare_select_cancel(
4749 &self,
4750 stmt: &SelectStatement,
4751 cancel: CancelToken<'_>,
4752 ) -> Result<QueryResult, EngineError> {
4753 if select_has_window(stmt) {
4758 return self.exec_select_with_window(stmt, cancel);
4759 }
4760 let Some(from) = &stmt.from else {
4765 let empty_schema: Vec<ColumnSchema> = Vec::new();
4766 let ctx = self.ev_ctx(&empty_schema, None);
4767 let projection = build_projection(&stmt.items, &empty_schema, "")?;
4768 let dummy_row = Row::new(Vec::new());
4769 let mut values = Vec::with_capacity(projection.len());
4770 for p in &projection {
4771 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
4772 }
4773 let columns: Vec<ColumnSchema> = projection
4774 .into_iter()
4775 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
4776 .collect();
4777 return Ok(QueryResult::Rows {
4778 columns,
4779 rows: alloc::vec![Row::new(values)],
4780 });
4781 };
4782 if !from.joins.is_empty() {
4786 return self.exec_joined_select(stmt, from);
4787 }
4788 if from.primary.unnest_expr.is_some() {
4795 return self.exec_select_unnest(stmt, &from.primary, cancel);
4796 }
4797 let primary = &from.primary;
4798 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
4799 StorageError::TableNotFound {
4800 name: primary.name.clone(),
4801 }
4802 })?;
4803 let schema_cols = &table.schema().columns;
4804 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
4807 let ctx = self.ev_ctx(schema_cols, Some(alias));
4808
4809 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
4814 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
4815 }
4816
4817 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
4825 try_index_seek(w, schema_cols, self.active_catalog(), table, alias).or_else(|| {
4828 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
4833 })
4834 });
4835
4836 if aggregate::uses_aggregate(stmt) {
4839 let mut filtered: Vec<&Row> = Vec::new();
4840 let mut memo = memoize::MemoizeCache::new();
4844 if let Some(rows) = &indexed_rows {
4845 for cow in rows {
4846 let row = cow.as_ref();
4847 if let Some(where_expr) = &stmt.where_ {
4848 let cond = self.eval_expr_with_correlated(
4849 where_expr,
4850 row,
4851 &ctx,
4852 cancel,
4853 Some(&mut memo),
4854 )?;
4855 if !matches!(cond, Value::Bool(true)) {
4856 continue;
4857 }
4858 }
4859 filtered.push(row);
4860 }
4861 } else {
4862 for i in 0..table.row_count() {
4863 let row = &table.rows()[i];
4864 if let Some(where_expr) = &stmt.where_ {
4865 let cond = self.eval_expr_with_correlated(
4866 where_expr,
4867 row,
4868 &ctx,
4869 cancel,
4870 Some(&mut memo),
4871 )?;
4872 if !matches!(cond, Value::Bool(true)) {
4873 continue;
4874 }
4875 }
4876 filtered.push(row);
4877 }
4878 }
4879 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
4880 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
4881 return Ok(QueryResult::Rows {
4882 columns: agg.columns,
4883 rows: agg.rows,
4884 });
4885 }
4886
4887 let projection = build_projection(&stmt.items, schema_cols, alias)?;
4888
4889 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
4892 let mut memo = memoize::MemoizeCache::new();
4894 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
4897 if loop_idx.is_multiple_of(256) {
4898 cancel.check()?;
4899 }
4900 if let Some(where_expr) = &stmt.where_ {
4901 let cond =
4902 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
4903 if !matches!(cond, Value::Bool(true)) {
4904 return Ok(());
4905 }
4906 }
4907 let mut values = Vec::with_capacity(projection.len());
4908 for p in &projection {
4909 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
4910 }
4911 let order_keys = if stmt.order_by.is_empty() {
4912 Vec::new()
4913 } else {
4914 build_order_keys(&stmt.order_by, row, &ctx)?
4915 };
4916 tagged.push((order_keys, Row::new(values)));
4917 Ok(())
4918 };
4919 if let Some(rows) = &indexed_rows {
4920 for (loop_idx, cow) in rows.iter().enumerate() {
4921 process_row(cow.as_ref(), loop_idx)?;
4922 }
4923 } else {
4924 for i in 0..table.row_count() {
4925 process_row(&table.rows()[i], i)?;
4926 }
4927 }
4928
4929 if !stmt.order_by.is_empty() {
4930 let keep = if stmt.distinct {
4935 None
4936 } else {
4937 stmt.limit_literal()
4938 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
4939 };
4940 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
4941 partial_sort_tagged(&mut tagged, keep, &descs);
4942 }
4943
4944 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
4945 if stmt.distinct {
4946 output_rows = dedup_rows(output_rows);
4947 }
4948 apply_offset_and_limit(
4949 &mut output_rows,
4950 stmt.offset_literal(),
4951 stmt.limit_literal(),
4952 );
4953
4954 let columns: Vec<ColumnSchema> = projection
4955 .into_iter()
4956 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
4957 .collect();
4958
4959 Ok(QueryResult::Rows {
4960 columns,
4961 rows: output_rows,
4962 })
4963 }
4964
4965 #[allow(clippy::too_many_lines)]
4972 fn exec_joined_select(
4973 &self,
4974 stmt: &SelectStatement,
4975 from: &FromClause,
4976 ) -> Result<QueryResult, EngineError> {
4977 let primary_table = self
4980 .active_catalog()
4981 .get(&from.primary.name)
4982 .ok_or_else(|| StorageError::TableNotFound {
4983 name: from.primary.name.clone(),
4984 })?;
4985 let primary_alias = from
4986 .primary
4987 .alias
4988 .as_deref()
4989 .unwrap_or(from.primary.name.as_str())
4990 .to_string();
4991 let mut joined_tables: Vec<(&Table, String, JoinKind, Option<&Expr>)> = Vec::new();
4992 for j in &from.joins {
4993 let t = self.active_catalog().get(&j.table.name).ok_or_else(|| {
4994 StorageError::TableNotFound {
4995 name: j.table.name.clone(),
4996 }
4997 })?;
4998 let a = j
4999 .table
5000 .alias
5001 .as_deref()
5002 .unwrap_or(j.table.name.as_str())
5003 .to_string();
5004 joined_tables.push((t, a, j.kind, j.on.as_ref()));
5005 }
5006
5007 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
5010 for col in &primary_table.schema().columns {
5011 combined_schema.push(ColumnSchema::new(
5012 alloc::format!("{primary_alias}.{}", col.name),
5013 col.ty,
5014 col.nullable,
5015 ));
5016 }
5017 for (t, a, _, _) in &joined_tables {
5018 for col in &t.schema().columns {
5019 combined_schema.push(ColumnSchema::new(
5020 alloc::format!("{a}.{}", col.name),
5021 col.ty,
5022 col.nullable,
5023 ));
5024 }
5025 }
5026 let ctx = EvalContext::new(&combined_schema, None);
5027
5028 let mut working: Vec<Row> = primary_table.rows().iter().cloned().collect();
5031 let mut produced_len = primary_table.schema().columns.len();
5032 for (t, _, kind, on) in &joined_tables {
5033 let right_arity = t.schema().columns.len();
5034 let mut next: Vec<Row> = Vec::new();
5035 for left in &working {
5036 let mut left_matched = false;
5037 for right in t.rows() {
5038 let mut combined_vals = left.values.clone();
5039 combined_vals.extend(right.values.iter().cloned());
5040 let combined = Row::new(combined_vals);
5043 let keep = if let Some(on_expr) = on {
5044 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
5045 matches!(cond, Value::Bool(true))
5046 } else {
5047 true
5049 };
5050 if keep {
5051 next.push(combined);
5052 left_matched = true;
5053 }
5054 }
5055 if !left_matched && matches!(kind, JoinKind::Left) {
5056 let mut combined_vals = left.values.clone();
5059 for _ in 0..right_arity {
5060 combined_vals.push(Value::Null);
5061 }
5062 next.push(Row::new(combined_vals));
5063 }
5064 }
5065 working = next;
5066 produced_len += right_arity;
5067 debug_assert!(produced_len <= combined_schema.len());
5068 }
5069
5070 let mut filtered: Vec<Row> = Vec::new();
5072 for row in working {
5073 if let Some(where_expr) = &stmt.where_ {
5074 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
5075 if !matches!(cond, Value::Bool(true)) {
5076 continue;
5077 }
5078 }
5079 filtered.push(row);
5080 }
5081
5082 if aggregate::uses_aggregate(stmt) {
5085 let refs: Vec<&Row> = filtered.iter().collect();
5086 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
5087 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
5088 return Ok(QueryResult::Rows {
5089 columns: agg.columns,
5090 rows: agg.rows,
5091 });
5092 }
5093
5094 let projection = build_projection(&stmt.items, &combined_schema, "")?;
5095 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
5096 for row in &filtered {
5097 let mut values = Vec::with_capacity(projection.len());
5098 for p in &projection {
5099 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
5100 }
5101 let order_keys = if stmt.order_by.is_empty() {
5102 Vec::new()
5103 } else {
5104 build_order_keys(&stmt.order_by, row, &ctx)?
5105 };
5106 tagged.push((order_keys, Row::new(values)));
5107 }
5108 if !stmt.order_by.is_empty() {
5109 let keep = if stmt.distinct {
5110 None
5111 } else {
5112 stmt.limit_literal()
5113 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
5114 };
5115 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
5116 partial_sort_tagged(&mut tagged, keep, &descs);
5117 }
5118 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
5119 if stmt.distinct {
5120 output_rows = dedup_rows(output_rows);
5121 }
5122 apply_offset_and_limit(
5123 &mut output_rows,
5124 stmt.offset_literal(),
5125 stmt.limit_literal(),
5126 );
5127 let columns: Vec<ColumnSchema> = projection
5128 .into_iter()
5129 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
5130 .collect();
5131 Ok(QueryResult::Rows {
5132 columns,
5133 rows: output_rows,
5134 })
5135 }
5136}
5137
5138#[derive(Debug, Clone)]
5141struct ProjectedItem {
5142 expr: Expr,
5143 output_name: String,
5144 ty: DataType,
5145 nullable: bool,
5146}
5147
5148fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
5154 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
5155 for r in rows {
5156 if !out.iter().any(|seen| seen == &r) {
5157 out.push(r);
5158 }
5159 }
5160 out
5161}
5162
5163fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
5167 match v {
5168 Value::Null => Ok(f64::INFINITY),
5169 Value::SmallInt(n) => Ok(f64::from(*n)),
5170 Value::Int(n) => Ok(f64::from(*n)),
5171 Value::Date(d) => Ok(f64::from(*d)),
5172 #[allow(clippy::cast_precision_loss)]
5173 Value::Timestamp(t) => Ok(*t as f64),
5174 #[allow(clippy::cast_precision_loss)]
5175 Value::Numeric { scaled, scale } => {
5176 let mut divisor = 1.0_f64;
5182 for _ in 0..*scale {
5183 divisor *= 10.0;
5184 }
5185 Ok((*scaled as f64) / divisor)
5186 }
5187 #[allow(clippy::cast_precision_loss)]
5188 Value::BigInt(n) => Ok(*n as f64),
5189 Value::Float(x) => Ok(*x),
5190 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
5191 Value::Text(s) => {
5192 let mut key: u64 = 0;
5196 for &b in s.as_bytes().iter().take(8) {
5197 key = (key << 8) | u64::from(b);
5198 }
5199 #[allow(clippy::cast_precision_loss)]
5200 Ok(key as f64)
5201 }
5202 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
5203 Err(EngineError::Unsupported(
5204 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
5205 ))
5206 }
5207 Value::Interval { .. } => Err(EngineError::Unsupported(
5208 "ORDER BY of an INTERVAL is not supported in v2.11 \
5209 (months vs micros has no single canonical ordering)"
5210 .into(),
5211 )),
5212 Value::Json(_) => Err(EngineError::Unsupported(
5213 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
5214 )),
5215 _ => Err(EngineError::Unsupported(
5219 "ORDER BY of this value type is not supported".into(),
5220 )),
5221 }
5222}
5223
5224fn try_nsw_knn(
5238 stmt: &SelectStatement,
5239 table: &Table,
5240 schema_cols: &[ColumnSchema],
5241 table_alias: &str,
5242) -> Option<Vec<usize>> {
5243 if stmt.distinct {
5244 return None;
5245 }
5246 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
5247 if limit == 0 {
5248 return None;
5249 }
5250 if stmt.order_by.len() != 1 {
5254 return None;
5255 }
5256 let order = &stmt.order_by[0];
5257 if order.desc {
5261 return None;
5262 }
5263 let Expr::Binary { lhs, op, rhs } = &order.expr else {
5264 return None;
5265 };
5266 let metric = match op {
5267 BinOp::L2Distance => spg_storage::NswMetric::L2,
5268 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
5269 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
5270 _ => return None,
5271 };
5272 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
5274 (lhs.as_ref(), rhs.as_ref())
5275 else {
5276 return None;
5277 };
5278 if let Some(q) = &col.qualifier
5279 && q != table_alias
5280 {
5281 return None;
5282 }
5283 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
5284 let query = literal_to_vector(literal)?;
5285 let idx = spg_storage::nsw_index_on(table, col_pos)?;
5286 if let Some(where_expr) = &stmt.where_ {
5287 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
5291 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
5292 let ctx = EvalContext::new(schema_cols, Some(table_alias));
5293 let mut kept: Vec<usize> = Vec::with_capacity(limit);
5294 for i in candidates {
5295 let row = &table.rows()[i];
5296 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
5297 if matches!(cond, Value::Bool(true)) {
5298 kept.push(i);
5299 if kept.len() >= limit {
5300 break;
5301 }
5302 }
5303 }
5304 Some(kept)
5305 } else {
5306 Some(spg_storage::nsw_query(
5307 table, &idx.name, &query, limit, metric,
5308 ))
5309 }
5310}
5311
5312const NSW_OVER_FETCH_FLOOR: usize = 32;
5316
5317fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
5320 match e {
5321 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
5322 Expr::Cast { expr, .. } => literal_to_vector(expr),
5323 _ => None,
5324 }
5325}
5326
5327fn materialise_in_order(
5331 stmt: &SelectStatement,
5332 table: &Table,
5333 schema_cols: &[ColumnSchema],
5334 table_alias: &str,
5335 ordered_rows: &[usize],
5336) -> Result<QueryResult, EngineError> {
5337 let ctx = EvalContext::new(schema_cols, Some(table_alias));
5338 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
5339 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
5340 for &i in ordered_rows {
5341 let row = &table.rows()[i];
5342 let mut values = Vec::with_capacity(projection.len());
5343 for p in &projection {
5344 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
5345 }
5346 output_rows.push(Row::new(values));
5347 }
5348 apply_offset_and_limit(
5349 &mut output_rows,
5350 stmt.offset_literal(),
5351 stmt.limit_literal(),
5352 );
5353 let columns: Vec<ColumnSchema> = projection
5354 .into_iter()
5355 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
5356 .collect();
5357 Ok(QueryResult::Rows {
5358 columns,
5359 rows: output_rows,
5360 })
5361}
5362
5363fn try_index_seek<'a>(
5364 where_expr: &Expr,
5365 schema_cols: &[ColumnSchema],
5366 catalog: &'a Catalog,
5367 table: &'a Table,
5368 table_alias: &str,
5369) -> Option<Vec<Cow<'a, Row>>> {
5370 if let Expr::Binary {
5377 lhs,
5378 op: BinOp::And,
5379 rhs,
5380 } = where_expr
5381 {
5382 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
5385 return Some(rows);
5386 }
5387 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
5388 }
5389 let Expr::Binary {
5390 lhs,
5391 op: BinOp::Eq,
5392 rhs,
5393 } = where_expr
5394 else {
5395 return None;
5396 };
5397 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
5398 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
5399 let idx = table.index_on(col_pos)?;
5400 let key = IndexKey::from_value(&value)?;
5401 let locators = idx.lookup_eq(&key);
5402 let table_name = table.schema().name.as_str();
5403 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
5411 for loc in locators {
5412 match *loc {
5413 spg_storage::RowLocator::Hot(i) => {
5414 if let Some(row) = table.rows().get(i) {
5415 out.push(Cow::Borrowed(row));
5416 }
5417 }
5418 spg_storage::RowLocator::Cold { segment_id, .. } => {
5419 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
5420 out.push(Cow::Owned(row));
5421 }
5422 }
5423 }
5424 }
5425 Some(out)
5426}
5427
5428fn try_gin_seek<'a>(
5447 where_expr: &Expr,
5448 schema_cols: &[ColumnSchema],
5449 catalog: &'a Catalog,
5450 table: &'a Table,
5451 table_alias: &str,
5452 ctx: &eval::EvalContext<'_>,
5453) -> Option<Vec<Cow<'a, Row>>> {
5454 if let Expr::Binary {
5455 lhs,
5456 op: BinOp::And,
5457 rhs,
5458 } = where_expr
5459 {
5460 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
5461 return Some(rows);
5462 }
5463 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
5464 }
5465 let Expr::Binary {
5466 lhs,
5467 op: BinOp::TsMatch,
5468 rhs,
5469 } = where_expr
5470 else {
5471 return None;
5472 };
5473 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
5478 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
5479 let idx = table
5480 .indices()
5481 .iter()
5482 .find(|i| i.column_position == col_pos && i.is_gin())?;
5483 let candidates = gin_query_candidates(idx, &query)?;
5484 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
5486 for loc in candidates {
5487 match loc {
5488 spg_storage::RowLocator::Hot(i) => {
5489 if let Some(row) = table.rows().get(i) {
5490 out.push(Cow::Borrowed(row));
5491 }
5492 }
5493 spg_storage::RowLocator::Cold { .. } => {}
5500 }
5501 }
5502 Some(out)
5503}
5504
5505fn resolve_gin_col_query(
5511 col_side: &Expr,
5512 query_side: &Expr,
5513 schema_cols: &[ColumnSchema],
5514 table_alias: &str,
5515 ctx: &eval::EvalContext<'_>,
5516) -> Option<(usize, spg_storage::TsQueryAst)> {
5517 let Expr::Column(c) = col_side else {
5518 return None;
5519 };
5520 if let Some(q) = &c.qualifier
5521 && q != table_alias
5522 {
5523 return None;
5524 }
5525 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
5526 let empty_row = Row::new(Vec::new());
5530 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
5531 let Value::TsQuery(q) = v else { return None };
5532 Some((pos, q))
5533}
5534
5535fn gin_query_candidates(
5546 idx: &spg_storage::Index,
5547 query: &spg_storage::TsQueryAst,
5548) -> Option<Vec<spg_storage::RowLocator>> {
5549 use spg_storage::TsQueryAst;
5550 match query {
5551 TsQueryAst::Term { word, .. } => {
5552 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
5553 v.sort_by_key(locator_sort_key);
5554 v.dedup_by_key(|l| locator_sort_key(l));
5555 Some(v)
5556 }
5557 TsQueryAst::And(l, r) => {
5558 let mut left = gin_query_candidates(idx, l)?;
5559 let mut right = gin_query_candidates(idx, r)?;
5560 left.sort_by_key(locator_sort_key);
5561 right.sort_by_key(locator_sort_key);
5562 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
5564 let (mut i, mut j) = (0usize, 0usize);
5565 while i < left.len() && j < right.len() {
5566 let lk = locator_sort_key(&left[i]);
5567 let rk = locator_sort_key(&right[j]);
5568 match lk.cmp(&rk) {
5569 core::cmp::Ordering::Less => i += 1,
5570 core::cmp::Ordering::Greater => j += 1,
5571 core::cmp::Ordering::Equal => {
5572 out.push(left[i]);
5573 i += 1;
5574 j += 1;
5575 }
5576 }
5577 }
5578 Some(out)
5579 }
5580 TsQueryAst::Or(l, r) => {
5581 let mut out = gin_query_candidates(idx, l)?;
5582 out.extend(gin_query_candidates(idx, r)?);
5583 out.sort_by_key(locator_sort_key);
5584 out.dedup_by_key(|l| locator_sort_key(l));
5585 Some(out)
5586 }
5587 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
5592 }
5593}
5594
5595fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
5600 match *l {
5601 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
5602 spg_storage::RowLocator::Cold {
5603 segment_id,
5604 page_offset,
5605 } => (1, u64::from(segment_id), u64::from(page_offset)),
5606 }
5607}
5608
5609fn try_pk_predicate(
5621 where_expr: &Expr,
5622 schema_cols: &[ColumnSchema],
5623 table_alias: &str,
5624) -> Option<(usize, IndexKey)> {
5625 let Expr::Binary {
5626 lhs,
5627 op: BinOp::Eq,
5628 rhs,
5629 } = where_expr
5630 else {
5631 return None;
5632 };
5633 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
5634 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
5635 let key = IndexKey::from_value(&value)?;
5636 Some((col_pos, key))
5637}
5638
5639fn resolve_col_literal_pair(
5640 col_side: &Expr,
5641 lit_side: &Expr,
5642 schema_cols: &[ColumnSchema],
5643 table_alias: &str,
5644) -> Option<(usize, Value)> {
5645 let Expr::Column(c) = col_side else {
5646 return None;
5647 };
5648 if let Some(q) = &c.qualifier
5649 && q != table_alias
5650 {
5651 return None;
5652 }
5653 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
5654 let Expr::Literal(l) = lit_side else {
5655 return None;
5656 };
5657 let v = match l {
5658 Literal::Integer(n) => {
5659 if let Ok(small) = i32::try_from(*n) {
5660 Value::Int(small)
5661 } else {
5662 Value::BigInt(*n)
5663 }
5664 }
5665 Literal::Float(x) => Value::Float(*x),
5666 Literal::String(s) => Value::Text(s.clone()),
5667 Literal::Bool(b) => Value::Bool(*b),
5668 Literal::Null => Value::Null,
5669 Literal::Vector(_) | Literal::Interval { .. } => return None,
5672 };
5673 Some((pos, v))
5674}
5675
5676fn resolve_projection_column<'a>(
5681 c: &ColumnName,
5682 schema_cols: &'a [ColumnSchema],
5683 table_alias: &str,
5684) -> Result<&'a ColumnSchema, EngineError> {
5685 if let Some(q) = &c.qualifier {
5686 let composite = alloc::format!("{q}.{name}", name = c.name);
5687 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
5688 return Ok(s);
5689 }
5690 if q == table_alias
5693 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
5694 {
5695 return Ok(s);
5696 }
5697 let prefix = alloc::format!("{q}.");
5701 let qualifier_known =
5702 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
5703 if !qualifier_known {
5704 return Err(EngineError::Eval(EvalError::UnknownQualifier {
5705 qualifier: q.clone(),
5706 }));
5707 }
5708 return Err(EngineError::Eval(EvalError::ColumnNotFound {
5709 name: c.name.clone(),
5710 }));
5711 }
5712 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
5713 return Ok(s);
5714 }
5715 let suffix = alloc::format!(".{name}", name = c.name);
5716 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
5717 let first = matches.next();
5718 let extra = matches.next();
5719 match (first, extra) {
5720 (Some(s), None) => Ok(s),
5721 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
5722 detail: alloc::format!("ambiguous column reference: {}", c.name),
5723 })),
5724 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
5725 name: c.name.clone(),
5726 })),
5727 }
5728}
5729
5730fn build_projection(
5731 items: &[SelectItem],
5732 schema_cols: &[ColumnSchema],
5733 table_alias: &str,
5734) -> Result<Vec<ProjectedItem>, EngineError> {
5735 let mut out = Vec::new();
5736 for item in items {
5737 match item {
5738 SelectItem::Wildcard => {
5739 for col in schema_cols {
5740 out.push(ProjectedItem {
5741 expr: Expr::Column(ColumnName {
5742 qualifier: None,
5743 name: col.name.clone(),
5744 }),
5745 output_name: col.name.clone(),
5746 ty: col.ty,
5747 nullable: col.nullable,
5748 });
5749 }
5750 }
5751 SelectItem::Expr { expr, alias } => {
5752 if let Expr::Column(c) = expr {
5757 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
5758 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
5759 out.push(ProjectedItem {
5760 expr: expr.clone(),
5761 output_name,
5762 ty: sch.ty,
5763 nullable: sch.nullable,
5764 });
5765 } else {
5766 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
5767 out.push(ProjectedItem {
5768 expr: expr.clone(),
5769 output_name,
5770 ty: DataType::Text,
5771 nullable: true,
5772 });
5773 }
5774 }
5775 }
5776 }
5777 Ok(out)
5778}
5779
5780fn numeric_from_integer(
5784 n: i128,
5785 precision: u8,
5786 scale: u8,
5787 col_name: &str,
5788) -> Result<Value, EngineError> {
5789 let factor = pow10_i128(scale);
5790 let scaled = n.checked_mul(factor).ok_or_else(|| {
5791 EngineError::Unsupported(alloc::format!(
5792 "integer overflow scaling value for column `{col_name}` to scale {scale}"
5793 ))
5794 })?;
5795 check_precision(scaled, precision, col_name)?;
5796 Ok(Value::Numeric { scaled, scale })
5797}
5798
5799#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
5802fn numeric_from_float(
5803 x: f64,
5804 precision: u8,
5805 scale: u8,
5806 col_name: &str,
5807) -> Result<Value, EngineError> {
5808 if !x.is_finite() {
5809 return Err(EngineError::Unsupported(alloc::format!(
5810 "cannot store non-finite float in NUMERIC column `{col_name}`"
5811 )));
5812 }
5813 let mut factor = 1.0_f64;
5814 for _ in 0..scale {
5815 factor *= 10.0;
5816 }
5817 let shifted = x * factor;
5822 let biased = if shifted >= 0.0 {
5823 shifted + 0.5
5824 } else {
5825 shifted - 0.5
5826 };
5827 if !(-1e38..=1e38).contains(&biased) {
5830 return Err(EngineError::Unsupported(alloc::format!(
5831 "value {x} overflows NUMERIC range for column `{col_name}`"
5832 )));
5833 }
5834 let scaled = biased as i128;
5835 check_precision(scaled, precision, col_name)?;
5836 Ok(Value::Numeric { scaled, scale })
5837}
5838
5839fn numeric_rescale(
5842 scaled: i128,
5843 src_scale: u8,
5844 precision: u8,
5845 dst_scale: u8,
5846 col_name: &str,
5847) -> Result<Value, EngineError> {
5848 let new_scaled = if dst_scale >= src_scale {
5849 let bump = pow10_i128(dst_scale - src_scale);
5850 scaled.checked_mul(bump).ok_or_else(|| {
5851 EngineError::Unsupported(alloc::format!(
5852 "overflow rescaling NUMERIC for column `{col_name}`"
5853 ))
5854 })?
5855 } else {
5856 let drop = pow10_i128(src_scale - dst_scale);
5857 let half = drop / 2;
5858 if scaled >= 0 {
5859 (scaled + half) / drop
5860 } else {
5861 (scaled - half) / drop
5862 }
5863 };
5864 check_precision(new_scaled, precision, col_name)?;
5865 Ok(Value::Numeric {
5866 scaled: new_scaled,
5867 scale: dst_scale,
5868 })
5869}
5870
5871const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
5874 if scale == 0 {
5875 return scaled;
5876 }
5877 let factor = pow10_i128_const(scale);
5878 scaled / factor
5879}
5880
5881fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
5885 if precision == 0 {
5886 return Ok(());
5887 }
5888 let limit = pow10_i128(precision);
5889 if scaled.unsigned_abs() >= limit.unsigned_abs() {
5890 return Err(EngineError::Unsupported(alloc::format!(
5891 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
5892 )));
5893 }
5894 Ok(())
5895}
5896
5897const fn pow10_i128_const(p: u8) -> i128 {
5898 let mut acc: i128 = 1;
5899 let mut i = 0;
5900 while i < p {
5901 acc *= 10;
5902 i += 1;
5903 }
5904 acc
5905}
5906
5907fn pow10_i128(p: u8) -> i128 {
5908 pow10_i128_const(p)
5909}
5910
5911impl Engine {
5926 #[allow(
5937 clippy::too_many_lines,
5938 clippy::type_complexity,
5939 clippy::needless_range_loop
5940 )] fn exec_select_with_window(
5942 &self,
5943 stmt: &SelectStatement,
5944 cancel: CancelToken<'_>,
5945 ) -> Result<QueryResult, EngineError> {
5946 let from = stmt.from.as_ref().ok_or_else(|| {
5947 EngineError::Unsupported("window functions require a FROM clause".into())
5948 })?;
5949 if !from.joins.is_empty() {
5952 return Err(EngineError::Unsupported(
5953 "JOIN with window functions not yet supported".into(),
5954 ));
5955 }
5956 let primary = &from.primary;
5957 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
5958 StorageError::TableNotFound {
5959 name: primary.name.clone(),
5960 }
5961 })?;
5962 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
5963 let schema_cols = &table.schema().columns;
5964 let ctx = self.ev_ctx(schema_cols, Some(alias));
5965
5966 let mut filtered: Vec<&Row> = Vec::new();
5968 for (i, row) in table.rows().iter().enumerate() {
5969 if i.is_multiple_of(256) {
5970 cancel.check()?;
5971 }
5972 if let Some(w) = &stmt.where_ {
5973 let cond = eval::eval_expr(w, row, &ctx)?;
5974 if !matches!(cond, Value::Bool(true)) {
5975 continue;
5976 }
5977 }
5978 filtered.push(row);
5979 }
5980 let n_rows = filtered.len();
5981
5982 let mut window_nodes: Vec<Expr> = Vec::new();
5984 for item in &stmt.items {
5985 if let SelectItem::Expr { expr, .. } = item {
5986 collect_window_nodes(expr, &mut window_nodes);
5987 }
5988 }
5989
5990 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
5993 for wnode in &window_nodes {
5994 let Expr::WindowFunction {
5995 name,
5996 args,
5997 partition_by,
5998 order_by,
5999 frame,
6000 null_treatment,
6001 } = wnode
6002 else {
6003 unreachable!("collect_window_nodes pushes only WindowFunction");
6004 };
6005 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
6007 Vec::with_capacity(n_rows);
6008 for (i, row) in filtered.iter().enumerate() {
6009 let pkey: Vec<Value> = partition_by
6010 .iter()
6011 .map(|p| eval::eval_expr(p, row, &ctx))
6012 .collect::<Result<_, _>>()?;
6013 let okey: Vec<(Value, bool)> = order_by
6014 .iter()
6015 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
6016 .collect::<Result<_, _>>()?;
6017 indexed.push((pkey, okey, i));
6018 }
6019 indexed.sort_by(|a, b| {
6022 let p_cmp = partition_key_cmp(&a.0, &b.0);
6023 if p_cmp != core::cmp::Ordering::Equal {
6024 return p_cmp;
6025 }
6026 order_key_cmp(&a.1, &b.1)
6027 });
6028 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
6030 let mut p_start = 0;
6031 while p_start < indexed.len() {
6032 let mut p_end = p_start + 1;
6033 while p_end < indexed.len()
6034 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
6035 == core::cmp::Ordering::Equal
6036 {
6037 p_end += 1;
6038 }
6039 compute_window_partition(
6041 name,
6042 args,
6043 !order_by.is_empty(),
6044 frame.as_ref(),
6045 *null_treatment,
6046 &indexed[p_start..p_end],
6047 &filtered,
6048 &ctx,
6049 &mut out_vals,
6050 )?;
6051 p_start = p_end;
6052 }
6053 win_vals.push(out_vals);
6054 }
6055
6056 let mut ext_cols = schema_cols.clone();
6058 for i in 0..window_nodes.len() {
6059 ext_cols.push(ColumnSchema::new(
6060 alloc::format!("__win_{i}"),
6061 DataType::Text, true,
6063 ));
6064 }
6065 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
6067 for i in 0..n_rows {
6068 let mut values = filtered[i].values.clone();
6069 for w in 0..window_nodes.len() {
6070 values.push(win_vals[w][i].clone());
6071 }
6072 ext_rows.push(Row::new(values));
6073 }
6074 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
6076 for item in &stmt.items {
6077 let new_item = match item {
6078 SelectItem::Wildcard => SelectItem::Wildcard,
6079 SelectItem::Expr { expr, alias } => {
6080 let mut e = expr.clone();
6081 rewrite_window_to_columns(&mut e, &window_nodes);
6082 SelectItem::Expr {
6083 expr: e,
6084 alias: alias.clone(),
6085 }
6086 }
6087 };
6088 rewritten_items.push(new_item);
6089 }
6090
6091 let ext_ctx = EvalContext::new(&ext_cols, Some(alias));
6093 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
6094 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
6095 for (i, row) in ext_rows.iter().enumerate() {
6096 if i.is_multiple_of(256) {
6097 cancel.check()?;
6098 }
6099 let mut values = Vec::with_capacity(projection.len());
6100 for p in &projection {
6101 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
6102 }
6103 let order_keys = if stmt.order_by.is_empty() {
6104 Vec::new()
6105 } else {
6106 let mut keys = Vec::with_capacity(stmt.order_by.len());
6107 for o in &stmt.order_by {
6108 let mut e = o.expr.clone();
6109 rewrite_window_to_columns(&mut e, &window_nodes);
6110 let key = eval::eval_expr(&e, row, &ext_ctx)?;
6111 keys.push(value_to_order_key(&key)?);
6112 }
6113 keys
6114 };
6115 tagged.push((order_keys, Row::new(values)));
6116 }
6117 if !stmt.order_by.is_empty() {
6119 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
6120 sort_by_keys(&mut tagged, &descs);
6121 }
6122 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
6123 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
6124 let final_cols: Vec<ColumnSchema> = projection
6125 .into_iter()
6126 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
6127 .collect();
6128 Ok(QueryResult::Rows {
6129 columns: final_cols,
6130 rows: out_rows,
6131 })
6132 }
6133
6134 fn exec_with_ctes(
6141 &self,
6142 stmt: &SelectStatement,
6143 cancel: CancelToken<'_>,
6144 ) -> Result<QueryResult, EngineError> {
6145 cancel.check()?;
6146 let mut catalog = self.active_catalog().clone();
6147 for cte in &stmt.ctes {
6148 if catalog.get(&cte.name).is_some() {
6149 return Err(EngineError::Unsupported(alloc::format!(
6150 "CTE name {:?} shadows an existing table; rename the CTE",
6151 cte.name
6152 )));
6153 }
6154 let (columns, rows) = if cte.recursive {
6155 self.materialise_recursive_cte(cte, &catalog, cancel)?
6156 } else {
6157 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
6158 let QueryResult::Rows { columns, rows } = body_result else {
6159 return Err(EngineError::Unsupported(alloc::format!(
6160 "CTE {:?} body did not return rows",
6161 cte.name
6162 )));
6163 };
6164 (columns, rows)
6165 };
6166 let inferred = infer_column_types(&columns, &rows);
6171 let mut columns = inferred;
6172 if !cte.column_overrides.is_empty() {
6174 if cte.column_overrides.len() != columns.len() {
6175 return Err(EngineError::Unsupported(alloc::format!(
6176 "CTE {:?} column list has {} names but body returns {} columns",
6177 cte.name,
6178 cte.column_overrides.len(),
6179 columns.len()
6180 )));
6181 }
6182 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
6183 col.name.clone_from(name);
6184 }
6185 }
6186 let schema = TableSchema::new(cte.name.clone(), columns);
6187 catalog.create_table(schema).map_err(EngineError::Storage)?;
6188 let table = catalog
6189 .get_mut(&cte.name)
6190 .expect("just-created CTE table must exist");
6191 for row in rows {
6192 table.insert(row).map_err(EngineError::Storage)?;
6193 }
6194 }
6195 let mut body = stmt.clone();
6198 body.ctes = Vec::new();
6199 let mut temp = Engine::restore(catalog);
6200 if let Some(c) = self.clock {
6201 temp = temp.with_clock(c);
6202 }
6203 if let Some(f) = self.salt_fn {
6204 temp = temp.with_salt_fn(f);
6205 }
6206 temp.exec_select_cancel(&body, cancel)
6207 }
6208
6209 #[allow(clippy::too_many_lines)]
6219 fn materialise_recursive_cte(
6220 &self,
6221 cte: &spg_sql::ast::Cte,
6222 base_catalog: &Catalog,
6223 cancel: CancelToken<'_>,
6224 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
6225 const MAX_TOTAL_ROWS: usize = 1_000_000;
6226 const MAX_ITERATIONS: usize = 100_000;
6227 cancel.check()?;
6228 if cte.body.unions.is_empty() {
6229 return Err(EngineError::Unsupported(alloc::format!(
6230 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
6231 cte.name
6232 )));
6233 }
6234 let mut anchor = cte.body.clone();
6236 let union_terms = core::mem::take(&mut anchor.unions);
6237 anchor.ctes = Vec::new();
6238 if select_refers_to(&anchor, &cte.name) {
6240 return Err(EngineError::Unsupported(alloc::format!(
6241 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
6242 cte.name
6243 )));
6244 }
6245 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
6246 let QueryResult::Rows {
6247 columns: anchor_cols,
6248 rows: anchor_rows,
6249 } = anchor_result
6250 else {
6251 return Err(EngineError::Unsupported(alloc::format!(
6252 "WITH RECURSIVE {:?}: anchor did not return rows",
6253 cte.name
6254 )));
6255 };
6256 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
6260 if !cte.column_overrides.is_empty() {
6261 if cte.column_overrides.len() != columns.len() {
6262 return Err(EngineError::Unsupported(alloc::format!(
6263 "CTE {:?} column list has {} names but anchor returns {} columns",
6264 cte.name,
6265 cte.column_overrides.len(),
6266 columns.len()
6267 )));
6268 }
6269 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
6270 col.name.clone_from(name);
6271 }
6272 }
6273 let mut all_rows: Vec<Row> = anchor_rows.clone();
6274 let mut working_set: Vec<Row> = anchor_rows;
6275 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
6276 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
6279 if !all_union_all {
6280 for r in &all_rows {
6281 seen.insert(encode_row_key(r));
6282 }
6283 }
6284 for iter in 0..MAX_ITERATIONS {
6285 cancel.check()?;
6286 if working_set.is_empty() {
6287 break;
6288 }
6289 let mut iter_catalog = base_catalog.clone();
6291 let schema = TableSchema::new(cte.name.clone(), columns.clone());
6292 iter_catalog
6293 .create_table(schema)
6294 .map_err(EngineError::Storage)?;
6295 {
6296 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
6297 for row in &working_set {
6298 table.insert(row.clone()).map_err(EngineError::Storage)?;
6299 }
6300 }
6301 let mut iter_engine = Engine::restore(iter_catalog);
6302 if let Some(c) = self.clock {
6303 iter_engine = iter_engine.with_clock(c);
6304 }
6305 if let Some(f) = self.salt_fn {
6306 iter_engine = iter_engine.with_salt_fn(f);
6307 }
6308 let mut next_set: Vec<Row> = Vec::new();
6310 for (_, term) in &union_terms {
6311 let mut term = term.clone();
6312 term.ctes = Vec::new();
6313 let r = iter_engine.exec_select_cancel(&term, cancel)?;
6314 let QueryResult::Rows {
6315 columns: rc,
6316 rows: rs,
6317 } = r
6318 else {
6319 return Err(EngineError::Unsupported(alloc::format!(
6320 "WITH RECURSIVE {:?}: recursive term did not return rows",
6321 cte.name
6322 )));
6323 };
6324 if rc.len() != columns.len() {
6325 return Err(EngineError::Unsupported(alloc::format!(
6326 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
6327 cte.name,
6328 rc.len(),
6329 columns.len()
6330 )));
6331 }
6332 for row in rs {
6333 if !all_union_all {
6334 let key = encode_row_key(&row);
6335 if !seen.insert(key) {
6336 continue;
6337 }
6338 }
6339 next_set.push(row);
6340 }
6341 }
6342 if next_set.is_empty() {
6343 break;
6344 }
6345 all_rows.extend(next_set.iter().cloned());
6346 working_set = next_set;
6347 if all_rows.len() > MAX_TOTAL_ROWS {
6348 return Err(EngineError::Unsupported(alloc::format!(
6349 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
6350 cte.name
6351 )));
6352 }
6353 if iter + 1 == MAX_ITERATIONS {
6354 return Err(EngineError::Unsupported(alloc::format!(
6355 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
6356 cte.name
6357 )));
6358 }
6359 }
6360 Ok((columns, all_rows))
6361 }
6362
6363 fn resolve_select_subqueries(
6364 &self,
6365 stmt: &mut SelectStatement,
6366 cancel: CancelToken<'_>,
6367 ) -> Result<(), EngineError> {
6368 for item in &mut stmt.items {
6369 if let SelectItem::Expr { expr, .. } = item {
6370 self.resolve_expr_subqueries(expr, cancel)?;
6371 }
6372 }
6373 if let Some(w) = &mut stmt.where_ {
6374 self.resolve_expr_subqueries(w, cancel)?;
6375 }
6376 if let Some(gs) = &mut stmt.group_by {
6377 for g in gs {
6378 self.resolve_expr_subqueries(g, cancel)?;
6379 }
6380 }
6381 if let Some(h) = &mut stmt.having {
6382 self.resolve_expr_subqueries(h, cancel)?;
6383 }
6384 for o in &mut stmt.order_by {
6385 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
6386 }
6387 for (_, peer) in &mut stmt.unions {
6388 self.resolve_select_subqueries(peer, cancel)?;
6389 }
6390 Ok(())
6391 }
6392
6393 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
6395 &self,
6396 e: &mut Expr,
6397 cancel: CancelToken<'_>,
6398 ) -> Result<(), EngineError> {
6399 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
6401 *e = replacement;
6402 return Ok(());
6403 }
6404 match e {
6405 Expr::Binary { lhs, rhs, .. } => {
6406 self.resolve_expr_subqueries(lhs, cancel)?;
6407 self.resolve_expr_subqueries(rhs, cancel)?;
6408 }
6409 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
6410 self.resolve_expr_subqueries(expr, cancel)?;
6411 }
6412 Expr::FunctionCall { args, .. } => {
6413 for a in args {
6414 self.resolve_expr_subqueries(a, cancel)?;
6415 }
6416 }
6417 Expr::Like { expr, pattern, .. } => {
6418 self.resolve_expr_subqueries(expr, cancel)?;
6419 self.resolve_expr_subqueries(pattern, cancel)?;
6420 }
6421 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
6422 Expr::WindowFunction {
6425 args,
6426 partition_by,
6427 order_by,
6428 ..
6429 } => {
6430 for a in args {
6431 self.resolve_expr_subqueries(a, cancel)?;
6432 }
6433 for p in partition_by {
6434 self.resolve_expr_subqueries(p, cancel)?;
6435 }
6436 for (e, _) in order_by {
6437 self.resolve_expr_subqueries(e, cancel)?;
6438 }
6439 }
6440 Expr::ScalarSubquery(_)
6444 | Expr::Exists { .. }
6445 | Expr::InSubquery { .. }
6446 | Expr::Literal(_)
6447 | Expr::Placeholder(_)
6448 | Expr::Column(_) => {}
6449 Expr::Array(items) => {
6451 for elem in items {
6452 self.resolve_expr_subqueries(elem, cancel)?;
6453 }
6454 }
6455 Expr::ArraySubscript { target, index } => {
6456 self.resolve_expr_subqueries(target, cancel)?;
6457 self.resolve_expr_subqueries(index, cancel)?;
6458 }
6459 Expr::AnyAll { expr, array, .. } => {
6460 self.resolve_expr_subqueries(expr, cancel)?;
6461 self.resolve_expr_subqueries(array, cancel)?;
6462 }
6463 Expr::Case {
6464 operand,
6465 branches,
6466 else_branch,
6467 } => {
6468 if let Some(o) = operand {
6469 self.resolve_expr_subqueries(o, cancel)?;
6470 }
6471 for (w, t) in branches {
6472 self.resolve_expr_subqueries(w, cancel)?;
6473 self.resolve_expr_subqueries(t, cancel)?;
6474 }
6475 if let Some(e) = else_branch {
6476 self.resolve_expr_subqueries(e, cancel)?;
6477 }
6478 }
6479 }
6480 Ok(())
6481 }
6482
6483 fn eval_expr_with_correlated(
6491 &self,
6492 expr: &Expr,
6493 row: &Row,
6494 ctx: &EvalContext<'_>,
6495 cancel: CancelToken<'_>,
6496 memo: Option<&mut memoize::MemoizeCache>,
6497 ) -> Result<Value, EngineError> {
6498 if !expr_has_subquery(expr) {
6499 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
6500 }
6501 let mut e = expr.clone();
6502 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
6503 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
6504 }
6505
6506 fn resolve_correlated_in_expr(
6507 &self,
6508 e: &mut Expr,
6509 row: &Row,
6510 ctx: &EvalContext<'_>,
6511 cancel: CancelToken<'_>,
6512 mut memo: Option<&mut memoize::MemoizeCache>,
6513 ) -> Result<(), EngineError> {
6514 match e {
6515 Expr::ScalarSubquery(inner) => {
6516 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
6521 subquery_repr: alloc::format!("{}", **inner),
6522 outer_values: row.values.clone(),
6523 });
6524 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
6525 && let Some(cached) = cache.get(k)
6526 {
6527 *e = value_to_literal_expr(cached)?;
6528 return Ok(());
6529 }
6530 let mut s = (**inner).clone();
6531 substitute_outer_columns(&mut s, row, ctx);
6532 let r = self.exec_select_cancel(&s, cancel)?;
6533 let QueryResult::Rows { rows, .. } = r else {
6534 return Err(EngineError::Unsupported(
6535 "scalar subquery: inner did not return rows".into(),
6536 ));
6537 };
6538 let value = match rows.as_slice() {
6539 [] => Value::Null,
6540 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
6541 _ => {
6542 return Err(EngineError::Unsupported(alloc::format!(
6543 "scalar subquery returned {} rows; expected 0 or 1",
6544 rows.len()
6545 )));
6546 }
6547 };
6548 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
6549 cache.insert(k, value.clone());
6550 }
6551 *e = value_to_literal_expr(value)?;
6552 }
6553 Expr::Exists { subquery, negated } => {
6554 let mut s = (**subquery).clone();
6555 substitute_outer_columns(&mut s, row, ctx);
6556 let r = self.exec_select_cancel(&s, cancel)?;
6557 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
6558 let bit = if *negated { !exists } else { exists };
6559 *e = Expr::Literal(Literal::Bool(bit));
6560 }
6561 Expr::InSubquery {
6562 expr: lhs,
6563 subquery,
6564 negated,
6565 } => {
6566 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
6567 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
6568 let mut s = (**subquery).clone();
6569 substitute_outer_columns(&mut s, row, ctx);
6570 let r = self.exec_select_cancel(&s, cancel)?;
6571 let QueryResult::Rows { columns, rows, .. } = r else {
6572 return Err(EngineError::Unsupported(
6573 "IN-subquery: inner did not return rows".into(),
6574 ));
6575 };
6576 if columns.len() != 1 {
6577 return Err(EngineError::Unsupported(alloc::format!(
6578 "IN-subquery must project exactly one column; got {}",
6579 columns.len()
6580 )));
6581 }
6582 let mut found = false;
6583 let mut any_null = false;
6584 for r0 in rows {
6585 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
6586 if v.is_null() {
6587 any_null = true;
6588 continue;
6589 }
6590 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
6591 found = true;
6592 break;
6593 }
6594 }
6595 let bit = if found {
6596 !*negated
6597 } else if any_null {
6598 return Err(EngineError::Unsupported(
6599 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
6600 ));
6601 } else {
6602 *negated
6603 };
6604 *e = Expr::Literal(Literal::Bool(bit));
6605 }
6606 Expr::Binary { lhs, rhs, .. } => {
6607 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
6608 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
6609 }
6610 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
6611 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
6612 }
6613 Expr::Like { expr, pattern, .. } => {
6614 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
6615 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
6616 }
6617 Expr::FunctionCall { args, .. } => {
6618 for a in args {
6619 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
6620 }
6621 }
6622 Expr::Extract { source, .. } => {
6623 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
6624 }
6625 Expr::WindowFunction { .. }
6626 | Expr::Literal(_)
6627 | Expr::Placeholder(_)
6628 | Expr::Column(_) => {}
6629 Expr::Array(items) => {
6631 for elem in items {
6632 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
6633 }
6634 }
6635 Expr::ArraySubscript { target, index } => {
6636 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
6637 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
6638 }
6639 Expr::AnyAll { expr, array, .. } => {
6640 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
6641 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
6642 }
6643 Expr::Case {
6644 operand,
6645 branches,
6646 else_branch,
6647 } => {
6648 if let Some(o) = operand {
6649 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
6650 }
6651 for (w, t) in branches {
6652 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
6653 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
6654 }
6655 if let Some(e) = else_branch {
6656 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
6657 }
6658 }
6659 }
6660 Ok(())
6661 }
6662
6663 fn subquery_replacement(
6664 &self,
6665 e: &Expr,
6666 cancel: CancelToken<'_>,
6667 ) -> Result<Option<Expr>, EngineError> {
6668 match e {
6669 Expr::ScalarSubquery(inner) => {
6670 let mut s = (**inner).clone();
6671 self.resolve_select_subqueries(&mut s, cancel)?;
6674 let r = match self.exec_bare_select_cancel(&s, cancel) {
6675 Ok(r) => r,
6676 Err(e) if is_correlation_error(&e) => return Ok(None),
6677 Err(e) => return Err(e),
6678 };
6679 let QueryResult::Rows { rows, .. } = r else {
6680 return Err(EngineError::Unsupported(
6681 "scalar subquery: inner statement did not return rows".into(),
6682 ));
6683 };
6684 let value = match rows.as_slice() {
6685 [] => Value::Null,
6686 [row] => row.values.first().cloned().unwrap_or(Value::Null),
6687 _ => {
6688 return Err(EngineError::Unsupported(alloc::format!(
6689 "scalar subquery returned {} rows; expected 0 or 1",
6690 rows.len()
6691 )));
6692 }
6693 };
6694 Ok(Some(value_to_literal_expr(value)?))
6695 }
6696 Expr::Exists { subquery, negated } => {
6697 let mut s = (**subquery).clone();
6698 self.resolve_select_subqueries(&mut s, cancel)?;
6699 let r = match self.exec_bare_select_cancel(&s, cancel) {
6700 Ok(r) => r,
6701 Err(e) if is_correlation_error(&e) => return Ok(None),
6702 Err(e) => return Err(e),
6703 };
6704 let exists = match r {
6705 QueryResult::Rows { rows, .. } => !rows.is_empty(),
6706 QueryResult::CommandOk { .. } => false,
6707 };
6708 let bit = if *negated { !exists } else { exists };
6709 Ok(Some(Expr::Literal(Literal::Bool(bit))))
6710 }
6711 Expr::InSubquery {
6712 expr,
6713 subquery,
6714 negated,
6715 } => {
6716 let mut s = (**subquery).clone();
6717 self.resolve_select_subqueries(&mut s, cancel)?;
6718 let r = match self.exec_bare_select_cancel(&s, cancel) {
6719 Ok(r) => r,
6720 Err(e) if is_correlation_error(&e) => return Ok(None),
6721 Err(e) => return Err(e),
6722 };
6723 let QueryResult::Rows { columns, rows, .. } = r else {
6724 return Err(EngineError::Unsupported(
6725 "IN-subquery: inner statement did not return rows".into(),
6726 ));
6727 };
6728 if columns.len() != 1 {
6729 return Err(EngineError::Unsupported(alloc::format!(
6730 "IN-subquery must project exactly one column; got {}",
6731 columns.len()
6732 )));
6733 }
6734 let mut acc: Option<Expr> = None;
6737 for row in rows {
6738 let v = row.values.into_iter().next().unwrap_or(Value::Null);
6739 let lit = value_to_literal_expr(v)?;
6740 let cmp = Expr::Binary {
6741 lhs: expr.clone(),
6742 op: BinOp::Eq,
6743 rhs: Box::new(lit),
6744 };
6745 acc = Some(match acc {
6746 None => cmp,
6747 Some(prev) => Expr::Binary {
6748 lhs: Box::new(prev),
6749 op: BinOp::Or,
6750 rhs: Box::new(cmp),
6751 },
6752 });
6753 }
6754 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
6755 let final_expr = if *negated {
6756 Expr::Unary {
6757 op: UnOp::Not,
6758 expr: Box::new(combined),
6759 }
6760 } else {
6761 combined
6762 };
6763 Ok(Some(final_expr))
6764 }
6765 _ => Ok(None),
6766 }
6767 }
6768}
6769
6770fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
6782 if let Some(from) = &stmt.from
6783 && from_refers_to(from, target)
6784 {
6785 return true;
6786 }
6787 for (_, peer) in &stmt.unions {
6788 if select_refers_to(peer, target) {
6789 return true;
6790 }
6791 }
6792 for item in &stmt.items {
6793 if let SelectItem::Expr { expr, .. } = item
6794 && expr_refers_to(expr, target)
6795 {
6796 return true;
6797 }
6798 }
6799 if let Some(w) = &stmt.where_
6800 && expr_refers_to(w, target)
6801 {
6802 return true;
6803 }
6804 false
6805}
6806
6807fn from_refers_to(from: &FromClause, target: &str) -> bool {
6808 if from.primary.name.eq_ignore_ascii_case(target) {
6809 return true;
6810 }
6811 from.joins
6812 .iter()
6813 .any(|j| j.table.name.eq_ignore_ascii_case(target))
6814}
6815
6816fn expr_refers_to(e: &Expr, target: &str) -> bool {
6817 match e {
6818 Expr::ScalarSubquery(s) => select_refers_to(s, target),
6819 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
6820 select_refers_to(subquery, target)
6821 }
6822 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
6823 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
6824 expr_refers_to(expr, target)
6825 }
6826 Expr::Like { expr, pattern, .. } => {
6827 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
6828 }
6829 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
6830 Expr::Extract { source, .. } => expr_refers_to(source, target),
6831 Expr::WindowFunction {
6832 args,
6833 partition_by,
6834 order_by,
6835 ..
6836 } => {
6837 args.iter().any(|a| expr_refers_to(a, target))
6838 || partition_by.iter().any(|p| expr_refers_to(p, target))
6839 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
6840 }
6841 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
6842 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
6843 Expr::ArraySubscript { target: t, index } => {
6844 expr_refers_to(t, target) || expr_refers_to(index, target)
6845 }
6846 Expr::AnyAll { expr, array, .. } => {
6847 expr_refers_to(expr, target) || expr_refers_to(array, target)
6848 }
6849 Expr::Case {
6850 operand,
6851 branches,
6852 else_branch,
6853 } => {
6854 operand.as_deref().is_some_and(|o| expr_refers_to(o, target))
6855 || branches
6856 .iter()
6857 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
6858 || else_branch
6859 .as_deref()
6860 .is_some_and(|e| expr_refers_to(e, target))
6861 }
6862 }
6863}
6864
6865fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
6871 let mut out = columns.to_vec();
6872 for (col_idx, col) in out.iter_mut().enumerate() {
6873 if col.ty != DataType::Text {
6874 continue;
6875 }
6876 let mut inferred: Option<DataType> = None;
6877 let mut all_null = true;
6878 for row in rows {
6879 let Some(v) = row.values.get(col_idx) else {
6880 continue;
6881 };
6882 let ty = match v {
6883 Value::Null => continue,
6884 Value::SmallInt(_) => DataType::SmallInt,
6885 Value::Int(_) => DataType::Int,
6886 Value::BigInt(_) => DataType::BigInt,
6887 Value::Float(_) => DataType::Float,
6888 Value::Bool(_) => DataType::Bool,
6889 Value::Vector(_) => DataType::Vector {
6890 dim: 0,
6891 encoding: VecEncoding::F32,
6892 },
6893 _ => DataType::Text,
6894 };
6895 all_null = false;
6896 inferred = Some(match inferred {
6897 None => ty,
6898 Some(prev) if prev == ty => prev,
6899 Some(_) => DataType::Text,
6900 });
6901 }
6902 if let Some(t) = inferred {
6903 col.ty = t;
6904 col.nullable = true;
6905 } else if all_null {
6906 col.nullable = true;
6907 }
6908 }
6909 out
6910}
6911
6912#[allow(clippy::too_many_lines, clippy::format_push_string)]
6917fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
6934 use alloc::collections::BTreeSet;
6935 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
6936 let mut out: Vec<String> = Vec::new();
6937 let cat = engine.active_catalog();
6938 let Some(from) = &stmt.from else {
6942 return out;
6943 };
6944 let mut tables: Vec<String> = Vec::new();
6945 tables.push(from.primary.name.clone());
6946 for j in &from.joins {
6947 tables.push(j.table.name.clone());
6948 }
6949 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
6952 if let Some(w) = &stmt.where_ {
6953 collect_column_refs(w, &mut col_refs);
6954 }
6955 for j in &from.joins {
6956 if let Some(on) = &j.on {
6957 collect_column_refs(on, &mut col_refs);
6958 }
6959 }
6960 for cn in &col_refs {
6961 let owner: Option<String> = if let Some(q) = &cn.qualifier {
6964 tables.iter().find(|t| t == &q).cloned()
6965 } else {
6966 tables.iter().find_map(|t| {
6967 cat.get(t).and_then(|tbl| {
6968 if tbl.schema().column_position(&cn.name).is_some() {
6969 Some(t.clone())
6970 } else {
6971 None
6972 }
6973 })
6974 })
6975 };
6976 let Some(owner) = owner else {
6977 continue;
6978 };
6979 let Some(tbl) = cat.get(&owner) else {
6980 continue;
6981 };
6982 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
6983 continue;
6984 };
6985 let already_indexed = tbl.indices().iter().any(|i| {
6988 matches!(i.kind, spg_storage::IndexKind::BTree(_))
6989 && i.column_position == col_pos
6990 && i.expression.is_none()
6991 && i.partial_predicate.is_none()
6992 });
6993 if already_indexed {
6994 continue;
6995 }
6996 if seen.insert((owner.clone(), cn.name.clone())) {
6997 out.push(alloc::format!(
6998 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
6999 owner,
7000 cn.name,
7001 owner,
7002 cn.name
7003 ));
7004 }
7005 }
7006 out
7007}
7008
7009fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
7012 match expr {
7013 Expr::Column(cn) => out.push(cn.clone()),
7014 Expr::FunctionCall { args, .. } => {
7015 for a in args {
7016 collect_column_refs(a, out);
7017 }
7018 }
7019 Expr::Binary { lhs, rhs, .. } => {
7020 collect_column_refs(lhs, out);
7021 collect_column_refs(rhs, out);
7022 }
7023 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
7024 _ => {}
7025 }
7026}
7027
7028fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
7029 let catalog = engine.active_catalog();
7030 let cold_ids = catalog.cold_segment_ids_global();
7031 let any_cold = !cold_ids.is_empty();
7032 let cold_ids_repr = if any_cold {
7033 let mut s = alloc::string::String::from("[");
7034 for (i, id) in cold_ids.iter().enumerate() {
7035 if i > 0 {
7036 s.push(',');
7037 }
7038 s.push_str(&alloc::format!("{id}"));
7039 }
7040 s.push(']');
7041 s
7042 } else {
7043 alloc::string::String::new()
7044 };
7045 for (idx, line) in lines.iter_mut().enumerate() {
7046 let trimmed = line.trim_start();
7047 let is_top_level = idx == 0;
7048 if is_top_level {
7049 line.push_str(&alloc::format!(" (rows={total_rows})"));
7050 continue;
7051 }
7052 if let Some(rest) = trimmed.strip_prefix("From: ") {
7053 let (name, scan_kind) = match rest.split_once(" [") {
7054 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
7055 None => (rest.trim(), ""),
7056 };
7057 let bare = name.split_whitespace().next().unwrap_or(name);
7058 let hot = catalog.get(bare).map(|t| t.rows().len());
7059 let annot = match (hot, scan_kind) {
7064 (Some(h), "full scan") => {
7065 let mut s = alloc::format!(" (hot_rows={h}");
7066 if any_cold {
7067 s.push_str(&alloc::format!(
7068 ", cold_tier=present, cold_segments={cold_ids_repr}"
7069 ));
7070 }
7071 s.push(')');
7072 s
7073 }
7074 (Some(h), "index seek") => {
7075 let mut s = alloc::format!(" (hot_rows≤{h}");
7076 if any_cold {
7077 s.push_str(&alloc::format!(
7078 ", cold_tier=present, cold_segments={cold_ids_repr}"
7079 ));
7080 }
7081 s.push(')');
7082 s
7083 }
7084 _ => " (rows=—)".to_string(),
7085 };
7086 line.push_str(&annot);
7087 continue;
7088 }
7089 line.push_str(" (rows=—)");
7091 }
7092}
7093
7094fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
7095 let pad = " ".repeat(depth);
7096 let top = if !stmt.ctes.is_empty() {
7098 if stmt.ctes.iter().any(|c| c.recursive) {
7099 "CTEScan (WITH RECURSIVE)"
7100 } else {
7101 "CTEScan (WITH)"
7102 }
7103 } else if !stmt.unions.is_empty() {
7104 "UnionScan"
7105 } else if select_has_window(stmt) {
7106 "WindowAgg"
7107 } else if aggregate::uses_aggregate(stmt) {
7108 "Aggregate"
7109 } else if stmt.distinct {
7110 "Distinct"
7111 } else if stmt.from.is_some() {
7112 "TableScan"
7113 } else {
7114 "Result"
7115 };
7116 out.push(alloc::format!("{pad}{top}"));
7117 let child = " ".repeat(depth + 1);
7118 for cte in &stmt.ctes {
7120 let head = if cte.recursive {
7121 alloc::format!("{child}CTE (recursive): {}", cte.name)
7122 } else {
7123 alloc::format!("{child}CTE: {}", cte.name)
7124 };
7125 out.push(head);
7126 explain_select(&cte.body, engine, depth + 2, out);
7127 }
7128 if let Some(from) = &stmt.from {
7130 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
7131 if let Some(alias) = &from.primary.alias {
7132 tag.push_str(&alloc::format!(" AS {alias}"));
7133 }
7134 if let Some(w) = &stmt.where_
7137 && let Some(table) = engine.active_catalog().get(&from.primary.name)
7138 {
7139 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
7140 let cols = &table.schema().columns;
7141 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
7142 tag.push_str(" [index seek]");
7143 } else {
7144 tag.push_str(" [full scan]");
7145 }
7146 } else {
7147 tag.push_str(" [full scan]");
7148 }
7149 out.push(tag);
7150 for j in &from.joins {
7151 let kind = match j.kind {
7152 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
7153 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
7154 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
7155 };
7156 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
7157 if let Some(alias) = &j.table.alias {
7158 s.push_str(&alloc::format!(" AS {alias}"));
7159 }
7160 if j.on.is_some() {
7161 s.push_str(" (ON …)");
7162 }
7163 out.push(s);
7164 }
7165 }
7166 if let Some(w) = &stmt.where_ {
7168 let mut s = alloc::format!("{child}Filter: {w}");
7169 if expr_has_subquery(w) {
7170 s.push_str(" [subquery]");
7171 }
7172 out.push(s);
7173 }
7174 if let Some(gs) = &stmt.group_by {
7175 let mut parts = Vec::new();
7176 for g in gs {
7177 parts.push(alloc::format!("{g}"));
7178 }
7179 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
7180 }
7181 if let Some(h) = &stmt.having {
7182 out.push(alloc::format!("{child}Having: {h}"));
7183 }
7184 for o in &stmt.order_by {
7185 let dir = if o.desc { "DESC" } else { "ASC" };
7186 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
7187 }
7188 if let Some(lim) = stmt.limit {
7189 out.push(alloc::format!("{child}Limit: {lim}"));
7190 }
7191 if let Some(off) = stmt.offset {
7192 out.push(alloc::format!("{child}Offset: {off}"));
7193 }
7194 if stmt
7196 .items
7197 .iter()
7198 .any(|it| matches!(it, SelectItem::Wildcard))
7199 {
7200 out.push(alloc::format!("{child}Project: *"));
7201 } else {
7202 out.push(alloc::format!(
7203 "{child}Project: {} item(s)",
7204 stmt.items.len()
7205 ));
7206 }
7207 for (kind, peer) in &stmt.unions {
7209 let label = match kind {
7210 UnionKind::All => "UNION ALL",
7211 UnionKind::Distinct => "UNION",
7212 };
7213 out.push(alloc::format!("{child}{label}"));
7214 explain_select(peer, engine, depth + 2, out);
7215 }
7216}
7217
7218fn is_correlation_error(e: &EngineError) -> bool {
7223 matches!(
7224 e,
7225 EngineError::Eval(
7226 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
7227 )
7228 )
7229}
7230
7231fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
7239 let Some(outer_alias) = ctx.table_alias else {
7240 return;
7241 };
7242 substitute_in_select(stmt, row, ctx, outer_alias);
7243}
7244
7245fn substitute_in_select(
7246 stmt: &mut SelectStatement,
7247 row: &Row,
7248 ctx: &EvalContext<'_>,
7249 outer_alias: &str,
7250) {
7251 for item in &mut stmt.items {
7252 if let SelectItem::Expr { expr, .. } = item {
7253 substitute_in_expr(expr, row, ctx, outer_alias);
7254 }
7255 }
7256 if let Some(w) = &mut stmt.where_ {
7257 substitute_in_expr(w, row, ctx, outer_alias);
7258 }
7259 if let Some(gs) = &mut stmt.group_by {
7260 for g in gs {
7261 substitute_in_expr(g, row, ctx, outer_alias);
7262 }
7263 }
7264 if let Some(h) = &mut stmt.having {
7265 substitute_in_expr(h, row, ctx, outer_alias);
7266 }
7267 for o in &mut stmt.order_by {
7268 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
7269 }
7270 for (_, peer) in &mut stmt.unions {
7271 substitute_in_select(peer, row, ctx, outer_alias);
7272 }
7273}
7274
7275fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
7276 if let Expr::Column(c) = e
7277 && let Some(qual) = &c.qualifier
7278 && qual.eq_ignore_ascii_case(outer_alias)
7279 {
7280 if let Some(idx) = ctx
7282 .columns
7283 .iter()
7284 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
7285 {
7286 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
7287 if let Ok(lit) = value_to_literal_expr(v) {
7288 *e = lit;
7289 return;
7290 }
7291 }
7292 }
7293 match e {
7294 Expr::Binary { lhs, rhs, .. } => {
7295 substitute_in_expr(lhs, row, ctx, outer_alias);
7296 substitute_in_expr(rhs, row, ctx, outer_alias);
7297 }
7298 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
7299 substitute_in_expr(expr, row, ctx, outer_alias);
7300 }
7301 Expr::Like { expr, pattern, .. } => {
7302 substitute_in_expr(expr, row, ctx, outer_alias);
7303 substitute_in_expr(pattern, row, ctx, outer_alias);
7304 }
7305 Expr::FunctionCall { args, .. } => {
7306 for a in args {
7307 substitute_in_expr(a, row, ctx, outer_alias);
7308 }
7309 }
7310 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
7311 Expr::WindowFunction {
7312 args,
7313 partition_by,
7314 order_by,
7315 ..
7316 } => {
7317 for a in args {
7318 substitute_in_expr(a, row, ctx, outer_alias);
7319 }
7320 for p in partition_by {
7321 substitute_in_expr(p, row, ctx, outer_alias);
7322 }
7323 for (o, _) in order_by {
7324 substitute_in_expr(o, row, ctx, outer_alias);
7325 }
7326 }
7327 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
7328 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
7329 substitute_in_select(subquery, row, ctx, outer_alias);
7330 }
7331 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
7332 Expr::Array(items) => {
7333 for elem in items {
7334 substitute_in_expr(elem, row, ctx, outer_alias);
7335 }
7336 }
7337 Expr::ArraySubscript { target, index } => {
7338 substitute_in_expr(target, row, ctx, outer_alias);
7339 substitute_in_expr(index, row, ctx, outer_alias);
7340 }
7341 Expr::AnyAll { expr, array, .. } => {
7342 substitute_in_expr(expr, row, ctx, outer_alias);
7343 substitute_in_expr(array, row, ctx, outer_alias);
7344 }
7345 Expr::Case {
7346 operand,
7347 branches,
7348 else_branch,
7349 } => {
7350 if let Some(o) = operand {
7351 substitute_in_expr(o, row, ctx, outer_alias);
7352 }
7353 for (w, t) in branches {
7354 substitute_in_expr(w, row, ctx, outer_alias);
7355 substitute_in_expr(t, row, ctx, outer_alias);
7356 }
7357 if let Some(e) = else_branch {
7358 substitute_in_expr(e, row, ctx, outer_alias);
7359 }
7360 }
7361 }
7362}
7363
7364fn encode_row_key(row: &Row) -> Vec<u8> {
7368 let mut out = Vec::new();
7369 for v in &row.values {
7370 let s = alloc::format!("{v:?}|");
7371 out.extend_from_slice(s.as_bytes());
7372 }
7373 out
7374}
7375
7376fn select_has_window(stmt: &SelectStatement) -> bool {
7377 for item in &stmt.items {
7378 if let SelectItem::Expr { expr, .. } = item
7379 && expr_has_window(expr)
7380 {
7381 return true;
7382 }
7383 }
7384 false
7385}
7386
7387fn expr_has_window(e: &Expr) -> bool {
7388 match e {
7389 Expr::WindowFunction { .. } => true,
7390 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
7391 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
7392 expr_has_window(expr)
7393 }
7394 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
7395 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
7396 Expr::Extract { source, .. } => expr_has_window(source),
7397 Expr::ScalarSubquery(_)
7398 | Expr::Exists { .. }
7399 | Expr::InSubquery { .. }
7400 | Expr::Literal(_)
7401 | Expr::Placeholder(_)
7402 | Expr::Column(_) => false,
7403 Expr::Array(items) => items.iter().any(expr_has_window),
7404 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
7405 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
7406 Expr::Case {
7407 operand,
7408 branches,
7409 else_branch,
7410 } => {
7411 operand.as_deref().is_some_and(expr_has_window)
7412 || branches
7413 .iter()
7414 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
7415 || else_branch.as_deref().is_some_and(expr_has_window)
7416 }
7417 }
7418}
7419
7420fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
7421 if let Expr::WindowFunction { .. } = e {
7422 if !out.iter().any(|x| x == e) {
7427 out.push(e.clone());
7428 }
7429 return;
7430 }
7431 match e {
7432 Expr::WindowFunction { .. } => unreachable!(),
7434 Expr::Binary { lhs, rhs, .. } => {
7435 collect_window_nodes(lhs, out);
7436 collect_window_nodes(rhs, out);
7437 }
7438 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
7439 collect_window_nodes(expr, out);
7440 }
7441 Expr::FunctionCall { args, .. } => {
7442 for a in args {
7443 collect_window_nodes(a, out);
7444 }
7445 }
7446 Expr::Like { expr, pattern, .. } => {
7447 collect_window_nodes(expr, out);
7448 collect_window_nodes(pattern, out);
7449 }
7450 Expr::Extract { source, .. } => collect_window_nodes(source, out),
7451 _ => {}
7452 }
7453}
7454
7455fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
7456 if let Expr::WindowFunction { .. } = e
7457 && let Some(idx) = window_nodes.iter().position(|w| w == e)
7458 {
7459 *e = Expr::Column(spg_sql::ast::ColumnName {
7460 qualifier: None,
7461 name: alloc::format!("__win_{idx}"),
7462 });
7463 return;
7464 }
7465 match e {
7466 Expr::Binary { lhs, rhs, .. } => {
7467 rewrite_window_to_columns(lhs, window_nodes);
7468 rewrite_window_to_columns(rhs, window_nodes);
7469 }
7470 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
7471 rewrite_window_to_columns(expr, window_nodes);
7472 }
7473 Expr::FunctionCall { args, .. } => {
7474 for a in args {
7475 rewrite_window_to_columns(a, window_nodes);
7476 }
7477 }
7478 Expr::Like { expr, pattern, .. } => {
7479 rewrite_window_to_columns(expr, window_nodes);
7480 rewrite_window_to_columns(pattern, window_nodes);
7481 }
7482 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
7483 _ => {}
7484 }
7485}
7486
7487fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
7491 for (x, y) in a.iter().zip(b.iter()) {
7492 let c = value_cmp(x, y);
7493 if c != core::cmp::Ordering::Equal {
7494 return c;
7495 }
7496 }
7497 a.len().cmp(&b.len())
7498}
7499
7500fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
7501 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
7502 let c = value_cmp(va, vb);
7503 let c = if *desc { c.reverse() } else { c };
7504 if c != core::cmp::Ordering::Equal {
7505 return c;
7506 }
7507 }
7508 a.len().cmp(&b.len())
7509}
7510
7511#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
7513 use core::cmp::Ordering;
7514 match (a, b) {
7515 (Value::Null, Value::Null) => Ordering::Equal,
7516 (Value::Null, _) => Ordering::Less,
7517 (_, Value::Null) => Ordering::Greater,
7518 (Value::Int(x), Value::Int(y)) => x.cmp(y),
7519 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
7520 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
7521 (Value::Text(x), Value::Text(y)) => x.cmp(y),
7522 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
7523 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
7524 (Value::Date(x), Value::Date(y)) => x.cmp(y),
7525 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
7526 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
7529 }
7530}
7531
7532#[allow(
7538 clippy::too_many_arguments,
7539 clippy::cast_possible_truncation,
7540 clippy::cast_possible_wrap,
7541 clippy::cast_precision_loss,
7542 clippy::cast_sign_loss,
7543 clippy::doc_markdown,
7544 clippy::too_many_lines,
7545 clippy::type_complexity,
7546 clippy::match_same_arms
7547)]
7548fn compute_window_partition(
7549 name: &str,
7550 args: &[Expr],
7551 ordered: bool,
7552 frame: Option<&WindowFrame>,
7553 null_treatment: spg_sql::ast::NullTreatment,
7554 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
7555 filtered_rows: &[&Row],
7556 ctx: &EvalContext<'_>,
7557 out_vals: &mut [Value],
7558) -> Result<(), EngineError> {
7559 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
7560 let lower = name.to_ascii_lowercase();
7561 match lower.as_str() {
7562 "row_number" => {
7563 for (rank, (_, _, idx)) in slice.iter().enumerate() {
7564 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
7565 }
7566 Ok(())
7567 }
7568 "rank" => {
7569 let mut prev_key: Option<&[(Value, bool)]> = None;
7570 let mut current_rank: i64 = 1;
7571 for (i, (_, okey, idx)) in slice.iter().enumerate() {
7572 if let Some(p) = prev_key
7573 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
7574 {
7575 current_rank = (i + 1) as i64;
7576 }
7577 if prev_key.is_none() {
7578 current_rank = 1;
7579 }
7580 out_vals[*idx] = Value::BigInt(current_rank);
7581 prev_key = Some(okey.as_slice());
7582 }
7583 Ok(())
7584 }
7585 "dense_rank" => {
7586 let mut prev_key: Option<&[(Value, bool)]> = None;
7587 let mut current_rank: i64 = 0;
7588 for (_, okey, idx) in slice {
7589 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
7590 current_rank += 1;
7591 }
7592 out_vals[*idx] = Value::BigInt(current_rank);
7593 prev_key = Some(okey.as_slice());
7594 }
7595 Ok(())
7596 }
7597 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
7598 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
7601 slice.iter().map(|_| Value::Null).collect()
7602 } else {
7603 slice
7604 .iter()
7605 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
7606 .collect::<Result<_, _>>()
7607 .map_err(EngineError::Eval)?
7608 };
7609 let eff = effective_frame(frame, ordered)?;
7613 #[allow(clippy::needless_range_loop)]
7614 for i in 0..slice.len() {
7615 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
7616 let mut sum: f64 = 0.0;
7617 let mut count: i64 = 0;
7618 let mut min_v: Option<f64> = None;
7619 let mut max_v: Option<f64> = None;
7620 let mut row_count: i64 = 0;
7621 if lo <= hi {
7622 for j in lo..=hi {
7623 let v = &arg_values[j];
7624 match lower.as_str() {
7625 "count_star" => row_count += 1,
7626 "count" => {
7627 if !v.is_null() {
7628 count += 1;
7629 }
7630 }
7631 _ => {
7632 if let Some(x) = value_to_f64(v) {
7633 sum += x;
7634 count += 1;
7635 min_v = Some(min_v.map_or(x, |m| m.min(x)));
7636 max_v = Some(max_v.map_or(x, |m| m.max(x)));
7637 }
7638 }
7639 }
7640 }
7641 }
7642 let value = match lower.as_str() {
7643 "count_star" => Value::BigInt(row_count),
7644 "count" => Value::BigInt(count),
7645 "sum" => Value::Float(sum),
7646 "avg" => {
7647 if count == 0 {
7648 Value::Null
7649 } else {
7650 Value::Float(sum / count as f64)
7651 }
7652 }
7653 "min" => min_v.map_or(Value::Null, Value::Float),
7654 "max" => max_v.map_or(Value::Null, Value::Float),
7655 _ => unreachable!(),
7656 };
7657 let (_, _, idx) = &slice[i];
7658 out_vals[*idx] = value;
7659 }
7660 Ok(())
7661 }
7662 "lag" | "lead" => {
7663 if args.is_empty() {
7666 return Err(EngineError::Unsupported(alloc::format!(
7667 "{lower}() requires at least one argument"
7668 )));
7669 }
7670 let offset: i64 = if args.len() >= 2 {
7671 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
7672 .map_err(EngineError::Eval)?;
7673 match v {
7674 Value::SmallInt(n) => i64::from(n),
7675 Value::Int(n) => i64::from(n),
7676 Value::BigInt(n) => n,
7677 _ => {
7678 return Err(EngineError::Unsupported(alloc::format!(
7679 "{lower}() offset must be integer"
7680 )));
7681 }
7682 }
7683 } else {
7684 1
7685 };
7686 let default: Value = if args.len() >= 3 {
7687 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
7688 .map_err(EngineError::Eval)?
7689 } else {
7690 Value::Null
7691 };
7692 let values: Vec<Value> = slice
7693 .iter()
7694 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
7695 .collect::<Result<_, _>>()
7696 .map_err(EngineError::Eval)?;
7697 let n = slice.len();
7698 for (i, (_, _, idx)) in slice.iter().enumerate() {
7699 let signed_offset = if lower == "lag" { -offset } else { offset };
7700 let v = if ignore_nulls {
7701 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
7705 let needed: i64 = signed_offset.abs();
7706 if needed == 0 {
7707 values[i].clone()
7708 } else {
7709 let mut j: i64 = i as i64;
7710 let mut hits: i64 = 0;
7711 let mut found: Option<Value> = None;
7712 loop {
7713 j += step;
7714 if j < 0 || j >= n as i64 {
7715 break;
7716 }
7717 #[allow(clippy::cast_sign_loss)]
7718 let v = &values[j as usize];
7719 if !v.is_null() {
7720 hits += 1;
7721 if hits == needed {
7722 found = Some(v.clone());
7723 break;
7724 }
7725 }
7726 }
7727 found.unwrap_or_else(|| default.clone())
7728 }
7729 } else {
7730 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
7731 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
7732 default.clone()
7733 } else {
7734 #[allow(clippy::cast_sign_loss)]
7735 {
7736 values[target_signed as usize].clone()
7737 }
7738 }
7739 };
7740 out_vals[*idx] = v;
7741 }
7742 Ok(())
7743 }
7744 "first_value" | "last_value" | "nth_value" => {
7745 if args.is_empty() {
7746 return Err(EngineError::Unsupported(alloc::format!(
7747 "{lower}() requires at least one argument"
7748 )));
7749 }
7750 let values: Vec<Value> = slice
7751 .iter()
7752 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
7753 .collect::<Result<_, _>>()
7754 .map_err(EngineError::Eval)?;
7755 let nth: usize = if lower == "nth_value" {
7756 if args.len() < 2 {
7757 return Err(EngineError::Unsupported(
7758 "nth_value() requires (expr, n)".into(),
7759 ));
7760 }
7761 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
7762 .map_err(EngineError::Eval)?;
7763 let raw = match v {
7764 Value::SmallInt(n) => i64::from(n),
7765 Value::Int(n) => i64::from(n),
7766 Value::BigInt(n) => n,
7767 _ => {
7768 return Err(EngineError::Unsupported(
7769 "nth_value() n must be integer".into(),
7770 ));
7771 }
7772 };
7773 if raw < 1 {
7774 return Err(EngineError::Unsupported(
7775 "nth_value() n must be >= 1".into(),
7776 ));
7777 }
7778 #[allow(clippy::cast_sign_loss)]
7779 {
7780 raw as usize
7781 }
7782 } else {
7783 0
7784 };
7785 let eff = effective_frame(frame, ordered)?;
7786 for i in 0..slice.len() {
7787 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
7788 let (_, _, idx) = &slice[i];
7789 let v = if lo > hi {
7790 Value::Null
7791 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
7792 if lower == "first_value" {
7795 (lo..=hi)
7796 .find_map(|j| {
7797 let v = &values[j];
7798 (!v.is_null()).then(|| v.clone())
7799 })
7800 .unwrap_or(Value::Null)
7801 } else {
7802 (lo..=hi)
7803 .rev()
7804 .find_map(|j| {
7805 let v = &values[j];
7806 (!v.is_null()).then(|| v.clone())
7807 })
7808 .unwrap_or(Value::Null)
7809 }
7810 } else {
7811 match lower.as_str() {
7812 "first_value" => values[lo].clone(),
7813 "last_value" => values[hi].clone(),
7814 "nth_value" => {
7815 let pos = lo + nth - 1;
7816 if pos > hi {
7817 Value::Null
7818 } else {
7819 values[pos].clone()
7820 }
7821 }
7822 _ => unreachable!(),
7823 }
7824 };
7825 out_vals[*idx] = v;
7826 }
7827 Ok(())
7828 }
7829 "ntile" => {
7830 if args.is_empty() {
7831 return Err(EngineError::Unsupported(
7832 "ntile(n) requires an integer argument".into(),
7833 ));
7834 }
7835 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
7836 .map_err(EngineError::Eval)?;
7837 let bucket_count: i64 = match v {
7838 Value::SmallInt(n) => i64::from(n),
7839 Value::Int(n) => i64::from(n),
7840 Value::BigInt(n) => n,
7841 _ => {
7842 return Err(EngineError::Unsupported(
7843 "ntile() argument must be integer".into(),
7844 ));
7845 }
7846 };
7847 if bucket_count < 1 {
7848 return Err(EngineError::Unsupported(
7849 "ntile() argument must be >= 1".into(),
7850 ));
7851 }
7852 #[allow(clippy::cast_sign_loss)]
7853 let buckets = bucket_count as usize;
7854 let n = slice.len();
7855 let base = n / buckets;
7858 let extras = n % buckets;
7859 let mut bucket: usize = 1;
7860 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
7861 let mut buckets_with_extra_remaining = extras;
7862 for (_, _, idx) in slice {
7863 if remaining_in_bucket == 0 {
7864 bucket += 1;
7865 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
7866 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
7867 base + 1
7868 } else {
7869 base
7870 };
7871 if remaining_in_bucket == 0 {
7874 remaining_in_bucket = 1;
7875 }
7876 }
7877 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
7878 remaining_in_bucket -= 1;
7879 }
7880 Ok(())
7881 }
7882 "percent_rank" => {
7883 let n = slice.len();
7886 let mut prev_key: Option<&[(Value, bool)]> = None;
7887 let mut current_rank: i64 = 1;
7888 for (i, (_, okey, idx)) in slice.iter().enumerate() {
7889 if let Some(p) = prev_key
7890 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
7891 {
7892 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
7893 }
7894 if prev_key.is_none() {
7895 current_rank = 1;
7896 }
7897 #[allow(clippy::cast_precision_loss)]
7898 let pr = if n <= 1 {
7899 0.0
7900 } else {
7901 (current_rank - 1) as f64 / (n - 1) as f64
7902 };
7903 out_vals[*idx] = Value::Float(pr);
7904 prev_key = Some(okey.as_slice());
7905 }
7906 Ok(())
7907 }
7908 "cume_dist" => {
7909 let n = slice.len();
7911 for i in 0..slice.len() {
7913 let peer_end = peer_group_end(slice, i);
7914 #[allow(clippy::cast_precision_loss)]
7915 let cd = (peer_end + 1) as f64 / n as f64;
7916 let (_, _, idx) = &slice[i];
7917 out_vals[*idx] = Value::Float(cd);
7918 }
7919 Ok(())
7920 }
7921 other => Err(EngineError::Unsupported(alloc::format!(
7922 "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)"
7923 ))),
7924 }
7925}
7926
7927fn effective_frame(
7934 frame: Option<&WindowFrame>,
7935 ordered: bool,
7936) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
7937 match frame {
7938 None => {
7939 if ordered {
7940 Ok((
7941 FrameKind::Range,
7942 FrameBound::UnboundedPreceding,
7943 FrameBound::CurrentRow,
7944 ))
7945 } else {
7946 Ok((
7947 FrameKind::Rows,
7948 FrameBound::UnboundedPreceding,
7949 FrameBound::UnboundedFollowing,
7950 ))
7951 }
7952 }
7953 Some(fr) => {
7954 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
7955 if matches!(fr.start, FrameBound::UnboundedFollowing)
7957 || matches!(end, FrameBound::UnboundedPreceding)
7958 {
7959 return Err(EngineError::Unsupported(alloc::format!(
7960 "invalid frame: start={:?} end={:?}",
7961 fr.start,
7962 end
7963 )));
7964 }
7965 if fr.kind == FrameKind::Range
7970 && (matches!(
7971 fr.start,
7972 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
7973 ) || matches!(
7974 end,
7975 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
7976 ))
7977 {
7978 return Err(EngineError::Unsupported(
7979 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
7980 ));
7981 }
7982 Ok((fr.kind, fr.start.clone(), end))
7983 }
7984 }
7985}
7986
7987#[allow(clippy::type_complexity)]
7991fn frame_bounds_for_row(
7992 eff: &(FrameKind, FrameBound, FrameBound),
7993 i: usize,
7994 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
7995) -> (usize, usize) {
7996 let (kind, start, end) = eff;
7997 let n = slice.len();
7998 let last = n.saturating_sub(1);
7999 let (mut lo, mut hi) = match kind {
8000 FrameKind::Rows => {
8001 let lo = match start {
8002 FrameBound::UnboundedPreceding => 0,
8003 FrameBound::OffsetPreceding(k) => {
8004 let k = usize::try_from(*k).unwrap_or(usize::MAX);
8005 i.saturating_sub(k)
8006 }
8007 FrameBound::CurrentRow => i,
8008 FrameBound::OffsetFollowing(k) => {
8009 let k = usize::try_from(*k).unwrap_or(usize::MAX);
8010 i.saturating_add(k).min(last)
8011 }
8012 FrameBound::UnboundedFollowing => last,
8013 };
8014 let hi = match end {
8015 FrameBound::UnboundedPreceding => 0,
8016 FrameBound::OffsetPreceding(k) => {
8017 let k = usize::try_from(*k).unwrap_or(usize::MAX);
8018 i.saturating_sub(k)
8019 }
8020 FrameBound::CurrentRow => i,
8021 FrameBound::OffsetFollowing(k) => {
8022 let k = usize::try_from(*k).unwrap_or(usize::MAX);
8023 i.saturating_add(k).min(last)
8024 }
8025 FrameBound::UnboundedFollowing => last,
8026 };
8027 (lo, hi)
8028 }
8029 FrameKind::Range => {
8030 let lo = match start {
8036 FrameBound::UnboundedPreceding => 0,
8037 FrameBound::CurrentRow => peer_group_start(slice, i),
8038 FrameBound::UnboundedFollowing => last,
8039 _ => unreachable!("offset bounds rejected for RANGE"),
8040 };
8041 let hi = match end {
8042 FrameBound::UnboundedPreceding => 0,
8043 FrameBound::CurrentRow => peer_group_end(slice, i),
8044 FrameBound::UnboundedFollowing => last,
8045 _ => unreachable!("offset bounds rejected for RANGE"),
8046 };
8047 (lo, hi)
8048 }
8049 };
8050 if hi >= n {
8051 hi = last;
8052 }
8053 if lo >= n {
8054 lo = last;
8055 }
8056 (lo, hi)
8057}
8058
8059#[allow(clippy::type_complexity)]
8063fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
8064 let key = &slice[i].1;
8065 let mut j = i;
8066 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
8067 j -= 1;
8068 }
8069 j
8070}
8071
8072#[allow(clippy::type_complexity)]
8075fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
8076 let key = &slice[i].1;
8077 let mut j = i;
8078 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
8079 j += 1;
8080 }
8081 j
8082}
8083
8084fn value_to_f64(v: &Value) -> Option<f64> {
8085 match v {
8086 Value::SmallInt(n) => Some(f64::from(*n)),
8087 Value::Int(n) => Some(f64::from(*n)),
8088 #[allow(clippy::cast_precision_loss)]
8089 Value::BigInt(n) => Some(*n as f64),
8090 Value::Float(x) => Some(*x),
8091 _ => None,
8092 }
8093}
8094
8095fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
8099 let mut any = false;
8100 for item in &stmt.items {
8101 if let SelectItem::Expr { expr, .. } = item {
8102 any = any || expr_has_subquery(expr);
8103 }
8104 }
8105 if let Some(w) = &stmt.where_ {
8106 any = any || expr_has_subquery(w);
8107 }
8108 if let Some(h) = &stmt.having {
8109 any = any || expr_has_subquery(h);
8110 }
8111 for o in &stmt.order_by {
8112 any = any || expr_has_subquery(&o.expr);
8113 }
8114 for (_, peer) in &stmt.unions {
8115 any = any || expr_tree_has_subquery(peer);
8116 }
8117 any
8118}
8119
8120fn expr_has_subquery(e: &Expr) -> bool {
8121 match e {
8122 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
8123 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
8124 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
8125 expr_has_subquery(expr)
8126 }
8127 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
8128 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
8129 Expr::Extract { source, .. } => expr_has_subquery(source),
8130 Expr::WindowFunction {
8131 args,
8132 partition_by,
8133 order_by,
8134 ..
8135 } => {
8136 args.iter().any(expr_has_subquery)
8137 || partition_by.iter().any(expr_has_subquery)
8138 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
8139 }
8140 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
8141 Expr::Array(items) => items.iter().any(expr_has_subquery),
8142 Expr::ArraySubscript { target, index } => {
8143 expr_has_subquery(target) || expr_has_subquery(index)
8144 }
8145 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
8146 Expr::Case {
8147 operand,
8148 branches,
8149 else_branch,
8150 } => {
8151 operand.as_deref().is_some_and(expr_has_subquery)
8152 || branches
8153 .iter()
8154 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
8155 || else_branch.as_deref().is_some_and(expr_has_subquery)
8156 }
8157 }
8158}
8159
8160fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
8167 let lit = match v {
8168 Value::Null => Literal::Null,
8169 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
8170 Value::Int(n) => Literal::Integer(i64::from(n)),
8171 Value::BigInt(n) => Literal::Integer(n),
8172 Value::Float(x) => Literal::Float(x),
8173 Value::Text(s) | Value::Json(s) => Literal::String(s),
8174 Value::Bool(b) => Literal::Bool(b),
8175 other => {
8176 return Err(EngineError::Unsupported(alloc::format!(
8177 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
8178 other.data_type()
8179 )));
8180 }
8181 };
8182 Ok(Expr::Literal(lit))
8183}
8184
8185fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
8191 let lit = match v {
8192 Value::Null => Literal::Null,
8193 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
8194 Value::Int(n) => Literal::Integer(i64::from(n)),
8195 Value::BigInt(n) => Literal::Integer(n),
8196 Value::Float(x) => Literal::Float(x),
8197 Value::Text(s) | Value::Json(s) => Literal::String(s),
8198 Value::Bool(b) => Literal::Bool(b),
8199 Value::Vector(xs) => Literal::Vector(xs),
8200 Value::Date(days) => {
8204 let micros = (i64::from(days)) * 86_400_000_000;
8205 Literal::String(format_timestamp_micros_as_date(micros))
8206 }
8207 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
8208 Value::Numeric { scaled, scale } => {
8209 Literal::String(format_numeric(scaled, scale))
8210 }
8211 other => {
8212 return Err(EngineError::Unsupported(alloc::format!(
8213 "INSERT … SELECT cannot materialise value of type {:?}; \
8214 add an explicit CAST in the inner SELECT",
8215 other.data_type()
8216 )));
8217 }
8218 };
8219 Ok(Expr::Literal(lit))
8220}
8221
8222fn format_timestamp_micros(us: i64) -> String {
8223 let days = us.div_euclid(86_400_000_000);
8225 let intra_day = us.rem_euclid(86_400_000_000);
8226 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
8227 let secs = intra_day / 1_000_000;
8228 let us_rem = intra_day % 1_000_000;
8229 let h = (secs / 3600) % 24;
8230 let m = (secs / 60) % 60;
8231 let s = secs % 60;
8232 if us_rem == 0 {
8233 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
8234 } else {
8235 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
8236 }
8237}
8238
8239fn format_timestamp_micros_as_date(us: i64) -> String {
8240 let days = us.div_euclid(86_400_000_000);
8243 let jdn = days + 2_440_588;
8245 let (y, mo, d) = jdn_to_ymd(jdn);
8246 alloc::format!("{y:04}-{mo:02}-{d:02}")
8247}
8248
8249fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
8250 let l = jdn + 68569;
8252 let n = (4 * l) / 146_097;
8253 let l = l - (146_097 * n + 3) / 4;
8254 let i = (4000 * (l + 1)) / 1_461_001;
8255 let l = l - (1461 * i) / 4 + 31;
8256 let j = (80 * l) / 2447;
8257 let day = (l - (2447 * j) / 80) as u32;
8258 let l = j / 11;
8259 let month = (j + 2 - 12 * l) as u32;
8260 let year = 100 * (n - 49) + i + l;
8261 (year, month, day)
8262}
8263
8264fn format_numeric(scaled: i128, scale: u8) -> String {
8265 if scale == 0 {
8266 return alloc::format!("{scaled}");
8267 }
8268 let abs = scaled.unsigned_abs();
8269 let divisor = 10u128.pow(u32::from(scale));
8270 let whole = abs / divisor;
8271 let frac = abs % divisor;
8272 let sign = if scaled < 0 { "-" } else { "" };
8273 alloc::format!(
8274 "{sign}{whole}.{frac:0width$}",
8275 width = usize::from(scale)
8276 )
8277}
8278
8279fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
8290 match stmt {
8291 Statement::Select(s) => substitute_select(s, params)?,
8292 Statement::Insert(ins) => {
8293 for row in &mut ins.rows {
8294 for e in row {
8295 substitute_expr(e, params)?;
8296 }
8297 }
8298 }
8299 Statement::Update(u) => {
8300 for (_, e) in &mut u.assignments {
8301 substitute_expr(e, params)?;
8302 }
8303 if let Some(w) = &mut u.where_ {
8304 substitute_expr(w, params)?;
8305 }
8306 }
8307 Statement::Delete(d) => {
8308 if let Some(w) = &mut d.where_ {
8309 substitute_expr(w, params)?;
8310 }
8311 }
8312 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
8313 _ => {}
8316 }
8317 Ok(())
8318}
8319
8320fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
8321 for item in &mut s.items {
8322 if let SelectItem::Expr { expr, .. } = item {
8323 substitute_expr(expr, params)?;
8324 }
8325 }
8326 if let Some(w) = &mut s.where_ {
8327 substitute_expr(w, params)?;
8328 }
8329 if let Some(gs) = &mut s.group_by {
8330 for g in gs {
8331 substitute_expr(g, params)?;
8332 }
8333 }
8334 if let Some(h) = &mut s.having {
8335 substitute_expr(h, params)?;
8336 }
8337 for o in &mut s.order_by {
8338 substitute_expr(&mut o.expr, params)?;
8339 }
8340 for (_, peer) in &mut s.unions {
8341 substitute_select(peer, params)?;
8342 }
8343 if let Some(le) = s.limit {
8348 s.limit = Some(resolve_limit_placeholder(le, params)?);
8349 }
8350 if let Some(le) = s.offset {
8351 s.offset = Some(resolve_limit_placeholder(le, params)?);
8352 }
8353 Ok(())
8354}
8355
8356fn resolve_limit_placeholder(
8357 le: spg_sql::ast::LimitExpr,
8358 params: &[Value],
8359) -> Result<spg_sql::ast::LimitExpr, EngineError> {
8360 use spg_sql::ast::LimitExpr;
8361 match le {
8362 LimitExpr::Literal(_) => Ok(le),
8363 LimitExpr::Placeholder(n) => {
8364 let idx = usize::from(n).saturating_sub(1);
8365 let v = params.get(idx).ok_or_else(|| {
8366 EngineError::Eval(EvalError::PlaceholderOutOfRange {
8367 n,
8368 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
8369 })
8370 })?;
8371 let int = match v {
8372 Value::SmallInt(x) => Some(i64::from(*x)),
8373 Value::Int(x) => Some(i64::from(*x)),
8374 Value::BigInt(x) => Some(*x),
8375 _ => None,
8376 }
8377 .ok_or_else(|| {
8378 EngineError::Unsupported(alloc::format!(
8379 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
8380 ))
8381 })?;
8382 if int < 0 {
8383 return Err(EngineError::Unsupported(alloc::format!(
8384 "LIMIT/OFFSET ${n} bound to negative value {int}"
8385 )));
8386 }
8387 let bounded = u32::try_from(int).map_err(|_| {
8388 EngineError::Unsupported(alloc::format!(
8389 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
8390 ))
8391 })?;
8392 Ok(LimitExpr::Literal(bounded))
8393 }
8394 }
8395}
8396
8397fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
8398 if let Expr::Placeholder(n) = e {
8399 let idx = usize::from(*n).saturating_sub(1);
8400 let v = params.get(idx).ok_or_else(|| {
8401 EngineError::Eval(EvalError::PlaceholderOutOfRange {
8402 n: *n,
8403 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
8404 })
8405 })?;
8406 *e = Expr::Literal(value_to_literal(v.clone()));
8407 return Ok(());
8408 }
8409 match e {
8410 Expr::Binary { lhs, rhs, .. } => {
8411 substitute_expr(lhs, params)?;
8412 substitute_expr(rhs, params)?;
8413 }
8414 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
8415 substitute_expr(expr, params)?;
8416 }
8417 Expr::FunctionCall { args, .. } => {
8418 for a in args {
8419 substitute_expr(a, params)?;
8420 }
8421 }
8422 Expr::Like { expr, pattern, .. } => {
8423 substitute_expr(expr, params)?;
8424 substitute_expr(pattern, params)?;
8425 }
8426 Expr::Extract { source, .. } => substitute_expr(source, params)?,
8427 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
8428 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
8429 Expr::InSubquery { expr, subquery, .. } => {
8430 substitute_expr(expr, params)?;
8431 substitute_select(subquery, params)?;
8432 }
8433 Expr::WindowFunction {
8434 args,
8435 partition_by,
8436 order_by,
8437 ..
8438 } => {
8439 for a in args {
8440 substitute_expr(a, params)?;
8441 }
8442 for p in partition_by {
8443 substitute_expr(p, params)?;
8444 }
8445 for (e, _) in order_by {
8446 substitute_expr(e, params)?;
8447 }
8448 }
8449 Expr::Literal(_) | Expr::Column(_) => {}
8450 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
8452 Expr::Array(items) => {
8453 for elem in items {
8454 substitute_expr(elem, params)?;
8455 }
8456 }
8457 Expr::ArraySubscript { target, index } => {
8458 substitute_expr(target, params)?;
8459 substitute_expr(index, params)?;
8460 }
8461 Expr::AnyAll { expr, array, .. } => {
8462 substitute_expr(expr, params)?;
8463 substitute_expr(array, params)?;
8464 }
8465 Expr::Case {
8466 operand,
8467 branches,
8468 else_branch,
8469 } => {
8470 if let Some(o) = operand {
8471 substitute_expr(o, params)?;
8472 }
8473 for (w, t) in branches {
8474 substitute_expr(w, params)?;
8475 substitute_expr(t, params)?;
8476 }
8477 if let Some(e) = else_branch {
8478 substitute_expr(e, params)?;
8479 }
8480 }
8481 }
8482 Ok(())
8483}
8484
8485fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
8503 use core::cmp::Ordering;
8504 match (a, b) {
8505 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
8506 (Value::Int(a), Value::Int(b)) => a.cmp(b),
8507 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
8508 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
8509 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
8510 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
8511 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
8512 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
8513 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
8514 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
8515 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
8516 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
8517 (Value::Date(a), Value::Date(b)) => a.cmp(b),
8518 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
8519 (Value::SmallInt(n), Value::Float(x)) => {
8521 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
8522 }
8523 (Value::Float(x), Value::SmallInt(n)) => {
8524 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
8525 }
8526 (Value::Int(n), Value::Float(x)) => {
8527 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
8528 }
8529 (Value::Float(x), Value::Int(n)) => {
8530 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
8531 }
8532 (Value::BigInt(n), Value::Float(x)) => {
8533 #[allow(clippy::cast_precision_loss)]
8534 let nf = *n as f64;
8535 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
8536 }
8537 (Value::Float(x), Value::BigInt(n)) => {
8538 #[allow(clippy::cast_precision_loss)]
8539 let nf = *n as f64;
8540 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
8541 }
8542 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
8545 }
8546}
8547
8548fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
8555 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
8556 out.push('[');
8557 for (i, b) in bounds.iter().enumerate() {
8558 if i > 0 {
8559 out.push_str(", ");
8560 }
8561 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
8562 if needs_quote {
8563 out.push('"');
8564 for ch in b.chars() {
8565 if ch == '"' || ch == '\\' {
8566 out.push('\\');
8567 }
8568 out.push(ch);
8569 }
8570 out.push('"');
8571 } else {
8572 out.push_str(b);
8573 }
8574 }
8575 out.push(']');
8576 out
8577}
8578
8579pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
8589 match v {
8590 Value::Null => "NULL".to_string(),
8591 Value::SmallInt(n) => alloc::format!("{n}"),
8592 Value::Int(n) => alloc::format!("{n}"),
8593 Value::BigInt(n) => alloc::format!("{n}"),
8594 Value::Float(x) => alloc::format!("{x:?}"),
8595 Value::Text(s) | Value::Json(s) => s.clone(),
8596 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
8597 Value::Date(d) => eval::format_date(*d),
8598 Value::Timestamp(t) => eval::format_timestamp(*t),
8599 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
8600 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
8601 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8602 alloc::format!("{v:?}")
8606 }
8607 _ => alloc::format!("{v:?}"),
8611 }
8612}
8613
8614const fn is_internal_table_name(_name: &str) -> bool {
8621 false
8622}
8623
8624fn value_to_literal(v: Value) -> Literal {
8625 match v {
8626 Value::Null => Literal::Null,
8627 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
8628 Value::Int(n) => Literal::Integer(i64::from(n)),
8629 Value::BigInt(n) => Literal::Integer(n),
8630 Value::Float(x) => Literal::Float(x),
8631 Value::Text(s) | Value::Json(s) => Literal::String(s),
8632 Value::Bool(b) => Literal::Bool(b),
8633 Value::Vector(v) => Literal::Vector(v),
8634 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
8635 Value::Date(d) => Literal::String(eval::format_date(d)),
8636 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
8637 Value::Interval { months, micros } => Literal::Interval {
8638 months,
8639 micros,
8640 text: eval::format_interval(months, micros),
8641 },
8642 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
8645 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
8646 v => Literal::String(alloc::format!("{v:?}")),
8650 }
8651}
8652
8653fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
8654 let Some(now) = now_micros else {
8655 return;
8656 };
8657 match stmt {
8658 Statement::Select(s) => rewrite_select_clock(s, now),
8659 Statement::Insert(ins) => {
8660 for row in &mut ins.rows {
8661 for e in row {
8662 rewrite_expr_clock(e, now);
8663 }
8664 }
8665 }
8666 _ => {}
8667 }
8668}
8669
8670fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
8671 for item in &mut s.items {
8672 if let SelectItem::Expr { expr, .. } = item {
8673 rewrite_expr_clock(expr, now);
8674 }
8675 }
8676 if let Some(w) = &mut s.where_ {
8677 rewrite_expr_clock(w, now);
8678 }
8679 if let Some(gs) = &mut s.group_by {
8680 for g in gs {
8681 rewrite_expr_clock(g, now);
8682 }
8683 }
8684 if let Some(h) = &mut s.having {
8685 rewrite_expr_clock(h, now);
8686 }
8687 for o in &mut s.order_by {
8688 rewrite_expr_clock(&mut o.expr, now);
8689 }
8690 for (_, peer) in &mut s.unions {
8691 rewrite_select_clock(peer, now);
8692 }
8693}
8694
8695fn rewrite_expr_clock(e: &mut Expr, now: i64) {
8703 if let Some(replacement) = clock_replacement_for(e, now) {
8707 *e = replacement;
8708 return;
8709 }
8710 match e {
8711 Expr::Binary { lhs, rhs, .. } => {
8712 rewrite_expr_clock(lhs, now);
8713 rewrite_expr_clock(rhs, now);
8714 }
8715 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
8716 rewrite_expr_clock(expr, now);
8717 }
8718 Expr::FunctionCall { args, .. } => {
8719 for a in args {
8720 rewrite_expr_clock(a, now);
8721 }
8722 }
8723 Expr::Like { expr, pattern, .. } => {
8724 rewrite_expr_clock(expr, now);
8725 rewrite_expr_clock(pattern, now);
8726 }
8727 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
8728 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
8732 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
8733 Expr::InSubquery { expr, subquery, .. } => {
8734 rewrite_expr_clock(expr, now);
8735 rewrite_select_clock(subquery, now);
8736 }
8737 Expr::WindowFunction {
8740 args,
8741 partition_by,
8742 order_by,
8743 ..
8744 } => {
8745 for a in args {
8746 rewrite_expr_clock(a, now);
8747 }
8748 for p in partition_by {
8749 rewrite_expr_clock(p, now);
8750 }
8751 for (e, _) in order_by {
8752 rewrite_expr_clock(e, now);
8753 }
8754 }
8755 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
8756 Expr::Array(items) => {
8757 for elem in items {
8758 rewrite_expr_clock(elem, now);
8759 }
8760 }
8761 Expr::ArraySubscript { target, index } => {
8762 rewrite_expr_clock(target, now);
8763 rewrite_expr_clock(index, now);
8764 }
8765 Expr::AnyAll { expr, array, .. } => {
8766 rewrite_expr_clock(expr, now);
8767 rewrite_expr_clock(array, now);
8768 }
8769 Expr::Case {
8770 operand,
8771 branches,
8772 else_branch,
8773 } => {
8774 if let Some(o) = operand {
8775 rewrite_expr_clock(o, now);
8776 }
8777 for (w, t) in branches {
8778 rewrite_expr_clock(w, now);
8779 rewrite_expr_clock(t, now);
8780 }
8781 if let Some(e) = else_branch {
8782 rewrite_expr_clock(e, now);
8783 }
8784 }
8785 }
8786}
8787
8788fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
8795 let (kind, name) = match e {
8796 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
8797 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
8798 _ => return None,
8799 };
8800 let matched = match name.len() {
8803 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => Some(true),
8804 12 if name.eq_ignore_ascii_case("current_date") => Some(false),
8805 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(true),
8806 _ => None,
8807 };
8808 let is_timestamp = matched?;
8809 let payload = if is_timestamp {
8810 now
8811 } else {
8812 now.div_euclid(86_400_000_000)
8813 };
8814 let target = if is_timestamp {
8815 spg_sql::ast::CastTarget::Timestamp
8816 } else {
8817 spg_sql::ast::CastTarget::Date
8818 };
8819 Some(Expr::Cast {
8820 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
8821 target,
8822 })
8823}
8824
8825#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8826enum ClockSite {
8827 Fn,
8828 BareIdent,
8829}
8830
8831fn expand_group_by_all(s: &mut SelectStatement) {
8842 if !s.group_by_all {
8843 for (_, peer) in &mut s.unions {
8844 expand_group_by_all(peer);
8845 }
8846 return;
8847 }
8848 let mut groups: Vec<Expr> = Vec::new();
8849 for item in &s.items {
8850 if let SelectItem::Expr { expr, .. } = item
8851 && !aggregate::contains_aggregate(expr)
8852 {
8853 groups.push(expr.clone());
8854 }
8855 }
8856 s.group_by = Some(groups);
8857 s.group_by_all = false;
8858 for (_, peer) in &mut s.unions {
8859 expand_group_by_all(peer);
8860 }
8861}
8862
8863fn resolve_order_by_position(s: &mut SelectStatement) {
8864 for order in &mut s.order_by {
8869 match &order.expr {
8870 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
8871 if let Ok(idx_one_based) = usize::try_from(*n) {
8872 let idx = idx_one_based - 1;
8873 if idx < s.items.len()
8874 && let SelectItem::Expr { expr, .. } = &s.items[idx]
8875 {
8876 order.expr = expr.clone();
8877 }
8878 }
8879 }
8880 Expr::Column(c) if c.qualifier.is_none() => {
8881 for item in &s.items {
8883 if let SelectItem::Expr {
8884 expr,
8885 alias: Some(a),
8886 } = item
8887 && a == &c.name
8888 {
8889 order.expr = expr.clone();
8890 break;
8891 }
8892 }
8893 }
8894 _ => {}
8895 }
8896 }
8897 for (_, peer) in &mut s.unions {
8898 resolve_order_by_position(peer);
8899 }
8900}
8901
8902fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
8915 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
8916 match keep {
8917 Some(k) if k < tagged.len() && k > 0 => {
8918 let pivot = k - 1;
8919 tagged.select_nth_unstable_by(pivot, cmp);
8920 tagged[..k].sort_by(cmp);
8921 tagged.truncate(k);
8922 }
8923 _ => {
8924 tagged.sort_by(cmp);
8925 }
8926 }
8927}
8928
8929fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
8930 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
8931}
8932
8933fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
8937 use core::cmp::Ordering;
8938 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
8939 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
8940 let ord = if descs.get(i).copied().unwrap_or(false) {
8941 ord.reverse()
8942 } else {
8943 ord
8944 };
8945 if ord != Ordering::Equal {
8946 return ord;
8947 }
8948 }
8949 Ordering::Equal
8950}
8951
8952fn build_order_keys(
8955 order_by: &[OrderBy],
8956 row: &Row,
8957 ctx: &EvalContext,
8958) -> Result<Vec<f64>, EngineError> {
8959 let mut keys = Vec::with_capacity(order_by.len());
8960 for o in order_by {
8961 let v = eval::eval_expr(&o.expr, row, ctx)?;
8962 keys.push(value_to_order_key(&v)?);
8963 }
8964 Ok(keys)
8965}
8966
8967fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
8971 if let Some(off) = offset {
8972 let off = off as usize;
8973 if off >= rows.len() {
8974 rows.clear();
8975 } else {
8976 rows.drain(..off);
8977 }
8978 }
8979 if let Some(n) = limit {
8980 rows.truncate(n as usize);
8981 }
8982}
8983
8984fn resolve_foreign_key(
8998 local_table_name: &str,
8999 local_cols: &[ColumnSchema],
9000 fk: spg_sql::ast::ForeignKeyConstraint,
9001 catalog: &Catalog,
9002) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
9003 let mut local_columns = Vec::with_capacity(fk.columns.len());
9005 for name in &fk.columns {
9006 let pos = local_cols
9007 .iter()
9008 .position(|c| c.name == *name)
9009 .ok_or_else(|| {
9010 EngineError::Unsupported(alloc::format!(
9011 "FOREIGN KEY references unknown local column {name:?}"
9012 ))
9013 })?;
9014 local_columns.push(pos);
9015 }
9016 let is_self_ref = fk.parent_table == local_table_name;
9020 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
9021 (local_cols, local_table_name)
9022 } else {
9023 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
9024 EngineError::Storage(StorageError::TableNotFound {
9025 name: fk.parent_table.clone(),
9026 })
9027 })?;
9028 (
9029 parent_table.schema().columns.as_slice(),
9030 fk.parent_table.as_str(),
9031 )
9032 };
9033 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
9038 if fk.columns.len() != 1 {
9039 return Err(EngineError::Unsupported(
9040 "composite FOREIGN KEY without explicit parent column list is not supported \
9041 — list the parent columns explicitly"
9042 .into(),
9043 ));
9044 }
9045 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
9047 .ok_or_else(|| {
9048 EngineError::Unsupported(alloc::format!(
9049 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
9050 to default the FOREIGN KEY against"
9051 ))
9052 })?;
9053 alloc::vec![pos]
9054 } else {
9055 let mut out = Vec::with_capacity(fk.parent_columns.len());
9056 for name in &fk.parent_columns {
9057 let pos = parent_cols_for_lookup
9058 .iter()
9059 .position(|c| c.name == *name)
9060 .ok_or_else(|| {
9061 EngineError::Unsupported(alloc::format!(
9062 "FOREIGN KEY references unknown parent column \
9063 {name:?} on table {parent_table_str:?}"
9064 ))
9065 })?;
9066 out.push(pos);
9067 }
9068 out
9069 };
9070 if parent_columns.len() != local_columns.len() {
9071 return Err(EngineError::Unsupported(alloc::format!(
9072 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
9073 local_columns.len(),
9074 parent_columns.len()
9075 )));
9076 }
9077 if !is_self_ref {
9087 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
9088 let primary_parent_col = parent_columns[0];
9089 let has_btree = parent_table
9090 .schema()
9091 .columns
9092 .get(primary_parent_col)
9093 .is_some()
9094 && parent_table.indices().iter().any(|idx| {
9095 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
9096 && idx.column_position == primary_parent_col
9097 && idx.partial_predicate.is_none()
9098 });
9099 if !has_btree {
9100 return Err(EngineError::Unsupported(alloc::format!(
9101 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
9102 index — create one with `CREATE INDEX ... ON {} ({})` first",
9103 parent_table_str,
9104 parent_table_str,
9105 parent_table.schema().columns[primary_parent_col].name,
9106 )));
9107 }
9108 }
9109 let on_delete = fk_action_sql_to_storage(fk.on_delete);
9110 let on_update = fk_action_sql_to_storage(fk.on_update);
9111 Ok(spg_storage::ForeignKeyConstraint {
9112 name: fk.name,
9113 local_columns,
9114 parent_table: fk.parent_table,
9115 parent_columns,
9116 on_delete,
9117 on_update,
9118 })
9119}
9120
9121fn pick_pk_index_column(
9127 catalog: &Catalog,
9128 parent_name: &str,
9129 is_self_ref: bool,
9130 local_cols: &[ColumnSchema],
9131) -> Option<usize> {
9132 if is_self_ref {
9133 let _ = local_cols;
9137 return Some(0);
9138 }
9139 let parent = catalog.get(parent_name)?;
9140 parent.indices().iter().find_map(|idx| {
9141 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
9142 && idx.partial_predicate.is_none()
9143 && idx.included_columns.is_empty()
9144 && idx.expression.is_none()
9145 {
9146 Some(idx.column_position)
9147 } else {
9148 None
9149 }
9150 })
9151}
9152
9153fn resolve_on_conflict_columns(
9160 catalog: &Catalog,
9161 table_name: &str,
9162 target: &[String],
9163) -> Result<Vec<usize>, EngineError> {
9164 let table = catalog.get(table_name).ok_or_else(|| {
9165 EngineError::Storage(StorageError::TableNotFound {
9166 name: table_name.into(),
9167 })
9168 })?;
9169 if target.is_empty() {
9170 let pos = table
9171 .indices()
9172 .iter()
9173 .find_map(|idx| {
9174 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
9175 && idx.partial_predicate.is_none()
9176 && idx.included_columns.is_empty()
9177 && idx.expression.is_none()
9178 {
9179 Some(idx.column_position)
9180 } else {
9181 None
9182 }
9183 })
9184 .ok_or_else(|| {
9185 EngineError::Unsupported(alloc::format!(
9186 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
9187 ))
9188 })?;
9189 return Ok(alloc::vec![pos]);
9190 }
9191 let mut out = Vec::with_capacity(target.len());
9192 for name in target {
9193 let pos = table
9194 .schema()
9195 .columns
9196 .iter()
9197 .position(|c| c.name == *name)
9198 .ok_or_else(|| {
9199 EngineError::Unsupported(alloc::format!(
9200 "ON CONFLICT target column {name:?} not found on {table_name:?}"
9201 ))
9202 })?;
9203 out.push(pos);
9204 }
9205 Ok(out)
9206}
9207
9208fn on_conflict_key_exists(
9211 catalog: &Catalog,
9212 table_name: &str,
9213 column_pos: usize,
9214 key: &Value,
9215) -> bool {
9216 let Some(table) = catalog.get(table_name) else {
9217 return false;
9218 };
9219 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
9220 return false;
9221 };
9222 table.indices().iter().any(|idx| {
9223 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
9224 && idx.column_position == column_pos
9225 && idx.partial_predicate.is_none()
9226 && !idx.lookup_eq(&idx_key).is_empty()
9227 })
9228}
9229
9230fn lookup_row_position_by_keys(
9236 catalog: &Catalog,
9237 table_name: &str,
9238 column_positions: &[usize],
9239 key: &[&Value],
9240) -> Option<usize> {
9241 let table = catalog.get(table_name)?;
9242 table.rows().iter().position(|r| {
9243 column_positions
9244 .iter()
9245 .enumerate()
9246 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
9247 })
9248}
9249
9250fn on_conflict_keys_exist(
9255 catalog: &Catalog,
9256 table_name: &str,
9257 column_positions: &[usize],
9258 key: &[&Value],
9259) -> bool {
9260 if column_positions.len() == 1 {
9261 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
9262 }
9263 let Some(table) = catalog.get(table_name) else {
9264 return false;
9265 };
9266 table.rows().iter().any(|r| {
9267 column_positions
9268 .iter()
9269 .enumerate()
9270 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
9271 })
9272}
9273
9274fn apply_on_conflict_assignments(
9287 catalog: &Catalog,
9288 table_name: &str,
9289 target_pos: usize,
9290 incoming: &[Value],
9291 assignments: &[(String, Expr)],
9292 where_: Option<&Expr>,
9293) -> Result<Option<Vec<Value>>, EngineError> {
9294 let table = catalog.get(table_name).ok_or_else(|| {
9295 EngineError::Storage(StorageError::TableNotFound {
9296 name: table_name.into(),
9297 })
9298 })?;
9299 let schema_cols = table.schema().columns.clone();
9300 let existing = table
9301 .rows()
9302 .get(target_pos)
9303 .ok_or_else(|| {
9304 EngineError::Unsupported(alloc::format!(
9305 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
9306 ))
9307 })?
9308 .clone();
9309 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
9310 if let Some(w) = where_ {
9312 let pred = w.clone();
9313 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
9314 let v = eval::eval_expr(&pred, &existing, &ctx)?;
9315 if !matches!(v, Value::Bool(true)) {
9316 return Ok(None);
9317 }
9318 }
9319 let mut new_values = existing.values.clone();
9320 for (col_name, expr) in assignments {
9321 let target_idx = schema_cols
9322 .iter()
9323 .position(|c| c.name == *col_name)
9324 .ok_or_else(|| {
9325 EngineError::Eval(EvalError::ColumnNotFound {
9326 name: col_name.clone(),
9327 })
9328 })?;
9329 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
9330 let v = eval::eval_expr(&sub, &existing, &ctx)?;
9331 new_values[target_idx] = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
9332 }
9333 Ok(Some(new_values))
9334}
9335
9336fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
9341 use spg_sql::ast::ColumnName;
9342 match expr {
9343 Expr::Column(ColumnName { qualifier, name })
9344 if qualifier
9345 .as_deref()
9346 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
9347 {
9348 let pos = schema_cols.iter().position(|c| c.name == name);
9349 match pos {
9350 Some(p) => {
9351 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
9352 value_to_literal_expr(v)
9353 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
9354 }
9355 None => Expr::Column(ColumnName { qualifier, name }),
9356 }
9357 }
9358 Expr::Binary { op, lhs, rhs } => Expr::Binary {
9359 op,
9360 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
9361 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
9362 },
9363 Expr::Unary { op, expr } => Expr::Unary {
9364 op,
9365 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
9366 },
9367 Expr::FunctionCall { name, args } => Expr::FunctionCall {
9368 name,
9369 args: args
9370 .into_iter()
9371 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
9372 .collect(),
9373 },
9374 other => other,
9375 }
9376}
9377
9378fn enforce_uniqueness_inserts(
9401 catalog: &Catalog,
9402 child_table: &str,
9403 constraints: &[spg_storage::UniquenessConstraint],
9404 rows: &[Vec<Value>],
9405) -> Result<(), EngineError> {
9406 if constraints.is_empty() {
9407 return Ok(());
9408 }
9409 let table = catalog.get(child_table).ok_or_else(|| {
9410 EngineError::Storage(StorageError::TableNotFound {
9411 name: child_table.into(),
9412 })
9413 })?;
9414 for uc in constraints {
9415 for (batch_idx, row_values) in rows.iter().enumerate() {
9416 let key: Vec<&Value> = uc.columns.iter().map(|&i| &row_values[i]).collect();
9417 let has_null = key.iter().any(|v| matches!(v, Value::Null));
9418 if has_null && !uc.nulls_not_distinct {
9423 continue;
9424 }
9425 let collides_in_table = table.rows().iter().any(|prow| {
9427 uc.columns
9428 .iter()
9429 .enumerate()
9430 .all(|(i, &p)| prow.values.get(p) == Some(key[i]))
9431 });
9432 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
9434 uc.columns
9435 .iter()
9436 .enumerate()
9437 .all(|(i, &p)| earlier.get(p) == Some(key[i]))
9438 });
9439 if collides_in_table || collides_in_batch {
9440 let kind = if uc.is_primary_key {
9441 "PRIMARY KEY"
9442 } else {
9443 "UNIQUE"
9444 };
9445 let col_names: Vec<String> = uc
9446 .columns
9447 .iter()
9448 .map(|&i| table.schema().columns[i].name.clone())
9449 .collect();
9450 return Err(EngineError::Unsupported(alloc::format!(
9451 "{kind} violation on {child_table:?} columns {col_names:?}: \
9452 row #{batch_idx} duplicates an existing key"
9453 )));
9454 }
9455 }
9456 }
9457 Ok(())
9458}
9459
9460fn predicate_truthy(v: &spg_storage::Value) -> bool {
9468 use spg_storage::Value as V;
9469 match v {
9470 V::Bool(b) => *b,
9471 V::Int(n) => *n != 0,
9472 V::BigInt(n) => *n != 0,
9473 V::SmallInt(n) => *n != 0,
9474 _ => false,
9475 }
9476}
9477
9478fn check_existing_unique_violation(
9483 idx: &spg_storage::Index,
9484 schema: &spg_storage::TableSchema,
9485 rows: &[spg_storage::Row],
9486) -> Result<(), EngineError> {
9487 let predicate_expr = match idx.partial_predicate.as_deref() {
9488 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
9489 EngineError::Unsupported(alloc::format!(
9490 "stored partial predicate {s:?} failed to re-parse: {e:?}"
9491 ))
9492 })?),
9493 None => None,
9494 };
9495 let ctx = eval::EvalContext::new(&schema.columns, None);
9496 let key_positions = unique_key_positions(idx);
9497 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
9498 for row in rows {
9499 if let Some(expr) = &predicate_expr {
9500 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
9501 EngineError::Unsupported(alloc::format!(
9502 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
9503 ))
9504 })?;
9505 if !predicate_truthy(&v) {
9506 continue;
9507 }
9508 }
9509 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
9510 .iter()
9511 .map(|&p| {
9512 row.values
9513 .get(p)
9514 .cloned()
9515 .unwrap_or(spg_storage::Value::Null)
9516 })
9517 .collect();
9518 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
9519 continue;
9520 }
9521 if seen.iter().any(|other| *other == key) {
9522 return Err(EngineError::Unsupported(alloc::format!(
9523 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
9524 idx.name
9525 )));
9526 }
9527 seen.push(key);
9528 }
9529 Ok(())
9530}
9531
9532fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
9536 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
9537 out.push(idx.column_position);
9538 out.extend_from_slice(&idx.extra_column_positions);
9539 out
9540}
9541
9542fn enforce_unique_index_inserts(
9550 catalog: &Catalog,
9551 table_name: &str,
9552 rows: &[alloc::vec::Vec<spg_storage::Value>],
9553) -> Result<(), EngineError> {
9554 let table = catalog.get(table_name).ok_or_else(|| {
9555 EngineError::Storage(StorageError::TableNotFound {
9556 name: table_name.into(),
9557 })
9558 })?;
9559 let schema = table.schema();
9560 let ctx = eval::EvalContext::new(&schema.columns, None);
9561 for idx in table.indices() {
9562 if !idx.is_unique {
9563 continue;
9564 }
9565 let predicate_expr = match idx.partial_predicate.as_deref() {
9567 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
9568 EngineError::Unsupported(alloc::format!(
9569 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
9570 idx.name
9571 ))
9572 })?),
9573 None => None,
9574 };
9575 let key_positions = unique_key_positions(idx);
9576 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
9577 key_positions
9578 .iter()
9579 .map(|&p| values.get(p).cloned().unwrap_or(spg_storage::Value::Null))
9580 .collect()
9581 };
9582 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
9586 let Some(expr) = &predicate_expr else {
9587 return Ok(true);
9588 };
9589 let tmp_row = spg_storage::Row {
9590 values: values.to_vec(),
9591 };
9592 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
9593 EngineError::Unsupported(alloc::format!(
9594 "UNIQUE INDEX {:?} predicate eval: {e:?}",
9595 idx.name
9596 ))
9597 })?;
9598 Ok(predicate_truthy(&v))
9599 };
9600 for (batch_idx, row_values) in rows.iter().enumerate() {
9601 if !participates(row_values)? {
9602 continue;
9603 }
9604 let key = key_of(row_values);
9605 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
9606 continue;
9607 }
9608 for prow in table.rows() {
9610 if !participates(&prow.values)? {
9611 continue;
9612 }
9613 if key_of(&prow.values) == key {
9614 return Err(EngineError::Unsupported(alloc::format!(
9615 "UNIQUE INDEX {:?} violation on {table_name:?}: \
9616 row #{batch_idx} duplicates an existing key",
9617 idx.name
9618 )));
9619 }
9620 }
9621 for earlier in &rows[..batch_idx] {
9623 if !participates(earlier)? {
9624 continue;
9625 }
9626 if key_of(earlier) == key {
9627 return Err(EngineError::Unsupported(alloc::format!(
9628 "UNIQUE INDEX {:?} violation on {table_name:?}: \
9629 row #{batch_idx} duplicates an earlier row in the same batch",
9630 idx.name
9631 )));
9632 }
9633 }
9634 }
9635 }
9636 Ok(())
9637}
9638
9639fn any_column_changed(
9647 filter_cols: &[String],
9648 schema_cols: &[ColumnSchema],
9649 old_row: &Row,
9650 new_row: &Row,
9651) -> bool {
9652 for col_name in filter_cols {
9653 let Some(pos) = schema_cols
9654 .iter()
9655 .position(|c| c.name.eq_ignore_ascii_case(col_name))
9656 else {
9657 continue;
9658 };
9659 let old_v = old_row.values.get(pos);
9660 let new_v = new_row.values.get(pos);
9661 if old_v != new_v {
9662 return true;
9663 }
9664 }
9665 false
9666}
9667
9668fn enforce_check_constraints(
9673 catalog: &Catalog,
9674 table_name: &str,
9675 rows: &[alloc::vec::Vec<spg_storage::Value>],
9676) -> Result<(), EngineError> {
9677 let table = catalog.get(table_name).ok_or_else(|| {
9678 EngineError::Storage(StorageError::TableNotFound {
9679 name: table_name.into(),
9680 })
9681 })?;
9682 let schema = table.schema();
9683 if schema.checks.is_empty() {
9684 return Ok(());
9685 }
9686 let ctx = eval::EvalContext::new(&schema.columns, None);
9687 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
9688 for (i, src) in schema.checks.iter().enumerate() {
9689 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
9690 EngineError::Unsupported(alloc::format!(
9691 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
9692 ))
9693 })?;
9694 parsed.push((i, expr));
9695 }
9696 for (batch_idx, row_values) in rows.iter().enumerate() {
9697 let tmp_row = spg_storage::Row {
9698 values: row_values.clone(),
9699 };
9700 for (i, expr) in &parsed {
9701 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
9702 EngineError::Unsupported(alloc::format!(
9703 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
9704 ))
9705 })?;
9706 if matches!(v, spg_storage::Value::Bool(false)) {
9708 return Err(EngineError::Unsupported(alloc::format!(
9709 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
9710 schema.checks[*i]
9711 )));
9712 }
9713 }
9714 }
9715 Ok(())
9716}
9717
9718fn enforce_fk_inserts(
9719 catalog: &Catalog,
9720 child_table: &str,
9721 fks: &[spg_storage::ForeignKeyConstraint],
9722 rows: &[Vec<Value>],
9723) -> Result<(), EngineError> {
9724 for fk in fks {
9725 let parent_is_self = fk.parent_table == child_table;
9726 let parent = if parent_is_self {
9727 catalog.get(child_table).ok_or_else(|| {
9730 EngineError::Storage(StorageError::TableNotFound {
9731 name: child_table.into(),
9732 })
9733 })?
9734 } else {
9735 catalog.get(&fk.parent_table).ok_or_else(|| {
9736 EngineError::Storage(StorageError::TableNotFound {
9737 name: fk.parent_table.clone(),
9738 })
9739 })?
9740 };
9741 for (batch_idx, row_values) in rows.iter().enumerate() {
9742 if fk.local_columns.len() == 1 {
9746 let v = &row_values[fk.local_columns[0]];
9747 if matches!(v, Value::Null) {
9748 continue;
9749 }
9750 let parent_col = fk.parent_columns[0];
9751 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
9752 EngineError::Unsupported(alloc::format!(
9753 "FOREIGN KEY column value of type {:?} is not index-eligible",
9754 v.data_type()
9755 ))
9756 })?;
9757 let present_committed = parent.indices().iter().any(|idx| {
9758 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
9759 && idx.column_position == parent_col
9760 && idx.partial_predicate.is_none()
9761 && !idx.lookup_eq(&key).is_empty()
9762 });
9763 let present_in_batch = parent_is_self
9767 && rows[..batch_idx]
9768 .iter()
9769 .any(|earlier| earlier.get(parent_col) == Some(v));
9770 if !(present_committed || present_in_batch) {
9771 return Err(EngineError::Unsupported(alloc::format!(
9772 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
9773 fk.parent_table,
9774 parent
9775 .schema()
9776 .columns
9777 .get(parent_col)
9778 .map_or("?", |c| c.name.as_str()),
9779 v,
9780 )));
9781 }
9782 } else {
9783 if fk
9787 .local_columns
9788 .iter()
9789 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
9790 {
9791 continue;
9792 }
9793 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
9794 let parent_match_committed = parent.rows().iter().any(|prow| {
9795 fk.parent_columns
9796 .iter()
9797 .enumerate()
9798 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
9799 });
9800 let parent_match_in_batch = parent_is_self
9801 && rows[..batch_idx].iter().any(|earlier| {
9802 fk.parent_columns
9803 .iter()
9804 .enumerate()
9805 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
9806 });
9807 if !(parent_match_committed || parent_match_in_batch) {
9808 return Err(EngineError::Unsupported(alloc::format!(
9809 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
9810 fk.parent_table,
9811 )));
9812 }
9813 }
9814 }
9815 }
9816 Ok(())
9817}
9818
9819#[derive(Debug, Clone)]
9823struct FkChildStep {
9824 child_table: String,
9825 action: FkChildAction,
9826}
9827
9828#[derive(Debug, Clone)]
9829enum FkChildAction {
9830 Delete { positions: Vec<usize> },
9832 SetNull {
9836 positions: Vec<usize>,
9837 columns: Vec<usize>,
9838 },
9839 SetDefault {
9843 positions: Vec<usize>,
9844 columns: Vec<usize>,
9845 defaults: Vec<Value>,
9846 },
9847}
9848
9849fn plan_fk_parent_deletions(
9865 catalog: &Catalog,
9866 parent_table_name: &str,
9867 to_delete_positions: &[usize],
9868 to_delete_rows: &[Vec<Value>],
9869) -> Result<Vec<FkChildStep>, EngineError> {
9870 use alloc::collections::{BTreeMap, BTreeSet};
9871 if to_delete_rows.is_empty() {
9872 return Ok(Vec::new());
9873 }
9874 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
9875 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
9877 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
9878 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
9879 for &p in to_delete_positions {
9880 visited.insert((parent_table_name.to_string(), p));
9881 }
9882 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
9883 .iter()
9884 .map(|r| (parent_table_name.to_string(), r.clone()))
9885 .collect();
9886 while let Some((cur_parent, parent_row)) = work.pop() {
9887 for child_name in catalog.table_names() {
9888 let child = catalog
9889 .get(&child_name)
9890 .expect("table_names → catalog.get round-trip is total");
9891 for fk in &child.schema().foreign_keys {
9892 if fk.parent_table != cur_parent {
9893 continue;
9894 }
9895 let parent_key: Vec<&Value> = fk
9896 .parent_columns
9897 .iter()
9898 .map(|&pi| &parent_row[pi])
9899 .collect();
9900 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
9901 continue;
9902 }
9903 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
9904 if child_name == cur_parent
9905 && visited.contains(&(child_name.clone(), child_row_idx))
9906 {
9907 continue;
9908 }
9909 let matches_key = fk
9910 .local_columns
9911 .iter()
9912 .enumerate()
9913 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
9914 if !matches_key {
9915 continue;
9916 }
9917 match fk.on_delete {
9918 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
9919 return Err(EngineError::Unsupported(alloc::format!(
9920 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
9921 restricted by FK from {child_name:?}.{:?}",
9922 fk.local_columns,
9923 )));
9924 }
9925 spg_storage::FkAction::Cascade => {
9926 if visited.insert((child_name.clone(), child_row_idx)) {
9927 delete_plan
9928 .entry(child_name.clone())
9929 .or_default()
9930 .insert(child_row_idx);
9931 work.push((child_name.clone(), child_row.values.clone()));
9932 }
9933 }
9934 spg_storage::FkAction::SetNull => {
9935 for &li in &fk.local_columns {
9937 let col = child.schema().columns.get(li).ok_or_else(|| {
9938 EngineError::Unsupported(alloc::format!(
9939 "FK local column {li} missing in {child_name:?}"
9940 ))
9941 })?;
9942 if !col.nullable {
9943 return Err(EngineError::Unsupported(alloc::format!(
9944 "FOREIGN KEY ON DELETE SET NULL: column \
9945 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
9946 col.name,
9947 )));
9948 }
9949 }
9950 let entry = setnull_plan.entry(child_name.clone()).or_default();
9951 for &li in &fk.local_columns {
9952 entry.insert((child_row_idx, li));
9953 }
9954 }
9955 spg_storage::FkAction::SetDefault => {
9956 let entry = setdefault_plan.entry(child_name.clone()).or_default();
9958 for &li in &fk.local_columns {
9959 let col = child.schema().columns.get(li).ok_or_else(|| {
9960 EngineError::Unsupported(alloc::format!(
9961 "FK local column {li} missing in {child_name:?}"
9962 ))
9963 })?;
9964 let default = col.default.clone().ok_or_else(|| {
9965 EngineError::Unsupported(alloc::format!(
9966 "FOREIGN KEY ON DELETE SET DEFAULT: column \
9967 {child_name:?}.{:?} has no DEFAULT declared",
9968 col.name,
9969 ))
9970 })?;
9971 entry.insert((child_row_idx, li), default);
9972 }
9973 }
9974 }
9975 }
9976 }
9977 }
9978 }
9979 let mut steps: Vec<FkChildStep> = Vec::new();
9987 for (child_table, entries) in setnull_plan {
9988 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
9989 steps.push(FkChildStep {
9990 child_table,
9991 action: FkChildAction::SetNull { positions, columns },
9992 });
9993 }
9994 for (child_table, entries) in setdefault_plan {
9995 let mut positions = Vec::with_capacity(entries.len());
9996 let mut columns = Vec::with_capacity(entries.len());
9997 let mut defaults = Vec::with_capacity(entries.len());
9998 for ((p, c), v) in entries {
9999 positions.push(p);
10000 columns.push(c);
10001 defaults.push(v);
10002 }
10003 steps.push(FkChildStep {
10004 child_table,
10005 action: FkChildAction::SetDefault {
10006 positions,
10007 columns,
10008 defaults,
10009 },
10010 });
10011 }
10012 for (child_table, positions) in delete_plan {
10013 steps.push(FkChildStep {
10014 child_table,
10015 action: FkChildAction::Delete {
10016 positions: positions.into_iter().collect(),
10017 },
10018 });
10019 }
10020 Ok(steps)
10021}
10022
10023fn plan_fk_parent_updates(
10040 catalog: &Catalog,
10041 parent_table_name: &str,
10042 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
10043) -> Result<Vec<FkChildStep>, EngineError> {
10044 use alloc::collections::BTreeMap;
10045 if plan_with_old.is_empty() {
10046 return Ok(Vec::new());
10047 }
10048 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
10053 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
10054 BTreeMap::new();
10055 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
10056 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
10058
10059 for child_name in catalog.table_names() {
10060 let child = catalog
10061 .get(&child_name)
10062 .expect("table_names → catalog.get total");
10063 for fk in &child.schema().foreign_keys {
10064 if fk.parent_table != parent_table_name {
10065 continue;
10066 }
10067 for (_pos, old_row, new_row) in plan_with_old {
10068 let key_changed = fk
10070 .parent_columns
10071 .iter()
10072 .any(|&pi| old_row.get(pi) != new_row.get(pi));
10073 if !key_changed {
10074 continue;
10075 }
10076 let old_key: Vec<&Value> =
10078 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
10079 if old_key.iter().any(|v| matches!(v, Value::Null)) {
10080 continue;
10082 }
10083 let new_key: Vec<&Value> =
10084 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
10085 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
10086 if child_name == parent_table_name
10089 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
10090 {
10091 continue;
10092 }
10093 let matches_key = fk
10094 .local_columns
10095 .iter()
10096 .enumerate()
10097 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
10098 if !matches_key {
10099 continue;
10100 }
10101 match fk.on_update {
10102 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
10103 return Err(EngineError::Unsupported(alloc::format!(
10104 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
10105 restricted by FK from {child_name:?}.{:?}",
10106 fk.local_columns,
10107 )));
10108 }
10109 spg_storage::FkAction::Cascade => {
10110 let entry = cascade_plan.entry(child_name.clone()).or_default();
10112 for (i, &li) in fk.local_columns.iter().enumerate() {
10113 entry.insert((child_row_idx, li), new_key[i].clone());
10114 }
10115 }
10116 spg_storage::FkAction::SetNull => {
10117 for &li in &fk.local_columns {
10118 let col = child.schema().columns.get(li).ok_or_else(|| {
10119 EngineError::Unsupported(alloc::format!(
10120 "FK local column {li} missing in {child_name:?}"
10121 ))
10122 })?;
10123 if !col.nullable {
10124 return Err(EngineError::Unsupported(alloc::format!(
10125 "FOREIGN KEY ON UPDATE SET NULL: column \
10126 {child_name:?}.{:?} is NOT NULL",
10127 col.name,
10128 )));
10129 }
10130 }
10131 let entry = setnull_plan.entry(child_name.clone()).or_default();
10132 for &li in &fk.local_columns {
10133 entry.insert((child_row_idx, li));
10134 }
10135 }
10136 spg_storage::FkAction::SetDefault => {
10137 let entry = setdefault_plan.entry(child_name.clone()).or_default();
10138 for &li in &fk.local_columns {
10139 let col = child.schema().columns.get(li).ok_or_else(|| {
10140 EngineError::Unsupported(alloc::format!(
10141 "FK local column {li} missing in {child_name:?}"
10142 ))
10143 })?;
10144 let default = col.default.clone().ok_or_else(|| {
10145 EngineError::Unsupported(alloc::format!(
10146 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
10147 {child_name:?}.{:?} has no DEFAULT",
10148 col.name,
10149 ))
10150 })?;
10151 entry.insert((child_row_idx, li), default);
10152 }
10153 }
10154 }
10155 }
10156 }
10157 }
10158 }
10159 let mut steps: Vec<FkChildStep> = Vec::new();
10162 for (child_table, entries) in cascade_plan {
10163 let mut positions = Vec::with_capacity(entries.len());
10164 let mut columns = Vec::with_capacity(entries.len());
10165 let mut defaults = Vec::with_capacity(entries.len());
10166 for ((p, c), v) in entries {
10167 positions.push(p);
10168 columns.push(c);
10169 defaults.push(v);
10170 }
10171 steps.push(FkChildStep {
10176 child_table,
10177 action: FkChildAction::SetDefault {
10178 positions,
10179 columns,
10180 defaults,
10181 },
10182 });
10183 }
10184 for (child_table, entries) in setnull_plan {
10185 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
10186 steps.push(FkChildStep {
10187 child_table,
10188 action: FkChildAction::SetNull { positions, columns },
10189 });
10190 }
10191 for (child_table, entries) in setdefault_plan {
10192 let mut positions = Vec::with_capacity(entries.len());
10193 let mut columns = Vec::with_capacity(entries.len());
10194 let mut defaults = Vec::with_capacity(entries.len());
10195 for ((p, c), v) in entries {
10196 positions.push(p);
10197 columns.push(c);
10198 defaults.push(v);
10199 }
10200 steps.push(FkChildStep {
10201 child_table,
10202 action: FkChildAction::SetDefault {
10203 positions,
10204 columns,
10205 defaults,
10206 },
10207 });
10208 }
10209 let _ = delete_plan; Ok(steps)
10211}
10212
10213fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
10217 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
10218 EngineError::Storage(StorageError::TableNotFound {
10219 name: step.child_table.clone(),
10220 })
10221 })?;
10222 match &step.action {
10223 FkChildAction::Delete { positions } => {
10224 let _ = child.delete_rows(positions);
10225 }
10226 FkChildAction::SetNull { positions, columns } => {
10227 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
10228 }
10229 FkChildAction::SetDefault {
10230 positions,
10231 columns,
10232 defaults,
10233 } => {
10234 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
10235 }
10236 }
10237 Ok(())
10238}
10239
10240fn apply_per_cell_writes(
10246 child: &mut spg_storage::Table,
10247 positions: &[usize],
10248 columns: &[usize],
10249 mut value_for: impl FnMut(usize) -> Value,
10250) -> Result<(), EngineError> {
10251 use alloc::collections::BTreeMap;
10252 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
10253 for i in 0..positions.len() {
10254 by_row
10255 .entry(positions[i])
10256 .or_default()
10257 .push((columns[i], value_for(i)));
10258 }
10259 for (pos, mutations) in by_row {
10260 let mut new_values = child.rows()[pos].values.clone();
10261 for (col, v) in mutations {
10262 if let Some(slot) = new_values.get_mut(col) {
10263 *slot = v;
10264 }
10265 }
10266 child
10267 .update_row(pos, new_values)
10268 .map_err(EngineError::Storage)?;
10269 }
10270 Ok(())
10271}
10272
10273fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
10274 match a {
10275 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
10276 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
10277 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
10278 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
10279 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
10280 }
10281}
10282
10283fn resolve_column_default_free(
10289 col: &ColumnSchema,
10290 clock_fn: Option<ClockFn>,
10291) -> Result<Value, EngineError> {
10292 if let Some(rt) = &col.runtime_default {
10293 return eval_runtime_default_free(rt, col.ty, clock_fn);
10294 }
10295 Ok(col.default.clone().unwrap_or(Value::Null))
10296}
10297
10298fn eval_runtime_default_free(
10299 rt: &str,
10300 ty: DataType,
10301 clock_fn: Option<ClockFn>,
10302) -> Result<Value, EngineError> {
10303 let s = rt.trim().to_ascii_lowercase();
10304 let canonical = s.trim_end_matches("()");
10305 let now_us = match clock_fn {
10306 Some(f) => f(),
10307 None => 0,
10308 };
10309 let v = match canonical {
10310 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
10311 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
10312 "current_time" | "localtime" => Value::Timestamp(now_us),
10313 other => {
10314 return Err(EngineError::Unsupported(alloc::format!(
10315 "runtime DEFAULT expression {other:?} not supported \
10316 (v7.9.21 whitelist: now() / current_timestamp / \
10317 current_date / current_time / localtimestamp / \
10318 localtime)"
10319 )));
10320 }
10321 };
10322 coerce_value(v, ty, "DEFAULT", 0)
10323}
10324
10325fn is_runtime_default_expr(expr: &Expr) -> bool {
10331 match expr {
10332 Expr::FunctionCall { .. } => true,
10333 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
10334 _ => false,
10335 }
10336}
10337
10338fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
10339 let ty = column_type_to_data_type(c.ty);
10340 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
10341 if let Some(default_expr) = c.default {
10342 if is_runtime_default_expr(&default_expr) {
10348 let display = alloc::format!("{default_expr}");
10349 schema = schema.with_runtime_default(display);
10350 } else {
10351 let raw = literal_expr_to_value(default_expr)?;
10352 let coerced = coerce_value(raw, ty, &c.name, 0)?;
10353 schema = schema.with_default(coerced);
10354 }
10355 }
10356 if c.auto_increment {
10357 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
10359 return Err(EngineError::Unsupported(alloc::format!(
10360 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
10361 )));
10362 }
10363 schema = schema.with_auto_increment();
10364 }
10365 Ok(schema)
10366}
10367
10368fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
10373 let s = s.trim();
10374 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
10375 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
10377 if cleaned.len() % 2 != 0 {
10378 return Err("odd-length hex literal");
10379 }
10380 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
10381 let cleaned_bytes = cleaned.as_bytes();
10382 for i in (0..cleaned_bytes.len()).step_by(2) {
10383 let hi = hex_nibble(cleaned_bytes[i])?;
10384 let lo = hex_nibble(cleaned_bytes[i + 1])?;
10385 out.push((hi << 4) | lo);
10386 }
10387 return Ok(out);
10388 }
10389 let bytes = s.as_bytes();
10392 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
10393 let mut i = 0;
10394 while i < bytes.len() {
10395 let b = bytes[i];
10396 if b == b'\\' && i + 1 < bytes.len() {
10397 let n = bytes[i + 1];
10398 if n == b'\\' {
10399 out.push(b'\\');
10400 i += 2;
10401 continue;
10402 }
10403 if n.is_ascii_digit()
10404 && i + 3 < bytes.len()
10405 && bytes[i + 2].is_ascii_digit()
10406 && bytes[i + 3].is_ascii_digit()
10407 {
10408 let oct = |x: u8| (x - b'0') as u32;
10409 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
10410 if v <= 0xFF {
10411 out.push(v as u8);
10412 i += 4;
10413 continue;
10414 }
10415 }
10416 }
10417 out.push(b);
10418 i += 1;
10419 }
10420 Ok(out)
10421}
10422
10423fn hex_nibble(b: u8) -> Result<u8, &'static str> {
10424 match b {
10425 b'0'..=b'9' => Ok(b - b'0'),
10426 b'a'..=b'f' => Ok(b - b'a' + 10),
10427 b'A'..=b'F' => Ok(b - b'A' + 10),
10428 _ => Err("invalid hex digit"),
10429 }
10430}
10431
10432fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
10446 let mut has_text = false;
10447 let mut has_bigint = false;
10448 let mut has_int = false;
10449 for v in &items {
10450 match v {
10451 Value::Null => {}
10452 Value::Text(_) | Value::Json(_) => has_text = true,
10453 Value::BigInt(_) => has_bigint = true,
10454 Value::Int(_) | Value::SmallInt(_) => has_int = true,
10455 _ => has_text = true,
10456 }
10457 }
10458 if has_text || (!has_bigint && !has_int) {
10459 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
10460 .into_iter()
10461 .map(|v| match v {
10462 Value::Null => None,
10463 Value::Text(s) | Value::Json(s) => Some(s),
10464 other => Some(alloc::format!("{other:?}")),
10465 })
10466 .collect();
10467 return Value::TextArray(out);
10468 }
10469 if has_bigint {
10470 let out: alloc::vec::Vec<Option<i64>> = items
10471 .into_iter()
10472 .map(|v| match v {
10473 Value::Null => None,
10474 Value::Int(n) => Some(i64::from(n)),
10475 Value::SmallInt(n) => Some(i64::from(n)),
10476 Value::BigInt(n) => Some(n),
10477 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
10478 })
10479 .collect();
10480 return Value::BigIntArray(out);
10481 }
10482 let out: alloc::vec::Vec<Option<i32>> = items
10483 .into_iter()
10484 .map(|v| match v {
10485 Value::Null => None,
10486 Value::Int(n) => Some(n),
10487 Value::SmallInt(n) => Some(i32::from(n)),
10488 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
10489 })
10490 .collect();
10491 Value::IntArray(out)
10492}
10493
10494fn decode_text_array_literal(
10495 s: &str,
10496) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
10497 let trimmed = s.trim();
10498 let inner = trimmed
10499 .strip_prefix('{')
10500 .and_then(|x| x.strip_suffix('}'))
10501 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
10502 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
10503 if inner.trim().is_empty() {
10504 return Ok(out);
10505 }
10506 let bytes = inner.as_bytes();
10507 let mut i = 0;
10508 while i <= bytes.len() {
10509 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
10511 i += 1;
10512 }
10513 if i < bytes.len() && bytes[i] == b'"' {
10515 i += 1; let mut buf = alloc::string::String::new();
10517 while i < bytes.len() && bytes[i] != b'"' {
10518 if bytes[i] == b'\\' && i + 1 < bytes.len() {
10519 buf.push(bytes[i + 1] as char);
10520 i += 2;
10521 } else {
10522 buf.push(bytes[i] as char);
10523 i += 1;
10524 }
10525 }
10526 if i >= bytes.len() {
10527 return Err("unterminated quoted element");
10528 }
10529 i += 1; out.push(Some(buf));
10531 } else {
10532 let start = i;
10534 while i < bytes.len() && bytes[i] != b',' {
10535 i += 1;
10536 }
10537 let raw = inner[start..i].trim();
10538 if raw.eq_ignore_ascii_case("NULL") {
10539 out.push(None);
10540 } else {
10541 out.push(Some(alloc::string::ToString::to_string(raw)));
10542 }
10543 }
10544 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
10546 i += 1;
10547 }
10548 if i >= bytes.len() {
10549 break;
10550 }
10551 if bytes[i] != b',' {
10552 return Err("expected ',' between TEXT[] elements");
10553 }
10554 i += 1;
10555 }
10556 Ok(out)
10557}
10558
10559fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
10564 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
10565 out.push('{');
10566 for (i, item) in items.iter().enumerate() {
10567 if i > 0 {
10568 out.push(',');
10569 }
10570 match item {
10571 None => out.push_str("NULL"),
10572 Some(s) => {
10573 let needs_quote = s.is_empty()
10574 || s.eq_ignore_ascii_case("NULL")
10575 || s.chars()
10576 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
10577 if needs_quote {
10578 out.push('"');
10579 for c in s.chars() {
10580 if c == '"' || c == '\\' {
10581 out.push('\\');
10582 }
10583 out.push(c);
10584 }
10585 out.push('"');
10586 } else {
10587 out.push_str(s);
10588 }
10589 }
10590 }
10591 }
10592 out.push('}');
10593 out
10594}
10595
10596fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
10600 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
10601 out.push_str("\\x");
10602 for byte in b {
10603 let hi = byte >> 4;
10604 let lo = byte & 0x0F;
10605 out.push(hex_digit(hi));
10606 out.push(hex_digit(lo));
10607 }
10608 out
10609}
10610
10611const fn hex_digit(n: u8) -> char {
10612 match n {
10613 0..=9 => (b'0' + n) as char,
10614 10..=15 => (b'a' + n - 10) as char,
10615 _ => '?',
10616 }
10617}
10618
10619const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
10620 match t {
10621 ColumnTypeName::SmallInt => DataType::SmallInt,
10622 ColumnTypeName::Int => DataType::Int,
10623 ColumnTypeName::BigInt => DataType::BigInt,
10624 ColumnTypeName::Float => DataType::Float,
10625 ColumnTypeName::Text => DataType::Text,
10626 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
10627 ColumnTypeName::Char(n) => DataType::Char(n),
10628 ColumnTypeName::Bool => DataType::Bool,
10629 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
10630 dim,
10631 encoding: match encoding {
10632 SqlVecEncoding::F32 => VecEncoding::F32,
10633 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
10634 SqlVecEncoding::F16 => VecEncoding::F16,
10635 },
10636 },
10637 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
10638 ColumnTypeName::Date => DataType::Date,
10639 ColumnTypeName::Timestamp => DataType::Timestamp,
10640 ColumnTypeName::Timestamptz => DataType::Timestamptz,
10641 ColumnTypeName::Json => DataType::Json,
10642 ColumnTypeName::Jsonb => DataType::Jsonb,
10643 ColumnTypeName::Bytes => DataType::Bytes,
10644 ColumnTypeName::TextArray => DataType::TextArray,
10645 ColumnTypeName::IntArray => DataType::IntArray,
10646 ColumnTypeName::BigIntArray => DataType::BigIntArray,
10647 ColumnTypeName::TsVector => DataType::TsVector,
10648 ColumnTypeName::TsQuery => DataType::TsQuery,
10649 }
10650}
10651
10652fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
10656 match expr {
10657 Expr::Literal(l) => Ok(literal_to_value(l)),
10658 Expr::Cast { expr, target } => {
10659 let inner_value = literal_expr_to_value(*expr)?;
10660 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
10661 }
10662 Expr::Unary {
10663 op: UnOp::Neg,
10664 expr,
10665 } => match *expr {
10666 Expr::Literal(Literal::Integer(n)) => {
10667 let neg = n.checked_neg().ok_or_else(|| {
10670 EngineError::Unsupported("integer literal overflow on negation".into())
10671 })?;
10672 Ok(int_value_for(neg))
10673 }
10674 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
10675 other => Err(EngineError::Unsupported(alloc::format!(
10676 "unary minus over non-literal expression: {other:?}"
10677 ))),
10678 },
10679 Expr::Array(items) => {
10687 let mut materialised: alloc::vec::Vec<Value> =
10688 alloc::vec::Vec::with_capacity(items.len());
10689 for elem in items {
10690 materialised.push(literal_expr_to_value(elem)?);
10691 }
10692 Ok(array_literal_widen(materialised))
10693 }
10694 other => Err(EngineError::Unsupported(alloc::format!(
10695 "non-literal INSERT value expression: {other:?}"
10696 ))),
10697 }
10698}
10699
10700fn literal_to_value(l: Literal) -> Value {
10701 match l {
10702 Literal::Integer(n) => int_value_for(n),
10703 Literal::Float(x) => Value::Float(x),
10704 Literal::String(s) => Value::Text(s),
10705 Literal::Bool(b) => Value::Bool(b),
10706 Literal::Null => Value::Null,
10707 Literal::Vector(v) => Value::Vector(v),
10708 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
10709 }
10710}
10711
10712fn int_value_for(n: i64) -> Value {
10716 if let Ok(small) = i32::try_from(n) {
10717 Value::Int(small)
10718 } else {
10719 Value::BigInt(n)
10720 }
10721}
10722
10723#[allow(clippy::too_many_lines)]
10729fn coerce_value(
10730 v: Value,
10731 expected: DataType,
10732 col_name: &str,
10733 position: usize,
10734) -> Result<Value, EngineError> {
10735 if v.is_null() {
10736 return Ok(Value::Null);
10737 }
10738 let actual = v.data_type().expect("non-null");
10739 if actual == expected {
10740 return Ok(v);
10741 }
10742 let coerced = match (v, expected) {
10743 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
10744 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
10745 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
10746 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
10747 i128::from(n),
10748 precision,
10749 scale,
10750 col_name,
10751 )?),
10752 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
10753 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
10754 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
10755 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
10756 i128::from(n),
10757 precision,
10758 scale,
10759 col_name,
10760 )?),
10761 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
10762 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
10763 #[allow(clippy::cast_precision_loss)]
10764 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
10765 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
10766 i128::from(n),
10767 precision,
10768 scale,
10769 col_name,
10770 )?),
10771 (Value::Float(x), DataType::Numeric { precision, scale }) => {
10772 Some(numeric_from_float(x, precision, scale, col_name)?)
10773 }
10774 (Value::Text(s), DataType::Date) => {
10776 let d = eval::parse_date_literal(&s).ok_or_else(|| {
10777 EngineError::Eval(EvalError::TypeMismatch {
10778 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
10779 })
10780 })?;
10781 Some(Value::Date(d))
10782 }
10783 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
10787 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
10788 (Value::Text(s), DataType::Bytes) => {
10795 let bytes = decode_bytea_literal(&s).map_err(|e| {
10796 EngineError::Eval(EvalError::TypeMismatch {
10797 detail: alloc::format!(
10798 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
10799 ),
10800 })
10801 })?;
10802 Some(Value::Bytes(bytes))
10803 }
10804 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
10808 (Value::Text(s), DataType::TextArray) => {
10813 let arr = decode_text_array_literal(&s).map_err(|e| {
10814 EngineError::Eval(EvalError::TypeMismatch {
10815 detail: alloc::format!(
10816 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
10817 ),
10818 })
10819 })?;
10820 Some(Value::TextArray(arr))
10821 }
10822 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
10826 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
10827 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
10828 EngineError::Eval(EvalError::TypeMismatch {
10829 detail: alloc::format!(
10830 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
10831 ),
10832 })
10833 })?;
10834 Some(Value::Timestamp(t))
10835 }
10836 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
10839 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
10840 }
10841 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
10845 (Value::Timestamp(t), DataType::Date) => {
10846 let days = t.div_euclid(86_400_000_000);
10847 i32::try_from(days).ok().map(Value::Date)
10848 }
10849 (
10850 Value::Numeric {
10851 scaled,
10852 scale: src_scale,
10853 },
10854 DataType::Numeric { precision, scale },
10855 ) => Some(numeric_rescale(
10856 scaled, src_scale, precision, scale, col_name,
10857 )?),
10858 #[allow(clippy::cast_precision_loss)]
10859 (Value::Numeric { scaled, scale }, DataType::Float) => {
10860 let mut div = 1.0_f64;
10861 for _ in 0..scale {
10862 div *= 10.0;
10863 }
10864 Some(Value::Float((scaled as f64) / div))
10865 }
10866 (Value::Numeric { scaled, scale }, DataType::Int) => {
10867 let truncated = numeric_truncate_to_integer(scaled, scale);
10868 i32::try_from(truncated).ok().map(Value::Int)
10869 }
10870 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
10871 let truncated = numeric_truncate_to_integer(scaled, scale);
10872 i64::try_from(truncated).ok().map(Value::BigInt)
10873 }
10874 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
10875 let truncated = numeric_truncate_to_integer(scaled, scale);
10876 i16::try_from(truncated).ok().map(Value::SmallInt)
10877 }
10878 (Value::Text(s), DataType::Varchar(max)) => {
10880 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
10881 Some(Value::Text(s))
10882 } else {
10883 return Err(EngineError::Unsupported(alloc::format!(
10884 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
10885 {} chars",
10886 s.chars().count()
10887 )));
10888 }
10889 }
10890 (
10898 Value::Vector(v),
10899 DataType::Vector {
10900 dim,
10901 encoding: VecEncoding::Sq8,
10902 },
10903 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
10904 (
10909 Value::Vector(v),
10910 DataType::Vector {
10911 dim,
10912 encoding: VecEncoding::F16,
10913 },
10914 ) if v.len() == dim as usize => Some(Value::HalfVector(
10915 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
10916 )),
10917 (Value::Text(s), DataType::Char(size)) => {
10921 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
10922 if len > size {
10923 return Err(EngineError::Unsupported(alloc::format!(
10924 "value for CHAR({size}) column `{col_name}` exceeds length: \
10925 {len} chars"
10926 )));
10927 }
10928 let need = (size - len) as usize;
10929 let mut padded = s;
10930 padded.reserve(need);
10931 for _ in 0..need {
10932 padded.push(' ');
10933 }
10934 Some(Value::Text(padded))
10935 }
10936 _ => None,
10937 };
10938 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
10939 column: col_name.into(),
10940 expected,
10941 actual,
10942 position,
10943 }))
10944}
10945
10946fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
10952 use core::fmt::Write;
10953 let mut out = alloc::string::String::from("(");
10954 for (i, a) in args.iter().enumerate() {
10955 if i > 0 {
10956 out.push_str(", ");
10957 }
10958 match a.mode {
10959 spg_sql::ast::FunctionArgMode::In => {}
10960 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
10961 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
10962 }
10963 if let Some(n) = &a.name {
10964 out.push_str(n);
10965 out.push(' ');
10966 }
10967 match &a.ty {
10968 spg_sql::ast::FunctionArgType::Typed(t) => {
10969 let _ = write!(out, "{t}");
10970 }
10971 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
10972 }
10973 }
10974 out.push(')');
10975 out
10976}
10977
10978#[cfg(test)]
10979mod tests {
10980 use super::*;
10981 use alloc::vec;
10982
10983 fn unwrap_command_ok(r: &QueryResult) -> usize {
10984 match r {
10985 QueryResult::CommandOk { affected, .. } => *affected,
10986 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
10987 }
10988 }
10989
10990 #[test]
10991 fn create_table_registers_schema() {
10992 let mut e = Engine::new();
10993 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
10994 .unwrap();
10995 assert_eq!(e.catalog().table_count(), 1);
10996 let t = e.catalog().get("foo").unwrap();
10997 assert_eq!(t.schema().columns.len(), 2);
10998 assert_eq!(t.schema().columns[0].ty, DataType::Int);
10999 assert!(!t.schema().columns[0].nullable);
11000 assert_eq!(t.schema().columns[1].ty, DataType::Text);
11001 }
11002
11003 #[test]
11004 fn create_table_vector_default_is_f32_encoded() {
11005 let mut e = Engine::new();
11006 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
11007 let t = e.catalog().get("t").unwrap();
11008 assert_eq!(
11009 t.schema().columns[0].ty,
11010 DataType::Vector {
11011 dim: 8,
11012 encoding: VecEncoding::F32,
11013 },
11014 );
11015 }
11016
11017 #[test]
11018 fn create_table_vector_using_sq8_succeeds() {
11019 let mut e = Engine::new();
11023 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
11024 let t = e.catalog().get("t").unwrap();
11025 assert_eq!(
11026 t.schema().columns[0].ty,
11027 DataType::Vector {
11028 dim: 8,
11029 encoding: VecEncoding::Sq8,
11030 },
11031 );
11032 }
11033
11034 #[test]
11035 fn insert_into_sq8_column_quantises_f32_payload() {
11036 let mut e = Engine::new();
11043 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
11044 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
11045 .unwrap();
11046 let t = e.catalog().get("t").unwrap();
11047 assert_eq!(t.rows().len(), 1);
11048 match &t.rows()[0].values[0] {
11049 Value::Sq8Vector(q) => {
11050 assert_eq!(q.bytes.len(), 4);
11051 assert!((q.min - 0.0).abs() < 1e-6);
11053 assert!((q.max - 1.0).abs() < 1e-6);
11054 }
11055 other => panic!("expected Sq8Vector cell, got {other:?}"),
11056 }
11057 }
11058
11059 #[test]
11060 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
11061 let mut e = Engine::new();
11068 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
11069 .unwrap();
11070 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
11071 .unwrap();
11072 let t = e.catalog().get("t").unwrap();
11073 assert_eq!(t.rows().len(), 1);
11074 match &t.rows()[0].values[0] {
11075 Value::HalfVector(h) => {
11076 assert_eq!(h.dim(), 4);
11077 let back = h.to_f32_vec();
11078 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
11079 for (g, e) in back.iter().zip(expected.iter()) {
11080 assert!(
11081 (g - e).abs() < 1e-6,
11082 "{g} vs {e} should be exact on f16 grid"
11083 );
11084 }
11085 }
11086 other => panic!("expected HalfVector cell, got {other:?}"),
11087 }
11088 }
11089
11090 #[test]
11091 fn alter_index_rebuild_in_place_succeeds() {
11092 let mut e = Engine::new();
11097 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
11098 .unwrap();
11099 for i in 0..8_i32 {
11100 #[allow(clippy::cast_precision_loss)]
11101 let base = (i as f32) * 0.1;
11102 e.execute(&alloc::format!(
11103 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
11104 b1 = base + 0.01,
11105 b2 = base + 0.02,
11106 ))
11107 .unwrap();
11108 }
11109 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
11110 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
11111 assert_eq!(
11113 e.catalog().get("t").unwrap().schema().columns[1].ty,
11114 DataType::Vector {
11115 dim: 3,
11116 encoding: VecEncoding::F32,
11117 },
11118 );
11119 }
11120
11121 #[test]
11122 fn alter_index_rebuild_with_encoding_switches_cell_type() {
11123 let mut e = Engine::new();
11128 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
11129 .unwrap();
11130 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
11131 .unwrap();
11132 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
11133 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
11134 .unwrap();
11135 let t = e.catalog().get("t").unwrap();
11136 assert_eq!(
11137 t.schema().columns[1].ty,
11138 DataType::Vector {
11139 dim: 4,
11140 encoding: VecEncoding::Sq8,
11141 },
11142 );
11143 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
11144 }
11145
11146 #[test]
11147 fn alter_index_rebuild_unknown_index_errors() {
11148 let mut e = Engine::new();
11149 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
11150 assert!(
11151 matches!(
11152 &err,
11153 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
11154 ),
11155 "got: {err}"
11156 );
11157 }
11158
11159 #[test]
11160 fn alter_index_rebuild_on_btree_index_errors() {
11161 let mut e = Engine::new();
11164 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
11165 e.execute("INSERT INTO t VALUES (1)").unwrap();
11166 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
11167 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
11168 assert!(
11169 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
11170 "got: {err}"
11171 );
11172 }
11173
11174 #[test]
11175 fn prepared_insert_substitutes_placeholders() {
11176 let mut e = Engine::new();
11182 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
11183 .unwrap();
11184 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
11185 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
11186 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
11187 .unwrap();
11188 }
11189 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
11191 let QueryResult::Rows { rows, .. } = rows_result else {
11192 panic!("expected Rows")
11193 };
11194 assert_eq!(rows.len(), 3);
11195 }
11196
11197 #[test]
11198 fn prepared_select_with_placeholder_filters_rows() {
11199 let mut e = Engine::new();
11200 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
11201 .unwrap();
11202 for i in 0..10_i32 {
11203 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
11204 .unwrap();
11205 }
11206 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
11207 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
11208 else {
11209 panic!("expected Rows")
11210 };
11211 assert_eq!(rows.len(), 1);
11213 assert_eq!(rows[0].values[0], Value::Int(5));
11214 }
11215
11216 #[test]
11217 fn prepared_too_few_params_errors() {
11218 let mut e = Engine::new();
11219 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
11220 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
11221 let err = e.execute_prepared(stmt, &[]).unwrap_err();
11222 assert!(
11223 matches!(
11224 &err,
11225 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
11226 ),
11227 "got: {err}"
11228 );
11229 }
11230
11231 #[test]
11232 fn insert_into_half_column_dim_mismatch_errors() {
11233 let mut e = Engine::new();
11234 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
11235 .unwrap();
11236 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
11237 assert!(matches!(
11238 &err,
11239 EngineError::Storage(StorageError::TypeMismatch { .. })
11240 ));
11241 }
11242
11243 #[test]
11244 fn insert_into_sq8_column_dim_mismatch_errors() {
11245 let mut e = Engine::new();
11250 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
11251 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
11252 assert!(
11253 matches!(
11254 &err,
11255 EngineError::Storage(StorageError::TypeMismatch { .. })
11256 ),
11257 "got: {err}",
11258 );
11259 }
11260
11261 #[test]
11262 fn create_table_duplicate_errors() {
11263 let mut e = Engine::new();
11264 e.execute("CREATE TABLE foo (a INT)").unwrap();
11265 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
11266 assert!(matches!(
11267 err,
11268 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
11269 ));
11270 }
11271
11272 #[test]
11273 fn insert_into_unknown_table_errors() {
11274 let mut e = Engine::new();
11275 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
11276 assert!(matches!(
11277 err,
11278 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
11279 ));
11280 }
11281
11282 #[test]
11283 fn insert_happy_path_reports_one_affected() {
11284 let mut e = Engine::new();
11285 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
11286 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
11287 assert_eq!(unwrap_command_ok(&r), 1);
11288 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
11289 }
11290
11291 #[test]
11292 fn insert_arity_mismatch_propagates() {
11293 let mut e = Engine::new();
11294 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
11295 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
11296 assert!(matches!(
11297 err,
11298 EngineError::Storage(StorageError::ArityMismatch { .. })
11299 ));
11300 }
11301
11302 #[test]
11303 fn insert_negative_integer_via_unary_minus() {
11304 let mut e = Engine::new();
11305 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
11306 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
11307 let rows = e.catalog().get("foo").unwrap().rows();
11308 assert_eq!(rows[0].values[0], Value::Int(-7));
11309 }
11310
11311 #[test]
11312 fn insert_non_literal_expr_unsupported() {
11313 let mut e = Engine::new();
11314 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
11315 let err = e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap_err();
11316 assert!(matches!(err, EngineError::Unsupported(_)));
11317 }
11318
11319 #[test]
11320 fn select_star_returns_all_rows_in_insertion_order() {
11321 let mut e = Engine::new();
11322 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
11323 .unwrap();
11324 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
11325 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
11326 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
11327
11328 let r = e.execute("SELECT * FROM foo").unwrap();
11329 let QueryResult::Rows { columns, rows } = r else {
11330 panic!("expected Rows")
11331 };
11332 assert_eq!(columns.len(), 2);
11333 assert_eq!(columns[0].name, "a");
11334 assert_eq!(rows.len(), 3);
11335 assert_eq!(
11336 rows[1].values,
11337 vec![Value::Int(2), Value::Text("two".into())]
11338 );
11339 }
11340
11341 #[test]
11342 fn select_star_on_empty_table_returns_zero_rows() {
11343 let mut e = Engine::new();
11344 e.execute("CREATE TABLE foo (a INT)").unwrap();
11345 let r = e.execute("SELECT * FROM foo").unwrap();
11346 match r {
11347 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
11348 QueryResult::CommandOk { .. } => panic!("expected Rows"),
11349 }
11350 }
11351
11352 fn make_three_row_users(e: &mut Engine) {
11355 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
11356 .unwrap();
11357 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
11358 .unwrap();
11359 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
11360 .unwrap();
11361 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
11362 .unwrap();
11363 }
11364
11365 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
11366 match r {
11367 QueryResult::Rows { columns, rows } => (columns, rows),
11368 QueryResult::CommandOk { .. } => panic!("expected Rows"),
11369 }
11370 }
11371
11372 #[test]
11373 fn where_filter_passes_only_true_rows() {
11374 let mut e = Engine::new();
11375 make_three_row_users(&mut e);
11376 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
11377 let (_, rows) = unwrap_rows(r);
11378 assert_eq!(rows.len(), 2);
11379 assert_eq!(rows[0].values[0], Value::Int(2));
11380 assert_eq!(rows[1].values[0], Value::Int(3));
11381 }
11382
11383 #[test]
11384 fn where_with_null_result_filters_out_row() {
11385 let mut e = Engine::new();
11386 make_three_row_users(&mut e);
11387 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
11389 let (_, rows) = unwrap_rows(r);
11390 assert_eq!(rows.len(), 1);
11391 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
11392 }
11393
11394 #[test]
11395 fn projection_named_columns() {
11396 let mut e = Engine::new();
11397 make_three_row_users(&mut e);
11398 let r = e.execute("SELECT name, score FROM users").unwrap();
11399 let (cols, rows) = unwrap_rows(r);
11400 assert_eq!(cols.len(), 2);
11401 assert_eq!(cols[0].name, "name");
11402 assert_eq!(cols[1].name, "score");
11403 assert_eq!(rows.len(), 3);
11404 assert_eq!(
11405 rows[0].values,
11406 vec![Value::Text("alice".into()), Value::Int(90)]
11407 );
11408 }
11409
11410 #[test]
11411 fn projection_with_column_alias() {
11412 let mut e = Engine::new();
11413 make_three_row_users(&mut e);
11414 let r = e
11415 .execute("SELECT name AS who FROM users WHERE id = 1")
11416 .unwrap();
11417 let (cols, rows) = unwrap_rows(r);
11418 assert_eq!(cols[0].name, "who");
11419 assert_eq!(rows.len(), 1);
11420 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
11421 }
11422
11423 #[test]
11424 fn qualified_column_with_table_alias_resolves() {
11425 let mut e = Engine::new();
11426 make_three_row_users(&mut e);
11427 let r = e
11428 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
11429 .unwrap();
11430 let (cols, rows) = unwrap_rows(r);
11431 assert_eq!(cols.len(), 2);
11432 assert_eq!(rows.len(), 2);
11433 }
11434
11435 #[test]
11436 fn qualified_column_with_wrong_alias_errors() {
11437 let mut e = Engine::new();
11438 make_three_row_users(&mut e);
11439 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
11440 assert!(matches!(
11441 err,
11442 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
11443 ));
11444 }
11445
11446 #[test]
11447 fn select_unknown_column_errors_in_projection() {
11448 let mut e = Engine::new();
11449 make_three_row_users(&mut e);
11450 let err = e.execute("SELECT ghost FROM users").unwrap_err();
11451 assert!(matches!(
11452 err,
11453 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
11454 ));
11455 }
11456
11457 #[test]
11458 fn where_unknown_column_errors() {
11459 let mut e = Engine::new();
11460 make_three_row_users(&mut e);
11461 let err = e
11462 .execute("SELECT * FROM users WHERE ghost = 1")
11463 .unwrap_err();
11464 assert!(matches!(
11465 err,
11466 EngineError::Eval(EvalError::ColumnNotFound { .. })
11467 ));
11468 }
11469
11470 #[test]
11471 fn expression_projection_evaluates_and_renders() {
11472 let mut e = Engine::new();
11475 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
11476 e.execute("INSERT INTO t VALUES (3)").unwrap();
11477 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
11478 assert_eq!(rows.len(), 1);
11479 assert_eq!(rows[0].values[0], Value::Int(3));
11482 }
11483
11484 #[test]
11485 fn select_unknown_table_errors() {
11486 let mut e = Engine::new();
11487 let err = e.execute("SELECT * FROM ghost").unwrap_err();
11488 assert!(matches!(
11489 err,
11490 EngineError::Storage(StorageError::TableNotFound { .. })
11491 ));
11492 }
11493
11494 #[test]
11495 fn invalid_sql_returns_parse_error() {
11496 let mut e = Engine::new();
11499 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
11500 assert!(matches!(err, EngineError::Parse(_)));
11501 }
11502
11503 #[test]
11506 fn create_index_registers_on_table() {
11507 let mut e = Engine::new();
11508 make_three_row_users(&mut e);
11509 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
11510 let t = e.catalog().get("users").unwrap();
11511 assert_eq!(t.indices().len(), 1);
11512 assert_eq!(t.indices()[0].name, "by_name");
11513 }
11514
11515 #[test]
11516 fn create_index_on_unknown_table_errors() {
11517 let mut e = Engine::new();
11518 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
11519 assert!(matches!(
11520 err,
11521 EngineError::Storage(StorageError::TableNotFound { .. })
11522 ));
11523 }
11524
11525 #[test]
11526 fn create_index_on_unknown_column_errors() {
11527 let mut e = Engine::new();
11528 make_three_row_users(&mut e);
11529 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
11530 assert!(matches!(
11531 err,
11532 EngineError::Storage(StorageError::ColumnNotFound { .. })
11533 ));
11534 }
11535
11536 #[test]
11537 fn select_eq_uses_index_returns_same_rows_as_scan() {
11538 let mut without = Engine::new();
11542 make_three_row_users(&mut without);
11543 let mut with = Engine::new();
11544 make_three_row_users(&mut with);
11545 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
11546
11547 let q = "SELECT * FROM users WHERE id = 2";
11548 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
11549 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
11550 assert_eq!(no_idx_rows, idx_rows);
11551 assert_eq!(idx_rows.len(), 1);
11552 }
11553
11554 #[test]
11555 fn select_eq_with_no_matching_index_value_returns_empty() {
11556 let mut e = Engine::new();
11557 make_three_row_users(&mut e);
11558 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
11559 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
11560 assert_eq!(rows.len(), 0);
11561 }
11562
11563 #[test]
11566 fn begin_sets_in_transaction_flag() {
11567 let mut e = Engine::new();
11568 assert!(!e.in_transaction());
11569 e.execute("BEGIN").unwrap();
11570 assert!(e.in_transaction());
11571 }
11572
11573 #[test]
11574 fn double_begin_errors() {
11575 let mut e = Engine::new();
11576 e.execute("BEGIN").unwrap();
11577 let err = e.execute("BEGIN").unwrap_err();
11578 assert_eq!(err, EngineError::TransactionAlreadyOpen);
11579 }
11580
11581 #[test]
11582 fn commit_without_begin_errors() {
11583 let mut e = Engine::new();
11584 let err = e.execute("COMMIT").unwrap_err();
11585 assert_eq!(err, EngineError::NoActiveTransaction);
11586 }
11587
11588 #[test]
11589 fn rollback_without_begin_errors() {
11590 let mut e = Engine::new();
11591 let err = e.execute("ROLLBACK").unwrap_err();
11592 assert_eq!(err, EngineError::NoActiveTransaction);
11593 }
11594
11595 #[test]
11596 fn commit_applies_shadow_to_committed_catalog() {
11597 let mut e = Engine::new();
11598 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
11599 e.execute("BEGIN").unwrap();
11600 e.execute("INSERT INTO t VALUES (1)").unwrap();
11601 e.execute("INSERT INTO t VALUES (2)").unwrap();
11602 e.execute("COMMIT").unwrap();
11603 assert!(!e.in_transaction());
11604 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
11605 }
11606
11607 #[test]
11608 fn rollback_discards_shadow() {
11609 let mut e = Engine::new();
11610 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
11611 e.execute("BEGIN").unwrap();
11612 e.execute("INSERT INTO t VALUES (1)").unwrap();
11613 e.execute("INSERT INTO t VALUES (2)").unwrap();
11614 e.execute("ROLLBACK").unwrap();
11615 assert!(!e.in_transaction());
11616 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
11617 }
11618
11619 #[test]
11620 fn select_during_tx_sees_uncommitted_writes_own_session() {
11621 let mut e = Engine::new();
11624 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
11625 e.execute("BEGIN").unwrap();
11626 e.execute("INSERT INTO t VALUES (42)").unwrap();
11627 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
11628 assert_eq!(rows.len(), 1);
11629 assert_eq!(rows[0].values[0], Value::Int(42));
11630 }
11631
11632 #[test]
11633 fn snapshot_with_no_users_is_bare_catalog_format() {
11634 let mut e = Engine::new();
11635 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
11636 let bytes = e.snapshot();
11637 assert_eq!(
11638 &bytes[..8],
11639 b"SPGDB001",
11640 "must be the bare v3.x catalog magic"
11641 );
11642 let e2 = Engine::restore_envelope(&bytes).unwrap();
11643 assert!(e2.users().is_empty());
11644 assert_eq!(e2.catalog().table_count(), 1);
11645 }
11646
11647 #[test]
11648 fn snapshot_with_users_round_trips_both_via_envelope() {
11649 let mut e = Engine::new();
11650 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
11651 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
11652 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
11653 .unwrap();
11654 let bytes = e.snapshot();
11655 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
11656 let e2 = Engine::restore_envelope(&bytes).unwrap();
11657 assert_eq!(e2.users().len(), 2);
11658 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
11659 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
11660 assert_eq!(e2.verify_user("alice", "wrong"), None);
11661 assert_eq!(e2.catalog().table_count(), 1);
11662 }
11663
11664 #[test]
11665 fn ddl_inside_tx_also_rolled_back() {
11666 let mut e = Engine::new();
11667 e.execute("BEGIN").unwrap();
11668 e.execute("CREATE TABLE t (v INT)").unwrap();
11669 e.execute("SELECT * FROM t").unwrap();
11671 e.execute("ROLLBACK").unwrap();
11672 let err = e.execute("SELECT * FROM t").unwrap_err();
11674 assert!(matches!(
11675 err,
11676 EngineError::Storage(StorageError::TableNotFound { .. })
11677 ));
11678 }
11679
11680 #[test]
11683 fn create_publication_lands_in_catalog() {
11684 let mut e = Engine::new();
11685 assert!(e.publications().is_empty());
11686 e.execute("CREATE PUBLICATION pub_a").unwrap();
11687 assert_eq!(e.publications().len(), 1);
11688 assert!(e.publications().contains("pub_a"));
11689 }
11690
11691 #[test]
11692 fn create_publication_duplicate_errors() {
11693 let mut e = Engine::new();
11694 e.execute("CREATE PUBLICATION pub_a").unwrap();
11695 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
11696 assert!(
11697 alloc::format!("{err:?}").contains("DuplicateName"),
11698 "got {err:?}"
11699 );
11700 }
11701
11702 #[test]
11703 fn drop_publication_silent_when_absent() {
11704 let mut e = Engine::new();
11705 let r = e.execute("DROP PUBLICATION nope").unwrap();
11708 match r {
11709 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
11710 other => panic!("expected CommandOk, got {other:?}"),
11711 }
11712 }
11713
11714 #[test]
11715 fn drop_publication_present_reports_one_affected() {
11716 let mut e = Engine::new();
11717 e.execute("CREATE PUBLICATION pub_a").unwrap();
11718 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
11719 match r {
11720 QueryResult::CommandOk {
11721 affected,
11722 modified_catalog,
11723 } => {
11724 assert_eq!(affected, 1);
11725 assert!(modified_catalog);
11726 }
11727 other => panic!("expected CommandOk, got {other:?}"),
11728 }
11729 assert!(e.publications().is_empty());
11730 }
11731
11732 #[test]
11733 fn publications_persist_across_snapshot_restore() {
11734 let mut e = Engine::new();
11739 e.execute("CREATE PUBLICATION pub_a").unwrap();
11740 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
11741 .unwrap();
11742 let snap = e.snapshot();
11743 let e2 = Engine::restore_envelope(&snap).unwrap();
11744 assert_eq!(e2.publications().len(), 2);
11745 assert!(e2.publications().contains("pub_a"));
11746 assert!(e2.publications().contains("pub_b"));
11747 }
11748
11749 #[test]
11750 fn create_publication_allowed_inside_transaction() {
11751 let mut e = Engine::new();
11755 e.execute("BEGIN").unwrap();
11756 e.execute("CREATE PUBLICATION pub_a").unwrap();
11757 e.execute("COMMIT").unwrap();
11758 assert!(e.publications().contains("pub_a"));
11759 }
11760
11761 #[test]
11764 fn create_publication_for_table_list_lands_with_scope() {
11765 let mut e = Engine::new();
11766 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
11767 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
11768 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
11769 .unwrap();
11770 let scope = e.publications().get("pub_a").cloned();
11771 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
11772 panic!("expected ForTables scope, got {scope:?}")
11773 };
11774 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
11775 }
11776
11777 #[test]
11778 fn create_publication_all_tables_except_lands_with_scope() {
11779 let mut e = Engine::new();
11780 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
11781 .unwrap();
11782 let scope = e.publications().get("pub_a").cloned();
11783 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
11784 panic!("expected AllTablesExcept scope, got {scope:?}")
11785 };
11786 assert_eq!(ts, alloc::vec!["t3".to_string()]);
11787 }
11788
11789 #[test]
11790 fn show_publications_empty_returns_zero_rows() {
11791 let e = Engine::new();
11792 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
11793 let QueryResult::Rows { rows, columns } = r else {
11794 panic!()
11795 };
11796 assert!(rows.is_empty());
11797 assert_eq!(columns.len(), 3);
11798 assert_eq!(columns[0].name, "name");
11799 assert_eq!(columns[1].name, "scope");
11800 assert_eq!(columns[2].name, "table_count");
11801 }
11802
11803 #[test]
11804 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
11805 let mut e = Engine::new();
11806 e.execute("CREATE PUBLICATION z_pub").unwrap();
11807 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
11808 .unwrap();
11809 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
11810 .unwrap();
11811 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
11812 let QueryResult::Rows { rows, .. } = r else {
11813 panic!()
11814 };
11815 assert_eq!(rows.len(), 3);
11816 let names: Vec<&str> = rows
11818 .iter()
11819 .map(|r| {
11820 if let Value::Text(s) = &r.values[0] {
11821 s.as_str()
11822 } else {
11823 panic!()
11824 }
11825 })
11826 .collect();
11827 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
11828 match &rows[0].values[1] {
11830 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
11831 other => panic!("expected Text, got {other:?}"),
11832 }
11833 assert_eq!(rows[0].values[2], Value::Int(2));
11834 match &rows[1].values[1] {
11836 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
11837 other => panic!("expected Text, got {other:?}"),
11838 }
11839 assert_eq!(rows[1].values[2], Value::Int(1));
11840 match &rows[2].values[1] {
11842 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
11843 other => panic!("expected Text, got {other:?}"),
11844 }
11845 assert_eq!(rows[2].values[2], Value::Null);
11846 }
11847
11848 #[test]
11849 fn for_list_scopes_persist_across_snapshot() {
11850 let mut e = Engine::new();
11853 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
11854 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
11855 .unwrap();
11856 let snap = e.snapshot();
11857 let e2 = Engine::restore_envelope(&snap).unwrap();
11858 assert_eq!(e2.publications().len(), 2);
11859 let p1 = e2.publications().get("p1").cloned();
11860 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
11861 panic!("p1 scope lost: {p1:?}")
11862 };
11863 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
11864 let p2 = e2.publications().get("p2").cloned();
11865 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
11866 panic!("p2 scope lost: {p2:?}")
11867 };
11868 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
11869 }
11870
11871 #[test]
11874 fn create_subscription_lands_in_catalog_with_defaults() {
11875 let mut e = Engine::new();
11876 e.execute(
11877 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
11878 )
11879 .unwrap();
11880 let s = e.subscriptions().get("sub_a").cloned().expect("present");
11881 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
11882 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
11883 assert!(s.enabled);
11884 assert_eq!(s.last_received_pos, 0);
11885 }
11886
11887 #[test]
11888 fn create_subscription_duplicate_name_errors() {
11889 let mut e = Engine::new();
11890 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
11891 .unwrap();
11892 let err = e
11893 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
11894 .unwrap_err();
11895 assert!(
11896 alloc::format!("{err:?}").contains("DuplicateName"),
11897 "got {err:?}"
11898 );
11899 }
11900
11901 #[test]
11902 fn drop_subscription_silent_when_absent() {
11903 let mut e = Engine::new();
11904 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
11905 match r {
11906 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
11907 other => panic!("expected CommandOk, got {other:?}"),
11908 }
11909 }
11910
11911 #[test]
11912 fn subscription_advance_updates_last_pos_monotone() {
11913 let mut e = Engine::new();
11914 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
11915 .unwrap();
11916 assert!(e.subscription_advance("s", 100));
11917 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
11918 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
11920 assert!(e.subscription_advance("s", 200));
11921 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
11922 assert!(!e.subscription_advance("missing", 1));
11923 }
11924
11925 #[test]
11926 fn show_subscriptions_returns_rows_ordered_by_name() {
11927 let mut e = Engine::new();
11928 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
11929 .unwrap();
11930 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
11931 .unwrap();
11932 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
11933 let QueryResult::Rows { rows, columns } = r else {
11934 panic!()
11935 };
11936 assert_eq!(rows.len(), 2);
11937 assert_eq!(columns.len(), 5);
11938 assert_eq!(columns[0].name, "name");
11939 assert_eq!(columns[4].name, "last_received_pos");
11940 let names: Vec<&str> = rows
11942 .iter()
11943 .map(|r| {
11944 if let Value::Text(s) = &r.values[0] {
11945 s.as_str()
11946 } else {
11947 panic!()
11948 }
11949 })
11950 .collect();
11951 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
11952 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
11954 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
11955 assert_eq!(rows[0].values[3], Value::Bool(true));
11956 assert_eq!(rows[0].values[4], Value::BigInt(0));
11957 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
11959 }
11960
11961 #[test]
11962 fn subscriptions_persist_across_snapshot_envelope_v4() {
11963 let mut e = Engine::new();
11964 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
11965 .unwrap();
11966 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
11967 .unwrap();
11968 e.subscription_advance("s2", 42);
11969 let snap = e.snapshot();
11970 let e2 = Engine::restore_envelope(&snap).unwrap();
11971 assert_eq!(e2.subscriptions().len(), 2);
11972 let s1 = e2.subscriptions().get("s1").unwrap();
11973 assert_eq!(s1.conn_str, "h=A");
11974 assert_eq!(
11975 s1.publications,
11976 alloc::vec!["p1".to_string(), "p2".to_string()]
11977 );
11978 assert_eq!(s1.last_received_pos, 0);
11979 let s2 = e2.subscriptions().get("s2").unwrap();
11980 assert_eq!(s2.last_received_pos, 42);
11981 }
11982
11983 #[test]
11984 fn v3_envelope_loads_with_empty_subscriptions() {
11985 let mut e = Engine::new();
11989 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
11990 let catalog = e.catalog.serialize();
11991 let users = crate::users::serialize_users(&e.users);
11992 let pubs = e.publications.serialize();
11993 let mut buf = Vec::new();
11994 buf.extend_from_slice(b"SPGENV01");
11995 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
11997 buf.extend_from_slice(&catalog);
11998 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
11999 buf.extend_from_slice(&users);
12000 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
12001 buf.extend_from_slice(&pubs);
12002 let crc = spg_crypto::crc32::crc32(&buf);
12003 buf.extend_from_slice(&crc.to_le_bytes());
12004
12005 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
12006 assert!(e2.subscriptions().is_empty());
12007 assert!(e2.publications().contains("pub_legacy"));
12008 }
12009
12010 #[test]
12011 fn create_subscription_allowed_inside_transaction() {
12012 let mut e = Engine::new();
12013 e.execute("BEGIN").unwrap();
12014 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
12015 .unwrap();
12016 e.execute("COMMIT").unwrap();
12017 assert!(e.subscriptions().contains("s"));
12018 }
12019
12020 #[test]
12022 fn analyze_populates_histogram_bounds() {
12023 let mut e = Engine::new();
12024 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
12025 .unwrap();
12026 for i in 0..50 {
12027 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
12028 .unwrap();
12029 }
12030 e.execute("ANALYZE t").unwrap();
12031 let stats = e.statistics();
12032 let id_stats = stats.get("t", "id").unwrap();
12033 assert!(id_stats.histogram_bounds.len() >= 2);
12034 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
12035 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
12036 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
12037 assert_eq!(id_stats.n_distinct, 50);
12038 }
12039
12040 #[test]
12041 fn reanalyze_overwrites_prior_stats() {
12042 let mut e = Engine::new();
12043 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
12044 for i in 0..10 {
12045 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12046 .unwrap();
12047 }
12048 e.execute("ANALYZE t").unwrap();
12049 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
12050 assert_eq!(n1, 10);
12051 for i in 10..30 {
12052 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12053 .unwrap();
12054 }
12055 e.execute("ANALYZE t").unwrap();
12056 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
12057 assert_eq!(n2, 30);
12058 }
12059
12060 #[test]
12061 fn analyze_unknown_table_errors() {
12062 let mut e = Engine::new();
12063 let err = e.execute("ANALYZE nonexistent").unwrap_err();
12064 assert!(matches!(
12065 err,
12066 EngineError::Storage(StorageError::TableNotFound { .. })
12067 ));
12068 }
12069
12070 #[test]
12071 fn bare_analyze_covers_all_user_tables() {
12072 let mut e = Engine::new();
12073 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
12074 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
12075 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
12076 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
12077 let r = e.execute("ANALYZE").unwrap();
12078 match r {
12079 QueryResult::CommandOk {
12080 affected,
12081 modified_catalog,
12082 } => {
12083 assert_eq!(affected, 2);
12084 assert!(modified_catalog);
12085 }
12086 other => panic!("expected CommandOk, got {other:?}"),
12087 }
12088 assert!(e.statistics().get("t1", "id").is_some());
12089 assert!(e.statistics().get("t2", "name").is_some());
12090 }
12091
12092 #[test]
12093 fn select_from_spg_statistic_returns_rows_per_column() {
12094 let mut e = Engine::new();
12095 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
12096 .unwrap();
12097 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
12098 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
12099 e.execute("ANALYZE t").unwrap();
12100 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
12101 let QueryResult::Rows { rows, columns } = r else {
12102 panic!()
12103 };
12104 assert_eq!(columns.len(), 6);
12106 assert_eq!(columns[0].name, "table_name");
12107 assert_eq!(columns[4].name, "histogram_bounds");
12108 assert_eq!(columns[5].name, "cold_row_count");
12109 assert_eq!(rows.len(), 2, "one row per column of t");
12110 match (&rows[0].values[0], &rows[0].values[1]) {
12112 (Value::Text(t), Value::Text(c)) => {
12113 assert_eq!(t, "t");
12114 assert_eq!(c, "id");
12116 }
12117 _ => panic!(),
12118 }
12119 }
12120
12121 #[test]
12122 fn analyze_skips_vector_columns() {
12123 let mut e = Engine::new();
12126 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
12127 .unwrap();
12128 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
12129 e.execute("ANALYZE t").unwrap();
12130 assert!(e.statistics().get("t", "id").is_some());
12131 assert!(e.statistics().get("t", "v").is_none());
12132 }
12133
12134 #[test]
12135 fn statistics_persist_across_envelope_v5_round_trip() {
12136 let mut e = Engine::new();
12137 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
12138 for i in 0..20 {
12139 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12140 .unwrap();
12141 }
12142 e.execute("ANALYZE").unwrap();
12143 let snap = e.snapshot();
12144 let e2 = Engine::restore_envelope(&snap).unwrap();
12145 let s = e2.statistics().get("t", "id").unwrap();
12146 assert_eq!(s.n_distinct, 20);
12147 }
12148
12149 #[test]
12152 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
12153 let mut e = Engine::new();
12157 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
12158 for i in 0..9 {
12159 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12160 .unwrap();
12161 }
12162 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
12163 e.execute("INSERT INTO t VALUES (9)").unwrap();
12164 let needs = e.tables_needing_analyze();
12165 assert_eq!(needs, alloc::vec!["t".to_string()]);
12166 }
12167
12168 #[test]
12169 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
12170 let mut e = Engine::new();
12176 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
12177 for i in 0..1000 {
12178 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12179 .unwrap();
12180 }
12181 e.execute("ANALYZE t").unwrap();
12182 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
12183 for i in 1000..1050 {
12184 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12185 .unwrap();
12186 }
12187 assert!(
12188 e.tables_needing_analyze().is_empty(),
12189 "50 inserts < threshold of ~105"
12190 );
12191 for i in 1050..1200 {
12192 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12193 .unwrap();
12194 }
12195 assert_eq!(
12196 e.tables_needing_analyze(),
12197 alloc::vec!["t".to_string()],
12198 "200 inserts > 0.1 × 1200 threshold"
12199 );
12200 }
12201
12202 #[test]
12203 fn auto_analyze_threshold_resets_after_analyze() {
12204 let mut e = Engine::new();
12205 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
12206 for i in 0..200 {
12207 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
12208 .unwrap();
12209 }
12210 assert!(!e.tables_needing_analyze().is_empty());
12211 e.execute("ANALYZE").unwrap();
12212 assert!(
12213 e.tables_needing_analyze().is_empty(),
12214 "ANALYZE must reset the counter"
12215 );
12216 }
12217
12218 #[test]
12219 fn auto_analyze_threshold_tracks_updates_and_deletes() {
12220 let mut e = Engine::new();
12221 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
12222 .unwrap();
12223 for i in 0..50 {
12224 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
12225 .unwrap();
12226 }
12227 e.execute("ANALYZE t").unwrap();
12228 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
12231 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
12232 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
12233 }
12234
12235 #[test]
12236 fn v4_envelope_loads_with_empty_statistics() {
12237 let mut e = Engine::new();
12241 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
12242 .unwrap();
12243 let catalog = e.catalog.serialize();
12244 let users = crate::users::serialize_users(&e.users);
12245 let pubs = e.publications.serialize();
12246 let subs = e.subscriptions.serialize();
12247 let mut buf = Vec::new();
12248 buf.extend_from_slice(b"SPGENV01");
12249 buf.push(4u8);
12250 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
12251 buf.extend_from_slice(&catalog);
12252 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
12253 buf.extend_from_slice(&users);
12254 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
12255 buf.extend_from_slice(&pubs);
12256 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
12257 buf.extend_from_slice(&subs);
12258 let crc = spg_crypto::crc32::crc32(&buf);
12259 buf.extend_from_slice(&crc.to_le_bytes());
12260 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
12261 assert!(e2.statistics().is_empty());
12262 }
12263
12264 #[test]
12265 fn v1_v2_envelope_loads_with_empty_publications() {
12266 let mut e = Engine::new();
12273 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
12276 .unwrap();
12277
12278 let catalog = e.catalog.serialize();
12280 let users = crate::users::serialize_users(&e.users);
12281 let mut buf = Vec::new();
12282 buf.extend_from_slice(b"SPGENV01");
12283 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
12285 buf.extend_from_slice(&catalog);
12286 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
12287 buf.extend_from_slice(&users);
12288 let crc = spg_crypto::crc32::crc32(&buf);
12289 buf.extend_from_slice(&crc.to_le_bytes());
12290
12291 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
12292 assert!(e2.publications().is_empty());
12293 }
12294}