#![allow(clippy::similar_names)]
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::column_type::ColumnType;
use crate::ir::default_expr::DefaultExpr;
use crate::ir::sequence::{Sequence, SequenceOwner};
use crate::ir::table::Table;
use crate::parse::error::{ParseError, SourceLocation};
pub fn desugar_serials_in_table(
table: &mut Table,
location: &SourceLocation,
) -> Result<Vec<Sequence>, ParseError> {
let mut produced: Vec<Sequence> = Vec::new();
let table_qname = table.qname.clone();
for col in &mut table.columns {
let raw = match &col.ty {
ColumnType::Other { raw } => raw.to_ascii_lowercase(),
_ => continue,
};
let Some(integer_ty) = serial_to_integer(&raw) else {
continue;
};
col.ty = integer_ty.clone();
col.nullable = false;
let seq_name = format!("{}_{}_seq", table_qname.name.as_str(), col.name.as_str());
let seq_qname = QualifiedName::new(
table_qname.schema.clone(),
Identifier::from_unquoted(&seq_name).map_err(|e| ParseError::Ir {
location: location.clone(),
source: crate::ir::IrError::InvalidIdentifier(e.to_string()),
})?,
);
col.default = Some(DefaultExpr::Sequence(seq_qname.clone()));
produced.push(Sequence {
qname: seq_qname,
data_type: integer_ty,
start: 1,
increment: 1,
min_value: None,
max_value: None,
cache: 1,
cycle: false,
owned_by: Some(SequenceOwner {
table: table_qname.clone(),
column: col.name.clone(),
}),
comment: None,
owner: None,
grants: vec![],
});
}
Ok(produced)
}
fn serial_to_integer(raw: &str) -> Option<ColumnType> {
match raw {
"serial" | "serial4" => Some(ColumnType::Integer),
"bigserial" | "serial8" => Some(ColumnType::BigInt),
"smallserial" | "serial2" => Some(ColumnType::SmallInt),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::column::Column;
use std::path::PathBuf;
fn loc() -> SourceLocation {
SourceLocation::new(PathBuf::from("test.sql"), 1, 1)
}
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(name: &str) -> QualifiedName {
QualifiedName::new(id("app"), id(name))
}
fn table_with_serial(raw_type: &str) -> Table {
Table {
qname: qn("users"),
columns: vec![Column {
name: id("id"),
ty: ColumnType::Other {
raw: raw_type.into(),
},
nullable: true,
default: None,
identity: None,
generated: None,
collation: None,
storage: None,
compression: None,
comment: None,
}],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
access_method: None,
tablespace: None,
}
}
#[test]
fn desugars_serial_to_integer() {
let mut t = table_with_serial("serial");
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert_eq!(t.columns[0].ty, ColumnType::Integer);
assert!(!t.columns[0].nullable);
match &t.columns[0].default {
Some(DefaultExpr::Sequence(q)) => assert_eq!(q.to_string(), "app.users_id_seq"),
other => panic!("expected sequence default, got {other:?}"),
}
assert_eq!(seqs.len(), 1);
assert_eq!(seqs[0].qname.to_string(), "app.users_id_seq");
assert_eq!(seqs[0].data_type, ColumnType::Integer);
let owner = seqs[0].owned_by.as_ref().expect("owned_by");
assert_eq!(owner.table.to_string(), "app.users");
assert_eq!(owner.column.as_str(), "id");
}
#[test]
fn desugars_bigserial_to_bigint() {
let mut t = table_with_serial("bigserial");
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert_eq!(t.columns[0].ty, ColumnType::BigInt);
assert_eq!(seqs[0].data_type, ColumnType::BigInt);
}
#[test]
fn desugars_smallserial_to_smallint() {
let mut t = table_with_serial("smallserial");
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert_eq!(t.columns[0].ty, ColumnType::SmallInt);
assert_eq!(seqs[0].data_type, ColumnType::SmallInt);
}
#[test]
fn desugars_serial4_alias() {
let mut t = table_with_serial("serial4");
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert_eq!(t.columns[0].ty, ColumnType::Integer);
assert_eq!(seqs.len(), 1);
}
#[test]
fn ignores_non_serial_other() {
let mut t = table_with_serial("custom_type");
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert!(seqs.is_empty());
assert!(matches!(t.columns[0].ty, ColumnType::Other { .. }));
}
#[test]
fn ignores_already_canonical_types() {
let mut t = Table {
qname: qn("t"),
columns: vec![Column {
name: id("id"),
ty: ColumnType::Integer,
nullable: true,
default: None,
identity: None,
generated: None,
collation: None,
storage: None,
compression: None,
comment: None,
}],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
access_method: None,
tablespace: None,
};
let seqs = desugar_serials_in_table(&mut t, &loc()).unwrap();
assert!(seqs.is_empty());
assert!(t.columns[0].nullable);
}
}