use super::{datum, descriptor, CompositeTypeDescriptor};
use crate::{ColumnType, SQLError};
use std::{collections::BTreeMap, sync::Arc};
use uqa_core::{ArrayValue, Value};
mod tuple;
#[cfg(test)]
mod tests;
pub(crate) type Descriptors = BTreeMap<u32, Arc<CompositeTypeDescriptor>>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CompositeConstantSource {
pub value: Value,
pub descriptors: Vec<CompositeTypeDescriptor>,
}
impl PartialEq for CompositeConstantSource {
fn eq(&self, other: &Self) -> bool {
self.descriptors == other.descriptors && self.value.has_same_representation(&other.value)
}
}
impl CompositeConstantSource {
pub(crate) fn capture(
value: &Value,
ty: &ColumnType,
catalog: Option<&dyn super::CompositeTypeCatalog>,
enums: Option<&dyn crate::expr::enums::EnumLabelCatalog>,
) -> Result<Self, SQLError> {
let mut descriptors = Descriptors::new();
capture_type(ty, catalog, &mut descriptors)?;
Ok(Self {
value: crate::expr::enums::retain_enum_oids(enums, value)?,
descriptors: descriptors
.into_values()
.map(|value| value.as_ref().clone())
.collect(),
})
}
pub(crate) fn retain_enum_oids(
&mut self,
catalog: Option<&dyn crate::expr::enums::EnumLabelCatalog>,
) -> Result<(), SQLError> {
if crate::expr::enums::has_missing_enum_oid(&self.value) {
self.value = crate::expr::enums::retain_enum_oids(catalog, &self.value)?;
}
Ok(())
}
pub(crate) fn project_type_change(
&self,
ty: &ColumnType,
target: u32,
name: &str,
to: &ColumnType,
catalog: &dyn super::CompositeTypeCatalog,
) -> Result<Value, SQLError> {
let original = self
.descriptors
.iter()
.map(|descriptor| (descriptor.type_oid, Arc::new(descriptor.clone())))
.collect::<Descriptors>();
let mut current = Descriptors::new();
for oid in original.keys() {
if let Some(descriptor) = catalog.composite_type(*oid)? {
current.insert(*oid, descriptor);
}
}
if let Some(descriptor) = current.get_mut(&target) {
if let Some(attribute) = Arc::make_mut(descriptor)
.attributes
.iter_mut()
.find(|attribute| attribute.name == name)
{
attribute.ty = to.clone();
}
}
project_value(&self.value, ty, &original, ¤t, true)
}
}
pub(crate) fn capture_type(
ty: &ColumnType,
catalog: Option<&dyn super::CompositeTypeCatalog>,
output: &mut Descriptors,
) -> Result<(), SQLError> {
match ty {
ColumnType::Domain { base, .. } | ColumnType::Array(base) => {
capture_type(base, catalog, output)
}
ColumnType::Composite(reference) => capture_descriptor(reference.oid, catalog, output),
_ => Ok(()),
}
}
pub(crate) fn capture_descriptor(
oid: u32,
catalog: Option<&dyn super::CompositeTypeCatalog>,
output: &mut Descriptors,
) -> Result<(), SQLError> {
if output.contains_key(&oid) {
return Ok(());
}
let descriptor = descriptor(catalog, oid)?;
output.insert(oid, descriptor.clone());
for attribute in &descriptor.attributes {
capture_type(&attribute.ty, catalog, output)?;
}
Ok(())
}
pub(crate) fn project_value(
value: &Value,
ty: &ColumnType,
original: &Descriptors,
current: &Descriptors,
interpret_datums: bool,
) -> Result<Value, SQLError> {
match (ty, value) {
(_, Value::Null) => Ok(Value::Null),
(ColumnType::Domain { base, .. }, _) => {
project_value(value, base, original, current, interpret_datums)
}
(ColumnType::Composite(reference), Value::Record(fields)) => {
let type_oid = fields.type_oid().unwrap_or(reference.oid);
let (Some(before), Some(after)) = (original.get(&type_oid), current.get(&type_oid))
else {
return Ok(value.clone());
};
if interpret_datums {
if let Some(Value::Record(mut projected)) =
tuple::project(fields, before, after, original)
{
for (attribute, (_, value)) in after.attributes.iter().zip(&mut projected) {
let Some(old) = before
.attributes
.iter()
.find(|old| old.number == attribute.number)
else {
continue;
};
let mut leaf = &old.ty;
while let ColumnType::Domain { base, .. } | ColumnType::Array(base) = leaf {
leaf = base;
}
if matches!(leaf, ColumnType::Composite(_)) {
*value = project_value(value, &old.ty, original, current, true)?;
}
}
return Ok(Value::Record(projected));
}
}
after
.attributes
.iter()
.map(|attribute| {
let retained = before
.attributes
.iter()
.find(|old| old.number == attribute.number)
.and_then(|old| {
fields
.iter()
.find(|(name, _)| *name == old.name)
.map(|(_, value)| (old, value))
});
let value = retained.map_or(Ok(Value::Null), |(old, value)| {
let reinterpreted = interpret_datums
.then(|| datum::reinterpret(value, &old.ty, &attribute.ty))
.flatten();
project_value(
reinterpreted.as_ref().unwrap_or(value),
&attribute.ty,
original,
current,
interpret_datums,
)
})?;
Ok((attribute.name.clone(), value))
})
.collect::<Result<Vec<_>, _>>()
.map(|output| {
Value::Record(uqa_core::RecordValue::from_parts(
output,
Some(fields.type_oid().unwrap_or(reference.oid)),
))
})
}
(ColumnType::Array(element), Value::Array(array)) => {
let values = array
.elements()
.iter()
.map(|value| {
project_array_element(value, element, original, current, interpret_datums)
})
.collect::<Result<Vec<_>, _>>()?;
ArrayValue::with_lower_bounds(values, array.lower_bounds().to_vec())
.map(|converted| converted.with_element_type_oid(array.element_type_oid()))
.map(Value::Array)
.ok_or_else(|| {
SQLError::Internal("retained composite projection changed array shape".into())
})
}
_ => Ok(value.clone()),
}
}
fn project_array_element(
value: &Value,
element: &ColumnType,
original: &Descriptors,
current: &Descriptors,
interpret_datums: bool,
) -> Result<Value, SQLError> {
match value {
Value::List(values) => values
.iter()
.map(|value| project_array_element(value, element, original, current, interpret_datums))
.collect::<Result<Vec<_>, _>>()
.map(Value::List),
value => project_value(value, element, original, current, interpret_datums),
}
}