@@ -9,6 +9,7 @@ use catalog_tables::is_catalog_table;
99use common:: IndexType ;
1010use dataflow:: ops:: grouped:: aggregate:: Aggregation ;
1111use dataflow:: ops:: union;
12+ use dataflow:: ops:: window:: WindowOperation ;
1213use lazy_static:: lazy_static;
1314use mir:: graph:: MirGraph ;
1415use mir:: node:: node_inner:: MirNodeInner ;
@@ -24,7 +25,7 @@ use readyset_errors::{
2425 unsupported_err, ReadySetError , ReadySetResult ,
2526} ;
2627use readyset_sql:: analysis:: visit:: { walk_expr, Visitor } ;
27- use readyset_sql:: analysis:: { self , ReferredColumns } ;
28+ use readyset_sql:: analysis:: { self , is_aggregate , ReferredColumns } ;
2829use readyset_sql:: ast:: {
2930 self , BinaryOperator , CaseWhenBranch , ColumnSpecification , CompoundSelectOperator ,
3031 CreateTableBody , Expr , FieldDefinitionExpr , FieldReference , FunctionExpr , GroupByClause ,
@@ -47,6 +48,7 @@ use crate::controller::sql::query_graph::{
4748 to_query_graph, ExprColumn , OutputColumn , Pagination , QueryGraph ,
4849} ;
4950use crate :: controller:: sql:: query_signature:: Signature ;
51+ use crate :: sql:: query_graph:: WindowFunction ;
5052
5153mod grouped;
5254mod join;
@@ -2065,6 +2067,134 @@ impl SqlToMirConverter {
20652067 Ok ( leaf)
20662068 }
20672069
2070+ fn make_window_node (
2071+ & mut self ,
2072+ query_name : & Relation ,
2073+ name : Relation ,
2074+ mut parent : NodeIndex ,
2075+ funcs : & [ WindowFunction ] ,
2076+ group_by : Vec < Column > ,
2077+ ) -> ReadySetResult < NodeIndex > {
2078+ if funcs. is_empty ( ) {
2079+ return Ok ( parent) ;
2080+ } else if funcs. len ( ) != 1 {
2081+ // TOOD(mohamed): On paper, it should be possible by simply connecting the
2082+ // window functions in series (fn1 -> fn2 -> fn3 -> ... -> reader)
2083+ // However, the performance will be so bad that we rather just not support
2084+ // it for now. Making the execution "smarter" with how it handles
2085+ // its inputs and outputs will make this possible, but should be left
2086+ // as a future improvement
2087+ unsupported ! ( "Multiple window functions not yet supported" ) ;
2088+ }
2089+
2090+ let dialect = readyset_sql:: Dialect :: MySQL ;
2091+ let WindowFunction {
2092+ function,
2093+ partition_by,
2094+ order_by,
2095+ alias,
2096+ } = funcs. first ( ) . unwrap ( ) . clone ( ) ;
2097+
2098+ let arguments = function. arguments ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ;
2099+
2100+ let function = WindowOperation :: from_fn ( function) ?;
2101+
2102+ let order_cols = order_by
2103+ . iter ( )
2104+ . map ( |( e, _, _) | e)
2105+ . cloned ( )
2106+ . collect :: < Vec < _ > > ( ) ;
2107+
2108+ let has_agg = |e : & Expr | matches ! ( e, Expr :: Call ( f) if is_aggregate( f) ) ;
2109+ if partition_by. iter ( ) . any ( has_agg)
2110+ || arguments. iter ( ) . any ( has_agg)
2111+ || order_cols. iter ( ) . any ( has_agg)
2112+ {
2113+ unsupported ! ( "Aggregates in window functions not yet supported" ) ;
2114+ }
2115+
2116+ // if the partition, ordering cols, or args require projection,
2117+ // create a projection node and use that as a parent
2118+ // TODO: do we need to make this strict? i.e. only project certain
2119+ // Expr variants?
2120+ let p = |e : & Expr | !matches ! ( e, Expr :: Column ( _) ) ;
2121+ let needs_proj_node =
2122+ partition_by. iter ( ) . any ( p) || arguments. iter ( ) . any ( p) || order_cols. iter ( ) . any ( p) ;
2123+
2124+ if needs_proj_node {
2125+ let node_name = format ! (
2126+ "{}_window_project_n{}" ,
2127+ name. display_unquoted( ) ,
2128+ self . mir_graph. node_count( )
2129+ ) ;
2130+
2131+ let node = self . make_project_node (
2132+ query_name,
2133+ node_name. into ( ) ,
2134+ parent,
2135+ partition_by
2136+ . iter ( )
2137+ . chain ( arguments. iter ( ) )
2138+ . chain ( order_cols. iter ( ) )
2139+ . cloned ( )
2140+ . map ( |e| -> ReadySetResult < _ > {
2141+ Ok ( ProjectExpr :: Expr {
2142+ alias : e
2143+ . alias ( dialect)
2144+ // returns None if e is a placeholder or a variable
2145+ . ok_or_else ( || {
2146+ unsupported_err ! ( "Placeholders not allowed in this context" )
2147+ } ) ?,
2148+ expr : e,
2149+ } )
2150+ } )
2151+ . collect :: < ReadySetResult < Vec < _ > > > ( ) ?,
2152+ ) ;
2153+ parent = node;
2154+ }
2155+
2156+ let output_column = Column :: named ( alias) ;
2157+
2158+ let partition_by = partition_by
2159+ . iter ( )
2160+ . map ( |e| e. alias ( dialect) . unwrap ( ) )
2161+ . map ( |e| Column :: named ( e) )
2162+ . collect ( ) ;
2163+
2164+ let order_by = order_by
2165+ . into_iter ( )
2166+ . map ( |( e, order, no) | ( Column :: named ( e. alias ( dialect) . unwrap ( ) ) , order, no) )
2167+ . collect ( ) ;
2168+
2169+ let args = arguments
2170+ . iter ( )
2171+ . map ( |e| e. alias ( dialect) . unwrap ( ) )
2172+ . map ( |e| Column :: named ( e) )
2173+ . collect ( ) ;
2174+
2175+ let node_name = format ! (
2176+ "{}_window_n{}" ,
2177+ name. display_unquoted( ) ,
2178+ self . mir_graph. node_count( )
2179+ ) ;
2180+
2181+ Ok ( self . add_query_node (
2182+ query_name. clone ( ) ,
2183+ MirNode :: new (
2184+ node_name. into ( ) ,
2185+ MirNodeInner :: Window {
2186+ group_by,
2187+ partition_by,
2188+ order_by,
2189+ output_column,
2190+ function,
2191+ args,
2192+ } ,
2193+ ) ,
2194+ & [ parent] ,
2195+ ) )
2196+ }
2197+
20682198 fn predicates_above_group_by < ' a > (
20692199 & mut self ,
20702200 query_name : & Relation ,
@@ -2370,7 +2500,21 @@ impl SqlToMirConverter {
23702500 prev_node = subquery_leaf;
23712501 }
23722502
2373- // 8. Add function and grouped nodes
2503+ // 8. Add window functions or grouped nodes (mutually exclusive for now)
2504+ if !query_graph. aggregates . is_empty ( ) && !query_graph. window_functions . is_empty ( ) {
2505+ unsupported ! ( "Mixing window functions and aggregates is not supported yet" )
2506+ } ;
2507+
2508+ let group_by: Vec < _ > = view_key. columns . iter ( ) . map ( |( c, _) | c. clone ( ) ) . collect ( ) ;
2509+
2510+ prev_node = self . make_window_node (
2511+ query_name,
2512+ format ! ( "q_{:x}" , query_graph. signature( ) . hash) . into ( ) ,
2513+ prev_node,
2514+ & query_graph. window_functions ,
2515+ group_by,
2516+ ) ?;
2517+
23742518 let mut func_nodes: Vec < NodeIndex > = make_grouped (
23752519 self ,
23762520 query_name,
@@ -2620,6 +2764,10 @@ impl SqlToMirConverter {
26202764 let are_repeat_reads_required =
26212765 query_graph. collapsed_where_in || view_key. index_type == IndexType :: BTreeMap ;
26222766
2767+ if are_repeat_reads_required && !query_graph. window_functions . is_empty ( ) {
2768+ unsupported ! ( "Post-lookups are not supported for window functions" ) ;
2769+ }
2770+
26232771 let post_lookup_aggregates = if are_repeat_reads_required {
26242772 // When a query contains WHERE col IN (?, ?, ...), it gets rewritten
26252773 // (or collapsed) to WHERE col = ? during SQL parsing, with the
0 commit comments