use crate::translate::plan::Operation;
use crate::vdbe::builder::TableRefIdCounter;
use crate::{
bail_parse_error,
schema::{Schema, Table},
util::normalize_ident,
vdbe::builder::{ProgramBuilder, ProgramBuilderOpts, QueryMode},
SymbolTable,
};
use limbo_sqlite3_parser::ast::{self, Expr, ResultColumn, SortOrder, Update};
use super::emitter::emit_program;
use super::optimizer::optimize_plan;
use super::plan::{
ColumnUsedMask, IterationDirection, JoinedTable, Plan, ResultSetColumn, TableReferences,
UpdatePlan,
};
use super::planner::bind_column_references;
use super::planner::{parse_limit, parse_where};
pub fn translate_update(
query_mode: QueryMode,
schema: &Schema,
body: &mut Update,
syms: &SymbolTable,
mut program: ProgramBuilder,
) -> crate::Result<ProgramBuilder> {
let mut plan = prepare_update_plan(schema, body, &mut program.table_reference_counter)?;
optimize_plan(&mut plan, schema)?;
let opts = ProgramBuilderOpts {
query_mode,
num_cursors: 1,
approx_num_insns: 20,
approx_num_labels: 4,
};
program.extend(&opts);
emit_program(&mut program, plan, schema, syms, |_| {})?;
Ok(program)
}
pub fn translate_update_with_after(
query_mode: QueryMode,
schema: &Schema,
body: &mut Update,
syms: &SymbolTable,
mut program: ProgramBuilder,
after: impl FnOnce(&mut ProgramBuilder),
) -> crate::Result<ProgramBuilder> {
let mut plan = prepare_update_plan(schema, body, &mut program.table_reference_counter)?;
optimize_plan(&mut plan, schema)?;
let opts = ProgramBuilderOpts {
query_mode,
num_cursors: 1,
approx_num_insns: 20,
approx_num_labels: 4,
};
program.extend(&opts);
emit_program(&mut program, plan, schema, syms, after)?;
Ok(program)
}
pub fn prepare_update_plan(
schema: &Schema,
body: &mut Update,
table_ref_counter: &mut TableRefIdCounter,
) -> crate::Result<Plan> {
if body.with.is_some() {
bail_parse_error!("WITH clause is not supported");
}
if body.or_conflict.is_some() {
bail_parse_error!("ON CONFLICT clause is not supported");
}
let table_name = &body.tbl_name.name;
#[cfg(not(feature = "index_experimental"))]
{
if schema.table_has_indexes(&table_name.to_string()) {
bail_parse_error!(
"UPDATE table disabled for table with indexes and without index_experimental feature flag"
);
}
}
let table = match schema.get_table(table_name.0.as_str()) {
Some(table) => table,
None => bail_parse_error!("Parse error: no such table: {}", table_name),
};
let iter_dir = body
.order_by
.as_ref()
.and_then(|order_by| {
order_by.first().and_then(|ob| {
ob.order.map(|o| match o {
SortOrder::Asc => IterationDirection::Forwards,
SortOrder::Desc => IterationDirection::Backwards,
})
})
})
.unwrap_or(IterationDirection::Forwards);
let joined_tables = vec![JoinedTable {
table: match table.as_ref() {
Table::Virtual(vtab) => Table::Virtual(vtab.clone()),
Table::BTree(btree_table) => Table::BTree(btree_table.clone()),
_ => unreachable!(),
},
identifier: table_name.0.clone(),
internal_id: table_ref_counter.next(),
op: Operation::Scan {
iter_dir,
index: None,
},
join_info: None,
col_used_mask: ColumnUsedMask::new(),
}];
let mut table_references = TableReferences::new(joined_tables, vec![]);
let set_clauses = body
.sets
.iter_mut()
.map(|set| {
let ident = normalize_ident(set.col_names[0].0.as_str());
let col_index = table
.columns()
.iter()
.enumerate()
.find_map(|(i, col)| {
col.name
.as_ref()
.filter(|name| name.eq_ignore_ascii_case(&ident))
.map(|_| i)
})
.ok_or_else(|| {
crate::LimboError::ParseError(format!(
"column '{}' not found in table '{}'",
ident, table_name.0
))
})?;
let _ = bind_column_references(&mut set.expr, &mut table_references, None);
Ok((col_index, set.expr.clone()))
})
.collect::<Result<Vec<(usize, Expr)>, crate::LimboError>>()?;
let mut where_clause = vec![];
let mut result_columns = vec![];
if let Some(returning) = &mut body.returning {
for rc in returning.iter_mut() {
if let ResultColumn::Expr(expr, alias) = rc {
bind_column_references(expr, &mut table_references, None)?;
result_columns.push(ResultSetColumn {
expr: expr.clone(),
alias: alias.as_ref().and_then(|a| {
if let ast::As::As(name) = a {
Some(name.to_string())
} else {
None
}
}),
contains_aggregates: false,
});
} else {
bail_parse_error!("Only expressions are allowed in RETURNING clause");
}
}
}
let order_by = body.order_by.as_ref().map(|order| {
order
.iter()
.map(|o| (o.expr.clone(), o.order.unwrap_or(SortOrder::Asc)))
.collect()
});
parse_where(
body.where_clause.as_ref().map(|w| *w.clone()),
&mut table_references,
Some(&result_columns),
&mut where_clause,
)?;
let (limit, offset) = body
.limit
.as_ref()
.map(|l| parse_limit(l))
.unwrap_or(Ok((None, None)))?;
let indexes = schema.get_indices(&table_name.0);
let indexes_to_update = indexes
.iter()
.filter(|index| {
index.columns.iter().any(|index_column| {
set_clauses
.iter()
.any(|(set_index_column, _)| index_column.pos_in_table == *set_index_column)
})
})
.cloned()
.collect();
Ok(Plan::Update(UpdatePlan {
table_references,
set_clauses,
where_clause,
returning: Some(result_columns),
order_by,
limit,
offset,
contains_constant_false_condition: false,
indexes_to_update,
}))
}