Skip to content

Commit 2bf5eb8

Browse files
authored
Merge 'Prevent misuse of subqueries that return multiple columns' from Jussi Saurio
Closes #3892 Closes #3888 Stuff like: ```sql turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select case (select y, z from t2) when 1 then 'one' else 'other' end from t1; × Parse error: base expression in CASE must return 1 value turso> create table t(x, y); insert into t values (1, 2); select (select x, y from t) as result; × Parse error: result column must return 1 value, got 2 turso> create table t1(x,y); create table t2(y); insert into t1 values (1,1); insert into t2 values (1); select * from t2 where y = (select x,y from t1); × Parse error: all arguments to binary operator = must return the same number of │ values. Got: (1) = (2) turso> create table orders(customer_id, amount); create table thresholds(min_amount, max_amount); insert into orders values (100, 50), (100, 150); insert into thresholds values (100, 200); select customer_id, sum(amount) as total from orders group by customer_id having total > (select min_amount, max_amount from thresholds); × Parse error: all arguments to binary operator > must return the same number of │ values. Got: (1) > (2) turso> create table items(id); create table config(max_results, other_col); insert into items values (1), (2), (3); insert into config values (2, 3); select * from items limit (select max_results, other_col from config); × Parse error: limit expression must return 1 value, got 2 turso> create table items(id); create table config(skip_count, other_col); insert into items values (1), (2), (3); insert into config values (1, 2); select * from items limit 1 offset (select skip_count, other_col from config); × Parse error: offset expression must return 1 value, got 2 turso> create table items(id, name); create table sort_order(priority, other_col); insert into items values (1, 'a'), (2, 'b'); insert into sort_order values (1, 2); select * from items order by (select priority, other_col from sort_order); × Parse error: order by expression must return 1 value, got 2 turso> create table sales(product_id, amount); create table grouping(category, other_col); insert into sales values (1, 100), (2, 200); insert into grouping values (1, 2); select sum(amount) from sales group by (select category, other_col from grouping); × Parse error: group by expression must return 1 value, got 2 turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select case when (select y, z from t2) then 'yes' else 'no' end from t1; × Parse error: when expression in CASE must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select case when x = 1 then (select y, z from t2) else 0 end from t1; × Parse error: then expression in CASE must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select case when x = 2 then 0 else (select y, z from t2) end from t1; × Parse error: else expression in CASE must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select max((select y, z from t2)) from t1; × Parse error: argument 0 to function call max must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select x + (select y, z from t2) from t1; × Parse error: all arguments to binary operator + must return the same number of │ values. Got: (1) + (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (5); insert into t2 values (1, 2); select * from t1 where x between (select y, z from t2) and 10; × Parse error: all arguments to binary operator <= must return the same number of │ values. Got: (2) <= (1) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select cast((select y, z from t2) as integer) from t1; × Parse error: argument to CAST must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values ('a', 'b'); select (select y, z from t2) collate nocase from t1; × Parse error: argument to COLLATE must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select * from t1 where (select y, z from t2) is null; × Parse error: all arguments to binary operator IS must return the same number of │ values. Got: (2) IS (1) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select * from t1 where (select y, z from t2) not null; × Parse error: argument to NOT NULL must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values ('a', 'b'); select * from t1 where (select y, z from t2) like 'a%'; × Parse error: left operand of LIKE must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select -(select y, z from t2) from t1; × Parse error: argument to unary operator - must return 1 value. Got: (2) turso> create table t1(x); create table t2(y, z); insert into t1 values (1); insert into t2 values (1, 2); select abs((select y, z from t2)) from t1; × Parse error: argument 0 to function call abs must return 1 value. Got: (2) ``` Closes #3906
2 parents 9aae220 + 005d922 commit 2bf5eb8

File tree

3 files changed

+436
-2
lines changed

3 files changed

+436
-2
lines changed

core/translate/expr.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4544,3 +4544,168 @@ pub(crate) fn emit_returning_results(
45444544

45454545
Ok(())
45464546
}
4547+
4548+
/// Get the number of values returned by an expression
4549+
pub fn expr_vector_size(expr: &Expr) -> Result<usize> {
4550+
Ok(match unwrap_parens(expr)? {
4551+
Expr::Between {
4552+
lhs, start, end, ..
4553+
} => {
4554+
let evs_left = expr_vector_size(lhs)?;
4555+
let evs_start = expr_vector_size(start)?;
4556+
let evs_end = expr_vector_size(end)?;
4557+
if evs_left != evs_start || evs_left != evs_end {
4558+
crate::bail_parse_error!("all arguments to BETWEEN must return the same number of values. Got: ({evs_left}) BETWEEN ({evs_start}) AND ({evs_end})");
4559+
}
4560+
1
4561+
}
4562+
Expr::Binary(expr, operator, expr1) => {
4563+
let evs_left = expr_vector_size(expr)?;
4564+
let evs_right = expr_vector_size(expr1)?;
4565+
if evs_left != evs_right {
4566+
crate::bail_parse_error!("all arguments to binary operator {operator} must return the same number of values. Got: ({evs_left}) {operator} ({evs_right})");
4567+
}
4568+
1
4569+
}
4570+
Expr::Register(_) => 1,
4571+
Expr::Case {
4572+
base,
4573+
when_then_pairs,
4574+
else_expr,
4575+
} => {
4576+
if let Some(base) = base {
4577+
let evs_base = expr_vector_size(base)?;
4578+
if evs_base != 1 {
4579+
crate::bail_parse_error!(
4580+
"base expression in CASE must return 1 value. Got: ({evs_base})"
4581+
);
4582+
}
4583+
}
4584+
for (when, then) in when_then_pairs {
4585+
let evs_when = expr_vector_size(when)?;
4586+
if evs_when != 1 {
4587+
crate::bail_parse_error!(
4588+
"when expression in CASE must return 1 value. Got: ({evs_when})"
4589+
);
4590+
}
4591+
let evs_then = expr_vector_size(then)?;
4592+
if evs_then != 1 {
4593+
crate::bail_parse_error!(
4594+
"then expression in CASE must return 1 value. Got: ({evs_then})"
4595+
);
4596+
}
4597+
}
4598+
if let Some(else_expr) = else_expr {
4599+
let evs_else_expr = expr_vector_size(else_expr)?;
4600+
if evs_else_expr != 1 {
4601+
crate::bail_parse_error!(
4602+
"else expression in CASE must return 1 value. Got: ({evs_else_expr})"
4603+
);
4604+
}
4605+
}
4606+
1
4607+
}
4608+
Expr::Cast { expr, .. } => {
4609+
let evs_expr = expr_vector_size(expr)?;
4610+
if evs_expr != 1 {
4611+
crate::bail_parse_error!("argument to CAST must return 1 value. Got: ({evs_expr})");
4612+
}
4613+
1
4614+
}
4615+
Expr::Collate(expr, _) => {
4616+
let evs_expr = expr_vector_size(expr)?;
4617+
if evs_expr != 1 {
4618+
crate::bail_parse_error!(
4619+
"argument to COLLATE must return 1 value. Got: ({evs_expr})"
4620+
);
4621+
}
4622+
1
4623+
}
4624+
Expr::DoublyQualified(..) => 1,
4625+
Expr::Exists(_) => todo!(),
4626+
Expr::FunctionCall { name, args, .. } => {
4627+
for (pos, arg) in args.iter().enumerate() {
4628+
let evs_arg = expr_vector_size(arg)?;
4629+
if evs_arg != 1 {
4630+
crate::bail_parse_error!(
4631+
"argument {} to function call {name} must return 1 value. Got: ({evs_arg})",
4632+
pos + 1
4633+
);
4634+
}
4635+
}
4636+
1
4637+
}
4638+
Expr::FunctionCallStar { .. } => 1,
4639+
Expr::Id(_) => 1,
4640+
Expr::Column { .. } => 1,
4641+
Expr::RowId { .. } => 1,
4642+
Expr::InList { lhs, rhs, .. } => {
4643+
let evs_lhs = expr_vector_size(lhs)?;
4644+
for rhs in rhs.iter() {
4645+
let evs_rhs = expr_vector_size(rhs)?;
4646+
if evs_lhs != evs_rhs {
4647+
crate::bail_parse_error!("all arguments to IN list must return the same number of values, got: ({evs_lhs}) IN ({evs_rhs})");
4648+
}
4649+
}
4650+
1
4651+
}
4652+
Expr::InSelect { .. } => {
4653+
crate::bail_parse_error!("InSelect is not supported in this position")
4654+
}
4655+
Expr::InTable { .. } => {
4656+
crate::bail_parse_error!("InTable is not supported in this position")
4657+
}
4658+
Expr::IsNull(expr) => {
4659+
let evs_expr = expr_vector_size(expr)?;
4660+
if evs_expr != 1 {
4661+
crate::bail_parse_error!(
4662+
"argument to IS NULL must return 1 value. Got: ({evs_expr})"
4663+
);
4664+
}
4665+
1
4666+
}
4667+
Expr::Like { lhs, rhs, .. } => {
4668+
let evs_lhs = expr_vector_size(lhs)?;
4669+
if evs_lhs != 1 {
4670+
crate::bail_parse_error!(
4671+
"left operand of LIKE must return 1 value. Got: ({evs_lhs})"
4672+
);
4673+
}
4674+
let evs_rhs = expr_vector_size(rhs)?;
4675+
if evs_rhs != 1 {
4676+
crate::bail_parse_error!(
4677+
"right operand of LIKE must return 1 value. Got: ({evs_rhs})"
4678+
);
4679+
}
4680+
1
4681+
}
4682+
Expr::Literal(_) => 1,
4683+
Expr::Name(_) => 1,
4684+
Expr::NotNull(expr) => {
4685+
let evs_expr = expr_vector_size(expr)?;
4686+
if evs_expr != 1 {
4687+
crate::bail_parse_error!(
4688+
"argument to NOT NULL must return 1 value. Got: ({evs_expr})"
4689+
);
4690+
}
4691+
1
4692+
}
4693+
Expr::Parenthesized(exprs) => exprs.len(),
4694+
Expr::Qualified(..) => 1,
4695+
Expr::Raise(..) => crate::bail_parse_error!("RAISE is not supported"),
4696+
Expr::Subquery(_) => todo!(),
4697+
Expr::Unary(unary_operator, expr) => {
4698+
let evs_expr = expr_vector_size(expr)?;
4699+
if evs_expr != 1 {
4700+
crate::bail_parse_error!("argument to unary operator {unary_operator} must return 1 value. Got: ({evs_expr})");
4701+
}
4702+
1
4703+
}
4704+
Expr::Variable(_) => 1,
4705+
Expr::SubqueryResult { query_type, .. } => match query_type {
4706+
SubqueryType::Exists { .. } => 1,
4707+
SubqueryType::In { .. } => 1,
4708+
SubqueryType::RowValue { num_regs, .. } => *num_regs,
4709+
},
4710+
})
4711+
}

core/translate/select.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use super::plan::{
55
};
66
use crate::schema::Table;
77
use crate::translate::emitter::{OperationMode, Resolver};
8-
use crate::translate::expr::{bind_and_rewrite_expr, BindingBehavior, ParamState};
8+
use crate::translate::expr::{
9+
bind_and_rewrite_expr, expr_vector_size, BindingBehavior, ParamState,
10+
};
911
use crate::translate::group_by::compute_group_by_sort_order;
1012
use crate::translate::optimizer::optimize_plan;
1113
use crate::translate::plan::{GroupBy, Plan, ResultSetColumn, SelectPlan};
@@ -506,6 +508,8 @@ fn prepare_one_select_plan(
506508

507509
plan_subqueries_from_select_plan(program, &mut plan, resolver, connection)?;
508510

511+
validate_expr_correct_column_counts(&plan)?;
512+
509513
// Return the unoptimized query plan
510514
Ok(plan)
511515
}
@@ -562,11 +566,93 @@ fn prepare_one_select_plan(
562566
non_from_clause_subqueries: vec![],
563567
};
564568

569+
validate_expr_correct_column_counts(&plan)?;
570+
565571
Ok(plan)
566572
}
567573
}
568574
}
569575

576+
/// Validate that all expressions in the plan return the correct number of values;
577+
/// generally this only applies to parenthesized lists and subqueries.
578+
fn validate_expr_correct_column_counts(plan: &SelectPlan) -> Result<()> {
579+
for result_column in plan.result_columns.iter() {
580+
let vec_size = expr_vector_size(&result_column.expr)?;
581+
if vec_size != 1 {
582+
crate::bail_parse_error!("result column must return 1 value, got {}", vec_size);
583+
}
584+
}
585+
for (expr, _) in plan.order_by.iter() {
586+
let vec_size = expr_vector_size(expr)?;
587+
if vec_size != 1 {
588+
crate::bail_parse_error!("order by expression must return 1 value, got {}", vec_size);
589+
}
590+
}
591+
if let Some(group_by) = &plan.group_by {
592+
for expr in group_by.exprs.iter() {
593+
let vec_size = expr_vector_size(expr)?;
594+
if vec_size != 1 {
595+
crate::bail_parse_error!(
596+
"group by expression must return 1 value, got {}",
597+
vec_size
598+
);
599+
}
600+
}
601+
if let Some(having) = &group_by.having {
602+
for expr in having.iter() {
603+
let vec_size = expr_vector_size(expr)?;
604+
if vec_size != 1 {
605+
crate::bail_parse_error!(
606+
"having expression must return 1 value, got {}",
607+
vec_size
608+
);
609+
}
610+
}
611+
}
612+
}
613+
for aggregate in plan.aggregates.iter() {
614+
for arg in aggregate.args.iter() {
615+
let vec_size = expr_vector_size(arg)?;
616+
if vec_size != 1 {
617+
crate::bail_parse_error!(
618+
"aggregate argument must return 1 value, got {}",
619+
vec_size
620+
);
621+
}
622+
}
623+
}
624+
for term in plan.where_clause.iter() {
625+
let vec_size = expr_vector_size(&term.expr)?;
626+
if vec_size != 1 {
627+
crate::bail_parse_error!(
628+
"where clause expression must return 1 value, got {}",
629+
vec_size
630+
);
631+
}
632+
}
633+
for expr in plan.values.iter() {
634+
for value in expr.iter() {
635+
let vec_size = expr_vector_size(value)?;
636+
if vec_size != 1 {
637+
crate::bail_parse_error!("value must return 1 value, got {}", vec_size);
638+
}
639+
}
640+
}
641+
if let Some(limit) = &plan.limit {
642+
let vec_size = expr_vector_size(limit)?;
643+
if vec_size != 1 {
644+
crate::bail_parse_error!("limit expression must return 1 value, got {}", vec_size);
645+
}
646+
}
647+
if let Some(offset) = &plan.offset {
648+
let vec_size = expr_vector_size(offset)?;
649+
if vec_size != 1 {
650+
crate::bail_parse_error!("offset expression must return 1 value, got {}", vec_size);
651+
}
652+
}
653+
Ok(())
654+
}
655+
570656
fn add_vtab_predicates_to_where_clause(
571657
vtab_predicates: &mut Vec<Expr>,
572658
plan: &mut SelectPlan,

0 commit comments

Comments
 (0)