1use nodedb_types::DatabaseId;
6use sqlparser::ast;
7
8use super::super::dml_helpers::{
9 VectorPrimaryUpdateParams, build_vector_primary_delete_plan,
10 build_vector_primary_truncate_plan, build_vector_primary_update_plan,
11 check_declared_float_ranges_in_assignments, check_declared_int_ranges_in_assignments,
12 extract_point_keys, extract_table_name_from_table_with_joins, is_vector_primary,
13};
14use crate::engine_rules::{self, DeleteParams, TruncateParams, UpdateParams};
15use crate::error::{Result, SqlError};
16use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_object_name_checked};
17use crate::planner::declared_type_coerce::coerce_assignments_to_declared_types;
18use crate::resolver::ColumnScope;
19use crate::resolver::columns::{ResolvedTable, TableScope};
20use crate::resolver::expr::convert_expr;
21use crate::types::*;
22
23pub fn plan_update(stmt: &ast::Statement, catalog: &dyn SqlCatalog) -> Result<Vec<SqlPlan>> {
25 let ast::Statement::Update(update) = stmt else {
26 return Err(SqlError::Parse {
27 detail: "expected UPDATE statement".into(),
28 });
29 };
30
31 if update.limit.is_some() {
32 return Err(SqlError::Unsupported {
33 detail: "UPDATE ... LIMIT is not supported".into(),
34 });
35 }
36
37 if let Some(from_kind) = update.from.as_ref() {
39 return super::update_from::plan_update_from(update, from_kind, catalog);
40 }
41
42 let table_name = extract_table_name_from_table_with_joins(&update.table)?;
43 crate::parser::normalize::table_name_from_factor(&update.table.relation)?;
46 let info = catalog
47 .get_collection(DatabaseId::DEFAULT, &table_name)?
48 .ok_or_else(|| SqlError::UnknownTable {
49 name: table_name.clone(),
50 })?;
51
52 let target_scope = TableScope::single(ResolvedTable {
53 name: table_name.clone(),
54 alias: None,
55 info: info.clone(),
56 })?;
57
58 let mut assigns = convert_assignments(&update.assignments, &target_scope, &target_scope)?;
59 coerce_assignments_to_declared_types(&info.columns, &mut assigns, info.primary_key.as_deref())?;
67 check_declared_int_ranges_in_assignments(&info.columns, &assigns)?;
68 check_declared_float_ranges_in_assignments(&info.columns, &assigns)?;
69
70 let filters = match &update.selection {
71 Some(expr) => super::super::select::convert_where_to_filters(expr, &target_scope)?,
72 None => Vec::new(),
73 };
74
75 let target_keys = extract_point_keys(update.selection.as_ref(), &info);
76
77 if is_vector_primary(&info)
80 && let Some(ref vpc) = info.vector_primary
81 {
82 return build_vector_primary_update_plan(VectorPrimaryUpdateParams {
83 collection: &table_name,
84 info: &info,
85 vpc,
86 assignments: assigns,
87 filters,
88 target_keys,
89 returning: update.returning.is_some(),
90 });
91 }
92
93 let rules = engine_rules::resolve_engine_rules(info.engine);
94 rules.plan_update(UpdateParams {
95 collection: table_name,
96 assignments: assigns,
97 filters,
98 target_keys,
99 returning: update.returning.is_some(),
100 })
101}
102
103pub(super) fn convert_assignments(
105 assignments: &[ast::Assignment],
106 target_scope: &TableScope,
107 value_scope: &TableScope,
108) -> Result<Vec<(String, SqlExpr)>> {
109 assignments
110 .iter()
111 .map(|a| {
112 let col = match &a.target {
113 ast::AssignmentTarget::ColumnName(name) => {
114 if name.0.len() > 1 {
115 return Err(SqlError::Unsupported {
116 detail: format!(
117 "qualified column name in SET target: {SCHEMA_QUALIFIED_MSG}"
118 ),
119 });
120 }
121 normalize_object_name_checked(name)?
122 }
123 ast::AssignmentTarget::Tuple(names) => names
124 .iter()
125 .map(normalize_object_name_checked)
126 .collect::<Result<Vec<_>>>()?
127 .join(","),
128 };
129 for name in col.split(',') {
130 target_scope.check_name(None, name)?;
131 }
132 let val = convert_expr(&a.value, &ColumnScope::Relations(value_scope))?;
133 Ok((col, val))
134 })
135 .collect()
136}
137
138pub fn plan_delete(stmt: &ast::Statement, catalog: &dyn SqlCatalog) -> Result<Vec<SqlPlan>> {
140 let ast::Statement::Delete(delete) = stmt else {
141 return Err(SqlError::Parse {
142 detail: "expected DELETE statement".into(),
143 });
144 };
145
146 if delete.limit.is_some() {
147 return Err(SqlError::Unsupported {
148 detail: "DELETE ... LIMIT is not supported".into(),
149 });
150 }
151 if !delete.order_by.is_empty() {
152 return Err(SqlError::Unsupported {
153 detail: "DELETE ... ORDER BY is not supported".into(),
154 });
155 }
156
157 let from_tables = match &delete.from {
158 ast::FromTable::WithFromKeyword(tables) | ast::FromTable::WithoutKeyword(tables) => tables,
159 };
160 let from = from_tables.first().ok_or_else(|| SqlError::Parse {
161 detail: "DELETE requires a FROM table".into(),
162 })?;
163 let table_name = extract_table_name_from_table_with_joins(from)?;
164 crate::parser::normalize::table_name_from_factor(&from.relation)?;
165 let info = catalog
166 .get_collection(DatabaseId::DEFAULT, &table_name)?
167 .ok_or_else(|| SqlError::UnknownTable {
168 name: table_name.clone(),
169 })?;
170
171 let target_scope = TableScope::single(ResolvedTable {
172 name: table_name.clone(),
173 alias: None,
174 info: info.clone(),
175 })?;
176
177 let filters = match &delete.selection {
178 Some(expr) => super::super::select::convert_where_to_filters(expr, &target_scope)?,
179 None => Vec::new(),
180 };
181
182 let target_keys = extract_point_keys(delete.selection.as_ref(), &info);
183
184 if is_vector_primary(&info)
186 && let Some(ref vpc) = info.vector_primary
187 {
188 return Ok(build_vector_primary_delete_plan(
189 &table_name,
190 &info,
191 vpc,
192 filters,
193 target_keys,
194 ));
195 }
196
197 let rules = engine_rules::resolve_engine_rules(info.engine);
198 rules.plan_delete(DeleteParams {
199 collection: table_name,
200 filters,
201 target_keys,
202 })
203}
204
205pub fn plan_truncate_stmt(stmt: &ast::Statement, catalog: &dyn SqlCatalog) -> Result<Vec<SqlPlan>> {
211 let ast::Statement::Truncate(truncate) = stmt else {
212 return Err(SqlError::Parse {
213 detail: "expected TRUNCATE statement".into(),
214 });
215 };
216 let restart_identity = matches!(
217 truncate.identity,
218 Some(sqlparser::ast::TruncateIdentityOption::Restart)
219 );
220 let mut plans = Vec::with_capacity(truncate.table_names.len());
221 for target in &truncate.table_names {
222 let table_name = normalize_object_name_checked(&target.name)?;
223 if catalog.array_exists(&table_name) {
226 plans.append(
227 &mut engine_rules::resolve_engine_rules(EngineType::Array).plan_truncate(
228 TruncateParams {
229 collection: table_name,
230 restart_identity,
231 },
232 )?,
233 );
234 continue;
235 }
236 let info = catalog
237 .get_collection(DatabaseId::DEFAULT, &table_name)?
238 .ok_or_else(|| SqlError::UnknownTable {
239 name: table_name.clone(),
240 })?;
241 if is_vector_primary(&info)
243 && let Some(ref vpc) = info.vector_primary
244 {
245 plans.push(build_vector_primary_truncate_plan(
246 &table_name,
247 vpc,
248 restart_identity,
249 ));
250 continue;
251 }
252 let rules = engine_rules::resolve_engine_rules(info.engine);
253 plans.append(&mut rules.plan_truncate(TruncateParams {
254 collection: table_name,
255 restart_identity,
256 })?);
257 }
258 Ok(plans)
259}