Skip to content

Commit 07ef2a8

Browse files
committed
sql: Support (NOT) IN in projected fields
Support projecting IN and NOT IN in the results of a query, by compiling a LEFT JOIN with a DISTINCT and a marker column on the rhs, followed by an IS NULL expr for the result. Release-Note-Core: Add support for queries with a IN and NOT IN expressions with subqueries on the right-hand side in the projected field list Change-Id: Id75e7127d2b1ee34b0cd10e9e849a0420bf54d36 Reviewed-on: https://gerrit.readyset.name/c/readyset/+/5525 Tested-by: Buildkite CI Reviewed-by: Dan Wilbanks <dan@readyset.io>
1 parent 3c87547 commit 07ef2a8

File tree

2 files changed

+158
-3
lines changed

2 files changed

+158
-3
lines changed

logictests/in_subquery.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,20 @@ where x not in (select x from t2)
5151
----
5252
2
5353
3
54+
55+
query III rowsort
56+
select
57+
y,
58+
x in (select x from t2),
59+
x not in (select x from t2)
60+
from t1
61+
----
62+
1
63+
1
64+
0
65+
2
66+
0
67+
1
68+
3
69+
0
70+
1

readyset-server/src/controller/sql/mir/mod.rs

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use nom_sql::analysis::ReferredColumns;
1919
use nom_sql::{
2020
BinaryOperator, ColumnSpecification, CompoundSelectOperator, CreateTableBody, Expr,
2121
FieldDefinitionExpr, FieldReference, FunctionExpr, InValue, LimitClause, Literal, OrderClause,
22-
OrderType, Relation, SqlIdentifier, TableKey,
22+
OrderType, Relation, SelectStatement, SqlIdentifier, TableKey,
2323
};
2424
use petgraph::visit::Reversed;
2525
use petgraph::Direction;
@@ -38,7 +38,9 @@ use crate::controller::sql::mir::grouped::{
3838
post_lookup_aggregates,
3939
};
4040
use crate::controller::sql::mir::join::{make_cross_joins, make_joins};
41-
use crate::controller::sql::query_graph::{to_query_graph, OutputColumn, Pagination, QueryGraph};
41+
use crate::controller::sql::query_graph::{
42+
to_query_graph, ExprColumn, OutputColumn, Pagination, QueryGraph,
43+
};
4244
use crate::controller::sql::query_signature::Signature;
4345

4446
mod grouped;
@@ -1096,6 +1098,116 @@ impl SqlToMirConverter {
10961098
))
10971099
}
10981100

1101+
/// Project out a column representing an `IN (subquery)` expr.
1102+
///
1103+
/// Internally, these are compiled via a LEFT JOIN with a DISTINCT and a marker column on the
1104+
/// right-hand side, followed by an `IS NULL` expr projected out for the result
1105+
fn project_in_subquery(
1106+
&mut self,
1107+
query_name: &Relation,
1108+
parent: NodeIndex,
1109+
name: &str,
1110+
lhs: &Expr,
1111+
subquery: SelectStatement,
1112+
negated: bool,
1113+
) -> ReadySetResult<NodeIndex> {
1114+
let (lhs, parent) = match lhs {
1115+
Expr::Column(col) => (col.clone(), parent),
1116+
expr => {
1117+
// The lhs is a non-column expr, so we need to project it first
1118+
let label = lhs.display(nom_sql::Dialect::MySQL).to_string();
1119+
let prj = self.make_project_node(
1120+
query_name,
1121+
self.generate_label(&"in_lhs_project".into()),
1122+
parent,
1123+
vec![ProjectExpr::Expr {
1124+
alias: label.clone().into(),
1125+
expr: expr.clone(),
1126+
}],
1127+
);
1128+
(
1129+
nom_sql::Column {
1130+
name: label.into(),
1131+
table: None,
1132+
},
1133+
prj,
1134+
)
1135+
}
1136+
};
1137+
1138+
let query_graph = to_query_graph(subquery)?;
1139+
let subquery_leaf = self.named_query_to_mir(
1140+
query_name,
1141+
&query_graph,
1142+
&HashMap::new(),
1143+
LeafBehavior::Anonymous,
1144+
)?;
1145+
1146+
let cols = self.columns(subquery_leaf);
1147+
if cols.len() != 1 {
1148+
invalid!("Subquery on right-hand side of IN must have exactly one column");
1149+
}
1150+
let col = cols.into_iter().next().expect("Just checked");
1151+
let distinct = self.make_distinct_node(
1152+
query_name,
1153+
self.generate_label(&"in_subquery_distinct".into()),
1154+
subquery_leaf,
1155+
vec![col.clone()],
1156+
);
1157+
1158+
let mark_col = SqlIdentifier::from("__mark");
1159+
let right_mark = self.make_project_node(
1160+
query_name,
1161+
self.generate_label(&format!("{name}_in_mark").into()),
1162+
distinct,
1163+
self.columns(distinct)
1164+
.into_iter()
1165+
.map(ProjectExpr::Column)
1166+
.chain(iter::once(ProjectExpr::Expr {
1167+
expr: Expr::Literal(Literal::Integer(0)),
1168+
alias: mark_col.clone(),
1169+
}))
1170+
.collect(),
1171+
);
1172+
1173+
let join = self.make_join_node(
1174+
query_name,
1175+
self.generate_label(&format!("{name}_join").into()),
1176+
&[JoinPredicate {
1177+
left: lhs,
1178+
right: nom_sql::Column {
1179+
name: col.name,
1180+
table: col.table,
1181+
},
1182+
}],
1183+
parent,
1184+
right_mark,
1185+
JoinKind::Left,
1186+
)?;
1187+
1188+
Ok(self.make_project_node(
1189+
query_name,
1190+
self.generate_label(&format!("project_{name}").into()),
1191+
join,
1192+
self.columns(join)
1193+
.into_iter()
1194+
.map(ProjectExpr::Column)
1195+
.chain(iter::once(ProjectExpr::Expr {
1196+
expr: Expr::BinaryOp {
1197+
lhs: Box::new(Expr::Column(mark_col.into())),
1198+
op: if negated {
1199+
BinaryOperator::Is
1200+
} else {
1201+
BinaryOperator::IsNot
1202+
},
1203+
rhs: Box::new(Expr::Literal(Literal::Null)),
1204+
},
1205+
alias: name.into(),
1206+
}))
1207+
.collect(),
1208+
))
1209+
}
1210+
10991211
fn make_project_node(
11001212
&mut self,
11011213
query_name: &Relation,
@@ -1867,12 +1979,38 @@ impl SqlToMirConverter {
18671979
// 10. Generate leaf views that expose the query result
18681980

18691981
// We may already have added some of the expression and literal columns
1870-
let (_, already_computed): (Vec<_>, Vec<_>) = value_columns_needed_for_predicates(
1982+
let (_, mut already_computed): (Vec<_>, Vec<_>) = value_columns_needed_for_predicates(
18711983
&query_graph.columns,
18721984
&query_graph.global_predicates,
18731985
)
18741986
.into_iter()
18751987
.unzip();
1988+
1989+
// Project out any columns that need special handling
1990+
for oc in &query_graph.columns {
1991+
if let OutputColumn::Expr(ExprColumn {
1992+
name,
1993+
table: None,
1994+
expression:
1995+
Expr::In {
1996+
lhs,
1997+
rhs: InValue::Subquery(subquery),
1998+
negated,
1999+
},
2000+
}) = oc
2001+
{
2002+
final_node = self.project_in_subquery(
2003+
query_name,
2004+
final_node,
2005+
name,
2006+
lhs,
2007+
(**subquery).clone(),
2008+
*negated,
2009+
)?;
2010+
already_computed.push(oc.clone());
2011+
}
2012+
}
2013+
18762014
let mut emit = query_graph
18772015
.columns
18782016
.iter()

0 commit comments

Comments
 (0)