Skip to main content

uqa_sql/schema/sequences/
implicit.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL names and declaration metadata for SERIAL and IDENTITY sequences.
8use uqa_core::{RelationIdentity, Value};
9
10pub fn stored_owner_names_current(
11    table: &RelationIdentity,
12    column: &crate::ast::ColumnDef,
13    owner: &crate::ast::AutoIncrementOwner,
14) -> bool {
15    let table_matches =
16        RelationIdentity::parse_reference(&owner.table).is_ok_and(|(schema, name)| {
17            schema.is_none_or(|schema| schema == table.schema) && name == table.name
18        });
19    table_matches && owner.column == column.name
20}
21
22pub fn implicit_sequence_data_type(
23    column: &crate::ast::ColumnDef,
24) -> Result<crate::ast::SequenceDataType, String> {
25    match &column.ty {
26        crate::ast::ColumnType::SmallInteger => Ok(crate::ast::SequenceDataType::SmallInt),
27        crate::ast::ColumnType::Integer => Ok(crate::ast::SequenceDataType::Integer),
28        crate::ast::ColumnType::BigInteger => Ok(crate::ast::SequenceDataType::BigInt),
29        _ => Err(format!(
30            "implicit sequence column `{}` has non-integer type",
31            column.name
32        )),
33    }
34}
35
36const POSTGRES_IDENTIFIER_MAX_BYTES: usize = 63;
37
38fn clip_identifier_component(value: &str, byte_length: usize) -> &str {
39    let mut end = byte_length.min(value.len());
40    while !value.is_char_boundary(end) {
41        end -= 1;
42    }
43    &value[..end]
44}
45
46fn implicit_sequence_local_name(
47    table: &str,
48    column: &str,
49    collision_pass: usize,
50) -> Result<String, String> {
51    let label = if collision_pass == 0 {
52        "seq".to_string()
53    } else {
54        format!("seq{collision_pass}")
55    };
56    let overhead = label.len() + 2;
57    let available = POSTGRES_IDENTIFIER_MAX_BYTES
58        .checked_sub(overhead)
59        .filter(|available| *available > 0)
60        .ok_or_else(|| format!("cannot generate an implicit sequence name with label `{label}`"))?;
61    let mut table_bytes = table.len();
62    let mut column_bytes = column.len();
63    while table_bytes + column_bytes > available {
64        if table_bytes > column_bytes {
65            table_bytes -= 1;
66        } else {
67            column_bytes -= 1;
68        }
69    }
70    let table = clip_identifier_component(table, table_bytes);
71    let column = clip_identifier_component(column, column_bytes);
72    Ok(format!("{table}_{column}_{label}"))
73}
74
75pub fn choose_implicit_sequence_name<E>(
76    table: &RelationIdentity,
77    column: &str,
78    mut collides: impl FnMut(&RelationIdentity) -> Result<bool, E>,
79    invalid_name: impl Fn(String) -> E,
80) -> Result<String, E> {
81    for collision_pass in 0.. {
82        let candidate = RelationIdentity::new(
83            table.schema.clone(),
84            implicit_sequence_local_name(&table.name, column, collision_pass)
85                .map_err(&invalid_name)?,
86        );
87        if !collides(&candidate)? {
88            return Ok(candidate.qualified_name());
89        }
90    }
91    unreachable!("the collision pass is unbounded")
92}
93
94pub fn apply_implicit_sequence_metadata(
95    table_name: &str,
96    column: &mut crate::ast::ColumnDef,
97    sequence: String,
98) -> Result<(), String> {
99    let auto_increment = column.auto_increment.as_mut().ok_or_else(|| {
100        format!(
101            "implicit sequence column `{table_name}`.`{}` lost its generation metadata",
102            column.name
103        )
104    })?;
105    auto_increment.sequence = Some(sequence.clone());
106    auto_increment.owner = Some(crate::ast::AutoIncrementOwner {
107        table: table_name.to_string(),
108        column: column.name.clone(),
109    });
110    if auto_increment.kind == crate::ast::AutoIncrementKind::Serial {
111        column.default = Some(crate::ast::Expr::Func {
112            name: "nextval".into(),
113            binding: None,
114            args: vec![crate::ast::Expr::Literal(Value::Str(sequence))],
115            distinct: false,
116            order_by: Vec::new(),
117            filter: None,
118        });
119    }
120    Ok(())
121}