use super::{compile_pg_type_name, extract_string, parse_regtype_name};
use crate::{ColumnType, SQLError};
use pg_query::{
protobuf::{LimitOption, Node, ResTarget, SelectStmt, SetOperation, TypeName},
NodeEnum,
};
pub(in crate::compiler) fn preserve_alter_type_declaration(
ty: &TypeName,
column: &str,
) -> Result<ColumnType, SQLError> {
if ty.typmods.is_empty() {
if let Ok(compiled) = compile_pg_type_name(ty, column) {
return Ok(compiled);
}
}
let mut declaration = ty
.names
.iter()
.map(|node| extract_string(node).map(|name| format!("\"{}\"", name.replace('"', "\"\""))))
.collect::<Result<Vec<_>, _>>()?
.join(".");
if !ty.typmods.is_empty() {
declaration.push('(');
declaration.push_str(
&ty.typmods
.iter()
.map(modifier_sql)
.collect::<Result<Vec<_>, _>>()?
.join(", "),
);
declaration.push(')');
}
declaration.push_str(&"[]".repeat(ty.array_bounds.len()));
Ok(ColumnType::Named(declaration))
}
fn modifier_sql(modifier: &Node) -> Result<String, SQLError> {
let statement = NodeEnum::SelectStmt(Box::new(SelectStmt {
target_list: vec![Node {
node: Some(NodeEnum::ResTarget(Box::new(ResTarget {
val: Some(Box::new(modifier.clone())),
..ResTarget::default()
}))),
}],
op: SetOperation::SetopNone as i32,
limit_option: LimitOption::Default as i32,
..SelectStmt::default()
}));
let sql = statement.deparse()?;
sql.strip_prefix("SELECT ")
.map(str::to_owned)
.ok_or_else(|| {
SQLError::Internal("type modifier did not deparse as a SELECT target".into())
})
}
pub(crate) fn compile_retained_type_declaration(name: &str) -> Result<ColumnType, SQLError> {
compile_retained_type(name, compile_pg_type_name)
}
pub(crate) fn compile_retained_type_reference(name: &str) -> Result<ColumnType, SQLError> {
compile_retained_type(name, super::compile_pg_type_reference)
}
fn compile_retained_type(
name: &str,
compile: fn(&TypeName, &str) -> Result<ColumnType, SQLError>,
) -> Result<ColumnType, SQLError> {
if parse_regtype_name(name)?.is_none() {
return Err(SQLError::TypeMismatch(format!(
"invalid type declaration `{name}`"
)));
}
let parsed = crate::parser::parse(&format!("SELECT NULL::{name}"))?;
let ty = parsed
.protobuf
.stmts
.first()
.and_then(|statement| statement.stmt.as_ref())
.and_then(|node| match node.node.as_ref()? {
NodeEnum::SelectStmt(select) => select.target_list.first(),
_ => None,
})
.and_then(|node| match node.node.as_ref()? {
NodeEnum::ResTarget(target) => target.val.as_ref(),
_ => None,
})
.and_then(|node| match node.node.as_ref()? {
NodeEnum::TypeCast(cast) => cast.type_name.as_ref(),
_ => None,
})
.ok_or_else(|| SQLError::Internal("type declaration lost its parser type name".into()))?;
compile(ty, name)
}
#[cfg(test)]
mod tests;