use super::{Column, Oid, Resolved, Sql, cstr, elements, list_len, pg_sys, tag};
pub(super) const WINDOW_REASON: &str = "read under a window function";
pub(super) unsafe fn opaque_reason(query: *mut pg_sys::Query, partitioned: bool) -> Option<String> {
unsafe {
if (*query).hasWindowFuncs && !partitioned {
Some(WINDOW_REASON.to_string())
} else if !(*query).limitCount.is_null() || !(*query).limitOffset.is_null() {
Some("read under LIMIT/OFFSET".to_string())
} else if !(*query).groupingSets.is_null() {
Some("read under GROUPING SETS".to_string())
} else {
None
}
}
}
pub(super) unsafe fn windows_partitioned(query: *mut pg_sys::Query) -> bool {
unsafe {
let windows = elements::<pg_sys::WindowClause>((*query).windowClause);
(*query).hasWindowFuncs
&& !windows.is_empty()
&& windows.iter().all(|w| list_len((**w).partitionClause) > 0)
}
}
pub(super) unsafe fn top_opaque_reason(query: *mut pg_sys::Query) -> Option<String> {
unsafe {
opaque_reason(query, false)
.or_else(|| {
(*query)
.hasTargetSRFs
.then(|| "read under a set-returning function".to_string())
})
.map(|why| format!("{why} in the top-level SELECT"))
}
}
pub(super) unsafe fn output_position(query: *mut pg_sys::Query, name: &str) -> Option<usize> {
unsafe {
elements::<pg_sys::TargetEntry>((*query).targetList)
.iter()
.position(|tle| !(**tle).resjunk && cstr((**tle).resname) == name)
}
}
pub(super) unsafe fn in_clause(sortref: pg_sys::Index, clause: *mut pg_sys::List) -> bool {
sortref != 0
&& unsafe { elements::<pg_sys::SortGroupClause>(clause) }
.iter()
.any(|c| unsafe { (**c).tleSortGroupRef } == sortref)
}
pub(super) unsafe fn setop_leaves(node: *mut pg_sys::Node, leaves: &mut Vec<usize>) {
unsafe {
match tag(node) {
Some(pg_sys::NodeTag::T_RangeTblRef) => {
leaves.push(super::index((*node.cast::<pg_sys::RangeTblRef>()).rtindex));
}
Some(pg_sys::NodeTag::T_SetOperationStmt) => {
let op = node.cast::<pg_sys::SetOperationStmt>();
setop_leaves((*op).larg, leaves);
setop_leaves((*op).rarg, leaves);
}
_ => {}
}
}
}
pub(super) unsafe fn strip_relabel(mut node: *mut pg_sys::Node) -> *mut pg_sys::Node {
unsafe {
while tag(node) == Some(pg_sys::NodeTag::T_RelabelType) {
node = (*node.cast::<pg_sys::RelabelType>()).arg.cast();
}
}
node
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Cast {
pub(super) type_oid: Oid,
pub(super) strict: bool,
}
pub(super) unsafe fn strip_casts(mut node: *mut pg_sys::Node) -> (*mut pg_sys::Node, Vec<Cast>) {
let mut casts = Vec::new();
unsafe {
loop {
let (arg, cast) = match tag(node) {
Some(pg_sys::NodeTag::T_RelabelType) => {
let r = node.cast::<pg_sys::RelabelType>();
(
(*r).arg.cast(),
Cast {
type_oid: (*r).resulttype,
strict: true,
},
)
}
Some(pg_sys::NodeTag::T_CoerceViaIO) => {
let r = node.cast::<pg_sys::CoerceViaIO>();
(
(*r).arg.cast(),
Cast {
type_oid: (*r).resulttype,
strict: true,
},
)
}
Some(pg_sys::NodeTag::T_FuncExpr) => {
let f = node.cast::<pg_sys::FuncExpr>();
let is_cast = matches!(
(*f).funcformat,
pg_sys::CoercionForm::COERCE_EXPLICIT_CAST
| pg_sys::CoercionForm::COERCE_IMPLICIT_CAST
);
let [arg] = elements::<pg_sys::Node>((*f).args)[..] else {
break;
};
if !is_cast {
break;
}
let strict = pg_sys::func_strict((*f).funcid);
(
arg,
Cast {
type_oid: (*f).funcresulttype,
strict,
},
)
}
_ => break,
};
casts.push(cast);
node = arg;
}
}
casts.reverse();
(node, casts)
}
pub(super) unsafe fn is_required_sublink(node: *mut pg_sys::Node) -> bool {
unsafe {
tag(node) == Some(pg_sys::NodeTag::T_SubLink)
&& matches!(
(*node.cast::<pg_sys::SubLink>()).subLinkType,
pg_sys::SubLinkType::EXISTS_SUBLINK | pg_sys::SubLinkType::ANY_SUBLINK
)
}
}
pub(super) fn term_sql(term: &Resolved) -> Option<Sql> {
match term {
Resolved::Col(c) | Resolved::Inbound(c) => Some(c.sql()),
Resolved::Expr(e) if !e.element => {
let mut sql = Sql::text("(");
sql.push_sql(e.sql.clone());
sql.push_text(")");
Some(sql)
}
_ => None,
}
}
pub(super) unsafe fn unnest_array(node: *mut pg_sys::Node) -> Option<*mut pg_sys::Node> {
unsafe {
let node = strip_relabel(node);
if tag(node) != Some(pg_sys::NodeTag::T_FuncExpr) {
return None;
}
let f = node.cast::<pg_sys::FuncExpr>();
if (*f).funcid != Oid::from(pg_sys::F_UNNEST_ANYARRAY) {
return None;
}
let [array] = elements::<pg_sys::Node>((*f).args)[..] else {
return None;
};
Some(array)
}
}
pub(super) unsafe fn equality(
expr: *mut pg_sys::Node,
chosen: &[&Resolved],
) -> Option<(Column, Column)> {
unsafe {
let [Resolved::Col(x), Resolved::Col(y)] = chosen[..] else {
return None;
};
if tag(expr) != Some(pg_sys::NodeTag::T_OpExpr) {
return None;
}
let op = expr.cast::<pg_sys::OpExpr>();
let args = elements::<pg_sys::Node>((*op).args);
let plain = args.len() == 2
&& args.iter().all(|a| {
matches!(
tag(strip_relabel(*a)),
Some(pg_sys::NodeTag::T_Var | pg_sys::NodeTag::T_Param)
)
});
(plain && cstr(pg_sys::get_opname((*op).opno)) == "=").then(|| (x.clone(), y.clone()))
}
}
pub(super) fn quote_ident(name: &str) -> String {
crate::utils::quote_ident(name)
}
pub(super) fn quote_literal(text: &str) -> Option<String> {
let c = std::ffi::CString::new(text).ok()?;
Some(cstr(unsafe { pg_sys::quote_literal_cstr(c.as_ptr()) }))
}