use super::definition::SequenceDefinition;
use crate::ast::{
ColumnType, SequenceDataType, SequenceDeclaration, SequenceOptionValue, SequenceOwnership,
};
use crate::expr::integer_input::{parse_int8, IntegerInputError};
use crate::SQLError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeclaredSequence {
pub definition: SequenceDefinition,
pub current: i64,
}
pub fn declare_sequence(
declaration: &SequenceDeclaration,
data_type: &ColumnType,
identity: bool,
) -> Result<DeclaredSequence, SQLError> {
read_sequence_options(declaration, Some(data_type), SequenceBase::New, identity)
}
pub fn alter_declared_sequence(
declaration: &SequenceDeclaration,
definition: &SequenceDefinition,
current: i64,
identity: bool,
) -> Result<DeclaredSequence, SQLError> {
read_sequence_options(
declaration,
declaration.data_type.as_ref(),
SequenceBase::Existing {
definition,
current,
},
identity,
)
}
#[derive(Clone, Copy)]
enum SequenceBase<'a> {
New,
Existing {
definition: &'a SequenceDefinition,
current: i64,
},
}
fn read_sequence_options(
declaration: &SequenceDeclaration,
written_type: Option<&ColumnType>,
base: SequenceBase<'_>,
identity: bool,
) -> Result<DeclaredSequence, SQLError> {
let existing = match base {
SequenceBase::New => None,
SequenceBase::Existing {
definition,
current,
} => Some((definition, current)),
};
let definition = existing.map(|(definition, _)| definition);
let (data_type, reset_max, reset_min) = read_type(written_type, definition, identity)?;
let increment = match &declaration.increment {
Some(value) => {
let increment = sequence_option_integer("increment", value)?;
if increment == 0 {
return Err(invalid("INCREMENT must not be zero".into()));
}
increment
}
None => definition.map_or(1, |definition| definition.increment),
};
let cycle = declaration
.cycle
.or(definition.map(|definition| definition.cycle))
.unwrap_or(false);
let (min_value, max_value) = read_bounds(
declaration,
definition,
data_type,
increment,
(reset_min, reset_max),
)?;
let (start, current) = read_start(declaration, existing, increment, min_value, max_value)?;
let cache_size = match &declaration.cache {
Some(value) => {
let cache_size = sequence_option_integer("cache", value)?;
if cache_size <= 0 {
return Err(invalid(format!(
"CACHE ({cache_size}) must be greater than zero"
)));
}
cache_size
}
None => definition.map_or(1, |definition| definition.cache_size),
};
Ok(DeclaredSequence {
definition: SequenceDefinition {
start,
increment,
data_type,
min_value,
max_value,
cycle,
cache_size,
},
current,
})
}
fn read_type(
written: Option<&ColumnType>,
definition: Option<&SequenceDefinition>,
identity: bool,
) -> Result<(SequenceDataType, bool, bool), SQLError> {
Ok(match (written, definition) {
(Some(written), Some(definition)) => {
let (old_min, old_max) = definition.data_type.bounds();
(
sequence_data_type(written, identity)?,
definition.max_value == old_max,
definition.min_value == old_min,
)
}
(Some(written), None) => (sequence_data_type(written, identity)?, false, false),
(None, Some(definition)) => (definition.data_type, false, false),
(None, None) => (SequenceDataType::BigInt, false, false),
})
}
fn read_bounds(
declaration: &SequenceDeclaration,
definition: Option<&SequenceDefinition>,
data_type: SequenceDataType,
increment: i64,
(reset_min, reset_max): (bool, bool),
) -> Result<(i64, i64), SQLError> {
let (type_min, type_max) = data_type.bounds();
let max_value = match (&declaration.max_value, definition) {
(Some(value), _) if *value != SequenceOptionValue::Absent => {
sequence_option_integer("maxvalue", value)?
}
(Some(_), _) | (None, None) => {
if increment > 0 || reset_max {
type_max
} else {
-1
}
}
(None, Some(_)) if reset_max => type_max,
(None, Some(definition)) => definition.max_value,
};
if !(type_min..=type_max).contains(&max_value) {
return Err(invalid(format!(
"MAXVALUE ({max_value}) is out of range for sequence data type {}",
data_type.sql_name()
)));
}
let min_value = match (&declaration.min_value, definition) {
(Some(value), _) if *value != SequenceOptionValue::Absent => {
sequence_option_integer("minvalue", value)?
}
(Some(_), _) | (None, None) => {
if increment < 0 || reset_min {
type_min
} else {
1
}
}
(None, Some(_)) if reset_min => type_min,
(None, Some(definition)) => definition.min_value,
};
if !(type_min..=type_max).contains(&min_value) {
return Err(invalid(format!(
"MINVALUE ({min_value}) is out of range for sequence data type {}",
data_type.sql_name()
)));
}
if min_value >= max_value {
return Err(invalid(format!(
"MINVALUE ({min_value}) must be less than MAXVALUE ({max_value})"
)));
}
Ok((min_value, max_value))
}
fn read_start(
declaration: &SequenceDeclaration,
existing: Option<(&SequenceDefinition, i64)>,
increment: i64,
min_value: i64,
max_value: i64,
) -> Result<(i64, i64), SQLError> {
let start = match (&declaration.start, existing) {
(Some(value), _) => sequence_option_integer("start", value)?,
(None, Some((definition, _))) => definition.start,
(None, None) if increment > 0 => min_value,
(None, None) => max_value,
};
if start < min_value {
return Err(invalid(format!(
"START value ({start}) cannot be less than MINVALUE ({min_value})"
)));
}
if start > max_value {
return Err(invalid(format!(
"START value ({start}) cannot be greater than MAXVALUE ({max_value})"
)));
}
let current = match (&declaration.restart, existing) {
(Some(SequenceOptionValue::Absent), _) | (None, None) => start,
(Some(value), _) => sequence_option_integer("restart", value)?,
(None, Some((_, current))) => current,
};
if current < min_value {
return Err(invalid(format!(
"RESTART value ({current}) cannot be less than MINVALUE ({min_value})"
)));
}
if current > max_value {
return Err(invalid(format!(
"RESTART value ({current}) cannot be greater than MAXVALUE ({max_value})"
)));
}
Ok((start, current))
}
pub fn sequence_ownership(names: &[String]) -> Result<SequenceOwnership, SQLError> {
use crate::compiler::render_relation_component;
match names {
[none] if none == "none" => Ok(SequenceOwnership::Unowned),
[] | [_] => Err(SQLError::Diagnostic {
sqlstate: "42601".into(),
message: "invalid OWNED BY option".into(),
detail: None,
hint: Some("Specify OWNED BY table.column or OWNED BY NONE.".into()),
}),
[table, column] => Ok(SequenceOwnership::Column {
table: render_relation_component(table),
column: column.clone(),
}),
[schema, table, column] => Ok(SequenceOwnership::Column {
table: format!(
"{}.{}",
render_relation_component(schema),
render_relation_component(table)
),
column: column.clone(),
}),
_ => Err(SQLError::Unsupported(
"cross-database references are not implemented: OWNED BY".into(),
)),
}
}
fn sequence_data_type(
data_type: &ColumnType,
identity: bool,
) -> Result<SequenceDataType, SQLError> {
match data_type {
ColumnType::SmallInteger => Ok(SequenceDataType::SmallInt),
ColumnType::Integer => Ok(SequenceDataType::Integer),
ColumnType::BigInteger => Ok(SequenceDataType::BigInt),
_ => Err(invalid(
if identity {
"identity column type must be smallint, integer, or bigint"
} else {
"sequence type must be smallint, integer, or bigint"
}
.into(),
)),
}
}
pub fn sequence_option_integer(name: &str, value: &SequenceOptionValue) -> Result<i64, SQLError> {
match value {
SequenceOptionValue::Integer(value) => Ok(*value),
SequenceOptionValue::Text(text) => parse_int8(text).map_err(|error| match error {
IntegerInputError::OutOfRange => SQLError::Routine {
sqlstate: "22003".into(),
message: format!("value \"{text}\" is out of range for type bigint"),
},
IntegerInputError::InvalidSyntax => SQLError::Routine {
sqlstate: "22P02".into(),
message: format!("invalid input syntax for type bigint: \"{text}\""),
},
}),
SequenceOptionValue::Absent => Err(SQLError::Routine {
sqlstate: "42601".into(),
message: format!("{name} requires a numeric value"),
}),
}
}
fn invalid(message: String) -> SQLError {
SQLError::Routine {
sqlstate: "22023".into(),
message,
}
}
#[cfg(test)]
mod tests;