1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Value update logic for UPDATE operations
use std::collections::HashSet;
use vibesql_ast::Assignment;
use crate::{
errors::ExecutorError, evaluator::ExpressionEvaluator, insert::validation::coerce_value,
};
/// Applies assignment expressions to rows
pub struct ValueUpdater<'a> {
schema: &'a vibesql_catalog::TableSchema,
evaluator: &'a ExpressionEvaluator<'a>,
}
impl<'a> ValueUpdater<'a> {
/// Create a new value updater
pub fn new(
schema: &'a vibesql_catalog::TableSchema,
evaluator: &'a ExpressionEvaluator<'a>,
_table_name: &'a str, // Kept for API compatibility
) -> Self {
Self { schema, evaluator }
}
/// Apply assignments to a row
///
/// Returns the updated row and a set of changed column indices.
/// Expressions are evaluated against the original row (two-phase semantics).
pub fn apply_assignments(
&self,
original_row: &vibesql_storage::Row,
assignments: &[Assignment],
) -> Result<(vibesql_storage::Row, HashSet<usize>), ExecutorError> {
let mut new_row = original_row.clone();
let mut changed_columns = HashSet::new();
// Apply each assignment
for assignment in assignments {
// Check if this is a rowid assignment (SQLite compatibility)
let col_name_lower = assignment.column.to_lowercase();
let is_rowid =
col_name_lower == "rowid" || col_name_lower == "_rowid_" || col_name_lower == "oid";
// A *real* column literally named `rowid`/`oid`/`_rowid_` shadows the
// system rowid alias (triggerD-1.3/1.4, ticket
// [34d2ae1c6d08b5271ba5e5592936d4a1d913ffe3]). `UPDATE t SET rowid=..`
// on `t(rowid, ...)` must write that ordinary column, not relocate the
// row's internal rowid. Only treat the assignment as a system-rowid
// update when no ordinary column shadows the name — this preserves the
// virtual-rowid relocation behavior (#5517) for tables that have *no*
// such column. The INTEGER PRIMARY KEY alias is itself a real column
// (`rowid_alias_column`), so it also flows through the normal
// column-update path below and writes the PK column as before.
let shadowing_column = self.schema.get_column_index(&assignment.column);
if is_rowid && shadowing_column.is_none() {
// Handle rowid update
// If the table has an INTEGER PRIMARY KEY (rowid alias), update that column
// Otherwise, update the row's internal row_id field
if let Some(ipk_col_idx) = self.schema.rowid_alias_column {
// The INTEGER PRIMARY KEY column IS the rowid - update it
let new_value = self.evaluator.eval(&assignment.value, original_row)?;
new_row
.set(ipk_col_idx, new_value)
.map_err(|e| ExecutorError::StorageError(e.to_string()))?;
changed_columns.insert(ipk_col_idx);
} else {
// No INTEGER PRIMARY KEY - update the virtual rowid
// Evaluate the new rowid value
let new_value = self.evaluator.eval(&assignment.value, original_row)?;
// Convert to u64 for row_id
let new_rowid = match new_value {
vibesql_types::SqlValue::Integer(i) => i as u64,
vibesql_types::SqlValue::Bigint(i) => i as u64,
other => {
return Err(ExecutorError::UnsupportedExpression(format!(
"ROWID must be an integer, got {:?}",
other
)));
}
};
new_row.row_id = Some(new_rowid);
// Note: We don't add anything to changed_columns since row_id
// is not a real column. The storage layer will use the updated row_id.
}
continue;
}
// Find column index
// Use SQLite-compatible "no such column: X" error format
let col_index = self.schema.get_column_index(&assignment.column).ok_or_else(|| {
ExecutorError::NoSuchColumn { column_ref: assignment.column.clone() }
})?;
// Evaluate new value expression
// Handle DEFAULT specially before evaluating other expressions
let new_value = match &assignment.value {
vibesql_ast::Expression::Default => {
// Use column's default value, or NULL if no default is defined
let column = &self.schema.columns[col_index];
if let Some(default_expr) = &column.default_value {
// Evaluate the default expression (currently only supports literals)
match default_expr {
vibesql_ast::Expression::Literal(lit) => lit.clone(),
_ => {
return Err(ExecutorError::UnsupportedExpression(format!(
"Complex default expressions not yet supported for column '{}'",
column.name
)))
}
}
} else {
// No default value defined, use NULL
vibesql_types::SqlValue::Null
}
}
_ => {
// Evaluate other expressions against ORIGINAL row
self.evaluator.eval(&assignment.value, original_row)?
}
};
// Apply type affinity coercion (SQLite compatibility)
// This ensures UPDATE applies the same type conversion as INSERT
// e.g., UPDATE t SET r='5' on a REAL column stores 5.0, not '5'
let column = &self.schema.columns[col_index];
let coerced_value = coerce_value(new_value, &column.data_type)?;
// Update column in new row
new_row
.set(col_index, coerced_value)
.map_err(|e| ExecutorError::StorageError(e.to_string()))?;
// Track that this column changed
changed_columns.insert(col_index);
}
Ok((new_row, changed_columns))
}
}