use super::rows::RewriteRows;
use crate::mutation::{
assignment::MutationAssignmentContext, constraints::context::ConstraintContext,
publication::MutationStorage,
};
use crate::schema::keys::KeyValidationContext;
use uqa_core::DocId;
use uqa_sql::SQLError;
use uqa_storage::StorageBackendResult;
pub trait GeneratedRewriteState {
fn advance_next_id(&self, table: &str, id: DocId) -> StorageBackendResult<()>;
}
pub struct GeneratedRewriteContext<'a, S: Clone + 'static> {
pub cancellation: &'a uqa_core::CancellationToken,
pub keys: KeyValidationContext<'a>,
pub assignment: MutationAssignmentContext<'a, S>,
pub storage: &'a dyn MutationStorage,
pub state: &'a dyn GeneratedRewriteState,
pub identifiers: &'a dyn crate::mutation::identity::MutationIdentifiers,
}
pub fn update_rewritten_fields<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
id: DocId,
values: std::collections::BTreeMap<String, uqa_core::Value>,
vectors: crate::mutation::publication::DocumentVectors,
) -> Result<bool, SQLError> {
context.cancellation.check()?;
let Some(mut document) = context.keys.constraints.reads.get_document(table, id)? else {
return Ok(false);
};
document.extend(values);
let mut replacement_vectors = crate::mutation::vectors::document_vectors(
context.keys.constraints.catalog,
table,
&document,
)?;
replacement_vectors.extend(vectors);
context.storage.insert_document(
table,
id,
document,
replacement_vectors,
crate::mutation::publication::InsertedIdentity::Unknown,
)?;
Ok(true)
}
pub fn validate_and_rewrite_generated_rows<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
rewrite_physical_rows: bool,
changed: &[String],
) -> Result<(), SQLError> {
let allowance = context.keys.constraints.memory.work_mem_bytes()?;
let memory = uqa_core::memory::MemoryBudget::new(allowance / 2);
let read_memory = uqa_core::memory::MemoryBudget::new(allowance - allowance / 2);
let control =
uqa_storage::read_control::StorageReadControl::new(&read_memory, context.cancellation);
let mut original =
super::rows::capture(context.keys.constraints.reads, table, &memory, &control)?;
let mut rows = RewriteRows::new(&memory);
for position in 0..original.len() {
context.cancellation.check()?;
let super::rows::RewriteRow {
original_id: doc_id,
mut document,
..
} = original.get(position)?;
crate::mutation::assignment::refresh_selected_stored_generated_columns(
context.assignment,
table,
&mut document,
Some(changed),
)?;
rows.push(doc_id, document)?;
}
drop(original);
rewrite_table_rows(context, table, rows, rewrite_physical_rows, changed)
}
pub fn rewrite_table_rows<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
mut rows: RewriteRows,
write: bool,
changed: &[String],
) -> Result<(), SQLError> {
for position in 0..rows.len() {
context.cancellation.check()?;
let row = rows.get(position)?;
crate::mutation::constraints::validate_rewritten_row(
context.keys.constraints,
table,
&row.document,
)?;
}
publish_validated_rows(context, table, rows, write)?;
crate::schema::validation::validate_rewritten_foreign_keys(
context.keys.constraints,
table,
changed,
)
}
pub(in crate::schema) fn publish_validated_rows<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
mut rows: RewriteRows,
write: bool,
) -> Result<(), SQLError> {
super::super::keys::validate_key_constraint_rows(&context.keys, table, &mut rows)?;
if write {
publish_rewritten_rows(context, table, rows)?;
super::super::keys::validate_temporal_key_rows(&context.keys, table)?;
}
Ok(())
}
fn publish_rewritten_rows<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
mut rows: RewriteRows,
) -> Result<(), SQLError> {
let mut replacements = RewriteRows::new(rows.memory());
for position in 0..rows.len() {
context.cancellation.check()?;
let super::rows::RewriteRow {
original_id: old_doc_id,
document,
..
} = rows.get(position)?;
let new_doc_id = crate::mutation::identity::key_relocation(
context.keys.constraints.catalog,
context.identifiers,
table,
old_doc_id,
&document,
)?
.unwrap_or(old_doc_id);
replacements.push_replacement(old_doc_id, new_doc_id, document)?;
}
drop(rows);
replacements.spill()?;
remove_replaced_rows(context, table, replacements.memory())?;
for position in 0..replacements.len() {
context.cancellation.check()?;
let super::rows::RewriteRow {
original_id: old_doc_id,
target_id: new_doc_id,
document,
} = replacements.get(position)?;
let vectors = crate::mutation::vectors::document_vectors(
context.keys.constraints.catalog,
table,
&document,
)?;
context.storage.insert_document(
table,
new_doc_id,
document,
vectors,
crate::mutation::publication::InsertedIdentity::Vacant,
)?;
if new_doc_id != old_doc_id {
context
.state
.advance_next_id(table, new_doc_id)
.map_err(|error| {
crate::mutation::errors::identifier_storage_error(
"rewritten primary key",
&error,
)
})?;
}
}
Ok(())
}
fn remove_replaced_rows<S: Clone + 'static>(
context: &GeneratedRewriteContext<'_, S>,
table: &str,
memory: &uqa_core::memory::MemoryBudget,
) -> Result<(), SQLError> {
let control = uqa_storage::read_control::StorageReadControl::new(memory, context.cancellation);
let limit = (memory.available() / (4 * std::mem::size_of::<DocId>()))
.clamp(1, crate::DEFAULT_BATCH_SIZE);
let mut after = None;
loop {
context.cancellation.check()?;
let ids = context
.keys
.constraints
.reads
.live_table_doc_id_page(table, after, limit, &control)?;
let Some(last) = ids.last().copied() else {
return Ok(());
};
after = Some(last);
for id in ids.iter().copied() {
context.cancellation.check()?;
context.storage.delete_document(table, id)?;
}
}
}
pub fn stored_generated_columns(
constraints: ConstraintContext<'_>,
table: &str,
) -> Result<Vec<String>, SQLError> {
Ok(constraints
.catalog
.try_describe_table(table)
.map_err(|error| crate::mutation::errors::dml_storage_error("table rewrite", error))?
.ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
.into_iter()
.filter(|column| {
column.generated.as_ref().is_some_and(|generated| {
generated.kind == uqa_sql::ast::GeneratedColumnKind::Stored
})
})
.map(|column| column.name)
.collect())
}