use crate::{physical::physical_exec_error, spill::BufferedIndexedSpill, PhysicalRow, RowSchema};
use uqa_core::{memory::MemoryBudget, DocId, Value};
use uqa_sql::SQLError;
use uqa_storage::document_store::Document;
pub mod changes;
#[derive(Default)]
pub struct RewriteDeferral {
active: std::cell::Cell<bool>,
physical:
std::cell::RefCell<std::collections::BTreeMap<String, std::collections::BTreeSet<String>>>,
}
impl RewriteDeferral {
pub fn is_deferred(&self) -> bool {
self.active.get()
}
pub fn defer(&self) -> RewriteDeferralGuard<'_> {
RewriteDeferralGuard {
state: self,
previous: self.active.replace(true),
physical: self.physical.take(),
}
}
pub(in crate::schema) fn require_physical_rewrite(&self, table: &str, column: &str) {
self.physical
.borrow_mut()
.entry(table.to_string())
.or_default()
.insert(column.to_string());
}
pub(in crate::schema) fn needs_physical_rewrite(&self, table: &str) -> bool {
self.physical.borrow().contains_key(table)
}
pub(in crate::schema) fn physical_columns(&self, table: &str) -> Vec<String> {
self.physical
.borrow()
.get(table)
.into_iter()
.flatten()
.cloned()
.collect()
}
}
pub struct RewriteDeferralGuard<'a> {
state: &'a RewriteDeferral,
previous: bool,
physical: std::collections::BTreeMap<String, std::collections::BTreeSet<String>>,
}
impl Drop for RewriteDeferralGuard<'_> {
fn drop(&mut self) {
self.state.active.set(self.previous);
self.state
.physical
.replace(std::mem::take(&mut self.physical));
}
}
pub struct RewriteRow {
pub original_id: DocId,
pub target_id: DocId,
pub document: Document,
}
pub struct RewriteRows {
rows: BufferedIndexedSpill,
}
impl RewriteRows {
pub fn new(memory: &MemoryBudget) -> Self {
let relation = uqa_sql::ast::InternalRelationId::allocate();
Self {
rows: BufferedIndexedSpill::with_memory(
RowSchema::with_internal_relation_types(relation, vec![None; 3]),
memory,
),
}
}
pub fn memory(&self) -> &MemoryBudget {
self.rows.memory()
}
pub fn len(&self) -> u64 {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn push(&mut self, original_id: DocId, document: Document) -> Result<(), SQLError> {
self.push_replacement(original_id, original_id, document)
}
pub fn push_replacement(
&mut self,
original_id: DocId,
target_id: DocId,
document: Document,
) -> Result<(), SQLError> {
self.rows
.push(&PhysicalRow::from_values(vec![
Value::Bytes(original_id.to_le_bytes().to_vec()),
Value::Bytes(target_id.to_le_bytes().to_vec()),
Value::Map(document),
]))
.map_err(physical_exec_error)
}
pub fn get(&mut self, position: u64) -> Result<RewriteRow, SQLError> {
let mut values = self
.rows
.get(position)
.map_err(physical_exec_error)?
.into_physical_values()
.into_iter();
let original_id = decode_identity(values.next())?;
let target_id = decode_identity(values.next())?;
let Some(Value::Map(document)) = values.next() else {
return Err(SQLError::Internal("rewritten row lost its document".into()));
};
Ok(RewriteRow {
original_id,
target_id,
document,
})
}
pub fn spill(&mut self) -> Result<(), SQLError> {
self.rows.spill().map_err(physical_exec_error)
}
}
pub fn capture(
reads: &dyn crate::mutation::constraints::context::MutationRead,
table: &str,
memory: &MemoryBudget,
control: &uqa_storage::read_control::StorageReadControl,
) -> Result<RewriteRows, SQLError> {
let mut result = RewriteRows::new(memory);
visit_document_ids(reads, table, control, &mut |id| {
if let Some(document) = reads.get_document(table, id)? {
result.push(id, document)?;
}
Ok(())
})?;
Ok(result)
}
pub fn visit_document_ids(
reads: &dyn crate::mutation::constraints::context::MutationRead,
table: &str,
control: &uqa_storage::read_control::StorageReadControl,
visit: &mut dyn FnMut(DocId) -> Result<(), SQLError>,
) -> Result<(), SQLError> {
let mut after = None;
let limit = (control.memory().limit() / (4 * std::mem::size_of::<DocId>()))
.clamp(1, crate::DEFAULT_BATCH_SIZE);
loop {
control.cancellation().check()?;
let ids = reads.live_table_doc_id_page(table, after, limit, control)?;
let Some(last) = ids.last().copied() else {
break;
};
after = Some(last);
for id in ids.iter().copied() {
control.cancellation().check()?;
visit(id)?;
}
}
Ok(())
}
fn decode_identity(value: Option<Value>) -> Result<DocId, SQLError> {
let Some(Value::Bytes(bytes)) = value else {
return Err(SQLError::Internal("rewritten row lost its identity".into()));
};
let bytes = bytes
.try_into()
.map_err(|_| SQLError::Internal("rewritten row has an invalid identity".into()))?;
Ok(DocId::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests;