From 89513b19e89df2e73997bd3fc52faa2e9245e76a Mon Sep 17 00:00:00 2001 From: tamnd Date: Thu, 8 Oct 2026 11:13:14 +0700 Subject: [PATCH] Add subqueries in expressions --- CHANGELOG.md | 4 + crates/rupg-analyze/src/agg.rs | 99 +++++- crates/rupg-analyze/src/expr.rs | 95 +++++- crates/rupg-analyze/src/from.rs | 143 +++++--- crates/rupg-analyze/src/lib.rs | 85 ++++- crates/rupg-analyze/src/select.rs | 47 ++- crates/rupg-analyze/src/sublink.rs | 121 +++++++ crates/rupg-analyze/src/transform.rs | 2 +- crates/rupg-exec/src/agg.rs | 2 +- crates/rupg-exec/src/lib.rs | 465 +++++++++++++++++++-------- crates/rupg-exec/src/scan.rs | 18 +- crates/rupg-exec/src/tests.rs | 184 +++++++++++ 12 files changed, 1052 insertions(+), 213 deletions(-) create mode 100644 crates/rupg-analyze/src/sublink.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 834379e..c434dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All changes that a user can see are in this file. The version is 0.M.patch, wher ## Unreleased +### Added + +- Subqueries in expressions (spec/23 section 23.5): `EXISTS`, `IN`, `NOT IN`, `op ANY`, `op ALL`, `ARRAY(SELECT ...)` and a scalar subquery, in the target list, `WHERE`, `JOIN ... ON`, `HAVING`, `GROUP BY`, `ORDER BY`, `LIMIT` and `OFFSET`. A subquery can read the columns of the queries outside it at any depth. The analyzer finds a name as `parse_relation.c` does, from the subquery up to the outer queries, so a bad name gives the same error and hint as PostgreSQL, also for a close name of an outer relation. It gives the errors of `transformSubLink` for a wrong number of columns, and `subquery uses ungrouped column` for a column of a grouped outer query. The executor runs a subquery that reads no outer column once, as an `InitPlan` does, and runs the other subqueries again for each row. It reads the tables of a subquery once. `EXISTS` skips the target list when `simplify_EXISTS_query` can drop it, so `EXISTS (SELECT 1/0 FROM t)` gives no error, and a scalar subquery with more than one row gives `21000`. A row on the left of `IN` or `ANY`, `ARRAY()` of an array, an aggregate of an outer query, and a subquery in `FROM` give `0A000`. + ## 0.0.17 (2026-10-08) The seventeenth patch release on the 0.0 line. `SELECT` now has aggregate functions, `GROUP BY` and `HAVING`, and two new fuzz targets check the input, output, receive and send functions of 52 types against PostgreSQL. No crate is published. diff --git a/crates/rupg-analyze/src/agg.rs b/crates/rupg-analyze/src/agg.rs index d5692c6..c031e77 100644 --- a/crates/rupg-analyze/src/agg.rs +++ b/crates/rupg-analyze/src/agg.rs @@ -128,6 +128,31 @@ impl Analyzer<'_> { inner.location, )); } + // check_agg_arguments: an aggregate whose arguments read only the columns of outer queries belongs to the outer query. + let level = args + .iter() + .chain(filter.as_deref()) + .filter_map(|e| { + let mut min: Option = None; + e.find(0, &mut |e, depth| { + if let ExprKind::Var(var) = e.kind + && var.levels_up >= depth + { + let up = var.levels_up - depth; + min = Some(min.map_or(up, |m| m.min(up))); + } + None::<()> + }); + min + }) + .min(); + if level.is_some_and(|l| l > 0) { + return Err(Error::new( + SqlState::FEATURE_NOT_SUPPORTED, + "an aggregate of an outer query is not supported yet", + ) + .at_opt(at)); + } match self.kind { Kind::JoinOn => { return Err(grouping_error( @@ -160,17 +185,23 @@ impl Analyzer<'_> { let vars: Vec = grouped .iter() .filter_map(|e| match e.kind { - ExprKind::Var(var) => Some(var), + ExprKind::Var(var) if var.levels_up == 0 => Some(var), _ => None, }) .collect(); - let mut check = - Check { analyzer: self, grouped: &grouped, non_var, vars: &vars, proven: Vec::new() }; + let mut check = Check { + analyzer: self, + grouped: &grouped, + raised: Vec::new(), + non_var, + vars: &vars, + proven: Vec::new(), + }; for target in targets { - check.expr(&target.expr)?; + check.expr(&target.expr, 0)?; } if let Some(having) = having { - check.expr(having)?; + check.expr(having, 0)?; } Ok(()) } @@ -181,6 +212,8 @@ struct Check<'a, 'b> { analyzer: &'a Analyzer<'b>, /// The expressions of `GROUP BY`. grouped: &'a [&'a Expr], + /// `groupClauseSubLevels`: the expressions of `GROUP BY` as a subquery sees them, with the index 0 for a subquery of the query. + raised: Vec>, /// `have_non_var_grouping`: true when an item of `GROUP BY` is not a column. non_var: bool, /// The items of `GROUP BY` that are columns. @@ -190,26 +223,58 @@ struct Check<'a, 'b> { } impl Check<'_, '_> { - fn expr(&mut self, expr: &Expr) -> Result<()> { - if let ExprKind::Agg(_) = expr.kind { + /// Checks an expression of the query, or of a subquery `depth` levels down. + fn expr(&mut self, expr: &Expr, depth: usize) -> Result<()> { + if let ExprKind::Agg(_) = expr.kind + && depth == 0 + { return Ok(()); } - if self.non_var && self.grouped.iter().any(|g| g.same(expr)) { + if self.non_var && self.is_grouped(expr, depth) { return Ok(()); } match &expr.kind { ExprKind::Const(_) | ExprKind::Param(_) => Ok(()), - ExprKind::Var(var) => self.var(*var, expr.location), + ExprKind::Var(var) if var.levels_up == depth => { + self.var(Var { levels_up: 0, ..*var }, depth, expr.location) + } + ExprKind::Var(_) => Ok(()), _ => { for child in expr.children() { - self.expr(child)?; + self.expr(child, depth)?; + } + if let ExprKind::SubLink(sub) = &expr.kind { + for e in sub.query.exprs() { + self.expr(e, depth + 1)?; + } } Ok(()) } } } - fn var(&mut self, var: Var, at: Option) -> Result<()> { + /// True when the expression is an item of `GROUP BY`, as a subquery `depth` levels down sees it. + fn is_grouped(&mut self, expr: &Expr, depth: usize) -> bool { + if depth == 0 { + return self.grouped.iter().any(|g| g.same(expr)); + } + while self.raised.len() < depth { + let levels = self.raised.len() + 1; + let raised = self + .grouped + .iter() + .map(|g| { + let mut g = (*g).clone(); + g.raise(levels); + g + }) + .collect(); + self.raised.push(raised); + } + self.raised[depth - 1].iter().any(|g| g.same(expr)) + } + + fn var(&mut self, var: Var, depth: usize, at: Option) -> Result<()> { if !self.non_var && self.vars.contains(&var) { return Ok(()); } @@ -219,17 +284,19 @@ impl Check<'_, '_> { // check_functional_grouping: all the columns of the primary key are in GROUP BY. let relid = self.analyzer.scope.relations[var.relation].oid; if let Some(key) = builtin::primary_key(relid) - && key.iter().all(|&attnum| self.vars.contains(&Var { relation: var.relation, attnum })) + && key.iter().all(|&attnum| self.vars.contains(&Var { attnum, ..var })) { self.proven.push(var.relation); return Ok(()); } let (table, column) = self.analyzer.column_names(var); - Err(grouping_error( + let message = if depth == 0 { format!( "column \"{table}.{column}\" must appear in the GROUP BY clause or be used in an aggregate function" - ), - at, - )) + ) + } else { + format!("subquery uses ungrouped column \"{table}.{column}\" from outer query") + }; + Err(grouping_error(message, at)) } } diff --git a/crates/rupg-analyze/src/expr.rs b/crates/rupg-analyze/src/expr.rs index 0d3c48d..05e54af 100644 --- a/crates/rupg-analyze/src/expr.rs +++ b/crates/rupg-analyze/src/expr.rs @@ -2,6 +2,7 @@ use rupg_types::Value; +use crate::select::Query; use crate::sort::SortGroup; /// An expression with its type and its typmod. A typmod of -1 means that the expression has no typmod. @@ -56,6 +57,34 @@ pub enum ExprKind { Var(Var), /// `Aggref`: a call of an aggregate function, which reads all the rows of a group. Agg(Box), + /// `SubLink`: a subquery in an expression. + SubLink(Box), + /// `Param` of the kind `PARAM_SUBLINK`: the value of the column with this index, from 0, of a row of the subquery, in the test of `ANY` or `ALL`. + SubColumn(usize), +} + +/// A subquery in an expression, as `SubLink`. +#[derive(Clone, Debug, PartialEq)] +pub struct SubLink { + pub kind: SubLinkKind, + /// `testexpr`: for `ANY` and `ALL`, the test of each row of the subquery. It compares the left side with [`ExprKind::SubColumn`]. + pub test: Option, + pub query: Query, +} + +/// The kinds of `SubLink` that the analyzer takes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SubLinkKind { + /// `EXISTS (SELECT ...)`. + Exists, + /// `x op ALL (SELECT ...)`. + All, + /// `x op ANY (SELECT ...)` and `x IN (SELECT ...)`. + Any, + /// `(SELECT ...)`, which gives the value of the one column of the one row. + Expr, + /// `ARRAY(SELECT ...)`, which gives the values of the one column as an array. + Array, } /// A column of a relation of `FROM`. @@ -65,6 +94,8 @@ pub struct Var { pub relation: usize, /// The attribute number: from 1 for a column of the relation, or a negative number for a system column. pub attnum: i16, + /// `varlevelsup`: 0 for a relation of the query that has the expression, 1 for a relation of the query outside it, and so on. + pub levels_up: usize, } /// A call of a function. @@ -193,7 +224,9 @@ impl Expr { | ExprKind::Param(_) | ExprKind::CaseTest | ExprKind::SqlValue(_) - | ExprKind::Var(_) => Vec::new(), + | ExprKind::Var(_) + | ExprKind::SubColumn(_) => Vec::new(), + ExprKind::SubLink(sub) => sub.test.iter().collect(), ExprKind::Func(f) => f.args.iter().collect(), ExprKind::Agg(agg) => agg.args.iter().chain(agg.filter.as_deref()).collect(), ExprKind::Relabel(arg) @@ -225,7 +258,9 @@ impl Expr { | ExprKind::Param(_) | ExprKind::CaseTest | ExprKind::SqlValue(_) - | ExprKind::Var(_) => Vec::new(), + | ExprKind::Var(_) + | ExprKind::SubColumn(_) => Vec::new(), + ExprKind::SubLink(sub) => sub.test.iter_mut().collect(), ExprKind::Func(f) => f.args.iter_mut().collect(), ExprKind::Agg(agg) => { let Aggref { args, filter, .. } = &mut **agg; @@ -261,6 +296,9 @@ impl Expr { for child in expr.children_mut() { strip(child); } + if let ExprKind::SubLink(sub) = &mut expr.kind { + sub.query.exprs_mut().into_iter().for_each(strip); + } } let (mut a, mut b) = (self.clone(), other.clone()); strip(&mut a); @@ -268,12 +306,56 @@ impl Expr { a == b } - /// The first column that the expression reads, as `locate_var_of_level` finds it. + /// Calls `f` for the expression and for each expression in it, also in its subqueries, with the number of subqueries between the expression and this expression. The search stops at the first `Some` that `f` gives. + pub fn find<'a, T>( + &'a self, + depth: usize, + f: &mut impl FnMut(&'a Expr, usize) -> Option, + ) -> Option { + if let Some(found) = f(self, depth) { + return Some(found); + } + for child in self.children() { + if let Some(found) = child.find(depth, f) { + return Some(found); + } + } + if let ExprKind::SubLink(sub) = &self.kind { + for expr in sub.query.exprs() { + if let Some(found) = expr.find(depth + 1, f) { + return Some(found); + } + } + } + None + } + + /// The first column of the query of the expression that the expression reads, also in a subquery, as `locate_var_of_level` finds it with level 0. pub fn first_var(&self) -> Option<&Expr> { - if matches!(self.kind, ExprKind::Var(_)) { - return Some(self); + self.find(0, &mut |e, depth| match e.kind { + ExprKind::Var(var) if var.levels_up == depth => Some(e), + _ => None, + }) + } + + /// `IncrementVarSublevelsUp`: the columns of the outer queries in the expression, also in its subqueries, are `levels` more levels up. A column of a query in the expression does not change. + pub fn raise(&mut self, levels: usize) { + fn raise_at(expr: &mut Expr, levels: usize, depth: usize) { + if let ExprKind::Var(var) = &mut expr.kind + && var.levels_up >= depth + { + var.levels_up += levels; + } + for child in expr.children_mut() { + raise_at(child, levels, depth); + } + if let ExprKind::SubLink(sub) = &mut expr.kind { + for e in sub.query.exprs_mut() { + raise_at(e, levels, depth + 1); + } + } } - self.children().into_iter().find_map(Expr::first_var) + raise_at(self, levels, 0); } /// `contain_aggs_of_level` and `locate_agg_of_level`: the first aggregate call in the expression. @@ -309,6 +391,7 @@ impl Expr { | ExprKind::CoerceViaIo(arg) | ExprKind::NullTest(arg, _) | ExprKind::BooleanTest(arg, _) => Some(&**arg), + ExprKind::SubLink(sub) => sub.test.as_ref(), _ => None, }; match (self.location, first.and_then(Expr::place)) { diff --git a/crates/rupg-analyze/src/from.rs b/crates/rupg-analyze/src/from.rs index 4926304..974f08a 100644 --- a/crates/rupg-analyze/src/from.rs +++ b/crates/rupg-analyze/src/from.rs @@ -121,14 +121,17 @@ fn levenshtein(a: &str, b: &str) -> usize { /// `FuzzyAttrMatchState`: the best matches of a column name that does not exist. struct Fuzzy { distance: usize, - first: Option<(usize, usize)>, - second: Option<(usize, usize)>, - exact: Vec, + first: Option<(Place, usize)>, + second: Option<(Place, usize)>, + exact: Vec, } +/// The place of an entry of the range table: the query level, 0 for the query of the name, and the index of the entry at that level. +type Place = (usize, usize); + impl Fuzzy { /// `updateFuzzyAttrMatchState`. - fn update(&mut self, penalty: usize, entry: usize, actual: &str, wanted: &str, column: usize) { + fn update(&mut self, penalty: usize, entry: Place, actual: &str, wanted: &str, column: usize) { if penalty > self.distance || actual.is_empty() { return; } @@ -249,7 +252,8 @@ impl Analyzer<'_> { .enumerate() .map(|(i, (n, c))| { let attnum = i16::try_from(i + 1).unwrap_or(i16::MAX); - (n.clone(), Expr::new(ExprKind::Var(Var { relation: index, attnum }), c.ty)) + let var = Var { relation: index, attnum, levels_up: 0 }; + (n.clone(), Expr::new(ExprKind::Var(var), c.ty)) }) .collect(); self.scope.relations.push(Relation { oid: catalog.oid, columns }); @@ -555,17 +559,33 @@ impl Analyzer<'_> { .at_opt(at)); } }; - let Some(item) = self.item_by_name(schema, table, at)? else { + let Some((level, item)) = self.item_by_name(schema, table, at)? else { return Err(self.missing_entry(schema, table, at)); }; - match self.column_in(&item, column, at)? { + match self.column_in(level, &item, column, at)? { Some(expr) => Ok(expr), None => Err(self.missing_column(Some(table), column, at)), } } - /// `scanNSItemForColumn`: the column of an item with this name, or `None`. - fn column_in(&self, item: &Item, name: &str, at: Option) -> Result> { + /// The scopes of the query and of the queries outside it, with their levels: 0 for the query, 1 for the query outside it, and so on. + fn levels(&self) -> impl Iterator { + std::iter::once(&self.scope).chain(self.outer.iter().rev()).enumerate() + } + + /// The scope of a level. + fn level(&self, level: usize) -> &Scope { + self.levels().nth(level).map_or(&self.scope, |(_, scope)| scope) + } + + /// `scanNSItemForColumn`: the column of an item of a level with this name, or `None`. The columns of the expression are `levels_up` of the level. + fn column_in( + &self, + level: usize, + item: &Item, + name: &str, + at: Option, + ) -> Result> { let mut found = None; for (n, expr) in &item.columns { if n == name { @@ -576,16 +596,18 @@ impl Analyzer<'_> { } } if let Some(expr) = found { - return Ok(Some(expr.clone().at(at))); + let mut expr = expr.clone().at(at); + expr.raise(level); + return Ok(Some(expr)); } - let entry = &self.scope.entries[item.entry]; + let entry = &self.level(level).entries[item.entry]; let Some(relation) = entry.relation else { return Ok(None) }; if item.columns.len() != entry.columns.len() { // The item of a USING alias gives only the columns of USING. return Ok(None); } if name == "tableoid" { - let var = Var { relation, attnum: TABLE_OID_ATTNUM }; + let var = Var { relation, attnum: TABLE_OID_ATTNUM, levels_up: level }; return Ok(Some(Expr::new(ExprKind::Var(var), oid::OID).at(at))); } if OTHER_SYSTEM_COLUMNS.contains(&name) { @@ -596,8 +618,8 @@ impl Analyzer<'_> { /// The name of the relation and the name of the column of a `Var`, as `eref->aliasname` and `get_rte_attribute_name` give them for an error. pub(crate) fn column_names(&self, var: Var) -> (String, String) { - let Some(entry) = self.scope.entries.iter().find(|e| e.relation == Some(var.relation)) - else { + let scope = self.level(var.levels_up); + let Some(entry) = scope.entries.iter().find(|e| e.relation == Some(var.relation)) else { return (String::new(), String::new()); }; let column = match usize::try_from(var.attnum) { @@ -607,26 +629,47 @@ impl Analyzer<'_> { (entry.name.clone(), column) } - /// `colNameToVar`: the column that an unqualified name gives, or `None`. + /// `colNameToVar`: the column that an unqualified name gives, or `None`. The search goes up the query levels and stops at the first level that has the name. pub(crate) fn column_by_name(&self, name: &str, at: Option) -> Result> { - let mut result = None; - for item in self.scope.namespace.iter().filter(|i| i.cols_visible) { - if let Some(expr) = self.column_in(item, name, at)? { - if result.is_some() { - return Err(ambiguous(name, at)); + for (level, scope) in self.levels() { + let mut result = None; + for item in scope.namespace.iter().filter(|i| i.cols_visible) { + if let Some(expr) = self.column_in(level, item, name, at)? { + if result.is_some() { + return Err(ambiguous(name, at)); + } + result = Some(expr); } - result = Some(expr); + } + if result.is_some() { + return Ok(result); } } - Ok(result) + Ok(None) } - /// `refnameNamespaceItem`: the item that a table name gives, or `None`. + /// `refnameNamespaceItem`: the item that a table name gives and its level, or `None`. The search goes up the query levels and stops at the first level that has the name. fn item_by_name( &self, schema: Option<&str>, name: &str, at: Option, + ) -> Result> { + for (level, scope) in self.levels() { + if let Some(item) = self.item_in(scope, schema, name, at)? { + return Ok(Some((level, item))); + } + } + Ok(None) + } + + /// `scanNameSpaceForRefname` and `scanNameSpaceForRelid`: the item of a scope that a table name gives, or `None`. + fn item_in( + &self, + scope: &Scope, + schema: Option<&str>, + name: &str, + at: Option, ) -> Result> { if let Some(schema) = schema { // scanNameSpaceForRelid: a relation with no alias in that schema. @@ -638,8 +681,8 @@ impl Analyzer<'_> { }; let Some(catalog) = self.lookup_relation(&rv)? else { return Ok(None) }; let mut result = None; - for item in self.scope.namespace.iter().filter(|i| i.rel_visible) { - let entry = &self.scope.entries[item.entry]; + for item in scope.namespace.iter().filter(|i| i.rel_visible) { + let entry = &scope.entries[item.entry]; if entry.relation.is_some() && !entry.aliased && entry.oid == catalog.oid { if result.is_some() { return Err(Error::new( @@ -654,7 +697,7 @@ impl Analyzer<'_> { return Ok(result); } let mut result = None; - for item in self.scope.namespace.iter().filter(|i| i.rel_visible) { + for item in scope.namespace.iter().filter(|i| i.rel_visible) { if item.name == name { if result.is_some() { return Err(Error::new( @@ -678,31 +721,31 @@ impl Analyzer<'_> { ..RangeVar::default() }; let oid = self.lookup_relation(&rv).ok().flatten().map(|c| c.oid); - let found = self - .scope - .entries - .iter() - .position(|e| (e.relation.is_some() && Some(e.oid) == oid) || e.name == name); - let Some(found) = found else { + let found = self.levels().find_map(|(level, scope)| { + let index = scope + .entries + .iter() + .position(|e| (e.relation.is_some() && Some(e.oid) == oid) || e.name == name)?; + Some((level, index)) + }); + let Some((level, found)) = found else { return Error::new( SqlState::UNDEFINED_TABLE, format!("missing FROM-clause entry for table \"{name}\""), ) .at_opt(at); }; - let entry = &self.scope.entries[found]; + let entry = &self.level(level).entries[found]; let error = Error::new( SqlState::UNDEFINED_TABLE, format!("invalid reference to FROM-clause entry for table \"{name}\""), ) .at_opt(at); if entry.aliased && entry.name != name { - let visible = self - .scope - .namespace - .iter() - .filter(|i| i.rel_visible && i.name == entry.name) - .any(|i| i.entry == found); + let visible = matches!( + self.item_by_name(None, &entry.name, None), + Ok(Some((l, item))) if l == level && item.entry == found + ); if visible { return error.with_hint(format!( "Perhaps you meant to reference the table alias \"{}\".", @@ -724,7 +767,10 @@ impl Analyzer<'_> { second: None, exact: Vec::new(), }; - for (index, entry) in self.scope.entries.iter().enumerate() { + let entries = self.levels().flat_map(|(level, scope)| { + scope.entries.iter().enumerate().map(move |(index, entry)| ((level, index), entry)) + }); + for (index, entry) in entries { if entry.relation.is_none() { continue; } @@ -746,7 +792,7 @@ impl Analyzer<'_> { None => format!("column \"{column}\" does not exist"), }; let error = Error::new(SqlState::UNDEFINED_COLUMN, message).at_opt(at); - let entries = &self.scope.entries; + let entry = |(level, index): Place| &self.level(level).entries[index]; if fuzzy.exact.len() > 1 { let error = error.with_detail(format!("There are columns named \"{column}\", but they are in tables that cannot be referenced from this part of the query.")); return if table.is_none() { @@ -756,17 +802,19 @@ impl Analyzer<'_> { }; } if let Some(&index) = fuzzy.exact.first() { - let error = error.with_detail(format!("There is a column named \"{column}\" in table \"{}\", but it cannot be referenced from this part of the query.", entries[index].name)); + let error = error.with_detail(format!("There is a column named \"{column}\" in table \"{}\", but it cannot be referenced from this part of the query.", entry(index).name)); // rte_visible_if_qualified: the item has a name that a query can qualify. - let qualified = self.scope.namespace.iter().any(|i| i.entry == index && i.rel_visible); + let qualified = self.levels().any(|(level, scope)| { + level == index.0 + && scope.namespace.iter().any(|i| i.entry == index.1 && i.rel_visible) + }); return if table.is_none() && qualified { error.with_hint("To reference that column, you must use a table-qualified name.") } else { error }; } - let name = - |(e, c): (usize, usize)| format!("{}.{}", entries[e].name, entries[e].columns[c]); + let name = |(e, c): (Place, usize)| format!("{}.{}", entry(e).name, entry(e).columns[c]); match (fuzzy.first, fuzzy.second) { (Some(first), None) => error.with_hint(format!( "Perhaps you meant to reference the column \"{}\".", @@ -817,7 +865,10 @@ impl Analyzer<'_> { .at_opt(at)); } match self.item_by_name(schema, table, at)? { - Some(item) => vec![item], + Some((0, item)) => vec![item], + Some(_) => { + return Err(not_yet("a row expansion of a relation of an outer query", at)); + } None => return Err(self.missing_entry(schema, table, at)), } } diff --git a/crates/rupg-analyze/src/lib.rs b/crates/rupg-analyze/src/lib.rs index 0c2a7ee..ef9c57f 100644 --- a/crates/rupg-analyze/src/lib.rs +++ b/crates/rupg-analyze/src/lib.rs @@ -15,6 +15,7 @@ mod poly; mod resolve; mod select; mod sort; +mod sublink; mod transform; mod typcache; mod typename; @@ -28,7 +29,10 @@ use crate::coerce::AtOpt; pub use coerce::{Context, Path, can_coerce, find_path}; pub use colname::figure_colname; -pub use expr::{Aggref, BoolOp, BoolTest, Case, Expr, ExprKind, Func, FuncForm, SqlValue, Var}; +pub use expr::{ + Aggref, BoolOp, BoolTest, Case, Expr, ExprKind, Func, FuncForm, SqlValue, SubLink, SubLinkKind, + Var, +}; pub use from::{Column, FromItem, Join, JoinKind, Relation, TABLE_OID_ATTNUM}; pub use select::{Query, Target}; pub use sort::SortGroup; @@ -70,6 +74,8 @@ pub(crate) struct Analyzer<'a> { depth: usize, /// The relations and the names of `FROM`. scope: from::Scope, + /// The scopes of the queries outside the subquery that the analyzer reads now, as `parentParseState`. The last is the query just outside it. + outer: Vec, /// The part of the query that the analyzer reads now. kind: agg::Kind, /// `p_hasAggs`: true when the query has an aggregate call. @@ -90,6 +96,7 @@ impl<'a> Analyzer<'a> { notices: Vec::new(), depth: 0, scope: from::Scope::default(), + outer: Vec::new(), kind: agg::Kind::Other, has_aggs: false, } @@ -704,6 +711,82 @@ mod tests { }); } + #[test] + fn subquery_errors() { + big_stack(|| { + // The errors and the positions come from PostgreSQL 19. + let cases = [ + ("SELECT (SELECT 1, 2)", "42601", "subquery must return only one column", 7), + ("SELECT 1 IN (SELECT 1, 2)", "42601", "subquery has too many columns", 9), + ("SELECT 1 IN (SELECT FROM pg_am)", "42601", "subquery has too few columns", 9), + ( + "SELECT (SELECT c.amname) FROM pg_am c GROUP BY c.amtype", + "42803", + "subquery uses ungrouped column \"c.amname\" from outer query", + 15, + ), + ( + "SELECT 1 = ANY (SELECT 'a'::text)", + "42883", + "operator does not exist: integer = text", + 9, + ), + ( + "SELECT 1 + ANY (SELECT 1)", + "42804", + "row comparison operator must yield type boolean, not type integer", + 9, + ), + ( + "SELECT (SELECT nosuch FROM pg_am) FROM pg_class", + "42703", + "column \"nosuch\" does not exist", + 15, + ), + ( + "SELECT (SELECT x.oid FROM pg_am) FROM pg_class c", + "42P01", + "missing FROM-clause entry for table \"x\"", + 15, + ), + ( + "SELECT 1 FROM pg_am LIMIT (SELECT oid FROM pg_am a2 WHERE a2.oid = pg_am.oid)", + "42P10", + "argument of LIMIT must not contain variables", + 67, + ), + ]; + for (sql, state, message, at) in cases { + assert_eq!(error(sql), (state.to_string(), message.to_string(), Some(at)), "{sql}"); + } + assert_eq!( + error("SELECT ARRAY(SELECT NULL::pg_node_tree)"), + ( + "42704".to_string(), + "could not find array type for data type pg_node_tree".to_string(), + None + ) + ); + // The hint of a close name searches the relations of the outer queries too. + assert_eq!( + full_error("SELECT (SELECT amnam FROM pg_am) FROM pg_class")[3], + "Perhaps you meant to reference the column \"pg_am.amname\"." + ); + assert_eq!( + full_error("SELECT (SELECT relnam FROM pg_am) FROM pg_class")[3], + "Perhaps you meant to reference the column \"pg_class.relname\" or the column \"pg_class.relam\"." + ); + // A column of an outer query is a constant for LIMIT of the subquery. + let query = run( + "SELECT (SELECT a2.amname FROM pg_am a2 LIMIT a.oid::int8) FROM pg_am a", + &Params::default(), + ) + .expect("an outer column in LIMIT"); + let ExprKind::SubLink(sub) = &query.targets[0].expr.kind else { panic!("no SubLink") }; + assert!(sub.query.limit.as_ref().and_then(Expr::first_var).is_none()); + }); + } + #[test] fn groups() { big_stack(|| { diff --git a/crates/rupg-analyze/src/select.rs b/crates/rupg-analyze/src/select.rs index cef5fe2..acf945a 100644 --- a/crates/rupg-analyze/src/select.rs +++ b/crates/rupg-analyze/src/select.rs @@ -59,6 +59,49 @@ pub struct Target { pub junk: bool, } +impl Query { + /// The expressions of the query, in the order of `query_tree_walker`: the targets, the conditions of the joins, `WHERE`, `HAVING`, `OFFSET` and `LIMIT`. + pub fn exprs(&self) -> Vec<&Expr> { + fn joins<'a>(item: &'a FromItem, out: &mut Vec<&'a Expr>) { + if let FromItem::Join(j) = item { + joins(&j.left, out); + joins(&j.right, out); + out.extend(&j.on); + } + } + let mut out: Vec<&Expr> = self.targets.iter().map(|t| &t.expr).collect(); + for item in &self.from { + joins(item, &mut out); + } + out.extend(self.filter.iter().chain(&self.having).chain(&self.offset).chain(&self.limit)); + out + } + + /// The expressions of the query, in the order of [`Query::exprs`]. + pub fn exprs_mut(&mut self) -> Vec<&mut Expr> { + fn joins<'a>(item: &'a mut FromItem, out: &mut Vec<&'a mut Expr>) { + if let FromItem::Join(j) = item { + let j = &mut **j; + joins(&mut j.left, out); + joins(&mut j.right, out); + out.extend(&mut j.on); + } + } + let mut out: Vec<&mut Expr> = self.targets.iter_mut().map(|t| &mut t.expr).collect(); + for item in &mut self.from { + joins(item, &mut out); + } + out.extend( + self.filter + .iter_mut() + .chain(&mut self.having) + .chain(&mut self.offset) + .chain(&mut self.limit), + ); + out + } +} + /// The error of a clause that the analyzer does not take yet. fn not_yet(what: &str) -> Error { Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")) @@ -122,7 +165,9 @@ impl Analyzer<'_> { } // markTargetListOrigins: a target that is a column of a relation names the table and the column. for target in &mut targets { - if let ExprKind::Var(var) = &target.expr.kind { + if let ExprKind::Var(var) = &target.expr.kind + && var.levels_up == 0 + { target.origin = Some((self.scope.relations[var.relation].oid, var.attnum)); } } diff --git a/crates/rupg-analyze/src/sublink.rs b/crates/rupg-analyze/src/sublink.rs new file mode 100644 index 0000000..502d695 --- /dev/null +++ b/crates/rupg-analyze/src/sublink.rs @@ -0,0 +1,121 @@ +//! `transformSubLink` of `parse_expr.c`: a subquery in an expression, as `EXISTS`, `ANY`, `ALL`, `IN`, `ARRAY` or a scalar subquery. + +use rupg_common::{Error, Result, SqlState}; +use rupg_sql::nodes::{Node, SubLink as RawSubLink, SubLinkType}; +use rupg_types::oid; + +use crate::Analyzer; +use crate::agg::Kind; +use crate::coerce::AtOpt; +use crate::expr::{Expr, ExprKind, SubLink, SubLinkKind}; +use crate::select::Query; +use crate::typename::{names, place}; +use crate::types; + +/// An error for a subquery that the analyzer does not take yet. +fn not_yet(what: &str, at: Option) -> Error { + Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")).at_opt(at) +} + +impl Analyzer<'_> { + /// `parse_sub_analyze`: the query tree of a subquery. The subquery sees the names of this query and of the queries outside it. + fn sub_analyze(&mut self, node: Option<&Node>) -> Result { + let Some(Node::SelectStmt(stmt)) = node else { + return Err(Error::internal("a subquery that is not SelectStmt")); + }; + let scope = std::mem::take(&mut self.scope); + self.outer.push(scope); + let kind = std::mem::replace(&mut self.kind, Kind::Other); + let has_aggs = std::mem::replace(&mut self.has_aggs, false); + let result = self.select(stmt); + self.has_aggs = has_aggs; + self.kind = kind; + self.scope = self.outer.pop().unwrap_or_default(); + result + } + + /// `transformSubLink`. + pub(crate) fn transform_sublink(&mut self, s: &RawSubLink) -> Result { + let at = place(s.location); + let kind = match s.subLinkType { + SubLinkType::EXISTS_SUBLINK => SubLinkKind::Exists, + SubLinkType::ALL_SUBLINK => SubLinkKind::All, + SubLinkType::ANY_SUBLINK => SubLinkKind::Any, + SubLinkType::EXPR_SUBLINK => SubLinkKind::Expr, + SubLinkType::ARRAY_SUBLINK => SubLinkKind::Array, + SubLinkType::ROWCOMPARE_SUBLINK => { + return Err(not_yet("a row comparison with a subquery", at)); + } + _ => return Err(not_yet("this kind of subquery", at)), + }; + let query = self.sub_analyze(s.subselect.as_ref())?; + let columns: Vec<(u32, i32)> = + query.targets.iter().filter(|t| !t.junk).map(|t| (t.expr.ty, t.expr.typmod)).collect(); + let (ty, typmod, test) = match kind { + SubLinkKind::Exists => (oid::BOOL, -1, None), + SubLinkKind::Expr | SubLinkKind::Array => { + let [(ty, typmod)] = columns[..] else { + return Err(Error::new( + SqlState::SYNTAX_ERROR, + "subquery must return only one column", + ) + .at_opt(at)); + }; + if kind == SubLinkKind::Expr { + (ty, typmod, None) + } else if types::element(ty) != 0 { + return Err(not_yet("ARRAY() of an array", at)); + } else { + let array = types::array_of(ty); + if array == 0 { + return Err(Error::new( + SqlState::UNDEFINED_OBJECT, + format!("could not find array type for data type {}", types::name(ty)), + )); + } + (array, typmod, None) + } + } + SubLinkKind::Any | SubLinkKind::All => { + if let Some(Node::RowExpr(r)) = &s.testexpr { + return Err(not_yet("a row on the left of a subquery", place(r.location))); + } + let left = self.transform(s.testexpr.as_ref())?; + let op_names = if s.operName.is_empty() { vec!["="] } else { names(&s.operName) }; + match columns.len() { + 0 => { + return Err(Error::new( + SqlState::SYNTAX_ERROR, + "subquery has too few columns", + ) + .at_opt(at)); + } + 1 => {} + _ => { + return Err(Error::new( + SqlState::SYNTAX_ERROR, + "subquery has too many columns", + ) + .at_opt(at)); + } + } + let (ty, typmod) = columns[0]; + let param = Expr { kind: ExprKind::SubColumn(0), ty, typmod, location: None }; + let test = self.make_op(&op_names, Some(left), param, at)?; + if test.ty != oid::BOOL { + return Err(Error::new( + SqlState::DATATYPE_MISMATCH, + format!( + "row comparison operator must yield type boolean, not type {}", + types::name(test.ty) + ), + ) + .at_opt(at)); + } + (oid::BOOL, -1, Some(test)) + } + }; + let sub = SubLink { kind, test, query }; + Ok(Expr { kind: ExprKind::SubLink(Box::new(sub)), ty, typmod, location: at }) + } +} diff --git a/crates/rupg-analyze/src/transform.rs b/crates/rupg-analyze/src/transform.rs index 07c9585..3ad4294 100644 --- a/crates/rupg-analyze/src/transform.rs +++ b/crates/rupg-analyze/src/transform.rs @@ -152,7 +152,7 @@ impl Analyzer<'_> { } } Node::RowExpr(r) => Err(not_yet("ROW()", place(r.location))), - Node::SubLink(s) => Err(not_yet("a subquery", place(s.location))), + Node::SubLink(s) => self.transform_sublink(s), Node::A_Indirection(_) => Err(not_yet("a subscript or a field selection", None)), Node::GroupingFunc(g) => Err(Error::new( SqlState::GROUPING_ERROR, diff --git a/crates/rupg-exec/src/agg.rs b/crates/rupg-exec/src/agg.rs index f9e0fd7..bb2269f 100644 --- a/crates/rupg-exec/src/agg.rs +++ b/crates/rupg-exec/src/agg.rs @@ -315,7 +315,7 @@ fn collect<'q>(expr: &'q Expr, out: &mut Vec<(&'q Aggref, &'q Expr)>) { } /// The aggregate calls of the select list and of `HAVING`. -fn calls(query: &Query) -> Vec<(&Aggref, &Expr)> { +pub(crate) fn calls(query: &Query) -> Vec<(&Aggref, &Expr)> { let mut out = Vec::new(); for target in &query.targets { collect(&target.expr, &mut out); diff --git a/crates/rupg-exec/src/lib.rs b/crates/rupg-exec/src/lib.rs index 7493919..0d44a34 100644 --- a/crates/rupg-exec/src/lib.rs +++ b/crates/rupg-exec/src/lib.rs @@ -4,9 +4,12 @@ #![forbid(unsafe_code)] +use std::cell::RefCell; +use std::rc::Rc; + use rupg_analyze::{ Aggref, BoolOp, BoolTest, Case, Expr, ExprKind, FromItem, Func, Query, SortGroup, SqlValue, - Target, + SubLink, SubLinkKind, Target, }; use rupg_common::{Error, Result, SqlState}; use rupg_func::{Call, Kernel, Session, base_type}; @@ -54,7 +57,14 @@ fn check(expr: &Expr) -> Result<()> { | ExprKind::Param(_) | ExprKind::CaseTest | ExprKind::SqlValue(_) - | ExprKind::Var(_) => {} + | ExprKind::Var(_) + | ExprKind::SubColumn(_) => {} + ExprKind::SubLink(sub) => { + if let Some(test) = &sub.test { + check(test)?; + } + check_query(&sub.query, false)?; + } ExprKind::Func(f) => { kernel(f.oid, expr.location)?; f.args.iter().try_for_each(check)?; @@ -122,6 +132,12 @@ const MIXED_KEYS: &str = /// /// `0A000` for a function or a type that the engine does not have yet. pub fn prepare(query: Query) -> Result { + check_query(&query, true)?; + Ok(Plan { query }) +} + +/// Checks that the engine can run a query or a subquery. With `output`, it also checks that the engine has the output function of each column. +fn check_query(query: &Query, output: bool) -> Result<()> { // `create_ordinary_grouping_paths`: a `GROUP BY` sorts the rows when every type has an order, or else uses a hash table. The planner gives this error before the engine checks its functions. if !query.group.iter().all(|g| g.sort != 0) && !query.group.iter().all(|g| g.hashable) { return Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "could not implement GROUP BY") @@ -133,7 +149,7 @@ pub fn prepare(query: Query) -> Result { .with_detail(MIXED_KEYS)); } for target in &query.targets { - if !rupg_func::output_supported(target.expr.ty) { + if output && !rupg_func::output_supported(target.expr.ty) { return Err(not_yet(format!("output of type {}", format_type(target.expr.ty)))); } check(&target.expr)?; @@ -144,7 +160,7 @@ pub fn prepare(query: Query) -> Result { if let Some(having) = &query.having { check(having)?; } - agg::check(&query)?; + agg::check(query)?; for item in &query.from { check_join(item)?; } @@ -163,7 +179,140 @@ pub fn prepare(query: Query) -> Result { kernel(group.sort, None)?; } } - Ok(Plan { query }) + Ok(()) +} + +/// The rows of a query. +type Rows = Vec>; + +/// The state of a run that the query and its subqueries share. +#[derive(Default)] +struct Cache { + /// The rows of the relations of each query, by the address of the query. A subquery reads them once, also when it runs for each row of the query outside it. + tables: RefCell)>>, + /// The rows of each subquery that reads no column of an outer query, as an `InitPlan` keeps them. It runs once, when the query first needs it. + rows: RefCell)>>, +} + +impl Cache { + fn tables(&self, query: &Query, session: &dyn Session) -> Result> { + if let Some((_, tables)) = + self.tables.borrow().iter().find(|(q, _)| std::ptr::eq(*q, query)) + { + return Ok(Rc::clone(tables)); + } + let tables = Rc::new(scan::read(query, session)?); + self.tables.borrow_mut().push((query, Rc::clone(&tables))); + Ok(tables) + } +} + +/// The row of a query outside a subquery, which the columns of an outer query in the subquery read. +#[derive(Clone, Copy)] +struct Frame<'a> { + tables: &'a scan::Tables, + tuple: &'a [usize], +} + +/// True when a subquery reads a column of a query outside it, so that it must run again for each row of that query. +fn correlated(query: &Query) -> bool { + query.exprs().iter().any(|e| { + e.find(0, &mut |e, depth| match e.kind { + ExprKind::Var(var) if var.levels_up > depth => Some(()), + _ => None, + }) + .is_some() + }) +} + +/// True when an expression is a constant for `eval_const_expressions`: constants, parameters and immutable functions of them. +fn constant(expr: &Expr) -> bool { + match &expr.kind { + ExprKind::Const(_) | ExprKind::Param(_) => true, + ExprKind::Relabel(arg) => constant(arg), + ExprKind::Func(f) => { + builtin::proc_by_oid(f.oid).is_some_and(|p| p.volatile == b'i') + && f.args.iter().all(constant) + } + _ => false, + } +} + +/// Runs a query or a subquery and gives its rows, with no junk columns. `outer` has the rows of the queries outside a subquery, with the query just outside it last. With `exists`, the query runs as `simplify_EXISTS_query` makes it when it can, and gives one empty row when it has a row. +fn run_query( + query: &Query, + params: &[Value], + session: &dyn Session, + cache: &Cache, + outer: &[Frame<'_>], + exists: bool, +) -> Result>> { + let tables = cache.tables(query, session)?; + let tables = &*tables; + let mut eval = Eval { + params, + session, + case: Vec::new(), + tables, + tuple: &[], + aggs: Vec::new(), + cache, + outer, + sub: Vec::new(), + }; + // simplify_EXISTS_query: with no aggregate, no `HAVING`, no `OFFSET` and a `LIMIT` that is a constant more than 0 or null, `EXISTS` only needs a row of `FROM` and `WHERE`. + if exists && agg::calls(query).is_empty() && query.having.is_none() && query.offset.is_none() { + let simple = match &query.limit { + None => true, + Some(limit) if constant(limit) => match eval.eval(limit)? { + Value::Int8(n) => n > 0, + _ => true, + }, + Some(_) => false, + }; + if simple { + let tuples = scan::tuples(query, tables, &mut |expr, tuple| { + Ok(eval.at(tuple).eval(expr)? == Value::Bool(true)) + })?; + return Ok(if tuples.is_empty() { Vec::new() } else { vec![Vec::new()] }); + } + } + let (offset, count) = eval.limits(query)?; + if count == Some(0) { + return Ok(Vec::new()); + } + let tuples = scan::tuples(query, tables, &mut |expr, tuple| { + Ok(eval.at(tuple).eval(expr)? == Value::Bool(true)) + })?; + let none = vec![scan::NONE; query.relations.len()]; + let rows = if query.grouped { + agg::rows(&mut eval, query, &tuples, &none)? + } else { + // With no sort, `Limit` stops after the rows that it gives. + let ordered = !query.sort.is_empty() || !query.distinct.is_empty(); + let needed = match count { + Some(count) if !ordered => offset.saturating_add(count), + _ => usize::MAX, + }; + let mut rows = Vec::with_capacity(tuples.len().min(needed)); + for tuple in tuples.iter().take(needed) { + eval.tuple = tuple; + let row = + query.targets.iter().map(|t| eval.eval(&t.expr)).collect::>>()?; + rows.push(row); + } + rows + }; + let types: Vec = query.targets.iter().map(|t| t.expr.ty).collect(); + let rows = order(query, rows, &types, offset, count, session)?; + let width = query.targets.iter().take_while(|t| !t.junk).count(); + Ok(rows + .into_iter() + .map(|mut row| { + row.truncate(width); + row + }) + .collect()) } impl Plan { @@ -189,142 +338,85 @@ impl Plan { /// /// The error of a function, such as a division by zero. pub fn run(&self, params: &[Value], session: &dyn Session) -> Result>> { - let query = &self.query; - let tables = scan::read(query, session)?; - let mut eval = Eval { - params, - session, - case: Vec::new(), - tables: &tables, - tuple: &[], - aggs: Vec::new(), - }; - let (offset, count) = eval.limits(query)?; - if count == Some(0) { - return Ok(Vec::new()); - } - let tuples = scan::tuples(query, &tables, &mut |expr, tuple| { - let mut eval = Eval { - params, - session, - case: Vec::new(), - tables: &tables, - tuple, - aggs: Vec::new(), - }; - Ok(eval.eval(expr)? == Value::Bool(true)) - })?; - let none = vec![scan::NONE; query.relations.len()]; - let rows = if query.grouped { - agg::rows(&mut eval, query, &tuples, &none)? + run_query(&self.query, params, session, &Cache::default(), &[], false) + } +} + +/// `DISTINCT`, `ORDER BY`, `OFFSET` and `LIMIT`. A `DISTINCT` sorts the rows on the items of `DISTINCT` and keeps the first row of each group, as `Sort` and `Unique` do. When a type of `DISTINCT` has no order, it keeps the first row of each group in the order of the rows and then sorts for `ORDER BY`. A `Sort` below `Limit` keeps only the first `OFFSET` + `LIMIT` rows, as `tuplesort_set_bound` does. +fn order( + query: &Query, + mut rows: Vec>, + types: &[u32], + offset: usize, + count: Option, + session: &dyn Session, +) -> Result>> { + let mut sorted = query.sort.is_empty(); + if !query.distinct.is_empty() { + let equal = sort::Keys::new(&query.distinct, types, session)?; + if query.distinct.iter().all(|g| g.sort != 0) { + // The items of `DISTINCT ON` come first in `ORDER BY`, so the longer list sorts for both. + let list: &[SortGroup] = + if query.sort.len() > query.distinct.len() { &query.sort } else { &query.distinct }; + let keys = sort::Keys::new(list, types, session)?; + sort::qsort(&mut rows, &mut |a, b| keys.compare(a, b))?; + let mut unique: Vec> = Vec::new(); + for row in rows { + if let Some(last) = unique.last() + && equal.equal(last, &row)? + { + continue; + } + unique.push(row); + } + rows = unique; + sorted = true; } else { - // With no sort, `Limit` stops after the rows that it gives. - let ordered = !query.sort.is_empty() || !query.distinct.is_empty(); - let needed = match count { - Some(count) if !ordered => offset.saturating_add(count), - _ => usize::MAX, - }; - let mut rows = Vec::with_capacity(tuples.len().min(needed)); - for tuple in tuples.iter().take(needed) { - eval.tuple = tuple; - let row = - query.targets.iter().map(|t| eval.eval(&t.expr)).collect::>>()?; - rows.push(row); - } - rows - }; - let types: Vec = query.targets.iter().map(|t| t.expr.ty).collect(); - let rows = self.order(rows, &types, offset, count, session)?; - let width = self.columns().len(); - Ok(rows - .into_iter() - .map(|mut row| { - row.truncate(width); - row - }) - .collect()) - } - - /// `DISTINCT`, `ORDER BY`, `OFFSET` and `LIMIT`. A `DISTINCT` sorts the rows on the items of `DISTINCT` and keeps the first row of each group, as `Sort` and `Unique` do. When a type of `DISTINCT` has no order, it keeps the first row of each group in the order of the rows and then sorts for `ORDER BY`. A `Sort` below `Limit` keeps only the first `OFFSET` + `LIMIT` rows, as `tuplesort_set_bound` does. - fn order( - &self, - mut rows: Vec>, - types: &[u32], - offset: usize, - count: Option, - session: &dyn Session, - ) -> Result>> { - let query = &self.query; - let mut sorted = query.sort.is_empty(); - if !query.distinct.is_empty() { - let equal = sort::Keys::new(&query.distinct, types, session)?; - if query.distinct.iter().all(|g| g.sort != 0) { - // The items of `DISTINCT ON` come first in `ORDER BY`, so the longer list sorts for both. - let list: &[SortGroup] = if query.sort.len() > query.distinct.len() { - &query.sort - } else { - &query.distinct - }; - let keys = sort::Keys::new(list, types, session)?; - sort::qsort(&mut rows, &mut |a, b| keys.compare(a, b))?; - let mut unique: Vec> = Vec::new(); - for row in rows { - if let Some(last) = unique.last() - && equal.equal(last, &row)? - { - continue; + let mut unique: Vec> = Vec::new(); + for row in rows { + let mut seen = false; + for other in &unique { + if equal.equal(other, &row)? { + seen = true; + break; } - unique.push(row); } - rows = unique; - sorted = true; - } else { - let mut unique: Vec> = Vec::new(); - for row in rows { - let mut seen = false; - for other in &unique { - if equal.equal(other, &row)? { - seen = true; - break; - } - } - if !seen { - unique.push(row); - } + if !seen { + unique.push(row); } - rows = unique; } + rows = unique; } - if !sorted { - let keys = sort::Keys::new(&query.sort, types, session)?; - // `compute_tuples_needed` and `tuplesort_set_bound`: the bound must allow `bound * 2` in an `int`. - let bound = match count { - Some(count) if !query.with_ties => offset - .checked_add(count) - .filter(|b| *b <= usize::try_from(i32::MAX / 2).unwrap_or(usize::MAX)), - _ => None, - }; - match bound { - Some(bound) if rows.len() > bound * 2 => { - rows = sort::bounded(rows, bound, &mut |a, b| keys.compare(a, b))?; - } - _ => sort::qsort(&mut rows, &mut |a, b| keys.compare(a, b))?, + } + if !sorted { + let keys = sort::Keys::new(&query.sort, types, session)?; + // `compute_tuples_needed` and `tuplesort_set_bound`: the bound must allow `bound * 2` in an `int`. + let bound = match count { + Some(count) if !query.with_ties => offset + .checked_add(count) + .filter(|b| *b <= usize::try_from(i32::MAX / 2).unwrap_or(usize::MAX)), + _ => None, + }; + match bound { + Some(bound) if rows.len() > bound * 2 => { + rows = sort::bounded(rows, bound, &mut |a, b| keys.compare(a, b))?; } + _ => sort::qsort(&mut rows, &mut |a, b| keys.compare(a, b))?, } - let start = offset.min(rows.len()); - let mut end = count.map_or(rows.len(), |c| start.saturating_add(c).min(rows.len())); - if query.with_ties && end > start { - // `LIMIT_WINDOWEND_TIES`: the rows equal to the last row of the window on the items of `ORDER BY` come too. - let keys = sort::Keys::new(&query.sort, types, session)?; - let last = end - 1; - while end < rows.len() && keys.equal(&rows[last], &rows[end])? { - end += 1; - } + } + let start = offset.min(rows.len()); + let mut end = count.map_or(rows.len(), |c| start.saturating_add(c).min(rows.len())); + if query.with_ties && end > start { + // `LIMIT_WINDOWEND_TIES`: the rows equal to the last row of the window on the items of `ORDER BY` come too. + let keys = sort::Keys::new(&query.sort, types, session)?; + let last = end - 1; + while end < rows.len() && keys.equal(&rows[last], &rows[end])? { + end += 1; } - rows.truncate(end); - rows.drain(..start); - Ok(rows) } + rows.truncate(end); + rows.drain(..start); + Ok(rows) } /// The state of the evaluation of the expressions of one row. @@ -339,9 +431,32 @@ struct Eval<'a> { tuple: &'a [usize], /// The result of each aggregate call for the group, by the address of the call in the query. aggs: Vec<(*const Aggref, Value)>, + cache: &'a Cache, + /// The rows of the queries outside the query, with the query just outside it last. + outer: &'a [Frame<'a>], + /// The rows of the subqueries of `ANY` and `ALL` whose test runs now, for `SubColumn`. + sub: Vec>, } -impl Eval<'_> { +impl<'a> Eval<'a> { + /// A new state for the evaluation of a condition on another tuple of the same query. + fn at<'b>(&self, tuple: &'b [usize]) -> Eval<'b> + where + 'a: 'b, + { + Eval { + params: self.params, + session: self.session, + case: Vec::new(), + tables: self.tables, + tuple, + aggs: Vec::new(), + cache: self.cache, + outer: self.outer, + sub: Vec::new(), + } + } + /// `recompute_limits` of `nodeLimit.c`: the number of rows that `OFFSET` skips, and the number of rows that `LIMIT` gives or `None` for all rows. A null `OFFSET` is 0. fn limits(&mut self, query: &Query) -> Result<(usize, Option)> { let mut value = |expr: Option<&Expr>| -> Result> { @@ -456,7 +571,23 @@ impl Eval<'_> { ExprKind::ScalarArrayOp { func, any, args } => self.scalar_array_op(*func, *any, args), ExprKind::Array { multidims, elements, .. } => self.array(*multidims, elements), ExprKind::SqlValue(v) => self.sql_value(*v), - ExprKind::Var(var) => Ok(self.tables.var(self.tuple, *var)), + ExprKind::Var(var) if var.levels_up == 0 => Ok(self.tables.var(self.tuple, *var)), + ExprKind::Var(var) => { + let frame = self + .outer + .len() + .checked_sub(var.levels_up) + .and_then(|i| self.outer.get(i)) + .ok_or_else(|| Error::internal("a column of an outer query has no row"))?; + Ok(frame.tables.var(frame.tuple, *var)) + } + ExprKind::SubLink(sub) => self.sublink(sub), + ExprKind::SubColumn(n) => self + .sub + .last() + .and_then(|row| row.get(*n)) + .cloned() + .ok_or_else(|| Error::internal("a subquery column is outside of ANY or ALL")), ExprKind::Agg(agg) => self .aggs .iter() @@ -466,6 +597,74 @@ impl Eval<'_> { } } + /// `ExecScanSubPlan`: the value of a subquery in an expression. + fn sublink(&mut self, sub: &SubLink) -> Result { + let exists = sub.kind == SubLinkKind::Exists; + let query = &sub.query; + let cached = self + .cache + .rows + .borrow() + .iter() + .find(|(q, _)| std::ptr::eq(*q, query)) + .map(|(_, rows)| Rc::clone(rows)); + let rows = match cached { + Some(rows) => rows, + None => { + let mut frames = self.outer.to_vec(); + frames.push(Frame { tables: self.tables, tuple: self.tuple }); + let rows = Rc::new(run_query( + query, + self.params, + self.session, + self.cache, + &frames, + exists, + )?); + if !correlated(query) { + self.cache.rows.borrow_mut().push((query, Rc::clone(&rows))); + } + rows + } + }; + match sub.kind { + SubLinkKind::Exists => Ok(Value::Bool(!rows.is_empty())), + SubLinkKind::Expr => match rows.as_slice() { + [] => Ok(Value::Null), + [row] => row + .first() + .cloned() + .ok_or_else(|| Error::internal("a subquery row has no column")), + _ => Err(Error::new( + SqlState::CARDINALITY_VIOLATION, + "more than one row returned by a subquery used as an expression", + )), + }, + SubLinkKind::Array => { + let values = + rows.iter().map(|row| row.first().filter(|v| !v.is_null()).cloned()).collect(); + Ok(Value::Array(Box::new(Array::one(values)))) + } + SubLinkKind::Any | SubLinkKind::All => { + let test = + sub.test.as_ref().ok_or_else(|| Error::internal("ANY and ALL need a test"))?; + let any = sub.kind == SubLinkKind::Any; + let mut unknown = false; + for row in rows.iter() { + self.sub.push(row.clone()); + let result = self.eval(test); + self.sub.pop(); + match result?.as_bool() { + Some(v) if v == any => return Ok(Value::Bool(any)), + Some(_) => {} + None => unknown = true, + } + } + Ok(if unknown { Value::Null } else { Value::Bool(!any) }) + } + } + } + /// A call of a function with the values of its arguments. fn call( &self, diff --git a/crates/rupg-exec/src/scan.rs b/crates/rupg-exec/src/scan.rs index e19c38a..398f56b 100644 --- a/crates/rupg-exec/src/scan.rs +++ b/crates/rupg-exec/src/scan.rs @@ -39,14 +39,16 @@ impl Tables { } } -/// Calls `f` for each column that an expression reads. +/// Calls `f` for each column of the query that an expression reads, also in a subquery. A column of an outer query does not count. fn each_var(expr: &Expr, f: &mut impl FnMut(Var)) { - if let ExprKind::Var(var) = &expr.kind { - f(*var); - } - for child in expr.children() { - each_var(child, f); - } + expr.find(0, &mut |e, depth| { + if let ExprKind::Var(var) = e.kind + && var.levels_up == depth + { + f(var); + } + None::<()> + }); } /// The relations that an expression reads. @@ -190,7 +192,7 @@ fn key(value: Value) -> Option { /// The column of an expression that is a column, or a column with another type of the same form. fn plain_var(expr: &Expr) -> Option { match &expr.kind { - ExprKind::Var(var) => Some(*var), + ExprKind::Var(var) if var.levels_up == 0 => Some(*var), ExprKind::Relabel(arg) => plain_var(arg), _ => None, } diff --git a/crates/rupg-exec/src/tests.rs b/crates/rupg-exec/src/tests.rs index 1823eeb..ebc33e5 100644 --- a/crates/rupg-exec/src/tests.rs +++ b/crates/rupg-exec/src/tests.rs @@ -473,3 +473,187 @@ fn aggregates_and_groups() { ); }); } + +#[test] +fn subqueries() { + big_stack(|| { + // The results come from PostgreSQL 19. + assert_eq!( + one( + "SELECT (SELECT count(*) FROM pg_attribute a WHERE a.attrelid = c.oid AND a.attnum > 0) FROM pg_class c WHERE c.relname = 'pg_class'" + ), + "34" + ); + assert_eq!( + ordered( + "SELECT typname FROM pg_type t WHERE EXISTS (SELECT 1 FROM pg_type e WHERE e.oid = t.typelem AND e.typname = 'int4') ORDER BY 1" + ), + ["_int4"] + ); + assert_eq!( + ordered( + "SELECT amname FROM pg_am WHERE oid IN (SELECT opcmethod FROM pg_opclass WHERE opcname = 'int4_ops') ORDER BY 1" + ), + ["btree", "hash"] + ); + assert_eq!( + ordered( + "SELECT amname FROM pg_am WHERE oid NOT IN (SELECT opcmethod FROM pg_opclass) ORDER BY 1" + ), + ["heap"] + ); + assert_eq!( + one("SELECT ARRAY(SELECT amname FROM pg_am ORDER BY 1)"), + "{brin,btree,gin,gist,hash,heap,spgist}" + ); + assert_eq!( + row( + "SELECT 4 = ALL (SELECT typlen FROM pg_type WHERE typname IN ('int4','oid')), 4 = ALL (SELECT typlen FROM pg_type WHERE typname IN ('int4','int8'))" + ), + Ok(Some(vec!["t".to_string(), "f".to_string()])) + ); + assert_eq!( + ordered( + "SELECT 1 = ANY (SELECT NULL::int) IS NULL, 1 = ALL (SELECT 1 WHERE false), 1 = ANY (SELECT 1 WHERE false), 1 NOT IN (SELECT NULL::int) IS NULL" + ), + ["t|t|f|t"] + ); + assert_eq!( + one("SELECT 3 < ANY (SELECT typlen FROM pg_type WHERE typname IN ('int2','int4'))"), + "t" + ); + assert_eq!( + ordered( + "SELECT amtype, (SELECT count(*) FROM pg_am a2 WHERE a2.amtype = a.amtype) FROM pg_am a GROUP BY amtype ORDER BY 1" + ), + ["i|6", "t|1"] + ); + assert_eq!( + ordered( + "SELECT a.amname, (SELECT count(*) FROM pg_opclass o WHERE o.opcmethod = a.oid) FROM pg_am a ORDER BY 1" + ), + ["brin|72", "btree|45", "gin|4", "gist|9", "hash|42", "heap|0", "spgist|7"] + ); + assert_eq!( + ordered( + "SELECT a.amname FROM pg_am a WHERE EXISTS (SELECT 1 FROM pg_opclass o WHERE o.opcmethod = a.oid AND EXISTS (SELECT 1 FROM pg_type t WHERE t.oid = o.opcintype AND t.typname = 'int4' AND a.amname = 'hash'))" + ), + ["hash"] + ); + assert_eq!( + ordered( + "SELECT (SELECT a2.amname FROM pg_am a2 ORDER BY 1 OFFSET a.oid::int - a.oid::int LIMIT 1) FROM pg_am a LIMIT 1" + ), + ["brin"] + ); + assert_eq!( + ordered( + "SELECT amtype FROM pg_am GROUP BY amtype HAVING count(*) > (SELECT 1) ORDER BY 1" + ), + ["i"] + ); + assert_eq!( + ordered( + "SELECT a.amname, (SELECT max(o.opcname || a.amname) FROM pg_opclass o WHERE o.opcmethod = a.oid) FROM pg_am a WHERE a.amname = 'hash'" + ), + ["hash|xid_opshash"] + ); + assert_eq!( + ordered( + "SELECT a.amname FROM pg_am a JOIN pg_am b ON a.oid = b.oid AND EXISTS (SELECT 1 FROM pg_opclass o WHERE o.opcmethod = b.oid) ORDER BY 1" + ), + ["brin", "btree", "gin", "gist", "hash", "spgist"] + ); + assert_eq!( + ordered( + "SELECT amname FROM pg_am a ORDER BY (SELECT count(*) FROM pg_opclass o WHERE o.opcmethod = a.oid) DESC, amname LIMIT 3" + ), + ["brin", "btree", "hash"] + ); + assert_eq!( + ordered( + "SELECT pg_typeof((SELECT 'x')), pg_typeof(ARRAY(SELECT 'x')), pg_typeof(ARRAY(SELECT 1::int2))" + ), + ["text|text[]|smallint[]"] + ); + assert_eq!( + one( + "SELECT (SELECT amname FROM pg_am WHERE oid = c.oid OR c.relname = 'x') FROM pg_class c WHERE c.relname = 'pg_am'" + ), + "NULL" + ); + assert_eq!(one("SELECT (SELECT oid FROM pg_am WHERE false) IS NULL"), "t"); + assert_eq!(one("SELECT ARRAY(SELECT typname FROM pg_type WHERE false)"), "{}"); + assert_eq!( + ordered( + "SELECT ARRAY(SELECT oid::int - oid::int FROM pg_am WHERE amname IN ('heap','hash')), ARRAY(SELECT NULL::int)" + ), + ["{0,0}|{NULL}"] + ); + assert_eq!( + ordered( + "SELECT (SELECT relname FROM pg_class WHERE relname = 'pg_am')::text, (SELECT c.relname FROM pg_class c WHERE oid = 1259)" + ), + ["pg_am|pg_class"] + ); + assert_eq!(one("SELECT (SELECT relnatts FROM pg_class WHERE oid = 1259) + 1"), "35"); + assert_eq!(one("SELECT relname FROM pg_class c WHERE relname = (SELECT 'pg_am')"), "pg_am"); + assert_eq!( + one( + "SELECT count(*) FROM pg_am a WHERE a.oid > ALL (SELECT o.opcmethod FROM pg_opclass o WHERE o.opcname = 'int4_ops')" + ), + "4" + ); + assert_eq!(one("SELECT 1 FROM pg_am GROUP BY (SELECT 1)"), "1"); + assert_eq!(one("SELECT 1 FROM pg_am a WHERE (SELECT a.oid) = 2 LIMIT (SELECT 1)"), "1"); + assert_eq!(ordered("SELECT (SELECT count(*)) FROM pg_am"), ["1"; 7]); + assert_eq!( + ordered("SELECT amname FROM pg_am a WHERE a.oid IN (SELECT a.oid) ORDER BY 1 LIMIT 2"), + ["brin", "btree"] + ); + assert_eq!( + ordered( + "SELECT amname FROM pg_am a WHERE amname = ANY (SELECT amname FROM pg_am b WHERE b.oid < a.oid)" + ), + [""; 0] + ); + assert_eq!( + ordered( + "SELECT (SELECT b.amname FROM pg_am b WHERE b.oid = a.oid) FROM pg_am a ORDER BY 1 LIMIT 2" + ), + ["brin", "btree"] + ); + // simplify_EXISTS_query: with no aggregate, `HAVING` or `OFFSET`, EXISTS does not compute the target list. + assert_eq!(one("SELECT EXISTS (SELECT 1/0 FROM pg_am WHERE false)"), "f"); + assert_eq!(one("SELECT EXISTS (SELECT 1/(oid::int - oid::int) FROM pg_am)"), "t"); + assert_eq!(one("SELECT 1 WHERE EXISTS (SELECT 1/0 FROM pg_am)"), "1"); + assert_eq!(one("SELECT EXISTS (SELECT 1/0)"), "t"); + assert_eq!( + ordered( + "SELECT EXISTS (SELECT 1/(oid::int - oid::int) FROM pg_am LIMIT 1), EXISTS (SELECT 1/(oid::int - oid::int) FROM pg_am LIMIT NULL)" + ), + ["t|t"] + ); + assert_eq!(one("SELECT EXISTS (SELECT 1/(oid::int - oid::int) FROM pg_am LIMIT 0)"), "f"); + assert_eq!( + error("SELECT EXISTS (SELECT 1/(oid::int - oid::int) FROM pg_am OFFSET 0)"), + "22012" + ); + assert_eq!(error("SELECT EXISTS (SELECT count(*)/0 FROM pg_am)"), "22012"); + assert_eq!(one("SELECT (SELECT 1/(oid::int - oid::int) FROM pg_am WHERE false)"), "NULL"); + assert_eq!( + rows("SELECT (SELECT oid FROM pg_am)"), + Err(( + "21000".to_string(), + "more than one row returned by a subquery used as an expression".to_string() + )) + ); + // rupg does not take an aggregate of an outer query yet. PostgreSQL gives 2 here. + assert_eq!( + error( + "SELECT (SELECT max(a.oid) FROM pg_type LIMIT 1) FROM pg_am a WHERE a.amname = 'heap'" + ), + "0A000" + ); + }); +}