use std::collections::HashMap;
use uqa_sql::{ast::IndexKey, semantics::constraint_catalog::ConstraintCatalog, SQLError};
use crate::mutation::{
errors::dml_storage_error, identity::MutationIdentifiers, prepared::PreparedInsertConflict,
publication::InsertedIdentity,
};
use super::supplied_identities::ObservedIdentities;
pub(super) struct KnownNewInserts<'a> {
catalog: &'a dyn ConstraintCatalog,
id_column: &'a str,
resolves_conflicts: bool,
unique_identity: HashMap<String, bool>,
generates_unused: HashMap<String, bool>,
}
impl<'a> KnownNewInserts<'a> {
pub(super) fn new(
catalog: &'a dyn ConstraintCatalog,
id_column: &'a str,
resolves_conflicts: bool,
) -> Self {
Self {
catalog,
id_column,
resolves_conflicts,
unique_identity: HashMap::new(),
generates_unused: HashMap::new(),
}
}
pub(super) fn identity(
&mut self,
target_table: &str,
prepared: &PreparedInsertConflict,
observed: &ObservedIdentities,
identifiers: &dyn MutationIdentifiers,
) -> Result<InsertedIdentity, SQLError> {
if !self.contains(target_table, prepared)? {
return Ok(InsertedIdentity::Unknown);
}
let PreparedInsertConflict::Insert { doc_id, supplied } = prepared else {
return Ok(InsertedIdentity::Vacant);
};
if observed.unused(target_table, *doc_id) {
return Ok(InsertedIdentity::Unused);
}
if *supplied {
return Ok(InsertedIdentity::Vacant);
}
let generates_unused = match self.generates_unused.get(target_table) {
Some(generates_unused) => *generates_unused,
None => {
let generates_unused = identifiers.generates_unused_identities(target_table)?;
self.generates_unused
.insert(target_table.to_owned(), generates_unused);
generates_unused
}
};
Ok(if generates_unused {
InsertedIdentity::Unused
} else {
InsertedIdentity::Vacant
})
}
fn contains(
&mut self,
target_table: &str,
prepared: &PreparedInsertConflict,
) -> Result<bool, SQLError> {
if self.resolves_conflicts {
return Ok(false);
}
if !matches!(
prepared,
PreparedInsertConflict::Insert { supplied: true, .. }
) {
return Ok(true);
}
if let Some(unique) = self.unique_identity.get(target_table) {
return Ok(*unique);
}
let unique = self
.catalog
.enforced_keys(target_table)
.map_err(|error| dml_storage_error("INSERT", error))?
.iter()
.any(|key| {
key.predicate.is_none()
&& !key.without_overlaps
&& matches!(key.keys.as_slice(), [IndexKey::Column(column)] if column == self.id_column)
});
self.unique_identity.insert(target_table.to_owned(), unique);
Ok(unique)
}
}
#[cfg(test)]
mod tests;