use super::{
first_text_signal, operator_execution_error, BTreeMap, ColumnType, DocId, DriverResult,
EngineDriver, OperatorTree, Payload, PostingEntry, PostingList, SQLError, Value,
};
impl EngineDriver<'_> {
pub(super) fn execute_opaque(
kind: &str,
_children: &[OperatorTree],
_meta: &BTreeMap<String, Value>,
) -> DriverResult<PostingList> {
Err(SQLError::UnknownFunction(format!("operator::{kind}")))
}
pub(super) fn attention_query_features(
&self,
signals: &[OperatorTree],
explicit: &[f64],
) -> DriverResult<Vec<f64>> {
if !explicit.is_empty() {
return Ok(explicit.to_vec());
}
let Some(table_state) = self
.engine
.table(self.table)
.map_err(|error| operator_execution_error("resolve attention table", error))?
else {
return Err(SQLError::UnknownTable(self.table.to_string()));
};
let idx_guard = table_state.inverted_index.read();
let index_stats = idx_guard
.stats()
.map_err(|error| operator_execution_error("index statistics", error))?;
if let Some((field, query)) = first_text_signal(signals) {
let analyzer = idx_guard.get_search_analyzer(&field);
let terms = analyzer
.analyze(&query)
.map_err(|error| operator_execution_error("attention query analysis", error))?;
return Ok(
uqa_fusion::extract_query_features(&index_stats, &terms, Some(&field)).to_vec(),
);
}
Ok(vec![0.0; uqa_fusion::N_QUERY_FEATURES])
}
pub(super) fn require_column(&self, field: &str) -> DriverResult<()> {
let columns = self
.engine
.try_describe_query_table(self.table)
.map_err(|error| operator_execution_error("resolve operator table", error))?;
let Some(columns) = columns else {
return Err(SQLError::UnknownTable(self.table.to_string()));
};
if !columns.is_empty() && !columns.iter().any(|column| column.name == field) {
return Err(SQLError::UnknownColumn(field.to_string()));
}
Ok(())
}
pub(super) fn require_vector_query(
&self,
field: &str,
query_vector: &[f32],
) -> DriverResult<()> {
if !self
.engine
.has_table(self.table)
.map_err(|error| operator_execution_error("resolve vector table", error))?
{
return Err(SQLError::UnknownTable(self.table.to_string()));
}
let declared_type = self
.engine
.column_type(self.table, field)
.map_err(|error| operator_execution_error("resolve vector column", error))?;
if let Some(column_type) = declared_type.as_ref() {
if !matches!(column_type, ColumnType::Vector(_) | ColumnType::Tensor(_)) {
return Err(SQLError::TypeMismatch(format!(
"vector search requires a VECTOR or TENSOR field, but {field:?} is {column_type:?}"
)));
}
}
let table = self
.engine
.table(self.table)
.map_err(|error| operator_execution_error("resolve vector table", error))?
.ok_or_else(|| SQLError::UnknownTable(self.table.to_string()))?;
let indexes = table.vector_indexes.read();
let index = indexes
.get(field)
.ok_or_else(|| match declared_type.as_ref() {
Some(ColumnType::Vector(_) | ColumnType::Tensor(_)) => SQLError::Unsupported(
format!("vector field {field:?} has no physical vector index"),
),
Some(column_type) => SQLError::Internal(format!(
"non-vector field {field:?} with type {column_type:?} passed vector validation"
)),
None => SQLError::UnknownColumn(field.to_string()),
})?;
let indexed_dimensions = index.dimensions() as usize;
let expected_dimensions = match declared_type.as_ref() {
Some(ColumnType::Vector(dimensions) | ColumnType::Tensor(dimensions)) => {
*dimensions as usize
}
Some(column_type) => {
return Err(SQLError::Internal(format!(
"non-vector field {field:?} with type {column_type:?} passed vector validation"
)))
}
None => indexed_dimensions,
};
if expected_dimensions != indexed_dimensions {
return Err(SQLError::Internal(format!(
"vector schema for {field:?} declares {expected_dimensions} dimensions but its index has {indexed_dimensions}"
)));
}
if query_vector.len() != expected_dimensions {
return Err(SQLError::TypeMismatch(format!(
"vector query for {field:?} has {} dimensions, expected {expected_dimensions}",
query_vector.len()
)));
}
if query_vector.iter().any(|value| !value.is_finite()) {
return Err(SQLError::TypeMismatch(format!(
"vector query for {field:?} must contain only finite values"
)));
}
Ok(())
}
pub(super) fn bridge_context(&self) -> DriverResult<uqa_operators::base::ExecutionContext> {
if self.table.is_empty() {
return Ok(uqa_operators::base::ExecutionContext::new());
}
self.engine
.snapshot_context(self.table)?
.ok_or_else(|| SQLError::UnknownTable(self.table.to_string()))
}
pub(super) fn bridge_context_for_projection(
&self,
doc_ids: &[DocId],
fields: &[&str],
) -> DriverResult<uqa_operators::base::ExecutionContext> {
let columns = self
.engine
.try_describe_query_table(self.table)
.map_err(|error| operator_execution_error("resolve projected operator table", error))?
.ok_or_else(|| SQLError::UnknownTable(self.table.to_string()))?;
let projection = fields
.iter()
.map(|field| (*field).to_string())
.collect::<Vec<_>>();
if !crate::engine_generated::projection_contains_virtual_generated_column(
&columns,
&projection,
) {
return self.bridge_context();
}
let documents =
self.engine
.get_documents_with_virtual_projection(self.table, doc_ids, &projection)?;
let mut store = uqa_storage::MemoryDocumentStore::new();
for (doc_id, document) in documents {
uqa_storage::DocumentStore::put(&mut store, doc_id, document).map_err(|error| {
operator_execution_error("build projected operator snapshot", error)
})?;
}
self.engine
.snapshot_context_with_document_store(self.table, std::sync::Arc::new(store))?
.ok_or_else(|| SQLError::UnknownTable(self.table.to_string()))
}
pub(super) fn facet_vector_inline(
&self,
vec_pl: &PostingList,
facet_field: &str,
) -> DriverResult<PostingList> {
use std::collections::BTreeMap;
self.require_column(facet_field)?;
let doc_ids = vec_pl
.entries()
.iter()
.map(|entry| entry.doc_id)
.collect::<Vec<_>>();
let values = self
.engine
.get_document_fields(self.table, &doc_ids, facet_field)?;
let mut counts: BTreeMap<String, u64> = BTreeMap::new();
for entry in vec_pl.entries() {
let Some(value) = values.get(&entry.doc_id) else {
return Err(SQLError::Internal(format!(
"vector facet candidate {} is missing from table `{}`",
entry.doc_id, self.table
)));
};
if !matches!(value, Value::Null) {
let key = match value {
Value::Str(s) => s.clone(),
Value::Int(n) => n.to_string(),
Value::Float(f) => format!("{f}"),
Value::Bool(b) => b.to_string(),
other => format!("{other:?}"),
};
let count = counts.entry(key).or_insert(0);
*count = count.checked_add(1).ok_or_else(|| {
SQLError::Internal("vector facet count overflowed u64".to_string())
})?;
}
}
let mut entries: Vec<PostingEntry> = Vec::with_capacity(counts.len());
for (i, (value, count)) in counts.into_iter().enumerate() {
let count_value = i64::try_from(count).map_err(|_| {
SQLError::Internal(format!(
"vector facet count {count} exceeds the SQL BIGINT range"
))
})?;
if count > 9_007_199_254_740_992 {
return Err(SQLError::Internal(format!(
"vector facet count {count} cannot be represented exactly as an f64 score"
)));
}
let bucket_id = DocId::try_from(i).map_err(|_| {
SQLError::Internal(format!(
"vector facet bucket index {i} exceeds the document-id range"
))
})?;
let mut fields = std::collections::BTreeMap::new();
fields.insert(
"_facet_field".to_string(),
Value::Str(facet_field.to_string()),
);
fields.insert("_facet_value".to_string(), Value::Str(value));
fields.insert("_facet_count".to_string(), Value::Int(count_value));
entries.push(PostingEntry::new(
bucket_id,
Payload {
positions: Vec::new(),
score: count as f64,
fields,
},
));
}
Ok(PostingList::from_sorted_unchecked(entries))
}
}