#![allow(clippy::cast_ptr_alignment)]
use super::{
Column, Conjunct, DataEmbed, DataField, DataShape, IdentityKind, Lookup, Maps, Occurrence,
OutputColumn, Piece, QueryGraph, Root, Scope, Sql, WalkedIdentity,
};
use crate::error::{TViewError, TViewResult};
use pgrx::pg_sys::{self, Oid};
use pgrx::prelude::*;
use std::collections::{HashMap, HashSet};
use std::ffi::CStr;
pub const MAX_DEPTH: usize = 32;
pub(super) fn index(n: impl TryInto<usize>) -> usize {
n.try_into().unwrap_or(0)
}
pub struct Context<'a> {
pub tview_tables: &'a HashMap<Oid, String>,
pub tview_views: &'a HashMap<Oid, String>,
pub entity: &'a str,
pub key_column: &'a str,
}
pub fn analyze(view_oid: Oid, ctx: &Context<'_>) -> TViewResult<QueryGraph> {
let mut walker = Walker {
ctx,
graph: QueryGraph::default(),
levels: Vec::new(),
catalog: CatalogNames::default(),
cte_parent: None,
read_ctes: HashSet::new(),
wanted: None,
identity_level: false,
nullable: HashSet::new(),
unions: 0,
};
let query = unsafe { view_query(view_oid)? };
let flags = Flags::default();
unsafe { walker.top(query, &flags)? };
walker.note_virtual_columns();
Ok(walker.graph)
}
unsafe fn view_query(view_oid: Oid) -> TViewResult<*mut pg_sys::Query> {
unsafe {
let rel = pg_sys::try_relation_open(view_oid, pg_sys::AccessShareLock.cast_signed());
if rel.is_null() {
return Err(TViewError::CatalogError {
operation: format!("Open view {view_oid:?}"),
pg_error: "relation does not exist".to_string(),
});
}
if (*(*rel).rd_rel).relkind != pg_sys::RELKIND_VIEW.cast_signed() {
pg_sys::relation_close(rel, pg_sys::AccessShareLock.cast_signed());
return Err(TViewError::CatalogError {
operation: format!("Read the query of view {view_oid:?}"),
pg_error: "relation is not a view".to_string(),
});
}
let query = pg_sys::get_view_query(rel);
let copy = pg_sys::copyObjectImpl(query.cast()).cast::<pg_sys::Query>();
pg_sys::relation_close(rel, pg_sys::NoLock.cast_signed());
Ok(copy)
}
}
#[derive(Debug, Clone, Default)]
struct Flags {
unions: Scope,
via_view: Option<String>,
via_tview: Option<String>,
in_sublink: bool,
opaque_level: Option<String>,
unread: bool,
}
#[derive(Debug, Clone)]
enum Resolved {
Col(Column),
Alt(Vec<Self>, Vec<Scope>),
Expr(Computed),
Inbound(Column),
Opaque,
}
#[derive(Debug, Clone)]
struct Computed {
sql: Sql,
element: bool,
strict: bool,
type_oid: Oid,
}
impl Resolved {
fn occs(&self) -> Vec<usize> {
match self {
Self::Col(c) | Self::Inbound(c) => vec![c.occ],
Self::Alt(terms, _) => terms.iter().flat_map(Self::occs).collect(),
Self::Expr(e) => sql_occs(&e.sql),
Self::Opaque => vec![],
}
}
}
fn sql_occs(sql: &Sql) -> Vec<usize> {
let mut occs = Vec::new();
for piece in &sql.0 {
if let Piece::Column { occ, .. } = piece
&& !occs.contains(occ)
{
occs.push(*occ);
}
}
occs
}
#[derive(Debug, Clone)]
enum RteInfo {
Base(usize),
Outputs(Vec<Resolved>),
Join(*mut pg_sys::List),
Tview {
entity: String,
relid: Oid,
occ: usize,
},
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Link {
Top,
From,
Sublink {
required: bool,
},
}
struct Level {
query: *mut pg_sys::Query,
rtes: Vec<RteInfo>,
link: Link,
cte_parent: Option<usize>,
}
#[derive(Clone, Copy)]
enum Origin<'a> {
Required,
Outer { nullable: &'a HashSet<usize> },
None,
}
struct Site {
levelsup: usize,
candidates: Vec<Resolved>,
}
struct Operand {
sql: Sql,
type_oid: Oid,
column: bool,
element: bool,
}
#[derive(Default)]
struct CatalogNames {
operators: HashMap<u32, String>,
functions: HashMap<u32, (String, bool)>,
types: HashMap<u32, String>,
}
struct Walker<'c> {
ctx: &'c Context<'c>,
graph: QueryGraph,
levels: Vec<Level>,
catalog: CatalogNames,
cte_parent: Option<usize>,
read_ctes: HashSet<(usize, String)>,
wanted: Option<HashSet<i16>>,
identity_level: bool,
nullable: HashSet<usize>,
unions: usize,
}
fn cstr(ptr: *const std::ffi::c_char) -> String {
if ptr.is_null() {
return String::new();
}
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
}
unsafe fn elements<T>(list: *mut pg_sys::List) -> Vec<*mut T> {
if list.is_null() {
return Vec::new();
}
unsafe {
(0..(*list).length)
.map(|i| pg_sys::list_nth(list, i).cast::<T>())
.collect()
}
}
unsafe fn tag(node: *const pg_sys::Node) -> Option<pg_sys::NodeTag> {
unsafe { (!node.is_null()).then(|| (*node).type_) }
}
unsafe fn conjuncts(node: *mut pg_sys::Node) -> Vec<*mut pg_sys::Node> {
unsafe {
match tag(node) {
None => Vec::new(),
Some(pg_sys::NodeTag::T_List) => elements::<pg_sys::Node>(node.cast())
.into_iter()
.flat_map(|n| conjuncts(n))
.collect(),
Some(pg_sys::NodeTag::T_BoolExpr)
if (*node.cast::<pg_sys::BoolExpr>()).boolop == pg_sys::BoolExprType::AND_EXPR =>
{
elements::<pg_sys::Node>((*node.cast::<pg_sys::BoolExpr>()).args)
.into_iter()
.flat_map(|n| conjuncts(n))
.collect()
}
Some(_) => vec![node],
}
}
}
impl Clone for Level {
fn clone(&self) -> Self {
Self {
query: self.query,
rtes: self.rtes.clone(),
link: self.link,
cte_parent: self.cte_parent,
}
}
}
fn list_len(list: *mut pg_sys::List) -> usize {
if list.is_null() {
0
} else {
usize::try_from(unsafe { (*list).length }).unwrap_or(0)
}
}
mod collect;
mod data;
mod deparse;
mod expr;
mod level;
mod nodes;
mod predicate;
mod time;
use collect::{
base_read, collect_functions, collect_params, collect_sublinks, collect_vars,
column_read_counts, const_text, has_sublink, referenced_columns, relation_entry,
};
use nodes::{
Cast, WINDOW_REASON, equality, in_clause, is_required_sublink, opaque_reason, output_position,
quote_ident, quote_literal, setop_leaves, strip_casts, strip_relabel, term_sql,
top_opaque_reason, unnest_array, windows_partitioned,
};
#[cfg(test)]
mod tests {
use super::time::time_value_name;
use pgrx::pg_sys::SQLValueFunctionOp as Op;
#[test]
fn the_time_value_functions_are_named_as_written() {
assert_eq!(
time_value_name(Op::SVFOP_CURRENT_DATE),
Some("CURRENT_DATE")
);
assert_eq!(
time_value_name(Op::SVFOP_CURRENT_TIMESTAMP_N),
Some("CURRENT_TIMESTAMP")
);
assert_eq!(time_value_name(Op::SVFOP_LOCALTIME_N), Some("LOCALTIME"));
assert_eq!(time_value_name(Op::SVFOP_CURRENT_USER), None);
assert_eq!(time_value_name(Op::SVFOP_CURRENT_SCHEMA), None);
}
}