use super::expression_core::{builtin_syntax_call, dispatched_call};
use super::{compile_expr, extract_strings, BinaryOp, Expr, NodeEnum, Result, SQLError, Value};
use crate::ast::{FunctionDispatch, NumericOperator, PatternOperator};
fn compile_pattern_operand(
rhs: &pg_query::protobuf::Node,
wrapper_name: &str,
operator_name: &str,
operator_location: i32,
) -> Result<Expr> {
let Some(NodeEnum::FuncCall(function)) = rhs.node.as_ref() else {
return compile_expr(rhs);
};
let function_name = extract_strings(&function.funcname)?;
if function.location != operator_location
|| !matches!(function_name.as_slice(), [schema, local] if schema == "pg_catalog" && local == wrapper_name)
{
return compile_expr(rhs);
}
if function.agg_distinct
|| function.agg_star
|| function.agg_within_group
|| function.func_variadic
|| !function.agg_order.is_empty()
|| function.agg_filter.is_some()
|| function.over.is_some()
{
return Err(SQLError::Internal(format!(
"{operator_name} wrapper contains aggregate/function modifiers"
)));
}
if !matches!(function.args.len(), 1 | 2) {
return Err(SQLError::Internal(format!(
"{wrapper_name} expects a pattern and optional escape"
)));
}
Ok(dispatched_call(
FunctionDispatch::PatternEscape {
similar: wrapper_name == "similar_to_escape",
},
function
.args
.iter()
.map(compile_expr)
.collect::<Result<Vec<_>>>()?,
))
}
#[expect(
clippy::too_many_lines,
reason = "ordered PostgreSQL lowering preserves syntax and error precedence"
)]
pub(in crate::compiler) fn compile_a_expr(a: &pg_query::protobuf::AExpr) -> Result<Expr> {
use pg_query::protobuf::AExprKind;
let kind = a.kind();
match kind {
AExprKind::AexprOp => {
let names = extract_strings(&a.name)?;
let op_name = match names.as_slice() {
[schema, operator] if schema == "pg_catalog" => operator.clone(),
_ => names.join("."),
};
if a.lexpr.is_none() {
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("AExpr missing rhs".into()))?;
let rhs = compile_expr(rhs)?;
let numeric_operator = |operator, argument| {
dispatched_call(FunctionDispatch::NumericOperator(operator), vec![argument])
};
return match op_name.as_str() {
"+" => Ok(numeric_operator(NumericOperator::Plus, rhs)),
"-" => Ok(Expr::UnaryMinus(Box::new(rhs))),
"|/" => Ok(numeric_operator(NumericOperator::SquareRoot, rhs)),
"||/" => Ok(numeric_operator(NumericOperator::CubeRoot, rhs)),
"@" => Ok(numeric_operator(NumericOperator::Absolute, rhs)),
other => Err(SQLError::Unsupported(format!("unary operator `{other}`"))),
};
}
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("AExpr missing lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("AExpr missing rhs".into()))?;
let op = match op_name.as_str() {
"=" => BinaryOp::Equal,
"<>" | "!=" => BinaryOp::NotEqual,
"<" => BinaryOp::Less,
"<=" => BinaryOp::LessEqual,
">" => BinaryOp::Greater,
">=" => BinaryOp::GreaterEqual,
"+" => BinaryOp::Add,
"-" => BinaryOp::Subtract,
"*" => BinaryOp::Multiply,
"/" => BinaryOp::Divide,
"||" => {
return Ok(dispatched_call(
FunctionDispatch::Concat { oid: None },
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
"@@" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "fts_match".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"@?" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "jsonpath_exists".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"%" | "^" => {
let operator = if op_name == "%" {
NumericOperator::Modulo
} else {
NumericOperator::Power
};
return Ok(dispatched_call(
FunctionDispatch::NumericOperator(operator),
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
"~" | "~*" | "!~" | "!~*" | "~~" | "!~~" | "~~*" | "!~~*" => {
return Ok(dispatched_call(
FunctionDispatch::Pattern {
operator: PatternOperator::from_symbol(&op_name)
.expect("pattern symbol"),
oid: None,
},
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
"&&" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "array_overlap".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"-|-" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "range_adjacent".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"->" | "->>" => {
return Ok(dispatched_call(
FunctionDispatch::JsonExtract {
as_text: op_name == "->>",
path: false,
},
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
"#>" | "#>>" => {
return Ok(dispatched_call(
FunctionDispatch::JsonExtract {
as_text: op_name == "#>>",
path: true,
},
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
"#-" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "json_delete_path".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"@>" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "contains_op".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"<@" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "contained_by_op".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"?" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "json_has_key".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"?|" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "json_has_any_key".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
"?&" => {
return Ok(Expr::Func {
order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
binding: None,
name: "json_has_all_keys".into(),
args: vec![compile_expr(lhs)?, compile_expr(rhs)?],
distinct: false,
order_by: Vec::new(),
filter: None,
});
}
other => return Err(SQLError::Unsupported(format!("operator `{other}`"))),
};
Ok(Expr::Binary {
op,
lhs: Box::new(compile_expr(lhs)?),
rhs: Box::new(compile_expr(rhs)?),
})
}
AExprKind::AexprBetween
| AExprKind::AexprNotBetween
| AExprKind::AexprBetweenSym
| AExprKind::AexprNotBetweenSym => compile_between(a),
AExprKind::AexprDistinct | AExprKind::AexprNotDistinct => {
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("IS DISTINCT FROM without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("IS DISTINCT FROM without rhs".into()))?;
let left = compile_expr(lhs)?;
let right = compile_expr(rhs)?;
if matches!(left, Expr::Literal(Value::Null))
|| matches!(right, Expr::Literal(Value::Null))
{
let argument = if matches!(right, Expr::Literal(Value::Null)) {
left
} else {
right
};
return Ok(dispatched_call(
FunctionDispatch::ValueNullTest {
negated: matches!(kind, AExprKind::AexprDistinct),
},
vec![argument],
));
}
let call = dispatched_call(FunctionDispatch::IsDistinct, vec![left, right]);
Ok(if matches!(kind, AExprKind::AexprNotDistinct) {
Expr::Not(Box::new(call))
} else {
call
})
}
AExprKind::AexprSimilar => {
let op_name = extract_strings(&a.name)?.join("");
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("SIMILAR TO without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("SIMILAR TO without rhs".into()))?;
let args = vec![
compile_expr(lhs)?,
compile_pattern_operand(rhs, "similar_to_escape", "SIMILAR TO", a.location)?,
];
let operator = match op_name.as_str() {
"~" => PatternOperator::Regex,
"!~" => PatternOperator::NotRegex,
other => {
return Err(SQLError::Internal(format!(
"SIMILAR TO has unexpected operator `{other}`"
)))
}
};
Ok(dispatched_call(
FunctionDispatch::Pattern {
operator,
oid: None,
},
args,
))
}
AExprKind::AexprOpAny | AExprKind::AexprOpAll => {
let op_name = extract_strings(&a.name)?.join("");
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("ANY/ALL without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("ANY/ALL without rhs".into()))?;
let dispatch = if matches!(kind, AExprKind::AexprOpAny) {
FunctionDispatch::AnyOperator
} else {
FunctionDispatch::AllOperator
};
Ok(dispatched_call(
dispatch,
vec![
compile_expr(lhs)?,
compile_expr(rhs)?,
Expr::Literal(Value::Str(op_name)),
],
))
}
AExprKind::AexprNullif => {
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("NULLIF without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("NULLIF without rhs".into()))?;
return Ok(builtin_syntax_call(
"nullif",
vec![compile_expr(lhs)?, compile_expr(rhs)?],
));
}
AExprKind::AexprLike | AExprKind::AexprIlike => {
let op_name = extract_strings(&a.name)?.join("");
let operator = PatternOperator::from_symbol(&op_name)
.filter(|operator| operator.is_like())
.ok_or_else(|| {
SQLError::Internal(format!("LIKE has unexpected operator `{op_name}`"))
})?;
let lhs = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("LIKE without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("LIKE without rhs".into()))?;
let args = vec![
compile_expr(lhs)?,
compile_pattern_operand(rhs, "like_escape", "LIKE", a.location)?,
];
Ok(dispatched_call(
FunctionDispatch::Pattern {
operator,
oid: None,
},
args,
))
}
AExprKind::AexprIn => {
let expr = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("IN without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("IN without rhs".into()))?;
let items = match rhs.node.as_ref() {
Some(NodeEnum::List(l)) => l.items.clone(),
_ => return Err(SQLError::Internal("IN expects list".into())),
};
let list: Vec<Expr> = items.iter().map(compile_expr).collect::<Result<Vec<_>>>()?;
let operator = extract_strings(&a.name)?.join("");
let negated = match operator.as_str() {
"=" => false,
"<>" => true,
other => {
return Err(SQLError::Internal(format!(
"IN has unexpected operator `{other}`"
)));
}
};
Ok(Expr::InList {
expr: Box::new(compile_expr(expr)?),
list,
negated,
})
}
other => Err(SQLError::Unsupported(format!("AExpr kind: {other:?}"))),
}
}
pub(in crate::compiler) fn compile_bool_expr(b: &pg_query::protobuf::BoolExpr) -> Result<Expr> {
use pg_query::protobuf::BoolExprType;
let kind = b.boolop();
let args: Vec<Expr> = b
.args
.iter()
.map(compile_expr)
.collect::<Result<Vec<_>>>()?;
match kind {
BoolExprType::AndExpr if args.len() >= 2 => Ok(Expr::And(args)),
BoolExprType::OrExpr if args.len() >= 2 => Ok(Expr::Or(args)),
BoolExprType::AndExpr | BoolExprType::OrExpr => Err(SQLError::Internal(format!(
"{kind:?} requires at least two operands, got {}",
args.len()
))),
BoolExprType::NotExpr => {
let [arg] = args.as_slice() else {
return Err(SQLError::Internal(format!(
"NOT requires exactly one operand, got {}",
args.len()
)));
};
Ok(Expr::Not(Box::new(arg.clone())))
}
_ => Err(SQLError::Unsupported(format!("BoolExpr {kind:?}"))),
}
}
pub(in crate::compiler) fn compile_null_test(n: &pg_query::protobuf::NullTest) -> Result<Expr> {
use pg_query::protobuf::NullTestType;
let arg = n
.arg
.as_ref()
.ok_or_else(|| SQLError::Internal("NullTest without arg".into()))?;
let negated = match n.nulltesttype() {
NullTestType::IsNull => false,
NullTestType::IsNotNull => true,
other => {
return Err(SQLError::Internal(format!(
"NullTest has invalid kind {other:?}"
)));
}
};
Ok(Expr::IsNull {
expr: Box::new(compile_expr(arg)?),
negated,
})
}
fn compile_between(a: &pg_query::protobuf::AExpr) -> Result<Expr> {
use pg_query::protobuf::AExprKind;
let value = a
.lexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("BETWEEN without lhs".into()))?;
let rhs = a
.rexpr
.as_ref()
.ok_or_else(|| SQLError::Internal("BETWEEN without rhs".into()))?;
let Some(NodeEnum::List(bounds)) = rhs.node.as_ref() else {
return Err(SQLError::Internal("BETWEEN expects 2 bounds".into()));
};
let [low, high] = bounds.items.as_slice() else {
return Err(SQLError::Internal("BETWEEN expects 2 bounds".into()));
};
let negated = matches!(
a.kind(),
AExprKind::AexprNotBetween | AExprKind::AexprNotBetweenSym
);
crate::type_resolution::between::expand(
matches!(
a.kind(),
AExprKind::AexprBetweenSym | AExprKind::AexprNotBetweenSym
),
negated,
|op, upper| {
Ok(Expr::Binary {
op,
lhs: Box::new(compile_expr(value)?),
rhs: Box::new(compile_expr(if upper { high } else { low })?),
})
},
|and, items| {
if and {
Expr::And(items)
} else {
Expr::Or(items)
}
},
)
}