use super::{
compile_expr, compile_qualified_name, compile_sort_options, compile_window_frame, DecimalValue,
Expr, NodeEnum, OrderBy, Result, SQLError, Value, WindowReference, WindowReferenceKind,
WindowSpec,
};
use crate::ast::{FunctionBinding, FunctionOrderSyntax};
pub(in crate::compiler) fn compile_const(c: &pg_query::protobuf::AConst) -> Result<Expr> {
if c.isnull {
if c.val.is_some() {
return Err(SQLError::Internal(
"NULL constant unexpectedly has a value payload".into(),
));
}
return Ok(Expr::Literal(Value::Null));
}
use pg_query::protobuf::a_const::Val;
let Some(val) = c.val.as_ref() else {
return Err(SQLError::Internal(
"non-NULL constant has no value payload".into(),
));
};
let value = match val {
Val::Ival(i) => Value::Int(i64::from(i.ival)),
Val::Fval(f)
if f.fval
.strip_prefix('-')
.unwrap_or(&f.fval)
.bytes()
.all(|b| b.is_ascii_digit()) =>
{
f.fval.parse::<i64>().map(Value::Int).or_else(|_| {
DecimalValue::parse(&f.fval)
.map(Value::Decimal)
.ok_or_else(|| SQLError::Internal(format!("bad numeric literal {}", f.fval)))
})?
}
Val::Fval(f) => DecimalValue::parse(&f.fval).map_or_else(
|| {
f.fval
.parse::<f64>()
.map(Value::Float)
.map_err(|e| SQLError::Internal(e.to_string()))
},
|d| Ok(Value::Decimal(d)),
)?,
Val::Sval(s) => Value::Str(s.sval.clone()),
Val::Boolval(b) => Value::Bool(b.boolval),
other => {
return Err(SQLError::Unsupported(format!("constant: {other:?}")));
}
};
Ok(Expr::Literal(value))
}
pub(in crate::compiler) fn compile_column_ref(c: &pg_query::protobuf::ColumnRef) -> Result<Expr> {
let mut parts: Vec<String> = Vec::new();
for (index, f) in c.fields.iter().enumerate() {
let inner = f
.node
.as_ref()
.ok_or_else(|| SQLError::Internal("ColumnRef contains an empty field".into()))?;
match inner {
NodeEnum::String(s) if !s.sval.is_empty() => parts.push(s.sval.clone()),
NodeEnum::String(_) => {
return Err(SQLError::Internal(
"ColumnRef contains an empty name component".into(),
));
}
NodeEnum::AStar(_) if c.fields.len() == 1 && index == 0 => return Ok(Expr::Star),
NodeEnum::AStar(_) => {
let qualifier = parts.pop().ok_or_else(|| {
SQLError::Internal("qualified wildcard has no relation name".into())
})?;
return Ok(Expr::QualifiedStar(qualifier));
}
other => {
return Err(SQLError::Internal(format!(
"ColumnRef contains unexpected field {other:?}"
)));
}
}
}
match parts.len() {
0 => Err(SQLError::Internal("empty ColumnRef".into())),
1 => Ok(Expr::Column(parts.pop().ok_or_else(|| {
SQLError::Internal("ColumnRef component disappeared during lowering".into())
})?)),
_ => {
let column = parts.pop().ok_or_else(|| {
SQLError::Internal("ColumnRef column disappeared during lowering".into())
})?;
let qualifier = parts.pop().ok_or_else(|| {
SQLError::Internal("ColumnRef qualifier disappeared during lowering".into())
})?;
Ok(Expr::qualified_column(qualifier, column))
}
}
}
pub(in crate::compiler) fn compile_func_call(f: &pg_query::protobuf::FuncCall) -> Result<Expr> {
let mut raw_name = compile_qualified_name(&f.funcname, "function call")?;
if raw_name.is_empty() {
return Err(SQLError::Internal("function call has an empty name".into()));
}
if FunctionBinding::is_polymorphic_builtin_syntax_name(&raw_name) {
raw_name = format!("\"{raw_name}\"");
}
crate::expr::validate_named_argument_order(f.args.iter().map(|argument| {
match argument.node.as_ref() {
Some(NodeEnum::NamedArgExpr(argument)) => Some(argument.name.as_str()),
_ => None,
}
}))?;
let mut args = f
.args
.iter()
.map(compile_expr)
.collect::<Result<Vec<_>>>()?;
if f.func_variadic {
let argument = args.pop().ok_or_else(|| {
SQLError::Internal(format!(
"VARIADIC invocation of `{raw_name}` has no argument"
))
})?;
args.push(crate::expr::wrap_variadic_argument(argument));
}
if f.agg_star {
if !args.is_empty() {
return Err(SQLError::Internal(format!(
"function `{raw_name}` has both `*` and explicit arguments"
)));
}
if f.agg_distinct || !f.agg_order.is_empty() || f.agg_within_group {
return Err(SQLError::Internal(format!(
"function `{raw_name}(*)` has incompatible aggregate modifiers"
)));
}
args.push(Expr::Star);
}
if f.agg_within_group && f.agg_order.is_empty() {
return Err(SQLError::Internal(format!(
"ordered-set aggregate `{raw_name}` has no WITHIN GROUP ordering"
)));
}
if let Some(over) = f.over.as_ref() {
return compile_window_call(raw_name, args, f, over);
}
let mut agg_order: Vec<OrderBy> = Vec::new();
for sort_node in &f.agg_order {
let inner = sort_node.node.as_ref().ok_or_else(|| {
SQLError::Internal("aggregate ORDER BY contains an empty item".into())
})?;
let NodeEnum::SortBy(sb) = inner else {
return Err(SQLError::Internal(format!(
"aggregate ORDER BY expected SortBy, got {inner:?}"
)));
};
let expr_node = sb
.node
.as_ref()
.ok_or_else(|| SQLError::Internal("agg_order SortBy without expr".into()))?;
let key_expr = compile_expr(expr_node)?;
let (descending, nulls) = compile_sort_options(sb, "aggregate ORDER BY")?;
agg_order.push(OrderBy {
expr: key_expr,
descending,
nulls,
});
}
let agg_filter = match f.agg_filter.as_ref() {
Some(inner) => Some(Box::new(compile_expr(inner)?)),
None => None,
};
let order_syntax = if f.funcformat == pg_query::protobuf::CoercionForm::CoerceSqlSyntax as i32
&& raw_name == "pg_catalog.extract"
{
crate::ast::FunctionCallSyntax::Extract
} else if f.agg_within_group {
FunctionOrderSyntax::WithinGroup
} else {
FunctionOrderSyntax::Ordinary
};
Ok(Expr::Func {
binding: None,
name: raw_name,
args,
distinct: f.agg_distinct,
order_by: agg_order,
order_syntax,
filter: agg_filter,
})
}
fn compile_window_call(
name: String,
mut args: Vec<Expr>,
f: &pg_query::protobuf::FuncCall,
over: &pg_query::protobuf::WindowDef,
) -> Result<Expr> {
if f.agg_within_group {
for sort_node in &f.agg_order {
let Some(NodeEnum::SortBy(sort)) = sort_node.node.as_ref() else {
return Err(SQLError::Internal(format!(
"WITHIN GROUP expected SortBy, got {:?}",
sort_node.node
)));
};
let expression = sort
.node
.as_ref()
.ok_or_else(|| SQLError::Internal("WITHIN GROUP SortBy without expr".into()))?;
args.push(compile_expr(expression)?);
}
}
let filter = f
.agg_filter
.as_ref()
.map(|filter| compile_expr(filter).map(Box::new))
.transpose()?;
let spec = compile_window_spec(over)?;
Ok(Expr::WindowCall {
name,
args,
spec: Box::new(spec),
filter,
modifiers: crate::ast::WindowCallModifiers {
distinct: f.agg_distinct,
ordered: !f.agg_within_group && !f.agg_order.is_empty(),
within_group: f.agg_within_group,
},
})
}
pub(in crate::compiler) fn compile_window_spec(
w: &pg_query::protobuf::WindowDef,
) -> Result<WindowSpec> {
let reference = match (w.name.is_empty(), w.refname.is_empty()) {
(true, true) => None,
(false, true) => Some(WindowReference {
name: w.name.clone(),
kind: WindowReferenceKind::Direct,
}),
(true, false) => Some(WindowReference {
name: w.refname.clone(),
kind: WindowReferenceKind::Copy,
}),
(false, false) => {
return Err(SQLError::Internal(
"window call carries both direct and copied references".into(),
));
}
};
compile_window_spec_parts(w, reference)
}
pub(in crate::compiler) fn compile_named_window_spec(
w: &pg_query::protobuf::WindowDef,
) -> Result<WindowSpec> {
let reference = (!w.refname.is_empty()).then(|| WindowReference {
name: w.refname.clone(),
kind: WindowReferenceKind::Copy,
});
compile_window_spec_parts(w, reference)
}
fn compile_window_spec_parts(
w: &pg_query::protobuf::WindowDef,
reference: Option<WindowReference>,
) -> Result<WindowSpec> {
let partition_by: Vec<Expr> = w
.partition_clause
.iter()
.map(compile_expr)
.collect::<Result<Vec<_>>>()?;
let mut order_by = Vec::new();
for sort_node in &w.order_clause {
let inner = sort_node
.node
.as_ref()
.ok_or_else(|| SQLError::Internal("window ORDER BY contains an empty item".into()))?;
let NodeEnum::SortBy(sb) = inner else {
return Err(SQLError::Internal(format!(
"window ORDER BY expected SortBy, got {inner:?}"
)));
};
let expr_node = sb
.node
.as_ref()
.ok_or_else(|| SQLError::Internal("SortBy without expr".into()))?;
let expr = compile_expr(expr_node)?;
let (descending, nulls) = compile_sort_options(sb, "window ORDER BY")?;
order_by.push(OrderBy {
expr,
descending,
nulls,
});
}
let frame = compile_window_frame(w)?;
Ok(WindowSpec {
definition: None,
raw_definition: Some(super::window::raw_definition(w)),
reference,
partition_by,
order_by,
frame,
})
}