use super::{NextvalTarget, SequenceValueContext, SequenceValueError};
use crate::catalog::sequence::snapshot::SequenceReadSnapshot;
use crate::row_locks::{binding::acquire_relation, RelationLockMode};
use uqa_core::RelationIdentity;
use uqa_sql::catalog::resolution::RelationResolution;
pub(super) enum ValueAccess {
Next,
Current,
Set,
}
enum ValueReference {
Name(String),
Oid(i64),
}
pub(super) struct BoundSequenceValue {
pub name: String,
pub object_id: [u8; 16],
}
impl SequenceValueContext<'_> {
pub(super) fn read_snapshot(&self) -> Result<SequenceReadSnapshot, SequenceValueError> {
self.snapshots.sequence_read_snapshot().map_err(|error| {
super::persistence::sequence_storage_error("load sequence catalog", error)
})
}
pub(super) fn bind_sequence_value_reference(
&self,
reference: &str,
) -> Result<BoundSequenceValue, SequenceValueError> {
let resolved = self
.privileges
.resolution
.visible_relation_kind(reference)
.map_err(SequenceValueError::Security)?;
let target = match resolved {
RelationResolution::Found(name, "sequence") => ValueReference::Name(name),
RelationResolution::Found(_, kind) => {
return Err(SequenceValueError::WrongKind {
name: reference.to_string(),
kind,
});
}
RelationResolution::MissingRelation | RelationResolution::MissingSchema(_) => {
ValueReference::Oid(
reference
.parse::<i64>()
.map_err(|_| SequenceValueError::Undefined(reference.into()))?,
)
}
};
let snapshot = self.read_snapshot()?;
let relation = match target {
ValueReference::Name(name) => {
RelationIdentity::from_legacy_name(&name).map_err(|error| {
SequenceValueError::Internal(format!("resolve sequence `{name}`: {error}"))
})?
}
ValueReference::Oid(oid) => snapshot
.object_ids
.iter()
.find_map(|(relation, object_id)| {
(crate::catalog::projection::sequence_relation_oid(*object_id) == oid)
.then(|| relation.clone())
})
.ok_or_else(|| SequenceValueError::Undefined(reference.into()))?,
};
let name = relation.qualified_name();
let object_id = snapshot
.object_ids
.get(&relation)
.copied()
.ok_or_else(|| SequenceValueError::Undefined(name.clone()))?;
Ok(BoundSequenceValue { name, object_id })
}
pub(super) fn lock_sequence_value_target(
&self,
bound: &BoundSequenceValue,
access: ValueAccess,
) -> Result<NextvalTarget, SequenceValueError> {
let mut name = bound.name.clone();
loop {
let guard = acquire_relation(self.locks, &name, RelationLockMode::RowExclusive, false)?;
let snapshot = self.read_snapshot()?;
let relation = snapshot
.object_ids
.iter()
.find_map(|(relation, object_id)| {
(*object_id == bound.object_id).then(|| relation.clone())
})
.ok_or_else(|| {
SequenceValueError::MissingOid(
crate::catalog::projection::sequence_relation_oid(bound.object_id),
)
})?;
let current_name = relation.qualified_name();
if current_name != name {
name = current_name;
continue;
}
guard.retain_at(self.transactions.transaction_lock_mark());
return self.sequence_value_target(&snapshot, relation, bound.object_id, access);
}
}
fn sequence_value_target(
&self,
snapshot: &SequenceReadSnapshot,
relation: RelationIdentity,
object_id: [u8; 16],
access: ValueAccess,
) -> Result<NextvalTarget, SequenceValueError> {
let name = relation.qualified_name();
let state = snapshot
.sequences
.get(&relation)
.copied()
.ok_or_else(|| SequenceValueError::Undefined(name.clone()))?;
let temporary = snapshot.persistence.get(&relation)
== Some(&uqa_sql::ast::RelationPersistence::Temporary);
let privileges = snapshot.privileges(&self.privileges);
match access {
ValueAccess::Next => privileges.ensure_sequence_nextval_privilege(&name, &relation)?,
ValueAccess::Current => {
privileges.ensure_sequence_currval_privilege(&name, &relation)?;
}
ValueAccess::Set => privileges.ensure_sequence_setval_privilege(&name, &relation)?,
}
Ok(NextvalTarget {
name,
relation,
object_id,
state,
temporary,
})
}
}