Build a `SqlPlan::KvInsert` from a VALUES clause. Shared by plain INSERT, UPSERT, and `INSERT ... ON CONFLICT (key) DO UPDATE` — the three paths differ only in `intent` and `on_conflict_updates`, never in how entries are extracted from the row exprs. `pk_col` is the schema-defined primary-key column name from `CollectionInfo::primary_key`. When supplied, that column is used as the KV key regardl
(
table_name: String,
columns: &[String],
rows_ast: &[Vec<ast::Expr>],
intent: KvInsertIntent,
on_conflict_updates: Vec<(String, SqlExpr)>,
pk_col: Option<&str>,
)
| 306 | /// the literal name `"key"` when `pk_col` is `None` (legacy / generic |
| 307 | /// KV collections that use the built-in key/value column convention). |
| 308 | pub(super) fn build_kv_insert_plan( |
| 309 | table_name: String, |
| 310 | columns: &[String], |
| 311 | rows_ast: &[Vec<ast::Expr>], |
| 312 | intent: KvInsertIntent, |
| 313 | on_conflict_updates: Vec<(String, SqlExpr)>, |
| 314 | pk_col: Option<&str>, |
| 315 | ) -> Result<Vec<SqlPlan>> { |
| 316 | let key_col_name = pk_col.unwrap_or("key"); |
| 317 | let key_idx = columns.iter().position(|c| c == key_col_name); |
| 318 | let ttl_idx = columns.iter().position(|c| c == "ttl"); |
| 319 | // When using a named primary-key column (e.g. `k STRING PRIMARY KEY`), we |
| 320 | // store the key bytes in the KV key slot AND also keep the column in the |
| 321 | // value map. This allows scan filters on the primary-key column (e.g. |
| 322 | // `WHERE k = 'x'`) and projection (e.g. `SELECT k FROM ...`) to work |
| 323 | // without teaching the KV scan handler to inspect the raw key bytes. |
| 324 | // The only column we exclude from the value map is the built-in `"key"` |
| 325 | // sentinel (used by raw key/value KV collections) and `"ttl"`. |
| 326 | let exclude_from_value: std::collections::HashSet<usize> = { |
| 327 | let mut s = std::collections::HashSet::new(); |
| 328 | // Exclude the raw "key" sentinel column (not a named PK column). |
| 329 | if key_col_name == "key" |
| 330 | && let Some(idx) = key_idx |
| 331 | { |
| 332 | s.insert(idx); |
| 333 | } |
| 334 | if let Some(idx) = ttl_idx { |
| 335 | s.insert(idx); |
| 336 | } |
| 337 | s |
| 338 | }; |
| 339 | let mut entries = Vec::with_capacity(rows_ast.len()); |
| 340 | let mut ttl_secs: u64 = 0; |
| 341 | for row_exprs in rows_ast { |
| 342 | let key_val = match key_idx { |
| 343 | Some(idx) => expr_to_sql_value(&row_exprs[idx])?, |
| 344 | None => SqlValue::String(String::new()), |
| 345 | }; |
| 346 | if let Some(idx) = ttl_idx { |
| 347 | match expr_to_sql_value(&row_exprs[idx]) { |
| 348 | Ok(SqlValue::Int(n)) => ttl_secs = n.max(0) as u64, |
| 349 | Ok(SqlValue::Float(f)) => ttl_secs = f.max(0.0) as u64, |
| 350 | _ => {} |
| 351 | } |
| 352 | } |
| 353 | let value_cols: Vec<(String, SqlValue)> = columns |
| 354 | .iter() |
| 355 | .enumerate() |
| 356 | .filter(|(i, _)| !exclude_from_value.contains(i)) |
| 357 | .map(|(i, col)| { |
| 358 | let val = expr_to_sql_value(&row_exprs[i])?; |
| 359 | Ok((col.clone(), val)) |
| 360 | }) |
| 361 | .collect::<Result<Vec<_>>>()?; |
| 362 | entries.push((key_val, value_cols)); |
| 363 | } |
| 364 | Ok(vec![SqlPlan::KvInsert { |
| 365 | collection: table_name, |
no test coverage detected