@@ -41,8 +41,10 @@ use serde::{Deserialize, Serialize};
4141
4242/// Generate reports that compare planning statistics with runtime metrics.
4343///
44- /// Parser options are captured when the run starts, so `SET` statements do not
45- /// affect parsing in later query files.
44+ /// Parser options are refreshed before each query file is parsed, so `SET`
45+ /// statements affect later files. Because each file is parsed before any of its
46+ /// statements are executed, parser-setting changes do not affect later
47+ /// statements in the same file.
4648#[ derive( Debug , Args ) ]
4749#[ command( verbatim_doc_comment) ]
4850pub struct RunOpt {
@@ -68,7 +70,6 @@ impl RunOpt {
6870 let mut config = SessionConfig :: from_env ( ) ?. with_collect_statistics ( true ) ;
6971 config. options_mut ( ) . optimizer . prefer_hash_join = true ;
7072 let ctx = SessionContext :: new_with_config ( config) ;
71- let sql_parser_options = ctx. state ( ) . config_options ( ) . sql_parser . clone ( ) ;
7273 register_parquet_files ( & ctx, & self . path ) . await ?;
7374
7475 let branch = current_branch_name ( ) ;
@@ -87,48 +88,8 @@ impl RunOpt {
8788
8889 let mut reports = vec ! [ ] ;
8990 for query_path in query_files ( & self . query_path , self . query . as_deref ( ) ) ? {
90- let query = query_path
91- . file_stem ( )
92- . expect ( "query file has a filename" )
93- . to_string_lossy ( )
94- . to_string ( ) ;
95- let sql = fs:: read_to_string ( query_path) ?;
96- let statements = match sql_statements ( & sql, & sql_parser_options) {
97- Ok ( statements) => statements,
98- Err ( error) => {
99- let report = QueryReport {
100- query : query. clone ( ) ,
101- statement : 1 ,
102- operators : vec ! [ ] ,
103- success : false ,
104- error : Some ( error. to_string ( ) ) ,
105- } ;
106- print_query_report ( & report, previous. as_deref ( ) ) ;
107- reports. push ( report) ;
108- continue ;
109- }
110- } ;
111- for ( statement, sql) in statements. into_iter ( ) . enumerate ( ) {
112- let statement = statement + 1 ;
113- let report = match self . report_statement ( & ctx, sql) . await {
114- Ok ( operators) => QueryReport {
115- query : query. clone ( ) ,
116- statement,
117- operators,
118- success : true ,
119- error : None ,
120- } ,
121- Err ( error) => QueryReport {
122- query : query. clone ( ) ,
123- statement,
124- operators : vec ! [ ] ,
125- success : false ,
126- error : Some ( error. to_string ( ) ) ,
127- } ,
128- } ;
129- print_query_report ( & report, previous. as_deref ( ) ) ;
130- reports. push ( report) ;
131- }
91+ self . report_query_file ( & ctx, & query_path, previous. as_deref ( ) , & mut reports)
92+ . await ?;
13293 }
13394 store_report ( & result_path, & reports) ?;
13495 print_q_error_summary (
@@ -140,6 +101,59 @@ impl RunOpt {
140101 Ok ( ( ) )
141102 }
142103
104+ async fn report_query_file (
105+ & self ,
106+ ctx : & SessionContext ,
107+ query_path : & Path ,
108+ previous : Option < & [ QueryReport ] > ,
109+ reports : & mut Vec < QueryReport > ,
110+ ) -> Result < ( ) > {
111+ let query = query_path
112+ . file_stem ( )
113+ . expect ( "query file has a filename" )
114+ . to_string_lossy ( )
115+ . to_string ( ) ;
116+ let sql = fs:: read_to_string ( query_path) ?;
117+ let sql_parser_options = ctx. state ( ) . config_options ( ) . sql_parser . clone ( ) ;
118+ let statements = match sql_statements ( & sql, & sql_parser_options) {
119+ Ok ( statements) => statements,
120+ Err ( error) => {
121+ let report = QueryReport {
122+ query,
123+ statement : 1 ,
124+ operators : vec ! [ ] ,
125+ success : false ,
126+ error : Some ( error. to_string ( ) ) ,
127+ } ;
128+ print_query_report ( & report, previous) ;
129+ reports. push ( report) ;
130+ return Ok ( ( ) ) ;
131+ }
132+ } ;
133+ for ( statement, sql) in statements. into_iter ( ) . enumerate ( ) {
134+ let statement = statement + 1 ;
135+ let report = match self . report_statement ( ctx, sql) . await {
136+ Ok ( operators) => QueryReport {
137+ query : query. clone ( ) ,
138+ statement,
139+ operators,
140+ success : true ,
141+ error : None ,
142+ } ,
143+ Err ( error) => QueryReport {
144+ query : query. clone ( ) ,
145+ statement,
146+ operators : vec ! [ ] ,
147+ success : false ,
148+ error : Some ( error. to_string ( ) ) ,
149+ } ,
150+ } ;
151+ print_query_report ( & report, previous) ;
152+ reports. push ( report) ;
153+ }
154+ Ok ( ( ) )
155+ }
156+
143157 fn report_path ( & self , branch : & str ) -> PathBuf {
144158 let report_name = self . query . as_ref ( ) . map_or_else (
145159 || "statistics.json" . to_string ( ) ,
@@ -772,6 +786,88 @@ mod tests {
772786 assert ! ( !reports. is_empty( ) ) ;
773787 }
774788
789+ async fn report_query_files (
790+ options : & RunOpt ,
791+ ctx : & SessionContext ,
792+ ) -> Vec < QueryReport > {
793+ let mut reports = vec ! [ ] ;
794+ for path in query_files ( & options. query_path , options. query . as_deref ( ) ) . unwrap ( ) {
795+ options
796+ . report_query_file ( ctx, & path, None , & mut reports)
797+ . await
798+ . unwrap ( ) ;
799+ }
800+ reports
801+ }
802+
803+ #[ tokio:: test]
804+ async fn refreshes_sql_dialect_between_query_files ( ) {
805+ let directory = tempdir ( ) . unwrap ( ) ;
806+ fs:: write (
807+ directory. path ( ) . join ( "01.sql" ) ,
808+ "SET datafusion.sql_parser.dialect = 'MySQL'" ,
809+ )
810+ . unwrap ( ) ;
811+ fs:: write ( directory. path ( ) . join ( "02.sql" ) , "# MySQL comment\n SELECT 1" ) . unwrap ( ) ;
812+ fs:: write (
813+ directory. path ( ) . join ( "03.sql" ) ,
814+ "RESET datafusion.sql_parser.dialect" ,
815+ )
816+ . unwrap ( ) ;
817+ fs:: write ( directory. path ( ) . join ( "04.sql" ) , "SELECT 1," ) . unwrap ( ) ;
818+ let options = RunOpt {
819+ query : None ,
820+ compare : None ,
821+ path : directory. path ( ) . to_path_buf ( ) ,
822+ query_path : directory. path ( ) . to_path_buf ( ) ,
823+ } ;
824+
825+ let reports = report_query_files ( & options, & SessionContext :: new ( ) ) . await ;
826+
827+ assert_eq ! ( reports. len( ) , 4 ) ;
828+ assert ! ( reports. iter( ) . all( |report| report. success) ) ;
829+ assert_eq ! ( reports[ 1 ] . query, "02" ) ;
830+ assert ! ( !reports[ 1 ] . operators. is_empty( ) ) ;
831+ assert_eq ! ( reports[ 3 ] . query, "04" ) ;
832+ assert ! ( !reports[ 3 ] . operators. is_empty( ) ) ;
833+ }
834+
835+ #[ tokio:: test]
836+ async fn refreshes_parser_recursion_limit_between_query_files ( ) {
837+ let directory = tempdir ( ) . unwrap ( ) ;
838+ fs:: write (
839+ directory. path ( ) . join ( "01.sql" ) ,
840+ "SET datafusion.sql_parser.recursion_limit = 2" ,
841+ )
842+ . unwrap ( ) ;
843+ fs:: write (
844+ directory. path ( ) . join ( "02.sql" ) ,
845+ "SELECT (((((((((((1)))))))))))" ,
846+ )
847+ . unwrap ( ) ;
848+ let options = RunOpt {
849+ query : None ,
850+ compare : None ,
851+ path : directory. path ( ) . to_path_buf ( ) ,
852+ query_path : directory. path ( ) . to_path_buf ( ) ,
853+ } ;
854+
855+ let reports = report_query_files ( & options, & SessionContext :: new ( ) ) . await ;
856+
857+ assert_eq ! ( reports. len( ) , 2 ) ;
858+ assert ! ( reports[ 0 ] . success) ;
859+ assert ! ( !reports[ 1 ] . success) ;
860+ assert_eq ! ( reports[ 1 ] . query, "02" ) ;
861+ assert ! (
862+ reports[ 1 ]
863+ . error
864+ . as_deref( )
865+ . is_some_and( |error| error. contains( "RecursionLimitExceeded" ) ) ,
866+ "unexpected report: {:?}" ,
867+ reports[ 1 ]
868+ ) ;
869+ }
870+
775871 #[ test]
776872 fn persists_failed_reports ( ) {
777873 let directory = tempdir ( ) . unwrap ( ) ;
0 commit comments