use super::{
ColumnType, Expr, Interpreter, PLpgSQLDatum, PLpgSQLReturnValue, RoutineOutcome, SQLError,
TriggerRoutineContext, Value,
};
impl Interpreter<'_> {
pub(super) fn expression_type(&self, expr: &Expr) -> Result<Option<ColumnType>, SQLError> {
let bound = super::bind_expr(expr, &mut self.resolver())?;
let plan = uqa_sql::plan::ExpressionPlan::lower(bound);
self.services.expressions.expression_type(&plan)
}
pub(super) fn datum_type(&self, index: usize) -> Option<ColumnType> {
self.resolver().datum_type(index)
}
pub(super) fn record_expression_types(
&self,
expr: &Expr,
) -> Result<Option<Vec<Option<ColumnType>>>, SQLError> {
match expr {
Expr::Column(name) | Expr::QualifiedStar(name) => Ok(self
.resolver()
.lookup(name)
.and_then(|index| self.record_types.get(&index).cloned())),
Expr::Row(fields) => fields
.iter()
.map(|field| self.expression_type(field))
.collect::<Result<Vec<_>, _>>()
.map(Some),
Expr::Cast { expr, ty } if ty == "record" => self.record_expression_types(expr),
_ => Ok(None),
}
}
pub(super) fn return_record_types(
&self,
value: &PLpgSQLReturnValue,
) -> Result<Option<Vec<Option<ColumnType>>>, SQLError> {
match value {
PLpgSQLReturnValue::Expr(expr) => self.record_expression_types(expr),
PLpgSQLReturnValue::Datum(index) => match &self.datums[*index] {
PLpgSQLDatum::Row { fields } => Ok(Some(
fields
.iter()
.map(|field| self.datum_type(field.varno))
.collect(),
)),
_ => Ok(self.record_types.get(index).cloned()),
},
}
}
pub(super) fn return_value_type(
&self,
value: &PLpgSQLReturnValue,
) -> Result<Option<ColumnType>, SQLError> {
match value {
PLpgSQLReturnValue::Expr(expr) => self.expression_type(expr),
PLpgSQLReturnValue::Datum(index) => Ok(self.datum_type(*index)),
}
}
}
pub fn shape_trigger_outcome(
outcome: RoutineOutcome,
context: &TriggerRoutineContext,
) -> Result<Value, SQLError> {
let values = match outcome.value {
Value::Null => return Ok(Value::Null),
Value::Record(fields) => fields
.into_iter()
.map(|(_, value)| value)
.collect::<Vec<_>>(),
Value::Row(values) => values,
_ => return Err(trigger_shape_error()),
};
let types = outcome
.anonymous_record_column_types
.unwrap_or_else(|| values.iter().map(runtime_record_column_type).collect());
if values.len() != context.column_types.len()
|| types.len() != context.column_types.len()
|| types
.iter()
.zip(&context.column_types)
.any(|(source, target)| !record_types_match(source.as_ref(), target.as_ref()))
{
return Err(trigger_shape_error());
}
let fields = match (&context.new, &context.old) {
(Value::Record(fields), _) | (_, Value::Record(fields)) => fields,
_ => return Err(trigger_shape_error()),
};
Ok(Value::Record(
fields
.iter()
.map(|(name, _)| name.clone())
.zip(values)
.collect(),
))
}
fn record_types_match(source: Option<&ColumnType>, target: Option<&ColumnType>) -> bool {
match (source, target) {
(
Some(ColumnType::Domain { oid: source, .. }),
Some(ColumnType::Domain { oid: target, .. }),
) => source == target,
(Some(ColumnType::Domain { .. }), _) | (_, Some(ColumnType::Domain { .. })) => false,
(Some(source), Some(target)) => {
let source = uqa_sql::catalog::result_type::postgres_result_type(source);
let target = uqa_sql::catalog::result_type::postgres_result_type(target);
source.type_oid == target.type_oid
&& (target.type_modifier < 0 || source.type_modifier == target.type_modifier)
}
_ => false,
}
}
fn trigger_shape_error() -> SQLError {
SQLError::Routine {
sqlstate: "42804".into(),
message: "returned row structure does not match the structure of the triggering table"
.into(),
}
}
fn runtime_record_column_type(value: &Value) -> Option<ColumnType> {
if matches!(value, Value::Null) {
None
} else {
ColumnType::from_sql_name(uqa_sql::expr::value_type_name(value)).ok()
}
}