Skip to main content

uqa_sql/schema/sequences/
declaration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Read the options of a new sequence as `PostgreSQL`'s `init_params` reads them.
8
9use super::definition::SequenceDefinition;
10use crate::ast::{
11    ColumnType, SequenceDataType, SequenceDeclaration, SequenceOptionValue, SequenceOwnership,
12};
13use crate::expr::integer_input::{parse_int8, IntegerInputError};
14use crate::SQLError;
15
16/// A new sequence's definition, with the value its first `nextval` returns.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct DeclaredSequence {
19    pub definition: SequenceDefinition,
20    /// The value the first `nextval` returns: the start, or the value `RESTART` gives.
21    pub current: i64,
22}
23
24/// Read the options of a new sequence of `data_type`, its written `AS` type or the type of the identity column it serves, in `PostgreSQL`'s order: the type, the increment, cycling, the maximum, the minimum, their ranges, the start, the restart and the cache, each checked as it is read. An omitted option takes the default its type and the direction of its increment give it.
25pub fn declare_sequence(
26    declaration: &SequenceDeclaration,
27    data_type: &ColumnType,
28    identity: bool,
29) -> Result<DeclaredSequence, SQLError> {
30    read_sequence_options(declaration, Some(data_type), SequenceBase::New, identity)
31}
32
33/// Read the options of `ALTER SEQUENCE`, or of an identity column's `SET` and `RESTART`, against an existing sequence whose definition is `definition` and whose next value is `current`, in the same order. An omitted option keeps its value, `NO MINVALUE` and `NO MAXVALUE` take the defaults again, a new type takes its own bounds for the bounds that were the old type's, and the current value is checked against the bounds the options produce.
34pub fn alter_declared_sequence(
35    declaration: &SequenceDeclaration,
36    definition: &SequenceDefinition,
37    current: i64,
38    identity: bool,
39) -> Result<DeclaredSequence, SQLError> {
40    read_sequence_options(
41        declaration,
42        declaration.data_type.as_ref(),
43        SequenceBase::Existing {
44            definition,
45            current,
46        },
47        identity,
48    )
49}
50
51/// The sequence a list of options applies to.
52#[derive(Clone, Copy)]
53enum SequenceBase<'a> {
54    New,
55    Existing {
56        definition: &'a SequenceDefinition,
57        current: i64,
58    },
59}
60
61/// `PostgreSQL`'s `init_params`, which reads a new sequence's options and an existing sequence's changes alike.
62fn read_sequence_options(
63    declaration: &SequenceDeclaration,
64    written_type: Option<&ColumnType>,
65    base: SequenceBase<'_>,
66    identity: bool,
67) -> Result<DeclaredSequence, SQLError> {
68    let existing = match base {
69        SequenceBase::New => None,
70        SequenceBase::Existing {
71            definition,
72            current,
73        } => Some((definition, current)),
74    };
75    let definition = existing.map(|(definition, _)| definition);
76    let (data_type, reset_max, reset_min) = read_type(written_type, definition, identity)?;
77    let increment = match &declaration.increment {
78        Some(value) => {
79            let increment = sequence_option_integer("increment", value)?;
80            if increment == 0 {
81                return Err(invalid("INCREMENT must not be zero".into()));
82            }
83            increment
84        }
85        None => definition.map_or(1, |definition| definition.increment),
86    };
87    let cycle = declaration
88        .cycle
89        .or(definition.map(|definition| definition.cycle))
90        .unwrap_or(false);
91    let (min_value, max_value) = read_bounds(
92        declaration,
93        definition,
94        data_type,
95        increment,
96        (reset_min, reset_max),
97    )?;
98    let (start, current) = read_start(declaration, existing, increment, min_value, max_value)?;
99    let cache_size = match &declaration.cache {
100        Some(value) => {
101            let cache_size = sequence_option_integer("cache", value)?;
102            if cache_size <= 0 {
103                return Err(invalid(format!(
104                    "CACHE ({cache_size}) must be greater than zero"
105                )));
106            }
107            cache_size
108        }
109        None => definition.map_or(1, |definition| definition.cache_size),
110    };
111    Ok(DeclaredSequence {
112        definition: SequenceDefinition {
113            start,
114            increment,
115            data_type,
116            min_value,
117            max_value,
118            cycle,
119            cache_size,
120        },
121        current,
122    })
123}
124
125/// The type a sequence counts in, and whether its maximum and its minimum were the bounds of a type it leaves, which then follow the new type.
126fn read_type(
127    written: Option<&ColumnType>,
128    definition: Option<&SequenceDefinition>,
129    identity: bool,
130) -> Result<(SequenceDataType, bool, bool), SQLError> {
131    Ok(match (written, definition) {
132        (Some(written), Some(definition)) => {
133            let (old_min, old_max) = definition.data_type.bounds();
134            (
135                sequence_data_type(written, identity)?,
136                definition.max_value == old_max,
137                definition.min_value == old_min,
138            )
139        }
140        (Some(written), None) => (sequence_data_type(written, identity)?, false, false),
141        (None, Some(definition)) => (definition.data_type, false, false),
142        (None, None) => (SequenceDataType::BigInt, false, false),
143    })
144}
145
146/// The maximum and then the minimum, each read and checked against the type, and then against each other.
147fn read_bounds(
148    declaration: &SequenceDeclaration,
149    definition: Option<&SequenceDefinition>,
150    data_type: SequenceDataType,
151    increment: i64,
152    (reset_min, reset_max): (bool, bool),
153) -> Result<(i64, i64), SQLError> {
154    let (type_min, type_max) = data_type.bounds();
155    let max_value = match (&declaration.max_value, definition) {
156        (Some(value), _) if *value != SequenceOptionValue::Absent => {
157            sequence_option_integer("maxvalue", value)?
158        }
159        (Some(_), _) | (None, None) => {
160            if increment > 0 || reset_max {
161                type_max
162            } else {
163                -1
164            }
165        }
166        (None, Some(_)) if reset_max => type_max,
167        (None, Some(definition)) => definition.max_value,
168    };
169    if !(type_min..=type_max).contains(&max_value) {
170        return Err(invalid(format!(
171            "MAXVALUE ({max_value}) is out of range for sequence data type {}",
172            data_type.sql_name()
173        )));
174    }
175    let min_value = match (&declaration.min_value, definition) {
176        (Some(value), _) if *value != SequenceOptionValue::Absent => {
177            sequence_option_integer("minvalue", value)?
178        }
179        (Some(_), _) | (None, None) => {
180            if increment < 0 || reset_min {
181                type_min
182            } else {
183                1
184            }
185        }
186        (None, Some(_)) if reset_min => type_min,
187        (None, Some(definition)) => definition.min_value,
188    };
189    if !(type_min..=type_max).contains(&min_value) {
190        return Err(invalid(format!(
191            "MINVALUE ({min_value}) is out of range for sequence data type {}",
192            data_type.sql_name()
193        )));
194    }
195    if min_value >= max_value {
196        return Err(invalid(format!(
197            "MINVALUE ({min_value}) must be less than MAXVALUE ({max_value})"
198        )));
199    }
200    Ok((min_value, max_value))
201}
202
203/// The start and then the value the next `nextval` returns, each read and checked against the bounds.
204fn read_start(
205    declaration: &SequenceDeclaration,
206    existing: Option<(&SequenceDefinition, i64)>,
207    increment: i64,
208    min_value: i64,
209    max_value: i64,
210) -> Result<(i64, i64), SQLError> {
211    let start = match (&declaration.start, existing) {
212        (Some(value), _) => sequence_option_integer("start", value)?,
213        (None, Some((definition, _))) => definition.start,
214        (None, None) if increment > 0 => min_value,
215        (None, None) => max_value,
216    };
217    if start < min_value {
218        return Err(invalid(format!(
219            "START value ({start}) cannot be less than MINVALUE ({min_value})"
220        )));
221    }
222    if start > max_value {
223        return Err(invalid(format!(
224            "START value ({start}) cannot be greater than MAXVALUE ({max_value})"
225        )));
226    }
227    let current = match (&declaration.restart, existing) {
228        (Some(SequenceOptionValue::Absent), _) | (None, None) => start,
229        (Some(value), _) => sequence_option_integer("restart", value)?,
230        (None, Some((_, current))) => current,
231    };
232    if current < min_value {
233        return Err(invalid(format!(
234            "RESTART value ({current}) cannot be less than MINVALUE ({min_value})"
235        )));
236    }
237    if current > max_value {
238        return Err(invalid(format!(
239            "RESTART value ({current}) cannot be greater than MAXVALUE ({max_value})"
240        )));
241    }
242    Ok((start, current))
243}
244
245/// The column `OWNED BY` names, as `PostgreSQL` reads it once it has created the sequence: `NONE`, or a relation and one of its columns.
246pub fn sequence_ownership(names: &[String]) -> Result<SequenceOwnership, SQLError> {
247    use crate::compiler::render_relation_component;
248    match names {
249        [none] if none == "none" => Ok(SequenceOwnership::Unowned),
250        [] | [_] => Err(SQLError::Diagnostic {
251            sqlstate: "42601".into(),
252            message: "invalid OWNED BY option".into(),
253            detail: None,
254            hint: Some("Specify OWNED BY table.column or OWNED BY NONE.".into()),
255        }),
256        [table, column] => Ok(SequenceOwnership::Column {
257            table: render_relation_component(table),
258            column: column.clone(),
259        }),
260        [schema, table, column] => Ok(SequenceOwnership::Column {
261            table: format!(
262                "{}.{}",
263                render_relation_component(schema),
264                render_relation_component(table)
265            ),
266            column: column.clone(),
267        }),
268        _ => Err(SQLError::Unsupported(
269            "cross-database references are not implemented: OWNED BY".into(),
270        )),
271    }
272}
273
274/// The integer type a sequence counts in. An identity column's sequence counts in its column's type.
275fn sequence_data_type(
276    data_type: &ColumnType,
277    identity: bool,
278) -> Result<SequenceDataType, SQLError> {
279    match data_type {
280        ColumnType::SmallInteger => Ok(SequenceDataType::SmallInt),
281        ColumnType::Integer => Ok(SequenceDataType::Integer),
282        ColumnType::BigInteger => Ok(SequenceDataType::BigInt),
283        _ => Err(invalid(
284            if identity {
285                "identity column type must be smallint, integer, or bigint"
286            } else {
287                "sequence type must be smallint, integer, or bigint"
288            }
289            .into(),
290        )),
291    }
292}
293
294/// An option's value as `PostgreSQL`'s `defGetInt64` reads it.
295pub fn sequence_option_integer(name: &str, value: &SequenceOptionValue) -> Result<i64, SQLError> {
296    match value {
297        SequenceOptionValue::Integer(value) => Ok(*value),
298        SequenceOptionValue::Text(text) => parse_int8(text).map_err(|error| match error {
299            IntegerInputError::OutOfRange => SQLError::Routine {
300                sqlstate: "22003".into(),
301                message: format!("value \"{text}\" is out of range for type bigint"),
302            },
303            IntegerInputError::InvalidSyntax => SQLError::Routine {
304                sqlstate: "22P02".into(),
305                message: format!("invalid input syntax for type bigint: \"{text}\""),
306            },
307        }),
308        SequenceOptionValue::Absent => Err(SQLError::Routine {
309            sqlstate: "42601".into(),
310            message: format!("{name} requires a numeric value"),
311        }),
312    }
313}
314
315fn invalid(message: String) -> SQLError {
316    SQLError::Routine {
317        sqlstate: "22023".into(),
318        message,
319    }
320}
321
322#[cfg(test)]
323mod tests;