Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions crates/rupg-analyze/src/agg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub(crate) enum Kind {
FromFunction,
/// `EXPR_KIND_VALUES`.
Values,
/// `EXPR_KIND_VALUES_SINGLE`: a `VALUES` list with one row.
ValuesSingle,
/// `EXPR_KIND_EXECUTE_PARAMETER`.
ExecuteParameter,
}
Expand All @@ -61,7 +63,7 @@ impl Kind {
Kind::IndexExpression => "index expressions",
Kind::IndexPredicate => "index predicates",
Kind::FromFunction => "functions in FROM",
Kind::Values => "VALUES",
Kind::Values | Kind::ValuesSingle => "VALUES",
Kind::ExecuteParameter => "EXECUTE parameters",
}
}
Expand Down Expand Up @@ -108,6 +110,19 @@ pub(crate) struct Parts<'a> {
pub(crate) null_treatment: bool,
}

/// The hint of the errors of a call of a function that gives a set inside an expression that cannot take a set.
pub(crate) const SRF_HINT: &str =
"You might be able to move the set-returning function into a LATERAL FROM item.";

/// `check_agg_arguments_walker`: the first aggregate call or call of a function that gives a set in the arguments of an aggregate, outside their subqueries.
fn nested(expr: &Expr) -> Option<&Expr> {
match &expr.kind {
ExprKind::Agg(_) => Some(expr),
ExprKind::Func(f) if f.retset => Some(expr),
_ => expr.children().into_iter().find_map(nested),
}
}

/// An error of an aggregate call in a wrong place, with the SQLSTATE `42803`.
pub(crate) fn grouping_error(message: String, at: Option<usize>) -> Error {
Error::new(SqlState::GROUPING_ERROR, message).at_opt(at)
Expand Down Expand Up @@ -165,12 +180,20 @@ impl Analyzer<'_> {
};
let args: Vec<Expr> = targets.into_iter().map(|t| t.expr).collect();
let filter = parts.filter.map(Box::new);
// check_agg_arguments: an aggregate call in the arguments is of the same level.
if let Some(inner) = args.iter().chain(filter.as_deref()).find_map(Expr::first_agg) {
return Err(grouping_error(
"aggregate function calls cannot be nested".into(),
inner.location,
));
// check_agg_arguments: an aggregate call in the arguments is of the same level, and a set cannot be an argument.
if let Some(inner) = args.iter().chain(filter.as_deref()).find_map(nested) {
if let ExprKind::Agg(_) = inner.kind {
return Err(grouping_error(
"aggregate function calls cannot be nested".into(),
inner.location,
));
}
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
"aggregate function calls cannot contain set-returning function calls",
)
.with_hint(SRF_HINT)
.at_opt(inner.location));
}
// check_agg_arguments: an aggregate whose arguments read only the columns of outer queries belongs to the outer query.
let level = args
Expand Down Expand Up @@ -215,6 +238,7 @@ impl Analyzer<'_> {
| Kind::IndexPredicate
| Kind::FromFunction
| Kind::Values
| Kind::ValuesSingle
| Kind::ExecuteParameter => {
return Err(grouping_error(
format!("aggregate functions are not allowed in {}", self.kind.name()),
Expand Down
2 changes: 1 addition & 1 deletion crates/rupg-analyze/src/coerce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ fn build_cast(
-1
};
Expr {
kind: ExprKind::Func(Func { oid: func, args, form, variadic: false }),
kind: ExprKind::Func(Func { oid: func, args, form, variadic: false, retset: false }),
ty: target,
typmod,
location,
Expand Down
8 changes: 7 additions & 1 deletion crates/rupg-analyze/src/ddl/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,12 @@ fn nextval(sequence: u32) -> Result<Expr> {
.next()
.ok_or_else(|| Error::internal("function nextval does not exist"))?;
let arg = Expr::constant(Value::Oid(sequence), oid::REGCLASS, -1, None);
let func = Func { oid: proc.oid, args: vec![arg], form: FuncForm::Call, variadic: false };
let func = Func {
oid: proc.oid,
args: vec![arg],
form: FuncForm::Call,
variadic: false,
retset: false,
};
Ok(Expr::new(ExprKind::Func(func), proc.rettype))
}
10 changes: 10 additions & 0 deletions crates/rupg-analyze/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ pub struct Func {
pub form: FuncForm,
/// True when the last argument is the array of a variadic function, as `funcvariadic` of `FuncExpr`. A function with `VARIADIC "any"` then takes the elements of the array as its arguments.
pub variadic: bool,
/// `funcretset`: true when the function gives a set.
pub retset: bool,
}

/// A call of an aggregate function, as `Aggref`.
Expand Down Expand Up @@ -417,6 +419,14 @@ impl Expr {
self.children().into_iter().find_map(Expr::first_agg)
}

/// `expression_returns_set` and `exprLocation` of the first call: the first call of a function that gives a set in the expression, outside its subqueries.
pub fn first_set_call(&self) -> Option<&Expr> {
if matches!(&self.kind, ExprKind::Func(f) if f.retset) {
return Some(self);
}
self.children().into_iter().find_map(Expr::first_set_call)
}

/// `strip_implicit_coercions`: the expression without the casts that the analyzer added.
pub fn strip_implicit(&self) -> &Expr {
match &self.kind {
Expand Down
2 changes: 2 additions & 0 deletions crates/rupg-analyze/src/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ impl Analyzer<'_> {
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 srfs = self.srfs;
let result = self.select(stmt);
self.srfs = srfs;
self.has_aggs = has_aggs;
self.kind = kind;
let mut scope = self.outer.pop().unwrap_or_default();
Expand Down
3 changes: 3 additions & 0 deletions crates/rupg-analyze/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub(crate) struct Analyzer<'a> {
views: Vec<u32>,
/// The base type and the typmod of the domain whose check constraint the analyzer reads now, or `None`. Then the name `value` is [`ExprKind::DomainValue`].
domain_value: Option<(u32, i32)>,
/// The number of the calls of functions that give a set that the analysis of the query has made, and the place of the last one. It takes the place of `p_last_srf`: `CASE` and `COALESCE` compare the number before and after their arguments.
srfs: (usize, Option<usize>),
}

impl<'a> Analyzer<'a> {
Expand All @@ -128,6 +130,7 @@ impl<'a> Analyzer<'a> {
keep_unknowns: false,
views: Vec::new(),
domain_value: None,
srfs: (0, None),
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/rupg-analyze/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,8 @@ fn expr_of(item: &Item) -> Result<Expr> {
args: f.exprs("args")?,
form,
variadic: f.flag("variadic")?,
// A stored expression has no call of a function that gives a set.
retset: false,
})
}
"RELABEL" => ExprKind::Relabel(f.expr("arg")?, cast_form(&f)?),
Expand Down Expand Up @@ -876,6 +878,7 @@ mod tests {
args: vec![var(1), constant(Value::Int4(1), oid::INT4)],
form: FuncForm::Operator(551),
variadic: false,
retset: false,
}),
ty: oid::INT4,
typmod: -1,
Expand Down Expand Up @@ -929,6 +932,7 @@ mod tests {
args: vec![],
form: FuncForm::ImplicitCast,
variadic: true,
retset: false,
}),
ty: oid::VARCHAR,
typmod: 14,
Expand Down
72 changes: 54 additions & 18 deletions crates/rupg-analyze/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,13 +539,8 @@ impl Analyzer<'_> {
.at_opt(last.place()));
}
}
// check_srf_call_placement.
if proc.retset && self.kind.is_standalone() {
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
format!("set-returning functions are not allowed in {}", self.kind.name()),
)
.at_opt(at));
if proc.retset {
self.check_srf_placement(&signature(&name, &inputs), at)?;
}
let over = parts.as_ref().is_some_and(|p| p.over);
if let (Some(row), Some(parts)) = (aggregate, parts)
Expand Down Expand Up @@ -588,25 +583,65 @@ impl Analyzer<'_> {
)
.at_opt(at));
}
// A set-returning function can be at the top of a function in FROM, which `transformRangeFunction` checks.
if proc.retset && self.kind != Kind::FromFunction {
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
format!(
"set-returning function {} is not supported yet",
signature(&name, &inputs)
),
)
.at_opt(at));
if proc.retset {
self.srfs = (self.srfs.0 + 1, at);
}
Ok(Expr {
kind: ExprKind::Func(Func { oid: proc.oid, args, form: FuncForm::Call, variadic }),
kind: ExprKind::Func(Func {
oid: proc.oid,
args,
form: FuncForm::Call,
variadic,
retset: proc.retset,
}),
ty: result,
typmod: -1,
location: at,
})
}

/// `check_srf_call_placement`: the error of a call of a function that gives a set in a part of the query where it cannot be. A function in `FROM` can only be at the top, which `transformRangeFunction` checks.
fn check_srf_placement(&self, signature: &str, at: Option<usize>) -> Result<()> {
let message = match self.kind {
Kind::Select
| Kind::GroupBy
| Kind::OrderBy
| Kind::DistinctOn
| Kind::FromFunction => return Ok(()),
Kind::JoinOn => "set-returning functions are not allowed in JOIN conditions".to_owned(),
Kind::Where
| Kind::Having
| Kind::Filter
| Kind::Limit
| Kind::Offset
| Kind::Values
| Kind::ColumnDefault
| Kind::Check
| Kind::IndexExpression
| Kind::IndexPredicate
| Kind::ExecuteParameter => {
format!("set-returning functions are not allowed in {}", self.kind.name())
}
Kind::ValuesSingle | Kind::Other => {
format!("set-returning function {signature} is not supported yet")
}
};
Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, message).at_opt(at))
}

/// The error of a call of a function that gives a set in the arguments of `CASE` or `COALESCE`, when the analysis made such a call after `before`, which is the number of the calls before the arguments.
pub(crate) fn no_srf_since(&self, before: usize, construct: &str) -> Result<()> {
if self.srfs.0 == before {
return Ok(());
}
Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
format!("set-returning functions are not allowed in {construct}"),
)
.with_hint(crate::agg::SRF_HINT)
.at_opt(self.srfs.1))
}

/// The errors of a function that takes or gives `internal`.
fn check_internal(&self, declared: &[u32], result: u32, at: Option<usize>) -> Result<()> {
if declared.contains(&oid::INTERNAL) {
Expand Down Expand Up @@ -807,6 +842,7 @@ impl Analyzer<'_> {
args,
form: FuncForm::Operator(op.oid),
variadic: false,
retset: false,
}),
ty: result,
typmod: -1,
Expand Down
5 changes: 5 additions & 0 deletions crates/rupg-analyze/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ impl Query {
out
}

/// `hasTargetSRFs`: true when a target calls a function that gives a set.
pub fn has_target_srfs(&self) -> bool {
self.targets.iter().any(|t| t.expr.first_set_call().is_some())
}

/// The expressions of the query and of its subqueries in `FROM`, each with its depth: `depth` for the query, and one more for each subquery in `FROM` between the expression and the query. The subqueries in `FROM` come after the expressions of the query, as in `query_tree_walker`.
pub fn all_exprs(&self, depth: usize) -> Vec<(&Expr, usize)> {
let mut out: Vec<(&Expr, usize)> = self.exprs().into_iter().map(|e| (e, depth)).collect();
Expand Down
2 changes: 2 additions & 0 deletions crates/rupg-analyze/src/sublink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ impl Analyzer<'_> {
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 srfs = self.srfs;
let result = self.select(stmt);
self.srfs = srfs;
self.has_aggs = has_aggs;
self.kind = kind;
self.scope = self.outer.pop().unwrap_or_default();
Expand Down
9 changes: 8 additions & 1 deletion crates/rupg-analyze/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,21 @@ impl Analyzer<'_> {
Node::BoolExpr(b) => self.transform_bool(b),
Node::FuncCall(f) => self.transform_func(f),
Node::TypeCast(t) => self.transform_cast(t),
Node::CaseExpr(c) => self.transform_case(c),
Node::CaseExpr(c) => {
let before = self.srfs.0;
let expr = self.transform_case(c)?;
self.no_srf_since(before, "CASE")?;
Ok(expr)
}
Node::CoalesceExpr(c) => {
let before = self.srfs.0;
let args = self.transform_list(&c.args)?;
let ty = self.common_type(&args, "COALESCE")?;
let args = args
.into_iter()
.map(|a| self.coerce_to_common(a, ty, "COALESCE"))
.collect::<Result<Vec<_>>>()?;
self.no_srf_since(before, "COALESCE")?;
Ok(Expr {
kind: ExprKind::Coalesce(args),
ty,
Expand Down
3 changes: 2 additions & 1 deletion crates/rupg-analyze/src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ impl Analyzer<'_> {
return Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "WITH is not supported yet"));
}
let mut columns: Vec<Vec<Expr>> = Vec::new();
let kind = if s.valuesLists.len() == 1 { Kind::ValuesSingle } else { Kind::Values };
for (row, list) in s.valuesLists.iter().enumerate() {
let Some(Node::List(list)) = list else {
return Err(Error::internal("a VALUES row that is not a list"));
};
let mut exprs = Vec::with_capacity(list.len());
for node in list {
exprs.push(self.with_kind(Kind::Values, |a| a.transform(node.as_ref()))?);
exprs.push(self.with_kind(kind, |a| a.transform(node.as_ref()))?);
}
if row == 0 {
columns = vec![Vec::with_capacity(s.valuesLists.len()); exprs.len()];
Expand Down
3 changes: 2 additions & 1 deletion crates/rupg-exec/src/agg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ pub(crate) fn rows<'a>(
})
})
.collect::<Result<_>>()?;
let levels = super::set_levels(&query.targets);
let mut rows = Vec::new();
for group in groups(eval, query, tuples)? {
let mut states = aggs.iter().map(|a| a.start(eval)).collect::<Result<Vec<_>>>()?;
Expand Down Expand Up @@ -465,7 +466,7 @@ pub(crate) fn rows<'a>(
{
continue;
}
rows.push(query.targets.iter().map(|t| eval.eval(&t.expr)).collect::<Result<Vec<_>>>()?);
eval.project(&query.targets, &levels, &mut rows)?;
}
eval.aggs.clear();
Ok(rows)
Expand Down
Loading
Loading