1use fsqlite_ast::{
8 BinaryOp as AstBinaryOp, ColumnRef, ConflictAction, DeleteStatement, Expr, FunctionArgs,
9 InsertSource, InsertStatement, Literal, PlaceholderType, QualifiedTableRef, ResultColumn,
10 SelectCore, SelectStatement, Span, UnaryOp as AstUnaryOp, UpdateStatement,
11};
12use fsqlite_parser::expr::parse_expr as parse_sql_expr;
13use fsqlite_types::opcode::{Label, Opcode, P4, ProgramBuilder};
14
15const OE_ROLLBACK: u16 = 1;
21const OE_ABORT: u16 = 2;
23const OE_FAIL: u16 = 3;
25const OE_IGNORE: u16 = 4;
27const OE_REPLACE: u16 = 5;
29
30fn conflict_action_to_oe(action: Option<&ConflictAction>) -> u16 {
32 match action {
33 Some(ConflictAction::Rollback) => OE_ROLLBACK,
34 None | Some(ConflictAction::Abort) => OE_ABORT,
35 Some(ConflictAction::Fail) => OE_FAIL,
36 Some(ConflictAction::Ignore) => OE_IGNORE,
37 Some(ConflictAction::Replace) => OE_REPLACE,
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct ColumnInfo {
48 pub name: String,
50 pub affinity: char,
53 pub default_value: Option<String>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct IndexSchema {
60 pub name: String,
62 pub root_page: i32,
64 pub columns: Vec<String>,
66 pub is_unique: bool,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct TableSchema {
73 pub name: String,
75 pub root_page: i32,
77 pub columns: Vec<ColumnInfo>,
79 pub indexes: Vec<IndexSchema>,
81}
82
83impl TableSchema {
84 #[must_use]
86 pub fn affinity_string(&self) -> String {
87 self.columns.iter().map(|c| c.affinity).collect()
88 }
89
90 #[must_use]
92 pub fn column_index(&self, name: &str) -> Option<usize> {
93 self.columns
94 .iter()
95 .position(|c| c.name.eq_ignore_ascii_case(name))
96 }
97
98 #[must_use]
101 pub fn index_for_column(&self, col_name: &str) -> Option<&IndexSchema> {
102 self.indexes.iter().find(|idx| {
103 idx.columns
104 .first()
105 .is_some_and(|c| c.eq_ignore_ascii_case(col_name))
106 })
107 }
108}
109
110#[derive(Debug, Clone, Default, PartialEq, Eq)]
112pub struct CodegenContext {
113 pub concurrent_mode: bool,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum CodegenError {
121 TableNotFound(String),
123 ColumnNotFound { table: String, column: String },
125 Unsupported(String),
127}
128
129impl std::fmt::Display for CodegenError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 Self::TableNotFound(name) => write!(f, "table not found: {name}"),
133 Self::ColumnNotFound { table, column } => {
134 write!(f, "column {column} not found in table {table}")
135 }
136 Self::Unsupported(msg) => write!(f, "unsupported: {msg}"),
137 }
138 }
139}
140
141impl std::error::Error for CodegenError {}
142
143fn find_table<'a>(schema: &'a [TableSchema], name: &str) -> Result<&'a TableSchema, CodegenError> {
148 schema
149 .iter()
150 .find(|t| t.name.eq_ignore_ascii_case(name))
151 .ok_or_else(|| CodegenError::TableNotFound(name.to_owned()))
152}
153
154fn table_name_from_qualified(qtr: &QualifiedTableRef) -> &str {
155 &qtr.name.name
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159enum BindParamRef {
160 Anonymous,
161 Numbered(i32),
162}
163
164#[allow(clippy::too_many_lines)]
177pub fn codegen_select(
178 b: &mut ProgramBuilder,
179 stmt: &SelectStatement,
180 schema: &[TableSchema],
181 _ctx: &CodegenContext,
182) -> Result<(), CodegenError> {
183 let core = match &stmt.body.select {
184 SelectCore::Select { .. } => &stmt.body.select,
185 SelectCore::Values(rows) => return codegen_select_values(b, rows),
186 };
187
188 let (columns, from, where_clause) = match core {
189 SelectCore::Select {
190 columns,
191 from,
192 where_clause,
193 ..
194 } => (columns, from, where_clause),
195 SelectCore::Values(_) => unreachable!(),
196 };
197
198 if from.is_none() {
200 return codegen_select_no_from(b, columns);
201 }
202 let from_clause = from.as_ref().expect("checked above");
203
204 let table_name = single_table_select_source_name(&from_clause.source)?;
205
206 let table = find_table(schema, table_name)?;
207 let cursor = 0_i32;
208
209 let end_label = b.emit_label();
211 let done_label = b.emit_label();
212
213 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
215
216 b.emit_op(Opcode::Transaction, 0, 0, 0, P4::None, 0);
218
219 let out_col_count = result_column_count(columns, table);
221 let out_regs = b.alloc_regs(out_col_count);
222
223 let rowid_param = extract_rowid_bind_param(where_clause.as_deref());
225 let index_eq = if rowid_param.is_none() {
227 extract_column_eq_bind(where_clause.as_deref())
228 } else {
229 None
230 };
231 if where_clause.is_some() && rowid_param.is_none() && index_eq.is_none() {
232 return Err(CodegenError::Unsupported(
233 "SELECT WHERE currently supports only `rowid = ?` or `indexed_col = ?`".to_owned(),
234 ));
235 }
236
237 let mut index_cursor_to_close: Option<i32> = None;
238
239 if let Some(param_idx) = rowid_param {
240 let rowid_reg = b.alloc_reg();
242 b.emit_op(Opcode::Variable, param_idx, rowid_reg, 0, P4::None, 0);
243 b.emit_op(
244 Opcode::OpenRead,
245 cursor,
246 table.root_page,
247 0,
248 P4::Table(table.name.clone()),
249 0,
250 );
251 b.emit_jump_to_label(
252 Opcode::SeekRowid,
253 cursor,
254 rowid_reg,
255 done_label,
256 P4::None,
257 0,
258 );
259
260 emit_column_reads(b, cursor, columns, table, out_regs)?;
262
263 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
265 } else if let Some((col_name, param_idx)) = &index_eq {
266 let Some(idx_schema) = table.index_for_column(col_name) else {
268 return codegen_select_column_eq_scan(
269 b,
270 cursor,
271 table,
272 columns,
273 out_regs,
274 out_col_count,
275 done_label,
276 end_label,
277 col_name,
278 *param_idx,
279 );
280 };
281 let idx_cursor = 1_i32;
282 index_cursor_to_close = Some(idx_cursor);
283 let param_reg = b.alloc_reg();
284 b.emit_op(Opcode::Variable, *param_idx, param_reg, 0, P4::None, 0);
285 b.emit_jump_to_label(Opcode::IsNull, param_reg, 0, done_label, P4::None, 0);
286
287 let min_rowid_reg = b.alloc_reg();
289 b.emit_op(Opcode::Int64, 0, min_rowid_reg, 0, P4::Int64(i64::MIN), 0);
290 let probe_key_reg = b.alloc_reg();
291 b.emit_op(Opcode::MakeRecord, param_reg, 2, probe_key_reg, P4::None, 0);
292
293 b.emit_op(
294 Opcode::OpenRead,
295 cursor,
296 table.root_page,
297 0,
298 P4::Table(table.name.clone()),
299 0,
300 );
301 b.emit_op(
302 Opcode::OpenRead,
303 idx_cursor,
304 idx_schema.root_page,
305 0,
306 P4::Index(idx_schema.name.clone()),
307 0,
308 );
309 b.emit_jump_to_label(
310 Opcode::SeekGE,
311 idx_cursor,
312 probe_key_reg,
313 done_label,
314 P4::None,
315 0,
316 );
317
318 let loop_start = b.current_addr();
319 b.emit_jump_to_label(
320 Opcode::IdxGT,
321 idx_cursor,
322 probe_key_reg,
323 done_label,
324 P4::None,
325 1,
326 );
327
328 let rowid_reg = b.alloc_reg();
329 b.emit_op(Opcode::IdxRowid, idx_cursor, rowid_reg, 0, P4::None, 0);
330 let skip_row_label = b.emit_label();
331 b.emit_jump_to_label(
332 Opcode::SeekRowid,
333 cursor,
334 rowid_reg,
335 skip_row_label,
336 P4::None,
337 0,
338 );
339
340 emit_column_reads(b, cursor, columns, table, out_regs)?;
342
343 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
345 b.resolve_label(skip_row_label);
346
347 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
348 let loop_target = loop_start as i32;
349 b.emit_op(Opcode::Next, idx_cursor, loop_target, 0, P4::None, 0);
350 } else {
351 return codegen_select_full_scan(
353 b,
354 cursor,
355 table,
356 columns,
357 out_regs,
358 out_col_count,
359 done_label,
360 end_label,
361 );
362 }
363
364 b.resolve_label(done_label);
366 if let Some(idx_cursor) = index_cursor_to_close {
367 b.emit_op(Opcode::Close, idx_cursor, 0, 0, P4::None, 0);
368 }
369 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
370 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
371
372 b.resolve_label(end_label);
374
375 Ok(())
376}
377
378fn codegen_select_values(b: &mut ProgramBuilder, rows: &[Vec<Expr>]) -> Result<(), CodegenError> {
380 let end_label = b.emit_label();
381
382 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
384
385 b.emit_op(Opcode::Transaction, 0, 0, 0, P4::None, 0);
387
388 let Some(first_row) = rows.first() else {
389 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
390 b.resolve_label(end_label);
391 return Ok(());
392 };
393
394 let out_col_count = i32::try_from(first_row.len())
395 .map_err(|_| CodegenError::Unsupported("VALUES row has too many columns".to_owned()))?;
396
397 if rows.iter().any(|row| row.len() != first_row.len()) {
398 return Err(CodegenError::Unsupported(
399 "VALUES rows must have the same arity".to_owned(),
400 ));
401 }
402
403 let out_regs = b.alloc_regs(out_col_count);
404 for row in rows {
405 for (reg, expr) in (out_regs..).zip(row.iter()) {
406 emit_expr(b, expr, reg)?;
407 }
408 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
409 }
410
411 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
412
413 b.resolve_label(end_label);
415
416 Ok(())
417}
418
419fn single_table_select_source_name(
420 source: &fsqlite_ast::TableOrSubquery,
421) -> Result<&str, CodegenError> {
422 match source {
423 fsqlite_ast::TableOrSubquery::Table { name, .. } => Ok(&name.name),
424 fsqlite_ast::TableOrSubquery::ParenJoin(inner) if inner.joins.is_empty() => {
425 single_table_select_source_name(&inner.source)
426 }
427 fsqlite_ast::TableOrSubquery::ParenJoin(_) => Err(CodegenError::Unsupported(
428 "parenthesized JOIN source in single-table SELECT".to_owned(),
429 )),
430 fsqlite_ast::TableOrSubquery::Subquery { .. } => Err(CodegenError::Unsupported(
431 "subquery FROM source in single-table SELECT".to_owned(),
432 )),
433 fsqlite_ast::TableOrSubquery::TableFunction { .. } => Err(CodegenError::Unsupported(
434 "table-valued function FROM source in single-table SELECT".to_owned(),
435 )),
436 }
437}
438
439#[allow(clippy::too_many_arguments)]
441fn codegen_select_full_scan(
442 b: &mut ProgramBuilder,
443 cursor: i32,
444 table: &TableSchema,
445 columns: &[ResultColumn],
446 out_regs: i32,
447 out_col_count: i32,
448 done_label: Label,
449 end_label: Label,
450) -> Result<(), CodegenError> {
451 b.emit_op(
452 Opcode::OpenRead,
453 cursor,
454 table.root_page,
455 0,
456 P4::Table(table.name.clone()),
457 0,
458 );
459
460 let loop_start = b.current_addr();
462 b.emit_jump_to_label(Opcode::Rewind, cursor, 0, done_label, P4::None, 0);
463
464 emit_column_reads(b, cursor, columns, table, out_regs)?;
466
467 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
469
470 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
472 let loop_body = (loop_start + 1) as i32;
473 b.emit_op(Opcode::Next, cursor, loop_body, 0, P4::None, 0);
474
475 b.resolve_label(done_label);
477 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
478 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
479
480 b.resolve_label(end_label);
482
483 Ok(())
484}
485
486#[allow(
488 clippy::too_many_arguments,
489 clippy::cast_possible_truncation,
490 clippy::cast_possible_wrap
491)]
492fn codegen_select_column_eq_scan(
493 b: &mut ProgramBuilder,
494 cursor: i32,
495 table: &TableSchema,
496 columns: &[ResultColumn],
497 out_regs: i32,
498 out_col_count: i32,
499 done_label: Label,
500 end_label: Label,
501 filter_column: &str,
502 param_idx: i32,
503) -> Result<(), CodegenError> {
504 let filter_col_idx =
505 table
506 .column_index(filter_column)
507 .ok_or_else(|| CodegenError::ColumnNotFound {
508 table: table.name.clone(),
509 column: filter_column.to_owned(),
510 })?;
511 let param_reg = b.alloc_reg();
512 let value_reg = b.alloc_reg();
513 b.emit_op(Opcode::Variable, param_idx, param_reg, 0, P4::None, 0);
514 b.emit_op(
515 Opcode::OpenRead,
516 cursor,
517 table.root_page,
518 0,
519 P4::Table(table.name.clone()),
520 0,
521 );
522
523 let loop_start = b.current_addr();
524 b.emit_jump_to_label(Opcode::Rewind, cursor, 0, done_label, P4::None, 0);
525
526 let skip_row_label = b.emit_label();
527 b.emit_op(
528 Opcode::Column,
529 cursor,
530 filter_col_idx as i32,
531 value_reg,
532 P4::None,
533 0,
534 );
535 b.emit_jump_to_label(
536 Opcode::Ne,
537 param_reg,
538 value_reg,
539 skip_row_label,
540 P4::None,
541 0x10,
542 );
543 emit_column_reads(b, cursor, columns, table, out_regs)?;
544 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
545 b.resolve_label(skip_row_label);
546
547 let loop_body = (loop_start + 1) as i32;
548 b.emit_op(Opcode::Next, cursor, loop_body, 0, P4::None, 0);
549
550 b.resolve_label(done_label);
551 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
552 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
553 b.resolve_label(end_label);
554
555 Ok(())
556}
557
558#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
563fn codegen_select_no_from(
564 b: &mut ProgramBuilder,
565 columns: &[ResultColumn],
566) -> Result<(), CodegenError> {
567 for col in columns {
569 if matches!(col, ResultColumn::Star | ResultColumn::TableStar(_)) {
570 return Err(CodegenError::Unsupported(
571 "SELECT * without FROM".to_owned(),
572 ));
573 }
574 }
575
576 let out_col_count = columns.len() as i32;
577 let end_label = b.emit_label();
578
579 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
581
582 b.emit_op(Opcode::Transaction, 0, 0, 0, P4::None, 0);
584
585 let out_regs = b.alloc_regs(out_col_count);
587 for (reg, col) in (out_regs..).zip(columns.iter()) {
588 if let ResultColumn::Expr { expr, .. } = col {
589 emit_expr(b, expr, reg)?;
590 }
591 }
592
593 b.emit_op(Opcode::ResultRow, out_regs, out_col_count, 0, P4::None, 0);
595
596 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
598
599 b.resolve_label(end_label);
601
602 Ok(())
603}
604
605pub fn codegen_insert(
616 b: &mut ProgramBuilder,
617 stmt: &InsertStatement,
618 schema: &[TableSchema],
619 ctx: &CodegenContext,
620) -> Result<(), CodegenError> {
621 let table = find_table(schema, &stmt.table.name)?;
622 let cursor = 0_i32;
623
624 let end_label = b.emit_label();
625
626 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
628
629 b.emit_op(Opcode::Transaction, 0, 1, 0, P4::None, 0);
631
632 b.emit_op(
634 Opcode::OpenWrite,
635 cursor,
636 table.root_page,
637 0,
638 P4::Table(table.name.clone()),
639 0,
640 );
641
642 let oe_flag = conflict_action_to_oe(stmt.or_conflict.as_ref());
643
644 match &stmt.source {
645 InsertSource::Values(rows) => {
646 if rows.is_empty() {
647 return Err(CodegenError::Unsupported("empty VALUES".to_owned()));
648 }
649 codegen_insert_values(
650 b,
651 rows,
652 &stmt.columns,
653 cursor,
654 table,
655 &stmt.returning,
656 ctx,
657 oe_flag,
658 )?;
659 }
660 InsertSource::Select(select_stmt) => {
661 codegen_insert_select(
662 b,
663 select_stmt,
664 &stmt.columns,
665 cursor,
666 table,
667 schema,
668 &stmt.returning,
669 ctx,
670 oe_flag,
671 )?;
672 }
673 InsertSource::DefaultValues => {
674 codegen_insert_default_values(b, cursor, table, &stmt.returning, ctx, oe_flag)?;
675 }
676 }
677
678 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
680 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
681
682 b.resolve_label(end_label);
684
685 Ok(())
686}
687
688fn insert_target_indices(
689 insert_columns: &[String],
690 table: &TableSchema,
691) -> Result<Vec<usize>, CodegenError> {
692 if insert_columns.is_empty() {
693 return Ok((0..table.columns.len()).collect());
694 }
695
696 insert_columns
697 .iter()
698 .map(|column| {
699 table
700 .column_index(column)
701 .ok_or_else(|| CodegenError::ColumnNotFound {
702 table: table.name.clone(),
703 column: column.clone(),
704 })
705 })
706 .collect()
707}
708
709fn insert_default_exprs(table: &TableSchema) -> Result<Vec<Expr>, CodegenError> {
710 table
711 .columns
712 .iter()
713 .map(|col| default_value_to_expr(table, col))
714 .collect()
715}
716
717fn default_value_to_expr(table: &TableSchema, col: &ColumnInfo) -> Result<Expr, CodegenError> {
719 let span = Span::ZERO;
720 let Some(dv) = col.default_value.as_deref() else {
721 return Ok(Expr::Literal(Literal::Null, span));
722 };
723 parse_default_expr(dv).map_err(|err| {
724 CodegenError::Unsupported(format!(
725 "failed to parse DEFAULT expression `{}` for {}.{}: {err}",
726 dv.trim(),
727 table.name,
728 col.name
729 ))
730 })
731}
732
733fn parse_default_expr(default_sql: &str) -> Result<Expr, fsqlite_parser::ParseError> {
735 let trimmed = default_sql.trim();
736 parse_sql_expr(trimmed)
737}
738
739fn expand_insert_values_row(
740 row_values: &[Expr],
741 insert_columns: &[String],
742 table: &TableSchema,
743) -> Result<Vec<Expr>, CodegenError> {
744 if insert_columns.is_empty() {
745 return Ok(row_values.to_vec());
746 }
747
748 let target_indices = insert_target_indices(insert_columns, table)?;
749 if row_values.len() != target_indices.len() {
750 return Err(CodegenError::Unsupported(format!(
751 "INSERT VALUES column count mismatch: {} expressions for {} target columns",
752 row_values.len(),
753 target_indices.len(),
754 )));
755 }
756
757 let mut expanded = insert_default_exprs(table)?;
758 let mut filled_targets = vec![false; table.columns.len()];
759 for (source_idx, target_idx) in target_indices.into_iter().enumerate() {
760 if filled_targets[target_idx] {
761 continue;
762 }
763 expanded[target_idx] = row_values[source_idx].clone();
764 filled_targets[target_idx] = true;
765 }
766 Ok(expanded)
767}
768
769#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
770fn emit_insert_target_regs_from_source(
771 b: &mut ProgramBuilder,
772 source_reg_base: i32,
773 source_count: usize,
774 insert_columns: &[String],
775 target_table: &TableSchema,
776) -> Result<(i32, i32), CodegenError> {
777 if insert_columns.is_empty() {
778 let count = i32::try_from(source_count)
779 .map_err(|_| CodegenError::Unsupported("too many INSERT source columns".to_owned()))?;
780 return Ok((source_reg_base, count));
781 }
782
783 let target_indices = insert_target_indices(insert_columns, target_table)?;
784 if source_count != target_indices.len() {
785 return Err(CodegenError::Unsupported(format!(
786 "INSERT source column count mismatch: {source_count} values for {} target columns",
787 target_indices.len(),
788 )));
789 }
790
791 let target_count = i32::try_from(target_table.columns.len())
792 .map_err(|_| CodegenError::Unsupported("too many target columns".to_owned()))?;
793 let val_regs = b.alloc_regs(target_count);
794 let default_exprs = insert_default_exprs(target_table)?;
795 let mut source_for_target = vec![None; target_table.columns.len()];
796 for (source_idx, target_idx) in target_indices.into_iter().enumerate() {
797 source_for_target[target_idx].get_or_insert(source_idx);
798 }
799
800 for (target_idx, default_expr) in default_exprs.iter().enumerate() {
801 let target_reg = val_regs + i32::try_from(target_idx).unwrap_or(0);
802 if let Some(source_idx) = source_for_target[target_idx] {
803 let source_reg = source_reg_base + i32::try_from(source_idx).unwrap_or(0);
804 b.emit_op(Opcode::Copy, source_reg, target_reg, 0, P4::None, 0);
805 } else {
806 emit_expr(b, default_expr, target_reg)?;
807 }
808 }
809
810 Ok((val_regs, target_count))
811}
812
813#[allow(
815 clippy::too_many_arguments,
816 clippy::cast_possible_truncation,
817 clippy::cast_possible_wrap,
818 clippy::unnecessary_wraps
819)]
820fn codegen_insert_values(
821 b: &mut ProgramBuilder,
822 rows: &[Vec<Expr>],
823 insert_columns: &[String],
824 cursor: i32,
825 table: &TableSchema,
826 returning: &[ResultColumn],
827 ctx: &CodegenContext,
828 oe_flag: u16,
829) -> Result<(), CodegenError> {
830 let rowid_reg = b.alloc_reg();
831 let concurrent_flag = i32::from(ctx.concurrent_mode);
832
833 let mut param_idx = 1_i32;
834
835 for row_values in rows {
836 let expanded_values = expand_insert_values_row(row_values, insert_columns, table)?;
837 let n_cols = expanded_values.len();
838 let val_regs = b.alloc_regs(n_cols as i32);
839 b.emit_op(
840 Opcode::NewRowid,
841 cursor,
842 rowid_reg,
843 concurrent_flag,
844 P4::None,
845 0,
846 );
847
848 for (i, val_expr) in expanded_values.iter().enumerate() {
849 let reg = val_regs + i as i32;
850 match val_expr {
851 Expr::Placeholder(pt, _) => {
852 #[allow(clippy::cast_possible_wrap)]
853 let idx = if let fsqlite_ast::PlaceholderType::Numbered(n) = pt {
854 *n as i32
855 } else {
856 let p = param_idx;
857 param_idx += 1;
858 p
859 };
860 b.emit_op(Opcode::Variable, idx, reg, 0, P4::None, 0);
861 }
862 _ => {
863 emit_expr(b, val_expr, reg)?;
866 }
867 }
868 }
869
870 let rec_reg = b.alloc_reg();
871 let n_cols_i32 = n_cols as i32;
872 b.emit_op(
873 Opcode::MakeRecord,
874 val_regs,
875 n_cols_i32,
876 rec_reg,
877 P4::Affinity(table.affinity_string()),
878 0,
879 );
880
881 b.emit_op(
882 Opcode::Insert,
883 cursor,
884 rec_reg,
885 rowid_reg,
886 P4::Table(table.name.clone()),
887 oe_flag,
888 );
889
890 if !returning.is_empty() {
891 b.emit_op(Opcode::ResultRow, rowid_reg, 1, 0, P4::None, 0);
892 }
893 }
894
895 Ok(())
896}
897
898#[allow(
900 clippy::cast_possible_truncation,
901 clippy::cast_possible_wrap,
902 clippy::unnecessary_wraps
903)]
904fn codegen_insert_default_values(
905 b: &mut ProgramBuilder,
906 cursor: i32,
907 table: &TableSchema,
908 returning: &[ResultColumn],
909 ctx: &CodegenContext,
910 oe_flag: u16,
911) -> Result<(), CodegenError> {
912 let rowid_reg = b.alloc_reg();
913 let concurrent_flag = i32::from(ctx.concurrent_mode);
914 let n_cols = table.columns.len() as i32;
915
916 b.emit_op(
917 Opcode::NewRowid,
918 cursor,
919 rowid_reg,
920 concurrent_flag,
921 P4::None,
922 0,
923 );
924
925 let val_regs = b.alloc_regs(n_cols);
927 let default_exprs = insert_default_exprs(table)?;
928 for (idx, default_expr) in default_exprs.iter().enumerate() {
929 let reg = val_regs + idx as i32;
930 emit_expr(b, default_expr, reg)?;
931 }
932
933 let rec_reg = b.alloc_reg();
934 b.emit_op(
935 Opcode::MakeRecord,
936 val_regs,
937 n_cols,
938 rec_reg,
939 P4::Affinity(table.affinity_string()),
940 0,
941 );
942
943 b.emit_op(
944 Opcode::Insert,
945 cursor,
946 rec_reg,
947 rowid_reg,
948 P4::Table(table.name.clone()),
949 oe_flag,
950 );
951
952 if !returning.is_empty() {
953 b.emit_op(Opcode::ResultRow, rowid_reg, 1, 0, P4::None, 0);
954 }
955
956 Ok(())
957}
958
959#[allow(
964 clippy::too_many_arguments,
965 clippy::cast_possible_truncation,
966 clippy::cast_possible_wrap
967)]
968fn codegen_insert_select(
969 b: &mut ProgramBuilder,
970 select_stmt: &SelectStatement,
971 insert_columns: &[String],
972 write_cursor: i32,
973 target_table: &TableSchema,
974 schema: &[TableSchema],
975 returning: &[ResultColumn],
976 ctx: &CodegenContext,
977 oe_flag: u16,
978) -> Result<(), CodegenError> {
979 if !select_stmt.body.compounds.is_empty() {
980 return Err(CodegenError::Unsupported(
981 "INSERT ... SELECT with compounds (UNION, etc.)".to_owned(),
982 ));
983 }
984
985 let (columns, from, where_clause) = match &select_stmt.body.select {
987 SelectCore::Select {
988 columns,
989 from,
990 where_clause,
991 ..
992 } => (columns, from, where_clause),
993 SelectCore::Values(rows) => {
994 return codegen_insert_values(
995 b,
996 rows,
997 insert_columns,
998 write_cursor,
999 target_table,
1000 returning,
1001 ctx,
1002 oe_flag,
1003 );
1004 }
1005 };
1006
1007 let Some(from_clause) = from.as_ref() else {
1009 return codegen_insert_select_expr_only(
1010 b,
1011 columns,
1012 where_clause.as_deref(),
1013 insert_columns,
1014 write_cursor,
1015 target_table,
1016 returning,
1017 ctx,
1018 oe_flag,
1019 );
1020 };
1021
1022 let src_table_name = match &from_clause.source {
1023 fsqlite_ast::TableOrSubquery::Table { name, .. } => &name.name,
1024 _ => {
1025 return Err(CodegenError::Unsupported(
1026 "INSERT ... SELECT from non-table source".to_owned(),
1027 ));
1028 }
1029 };
1030
1031 let src_table = find_table(schema, src_table_name)?;
1032 let read_cursor = write_cursor + 1;
1033
1034 let n_source_cols = result_column_count(columns, src_table);
1035 let rowid_reg = b.alloc_reg();
1036 let source_regs = b.alloc_regs(n_source_cols);
1037 let rec_reg = b.alloc_reg();
1038 let concurrent_flag = i32::from(ctx.concurrent_mode);
1039
1040 let done_label = b.emit_label();
1041
1042 b.emit_op(
1044 Opcode::OpenRead,
1045 read_cursor,
1046 src_table.root_page,
1047 0,
1048 P4::Table(src_table.name.clone()),
1049 0,
1050 );
1051
1052 let loop_start = b.current_addr();
1054 b.emit_jump_to_label(Opcode::Rewind, read_cursor, 0, done_label, P4::None, 0);
1055
1056 emit_column_reads(b, read_cursor, columns, src_table, source_regs)?;
1058 let (val_regs, n_cols) = emit_insert_target_regs_from_source(
1059 b,
1060 source_regs,
1061 n_source_cols as usize,
1062 insert_columns,
1063 target_table,
1064 )?;
1065
1066 b.emit_op(
1068 Opcode::NewRowid,
1069 write_cursor,
1070 rowid_reg,
1071 concurrent_flag,
1072 P4::None,
1073 0,
1074 );
1075
1076 b.emit_op(
1078 Opcode::MakeRecord,
1079 val_regs,
1080 n_cols,
1081 rec_reg,
1082 P4::Affinity(target_table.affinity_string()),
1083 0,
1084 );
1085
1086 b.emit_op(
1088 Opcode::Insert,
1089 write_cursor,
1090 rec_reg,
1091 rowid_reg,
1092 P4::Table(target_table.name.clone()),
1093 oe_flag,
1094 );
1095
1096 if !returning.is_empty() {
1098 b.emit_op(Opcode::ResultRow, rowid_reg, 1, 0, P4::None, 0);
1099 }
1100
1101 let loop_body = (loop_start + 1) as i32;
1103 b.emit_op(Opcode::Next, read_cursor, loop_body, 0, P4::None, 0);
1104
1105 b.resolve_label(done_label);
1107 b.emit_op(Opcode::Close, read_cursor, 0, 0, P4::None, 0);
1108
1109 Ok(())
1110}
1111
1112#[allow(
1115 clippy::too_many_arguments,
1116 clippy::cast_possible_truncation,
1117 clippy::cast_possible_wrap
1118)]
1119fn codegen_insert_select_expr_only(
1120 b: &mut ProgramBuilder,
1121 columns: &[ResultColumn],
1122 where_clause: Option<&Expr>,
1123 insert_columns: &[String],
1124 write_cursor: i32,
1125 target_table: &TableSchema,
1126 returning: &[ResultColumn],
1127 ctx: &CodegenContext,
1128 oe_flag: u16,
1129) -> Result<(), CodegenError> {
1130 let n_source_cols = columns
1132 .iter()
1133 .map(|c| match c {
1134 ResultColumn::Expr { .. } | ResultColumn::Star | ResultColumn::TableStar(_) => 1i32,
1135 })
1136 .sum::<i32>();
1137
1138 if n_source_cols == 0 {
1139 return Err(CodegenError::Unsupported(
1140 "INSERT ... SELECT with no columns".to_owned(),
1141 ));
1142 }
1143
1144 let rowid_reg = b.alloc_reg();
1145 let source_regs = b.alloc_regs(n_source_cols);
1146 let rec_reg = b.alloc_reg();
1147 let concurrent_flag = i32::from(ctx.concurrent_mode);
1148 let done_label = b.emit_label();
1149
1150 if let Some(where_expr) = where_clause {
1152 let filter_reg = b.alloc_reg();
1153 emit_expr(b, where_expr, filter_reg)?;
1154 b.emit_jump_to_label(Opcode::IfNot, filter_reg, 1, done_label, P4::None, 0);
1155 }
1156
1157 let mut reg = source_regs;
1159 for col in columns {
1160 match col {
1161 ResultColumn::Expr { expr, .. } => {
1162 emit_expr(b, expr, reg)?;
1163 reg += 1;
1164 }
1165 ResultColumn::Star | ResultColumn::TableStar(_) => {
1166 b.emit_op(Opcode::Null, 0, reg, 0, P4::None, 0);
1168 reg += 1;
1169 }
1170 }
1171 }
1172 let (val_regs, n_cols) = emit_insert_target_regs_from_source(
1173 b,
1174 source_regs,
1175 usize::try_from(n_source_cols).unwrap_or(0),
1176 insert_columns,
1177 target_table,
1178 )?;
1179
1180 b.emit_op(
1182 Opcode::NewRowid,
1183 write_cursor,
1184 rowid_reg,
1185 concurrent_flag,
1186 P4::None,
1187 0,
1188 );
1189
1190 b.emit_op(
1192 Opcode::MakeRecord,
1193 val_regs,
1194 n_cols,
1195 rec_reg,
1196 P4::Affinity(target_table.affinity_string()),
1197 0,
1198 );
1199
1200 b.emit_op(
1202 Opcode::Insert,
1203 write_cursor,
1204 rec_reg,
1205 rowid_reg,
1206 P4::Table(target_table.name.clone()),
1207 oe_flag,
1208 );
1209
1210 if !returning.is_empty() {
1212 b.emit_op(Opcode::ResultRow, rowid_reg, 1, 0, P4::None, 0);
1213 }
1214
1215 b.resolve_label(done_label);
1216 Ok(())
1217}
1218
1219#[allow(clippy::too_many_lines)]
1230pub fn codegen_update(
1231 b: &mut ProgramBuilder,
1232 stmt: &UpdateStatement,
1233 schema: &[TableSchema],
1234 _ctx: &CodegenContext,
1235) -> Result<(), CodegenError> {
1236 let table_name = table_name_from_qualified(&stmt.table);
1237 let table = find_table(schema, table_name)?;
1238 let cursor = 0_i32;
1239 let n_cols = table.columns.len();
1240
1241 let end_label = b.emit_label();
1242 let done_label = b.emit_label();
1243
1244 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
1246
1247 b.emit_op(Opcode::Transaction, 0, 1, 0, P4::None, 0);
1249
1250 let mut param_idx = 1_i32;
1253
1254 let new_val_regs: Vec<(usize, i32)> = stmt
1256 .assignments
1257 .iter()
1258 .map(|assign| {
1259 let col_name = match &assign.target {
1260 fsqlite_ast::AssignmentTarget::Column(name) => name.as_str(),
1261 fsqlite_ast::AssignmentTarget::ColumnList(_) => {
1262 return Err(CodegenError::Unsupported(
1263 "multi-column SET (a, b) = (...) assignment is not yet supported"
1264 .to_owned(),
1265 ));
1266 }
1267 };
1268 let col_idx =
1269 table
1270 .column_index(col_name)
1271 .ok_or_else(|| CodegenError::ColumnNotFound {
1272 table: table.name.clone(),
1273 column: col_name.to_owned(),
1274 })?;
1275 let reg = b.alloc_reg();
1276 Ok((col_idx, reg))
1277 })
1278 .collect::<Result<Vec<_>, CodegenError>>()?;
1279
1280 for (i, assign) in stmt.assignments.iter().enumerate() {
1282 let (_col_idx, reg) = new_val_regs[i];
1283 match &assign.value {
1284 Expr::Placeholder(pt, _) => {
1285 #[allow(clippy::cast_possible_wrap)]
1286 let idx = if let fsqlite_ast::PlaceholderType::Numbered(n) = pt {
1287 param_idx = param_idx.max(*n as i32 + 1);
1290 *n as i32
1291 } else {
1292 let p = param_idx;
1293 param_idx += 1;
1294 p
1295 };
1296 b.emit_op(Opcode::Variable, idx, reg, 0, P4::None, 0);
1297 }
1298 _ => {
1299 emit_expr(b, &assign.value, reg)?;
1302 }
1303 }
1304 }
1305
1306 let rowid_bind = extract_rowid_bind(stmt.where_clause.as_ref()).ok_or_else(|| {
1308 CodegenError::Unsupported("UPDATE currently supports only `WHERE rowid = ?`".to_owned())
1309 })?;
1310 let rowid_reg = b.alloc_reg();
1311 let rowid_param = match rowid_bind {
1312 BindParamRef::Anonymous => param_idx,
1313 BindParamRef::Numbered(idx) => idx,
1314 };
1315 b.emit_op(Opcode::Variable, rowid_param, rowid_reg, 0, P4::None, 0);
1316
1317 b.emit_op(
1319 Opcode::OpenWrite,
1320 cursor,
1321 table.root_page,
1322 0,
1323 P4::Table(table.name.clone()),
1324 0,
1325 );
1326
1327 b.emit_jump_to_label(
1329 Opcode::NotExists,
1330 cursor,
1331 rowid_reg,
1332 done_label,
1333 P4::None,
1334 0,
1335 );
1336
1337 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1339 let col_regs = b.alloc_regs(n_cols as i32);
1340 for i in 0..n_cols {
1341 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1342 b.emit_op(
1343 Opcode::Column,
1344 cursor,
1345 i as i32,
1346 col_regs + i as i32,
1347 P4::None,
1348 0,
1349 );
1350 }
1351
1352 for (col_idx, new_reg) in &new_val_regs {
1354 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1355 let target = col_regs + *col_idx as i32;
1356 b.emit_op(Opcode::Copy, *new_reg, target, 0, P4::None, 0);
1357 }
1358
1359 let rec_reg = b.alloc_reg();
1361 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1362 let n_cols_i32 = n_cols as i32;
1363 b.emit_op(
1364 Opcode::MakeRecord,
1365 col_regs,
1366 n_cols_i32,
1367 rec_reg,
1368 P4::Affinity(table.affinity_string()),
1369 0,
1370 );
1371
1372 b.emit_op(
1374 Opcode::Insert,
1375 cursor,
1376 rec_reg,
1377 rowid_reg,
1378 P4::Table(table.name.clone()),
1379 0x08, );
1381
1382 b.resolve_label(done_label);
1384 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
1385 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
1386
1387 b.resolve_label(end_label);
1389
1390 Ok(())
1391}
1392
1393pub fn codegen_delete(
1404 b: &mut ProgramBuilder,
1405 stmt: &DeleteStatement,
1406 schema: &[TableSchema],
1407 _ctx: &CodegenContext,
1408) -> Result<(), CodegenError> {
1409 let table_name = table_name_from_qualified(&stmt.table);
1410 let table = find_table(schema, table_name)?;
1411 let cursor = 0_i32;
1412
1413 let end_label = b.emit_label();
1414 let done_label = b.emit_label();
1415
1416 b.emit_jump_to_label(Opcode::Init, 0, 0, end_label, P4::None, 0);
1418
1419 b.emit_op(Opcode::Transaction, 0, 1, 0, P4::None, 0);
1421
1422 let rowid_reg = b.alloc_reg();
1424 b.emit_op(Opcode::Variable, 1, rowid_reg, 0, P4::None, 0);
1425
1426 b.emit_op(
1428 Opcode::OpenWrite,
1429 cursor,
1430 table.root_page,
1431 0,
1432 P4::Table(table.name.clone()),
1433 0,
1434 );
1435
1436 b.emit_jump_to_label(
1438 Opcode::NotExists,
1439 cursor,
1440 rowid_reg,
1441 done_label,
1442 P4::None,
1443 0,
1444 );
1445
1446 b.emit_op(
1448 Opcode::Delete,
1449 cursor,
1450 0,
1451 0,
1452 P4::Table(table.name.clone()),
1453 0,
1454 );
1455
1456 b.resolve_label(done_label);
1458 b.emit_op(Opcode::Close, cursor, 0, 0, P4::None, 0);
1459 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
1460
1461 b.resolve_label(end_label);
1463
1464 Ok(())
1465}
1466
1467#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1473fn result_column_count(columns: &[ResultColumn], table: &TableSchema) -> i32 {
1474 let mut count = 0i32;
1475 for col in columns {
1476 match col {
1477 ResultColumn::Star | ResultColumn::TableStar(_) => {
1478 count += table.columns.len() as i32;
1479 }
1480 ResultColumn::Expr { .. } => count += 1,
1481 }
1482 }
1483 count
1484}
1485
1486#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1488fn emit_column_reads(
1489 b: &mut ProgramBuilder,
1490 cursor: i32,
1491 columns: &[ResultColumn],
1492 table: &TableSchema,
1493 base_reg: i32,
1494) -> Result<(), CodegenError> {
1495 let mut reg = base_reg;
1496 for col in columns {
1497 match col {
1498 ResultColumn::Star | ResultColumn::TableStar(_) => {
1499 for i in 0..table.columns.len() {
1500 b.emit_op(Opcode::Column, cursor, i as i32, reg, P4::None, 0);
1501 reg += 1;
1502 }
1503 }
1504 ResultColumn::Expr { expr, .. } => {
1505 if let Expr::Column(col_ref, _) = expr {
1506 if is_rowid_ref(col_ref) {
1507 b.emit_op(Opcode::Rowid, cursor, reg, 0, P4::None, 0);
1509 } else {
1510 let col_idx = table.column_index(&col_ref.column).ok_or_else(|| {
1511 CodegenError::ColumnNotFound {
1512 table: table.name.clone(),
1513 column: col_ref.column.to_string(),
1514 }
1515 })?;
1516 b.emit_op(Opcode::Column, cursor, col_idx as i32, reg, P4::None, 0);
1517 }
1518 } else {
1519 emit_expr(b, expr, reg)?;
1522 }
1523 reg += 1;
1524 }
1525 }
1526 }
1527 Ok(())
1528}
1529
1530fn extract_rowid_bind_param(where_clause: Option<&Expr>) -> Option<i32> {
1534 extract_rowid_bind(where_clause).map(|bind| match bind {
1535 BindParamRef::Anonymous => 1,
1536 BindParamRef::Numbered(idx) => idx,
1537 })
1538}
1539
1540fn extract_rowid_bind(where_clause: Option<&Expr>) -> Option<BindParamRef> {
1542 let expr = where_clause?;
1543 if let Expr::BinaryOp {
1544 left,
1545 op: fsqlite_ast::BinaryOp::Eq,
1546 right,
1547 ..
1548 } = expr
1549 {
1550 if is_rowid_expr(left) {
1552 return bind_param_ref(right);
1553 }
1554 if is_rowid_expr(right) {
1555 return bind_param_ref(left);
1556 }
1557 }
1558 None
1559}
1560
1561fn extract_column_eq_bind(where_clause: Option<&Expr>) -> Option<(String, i32)> {
1563 let expr = where_clause?;
1564 if let Expr::BinaryOp {
1565 left,
1566 op: fsqlite_ast::BinaryOp::Eq,
1567 right,
1568 ..
1569 } = expr
1570 {
1571 if let (Some(col_name), Some(param_idx)) = (column_name(left), bind_param_index(right)) {
1572 return Some((col_name, param_idx));
1573 }
1574 if let (Some(col_name), Some(param_idx)) = (column_name(right), bind_param_index(left)) {
1575 return Some((col_name, param_idx));
1576 }
1577 }
1578 None
1579}
1580
1581fn column_name(expr: &Expr) -> Option<String> {
1583 if let Expr::Column(col_ref, _) = expr {
1584 if !is_rowid_ref(col_ref) {
1585 return Some(col_ref.column.to_string());
1586 }
1587 }
1588 None
1589}
1590
1591fn is_rowid_expr(expr: &Expr) -> bool {
1593 if let Expr::Column(col_ref, _) = expr {
1594 is_rowid_ref(col_ref)
1595 } else {
1596 false
1597 }
1598}
1599
1600fn is_rowid_ref(col_ref: &ColumnRef) -> bool {
1601 let name = col_ref.column.to_ascii_lowercase();
1602 name == "rowid" || name == "_rowid_" || name == "oid"
1603}
1604
1605fn bind_param_index(expr: &Expr) -> Option<i32> {
1607 bind_param_ref(expr).map(|bind| match bind {
1608 BindParamRef::Anonymous => 1,
1609 BindParamRef::Numbered(idx) => idx,
1610 })
1611}
1612
1613fn bind_param_ref(expr: &Expr) -> Option<BindParamRef> {
1615 if let Expr::Placeholder(pt, _) = expr {
1616 match pt {
1617 PlaceholderType::Anonymous => Some(BindParamRef::Anonymous),
1618 PlaceholderType::Numbered(n) =>
1619 {
1620 #[allow(clippy::cast_possible_wrap)]
1621 Some(BindParamRef::Numbered(*n as i32))
1622 }
1623 _ => None,
1624 }
1625 } else {
1626 None
1627 }
1628}
1629
1630fn binary_op_to_opcode(op: AstBinaryOp) -> Opcode {
1632 match op {
1633 AstBinaryOp::Add => Opcode::Add,
1634 AstBinaryOp::Subtract => Opcode::Subtract,
1635 AstBinaryOp::Multiply => Opcode::Multiply,
1636 AstBinaryOp::Divide => Opcode::Divide,
1637 AstBinaryOp::Modulo => Opcode::Remainder,
1638 AstBinaryOp::Concat => Opcode::Concat,
1639 AstBinaryOp::BitAnd => Opcode::BitAnd,
1640 AstBinaryOp::BitOr => Opcode::BitOr,
1641 AstBinaryOp::ShiftLeft => Opcode::ShiftLeft,
1642 AstBinaryOp::ShiftRight => Opcode::ShiftRight,
1643 AstBinaryOp::And => Opcode::And,
1644 AstBinaryOp::Or => Opcode::Or,
1645 AstBinaryOp::Eq | AstBinaryOp::Is => Opcode::Eq,
1646 AstBinaryOp::Ne | AstBinaryOp::IsNot => Opcode::Ne,
1647 AstBinaryOp::Lt => Opcode::Lt,
1648 AstBinaryOp::Le => Opcode::Le,
1649 AstBinaryOp::Gt => Opcode::Gt,
1650 AstBinaryOp::Ge => Opcode::Ge,
1651 }
1652}
1653
1654fn is_comparison_op(op: AstBinaryOp) -> bool {
1656 matches!(
1657 op,
1658 AstBinaryOp::Eq
1659 | AstBinaryOp::Ne
1660 | AstBinaryOp::Lt
1661 | AstBinaryOp::Le
1662 | AstBinaryOp::Gt
1663 | AstBinaryOp::Ge
1664 | AstBinaryOp::Is
1665 | AstBinaryOp::IsNot
1666 )
1667}
1668
1669fn emit_comparison_expr(
1671 b: &mut ProgramBuilder,
1672 left: &Expr,
1673 op: AstBinaryOp,
1674 right: &Expr,
1675 dest: i32,
1676) -> Result<(), CodegenError> {
1677 let lhs = b.alloc_temp();
1678 let rhs = b.alloc_temp();
1679 emit_expr(b, left, lhs)?;
1680 emit_expr(b, right, rhs)?;
1681
1682 let opcode = binary_op_to_opcode(op);
1683 let p5 = if matches!(op, AstBinaryOp::Is | AstBinaryOp::IsNot) {
1685 0x80
1686 } else {
1687 0
1688 };
1689 let true_label = b.emit_label();
1690 let done_label = b.emit_label();
1691
1692 b.emit_jump_to_label(opcode, rhs, lhs, true_label, P4::None, p5);
1694 b.emit_op(Opcode::Integer, 0, dest, 0, P4::None, 0);
1695 b.emit_jump_to_label(Opcode::Goto, 0, 0, done_label, P4::None, 0);
1696 b.resolve_label(true_label);
1697 b.emit_op(Opcode::Integer, 1, dest, 0, P4::None, 0);
1698 b.resolve_label(done_label);
1699
1700 b.free_temp(lhs);
1701 b.free_temp(rhs);
1702 Ok(())
1703}
1704
1705fn type_to_affinity(type_name: &str) -> char {
1707 let upper = type_name.to_uppercase();
1709 if upper.contains("INT") {
1710 'D' } else if upper.contains("CHAR")
1712 || upper.contains("CLOB")
1713 || upper.contains("TEXT")
1714 || upper.contains("VARCHAR")
1715 {
1716 'B' } else if upper.contains("BLOB") || upper.is_empty() {
1718 'A' } else if upper.contains("REAL") || upper.contains("FLOA") || upper.contains("DOUB") {
1720 'E' } else {
1722 'C' }
1724}
1725
1726#[allow(
1731 clippy::cast_possible_truncation,
1732 clippy::cast_possible_wrap,
1733 clippy::too_many_lines
1734)]
1735fn emit_expr(b: &mut ProgramBuilder, expr: &Expr, reg: i32) -> Result<(), CodegenError> {
1736 match expr {
1737 Expr::Placeholder(pt, _) => {
1738 let idx = match pt {
1739 fsqlite_ast::PlaceholderType::Numbered(n) => *n as i32,
1740 _ => 1, };
1742 b.emit_op(Opcode::Variable, idx, reg, 0, P4::None, 0);
1743 Ok(())
1744 }
1745 Expr::Literal(lit, _) => match lit {
1746 Literal::Integer(n) => {
1747 if let Ok(as_i32) = i32::try_from(*n) {
1748 b.emit_op(Opcode::Integer, as_i32, reg, 0, P4::None, 0);
1749 } else {
1750 b.emit_op(Opcode::Int64, 0, reg, 0, P4::Int64(*n), 0);
1751 }
1752 Ok(())
1753 }
1754 Literal::Float(f) => {
1755 b.emit_op(Opcode::Real, 0, reg, 0, P4::Real(*f), 0);
1756 Ok(())
1757 }
1758 Literal::String(s) => {
1759 b.emit_op(Opcode::String8, 0, reg, 0, P4::Str(s.clone()), 0);
1760 Ok(())
1761 }
1762 Literal::Blob(bytes) => {
1763 b.emit_op(
1764 Opcode::Blob,
1765 bytes.len() as i32,
1766 reg,
1767 0,
1768 P4::Blob(bytes.clone()),
1769 0,
1770 );
1771 Ok(())
1772 }
1773 Literal::Null => {
1774 b.emit_op(Opcode::Null, 0, reg, 0, P4::None, 0);
1775 Ok(())
1776 }
1777 Literal::True => {
1778 b.emit_op(Opcode::Integer, 1, reg, 0, P4::None, 0);
1779 Ok(())
1780 }
1781 Literal::False => {
1782 b.emit_op(Opcode::Integer, 0, reg, 0, P4::None, 0);
1783 Ok(())
1784 }
1785 Literal::CurrentTime => {
1786 let arg_reg = b.alloc_temp();
1788 b.emit_op(Opcode::String8, 0, arg_reg, 0, P4::Str("now".to_owned()), 0);
1789 b.emit_op(
1790 Opcode::PureFunc,
1791 0,
1792 arg_reg,
1793 reg,
1794 P4::FuncName("TIME".to_owned()),
1795 1,
1796 );
1797 b.free_temp(arg_reg);
1798 Ok(())
1799 }
1800 Literal::CurrentDate => {
1801 let arg_reg = b.alloc_temp();
1802 b.emit_op(Opcode::String8, 0, arg_reg, 0, P4::Str("now".to_owned()), 0);
1803 b.emit_op(
1804 Opcode::PureFunc,
1805 0,
1806 arg_reg,
1807 reg,
1808 P4::FuncName("DATE".to_owned()),
1809 1,
1810 );
1811 b.free_temp(arg_reg);
1812 Ok(())
1813 }
1814 Literal::CurrentTimestamp => {
1815 let arg_reg = b.alloc_temp();
1816 b.emit_op(Opcode::String8, 0, arg_reg, 0, P4::Str("now".to_owned()), 0);
1817 b.emit_op(
1818 Opcode::PureFunc,
1819 0,
1820 arg_reg,
1821 reg,
1822 P4::FuncName("DATETIME".to_owned()),
1823 1,
1824 );
1825 b.free_temp(arg_reg);
1826 Ok(())
1827 }
1828 },
1829
1830 Expr::BinaryOp {
1832 left, op, right, ..
1833 } => {
1834 if is_comparison_op(*op) {
1835 return emit_comparison_expr(b, left, *op, right, reg);
1836 }
1837 let tmp = b.alloc_temp();
1839 emit_expr(b, left, reg)?;
1840 emit_expr(b, right, tmp)?;
1841 let opcode = binary_op_to_opcode(*op);
1842 b.emit_op(opcode, tmp, reg, reg, P4::None, 0);
1844 b.free_temp(tmp);
1845 Ok(())
1846 }
1847
1848 Expr::UnaryOp {
1850 op, expr: inner, ..
1851 } => {
1852 emit_expr(b, inner, reg)?;
1853 match op {
1854 AstUnaryOp::Negate => {
1855 let tmp = b.alloc_temp();
1856 b.emit_op(Opcode::Integer, -1, tmp, 0, P4::None, 0);
1857 b.emit_op(Opcode::Multiply, tmp, reg, reg, P4::None, 0);
1858 b.free_temp(tmp);
1859 }
1860 AstUnaryOp::Plus => {} AstUnaryOp::Not => {
1862 b.emit_op(Opcode::Not, reg, reg, 0, P4::None, 0);
1863 }
1864 AstUnaryOp::BitNot => {
1865 b.emit_op(Opcode::BitNot, reg, reg, 0, P4::None, 0);
1866 }
1867 }
1868 Ok(())
1869 }
1870
1871 Expr::IsNull {
1873 expr: inner, not, ..
1874 } => {
1875 emit_expr(b, inner, reg)?;
1876 let true_label = b.emit_label();
1877 let done_label = b.emit_label();
1878 if *not {
1879 b.emit_jump_to_label(Opcode::NotNull, reg, 0, true_label, P4::None, 0);
1880 } else {
1881 b.emit_jump_to_label(Opcode::IsNull, reg, 0, true_label, P4::None, 0);
1882 }
1883 b.emit_op(Opcode::Integer, 0, reg, 0, P4::None, 0);
1884 b.emit_jump_to_label(Opcode::Goto, 0, 0, done_label, P4::None, 0);
1885 b.resolve_label(true_label);
1886 b.emit_op(Opcode::Integer, 1, reg, 0, P4::None, 0);
1887 b.resolve_label(done_label);
1888 Ok(())
1889 }
1890
1891 Expr::Cast {
1893 expr: inner,
1894 type_name,
1895 ..
1896 } => {
1897 emit_expr(b, inner, reg)?;
1898 let affinity = type_to_affinity(&type_name.name);
1899 b.emit_op(Opcode::Cast, reg, i32::from(affinity as u8), 0, P4::None, 0);
1902 Ok(())
1903 }
1904
1905 Expr::FunctionCall { name, args, .. } => {
1907 let canon = name.trim().to_ascii_uppercase();
1909 match args {
1910 FunctionArgs::Star => {
1911 b.emit_op(Opcode::PureFunc, 0, 0, reg, P4::FuncName(canon), 0);
1912 }
1913 FunctionArgs::List(arg_list) => {
1914 let n_args = arg_list.len();
1915 if n_args == 0 {
1916 b.emit_op(Opcode::PureFunc, 0, 0, reg, P4::FuncName(canon), 0);
1917 } else {
1918 let first_arg_reg = b.alloc_regs(n_args as i32);
1919 for (i, arg) in arg_list.iter().enumerate() {
1920 emit_expr(b, arg, first_arg_reg + i as i32)?;
1921 }
1922 b.emit_op(
1923 Opcode::PureFunc,
1924 0,
1925 first_arg_reg,
1926 reg,
1927 P4::FuncName(canon),
1928 n_args as u16,
1929 );
1930 }
1931 }
1932 }
1933 Ok(())
1934 }
1935
1936 Expr::Case {
1938 operand,
1939 whens,
1940 else_expr,
1941 ..
1942 } => {
1943 let done_label = b.emit_label();
1944 if let Some(base_expr) = operand {
1945 let base_reg = b.alloc_temp();
1946 emit_expr(b, base_expr, base_reg)?;
1947 for (when_val, then_val) in whens {
1948 let next_label = b.emit_label();
1949 let when_reg = b.alloc_temp();
1950 emit_expr(b, when_val, when_reg)?;
1951 b.emit_jump_to_label(
1952 Opcode::Ne,
1953 when_reg,
1954 base_reg,
1955 next_label,
1956 P4::None,
1957 0x10,
1958 );
1959 b.free_temp(when_reg);
1960 emit_expr(b, then_val, reg)?;
1961 b.emit_jump_to_label(Opcode::Goto, 0, 0, done_label, P4::None, 0);
1962 b.resolve_label(next_label);
1963 }
1964 b.free_temp(base_reg);
1965 } else {
1966 for (when_cond, then_val) in whens {
1967 let next_label = b.emit_label();
1968 let cond_reg = b.alloc_temp();
1969 emit_expr(b, when_cond, cond_reg)?;
1970 b.emit_jump_to_label(Opcode::IfNot, cond_reg, 0, next_label, P4::None, 1);
1971 b.free_temp(cond_reg);
1972 emit_expr(b, then_val, reg)?;
1973 b.emit_jump_to_label(Opcode::Goto, 0, 0, done_label, P4::None, 0);
1974 b.resolve_label(next_label);
1975 }
1976 }
1977 if let Some(else_val) = else_expr {
1978 emit_expr(b, else_val, reg)?;
1979 } else {
1980 b.emit_op(Opcode::Null, 0, reg, 0, P4::None, 0);
1981 }
1982 b.resolve_label(done_label);
1983 Ok(())
1984 }
1985
1986 Expr::Collate { expr: inner, .. } => emit_expr(b, inner, reg),
1988
1989 _ => Err(CodegenError::Unsupported(
1990 "planner expression codegen for this expression type".to_owned(),
1991 )),
1992 }
1993}
1994
1995#[cfg(test)]
2000mod tests {
2001 use super::*;
2002 use fsqlite_ast::{
2003 Assignment, AssignmentTarget, BinaryOp as AstBinaryOp, ColumnRef, DeleteStatement,
2004 Distinctness, Expr, FromClause, InsertSource, InsertStatement, Literal, PlaceholderType,
2005 QualifiedName, QualifiedTableRef, ResultColumn, SelectBody, SelectCore, SelectStatement,
2006 Span, TableOrSubquery, UpdateStatement,
2007 };
2008 use fsqlite_types::opcode::{Opcode, ProgramBuilder, VdbeProgram};
2009
2010 fn test_schema() -> Vec<TableSchema> {
2011 vec![TableSchema {
2012 name: "t".to_owned(),
2013 root_page: 2,
2014 columns: vec![
2015 ColumnInfo {
2016 name: "a".to_owned(),
2017 affinity: 'd',
2018 default_value: None,
2019 },
2020 ColumnInfo {
2021 name: "b".to_owned(),
2022 affinity: 'C',
2023 default_value: None,
2024 },
2025 ],
2026 indexes: vec![],
2027 }]
2028 }
2029
2030 fn test_schema_with_index() -> Vec<TableSchema> {
2031 vec![TableSchema {
2032 name: "t".to_owned(),
2033 root_page: 2,
2034 columns: vec![
2035 ColumnInfo {
2036 name: "a".to_owned(),
2037 affinity: 'd',
2038 default_value: None,
2039 },
2040 ColumnInfo {
2041 name: "b".to_owned(),
2042 affinity: 'C',
2043 default_value: None,
2044 },
2045 ],
2046 indexes: vec![IndexSchema {
2047 name: "idx_t_b".to_owned(),
2048 root_page: 3,
2049 columns: vec!["b".to_owned()],
2050 is_unique: false,
2051 }],
2052 }]
2053 }
2054
2055 fn from_table(name: &str) -> FromClause {
2056 FromClause {
2057 source: TableOrSubquery::Table {
2058 name: QualifiedName::bare(name),
2059 alias: None,
2060 index_hint: None,
2061 time_travel: None,
2062 },
2063 joins: vec![],
2064 }
2065 }
2066
2067 fn placeholder(n: u32) -> Expr {
2068 Expr::Placeholder(PlaceholderType::Numbered(n), Span::ZERO)
2069 }
2070
2071 #[test]
2072 fn test_table_schema_accessors_affinity_index_and_case_insensitive_lookup() {
2073 let schema = TableSchema {
2074 name: "t".to_owned(),
2075 root_page: 2,
2076 columns: vec![
2077 ColumnInfo {
2078 name: "id".to_owned(),
2079 affinity: 'd',
2080 default_value: None,
2081 },
2082 ColumnInfo {
2083 name: "name".to_owned(),
2084 affinity: 'b',
2085 default_value: None,
2086 },
2087 ColumnInfo {
2088 name: "age".to_owned(),
2089 affinity: 'c',
2090 default_value: None,
2091 },
2092 ],
2093 indexes: vec![IndexSchema {
2094 name: "idx_age_name".to_owned(),
2095 root_page: 3,
2096 columns: vec!["age".to_owned(), "name".to_owned()],
2097 is_unique: false,
2098 }],
2099 };
2100
2101 assert_eq!(schema.affinity_string(), "dbc");
2103
2104 assert_eq!(schema.column_index("id"), Some(0));
2106 assert_eq!(
2107 schema.column_index("NAME"),
2108 Some(1),
2109 "lookup is case-insensitive"
2110 );
2111 assert_eq!(schema.column_index("Age"), Some(2));
2112 assert_eq!(schema.column_index("missing"), None);
2113
2114 assert_eq!(
2116 schema.index_for_column("age").map(|i| i.name.as_str()),
2117 Some("idx_age_name")
2118 );
2119 assert_eq!(
2120 schema.index_for_column("AGE").map(|i| i.name.as_str()),
2121 Some("idx_age_name"),
2122 "case-insensitive"
2123 );
2124 assert!(
2125 schema.index_for_column("name").is_none(),
2126 "a non-leftmost index column is not matched"
2127 );
2128 assert!(schema.index_for_column("id").is_none());
2129 }
2130
2131 #[test]
2132 fn test_type_to_affinity_follows_sqlite_rules() {
2133 assert_eq!(type_to_affinity("INTEGER"), 'D');
2137 assert_eq!(type_to_affinity("int"), 'D');
2138 assert_eq!(type_to_affinity("BIGINT"), 'D');
2139 assert_eq!(type_to_affinity("TINYINT"), 'D');
2140 assert_eq!(type_to_affinity("POINT"), 'D');
2142
2143 assert_eq!(type_to_affinity("TEXT"), 'B');
2144 assert_eq!(type_to_affinity("VARCHAR(255)"), 'B');
2145 assert_eq!(type_to_affinity("CHARACTER(20)"), 'B');
2146 assert_eq!(type_to_affinity("CLOB"), 'B');
2147
2148 assert_eq!(type_to_affinity("BLOB"), 'A');
2150 assert_eq!(type_to_affinity(""), 'A');
2151
2152 assert_eq!(type_to_affinity("REAL"), 'E');
2153 assert_eq!(type_to_affinity("DOUBLE PRECISION"), 'E');
2154 assert_eq!(type_to_affinity("FLOAT"), 'E');
2155
2156 assert_eq!(type_to_affinity("NUMERIC"), 'C');
2158 assert_eq!(type_to_affinity("DECIMAL(10,2)"), 'C');
2159 assert_eq!(type_to_affinity("BOOLEAN"), 'C');
2160 assert_eq!(type_to_affinity("DATETIME"), 'C');
2161 }
2162
2163 #[test]
2164 fn test_rowid_ref_aliases_and_conflict_action_codes() {
2165 for name in ["rowid", "ROWID", "_rowid_", "oid", "OID", "RowId"] {
2167 assert!(
2168 is_rowid_ref(&ColumnRef::bare(name)),
2169 "{name} is a rowid alias"
2170 );
2171 }
2172 for name in ["id", "name", "rowid_", "row_id", "_oid_"] {
2173 assert!(
2174 !is_rowid_ref(&ColumnRef::bare(name)),
2175 "{name} is not a rowid alias"
2176 );
2177 }
2178
2179 assert_eq!(
2181 conflict_action_to_oe(None),
2182 OE_ABORT,
2183 "default conflict action is ABORT"
2184 );
2185 assert_eq!(
2186 conflict_action_to_oe(Some(&ConflictAction::Abort)),
2187 OE_ABORT
2188 );
2189 assert_eq!(
2190 conflict_action_to_oe(Some(&ConflictAction::Rollback)),
2191 OE_ROLLBACK
2192 );
2193 assert_eq!(conflict_action_to_oe(Some(&ConflictAction::Fail)), OE_FAIL);
2194 assert_eq!(
2195 conflict_action_to_oe(Some(&ConflictAction::Ignore)),
2196 OE_IGNORE
2197 );
2198 assert_eq!(
2199 conflict_action_to_oe(Some(&ConflictAction::Replace)),
2200 OE_REPLACE
2201 );
2202 let codes: std::collections::HashSet<u16> =
2204 [OE_ROLLBACK, OE_ABORT, OE_FAIL, OE_IGNORE, OE_REPLACE]
2205 .into_iter()
2206 .collect();
2207 assert_eq!(codes.len(), 5, "OE conflict codes must be distinct");
2208 }
2209
2210 #[test]
2211 fn test_emit_comparison_expr_shape_and_is_nulleq_flag() {
2212 let lit = |n: i64| Expr::Literal(Literal::Integer(n), Span::ZERO);
2218
2219 let mut b = ProgramBuilder::new();
2221 let dest = b.alloc_reg();
2222 emit_comparison_expr(&mut b, &lit(3), AstBinaryOp::Lt, &lit(5), dest).unwrap();
2223 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
2224 let prog = b.finish().unwrap();
2225 assert!(has_opcodes(
2226 &prog,
2227 &[
2228 Opcode::Integer, Opcode::Integer, Opcode::Lt, Opcode::Integer, Opcode::Goto,
2233 Opcode::Integer, ]
2235 ));
2236 let lt = prog
2237 .ops()
2238 .iter()
2239 .find(|op| op.opcode == Opcode::Lt)
2240 .expect("Lt present");
2241 assert_eq!(lt.p5, 0, "a plain comparison carries no NULLEQ flag");
2242
2243 let mut b2 = ProgramBuilder::new();
2245 let d2 = b2.alloc_reg();
2246 emit_comparison_expr(&mut b2, &lit(3), AstBinaryOp::Is, &lit(5), d2).unwrap();
2247 b2.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
2248 let prog2 = b2.finish().unwrap();
2249 let eq = prog2
2250 .ops()
2251 .iter()
2252 .find(|op| op.opcode == Opcode::Eq)
2253 .expect("Eq present");
2254 assert_eq!(
2255 eq.p5, 0x80,
2256 "IS uses the NULLEQ flag so NULL IS NULL is true"
2257 );
2258 }
2259
2260 #[test]
2261 fn test_is_rowid_ref_recognizes_aliases_case_insensitively() {
2262 for name in [
2266 "rowid", "ROWID", "RowId", "_rowid_", "_ROWID_", "oid", "OID",
2267 ] {
2268 assert!(
2269 is_rowid_ref(&ColumnRef::bare(name)),
2270 "{name} should be a rowid alias"
2271 );
2272 }
2273 for name in ["id", "row_id", "rowid2", "oids", "_rowid", "rowi", ""] {
2274 assert!(
2275 !is_rowid_ref(&ColumnRef::bare(name)),
2276 "{name} should NOT be a rowid alias"
2277 );
2278 }
2279 }
2280
2281 #[test]
2282 fn test_binary_op_to_opcode_and_is_comparison_classification() {
2283 use AstBinaryOp as B;
2284
2285 assert_eq!(binary_op_to_opcode(B::Add), Opcode::Add);
2287 assert_eq!(binary_op_to_opcode(B::Subtract), Opcode::Subtract);
2288 assert_eq!(binary_op_to_opcode(B::Multiply), Opcode::Multiply);
2289 assert_eq!(binary_op_to_opcode(B::Divide), Opcode::Divide);
2290 assert_eq!(binary_op_to_opcode(B::Modulo), Opcode::Remainder);
2292 assert_eq!(binary_op_to_opcode(B::Concat), Opcode::Concat);
2293 assert_eq!(binary_op_to_opcode(B::BitAnd), Opcode::BitAnd);
2294 assert_eq!(binary_op_to_opcode(B::BitOr), Opcode::BitOr);
2295 assert_eq!(binary_op_to_opcode(B::ShiftLeft), Opcode::ShiftLeft);
2296 assert_eq!(binary_op_to_opcode(B::ShiftRight), Opcode::ShiftRight);
2297 assert_eq!(binary_op_to_opcode(B::And), Opcode::And);
2298 assert_eq!(binary_op_to_opcode(B::Or), Opcode::Or);
2299
2300 assert_eq!(binary_op_to_opcode(B::Eq), Opcode::Eq);
2302 assert_eq!(binary_op_to_opcode(B::Is), Opcode::Eq);
2303 assert_eq!(binary_op_to_opcode(B::Ne), Opcode::Ne);
2304 assert_eq!(binary_op_to_opcode(B::IsNot), Opcode::Ne);
2305 assert_eq!(binary_op_to_opcode(B::Lt), Opcode::Lt);
2306 assert_eq!(binary_op_to_opcode(B::Le), Opcode::Le);
2307 assert_eq!(binary_op_to_opcode(B::Gt), Opcode::Gt);
2308 assert_eq!(binary_op_to_opcode(B::Ge), Opcode::Ge);
2309
2310 for op in [B::Eq, B::Ne, B::Lt, B::Le, B::Gt, B::Ge, B::Is, B::IsNot] {
2312 assert!(is_comparison_op(op), "{op:?} is a comparison");
2313 }
2314 for op in [
2315 B::Add,
2316 B::Subtract,
2317 B::Multiply,
2318 B::Divide,
2319 B::Modulo,
2320 B::Concat,
2321 B::BitAnd,
2322 B::BitOr,
2323 B::ShiftLeft,
2324 B::ShiftRight,
2325 B::And,
2326 B::Or,
2327 ] {
2328 assert!(!is_comparison_op(op), "{op:?} is not a comparison");
2329 }
2330 }
2331
2332 #[test]
2333 fn test_extract_column_eq_bind_symmetry_and_rowid_exclusion() {
2334 let bin = |left: Expr, right: Expr| Expr::BinaryOp {
2335 left: Box::new(left),
2336 op: AstBinaryOp::Eq,
2337 right: Box::new(right),
2338 span: Span::ZERO,
2339 };
2340 let col = |name: &str| Expr::Column(ColumnRef::bare(name), Span::ZERO);
2341
2342 let e = bin(col("name"), placeholder(2));
2344 assert_eq!(
2345 extract_column_eq_bind(Some(&e)),
2346 Some(("name".to_owned(), 2))
2347 );
2348
2349 let e = bin(placeholder(3), col("age"));
2351 assert_eq!(
2352 extract_column_eq_bind(Some(&e)),
2353 Some(("age".to_owned(), 3))
2354 );
2355
2356 let e = bin(col("rowid"), placeholder(1));
2358 assert_eq!(extract_column_eq_bind(Some(&e)), None);
2359
2360 let e = bin(col("name"), col("age"));
2362 assert_eq!(extract_column_eq_bind(Some(&e)), None);
2363
2364 let lt = Expr::BinaryOp {
2366 left: Box::new(col("name")),
2367 op: AstBinaryOp::Lt,
2368 right: Box::new(placeholder(1)),
2369 span: Span::ZERO,
2370 };
2371 assert_eq!(extract_column_eq_bind(Some(<)), None);
2372
2373 assert_eq!(extract_column_eq_bind(None), None);
2375 }
2376
2377 #[test]
2378 fn test_extract_rowid_bind_symmetry_and_form_preservation() {
2379 let eq = |left: Expr, right: Expr| Expr::BinaryOp {
2380 left: Box::new(left),
2381 op: AstBinaryOp::Eq,
2382 right: Box::new(right),
2383 span: Span::ZERO,
2384 };
2385 let col = |name: &str| Expr::Column(ColumnRef::bare(name), Span::ZERO);
2386
2387 let e = eq(col("rowid"), placeholder(2));
2389 assert_eq!(
2390 extract_rowid_bind(Some(&e)),
2391 Some(BindParamRef::Numbered(2))
2392 );
2393 assert_eq!(extract_rowid_bind_param(Some(&e)), Some(2));
2394
2395 let e = eq(placeholder(3), col("oid"));
2397 assert_eq!(
2398 extract_rowid_bind(Some(&e)),
2399 Some(BindParamRef::Numbered(3))
2400 );
2401
2402 let e = eq(
2404 col("rowid"),
2405 Expr::Placeholder(PlaceholderType::Anonymous, Span::ZERO),
2406 );
2407 assert_eq!(extract_rowid_bind(Some(&e)), Some(BindParamRef::Anonymous));
2408 assert_eq!(extract_rowid_bind_param(Some(&e)), Some(1));
2409
2410 let e = eq(col("name"), placeholder(1));
2412 assert_eq!(extract_rowid_bind(Some(&e)), None);
2413
2414 assert_eq!(extract_rowid_bind(Some(&eq(col("rowid"), col("id")))), None);
2416 assert_eq!(extract_rowid_bind(None), None);
2417 }
2418
2419 #[test]
2420 fn test_result_column_count_expands_stars() {
2421 let table = TableSchema {
2422 name: "t".to_owned(),
2423 root_page: 2,
2424 columns: vec![
2425 ColumnInfo {
2426 name: "a".to_owned(),
2427 affinity: 'd',
2428 default_value: None,
2429 },
2430 ColumnInfo {
2431 name: "b".to_owned(),
2432 affinity: 'b',
2433 default_value: None,
2434 },
2435 ColumnInfo {
2436 name: "c".to_owned(),
2437 affinity: 'c',
2438 default_value: None,
2439 },
2440 ],
2441 indexes: vec![],
2442 };
2443 let expr = || ResultColumn::Expr {
2444 expr: Expr::Literal(Literal::Integer(1), Span::ZERO),
2445 alias: None,
2446 };
2447
2448 assert_eq!(result_column_count(&[], &table), 0);
2449 assert_eq!(result_column_count(&[expr(), expr()], &table), 2);
2450 assert_eq!(result_column_count(&[ResultColumn::Star], &table), 3);
2452 assert_eq!(
2453 result_column_count(&[ResultColumn::Star, expr()], &table),
2454 4
2455 );
2456 assert_eq!(
2458 result_column_count(&[ResultColumn::Star, ResultColumn::Star], &table),
2459 6
2460 );
2461
2462 let table_star = || ResultColumn::TableStar(QualifiedName::bare("t"));
2466 assert_eq!(result_column_count(&[table_star()], &table), 3);
2467 assert_eq!(
2468 result_column_count(&[table_star(), ResultColumn::Star], &table),
2469 6
2470 );
2471 assert_eq!(result_column_count(&[table_star(), expr()], &table), 4);
2472 }
2473
2474 #[test]
2475 fn test_expr_classifiers_column_name_rowid_and_bind_param() {
2476 let col = |name: &str| Expr::Column(ColumnRef::bare(name), Span::ZERO);
2477 let lit = Expr::Literal(Literal::Integer(7), Span::ZERO);
2478
2479 assert_eq!(column_name(&col("name")), Some("name".to_owned()));
2481 assert_eq!(
2482 column_name(&col("rowid")),
2483 None,
2484 "rowid is excluded from column extraction"
2485 );
2486 assert_eq!(column_name(&col("OID")), None);
2487 assert_eq!(column_name(&lit), None);
2488
2489 assert!(is_rowid_expr(&col("rowid")));
2491 assert!(is_rowid_expr(&col("_rowid_")));
2492 assert!(!is_rowid_expr(&col("name")));
2493 assert!(!is_rowid_expr(&lit));
2494
2495 assert_eq!(
2497 bind_param_ref(&placeholder(5)),
2498 Some(BindParamRef::Numbered(5))
2499 );
2500 assert_eq!(
2501 bind_param_ref(&Expr::Placeholder(PlaceholderType::Anonymous, Span::ZERO)),
2502 Some(BindParamRef::Anonymous)
2503 );
2504 assert_eq!(bind_param_ref(&lit), None);
2505 assert_eq!(bind_param_index(&placeholder(5)), Some(5));
2507 assert_eq!(
2508 bind_param_index(&Expr::Placeholder(PlaceholderType::Anonymous, Span::ZERO)),
2509 Some(1)
2510 );
2511 }
2512
2513 #[test]
2514 fn test_default_value_to_expr_handles_missing_valid_and_invalid_defaults() {
2515 let no_default = ColumnInfo {
2516 name: "a".to_owned(),
2517 affinity: 'd',
2518 default_value: None,
2519 };
2520 let with_default = ColumnInfo {
2521 name: "b".to_owned(),
2522 affinity: 'd',
2523 default_value: Some("42".to_owned()),
2524 };
2525 let bad_default = ColumnInfo {
2526 name: "c".to_owned(),
2527 affinity: 'd',
2528 default_value: Some("1 +".to_owned()),
2529 };
2530 let table = TableSchema {
2531 name: "t".to_owned(),
2532 root_page: 2,
2533 columns: vec![no_default.clone(), with_default.clone()],
2534 indexes: vec![],
2535 };
2536
2537 assert!(matches!(
2539 default_value_to_expr(&table, &no_default),
2540 Ok(Expr::Literal(Literal::Null, _))
2541 ));
2542 assert!(default_value_to_expr(&table, &with_default).is_ok());
2544 assert!(matches!(
2546 default_value_to_expr(&table, &bad_default),
2547 Err(CodegenError::Unsupported(_))
2548 ));
2549
2550 let defaults = insert_default_exprs(&table).expect("defaults compile");
2553 assert_eq!(defaults.len(), 2);
2554 assert!(
2555 matches!(defaults[0], Expr::Literal(Literal::Null, _)),
2556 "column a has no default -> NULL"
2557 );
2558 }
2559
2560 #[test]
2561 fn test_insert_target_indices_resolution_order_and_errors() {
2562 let table = TableSchema {
2563 name: "t".to_owned(),
2564 root_page: 2,
2565 columns: vec![
2566 ColumnInfo {
2567 name: "a".to_owned(),
2568 affinity: 'd',
2569 default_value: None,
2570 },
2571 ColumnInfo {
2572 name: "b".to_owned(),
2573 affinity: 'd',
2574 default_value: None,
2575 },
2576 ColumnInfo {
2577 name: "c".to_owned(),
2578 affinity: 'd',
2579 default_value: None,
2580 },
2581 ],
2582 indexes: vec![],
2583 };
2584
2585 assert_eq!(insert_target_indices(&[], &table).unwrap(), vec![0, 1, 2]);
2587
2588 assert_eq!(
2591 insert_target_indices(&["c".to_owned(), "A".to_owned()], &table).unwrap(),
2592 vec![2, 0]
2593 );
2594
2595 assert!(matches!(
2597 insert_target_indices(&["a".to_owned(), "missing".to_owned()], &table),
2598 Err(CodegenError::ColumnNotFound { ref column, .. }) if column == "missing"
2599 ));
2600 }
2601
2602 #[test]
2603 fn test_expand_insert_values_row_places_values_and_fills_defaults() {
2604 let lit = |n: i64| Expr::Literal(Literal::Integer(n), Span::ZERO);
2605 let table = TableSchema {
2606 name: "t".to_owned(),
2607 root_page: 2,
2608 columns: vec![
2609 ColumnInfo {
2610 name: "a".to_owned(),
2611 affinity: 'd',
2612 default_value: None,
2613 },
2614 ColumnInfo {
2615 name: "b".to_owned(),
2616 affinity: 'd',
2617 default_value: Some("99".to_owned()),
2618 },
2619 ColumnInfo {
2620 name: "c".to_owned(),
2621 affinity: 'd',
2622 default_value: None,
2623 },
2624 ],
2625 indexes: vec![],
2626 };
2627
2628 let passed = expand_insert_values_row(&[lit(1), lit(2), lit(3)], &[], &table).unwrap();
2630 assert_eq!(passed.len(), 3);
2631 assert!(matches!(passed[0], Expr::Literal(Literal::Integer(1), _)));
2632 assert!(matches!(passed[2], Expr::Literal(Literal::Integer(3), _)));
2633
2634 let expanded = expand_insert_values_row(
2637 &[lit(10), lit(20)],
2638 &["c".to_owned(), "a".to_owned()],
2639 &table,
2640 )
2641 .unwrap();
2642 assert_eq!(expanded.len(), 3);
2643 assert!(
2644 matches!(expanded[0], Expr::Literal(Literal::Integer(20), _)),
2645 "a = 20"
2646 );
2647 assert!(
2648 matches!(expanded[1], Expr::Literal(Literal::Integer(99), _)),
2649 "b = default 99"
2650 );
2651 assert!(
2652 matches!(expanded[2], Expr::Literal(Literal::Integer(10), _)),
2653 "c = 10"
2654 );
2655
2656 assert!(matches!(
2658 expand_insert_values_row(&[lit(1)], &["a".to_owned(), "b".to_owned()], &table),
2659 Err(CodegenError::Unsupported(_))
2660 ));
2661 }
2662
2663 #[test]
2664 fn test_single_table_select_source_name_resolves_table_and_rejects_subquery() {
2665 let from = from_table("users");
2667 assert_eq!(
2668 single_table_select_source_name(&from.source).unwrap(),
2669 "users"
2670 );
2671
2672 let subquery = TableOrSubquery::Subquery {
2674 query: Box::new(star_select("x")),
2675 alias: None,
2676 };
2677 assert!(matches!(
2678 single_table_select_source_name(&subquery),
2679 Err(CodegenError::Unsupported(_))
2680 ));
2681 }
2682
2683 #[test]
2684 fn test_find_table_is_case_insensitive_and_errors_on_missing() {
2685 let schema = vec![
2686 TableSchema {
2687 name: "Users".to_owned(),
2688 root_page: 2,
2689 columns: vec![],
2690 indexes: vec![],
2691 },
2692 TableSchema {
2693 name: "orders".to_owned(),
2694 root_page: 3,
2695 columns: vec![],
2696 indexes: vec![],
2697 },
2698 ];
2699 assert_eq!(find_table(&schema, "Users").unwrap().name, "Users");
2701 assert_eq!(
2702 find_table(&schema, "users").unwrap().name,
2703 "Users",
2704 "case-insensitive"
2705 );
2706 assert_eq!(find_table(&schema, "ORDERS").unwrap().name, "orders");
2707 assert!(matches!(
2709 find_table(&schema, "missing"),
2710 Err(CodegenError::TableNotFound(ref n)) if n == "missing"
2711 ));
2712 }
2713
2714 #[test]
2715 fn test_codegen_error_display_messages() {
2716 assert_eq!(
2717 CodegenError::TableNotFound("users".to_owned()).to_string(),
2718 "table not found: users"
2719 );
2720 assert_eq!(
2722 CodegenError::ColumnNotFound {
2723 table: "t".to_owned(),
2724 column: "c".to_owned(),
2725 }
2726 .to_string(),
2727 "column c not found in table t"
2728 );
2729 assert_eq!(
2730 CodegenError::Unsupported("DISTINCT".to_owned()).to_string(),
2731 "unsupported: DISTINCT"
2732 );
2733 }
2734
2735 #[test]
2736 fn test_codegen_select_no_from_emits_resultrow_without_cursor() {
2737 let stmt = SelectStatement {
2738 with: None,
2739 body: SelectBody {
2740 select: SelectCore::Select {
2741 distinct: Distinctness::All,
2742 columns: vec![ResultColumn::Expr {
2743 expr: Expr::Literal(Literal::Integer(1), Span::ZERO),
2744 alias: None,
2745 }],
2746 from: None,
2747 where_clause: None,
2748 group_by: vec![],
2749 having: None,
2750 windows: vec![],
2751 },
2752 compounds: vec![],
2753 },
2754 order_by: vec![],
2755 limit: None,
2756 };
2757 let schema: Vec<TableSchema> = vec![];
2758 let ctx = CodegenContext::default();
2759 let mut b = ProgramBuilder::new();
2760 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
2761 let prog = b.finish().unwrap();
2762
2763 assert!(has_opcodes(
2765 &prog,
2766 &[Opcode::Integer, Opcode::ResultRow, Opcode::Halt]
2767 ));
2768 assert!(
2770 prog.ops().iter().all(|op| op.opcode != Opcode::OpenRead),
2771 "a no-FROM SELECT must not open a read cursor"
2772 );
2773 }
2774
2775 #[test]
2776 fn test_codegen_select_column_eq_emits_filtered_scan() {
2777 let stmt = simple_select(&["a"], "t", Some(col_eq_param("b", 1)));
2780 let schema = test_schema();
2781 let ctx = CodegenContext::default();
2782 let mut b = ProgramBuilder::new();
2783 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
2784 let prog = b.finish().unwrap();
2785
2786 assert!(has_opcodes(
2789 &prog,
2790 &[
2791 Opcode::Variable,
2792 Opcode::OpenRead,
2793 Opcode::Rewind,
2794 Opcode::Column,
2795 Opcode::Ne,
2796 Opcode::ResultRow,
2797 Opcode::Next,
2798 Opcode::Halt,
2799 ]
2800 ));
2801 }
2802
2803 #[test]
2804 fn test_codegen_select_values_emits_one_resultrow_per_row() {
2805 let lit = |n: i64| Expr::Literal(Literal::Integer(n), Span::ZERO);
2806 let values = |rows: Vec<Vec<Expr>>| SelectStatement {
2807 with: None,
2808 body: SelectBody {
2809 select: SelectCore::Values(rows),
2810 compounds: vec![],
2811 },
2812 order_by: vec![],
2813 limit: None,
2814 };
2815 let schema: Vec<TableSchema> = vec![];
2816 let ctx = CodegenContext::default();
2817
2818 let stmt = values(vec![vec![lit(1), lit(2)], vec![lit(3), lit(4)]]);
2820 let mut b = ProgramBuilder::new();
2821 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
2822 let prog = b.finish().unwrap();
2823 let resultrows = prog
2824 .ops()
2825 .iter()
2826 .filter(|op| op.opcode == Opcode::ResultRow)
2827 .count();
2828 assert_eq!(resultrows, 2, "one ResultRow per VALUES row");
2829 assert!(
2830 prog.ops().iter().all(|op| op.opcode != Opcode::OpenRead),
2831 "VALUES has no table cursor"
2832 );
2833 assert!(has_opcodes(&prog, &[Opcode::ResultRow, Opcode::Halt]));
2834
2835 let bad = values(vec![vec![lit(1)], vec![lit(1), lit(2)]]);
2837 let mut b2 = ProgramBuilder::new();
2838 assert!(matches!(
2839 codegen_select(&mut b2, &bad, &schema, &ctx),
2840 Err(CodegenError::Unsupported(_))
2841 ));
2842 }
2843
2844 #[test]
2845 fn test_codegen_insert_threads_conflict_action_into_insert_op() {
2846 let make = |conflict: Option<ConflictAction>| InsertStatement {
2847 with: None,
2848 or_conflict: conflict,
2849 table: QualifiedName::bare("t"),
2850 alias: None,
2851 columns: vec![],
2852 source: InsertSource::Values(vec![vec![placeholder(1), placeholder(2)]]),
2853 upsert: vec![],
2854 returning: vec![],
2855 };
2856 let schema = test_schema();
2857 let ctx = CodegenContext::default();
2858
2859 let oe_of = |conflict: Option<ConflictAction>| -> u16 {
2861 let mut b = ProgramBuilder::new();
2862 codegen_insert(&mut b, &make(conflict), &schema, &ctx).unwrap();
2863 let prog = b.finish().unwrap();
2864 prog.ops()
2865 .iter()
2866 .find(|op| op.opcode == Opcode::Insert)
2867 .expect("Insert op present")
2868 .p5
2869 };
2870
2871 assert_eq!(oe_of(None), OE_ABORT);
2873 assert_eq!(oe_of(Some(ConflictAction::Ignore)), OE_IGNORE);
2874 assert_eq!(oe_of(Some(ConflictAction::Replace)), OE_REPLACE);
2875 }
2876
2877 fn rowid_eq_param() -> Box<Expr> {
2878 Box::new(Expr::BinaryOp {
2879 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
2880 op: AstBinaryOp::Eq,
2881 right: Box::new(placeholder(1)),
2882 span: Span::ZERO,
2883 })
2884 }
2885
2886 fn col_eq_param(col: &str, n: u32) -> Box<Expr> {
2887 Box::new(Expr::BinaryOp {
2888 left: Box::new(Expr::Column(ColumnRef::bare(col), Span::ZERO)),
2889 op: AstBinaryOp::Eq,
2890 right: Box::new(placeholder(n)),
2891 span: Span::ZERO,
2892 })
2893 }
2894
2895 fn simple_select(
2896 cols: &[&str],
2897 table: &str,
2898 where_clause: Option<Box<Expr>>,
2899 ) -> SelectStatement {
2900 SelectStatement {
2901 with: None,
2902 body: SelectBody {
2903 select: SelectCore::Select {
2904 distinct: Distinctness::All,
2905 columns: cols
2906 .iter()
2907 .map(|c| ResultColumn::Expr {
2908 expr: Expr::Column(ColumnRef::bare(*c), Span::ZERO),
2909 alias: None,
2910 })
2911 .collect(),
2912 from: Some(from_table(table)),
2913 where_clause,
2914 group_by: vec![],
2915 having: None,
2916 windows: vec![],
2917 },
2918 compounds: vec![],
2919 },
2920 order_by: vec![],
2921 limit: None,
2922 }
2923 }
2924
2925 fn star_select(table: &str) -> SelectStatement {
2926 SelectStatement {
2927 with: None,
2928 body: SelectBody {
2929 select: SelectCore::Select {
2930 distinct: Distinctness::All,
2931 columns: vec![ResultColumn::Star],
2932 from: Some(from_table(table)),
2933 where_clause: None,
2934 group_by: vec![],
2935 having: None,
2936 windows: vec![],
2937 },
2938 compounds: vec![],
2939 },
2940 order_by: vec![],
2941 limit: None,
2942 }
2943 }
2944
2945 fn opcode_sequence(prog: &VdbeProgram) -> Vec<Opcode> {
2946 prog.ops().iter().map(|op| op.opcode).collect()
2947 }
2948
2949 fn has_opcodes(prog: &VdbeProgram, expected: &[Opcode]) -> bool {
2950 let ops = opcode_sequence(prog);
2951 let mut ops_iter = ops.iter();
2953 for expected_op in expected {
2954 if !ops_iter.any(|op| op == expected_op) {
2955 return false;
2956 }
2957 }
2958 true
2959 }
2960
2961 #[test]
2962 fn test_emit_expr_literals() {
2963 let mut b = ProgramBuilder::new();
2964
2965 let reg_real = b.alloc_reg();
2966 emit_expr(
2967 &mut b,
2968 &Expr::Literal(Literal::Float(3.25), Span::ZERO),
2969 reg_real,
2970 )
2971 .unwrap();
2972
2973 let reg_blob = b.alloc_reg();
2974 emit_expr(
2975 &mut b,
2976 &Expr::Literal(Literal::Blob(vec![0, 1, 2, 3]), Span::ZERO),
2977 reg_blob,
2978 )
2979 .unwrap();
2980
2981 let reg_null = b.alloc_reg();
2982 emit_expr(&mut b, &Expr::Literal(Literal::Null, Span::ZERO), reg_null).unwrap();
2983
2984 let reg_true = b.alloc_reg();
2985 emit_expr(&mut b, &Expr::Literal(Literal::True, Span::ZERO), reg_true).unwrap();
2986
2987 let reg_false = b.alloc_reg();
2988 emit_expr(
2989 &mut b,
2990 &Expr::Literal(Literal::False, Span::ZERO),
2991 reg_false,
2992 )
2993 .unwrap();
2994
2995 let reg_current_time = b.alloc_reg();
2996 emit_expr(
2997 &mut b,
2998 &Expr::Literal(Literal::CurrentTime, Span::ZERO),
2999 reg_current_time,
3000 )
3001 .unwrap();
3002
3003 let reg_current_date = b.alloc_reg();
3004 emit_expr(
3005 &mut b,
3006 &Expr::Literal(Literal::CurrentDate, Span::ZERO),
3007 reg_current_date,
3008 )
3009 .unwrap();
3010
3011 let reg_current_timestamp = b.alloc_reg();
3012 emit_expr(
3013 &mut b,
3014 &Expr::Literal(Literal::CurrentTimestamp, Span::ZERO),
3015 reg_current_timestamp,
3016 )
3017 .unwrap();
3018
3019 let prog = b.finish().unwrap();
3020 let ops = prog.ops();
3021 assert_eq!(ops.len(), 11);
3023
3024 assert_eq!(ops[0].opcode, Opcode::Real);
3025 assert_eq!(ops[0].p2, reg_real);
3026 assert_eq!(ops[0].p4, P4::Real(3.25));
3027
3028 assert_eq!(ops[1].opcode, Opcode::Blob);
3029 assert_eq!(ops[1].p1, 4);
3030 assert_eq!(ops[1].p2, reg_blob);
3031 assert_eq!(ops[1].p4, P4::Blob(vec![0, 1, 2, 3]));
3032
3033 assert_eq!(ops[2].opcode, Opcode::Null);
3034 assert_eq!(ops[2].p2, reg_null);
3035 assert_eq!(ops[2].p4, P4::None);
3036
3037 assert_eq!(ops[3].opcode, Opcode::Integer);
3038 assert_eq!(ops[3].p1, 1);
3039 assert_eq!(ops[3].p2, reg_true);
3040 assert_eq!(ops[3].p4, P4::None);
3041
3042 assert_eq!(ops[4].opcode, Opcode::Integer);
3043 assert_eq!(ops[4].p1, 0);
3044 assert_eq!(ops[4].p2, reg_false);
3045 assert_eq!(ops[4].p4, P4::None);
3046
3047 assert_eq!(ops[5].opcode, Opcode::String8);
3049 assert_eq!(ops[6].opcode, Opcode::PureFunc);
3050 assert_eq!(ops[6].p3, reg_current_time);
3051
3052 assert_eq!(ops[7].opcode, Opcode::String8);
3054 assert_eq!(ops[8].opcode, Opcode::PureFunc);
3055 assert_eq!(ops[8].p3, reg_current_date);
3056
3057 assert_eq!(ops[9].opcode, Opcode::String8);
3059 assert_eq!(ops[10].opcode, Opcode::PureFunc);
3060 assert_eq!(ops[10].p3, reg_current_timestamp);
3061 }
3062
3063 #[test]
3064 fn test_emit_expr_scalar_literal_opcodes() {
3065 let emit_first = |lit: Literal| -> (Opcode, i32) {
3069 let mut b = ProgramBuilder::new();
3070 let reg = b.alloc_reg();
3071 emit_expr(&mut b, &Expr::Literal(lit, Span::ZERO), reg).unwrap();
3072 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3073 let prog = b.finish().unwrap();
3074 let op = prog
3075 .ops()
3076 .iter()
3077 .find(|o| o.opcode != Opcode::Halt)
3078 .expect("a literal op");
3079 (op.opcode, op.p1)
3080 };
3081
3082 assert_eq!(emit_first(Literal::Null).0, Opcode::Null);
3083 assert_eq!(emit_first(Literal::Float(1.5)).0, Opcode::Real);
3084 assert_eq!(
3085 emit_first(Literal::String("hi".to_owned())).0,
3086 Opcode::String8
3087 );
3088 assert_eq!(emit_first(Literal::Blob(vec![1, 2, 3])).0, Opcode::Blob);
3089
3090 assert_eq!(emit_first(Literal::True), (Opcode::Integer, 1));
3092 assert_eq!(emit_first(Literal::False), (Opcode::Integer, 0));
3093 }
3094
3095 #[test]
3096 fn test_emit_expr_arithmetic_and_unary_ops() {
3097 let lit = |n: i64| Box::new(Expr::Literal(Literal::Integer(n), Span::ZERO));
3101 let prog_of = |expr: Expr| {
3102 let mut b = ProgramBuilder::new();
3103 let reg = b.alloc_reg();
3104 emit_expr(&mut b, &expr, reg).unwrap();
3105 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3106 b.finish().unwrap()
3107 };
3108
3109 let add = prog_of(Expr::BinaryOp {
3111 left: lit(3),
3112 op: AstBinaryOp::Add,
3113 right: lit(5),
3114 span: Span::ZERO,
3115 });
3116 assert!(has_opcodes(
3117 &add,
3118 &[Opcode::Integer, Opcode::Integer, Opcode::Add]
3119 ));
3120
3121 let neg = prog_of(Expr::UnaryOp {
3123 op: AstUnaryOp::Negate,
3124 expr: lit(3),
3125 span: Span::ZERO,
3126 });
3127 assert!(has_opcodes(
3128 &neg,
3129 &[Opcode::Integer, Opcode::Integer, Opcode::Multiply]
3130 ));
3131
3132 let not = prog_of(Expr::UnaryOp {
3134 op: AstUnaryOp::Not,
3135 expr: lit(3),
3136 span: Span::ZERO,
3137 });
3138 assert!(not.ops().iter().any(|o| o.opcode == Opcode::Not));
3139 let bitnot = prog_of(Expr::UnaryOp {
3140 op: AstUnaryOp::BitNot,
3141 expr: lit(3),
3142 span: Span::ZERO,
3143 });
3144 assert!(bitnot.ops().iter().any(|o| o.opcode == Opcode::BitNot));
3145
3146 let plus = prog_of(Expr::UnaryOp {
3148 op: AstUnaryOp::Plus,
3149 expr: lit(3),
3150 span: Span::ZERO,
3151 });
3152 let non_halt: Vec<Opcode> = plus
3153 .ops()
3154 .iter()
3155 .map(|o| o.opcode)
3156 .filter(|&o| o != Opcode::Halt)
3157 .collect();
3158 assert_eq!(
3159 non_halt,
3160 vec![Opcode::Integer],
3161 "unary plus emits only the operand"
3162 );
3163 }
3164
3165 #[test]
3166 fn test_emit_expr_cast_threads_affinity_char_into_cast_op_p2() {
3167 let cast_p2 = |type_name: &str| -> i32 {
3172 let mut b = ProgramBuilder::new();
3173 let reg = b.alloc_reg();
3174 let expr = Expr::Cast {
3175 expr: Box::new(Expr::Literal(Literal::Integer(3), Span::ZERO)),
3176 type_name: fsqlite_ast::TypeName {
3177 name: type_name.to_owned(),
3178 arg1: None,
3179 arg2: None,
3180 },
3181 span: Span::ZERO,
3182 };
3183 emit_expr(&mut b, &expr, reg).unwrap();
3184 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3185 let prog = b.finish().unwrap();
3186 prog.ops()
3187 .iter()
3188 .find(|o| o.opcode == Opcode::Cast)
3189 .expect("Cast op present")
3190 .p2
3191 };
3192
3193 assert_eq!(cast_p2("INTEGER"), i32::from(b'D'));
3195 assert_eq!(cast_p2("TEXT"), i32::from(b'B'));
3196 assert_eq!(cast_p2("REAL"), i32::from(b'E'));
3197 assert_eq!(cast_p2("BLOB"), i32::from(b'A'));
3198 }
3199
3200 #[test]
3201 fn test_emit_expr_is_null_vs_is_not_null_guard_opcode() {
3202 let prog_of = |not: bool| {
3205 let mut b = ProgramBuilder::new();
3206 let reg = b.alloc_reg();
3207 let expr = Expr::IsNull {
3208 expr: Box::new(Expr::Literal(Literal::Integer(3), Span::ZERO)),
3209 not,
3210 span: Span::ZERO,
3211 };
3212 emit_expr(&mut b, &expr, reg).unwrap();
3213 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3214 b.finish().unwrap()
3215 };
3216
3217 let is_null = prog_of(false);
3219 assert!(has_opcodes(
3220 &is_null,
3221 &[
3222 Opcode::IsNull,
3223 Opcode::Integer,
3224 Opcode::Goto,
3225 Opcode::Integer
3226 ]
3227 ));
3228 assert!(!is_null.ops().iter().any(|o| o.opcode == Opcode::NotNull));
3229
3230 let is_not_null = prog_of(true);
3232 assert!(has_opcodes(
3233 &is_not_null,
3234 &[
3235 Opcode::NotNull,
3236 Opcode::Integer,
3237 Opcode::Goto,
3238 Opcode::Integer
3239 ]
3240 ));
3241 assert!(!is_not_null.ops().iter().any(|o| o.opcode == Opcode::IsNull));
3242 }
3243
3244 #[test]
3245 fn test_emit_expr_function_call_canonicalizes_name_and_threads_arg_count() {
3246 let mut b = ProgramBuilder::new();
3250 let reg = b.alloc_reg();
3251 let expr = Expr::FunctionCall {
3252 name: "abs".to_owned(), args: FunctionArgs::List(vec![Expr::Literal(Literal::Integer(3), Span::ZERO)]),
3254 distinct: false,
3255 order_by: vec![],
3256 filter: None,
3257 over: None,
3258 span: Span::ZERO,
3259 };
3260 emit_expr(&mut b, &expr, reg).unwrap();
3261 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3262 let prog = b.finish().unwrap();
3263 let func = prog
3264 .ops()
3265 .iter()
3266 .find(|o| o.opcode == Opcode::PureFunc)
3267 .expect("PureFunc op present");
3268 assert!(
3269 matches!(&func.p4, P4::FuncName(n) if n.as_str() == "ABS"),
3270 "lowercase 'abs' must be canonicalized to uppercase in P4"
3271 );
3272 assert_eq!(func.p5, 1, "one argument threaded into p5");
3273 }
3274
3275 #[test]
3276 fn test_emit_expr_collate_is_transparent() {
3277 let mut b = ProgramBuilder::new();
3280 let reg = b.alloc_reg();
3281 let expr = Expr::Collate {
3282 expr: Box::new(Expr::Literal(Literal::Integer(3), Span::ZERO)),
3283 collation: "NOCASE".to_owned(),
3284 span: Span::ZERO,
3285 };
3286 emit_expr(&mut b, &expr, reg).unwrap();
3287 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3288 let prog = b.finish().unwrap();
3289 let non_halt: Vec<Opcode> = prog
3290 .ops()
3291 .iter()
3292 .map(|o| o.opcode)
3293 .filter(|&o| o != Opcode::Halt)
3294 .collect();
3295 assert_eq!(
3296 non_halt,
3297 vec![Opcode::Integer],
3298 "COLLATE wraps transparently: only the inner literal's opcode is emitted"
3299 );
3300 }
3301
3302 #[test]
3303 fn test_emit_expr_case_searched_and_simple_forms() {
3304 let lit = |n: i64| Expr::Literal(Literal::Integer(n), Span::ZERO);
3305 let prog_of = |expr: Expr| {
3306 let mut b = ProgramBuilder::new();
3307 let reg = b.alloc_reg();
3308 emit_expr(&mut b, &expr, reg).unwrap();
3309 b.emit_op(Opcode::Halt, 0, 0, 0, P4::None, 0);
3310 b.finish().unwrap()
3311 };
3312
3313 let searched = prog_of(Expr::Case {
3316 operand: None,
3317 whens: vec![(lit(1), lit(10))],
3318 else_expr: None,
3319 span: Span::ZERO,
3320 });
3321 assert!(
3322 searched.ops().iter().any(|o| o.opcode == Opcode::IfNot),
3323 "searched CASE guards each WHEN with IfNot"
3324 );
3325 assert!(
3326 searched.ops().iter().any(|o| o.opcode == Opcode::Null),
3327 "a CASE with no ELSE falls through to a Null default"
3328 );
3329
3330 let simple = prog_of(Expr::Case {
3333 operand: Some(Box::new(lit(5))),
3334 whens: vec![(lit(5), lit(10))],
3335 else_expr: Some(Box::new(lit(0))),
3336 span: Span::ZERO,
3337 });
3338 assert!(
3339 simple.ops().iter().any(|o| o.opcode == Opcode::Ne),
3340 "simple CASE compares the operand against each WHEN with Ne"
3341 );
3342 assert!(
3343 !simple.ops().iter().any(|o| o.opcode == Opcode::Null),
3344 "an explicit ELSE means no implicit Null default"
3345 );
3346 }
3347
3348 #[test]
3349 fn test_emit_expr_unsupported_expr_returns_error() {
3350 let mut b = ProgramBuilder::new();
3356 let reg = b.alloc_reg();
3357 let err = emit_expr(&mut b, &Expr::Column(ColumnRef::bare("x"), Span::ZERO), reg)
3358 .expect_err("a free column reference is not emittable by emit_expr");
3359 assert!(matches!(err, CodegenError::Unsupported(_)));
3360 }
3361
3362 #[test]
3363 fn test_emit_expr_large_integer_literal_uses_int64_opcode() {
3364 let mut b = ProgramBuilder::new();
3365 let reg = b.alloc_reg();
3366 let value = 4_102_444_800_000_000_i64;
3367 emit_expr(
3368 &mut b,
3369 &Expr::Literal(Literal::Integer(value), Span::ZERO),
3370 reg,
3371 )
3372 .unwrap();
3373
3374 let prog = b.finish().unwrap();
3375 let ops = prog.ops();
3376 assert_eq!(ops.len(), 1);
3377 assert_eq!(ops[0].opcode, Opcode::Int64);
3378 assert_eq!(ops[0].p2, reg);
3379 assert_eq!(ops[0].p4, P4::Int64(value));
3380 }
3381
3382 #[test]
3384 fn test_codegen_select_by_rowid() {
3385 let stmt = simple_select(&["b"], "t", Some(rowid_eq_param()));
3386 let schema = test_schema();
3387 let ctx = CodegenContext::default();
3388 let mut b = ProgramBuilder::new();
3389 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
3390 let prog = b.finish().unwrap();
3391
3392 assert!(has_opcodes(
3393 &prog,
3394 &[
3395 Opcode::Init,
3396 Opcode::Transaction,
3397 Opcode::Variable,
3398 Opcode::OpenRead,
3399 Opcode::SeekRowid,
3400 Opcode::Column,
3401 Opcode::ResultRow,
3402 Opcode::Close,
3403 Opcode::Halt,
3404 ]
3405 ));
3406 let txn = prog
3408 .ops()
3409 .iter()
3410 .find(|op| op.opcode == Opcode::Transaction)
3411 .unwrap();
3412 assert_eq!(txn.p2, 0);
3413 }
3414
3415 #[test]
3416 fn test_codegen_select_parenthesized_single_table_source() -> Result<(), String> {
3417 let stmt = SelectStatement {
3418 with: None,
3419 body: SelectBody {
3420 select: SelectCore::Select {
3421 distinct: Distinctness::All,
3422 columns: vec![ResultColumn::Expr {
3423 expr: Expr::Column(ColumnRef::bare("b"), Span::ZERO),
3424 alias: None,
3425 }],
3426 from: Some(FromClause {
3427 source: TableOrSubquery::ParenJoin(Box::new(from_table("t"))),
3428 joins: vec![],
3429 }),
3430 where_clause: Some(rowid_eq_param()),
3431 group_by: vec![],
3432 having: None,
3433 windows: vec![],
3434 },
3435 compounds: vec![],
3436 },
3437 order_by: vec![],
3438 limit: None,
3439 };
3440 let schema = test_schema();
3441 let ctx = CodegenContext::default();
3442 let mut b = ProgramBuilder::new();
3443 codegen_select(&mut b, &stmt, &schema, &ctx).map_err(|err| format!("{err:?}"))?;
3444 let prog = b.finish().map_err(|err| format!("{err:?}"))?;
3445
3446 if !has_opcodes(
3447 &prog,
3448 &[
3449 Opcode::OpenRead,
3450 Opcode::SeekRowid,
3451 Opcode::Column,
3452 Opcode::ResultRow,
3453 ],
3454 ) {
3455 return Err(format!(
3456 "parenthesized table source should use table SELECT path, got {:?}",
3457 opcode_sequence(&prog)
3458 ));
3459 }
3460
3461 let open_read_root = prog
3462 .ops()
3463 .iter()
3464 .find(|op| op.opcode == Opcode::OpenRead)
3465 .map(|op| op.p2);
3466 if open_read_root != Some(2) {
3467 return Err(format!(
3468 "expected OpenRead root page 2, got {open_read_root:?}"
3469 ));
3470 }
3471
3472 Ok(())
3473 }
3474
3475 #[test]
3476 fn test_codegen_select_values_multirow() -> Result<(), String> {
3477 let stmt = SelectStatement {
3478 with: None,
3479 body: SelectBody {
3480 select: SelectCore::Values(vec![
3481 vec![
3482 Expr::Literal(Literal::Integer(1), Span::ZERO),
3483 Expr::Literal(Literal::String("alpha".to_owned()), Span::ZERO),
3484 ],
3485 vec![
3486 Expr::Literal(Literal::Integer(2), Span::ZERO),
3487 Expr::Literal(Literal::String("beta".to_owned()), Span::ZERO),
3488 ],
3489 ]),
3490 compounds: vec![],
3491 },
3492 order_by: vec![],
3493 limit: None,
3494 };
3495 let ctx = CodegenContext::default();
3496 let mut b = ProgramBuilder::new();
3497 codegen_select(&mut b, &stmt, &[], &ctx).map_err(|err| format!("{err:?}"))?;
3498 let prog = b.finish().map_err(|err| format!("{err:?}"))?;
3499
3500 if !has_opcodes(
3501 &prog,
3502 &[
3503 Opcode::Init,
3504 Opcode::Transaction,
3505 Opcode::Integer,
3506 Opcode::String8,
3507 Opcode::ResultRow,
3508 Opcode::Integer,
3509 Opcode::String8,
3510 Opcode::ResultRow,
3511 Opcode::Halt,
3512 ],
3513 ) {
3514 return Err(format!(
3515 "VALUES SELECT should emit one result row per VALUES row, got {:?}",
3516 opcode_sequence(&prog)
3517 ));
3518 }
3519
3520 let result_row_count = prog
3521 .ops()
3522 .iter()
3523 .filter(|op| op.opcode == Opcode::ResultRow && op.p2 == 2)
3524 .count();
3525 if result_row_count != 2 {
3526 return Err(format!(
3527 "VALUES SELECT should emit two two-column ResultRow ops, got {result_row_count}"
3528 ));
3529 }
3530
3531 Ok(())
3532 }
3533
3534 #[test]
3535 fn test_codegen_select_values_rejects_mismatched_arity() -> Result<(), String> {
3536 let stmt = SelectStatement {
3537 with: None,
3538 body: SelectBody {
3539 select: SelectCore::Values(vec![
3540 vec![Expr::Literal(Literal::Integer(1), Span::ZERO)],
3541 vec![
3542 Expr::Literal(Literal::Integer(2), Span::ZERO),
3543 Expr::Literal(Literal::Integer(3), Span::ZERO),
3544 ],
3545 ]),
3546 compounds: vec![],
3547 },
3548 order_by: vec![],
3549 limit: None,
3550 };
3551 let ctx = CodegenContext::default();
3552 let mut b = ProgramBuilder::new();
3553
3554 match codegen_select(&mut b, &stmt, &[], &ctx) {
3555 Err(CodegenError::Unsupported(msg)) if msg.contains("same arity") => Ok(()),
3556 other => Err(format!("expected VALUES arity error, got {other:?}")),
3557 }
3558 }
3559
3560 #[test]
3562 fn test_codegen_insert_values() {
3563 let stmt = InsertStatement {
3564 with: None,
3565 or_conflict: None,
3566 table: QualifiedName::bare("t"),
3567 alias: None,
3568 columns: vec![],
3569 source: InsertSource::Values(vec![vec![placeholder(1), placeholder(2)]]),
3570 upsert: vec![],
3571 returning: vec![],
3572 };
3573 let schema = test_schema();
3574 let ctx = CodegenContext::default();
3575 let mut b = ProgramBuilder::new();
3576 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3577 let prog = b.finish().unwrap();
3578
3579 assert!(has_opcodes(
3580 &prog,
3581 &[
3582 Opcode::Init,
3583 Opcode::Transaction,
3584 Opcode::OpenWrite,
3585 Opcode::NewRowid,
3586 Opcode::Variable,
3587 Opcode::Variable,
3588 Opcode::MakeRecord,
3589 Opcode::Insert,
3590 Opcode::Close,
3591 Opcode::Halt,
3592 ]
3593 ));
3594 let txn = prog
3596 .ops()
3597 .iter()
3598 .find(|op| op.opcode == Opcode::Transaction)
3599 .unwrap();
3600 assert_eq!(txn.p2, 1);
3601 }
3602
3603 #[test]
3604 fn test_codegen_insert_values_uses_declared_defaults_for_omitted_columns() {
3605 let stmt = InsertStatement {
3606 with: None,
3607 or_conflict: None,
3608 table: QualifiedName::bare("t"),
3609 alias: None,
3610 columns: vec!["name".to_owned()],
3611 source: InsertSource::Values(vec![vec![placeholder(1)]]),
3612 upsert: vec![],
3613 returning: vec![],
3614 };
3615 let schema = vec![TableSchema {
3616 name: "t".to_owned(),
3617 root_page: 2,
3618 columns: vec![
3619 ColumnInfo {
3620 name: "id".to_owned(),
3621 affinity: 'd',
3622 default_value: None,
3623 },
3624 ColumnInfo {
3625 name: "name".to_owned(),
3626 affinity: 'C',
3627 default_value: None,
3628 },
3629 ColumnInfo {
3630 name: "status".to_owned(),
3631 affinity: 'C',
3632 default_value: Some("'pending'".to_owned()),
3633 },
3634 ],
3635 indexes: vec![],
3636 }];
3637 let ctx = CodegenContext::default();
3638 let mut b = ProgramBuilder::new();
3639 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3640 let prog = b.finish().unwrap();
3641
3642 assert!(
3643 prog.ops()
3644 .iter()
3645 .any(|op| op.opcode == Opcode::String8 && op.p4 == P4::Str("pending".to_owned()))
3646 );
3647 }
3648
3649 #[test]
3650 fn test_codegen_insert_allows_duplicate_target_columns() {
3651 let stmt = InsertStatement {
3652 with: None,
3653 or_conflict: None,
3654 table: QualifiedName::bare("t"),
3655 alias: None,
3656 columns: vec!["b".to_owned(), "b".to_owned()],
3657 source: InsertSource::Values(vec![vec![placeholder(1), placeholder(2)]]),
3658 upsert: vec![],
3659 returning: vec![],
3660 };
3661 let schema = test_schema();
3662 let ctx = CodegenContext::default();
3663 let mut b = ProgramBuilder::new();
3664 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3665 }
3666
3667 #[test]
3668 fn test_codegen_insert_default_values_uses_declared_defaults() {
3669 let stmt = InsertStatement {
3670 with: None,
3671 or_conflict: None,
3672 table: QualifiedName::bare("t"),
3673 alias: None,
3674 columns: vec![],
3675 source: InsertSource::DefaultValues,
3676 upsert: vec![],
3677 returning: vec![],
3678 };
3679 let schema = vec![TableSchema {
3680 name: "t".to_owned(),
3681 root_page: 2,
3682 columns: vec![
3683 ColumnInfo {
3684 name: "id".to_owned(),
3685 affinity: 'd',
3686 default_value: None,
3687 },
3688 ColumnInfo {
3689 name: "status".to_owned(),
3690 affinity: 'C',
3691 default_value: Some("'active'".to_owned()),
3692 },
3693 ColumnInfo {
3694 name: "count".to_owned(),
3695 affinity: 'd',
3696 default_value: Some("42".to_owned()),
3697 },
3698 ],
3699 indexes: vec![],
3700 }];
3701 let ctx = CodegenContext::default();
3702 let mut b = ProgramBuilder::new();
3703 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3704 let prog = b.finish().unwrap();
3705
3706 assert!(
3707 prog.ops()
3708 .iter()
3709 .any(|op| op.opcode == Opcode::String8 && op.p4 == P4::Str("active".to_owned()))
3710 );
3711 assert!(
3712 prog.ops()
3713 .iter()
3714 .any(|op| op.opcode == Opcode::Integer && op.p1 == 42)
3715 );
3716 }
3717
3718 #[test]
3719 fn test_codegen_insert_default_values_uses_expression_defaults() {
3720 let stmt = InsertStatement {
3721 with: None,
3722 or_conflict: None,
3723 table: QualifiedName::bare("t"),
3724 alias: None,
3725 columns: vec![],
3726 source: InsertSource::DefaultValues,
3727 upsert: vec![],
3728 returning: vec![],
3729 };
3730 let schema = vec![TableSchema {
3731 name: "t".to_owned(),
3732 root_page: 2,
3733 columns: vec![
3734 ColumnInfo {
3735 name: "id".to_owned(),
3736 affinity: 'd',
3737 default_value: None,
3738 },
3739 ColumnInfo {
3740 name: "total".to_owned(),
3741 affinity: 'd',
3742 default_value: Some("(40 + 2)".to_owned()),
3743 },
3744 ],
3745 indexes: vec![],
3746 }];
3747 let ctx = CodegenContext::default();
3748 let mut b = ProgramBuilder::new();
3749 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3750 let prog = b.finish().unwrap();
3751
3752 assert!(
3753 prog.ops().iter().any(|op| op.opcode == Opcode::Add),
3754 "expression defaults should compile as expressions, not string literals"
3755 );
3756 }
3757
3758 #[test]
3759 fn test_codegen_insert_default_values_rejects_unparseable_defaults() {
3760 let stmt = InsertStatement {
3761 with: None,
3762 or_conflict: None,
3763 table: QualifiedName::bare("t"),
3764 alias: None,
3765 columns: vec![],
3766 source: InsertSource::DefaultValues,
3767 upsert: vec![],
3768 returning: vec![],
3769 };
3770 let schema = vec![TableSchema {
3771 name: "t".to_owned(),
3772 root_page: 2,
3773 columns: vec![ColumnInfo {
3774 name: "broken".to_owned(),
3775 affinity: 'C',
3776 default_value: Some("('unterminated".to_owned()),
3777 }],
3778 indexes: vec![],
3779 }];
3780 let ctx = CodegenContext::default();
3781 let mut b = ProgramBuilder::new();
3782 let err = codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap_err();
3783 assert!(
3784 matches!(err, CodegenError::Unsupported(ref msg) if msg.contains("failed to parse DEFAULT expression")),
3785 "unexpected error: {err:?}"
3786 );
3787 }
3788
3789 #[test]
3791 #[allow(clippy::too_many_lines)]
3792 fn test_codegen_insert_select_values() {
3793 let inner_values = SelectStatement {
3795 with: None,
3796 body: SelectBody {
3797 select: SelectCore::Values(vec![vec![placeholder(1)]]),
3798 compounds: vec![],
3799 },
3800 order_by: vec![],
3801 limit: None,
3802 };
3803
3804 let stmt = InsertStatement {
3805 with: None,
3806 or_conflict: None,
3807 table: QualifiedName::bare("t"),
3808 alias: None,
3809 columns: vec![],
3810 source: InsertSource::Select(Box::new(inner_values)),
3811 upsert: vec![],
3812 returning: vec![],
3813 };
3814
3815 let schema = test_schema();
3816 let ctx = CodegenContext::default();
3817 let mut b = ProgramBuilder::new();
3818 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3819 let prog = b.finish().unwrap();
3820
3821 assert!(has_opcodes(
3823 &prog,
3824 &[
3825 Opcode::Init,
3826 Opcode::Transaction,
3827 Opcode::OpenWrite,
3828 Opcode::NewRowid,
3829 Opcode::Variable,
3830 Opcode::MakeRecord,
3831 Opcode::Insert,
3832 Opcode::Close,
3833 Opcode::Halt,
3834 ]
3835 ));
3836 }
3837
3838 #[test]
3839 #[allow(clippy::too_many_lines)]
3840 fn test_codegen_insert_select() {
3841 let schema = vec![
3843 TableSchema {
3844 name: "t".to_owned(),
3845 root_page: 2,
3846 columns: vec![
3847 ColumnInfo {
3848 name: "a".to_owned(),
3849 affinity: 'd',
3850 default_value: None,
3851 },
3852 ColumnInfo {
3853 name: "b".to_owned(),
3854 affinity: 'C',
3855 default_value: None,
3856 },
3857 ],
3858 indexes: vec![],
3859 },
3860 TableSchema {
3861 name: "s".to_owned(),
3862 root_page: 3,
3863 columns: vec![
3864 ColumnInfo {
3865 name: "x".to_owned(),
3866 affinity: 'd',
3867 default_value: None,
3868 },
3869 ColumnInfo {
3870 name: "y".to_owned(),
3871 affinity: 'C',
3872 default_value: None,
3873 },
3874 ],
3875 indexes: vec![],
3876 },
3877 ];
3878
3879 let inner_select = SelectStatement {
3881 with: None,
3882 body: SelectBody {
3883 select: SelectCore::Select {
3884 distinct: Distinctness::All,
3885 columns: vec![ResultColumn::Star],
3886 from: Some(FromClause {
3887 source: TableOrSubquery::Table {
3888 name: QualifiedName::bare("s"),
3889 alias: None,
3890 index_hint: None,
3891 time_travel: None,
3892 },
3893 joins: vec![],
3894 }),
3895 where_clause: None,
3896 group_by: vec![],
3897 having: None,
3898 windows: vec![],
3899 },
3900 compounds: vec![],
3901 },
3902 order_by: vec![],
3903 limit: None,
3904 };
3905
3906 let stmt = InsertStatement {
3907 with: None,
3908 or_conflict: None,
3909 table: QualifiedName::bare("t"),
3910 alias: None,
3911 columns: vec![],
3912 source: InsertSource::Select(Box::new(inner_select)),
3913 upsert: vec![],
3914 returning: vec![],
3915 };
3916
3917 let ctx = CodegenContext::default();
3918 let mut b = ProgramBuilder::new();
3919 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
3920 let prog = b.finish().unwrap();
3921
3922 assert!(has_opcodes(
3923 &prog,
3924 &[
3925 Opcode::Init,
3926 Opcode::Transaction,
3927 Opcode::OpenWrite,
3928 Opcode::OpenRead,
3929 Opcode::Rewind,
3930 Opcode::Column,
3931 Opcode::Column,
3932 Opcode::NewRowid,
3933 Opcode::MakeRecord,
3934 Opcode::Insert,
3935 Opcode::Next,
3936 Opcode::Close,
3937 Opcode::Close,
3938 Opcode::Halt,
3939 ]
3940 ));
3941
3942 let txn = prog
3944 .ops()
3945 .iter()
3946 .find(|op| op.opcode == Opcode::Transaction)
3947 .unwrap();
3948 assert_eq!(txn.p2, 1);
3949
3950 let open_write = prog
3952 .ops()
3953 .iter()
3954 .find(|op| op.opcode == Opcode::OpenWrite)
3955 .unwrap();
3956 assert_eq!(open_write.p2, 2);
3957
3958 let open_read = prog
3960 .ops()
3961 .iter()
3962 .find(|op| op.opcode == Opcode::OpenRead)
3963 .unwrap();
3964 assert_eq!(open_read.p2, 3);
3965 }
3966
3967 #[test]
3969 fn test_codegen_insert_select_without_from() {
3970 let inner_select = SelectStatement {
3972 with: None,
3973 body: SelectBody {
3974 select: SelectCore::Select {
3975 distinct: Distinctness::All,
3976 columns: vec![
3977 ResultColumn::Expr {
3978 expr: Expr::Literal(Literal::Integer(42), Span::ZERO),
3979 alias: None,
3980 },
3981 ResultColumn::Expr {
3982 expr: Expr::Literal(Literal::String("hello".to_owned()), Span::ZERO),
3983 alias: None,
3984 },
3985 ],
3986 from: None,
3987 where_clause: None,
3988 group_by: vec![],
3989 having: None,
3990 windows: vec![],
3991 },
3992 compounds: vec![],
3993 },
3994 order_by: vec![],
3995 limit: None,
3996 };
3997
3998 let stmt = InsertStatement {
3999 with: None,
4000 or_conflict: None,
4001 table: QualifiedName::bare("t"),
4002 alias: None,
4003 columns: vec![],
4004 source: InsertSource::Select(Box::new(inner_select)),
4005 upsert: vec![],
4006 returning: vec![],
4007 };
4008
4009 let schema = test_schema();
4010 let ctx = CodegenContext::default();
4011 let mut b = ProgramBuilder::new();
4012 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
4013 let prog = b.finish().unwrap();
4014
4015 assert!(has_opcodes(
4017 &prog,
4018 &[
4019 Opcode::Init,
4020 Opcode::Transaction,
4021 Opcode::OpenWrite,
4022 Opcode::Integer, Opcode::String8, Opcode::NewRowid,
4025 Opcode::MakeRecord,
4026 Opcode::Insert,
4027 Opcode::Close,
4028 Opcode::Halt,
4029 ]
4030 ));
4031
4032 assert!(prog.ops().iter().all(|op| op.opcode != Opcode::OpenRead));
4034
4035 let txn = prog
4037 .ops()
4038 .iter()
4039 .find(|op| op.opcode == Opcode::Transaction)
4040 .unwrap();
4041 assert_eq!(txn.p2, 1);
4042 }
4043
4044 #[test]
4046 fn test_codegen_update_by_rowid() {
4047 let stmt = UpdateStatement {
4048 with: None,
4049 or_conflict: None,
4050 table: QualifiedTableRef {
4051 name: QualifiedName::bare("t"),
4052 alias: None,
4053 index_hint: None,
4054 time_travel: None,
4055 },
4056 assignments: vec![Assignment {
4057 target: AssignmentTarget::Column("b".to_owned()),
4058 value: placeholder(1),
4059 }],
4060 from: None,
4061 where_clause: Some(Expr::BinaryOp {
4062 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4063 op: AstBinaryOp::Eq,
4064 right: Box::new(placeholder(2)),
4065 span: Span::ZERO,
4066 }),
4067 returning: vec![],
4068 order_by: vec![],
4069 limit: None,
4070 };
4071 let schema = test_schema();
4072 let ctx = CodegenContext::default();
4073 let mut b = ProgramBuilder::new();
4074 codegen_update(&mut b, &stmt, &schema, &ctx).unwrap();
4075 let prog = b.finish().unwrap();
4076
4077 assert!(has_opcodes(
4079 &prog,
4080 &[
4081 Opcode::Init,
4082 Opcode::Transaction,
4083 Opcode::Variable, Opcode::Variable, Opcode::OpenWrite,
4086 Opcode::NotExists,
4087 Opcode::Column, Opcode::Column, Opcode::Copy, Opcode::MakeRecord, Opcode::Insert, Opcode::Close,
4093 Opcode::Halt,
4094 ]
4095 ));
4096
4097 let mr = prog
4099 .ops()
4100 .iter()
4101 .find(|op| op.opcode == Opcode::MakeRecord)
4102 .unwrap();
4103 assert_eq!(mr.p2, 2); }
4105
4106 #[test]
4107 fn test_codegen_update_notexists_jump_skips_insert_to_close() {
4108 let stmt = UpdateStatement {
4115 with: None,
4116 or_conflict: None,
4117 table: QualifiedTableRef {
4118 name: QualifiedName::bare("t"),
4119 alias: None,
4120 index_hint: None,
4121 time_travel: None,
4122 },
4123 assignments: vec![Assignment {
4124 target: AssignmentTarget::Column("b".to_owned()),
4125 value: placeholder(1),
4126 }],
4127 from: None,
4128 where_clause: Some(Expr::BinaryOp {
4129 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4130 op: AstBinaryOp::Eq,
4131 right: Box::new(placeholder(2)),
4132 span: Span::ZERO,
4133 }),
4134 returning: vec![],
4135 order_by: vec![],
4136 limit: None,
4137 };
4138 let schema = test_schema();
4139 let ctx = CodegenContext::default();
4140 let mut b = ProgramBuilder::new();
4141 codegen_update(&mut b, &stmt, &schema, &ctx).unwrap();
4142 let prog = b.finish().unwrap();
4143 let ops = prog.ops();
4144
4145 let notexists = ops
4146 .iter()
4147 .position(|op| op.opcode == Opcode::NotExists)
4148 .expect("NotExists op present");
4149 let insert = ops
4150 .iter()
4151 .position(|op| op.opcode == Opcode::Insert)
4152 .expect("Insert (REPLACE write-back) op present");
4153 let close = ops
4154 .iter()
4155 .position(|op| op.opcode == Opcode::Close)
4156 .expect("Close op present");
4157
4158 assert!(
4161 notexists < insert,
4162 "NotExists must precede the write-back Insert"
4163 );
4164 assert!(insert < close, "the write-back Insert must precede Close");
4165
4166 assert_eq!(
4168 usize::try_from(ops[notexists].p2).unwrap(),
4169 close,
4170 "NotExists must jump to Close (skipping the write-back Insert) when the rowid is absent"
4171 );
4172 }
4173
4174 #[test]
4175 fn test_codegen_update_makerecord_carries_table_affinity_string() {
4176 let stmt = UpdateStatement {
4183 with: None,
4184 or_conflict: None,
4185 table: QualifiedTableRef {
4186 name: QualifiedName::bare("t"),
4187 alias: None,
4188 index_hint: None,
4189 time_travel: None,
4190 },
4191 assignments: vec![Assignment {
4192 target: AssignmentTarget::Column("b".to_owned()),
4193 value: placeholder(1),
4194 }],
4195 from: None,
4196 where_clause: Some(Expr::BinaryOp {
4197 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4198 op: AstBinaryOp::Eq,
4199 right: Box::new(placeholder(2)),
4200 span: Span::ZERO,
4201 }),
4202 returning: vec![],
4203 order_by: vec![],
4204 limit: None,
4205 };
4206 let schema = test_schema();
4207 let expected_affinity = schema[0].affinity_string();
4210 let ctx = CodegenContext::default();
4211 let mut b = ProgramBuilder::new();
4212 codegen_update(&mut b, &stmt, &schema, &ctx).unwrap();
4213 let prog = b.finish().unwrap();
4214
4215 let make_record = prog
4216 .ops()
4217 .iter()
4218 .find(|op| op.opcode == Opcode::MakeRecord)
4219 .expect("MakeRecord op present");
4220 match &make_record.p4 {
4221 P4::Affinity(aff) => assert_eq!(
4222 *aff, expected_affinity,
4223 "MakeRecord P4 affinity must equal the table's affinity_string()"
4224 ),
4225 _ => panic!("MakeRecord P4 must be P4::Affinity (got a different P4 variant)"),
4226 }
4227 }
4228
4229 #[test]
4231 fn test_codegen_delete_by_rowid() {
4232 let stmt = DeleteStatement {
4233 with: None,
4234 table: QualifiedTableRef {
4235 name: QualifiedName::bare("t"),
4236 alias: None,
4237 index_hint: None,
4238 time_travel: None,
4239 },
4240 where_clause: Some(Expr::BinaryOp {
4241 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4242 op: AstBinaryOp::Eq,
4243 right: Box::new(placeholder(1)),
4244 span: Span::ZERO,
4245 }),
4246 returning: vec![],
4247 order_by: vec![],
4248 limit: None,
4249 };
4250 let schema = test_schema();
4251 let ctx = CodegenContext::default();
4252 let mut b = ProgramBuilder::new();
4253 codegen_delete(&mut b, &stmt, &schema, &ctx).unwrap();
4254 let prog = b.finish().unwrap();
4255
4256 assert!(has_opcodes(
4257 &prog,
4258 &[
4259 Opcode::Init,
4260 Opcode::Transaction,
4261 Opcode::Variable,
4262 Opcode::OpenWrite,
4263 Opcode::NotExists,
4264 Opcode::Delete,
4265 Opcode::Close,
4266 Opcode::Halt,
4267 ]
4268 ));
4269 }
4270
4271 #[test]
4272 fn test_codegen_delete_notexists_jump_skips_delete_to_close() {
4273 let stmt = DeleteStatement {
4280 with: None,
4281 table: QualifiedTableRef {
4282 name: QualifiedName::bare("t"),
4283 alias: None,
4284 index_hint: None,
4285 time_travel: None,
4286 },
4287 where_clause: Some(Expr::BinaryOp {
4288 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4289 op: AstBinaryOp::Eq,
4290 right: Box::new(placeholder(1)),
4291 span: Span::ZERO,
4292 }),
4293 returning: vec![],
4294 order_by: vec![],
4295 limit: None,
4296 };
4297 let schema = test_schema();
4298 let ctx = CodegenContext::default();
4299 let mut b = ProgramBuilder::new();
4300 codegen_delete(&mut b, &stmt, &schema, &ctx).unwrap();
4301 let prog = b.finish().unwrap();
4302 let ops = prog.ops();
4303
4304 let notexists = ops
4305 .iter()
4306 .position(|op| op.opcode == Opcode::NotExists)
4307 .expect("NotExists op present");
4308 let delete = ops
4309 .iter()
4310 .position(|op| op.opcode == Opcode::Delete)
4311 .expect("Delete op present");
4312 let close = ops
4313 .iter()
4314 .position(|op| op.opcode == Opcode::Close)
4315 .expect("Close op present");
4316
4317 assert!(notexists < delete, "NotExists must precede Delete");
4320 assert!(delete < close, "Delete must precede Close");
4321
4322 assert_eq!(
4324 usize::try_from(ops[notexists].p2).unwrap(),
4325 close,
4326 "NotExists must jump to Close (skipping Delete) when the rowid is absent"
4327 );
4328 }
4329
4330 #[test]
4332 fn test_codegen_label_resolution() {
4333 let stmt = simple_select(&["a"], "t", Some(rowid_eq_param()));
4334 let schema = test_schema();
4335 let ctx = CodegenContext::default();
4336 let mut b = ProgramBuilder::new();
4337 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
4338 let prog = b.finish().unwrap();
4339
4340 for op in prog.ops() {
4342 if op.opcode.is_jump() {
4343 assert!(
4344 op.p2 >= 0,
4345 "unresolved jump at {:?}: p2 = {}",
4346 op.opcode,
4347 op.p2
4348 );
4349 assert!(
4350 usize::try_from(op.p2).unwrap() <= prog.len(),
4351 "jump target out of range at {:?}: p2 = {} (prog len = {})",
4352 op.opcode,
4353 op.p2,
4354 prog.len()
4355 );
4356 }
4357 }
4358 }
4359
4360 #[test]
4362 fn test_codegen_register_allocation() {
4363 let stmt = InsertStatement {
4364 with: None,
4365 or_conflict: None,
4366 table: QualifiedName::bare("t"),
4367 alias: None,
4368 columns: vec![],
4369 source: InsertSource::Values(vec![vec![placeholder(1), placeholder(2)]]),
4370 upsert: vec![],
4371 returning: vec![],
4372 };
4373 let schema = test_schema();
4374 let ctx = CodegenContext::default();
4375 let mut b = ProgramBuilder::new();
4376 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
4377 let prog = b.finish().unwrap();
4378
4379 let max_reg = prog.register_count();
4382 assert!(max_reg > 0);
4383
4384 for op in prog.ops() {
4386 if op.opcode == Opcode::Variable {
4387 assert!(
4388 op.p2 >= 1 && op.p2 <= max_reg,
4389 "Variable register out of range: p2 = {}, max = {}",
4390 op.p2,
4391 max_reg
4392 );
4393 }
4394 }
4395 }
4396
4397 #[test]
4399 fn test_codegen_concurrent_newrowid() {
4400 let stmt = InsertStatement {
4401 with: None,
4402 or_conflict: None,
4403 table: QualifiedName::bare("t"),
4404 alias: None,
4405 columns: vec![],
4406 source: InsertSource::Values(vec![vec![placeholder(1)]]),
4407 upsert: vec![],
4408 returning: vec![],
4409 };
4410 let schema = test_schema();
4411 let ctx = CodegenContext {
4412 concurrent_mode: true,
4413 };
4414 let mut b = ProgramBuilder::new();
4415 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
4416 let prog = b.finish().unwrap();
4417
4418 let nr = prog
4420 .ops()
4421 .iter()
4422 .find(|op| op.opcode == Opcode::NewRowid)
4423 .unwrap();
4424 assert_ne!(
4425 nr.p3, 0,
4426 "NewRowid p3 should be non-zero in concurrent mode"
4427 );
4428
4429 let ctx_normal = CodegenContext::default();
4431 let mut b2 = ProgramBuilder::new();
4432 codegen_insert(&mut b2, &stmt, &schema, &ctx_normal).unwrap();
4433 let prog2 = b2.finish().unwrap();
4434 let nr2 = prog2
4435 .ops()
4436 .iter()
4437 .find(|op| op.opcode == Opcode::NewRowid)
4438 .unwrap();
4439 assert_eq!(nr2.p3, 0, "NewRowid p3 should be 0 in normal mode");
4440 }
4441
4442 #[test]
4444 fn test_codegen_select_full_scan() {
4445 let stmt = star_select("t");
4446 let schema = test_schema();
4447 let ctx = CodegenContext::default();
4448 let mut b = ProgramBuilder::new();
4449 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
4450 let prog = b.finish().unwrap();
4451
4452 assert!(has_opcodes(
4453 &prog,
4454 &[
4455 Opcode::Init,
4456 Opcode::Transaction,
4457 Opcode::OpenRead,
4458 Opcode::Rewind,
4459 Opcode::Column,
4460 Opcode::Column,
4461 Opcode::ResultRow,
4462 Opcode::Next,
4463 Opcode::Close,
4464 Opcode::Halt,
4465 ]
4466 ));
4467
4468 let rr = prog
4470 .ops()
4471 .iter()
4472 .find(|op| op.opcode == Opcode::ResultRow)
4473 .unwrap();
4474 assert_eq!(rr.p2, 2);
4475 }
4476
4477 #[test]
4479 fn test_codegen_select_with_index() {
4480 let stmt = simple_select(&["a"], "t", Some(col_eq_param("b", 1)));
4481 let schema = test_schema_with_index();
4482 let ctx = CodegenContext::default();
4483 let mut b = ProgramBuilder::new();
4484 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
4485 let prog = b.finish().unwrap();
4486
4487 let open_reads = prog
4489 .ops()
4490 .iter()
4491 .filter(|op| op.opcode == Opcode::OpenRead)
4492 .count();
4493 assert_eq!(open_reads, 2, "should open both table and index");
4494
4495 assert!(has_opcodes(
4497 &prog,
4498 &[
4499 Opcode::MakeRecord,
4500 Opcode::OpenRead,
4501 Opcode::OpenRead,
4502 Opcode::SeekGE,
4503 Opcode::IdxGT,
4504 Opcode::IdxRowid,
4505 Opcode::SeekRowid,
4506 Opcode::Column,
4507 Opcode::ResultRow,
4508 ]
4509 ));
4510
4511 let variable = prog
4512 .ops()
4513 .iter()
4514 .find(|op| op.opcode == Opcode::Variable)
4515 .expect("Variable should load index probe parameter");
4516 let make_record = prog
4517 .ops()
4518 .iter()
4519 .find(|op| op.opcode == Opcode::MakeRecord)
4520 .expect("MakeRecord should encode index probe key");
4521 assert_eq!(
4522 make_record.p1, variable.p2,
4523 "MakeRecord source should be Variable destination register"
4524 );
4525 assert_eq!(
4526 make_record.p2, 2,
4527 "probe key should include indexed value and synthetic low rowid"
4528 );
4529 let int64 = prog
4530 .ops()
4531 .iter()
4532 .find(|op| op.opcode == Opcode::Int64)
4533 .expect("Int64 should load i64::MIN for duplicate-range seek lower bound");
4534 assert_eq!(int64.p4, P4::Int64(i64::MIN));
4535 assert_eq!(
4536 make_record.p1 + 1,
4537 int64.p2,
4538 "MakeRecord should consume [param_reg, min_rowid_reg]"
4539 );
4540 let seek_ge = prog
4541 .ops()
4542 .iter()
4543 .find(|op| op.opcode == Opcode::SeekGE)
4544 .expect("SeekGE should be emitted for index probe");
4545 assert_eq!(
4546 seek_ge.p3, make_record.p3,
4547 "SeekGE must read probe key from MakeRecord destination register"
4548 );
4549 let idx_gt = prog
4550 .ops()
4551 .iter()
4552 .find(|op| op.opcode == Opcode::IdxGT)
4553 .expect("IdxGT should bound index equality duplicates");
4554 assert_eq!(
4555 idx_gt.p3, make_record.p3,
4556 "IdxGT must compare against the same probe key as SeekGE"
4557 );
4558 assert_eq!(
4559 idx_gt.p5, 1,
4560 "IdxGT should compare only the indexed value prefix, not the synthetic low rowid"
4561 );
4562
4563 let is_null_count = prog
4564 .ops()
4565 .iter()
4566 .filter(|op| op.opcode == Opcode::IsNull)
4567 .count();
4568 assert!(
4569 is_null_count >= 1,
4570 "indexed equality should guard NULL probe"
4571 );
4572
4573 let seek_rowid = prog
4574 .ops()
4575 .iter()
4576 .find(|op| op.opcode == Opcode::SeekRowid)
4577 .expect("SeekRowid should follow IdxRowid");
4578 assert_ne!(
4579 seek_rowid.p2, 0,
4580 "SeekRowid miss target must not jump to pc=0"
4581 );
4582 let next = prog
4583 .ops()
4584 .iter()
4585 .find(|op| op.opcode == Opcode::Next)
4586 .expect("index equality path must iterate duplicates");
4587 assert_eq!(next.p1, 1, "Next should advance the index cursor");
4588 }
4589
4590 #[test]
4591 fn test_codegen_select_unindexed_column_eq_uses_filtered_scan() {
4592 let stmt = simple_select(&["b"], "t", Some(col_eq_param("a", 2)));
4593 let schema = test_schema();
4594 let ctx = CodegenContext::default();
4595 let mut b = ProgramBuilder::new();
4596 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
4597 let prog = b.finish().unwrap();
4598
4599 let open_reads = prog
4600 .ops()
4601 .iter()
4602 .filter(|op| op.opcode == Opcode::OpenRead)
4603 .count();
4604 assert_eq!(
4605 open_reads, 1,
4606 "unindexed equality should scan the table without opening an index"
4607 );
4608 assert!(
4609 !prog
4610 .ops()
4611 .iter()
4612 .any(|op| matches!(op.opcode, Opcode::SeekGE | Opcode::IdxGT | Opcode::IdxRowid)),
4613 "unindexed equality should not emit index-probe opcodes"
4614 );
4615 assert!(has_opcodes(
4616 &prog,
4617 &[
4618 Opcode::Init,
4619 Opcode::Transaction,
4620 Opcode::Variable,
4621 Opcode::OpenRead,
4622 Opcode::Rewind,
4623 Opcode::Column,
4624 Opcode::Ne,
4625 Opcode::Column,
4626 Opcode::ResultRow,
4627 Opcode::Next,
4628 Opcode::Close,
4629 Opcode::Halt,
4630 ]
4631 ));
4632
4633 let variable = prog
4634 .ops()
4635 .iter()
4636 .find(|op| op.opcode == Opcode::Variable)
4637 .expect("Variable should load the equality parameter");
4638 assert_eq!(variable.p1, 2, "numbered placeholder should be preserved");
4639 let ne = prog
4640 .ops()
4641 .iter()
4642 .find(|op| op.opcode == Opcode::Ne)
4643 .expect("filtered scan should skip rows that do not match");
4644 assert_eq!(ne.p1, variable.p2);
4645 assert_ne!(
4646 ne.p5 & 0x10,
4647 0,
4648 "WHERE equality must skip NULL comparisons instead of returning them"
4649 );
4650
4651 let next = prog
4652 .ops()
4653 .iter()
4654 .find(|op| op.opcode == Opcode::Next)
4655 .expect("filtered scan should advance the table cursor");
4656 assert_eq!(next.p1, 0, "Next should advance the table cursor");
4657 }
4658
4659 #[test]
4661 fn test_codegen_insert_returning() {
4662 let stmt = InsertStatement {
4663 with: None,
4664 or_conflict: None,
4665 table: QualifiedName::bare("t"),
4666 alias: None,
4667 columns: vec![],
4668 source: InsertSource::Values(vec![vec![placeholder(1)]]),
4669 upsert: vec![],
4670 returning: vec![ResultColumn::Expr {
4671 expr: Expr::Column(ColumnRef::bare("rowid"), Span::ZERO),
4672 alias: None,
4673 }],
4674 };
4675 let schema = test_schema();
4676 let ctx = CodegenContext::default();
4677 let mut b = ProgramBuilder::new();
4678 codegen_insert(&mut b, &stmt, &schema, &ctx).unwrap();
4679 let prog = b.finish().unwrap();
4680
4681 assert!(has_opcodes(
4683 &prog,
4684 &[Opcode::Insert, Opcode::ResultRow, Opcode::Close,]
4685 ));
4686 }
4687
4688 #[test]
4693 fn test_codegen_error_display_table_not_found() {
4694 let err = CodegenError::TableNotFound("users".to_owned());
4695 let msg = err.to_string();
4696 assert!(msg.contains("table not found"), "got: {msg}");
4697 assert!(msg.contains("users"), "got: {msg}");
4698 }
4699
4700 #[test]
4701 fn test_codegen_error_display_column_not_found() {
4702 let err = CodegenError::ColumnNotFound {
4703 table: "users".to_owned(),
4704 column: "email".to_owned(),
4705 };
4706 let msg = err.to_string();
4707 assert!(msg.contains("email"), "got: {msg}");
4708 assert!(msg.contains("users"), "got: {msg}");
4709 }
4710
4711 #[test]
4712 fn test_codegen_error_display_unsupported() {
4713 let err = CodegenError::Unsupported("window functions".to_owned());
4714 let msg = err.to_string();
4715 assert!(msg.contains("unsupported"), "got: {msg}");
4716 assert!(msg.contains("window functions"), "got: {msg}");
4717 }
4718
4719 #[test]
4720 fn test_codegen_error_is_error() {
4721 let err = CodegenError::TableNotFound("t".to_owned());
4722 assert!(std::error::Error::source(&err).is_none());
4723 }
4724
4725 #[test]
4730 fn test_table_schema_affinity_string() {
4731 let schema = TableSchema {
4732 name: "t".to_owned(),
4733 root_page: 2,
4734 columns: vec![
4735 ColumnInfo {
4736 name: "id".to_owned(),
4737 affinity: 'd',
4738 default_value: None,
4739 },
4740 ColumnInfo {
4741 name: "name".to_owned(),
4742 affinity: 'C',
4743 default_value: None,
4744 },
4745 ColumnInfo {
4746 name: "amount".to_owned(),
4747 affinity: 'e',
4748 default_value: None,
4749 },
4750 ],
4751 indexes: vec![],
4752 };
4753 assert_eq!(schema.affinity_string(), "dCe");
4754 }
4755
4756 #[test]
4757 fn test_table_schema_column_index() {
4758 let schema = test_schema();
4759 assert_eq!(schema[0].column_index("a"), Some(0));
4761 assert_eq!(schema[0].column_index("A"), Some(0));
4762 assert_eq!(schema[0].column_index("b"), Some(1));
4763 assert_eq!(schema[0].column_index("z"), None);
4764 }
4765
4766 #[test]
4767 fn test_table_schema_index_for_column() {
4768 let schema = test_schema_with_index();
4769 let table = &schema[0];
4770 let found = table.index_for_column("b");
4772 assert!(found.is_some());
4773 assert_eq!(found.unwrap().name, "idx_t_b");
4774
4775 let found = table.index_for_column("B");
4777 assert!(found.is_some());
4778
4779 assert!(table.index_for_column("a").is_none());
4781 }
4782
4783 #[test]
4784 fn test_table_schema_affinity_string_empty() {
4785 let schema = TableSchema {
4786 name: "empty".to_owned(),
4787 root_page: 2,
4788 columns: vec![],
4789 indexes: vec![],
4790 };
4791 assert_eq!(schema.affinity_string(), "");
4792 }
4793
4794 #[test]
4799 fn test_codegen_context_default() {
4800 let ctx = CodegenContext::default();
4801 assert!(!ctx.concurrent_mode);
4802 }
4803
4804 #[test]
4809 fn test_codegen_select_table_not_found() {
4810 let stmt = star_select("nonexistent");
4811 let schema = test_schema();
4812 let ctx = CodegenContext::default();
4813 let mut b = ProgramBuilder::new();
4814 let err = codegen_select(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
4815 assert!(matches!(err, CodegenError::TableNotFound(_)));
4816 }
4817
4818 #[test]
4819 fn test_codegen_insert_table_not_found() {
4820 let stmt = InsertStatement {
4821 with: None,
4822 or_conflict: None,
4823 table: QualifiedName::bare("nonexistent"),
4824 alias: None,
4825 columns: vec![],
4826 source: InsertSource::Values(vec![vec![placeholder(1)]]),
4827 upsert: vec![],
4828 returning: vec![],
4829 };
4830 let schema = test_schema();
4831 let ctx = CodegenContext::default();
4832 let mut b = ProgramBuilder::new();
4833 let err = codegen_insert(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
4834 assert!(matches!(err, CodegenError::TableNotFound(_)));
4835 }
4836
4837 #[test]
4838 fn test_codegen_update_table_not_found() {
4839 let stmt = UpdateStatement {
4840 with: None,
4841 or_conflict: None,
4842 table: QualifiedTableRef {
4843 name: QualifiedName::bare("nonexistent"),
4844 alias: None,
4845 index_hint: None,
4846 time_travel: None,
4847 },
4848 assignments: vec![],
4849 from: None,
4850 where_clause: None,
4851 returning: vec![],
4852 order_by: vec![],
4853 limit: None,
4854 };
4855 let schema = test_schema();
4856 let ctx = CodegenContext::default();
4857 let mut b = ProgramBuilder::new();
4858 let err = codegen_update(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
4859 assert!(matches!(err, CodegenError::TableNotFound(_)));
4860 }
4861
4862 #[test]
4863 fn test_codegen_update_unknown_assignment_column_returns_error() {
4864 let stmt = UpdateStatement {
4865 with: None,
4866 or_conflict: None,
4867 table: QualifiedTableRef {
4868 name: QualifiedName::bare("t"),
4869 alias: None,
4870 index_hint: None,
4871 time_travel: None,
4872 },
4873 assignments: vec![Assignment {
4874 target: AssignmentTarget::Column("no_such_col".to_owned()),
4875 value: placeholder(1),
4876 }],
4877 from: None,
4878 where_clause: Some(*rowid_eq_param()),
4879 returning: vec![],
4880 order_by: vec![],
4881 limit: None,
4882 };
4883 let schema = test_schema();
4884 let ctx = CodegenContext::default();
4885 let mut b = ProgramBuilder::new();
4886 let err = codegen_update(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
4887 assert!(matches!(
4888 err,
4889 CodegenError::ColumnNotFound { ref column, .. } if column == "no_such_col"
4890 ));
4891 }
4892
4893 #[test]
4894 fn test_codegen_update_requires_rowid_predicate() {
4895 let stmt = UpdateStatement {
4896 with: None,
4897 or_conflict: None,
4898 table: QualifiedTableRef {
4899 name: QualifiedName::bare("t"),
4900 alias: None,
4901 index_hint: None,
4902 time_travel: None,
4903 },
4904 assignments: vec![Assignment {
4905 target: AssignmentTarget::Column("b".to_owned()),
4906 value: placeholder(1),
4907 }],
4908 from: None,
4909 where_clause: None,
4910 returning: vec![],
4911 order_by: vec![],
4912 limit: None,
4913 };
4914 let schema = test_schema();
4915 let ctx = CodegenContext::default();
4916 let mut b = ProgramBuilder::new();
4917 let err = codegen_update(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
4918 assert!(matches!(err, CodegenError::Unsupported(_)));
4919 }
4920
4921 #[test]
4922 fn test_codegen_update_rowid_anonymous_bind_is_offset_after_assignments() {
4923 let stmt = UpdateStatement {
4924 with: None,
4925 or_conflict: None,
4926 table: QualifiedTableRef {
4927 name: QualifiedName::bare("t"),
4928 alias: None,
4929 index_hint: None,
4930 time_travel: None,
4931 },
4932 assignments: vec![Assignment {
4933 target: AssignmentTarget::Column("b".to_owned()),
4934 value: placeholder(1),
4935 }],
4936 from: None,
4937 where_clause: Some(Expr::BinaryOp {
4938 left: Box::new(Expr::Column(ColumnRef::bare("rowid"), Span::ZERO)),
4939 op: AstBinaryOp::Eq,
4940 right: Box::new(Expr::Placeholder(PlaceholderType::Anonymous, Span::ZERO)),
4941 span: Span::ZERO,
4942 }),
4943 returning: vec![],
4944 order_by: vec![],
4945 limit: None,
4946 };
4947 let schema = test_schema();
4948 let ctx = CodegenContext::default();
4949 let mut b = ProgramBuilder::new();
4950 codegen_update(&mut b, &stmt, &schema, &ctx).unwrap();
4951 let prog = b.finish().unwrap();
4952 let vars: Vec<_> = prog
4953 .ops()
4954 .iter()
4955 .filter(|op| op.opcode == Opcode::Variable)
4956 .collect();
4957 assert_eq!(vars.len(), 2);
4958 assert_eq!(vars[0].p1, 1, "first bind should be SET assignment");
4959 assert_eq!(vars[1].p1, 2, "rowid bind should follow SET binds");
4960 }
4961
4962 #[test]
4963 fn test_codegen_select_unindexed_filter_projected_column_uses_filtered_scan() {
4964 let stmt = simple_select(&["a"], "t", Some(col_eq_param("a", 1)));
4965 let schema = test_schema();
4966 let ctx = CodegenContext::default();
4967 let mut b = ProgramBuilder::new();
4968 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
4969 let prog = b.finish().unwrap();
4970
4971 assert!(has_opcodes(
4972 &prog,
4973 &[
4974 Opcode::Variable,
4975 Opcode::OpenRead,
4976 Opcode::Rewind,
4977 Opcode::Column,
4978 Opcode::Ne,
4979 Opcode::Column,
4980 Opcode::ResultRow,
4981 Opcode::Next,
4982 ]
4983 ));
4984 let column_reads: Vec<_> = prog
4985 .ops()
4986 .iter()
4987 .filter(|op| op.opcode == Opcode::Column)
4988 .collect();
4989 assert_eq!(
4990 column_reads.len(),
4991 2,
4992 "filtering and projecting the same unindexed column should read it for both the predicate and output"
4993 );
4994 assert!(
4995 column_reads.iter().all(|op| op.p2 == 0),
4996 "both reads should target column a"
4997 );
4998 let ne = prog
4999 .ops()
5000 .iter()
5001 .find(|op| op.opcode == Opcode::Ne)
5002 .expect("filtered scan should skip non-matching rows");
5003 assert_ne!(
5004 ne.p5 & 0x10,
5005 0,
5006 "WHERE equality must skip NULL comparisons instead of returning them"
5007 );
5008 }
5009
5010 #[test]
5011 fn test_codegen_select_unsupported_projection_expression_is_error() {
5012 let stmt = SelectStatement {
5013 with: None,
5014 body: SelectBody {
5015 select: SelectCore::Select {
5016 distinct: Distinctness::All,
5017 columns: vec![ResultColumn::Expr {
5018 expr: Expr::Between {
5019 expr: Box::new(Expr::Literal(Literal::Integer(5), Span::ZERO)),
5020 low: Box::new(Expr::Literal(Literal::Integer(1), Span::ZERO)),
5021 high: Box::new(Expr::Literal(Literal::Integer(10), Span::ZERO)),
5022 not: false,
5023 span: Span::ZERO,
5024 },
5025 alias: None,
5026 }],
5027 from: Some(FromClause {
5028 source: TableOrSubquery::Table {
5029 name: QualifiedName::bare("t"),
5030 alias: None,
5031 index_hint: None,
5032 time_travel: None,
5033 },
5034 joins: vec![],
5035 }),
5036 where_clause: None,
5037 group_by: vec![],
5038 having: None,
5039 windows: vec![],
5040 },
5041 compounds: vec![],
5042 },
5043 order_by: vec![],
5044 limit: None,
5045 };
5046 let schema = test_schema();
5047 let ctx = CodegenContext::default();
5048 let mut b = ProgramBuilder::new();
5049 let err = codegen_select(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
5050 assert!(matches!(err, CodegenError::Unsupported(_)));
5051 }
5052
5053 #[test]
5054 fn test_codegen_delete_table_not_found() {
5055 let stmt = DeleteStatement {
5056 with: None,
5057 table: QualifiedTableRef {
5058 name: QualifiedName::bare("nonexistent"),
5059 alias: None,
5060 index_hint: None,
5061 time_travel: None,
5062 },
5063 where_clause: None,
5064 returning: vec![],
5065 order_by: vec![],
5066 limit: None,
5067 };
5068 let schema = test_schema();
5069 let ctx = CodegenContext::default();
5070 let mut b = ProgramBuilder::new();
5071 let err = codegen_delete(&mut b, &stmt, &schema, &ctx).expect_err("should fail");
5072 assert!(matches!(err, CodegenError::TableNotFound(_)));
5073 }
5074
5075 #[test]
5080 fn test_codegen_select_rowid_projection() {
5081 let stmt = simple_select(&["rowid"], "t", None);
5082 let schema = test_schema();
5083 let ctx = CodegenContext::default();
5084 let mut b = ProgramBuilder::new();
5085 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
5086 let prog = b.finish().unwrap();
5087
5088 assert!(
5090 has_opcodes(&prog, &[Opcode::Rowid, Opcode::ResultRow]),
5091 "SELECT rowid should emit OP_Rowid"
5092 );
5093 }
5094
5095 #[test]
5096 fn test_codegen_select_rowid_alias_underscore() {
5097 let stmt = simple_select(&["_rowid_"], "t", None);
5098 let schema = test_schema();
5099 let ctx = CodegenContext::default();
5100 let mut b = ProgramBuilder::new();
5101 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
5102 let prog = b.finish().unwrap();
5103
5104 assert!(
5105 has_opcodes(&prog, &[Opcode::Rowid, Opcode::ResultRow]),
5106 "SELECT _rowid_ should emit OP_Rowid"
5107 );
5108 }
5109
5110 #[test]
5111 fn test_codegen_select_oid_alias() {
5112 let stmt = simple_select(&["oid"], "t", None);
5113 let schema = test_schema();
5114 let ctx = CodegenContext::default();
5115 let mut b = ProgramBuilder::new();
5116 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
5117 let prog = b.finish().unwrap();
5118
5119 assert!(
5120 has_opcodes(&prog, &[Opcode::Rowid, Opcode::ResultRow]),
5121 "SELECT oid should emit OP_Rowid"
5122 );
5123 }
5124
5125 #[test]
5126 fn test_codegen_select_rowid_with_columns() {
5127 let stmt = simple_select(&["rowid", "a", "b"], "t", None);
5129 let schema = test_schema();
5130 let ctx = CodegenContext::default();
5131 let mut b = ProgramBuilder::new();
5132 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
5133 let prog = b.finish().unwrap();
5134
5135 assert!(
5137 has_opcodes(
5138 &prog,
5139 &[
5140 Opcode::Rowid,
5141 Opcode::Column,
5142 Opcode::Column,
5143 Opcode::ResultRow
5144 ]
5145 ),
5146 "SELECT rowid, a, b should emit Rowid + Column + Column"
5147 );
5148 }
5149
5150 #[test]
5151 fn test_codegen_select_rowid_case_insensitive() {
5152 let stmt = simple_select(&["ROWID"], "t", None);
5154 let schema = test_schema();
5155 let ctx = CodegenContext::default();
5156 let mut b = ProgramBuilder::new();
5157 codegen_select(&mut b, &stmt, &schema, &ctx).unwrap();
5158 let prog = b.finish().unwrap();
5159
5160 assert!(
5161 has_opcodes(&prog, &[Opcode::Rowid, Opcode::ResultRow]),
5162 "SELECT ROWID should emit OP_Rowid (case-insensitive)"
5163 );
5164 }
5165}