use crate::{
ast::{AssignmentStep, AssignmentTarget},
ColumnType, SQLError,
};
use std::collections::BTreeMap;
#[cfg(test)]
mod tests;
fn error(code: &str, message: String) -> SQLError {
SQLError::Routine {
sqlstate: code.into(),
message,
}
}
pub fn validate_assignment_type<E>(
target: &AssignmentTarget<E>,
declared: Option<&ColumnType>,
) -> Result<(), SQLError> {
if declared.is_none() && !target.is_whole_column() {
array_assignment_type(target, &ColumnType::Named("unknown".into()))?;
}
Ok(())
}
pub fn validate_repeated_targets<'a, E: 'a>(
targets: impl IntoIterator<Item = &'a AssignmentTarget<E>>,
insert: bool,
) -> Result<(), SQLError> {
let mut seen = BTreeMap::new();
for target in targets {
let whole = target.is_whole_column();
if let Some(previous) = seen.insert(&target.column, whole) {
if whole || previous {
return Err(if insert {
error(
"42701",
format!("column \"{}\" specified more than once", target.column),
)
} else {
error(
"42601",
format!("multiple assignments to same column \"{}\"", target.column),
)
});
}
}
}
Ok(())
}
pub fn array_assignment_type<E>(
target: &AssignmentTarget<E>,
declared: &ColumnType,
) -> Result<ColumnType, SQLError> {
let mut base = declared;
while let ColumnType::Domain { base: inner, .. } = base {
base = inner;
}
if let Some(AssignmentStep::Field(field)) = target.indirection.first() {
return Err(error("42804", format!(
"cannot assign to field \"{field}\" of column \"{}\" because its type {} is not a composite type",
target.column, declared.sql_name()
)));
}
let array = match base {
ColumnType::Array(_) => base.clone(),
ColumnType::Int2Vector => ColumnType::Array(Box::new(ColumnType::SmallInteger)),
ColumnType::OidVector => ColumnType::Array(Box::new(ColumnType::Oid)),
_ => {
return Err(error(
"42804",
format!(
"cannot subscript type {} because it does not support subscripting",
declared.sql_name()
),
))
}
};
if let Some(AssignmentStep::Field(field)) = target
.indirection
.iter()
.find(|step| matches!(step, AssignmentStep::Field(_)))
{
let mut element = &array;
while let ColumnType::Array(inner) = element {
element = inner;
}
return Err(error("42804", format!(
"cannot assign to field \"{field}\" of column \"{}\" because its type {} is not a composite type",
target.column, element.sql_name()
)));
}
if target.indirection.len() > 6 {
return Err(error(
"54000",
format!(
"number of array dimensions ({}) exceeds the maximum allowed (6)",
target.indirection.len()
),
));
}
Ok(array)
}
pub enum AssignmentLevel<'a, E> {
Field(&'a str),
Subscripts(&'a [AssignmentStep<E>]),
}
impl<E> AssignmentLevel<'_, E> {
pub fn is_slice(&self) -> bool {
match self {
Self::Field(_) => false,
Self::Subscripts(steps) => steps
.iter()
.any(|step| matches!(step, AssignmentStep::Slice { .. })),
}
}
}
pub fn assignment_levels<E>(steps: &[AssignmentStep<E>]) -> Vec<AssignmentLevel<'_, E>> {
let mut levels = Vec::new();
let mut position = 0;
while position < steps.len() {
if let AssignmentStep::Field(field) = &steps[position] {
levels.push(AssignmentLevel::Field(field));
position += 1;
continue;
}
let start = position;
while position < steps.len() && !matches!(steps[position], AssignmentStep::Field(_)) {
position += 1;
}
levels.push(AssignmentLevel::Subscripts(&steps[start..position]));
}
levels
}
pub fn has_field_step<E>(target: &AssignmentTarget<E>) -> bool {
target
.indirection
.iter()
.any(|step| matches!(step, AssignmentStep::Field(_)))
}
pub fn field_assignment_types<E>(
target: &AssignmentTarget<E>,
declared: &ColumnType,
composites: Option<&dyn crate::expr::composites::CompositeTypeCatalog>,
) -> Result<Vec<ColumnType>, SQLError> {
let mut types = vec![declared.clone()];
for level in assignment_levels(&target.indirection) {
let current = types.last().expect("the declared type starts the levels");
let mut base = current;
while let ColumnType::Domain { base: inner, .. } = base {
base = inner;
}
let next = match level {
AssignmentLevel::Field(field) => {
let ColumnType::Composite(reference) = base else {
return Err(error("42804", format!(
"cannot assign to field \"{field}\" of column \"{}\" because its type {} is not a composite type",
target.column, current.display_name()
)));
};
let descriptor = crate::expr::composites::descriptor(composites, reference.oid)?;
let Some((_, attribute)) = descriptor.attribute(field) else {
return Err(error("42703", format!(
"cannot assign to field \"{field}\" of column \"{}\" because there is no such column in data type {}",
target.column, current.display_name()
)));
};
attribute.ty.clone()
}
AssignmentLevel::Subscripts(steps) => {
let array = match base {
ColumnType::Array(_) => base.clone(),
ColumnType::Int2Vector => ColumnType::Array(Box::new(ColumnType::SmallInteger)),
ColumnType::OidVector => ColumnType::Array(Box::new(ColumnType::Oid)),
_ => {
return Err(error(
"42804",
format!(
"cannot subscript type {} because it does not support subscripting",
current.display_name()
),
))
}
};
if steps.len() > 6 {
return Err(error(
"54000",
format!(
"number of array dimensions ({}) exceeds the maximum allowed (6)",
steps.len()
),
));
}
if steps
.iter()
.any(|step| matches!(step, AssignmentStep::Slice { .. }))
{
array
} else {
let mut element = &array;
while let ColumnType::Array(inner) = element {
element = inner;
}
element.clone()
}
}
};
types.push(next);
}
Ok(types)
}
pub fn assignment_value_type<E>(
target: &AssignmentTarget<E>,
declared: &ColumnType,
) -> Result<ColumnType, SQLError> {
if target.is_whole_column() {
return Ok(declared.clone());
}
let array = array_assignment_type(target, declared)?;
if target
.indirection
.iter()
.any(|step| matches!(step, AssignmentStep::Slice { .. }))
{
Ok(array)
} else {
let mut element = &array;
while let ColumnType::Array(inner) = element {
element = inner;
}
Ok(element.clone())
}
}
pub fn validate_assignment_source<E>(
target: &AssignmentTarget<E>,
required: &ColumnType,
source: Option<&ColumnType>,
) -> Result<(), SQLError> {
if let Some(source) = source {
if !crate::assignment_type_compatible(source, required) {
let levels = assignment_levels(&target.indirection);
let name = levels
.iter()
.rev()
.find_map(|level| match level {
AssignmentLevel::Field(field) => Some(*field),
AssignmentLevel::Subscripts(_) => None,
})
.unwrap_or(&target.column);
let message = match levels.last() {
None => format!(
"column \"{}\" is of type {} but expression is of type {}",
target.column,
required.display_name(),
source.display_name()
),
Some(AssignmentLevel::Field(field)) => format!(
"subfield \"{field}\" is of type {} but expression is of type {}",
required.display_name(),
source.display_name()
),
Some(AssignmentLevel::Subscripts(_)) => format!(
"subscripted assignment to \"{name}\" requires type {} but expression is of type {}",
required.display_name(),
source.display_name()
),
};
return Err(SQLError::Diagnostic {
sqlstate: "42804".into(),
message,
detail: None,
hint: Some("You will need to rewrite or cast the expression.".into()),
});
}
}
Ok(())
}
pub fn validate_assignment_result<E>(
target: &AssignmentTarget<E>,
declared: &ColumnType,
) -> Result<(), SQLError> {
if target.is_whole_column() || has_field_step(target) {
return Ok(());
}
let container = array_assignment_type(target, declared)?;
if !crate::type_resolution::explicit_type_compatible(&container, declared) {
return Err(error(
"42846",
format!(
"cannot cast type {} to {}",
container.sql_name(),
declared.sql_name()
),
));
}
Ok(())
}
pub fn validate_assignment_default<E>(target: &AssignmentTarget<E>) -> Result<(), SQLError> {
match target.indirection.first() {
None => Ok(()),
Some(AssignmentStep::Field(_)) => {
Err(error("0A000", "cannot set a subfield to DEFAULT".into()))
}
Some(_) => Err(error(
"0A000",
"cannot set an array element to DEFAULT".into(),
)),
}
}