Plan an UPSERT statement (pre-processed from `UPSERT INTO` to `INSERT INTO`). Same parsing as INSERT but routes through `engine_rules.plan_upsert()`.
(ins: &ast::Insert, catalog: &dyn SqlCatalog)
| 169 | /// |
| 170 | /// Same parsing as INSERT but routes through `engine_rules.plan_upsert()`. |
| 171 | pub fn plan_upsert(ins: &ast::Insert, catalog: &dyn SqlCatalog) -> Result<Vec<SqlPlan>> { |
| 172 | let table_name = match &ins.table { |
| 173 | ast::TableObject::TableName(name) => normalize_object_name_checked(name)?, |
| 174 | ast::TableObject::TableFunction(_) => { |
| 175 | return Err(SqlError::Unsupported { |
| 176 | detail: "UPSERT INTO table function not supported".into(), |
| 177 | }); |
| 178 | } |
| 179 | }; |
| 180 | let info = catalog |
| 181 | .get_collection(DatabaseId::DEFAULT, &table_name)? |
| 182 | .ok_or_else(|| SqlError::UnknownTable { |
| 183 | name: table_name.clone(), |
| 184 | })?; |
| 185 | |
| 186 | let columns: Vec<String> = ins.columns.iter().map(normalize_ident).collect(); |
| 187 | |
| 188 | let source = ins.source.as_ref().ok_or_else(|| SqlError::Parse { |
| 189 | detail: "UPSERT requires VALUES".into(), |
| 190 | })?; |
| 191 | |
| 192 | let rows_ast = match &*source.body { |
| 193 | ast::SetExpr::Values(values) => &values.rows, |
| 194 | _ => { |
| 195 | return Err(SqlError::Unsupported { |
| 196 | detail: "UPSERT source must be VALUES".into(), |
| 197 | }); |
| 198 | } |
| 199 | }; |
| 200 | |
| 201 | // KV: upsert is just a PUT (natural overwrite). |
| 202 | if info.engine == EngineType::KeyValue { |
| 203 | return build_kv_insert_plan( |
| 204 | table_name, |
| 205 | &columns, |
| 206 | rows_ast, |
| 207 | KvInsertIntent::Put, |
| 208 | Vec::new(), |
| 209 | info.primary_key.as_deref(), |
| 210 | ); |
| 211 | } |
| 212 | |
| 213 | let rows = convert_value_rows(&columns, rows_ast)?; |
| 214 | let column_defaults: Vec<(String, String)> = info |
| 215 | .columns |
| 216 | .iter() |
| 217 | .filter_map(|c| c.default.as_ref().map(|d| (c.name.clone(), d.clone()))) |
| 218 | .collect(); |
| 219 | let column_schema: Vec<(String, String)> = info |
| 220 | .columns |
| 221 | .iter() |
| 222 | .filter_map(|c| c.raw_type.as_ref().map(|t| (c.name.clone(), t.clone()))) |
| 223 | .collect(); |
| 224 | let rules = engine_rules::resolve_engine_rules(info.engine); |
| 225 | rules.plan_upsert(engine_rules::UpsertParams { |
| 226 | collection: table_name, |
| 227 | columns, |
| 228 | rows, |
no test coverage detected