use super::{
Oid, Resolved, Spi, Sql, Walker, collect_functions, cstr, elements, pg_sys, quote_if_needed,
quote_literal, tag, term_sql,
};
impl Walker<'_> {
pub(super) unsafe fn deparse(
&mut self,
expr: *mut pg_sys::Node,
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
unsafe {
match tag(expr)? {
pg_sys::NodeTag::T_Var => {
let term = term_of(expr)?;
next_var();
term_sql(&term)
}
pg_sys::NodeTag::T_Param => term_sql(&term_of(expr)?),
pg_sys::NodeTag::T_Const => self.deparse_const(expr.cast()),
pg_sys::NodeTag::T_OpExpr => {
let op = expr.cast::<pg_sys::OpExpr>();
let args = elements::<pg_sys::Node>((*op).args);
self.deparse_operator((*op).opno, &args, term_of, next_var)
}
pg_sys::NodeTag::T_ScalarArrayOpExpr => {
self.deparse_array_operator(expr.cast(), term_of, next_var)
}
pg_sys::NodeTag::T_FuncExpr => {
self.deparse_function(expr.cast(), term_of, next_var)
}
pg_sys::NodeTag::T_RelabelType => {
let r = expr.cast::<pg_sys::RelabelType>();
self.deparse_cast((*r).arg.cast(), (*r).resulttype, term_of, next_var)
}
pg_sys::NodeTag::T_ArrayCoerceExpr => {
let r = expr.cast::<pg_sys::ArrayCoerceExpr>();
self.deparse_cast((*r).arg.cast(), (*r).resulttype, term_of, next_var)
}
pg_sys::NodeTag::T_CoerceViaIO => {
let r = expr.cast::<pg_sys::CoerceViaIO>();
self.deparse_cast((*r).arg.cast(), (*r).resulttype, term_of, next_var)
}
pg_sys::NodeTag::T_BoolExpr => self.deparse_bool(expr.cast(), term_of, next_var),
_ => None,
}
}
}
unsafe fn deparse_const(&mut self, c: *mut pg_sys::Const) -> Option<Sql> {
unsafe {
let ty = self.type_name((*c).consttype);
if (*c).constisnull {
return Some(Sql::text(format!("NULL::{ty}")));
}
let mut output: Oid = Oid::INVALID;
let mut varlena = false;
pg_sys::getTypeOutputInfo((*c).consttype, &raw mut output, &raw mut varlena);
let text = cstr(pg_sys::OidOutputFunctionCall(output, (*c).constvalue));
Some(Sql::text(format!("{}::{ty}", quote_literal(&text)?)))
}
}
unsafe fn deparse_operator(
&mut self,
opno: Oid,
args: &[*mut pg_sys::Node],
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
let name = self.operator_name(opno);
let mut sql = Sql::text("(");
unsafe {
match *args {
[arg] => {
sql.push_text(&format!("OPERATOR({name}) "));
sql.push_sql(self.deparse(arg, term_of, next_var)?);
}
[l, r] => {
sql.push_sql(self.deparse(l, term_of, next_var)?);
sql.push_text(&format!(" OPERATOR({name}) "));
sql.push_sql(self.deparse(r, term_of, next_var)?);
}
_ => return None,
}
}
sql.push_text(")");
Some(sql)
}
unsafe fn deparse_array_operator(
&mut self,
op: *mut pg_sys::ScalarArrayOpExpr,
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
unsafe {
let name = self.operator_name((*op).opno);
let [l, r] = elements::<pg_sys::Node>((*op).args)[..] else {
return None;
};
let mut sql = Sql::text("(");
sql.push_sql(self.deparse(l, term_of, next_var)?);
sql.push_text(&format!(
" OPERATOR({name}) {} (",
if (*op).useOr { "ANY" } else { "ALL" }
));
sql.push_sql(self.deparse(r, term_of, next_var)?);
sql.push_text("))");
Some(sql)
}
}
unsafe fn deparse_function(
&mut self,
f: *mut pg_sys::FuncExpr,
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
unsafe {
let args = elements::<pg_sys::Node>((*f).args);
if (*f).funcformat != pg_sys::CoercionForm::COERCE_EXPLICIT_CALL
&& (*f).funcformat != pg_sys::CoercionForm::COERCE_SQL_SYNTAX
{
let [arg, ..] = args[..] else { return None };
return self.deparse_cast(arg, (*f).funcresulttype, term_of, next_var);
}
let (name, _) = self.function((*f).funcid);
let mut sql = Sql::text(format!("{name}("));
for (i, arg) in args.into_iter().enumerate() {
if i > 0 {
sql.push_text(", ");
}
sql.push_sql(self.deparse(arg, term_of, next_var)?);
}
sql.push_text(")");
Some(sql)
}
}
unsafe fn deparse_cast(
&mut self,
arg: *mut pg_sys::Node,
ty: Oid,
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
let mut sql = Sql::text("(");
sql.push_sql(unsafe { self.deparse(arg, term_of, next_var)? });
sql.push_text(&format!(")::{}", self.type_name(ty)));
Some(sql)
}
unsafe fn deparse_bool(
&mut self,
b: *mut pg_sys::BoolExpr,
term_of: &dyn Fn(*mut pg_sys::Node) -> Option<Resolved>,
next_var: &mut dyn FnMut(),
) -> Option<Sql> {
unsafe {
let args = elements::<pg_sys::Node>((*b).args);
let mut sql = Sql::text("(");
if (*b).boolop == pg_sys::BoolExprType::NOT_EXPR {
sql.push_text("NOT ");
sql.push_sql(self.deparse(*args.first()?, term_of, next_var)?);
} else {
let joiner = if (*b).boolop == pg_sys::BoolExprType::AND_EXPR {
" AND "
} else {
" OR "
};
for (i, arg) in args.into_iter().enumerate() {
if i > 0 {
sql.push_text(joiner);
}
sql.push_sql(self.deparse(arg, term_of, next_var)?);
}
}
sql.push_text(")");
Some(sql)
}
}
pub(super) fn type_name(&mut self, ty: Oid) -> String {
self.catalog
.types
.entry(ty.to_u32())
.or_insert_with(|| cstr(unsafe { pg_sys::format_type_be_qualified(ty) }))
.clone()
}
pub(super) fn operator_name(&mut self, opno: Oid) -> String {
self.catalog
.operators
.entry(opno.to_u32())
.or_insert_with(|| {
Spi::get_one_with_args::<String>(
"SELECT pg_catalog.quote_ident(n.nspname) || '.' || o.oprname \
FROM pg_catalog.pg_operator o \
JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace \
WHERE o.oid = $1",
&[crate::utils::spi::oid(opno)],
)
.ok()
.flatten()
.unwrap_or_default()
})
.clone()
}
pub(super) fn function(&mut self, funcid: Oid) -> (String, bool) {
self.catalog
.functions
.entry(funcid.to_u32())
.or_insert_with(|| {
unsafe {
let name = cstr(pg_sys::get_func_name(funcid));
let nsp = cstr(pg_sys::get_namespace_name(pg_sys::get_func_namespace(
funcid,
)));
let immutable = pg_sys::func_volatile(funcid)
== pg_sys::PROVOLATILE_IMMUTABLE.cast_signed();
(
format!("{}.{}", quote_if_needed(&nsp), quote_if_needed(&name)),
immutable,
)
}
})
.clone()
}
pub(super) unsafe fn note_functions(&mut self, query: *mut pg_sys::Node) {
let mut funcids = Vec::new();
unsafe { collect_functions(query, &mut funcids) };
for funcid in funcids {
let (name, immutable) = self.function(funcid);
if !immutable
&& !name.starts_with("pg_catalog.")
&& !self.graph.untracked_functions.contains(&funcid.to_u32())
{
self.graph.untracked_functions.push(funcid.to_u32());
}
}
}
}