use crate::{Engine, TableState};
use std::sync::Arc;
use uqa_core::{DocId, Value};
use uqa_execution::{
mutation::overlay::CommandMutationOverlay,
query::document_changes::DocumentChanges,
query::exact_lookup::{ExactLookup, ExactLookupOverlay, FieldPresence},
serializable::SerializableRelationRead,
storage_errors::storage_error,
};
use uqa_sql::SQLError;
use uqa_storage::ValueIndexKey;
struct CommandOverlay<'a> {
engine: &'a Engine,
table: String,
}
impl ExactLookupOverlay for CommandOverlay<'_> {
fn is_empty(&self) -> Result<bool, SQLError> {
Ok(!self
.engine
.session
.command_mutation_overlays
.lock()
.iter()
.any(|overlay| overlay.holds(&self.table)))
}
fn masks(&self, doc_id: DocId) -> Result<bool, SQLError> {
let control = self.engine.query_retention_control()?;
CommandMutationOverlay::stages(
&self.engine.session.command_mutation_overlays.lock(),
&self.table,
doc_id,
&control,
)
}
fn find_match(
&self,
columns: &[String],
values: &[Value],
presence: FieldPresence,
) -> Result<Option<DocId>, SQLError> {
self.engine
.command_overlay_exact_match(&self.table, columns, values, presence)
}
}
struct QueryOverlay<'a> {
fixed: DocumentChanges,
commands: CommandOverlay<'a>,
}
impl<'a> QueryOverlay<'a> {
fn new(engine: &'a Engine, table: &str) -> Result<Self, SQLError> {
let canonical = engine.command_overlay_table_name(table)?;
Ok(Self {
fixed: engine
.fixed_transaction_row_changes(&canonical)?
.unwrap_or_default(),
commands: CommandOverlay {
engine,
table: canonical,
},
})
}
}
impl ExactLookupOverlay for QueryOverlay<'_> {
fn is_empty(&self) -> Result<bool, SQLError> {
Ok(!self.fixed.has_changes() && self.commands.is_empty()?)
}
fn masks(&self, doc_id: DocId) -> Result<bool, SQLError> {
Ok(self.commands.masks(doc_id)? || self.fixed.masks(doc_id)?)
}
fn find_match(
&self,
columns: &[String],
values: &[Value],
presence: FieldPresence,
) -> Result<Option<DocId>, SQLError> {
let staged = self.commands.find_match(columns, values, presence)?;
if !self.fixed.has_changes() {
return Ok(staged);
}
for change in self.fixed.changes() {
let (id, present) =
change.map_err(|error| storage_error("read private exact key", &error))?;
if staged.is_some_and(|staged| id >= staged) {
break;
}
if !present || self.commands.masks(id)? {
continue;
}
if self.fixed.row_matches(id, columns, values, presence)? {
return Ok(Some(id));
}
}
Ok(staged)
}
}
impl Engine {
pub fn find_doc_id_by_field(
&self,
table: &str,
field: &str,
value: &Value,
) -> Result<Option<DocId>, SQLError> {
self.with_direct_table_read(table, |engine, name, table| {
let overlay = QueryOverlay::new(engine, name)?;
let read = engine.serializable_table_state_read(table)?;
ExactLookup {
table: table.as_ref(),
overlay: &overlay,
read: read.as_ref(),
}
.find_field_with_index(field, value, |field, value| {
engine.read_value_index_state(
name,
table,
&ValueIndexKey::Column(field.into()),
|index| Ok(index.field_candidates(value)),
)
})
})
}
pub(crate) fn find_mutation_doc_id_by_field(
&self,
table: &str,
field: &str,
value: &Value,
) -> Result<Option<DocId>, SQLError> {
let table_state = self.require_table(table)?;
let overlay = CommandOverlay {
engine: self,
table: self.command_overlay_table_name(table)?,
};
ExactLookup {
table: table_state.as_ref(),
overlay: &overlay,
read: None,
}
.find_field_with_index(field, value, |field, value| {
self.read_value_index_state(
table,
&table_state,
&ValueIndexKey::Column(field.into()),
|index| Ok(index.field_candidates(value)),
)
})
}
pub fn find_conflict(
&self,
table: &str,
columns: &[String],
values: &[Value],
) -> Result<Option<DocId>, SQLError> {
self.with_direct_table_read(table, |engine, name, table| {
let overlay = QueryOverlay::new(engine, name)?;
let read = engine.serializable_table_state_read(table)?;
engine.find_conflict_in_state(name, table, columns, values, &overlay, read.as_ref())
})
}
pub(crate) fn find_mutation_conflict(
&self,
table: &str,
columns: &[String],
values: &[Value],
) -> Result<Option<DocId>, SQLError> {
let table_state = self.require_table(table)?;
let overlay = CommandOverlay {
engine: self,
table: self.command_overlay_table_name(table)?,
};
self.find_conflict_in_state(table, &table_state, columns, values, &overlay, None)
}
fn find_conflict_in_state(
&self,
name: &str,
table: &Arc<TableState>,
columns: &[String],
values: &[Value],
overlay: &dyn ExactLookupOverlay,
read: Option<&SerializableRelationRead>,
) -> Result<Option<DocId>, SQLError> {
let schema_columns = table.columns.snapshot();
ExactLookup {
table: table.as_ref(),
overlay,
read,
}
.find_conflict(&schema_columns, columns, values, |column, predicate| {
self.value_index_scan_state(
name,
table,
&ValueIndexKey::Column(column.into()),
predicate,
read,
)
})
}
}