uqa_sql/schema/sequences/
implicit.rs1use 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}