use super::PreparedStatementPlan;
use crate::{
ast::FunctionBinding,
plan::{QueryPlan, RelationalPlan, SourcePlan, UnifiedPlan},
ColumnType, ScalarExpr,
};
use uqa_core::Value;
impl PreparedStatementPlan {
#[must_use]
pub fn has_tracked_executable_dependencies(&self) -> bool {
if self.needs_analysis
|| !self.dependencies.routines.is_empty()
|| !self
.parameter_types
.iter()
.all(|ty| ty.as_ref().is_some_and(independent_type))
{
return false;
}
let Some(schema) = &self.result_schema else {
return false;
};
if !schema
.column_types()
.iter()
.all(|ty| ty.as_ref().is_some_and(independent_type))
|| (0..schema.len()).any(|index| schema.record_fields(index).is_some())
{
return false;
}
let tracked_relations = !self.dependencies.relations.is_empty()
&& self.dependency_snapshot.as_ref().is_some_and(|snapshot| {
snapshot.global_catalog.is_some()
&& self
.dependencies
.relations
.iter()
.all(|oid| snapshot.relations.get(oid).is_some_and(Option::is_some))
});
if !self.dependencies.relations.is_empty() && !tracked_relations {
return false;
}
self.plan
.as_ref()
.is_some_and(|plan| independent_plan(plan, true, tracked_relations))
&& independent_plan(&self.source_plan, false, tracked_relations)
&& independent_plan(&self.logical_plan, false, tracked_relations)
}
}
fn independent_plan(plan: &UnifiedPlan, executable: bool, tracked_relations: bool) -> bool {
match plan {
UnifiedPlan::Query(query) => independent_query(query, executable, tracked_relations),
UnifiedPlan::Command(_) => false,
}
}
fn independent_query(query: &QueryPlan, executable: bool, tracked_relations: bool) -> bool {
if !query.ctes.is_empty() {
return false;
}
let admitted = |expression: &ScalarExpr| independent_expression(expression, executable);
match &query.root {
RelationalPlan::QueryBlock(block) => {
block.from.as_ref().is_none_or(|source| {
tracked_relations && matches!(source, SourcePlan::Table { .. })
}) && block.subqueries.is_empty()
&& block.windows.is_empty()
&& block.locking.is_empty()
&& block
.expressions()
.iter()
.all(|expression| admitted(expression))
}
RelationalPlan::SetOp {
left,
right,
order_by,
limit,
offset,
subqueries,
..
} => {
subqueries.is_empty()
&& independent_query(left, executable, tracked_relations)
&& independent_query(right, executable, tracked_relations)
&& order_by
.iter()
.map(|order| &order.expr)
.chain(limit.as_deref())
.chain(offset.as_deref())
.all(admitted)
}
RelationalPlan::Values { rows, subqueries } => {
subqueries.is_empty() && rows.iter().flatten().all(admitted)
}
}
}
fn independent_expression(expression: &ScalarExpr, executable: bool) -> bool {
let mut independent = true;
expression.visit(&mut |part| {
independent &= match part {
ScalarExpr::Literal(value) => independent_value(value),
ScalarExpr::Cast { ty, .. } => independent_type_name(ty),
ScalarExpr::TypedLiteral {
value,
ty,
bound_type,
parameter_index,
} => {
parameter_index.is_none()
&& independent_value(value)
&& bound_type
.as_ref()
.map_or_else(|| independent_type_name(ty), independent_type)
}
ScalarExpr::Func {
binding,
args,
distinct,
order_by,
filter,
..
} => {
!*distinct
&& order_by.is_empty()
&& filter.is_none()
&& binding.as_ref().map_or(!executable, |binding| {
independent_binding(binding)
&& (!executable
|| (args.len() == binding.argument_types.len()
&& crate::fixed_builtin_return_type(binding)
.is_some_and(|ty| independent_type(&ty))))
})
}
ScalarExpr::Column(_)
| ScalarExpr::QualifiedColumn { .. }
| ScalarExpr::Param(_)
| ScalarExpr::Array(_)
| ScalarExpr::Binary { .. }
| ScalarExpr::UnaryMinus(_)
| ScalarExpr::Not(_)
| ScalarExpr::And(_)
| ScalarExpr::Or(_)
| ScalarExpr::IsNull { .. }
| ScalarExpr::Between { .. }
| ScalarExpr::InList { .. }
| ScalarExpr::Case { .. } => true,
_ => false,
};
});
independent
}
fn independent_binding(binding: &FunctionBinding) -> bool {
binding.builtin
&& binding.object_id.is_none()
&& binding.dispatch.is_none()
&& binding.invocation.is_none()
&& binding.resolution_error.is_none()
&& binding
.argument_types
.iter()
.all(|ty| independent_type_name(ty))
}
fn independent_value(value: &Value) -> bool {
match value {
Value::Null
| Value::Bool(_)
| Value::Int(_)
| Value::Float(_)
| Value::Str(_)
| Value::FixedChar(_)
| Value::Bytes(_)
| Value::Decimal(_)
| Value::Json(_)
| Value::JsonB(_) => true,
Value::Array(array) => array.elements().iter().all(independent_value),
_ => false,
}
}
fn independent_type_name(name: &str) -> bool {
ColumnType::from_sql_name(name).is_ok_and(|ty| independent_type(&ty))
}
fn independent_type(ty: &ColumnType) -> bool {
match ty {
ColumnType::Array(element) => independent_type(element),
ColumnType::SmallInteger
| ColumnType::Integer
| ColumnType::BigInteger
| ColumnType::Boolean
| ColumnType::Text
| ColumnType::Name
| ColumnType::Uuid
| ColumnType::Varchar(_)
| ColumnType::Bpchar
| ColumnType::Character(_)
| ColumnType::Real
| ColumnType::DoublePrecision
| ColumnType::Numeric { .. }
| ColumnType::Json
| ColumnType::JsonB
| ColumnType::Bytea
| ColumnType::InternalChar => true,
_ => false,
}
}
#[cfg(test)]
mod tests;