use crate::errors::ExecutorError;
pub fn enforce_primary_key_constraint(
db: &vibesql_storage::Database,
schema: &vibesql_catalog::TableSchema,
table_name: &str,
row_values: &[vibesql_types::SqlValue],
batch_pk_values: &[Vec<vibesql_types::SqlValue>],
) -> Result<(), ExecutorError> {
if let Some(pk_indices) = schema.get_primary_key_indices() {
let new_pk_values: Vec<vibesql_types::SqlValue> =
pk_indices.iter().map(|&idx| row_values[idx].clone()).collect();
if new_pk_values.contains(&vibesql_types::SqlValue::Null) {
return Ok(());
}
if batch_pk_values.contains(&new_pk_values) {
let pk_col_names: Vec<String> = schema.primary_key.as_ref().unwrap().clone();
let qualified_cols: Vec<String> =
pk_col_names.iter().map(|col| format!("{}.{}", table_name, col)).collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
let table = db
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
if table.is_in_append_mode() {
return Ok(());
}
let snapshot = crate::mvcc::read_snapshot(db);
if let Some(pk_index) = table.primary_key_index() {
if let Some(&row_idx) = pk_index.get(&new_pk_values) {
if table.is_row_visible(row_idx, &snapshot) {
let pk_col_names: Vec<String> = schema.primary_key.as_ref().unwrap().clone();
let qualified_cols: Vec<String> =
pk_col_names.iter().map(|col| format!("{}.{}", table_name, col)).collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
} else {
for (_idx, existing_row) in table.scan_visible(&snapshot) {
let existing_pk_values: Vec<vibesql_types::SqlValue> =
pk_indices.iter().filter_map(|&idx| existing_row.get(idx).cloned()).collect();
if new_pk_values == existing_pk_values {
let pk_col_names: Vec<String> = schema.primary_key.as_ref().unwrap().clone();
let qualified_cols: Vec<String> =
pk_col_names.iter().map(|col| format!("{}.{}", table_name, col)).collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
}
}
Ok(())
}
pub fn enforce_unique_constraints(
db: &vibesql_storage::Database,
schema: &vibesql_catalog::TableSchema,
table_name: &str,
row_values: &[vibesql_types::SqlValue],
batch_unique_values: &[Vec<Vec<vibesql_types::SqlValue>>],
) -> Result<(), ExecutorError> {
let unique_constraint_indices = schema.get_unique_constraint_indices();
for (constraint_idx, unique_indices) in unique_constraint_indices.iter().enumerate() {
let new_unique_values: Vec<vibesql_types::SqlValue> =
unique_indices.iter().map(|&idx| row_values[idx].clone()).collect();
if new_unique_values.contains(&vibesql_types::SqlValue::Null) {
continue;
}
if constraint_idx < batch_unique_values.len()
&& batch_unique_values[constraint_idx].contains(&new_unique_values)
{
let unique_col_names: Vec<String> = schema.unique_constraints[constraint_idx].clone();
let qualified_cols: Vec<String> =
unique_col_names.iter().map(|col| format!("{}.{}", table_name, col)).collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
let table = db
.get_table(table_name)
.ok_or_else(|| ExecutorError::TableNotFound(table_name.to_string()))?;
let snapshot = crate::mvcc::read_snapshot(db);
if constraint_idx < table.unique_indexes().len() {
let unique_index = &table.unique_indexes()[constraint_idx];
if let Some(&row_idx) = unique_index.get(&new_unique_values) {
if table.is_row_visible(row_idx, &snapshot) {
let unique_col_names: Vec<String> =
schema.unique_constraints[constraint_idx].clone();
let qualified_cols: Vec<String> = unique_col_names
.iter()
.map(|col| format!("{}.{}", table_name, col))
.collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
} else {
for (_idx, existing_row) in table.scan_visible(&snapshot) {
let existing_unique_values: Vec<vibesql_types::SqlValue> = unique_indices
.iter()
.filter_map(|&idx| existing_row.get(idx).cloned())
.collect();
if existing_unique_values.contains(&vibesql_types::SqlValue::Null) {
continue;
}
if new_unique_values == existing_unique_values {
let unique_col_names: Vec<String> =
schema.unique_constraints[constraint_idx].clone();
let qualified_cols: Vec<String> = unique_col_names
.iter()
.map(|col| format!("{}.{}", table_name, col))
.collect();
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
qualified_cols.join(", ")
)));
}
}
}
}
Ok(())
}
pub fn enforce_check_constraints(
schema: &vibesql_catalog::TableSchema,
row_values: &[vibesql_types::SqlValue],
) -> Result<(), ExecutorError> {
if !schema.check_constraints.is_empty() {
let row = vibesql_storage::Row::new(row_values.to_vec());
let evaluator = crate::evaluator::ExpressionEvaluator::new(schema)
.with_schema_context(crate::evaluator::SchemaExprContext::CheckConstraint);
for (constraint_name, check_expr) in &schema.check_constraints {
let result = evaluator.eval(check_expr, &row)?;
if result == vibesql_types::SqlValue::Boolean(false) {
return Err(ExecutorError::SqliteCompatError(format!(
"CHECK constraint failed: {}",
constraint_name
)));
}
}
}
Ok(())
}
pub fn enforce_unique_indexes(
db: &vibesql_storage::Database,
schema: &vibesql_catalog::TableSchema,
table_name: &str,
row_values: &[vibesql_types::SqlValue],
) -> Result<(), ExecutorError> {
let indexes_for_table = db.list_indexes_for_table(table_name);
let snapshot = crate::mvcc::read_snapshot(db);
let table = db.get_table(table_name);
for index_name in indexes_for_table {
if let Some(index_metadata) = db.get_index(&index_name) {
if !index_metadata.unique {
continue;
}
if let Some(predicate) = index_metadata.where_clause.as_deref() {
let candidate_row = vibesql_storage::Row::new(row_values.to_vec());
let evaluator = crate::evaluator::ExpressionEvaluator::new(schema)
.with_schema_context(crate::evaluator::SchemaExprContext::Index);
let satisfied = match evaluator.eval(predicate, &candidate_row) {
Ok(v) => crate::partial_index_maintenance::is_predicate_truthy(&v),
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(_) => false,
};
if !satisfied {
continue;
}
}
if index_metadata.columns.iter().any(|col| col.is_expression()) {
enforce_unique_expression_index(
schema,
index_metadata,
row_values,
table,
&snapshot,
)?;
continue;
}
let mut key_values = Vec::new();
for index_col in &index_metadata.columns {
let col_idx =
schema.get_column_index(index_col.expect_column_name()).ok_or_else(|| {
ExecutorError::ColumnNotFound {
column_name: index_col.expect_column_name().to_string(),
table_name: table_name.to_string(),
searched_tables: vec![table_name.to_string()],
available_columns: schema
.columns
.iter()
.map(|c| c.name.clone())
.collect(),
}
})?;
key_values.push(row_values[col_idx].clone());
}
if key_values.contains(&vibesql_types::SqlValue::Null) {
continue;
}
if let Some(index_data) = db.get_index_data(&index_name) {
if let Some(row_indices) = index_data.get(&key_values) {
let key_is_live = match table {
Some(t) => {
row_indices.iter().any(|&idx| t.is_row_visible(idx, &snapshot))
}
None => !row_indices.is_empty(),
};
if key_is_live {
let columns_str = index_metadata
.columns
.iter()
.map(|col| format!("{}.{}", table_name, col.expect_column_name()))
.collect::<Vec<_>>()
.join(", ");
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: {}",
columns_str
)));
}
}
}
}
}
Ok(())
}
fn enforce_unique_expression_index(
schema: &vibesql_catalog::TableSchema,
index_metadata: &vibesql_storage::database::indexes::IndexMetadata,
row_values: &[vibesql_types::SqlValue],
table: Option<&vibesql_storage::Table>,
snapshot: &vibesql_storage::TxnSnapshot,
) -> Result<(), ExecutorError> {
let Some(table) = table else {
return Ok(());
};
let evaluator = crate::evaluator::ExpressionEvaluator::new(schema)
.with_schema_context(crate::evaluator::SchemaExprContext::Index);
let candidate_row = vibesql_storage::Row::new(row_values.to_vec());
let Some(new_key) =
eval_expression_index_key(&evaluator, schema, index_metadata, &candidate_row)?
else {
return Ok(());
};
for (_idx, existing_row) in table.scan_visible(snapshot) {
if let Some(predicate) = index_metadata.where_clause.as_deref() {
let in_index = match evaluator.eval(predicate, existing_row) {
Ok(v) => crate::partial_index_maintenance::is_predicate_truthy(&v),
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(_) => false,
};
if !in_index {
continue;
}
}
if eval_expression_index_key(&evaluator, schema, index_metadata, existing_row)?.as_deref()
== Some(&new_key[..])
{
return Err(ExecutorError::SqliteCompatError(format!(
"UNIQUE constraint failed: index '{}'",
index_metadata.index_name
)));
}
}
Ok(())
}
fn eval_expression_index_key(
evaluator: &crate::evaluator::ExpressionEvaluator,
schema: &vibesql_catalog::TableSchema,
index_metadata: &vibesql_storage::database::indexes::IndexMetadata,
row: &vibesql_storage::Row,
) -> Result<Option<Vec<vibesql_types::SqlValue>>, ExecutorError> {
let mut key = Vec::with_capacity(index_metadata.columns.len());
for col in &index_metadata.columns {
let value = if let Some(expr) = col.get_expression() {
match evaluator.eval(expr, row) {
Ok(v) => v,
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(_) => vibesql_types::SqlValue::Null,
}
} else if let Some(name) = col.column_name() {
match schema.get_column_index(name).and_then(|idx| row.values.get(idx)) {
Some(v) => v.clone(),
None => return Ok(None),
}
} else {
vibesql_types::SqlValue::Null
};
if matches!(value, vibesql_types::SqlValue::Null) {
return Ok(None);
}
key.push(value);
}
Ok(Some(key))
}
pub fn enforce_index_expression_determinism(
db: &vibesql_storage::Database,
schema: &vibesql_catalog::TableSchema,
table_name: &str,
row_values: &[vibesql_types::SqlValue],
) -> Result<(), ExecutorError> {
let indexes = db.catalog.get_table_indexes(table_name);
if !indexes
.iter()
.any(|idx| idx.is_partial() || idx.columns.iter().any(|col| col.is_expression()))
{
return Ok(());
}
let row = vibesql_storage::Row::new(row_values.to_vec());
let evaluator = crate::evaluator::ExpressionEvaluator::new(schema)
.with_schema_context(crate::evaluator::SchemaExprContext::Index);
for index in indexes {
if let Some(predicate) = index.where_clause.as_deref() {
let in_index = match evaluator.eval(predicate, &row) {
Ok(v) => crate::partial_index_maintenance::is_predicate_truthy(&v),
Err(e) if e.is_non_deterministic_use() => return Err(e),
Err(_) => false,
};
if !in_index {
continue;
}
}
for col in &index.columns {
if let Some(expr) = col.get_expression() {
if let Err(e) = evaluator.eval(expr, &row) {
if e.is_non_deterministic_use() {
return Err(e);
}
}
}
}
}
Ok(())
}