use std::sync::Arc;
use uqa_core::Value;
use super::{EngineHook, Result, SQLError};
use crate::ast::ColumnType;
mod changes;
pub mod constants;
pub mod constructor;
mod input;
pub mod literal;
pub use changes::{apply_attribute_change, type_contains_composite, AttributeChange};
pub use input::parse_record_fields;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompositeAttribute {
pub name: String,
pub ty: ColumnType,
pub number: i16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompositeTypeDescriptor {
pub type_oid: u32,
pub relation_oid: u32,
pub attributes: Vec<CompositeAttribute>,
}
impl CompositeTypeDescriptor {
pub fn attribute(&self, name: &str) -> Option<(usize, &CompositeAttribute)> {
self.attributes
.iter()
.enumerate()
.find(|(_, attribute)| attribute.name == name)
}
}
pub trait CompositeTypeCatalog {
fn composite_type(&self, type_oid: u32) -> Result<Option<Arc<CompositeTypeDescriptor>>>;
}
pub fn descriptor(
catalog: Option<&dyn CompositeTypeCatalog>,
type_oid: u32,
) -> Result<Arc<CompositeTypeDescriptor>> {
catalog
.map(|catalog| catalog.composite_type(type_oid))
.transpose()?
.flatten()
.ok_or_else(|| {
SQLError::Internal(format!(
"composite type OID {type_oid} is not available in the statement catalog"
))
})
}
fn cannot_cast(source: &str, target: &str, detail: Option<String>) -> SQLError {
SQLError::Diagnostic {
sqlstate: "42846".into(),
message: format!("cannot cast type {source} to {target}"),
detail,
hint: None,
}
}
fn string_category(ty: &ColumnType) -> bool {
match ty {
ColumnType::Text
| ColumnType::Varchar(_)
| ColumnType::Bpchar
| ColumnType::Character(_)
| ColumnType::Name => true,
ColumnType::Domain { base, .. } => string_category(base),
_ => false,
}
}
fn attribute_input(
engine: &dyn EngineHook,
text: Option<String>,
ty: &ColumnType,
) -> Result<Value> {
let value = text.map_or(Value::Null, Value::Str);
if matches!(value, Value::Null) && !matches!(ty, ColumnType::Domain { .. }) {
return Ok(Value::Null);
}
if crate::assignment::conversion::type_requires_catalog_resolution(ty) {
return super::cast_value_with_type_resolution(
&value,
Some("unknown"),
&ty.catalog_name(),
Some(engine),
);
}
crate::assignment::conversion::convert_value_to_column_type(value, ty)
}
pub fn composite_from_text(engine: &dyn EngineHook, text: &str, type_oid: u32) -> Result<Value> {
let descriptor = descriptor(engine.composite_types(), type_oid)?;
let mut fields = Vec::with_capacity(descriptor.attributes.len());
parse_record_fields(text, descriptor.attributes.len(), |index, field| {
let attribute = &descriptor.attributes[index];
fields.push((
attribute.name.clone(),
attribute_input(engine, field, &attribute.ty)?,
));
Ok(())
})?;
Ok(Value::Record(fields))
}
fn composite_from_fields(
engine: &dyn EngineHook,
values: &[Value],
descriptor: &CompositeTypeDescriptor,
target: &str,
) -> Result<Value> {
if values.len() != descriptor.attributes.len() {
let detail = if values.len() < descriptor.attributes.len() {
"Input has too few columns."
} else {
"Input has too many columns."
};
return Err(cannot_cast("record", target, Some(detail.into())));
}
values
.iter()
.zip(&descriptor.attributes)
.map(|(value, attribute)| {
let converted = super::cast_value_with_type_resolution(
value,
None,
&attribute.ty.catalog_name(),
Some(engine),
)?;
Ok((attribute.name.clone(), converted))
})
.collect::<Result<Vec<_>>>()
.map(Value::Record)
}
pub fn cast_to_composite(
engine: &dyn EngineHook,
value: &Value,
source: Option<&ColumnType>,
target: &ColumnType,
) -> Result<Option<Value>> {
let ColumnType::Composite(reference) = target else {
return Ok(None);
};
let target_name = || target.display_name();
match (value, source) {
(Value::Null, _) => Ok(Some(Value::Null)),
(Value::Str(text) | Value::FixedChar(text), source)
if source.is_none_or(string_category) =>
{
composite_from_text(engine, text, reference.oid).map(Some)
}
(Value::Record(_), Some(ColumnType::Composite(source))) if source.oid == reference.oid => {
Ok(Some(value.clone()))
}
(Value::Record(_) | Value::Row(_), Some(source @ ColumnType::Composite(_))) => {
Err(cannot_cast(&source.display_name(), &target_name(), None))
}
(Value::Row(values), _) => {
let descriptor = descriptor(engine.composite_types(), reference.oid)?;
composite_from_fields(engine, values, &descriptor, &target_name()).map(Some)
}
(Value::Record(fields), _) => {
let descriptor = descriptor(engine.composite_types(), reference.oid)?;
let values = fields
.iter()
.map(|(_, value)| value.clone())
.collect::<Vec<_>>();
composite_from_fields(engine, &values, &descriptor, &target_name()).map(Some)
}
(_, source) => Err(cannot_cast(
&source.map_or_else(|| "unknown".into(), ColumnType::display_name),
&target_name(),
None,
)),
}
}