@@ -19,7 +19,7 @@ use nom_sql::analysis::ReferredColumns;
1919use 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} ;
2424use petgraph:: visit:: Reversed ;
2525use petgraph:: Direction ;
@@ -38,7 +38,9 @@ use crate::controller::sql::mir::grouped::{
3838 post_lookup_aggregates,
3939} ;
4040use 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+ } ;
4244use crate :: controller:: sql:: query_signature:: Signature ;
4345
4446mod 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