use crate::ast::{
ColumnDef, ColumnType, GeneratedColumnKind, PartitionSpec, TableKeyConstraint,
TableKeyConstraintKind,
};
use crate::schema::columns::POSTGRES_SYSTEM_COLUMNS;
use crate::SQLError;
pub fn missing_key_column(column: &str) -> SQLError {
SQLError::Routine {
sqlstate: "42703".into(),
message: format!("column \"{column}\" named in key does not exist"),
}
}
pub fn repeated_key_column(kind: TableKeyConstraintKind, column: &str) -> SQLError {
let constraint = match kind {
TableKeyConstraintKind::PrimaryKey => "primary key",
TableKeyConstraintKind::Unique => "unique",
};
SQLError::Routine {
sqlstate: "42701".into(),
message: format!("column \"{column}\" appears twice in {constraint} constraint"),
}
}
pub fn multiple_primary_keys(table: &str) -> SQLError {
SQLError::Routine {
sqlstate: "42P16".into(),
message: format!("multiple primary keys for table \"{table}\" are not allowed"),
}
}
pub fn validate_overlaps_column(column: &str, ty: Option<&ColumnType>) -> Result<(), SQLError> {
if matches!(ty, Some(ColumnType::Range(_) | ColumnType::Multirange(_))) {
return Ok(());
}
Err(SQLError::Routine {
sqlstate: "42804".into(),
message: format!(
"column \"{column}\" in WITHOUT OVERLAPS is not a range or multirange type"
),
})
}
pub fn validate_overlaps_key_length(key: &TableKeyConstraint) -> Result<(), SQLError> {
if !key.without_overlaps || key.columns.len() >= 2 {
return Ok(());
}
Err(SQLError::Routine {
sqlstate: "42601".into(),
message: "constraint using WITHOUT OVERLAPS needs at least two columns".into(),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexOwner {
Key(TableKeyConstraintKind),
Index,
}
pub fn is_system_column(name: &str) -> bool {
POSTGRES_SYSTEM_COLUMNS.contains(&name)
}
fn missing_operator_class(type_name: &str, access_method: &str) -> SQLError {
SQLError::Diagnostic {
sqlstate: "42704".into(),
message: format!(
"data type {type_name} has no default operator class for access method \"{access_method}\""
),
detail: None,
hint: Some(
"You must specify an operator class for the index or define a default operator class for the data type."
.into(),
),
}
}
pub fn resolve_system_key_attribute(name: &str, access_method: &str) -> Result<(), SQLError> {
let type_name = match name {
"xmin" | "xmax" => "xid",
"cmin" | "cmax" => "cid",
"ctid" => "tid",
_ => return Ok(()),
};
let ordered = match access_method {
"hash" => true,
"" | "btree" => type_name == "tid",
_ => false,
};
if ordered {
Ok(())
} else {
Err(missing_operator_class(
type_name,
if access_method.is_empty() {
"btree"
} else {
access_method
},
))
}
}
pub fn validate_index_attribute(
columns: &[ColumnDef],
name: &str,
owner: IndexOwner,
) -> Result<(), SQLError> {
let Some(column) = columns.iter().find(|column| column.name == name) else {
if is_system_column(name) {
return Err(system_column_index());
}
return Err(SQLError::Internal(format!(
"index attribute `{name}` is not a column"
)));
};
if column
.generated
.as_ref()
.is_some_and(|generated| generated.kind == GeneratedColumnKind::Virtual)
{
let message = match owner {
IndexOwner::Key(TableKeyConstraintKind::PrimaryKey) => {
"primary keys on virtual generated columns are not supported"
}
IndexOwner::Key(TableKeyConstraintKind::Unique) => {
"unique constraints on virtual generated columns are not supported"
}
IndexOwner::Index => "indexes on virtual generated columns are not supported",
};
return Err(SQLError::Routine {
sqlstate: "0A000".into(),
message: message.into(),
});
}
Ok(())
}
pub fn system_column_index() -> SQLError {
SQLError::Routine {
sqlstate: "0A000".into(),
message: "index creation on system columns is not supported".into(),
}
}
pub struct KeyRelation<'a> {
pub table: &'a str,
pub columns: &'a [ColumnDef],
pub partition: Option<&'a PartitionSpec>,
pub has_primary_key: bool,
}
pub fn validate_key_definition(
relation: &KeyRelation<'_>,
key: &TableKeyConstraint,
) -> Result<(), SQLError> {
let declared = |name: &str| relation.columns.iter().any(|column| column.name == name);
let access_method = if key.without_overlaps {
"gist"
} else {
"btree"
};
for name in &key.columns {
if declared(name) {
continue;
}
if !is_system_column(name) {
return Err(missing_key_column(name));
}
resolve_system_key_attribute(name, access_method)?;
}
if let Some(name) = key
.included_columns
.iter()
.find(|name| !declared(name) && !is_system_column(name))
{
return Err(missing_key_column(name));
}
if key.kind == TableKeyConstraintKind::PrimaryKey && relation.has_primary_key {
let local = uqa_core::RelationIdentity::from_legacy_name(relation.table)
.map_err(SQLError::Internal)?;
return Err(multiple_primary_keys(&local.name));
}
if let Some(partition) = relation.partition {
crate::schema::indexes::unique::validate_partitioned_key_constraint(
relation.table,
key,
partition,
)?;
}
for name in key.columns.iter().chain(&key.included_columns) {
validate_index_attribute(relation.columns, name, IndexOwner::Key(key.kind))?;
}
Ok(())
}
pub struct KeyPartition<'a> {
pub table: &'a str,
pub partition: Option<&'a PartitionSpec>,
pub keys: &'a [TableKeyConstraint],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionKeyIndex {
Adopted,
Created,
}
pub fn define_partition_key(
partition: &KeyPartition<'_>,
key: &TableKeyConstraint,
) -> Result<PartitionKeyIndex, SQLError> {
if partition
.keys
.iter()
.any(|existing| crate::schema::inheritance::alter::key_equivalent(existing, key))
{
return Ok(PartitionKeyIndex::Adopted);
}
if key.kind == TableKeyConstraintKind::PrimaryKey
&& partition
.keys
.iter()
.any(|existing| existing.kind == TableKeyConstraintKind::PrimaryKey)
{
let local = uqa_core::RelationIdentity::from_legacy_name(partition.table)
.map_err(SQLError::Internal)?;
return Err(multiple_primary_keys(&local.name));
}
if let Some(spec) = partition.partition {
crate::schema::indexes::unique::validate_partitioned_key_constraint(
partition.table,
key,
spec,
)?;
}
Ok(PartitionKeyIndex::Created)
}
pub fn index_order(mut keys: Vec<TableKeyConstraint>) -> Vec<TableKeyConstraint> {
let mut ordered = Vec::with_capacity(keys.len());
if let Some(position) = keys
.iter()
.position(|key| key.kind == TableKeyConstraintKind::PrimaryKey)
{
ordered.push(keys.remove(position));
}
for key in keys {
match ordered
.iter_mut()
.find(|prior: &&mut TableKeyConstraint| same_index(prior, &key))
{
Some(prior) => {
if prior.name.is_none() {
prior.name = key.name;
}
}
None => ordered.push(key),
}
}
ordered
}
fn same_index(left: &TableKeyConstraint, right: &TableKeyConstraint) -> bool {
left.columns == right.columns
&& left.included_columns == right.included_columns
&& left.nulls_not_distinct == right.nulls_not_distinct
&& left.without_overlaps == right.without_overlaps
}