support VALUES clauses for compound select

This commit is contained in:
meteorgan
2025-07-27 19:13:23 +08:00
parent cc5d4dc3ba
commit ea660b947d
4 changed files with 126 additions and 31 deletions

View File

@@ -2,7 +2,7 @@ use crate::translate::emitter::Resolver;
use crate::translate::expr::{translate_expr_no_constant_opt, NoConstantOptReason};
use crate::translate::plan::{QueryDestination, SelectPlan};
use crate::vdbe::builder::ProgramBuilder;
use crate::vdbe::insn::Insn;
use crate::vdbe::insn::{IdxInsertFlags, Insn};
use crate::vdbe::BranchOffset;
use crate::Result;
@@ -21,7 +21,7 @@ pub fn emit_values(
QueryDestination::CoroutineYield { yield_reg, .. } => {
emit_values_in_subquery(program, plan, resolver, yield_reg)?
}
QueryDestination::EphemeralIndex { .. } => unreachable!(),
QueryDestination::EphemeralIndex { .. } => emit_toplevel_values(program, plan, resolver)?,
QueryDestination::EphemeralTable { .. } => unreachable!(),
};
Ok(reg_result_cols_start)
@@ -45,22 +45,7 @@ fn emit_values_when_single_row(
NoConstantOptReason::RegisterReuse,
)?;
}
match plan.query_destination {
QueryDestination::ResultRows => {
program.emit_insn(Insn::ResultRow {
start_reg,
count: row_len,
});
}
QueryDestination::CoroutineYield { yield_reg, .. } => {
program.emit_insn(Insn::Yield {
yield_reg,
end_offset: BranchOffset::Offset(0),
});
}
QueryDestination::EphemeralIndex { .. } => unreachable!(),
QueryDestination::EphemeralTable { .. } => unreachable!(),
}
emit_values_to_destination(program, plan, start_reg, row_len);
Ok(start_reg)
}
@@ -106,10 +91,8 @@ fn emit_toplevel_values(
});
}
program.emit_insn(Insn::ResultRow {
start_reg: copy_start_reg,
count: row_len,
});
emit_values_to_destination(program, plan, copy_start_reg, row_len);
program.emit_insn(Insn::Goto {
target_pc: goto_label,
});
@@ -145,3 +128,68 @@ fn emit_values_in_subquery(
Ok(start_reg)
}
fn emit_values_to_destination(
program: &mut ProgramBuilder,
plan: &SelectPlan,
start_reg: usize,
row_len: usize,
) {
match &plan.query_destination {
QueryDestination::ResultRows => {
program.emit_insn(Insn::ResultRow {
start_reg,
count: row_len,
});
}
QueryDestination::CoroutineYield { yield_reg, .. } => {
program.emit_insn(Insn::Yield {
yield_reg: *yield_reg,
end_offset: BranchOffset::Offset(0),
});
}
QueryDestination::EphemeralIndex { .. } => {
emit_values_to_index(program, plan, start_reg, row_len);
}
QueryDestination::EphemeralTable { .. } => unreachable!(),
}
}
fn emit_values_to_index(
program: &mut ProgramBuilder,
plan: &SelectPlan,
start_reg: usize,
row_len: usize,
) {
let (cursor_id, index, is_delete) = match &plan.query_destination {
QueryDestination::EphemeralIndex {
cursor_id,
index,
is_delete,
} => (cursor_id, index, is_delete),
_ => unreachable!(),
};
if *is_delete {
program.emit_insn(Insn::IdxDelete {
start_reg,
num_regs: row_len,
cursor_id: *cursor_id,
raise_error_if_no_matching_entry: false,
});
} else {
let record_reg = program.alloc_register();
program.emit_insn(Insn::MakeRecord {
start_reg,
count: row_len,
dest_reg: record_reg,
index_name: Some(index.name.clone()),
});
program.emit_insn(Insn::IdxInsert {
cursor_id: *cursor_id,
record_reg,
unpacked_start: None,
unpacked_count: None,
flags: IdxInsertFlags::new().no_op_duplicate(),
});
}
}

View File

@@ -344,6 +344,15 @@ if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-s
} {1|100
2|200}
do_execsql_test_on_specific_db {:memory:} insert_from_select_union-2 {
CREATE TABLE t (a, b);
CREATE TABLE t2 (b, c);
INSERT INTO t SELECT * FROM t UNION values(1, 100), (2, 200);
SELECT * FROM t;
} {1|100
2|200}
do_execsql_test_on_specific_db {:memory:} insert_from_select_intersect {
CREATE TABLE t (a, b);
CREATE TABLE t1 (a, b);

View File

@@ -583,6 +583,29 @@ if {[info exists ::env(SQLITE_EXEC)] && ($::env(SQLITE_EXEC) eq "scripts/limbo-s
select * from t INTERSECT select * from u EXCEPT select * from v;
} {}
do_execsql_test_on_specific_db {:memory:} select-values-union {
CREATE TABLE t (x TEXT, y TEXT);
INSERT INTO t VALUES('x','x'),('y','y');
values('x', 'x') UNION select * from t;
} {x|x
y|y}
do_execsql_test_on_specific_db {:memory:} select-values-union-2 {
CREATE TABLE t (x TEXT, y TEXT);
INSERT INTO t VALUES('x','x'),('y','y');
values('x', 'x'), ('y', 'y') UNION select * from t;
} {x|x
y|y}
do_execsql_test_on_specific_db {:memory:} select-values-except {
CREATE TABLE t (x TEXT, y TEXT);
INSERT INTO t VALUES('x','x'),('y','y');
select * from t EXCEPT values('x','x'),('z','y');
} {y|y}
}
do_execsql_test_on_specific_db {:memory:} select-no-match-in-leaf-page {

View File

@@ -569,14 +569,28 @@ mod tests {
.map(|c| c.to_string())
.collect::<Vec<_>>();
for _ in 0..num_selects_in_union {
// Randomly pick a table
let table_to_select_from = &table_names[rng.random_range(0..table_names.len())];
select_statements.push(format!(
"SELECT {} FROM {}",
cols_to_select.join(", "),
table_to_select_from
));
let mut has_right_most_values = false;
for i in 0..num_selects_in_union {
let p = 1.0 / table_names.len() as f64;
// Randomly decide whether to use a VALUES clause or a SELECT clause
if rng.random_bool(p) {
let values = (0..cols_to_select.len())
.map(|_| rng.random_range(-3..3))
.map(|val| val.to_string())
.collect::<Vec<_>>();
select_statements.push(format!("VALUES({})", values.join(", ")));
if i == (num_selects_in_union - 1) {
has_right_most_values = true;
}
} else {
// Randomly pick a table
let table_to_select_from = &table_names[rng.random_range(0..table_names.len())];
select_statements.push(format!(
"SELECT {} FROM {}",
cols_to_select.join(", "),
table_to_select_from
));
}
}
const COMPOUND_OPERATORS: [&str; 4] =
@@ -590,7 +604,8 @@ mod tests {
query.push_str(select_statement);
}
if rng.random_bool(0.8) {
// if the right most SELECT is a VALUES claude, no limit is not allowed
if rng.random_bool(0.8) && !has_right_most_values {
let limit_val = rng.random_range(0..=MAX_LIMIT_VALUE); // LIMIT 0 is valid
query = format!("{query} LIMIT {limit_val}");
}