use std::cmp::Ordering;
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;
fn not_yet(what: &str, at: Option<usize>) -> Error {
Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")).at_opt(at)
}
impl Analyzer<'_> {
pub(crate) fn sub_analyze(&mut self, node: Option<&Node>) -> Result<Query> {
let Some(Node::SelectStmt(stmt)) = node else {
return Err(Error::internal("a subquery that is not SelectStmt"));
};
self.sub_query(|analyzer| analyzer.select(stmt))
}
pub(crate) fn sub_query(
&mut self,
f: impl FnOnce(&mut Self) -> Result<Query>,
) -> Result<Query> {
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);
self.outer_kinds.push((Some(kind), has_aggs));
let srfs = self.srfs;
let result = f(self);
self.srfs = srfs;
self.has_aggs = self.outer_kinds.pop().map_or(has_aggs, |(_, has_aggs)| has_aggs);
self.kind = kind;
self.scope = self.outer.pop().unwrap_or_default();
result
}
pub(crate) fn transform_sublink(&mut self, s: &RawSubLink) -> Result<Expr> {
let at = place(s.location);
if self.kind.is_standalone() {
return Err(Error::new(
SqlState::FEATURE_NOT_SUPPORTED,
format!("cannot use subquery in {}", self.kind.subquery_name()),
)
.at_opt(at));
}
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 => SubLinkKind::RowCompare,
_ => 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::Multi { .. } => {
return Err(Error::internal("a multiple-column subquery outside of SET"));
}
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 {
let array = match types::array_of(ty) {
0 if types::element(ty) != 0 => ty,
array => array,
};
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 | SubLinkKind::RowCompare => {
let left = match &s.testexpr {
Some(Node::RowExpr(r)) => self.transform_expression_list(&r.args)?,
node => vec![self.transform(node.as_ref())?],
};
let op_names = if s.operName.is_empty() { vec!["="] } else { names(&s.operName) };
let message = match left.len().cmp(&columns.len()) {
Ordering::Less => Some("subquery has too many columns"),
Ordering::Greater => Some("subquery has too few columns"),
Ordering::Equal => None,
};
if let Some(message) = message {
return Err(Error::new(SqlState::SYNTAX_ERROR, message).at_opt(at));
}
let right = columns
.iter()
.enumerate()
.map(|(n, &(ty, typmod))| Expr {
kind: ExprKind::SubColumn(n),
ty,
typmod,
location: None,
})
.collect();
let test = self.row_comparison(&op_names, left, right, at)?;
(oid::BOOL, -1, Some(test))
}
};
let sub = SubLink { kind, test, query, alternative: None };
Ok(Expr { kind: ExprKind::SubLink(Box::new(sub)), ty, typmod, location: at })
}
}