1818//! This file contains an end to end test of parquet pruning. It writes
1919//! data into a parquet file and then verifies row groups are pruned as
2020//! expected.
21+ use std:: sync:: Arc ;
22+
23+ use arrow:: array:: { ArrayRef , Int32Array , RecordBatch } ;
24+ use arrow_schema:: { DataType , Field , Schema } ;
2125use datafusion:: prelude:: SessionConfig ;
22- use datafusion_common:: ScalarValue ;
26+ use datafusion_common:: { DataFusionError , ScalarValue } ;
2327use itertools:: Itertools ;
2428
2529use crate :: parquet:: Unit :: RowGroup ;
@@ -34,6 +38,7 @@ struct RowGroupPruningTest {
3438 expected_files_pruned_by_statistics : Option < usize > ,
3539 expected_row_group_matched_by_bloom_filter : Option < usize > ,
3640 expected_row_group_pruned_by_bloom_filter : Option < usize > ,
41+ expected_limit_pruned_row_groups : Option < usize > ,
3742 expected_rows : usize ,
3843}
3944impl RowGroupPruningTest {
@@ -48,6 +53,7 @@ impl RowGroupPruningTest {
4853 expected_files_pruned_by_statistics : None ,
4954 expected_row_group_matched_by_bloom_filter : None ,
5055 expected_row_group_pruned_by_bloom_filter : None ,
56+ expected_limit_pruned_row_groups : None ,
5157 expected_rows : 0 ,
5258 }
5359 }
@@ -99,6 +105,11 @@ impl RowGroupPruningTest {
99105 self
100106 }
101107
108+ fn with_limit_pruned_row_groups ( mut self , pruned_by_limit : Option < usize > ) -> Self {
109+ self . expected_limit_pruned_row_groups = pruned_by_limit;
110+ self
111+ }
112+
102113 /// Set the number of expected rows from the output of this test
103114 fn with_expected_rows ( mut self , rows : usize ) -> Self {
104115 self . expected_rows = rows;
@@ -143,6 +154,73 @@ impl RowGroupPruningTest {
143154 self . expected_row_group_pruned_by_bloom_filter,
144155 "mismatched row_groups_pruned_bloom_filter" ,
145156 ) ;
157+ assert_eq ! (
158+ output. limit_pruned_row_groups( ) ,
159+ self . expected_limit_pruned_row_groups,
160+ "mismatched limit_pruned_row_groups" ,
161+ ) ;
162+ assert_eq ! (
163+ output. result_rows,
164+ self . expected_rows,
165+ "Expected {} rows, got {}: {}" ,
166+ output. result_rows,
167+ self . expected_rows,
168+ output. description( ) ,
169+ ) ;
170+ }
171+
172+ // Execute the test with the current configuration
173+ async fn test_row_group_prune_with_custom_data (
174+ self ,
175+ schema : Arc < Schema > ,
176+ batches : Vec < RecordBatch > ,
177+ ) {
178+ let output = ContextWithParquet :: with_custom_data (
179+ self . scenario ,
180+ RowGroup ( 2 ) ,
181+ schema,
182+ batches,
183+ )
184+ . await
185+ . query ( & self . query )
186+ . await ;
187+
188+ println ! ( "{}" , output. description( ) ) ;
189+ assert_eq ! (
190+ output. predicate_evaluation_errors( ) ,
191+ self . expected_errors,
192+ "mismatched predicate_evaluation error"
193+ ) ;
194+ assert_eq ! (
195+ output. row_groups_matched_statistics( ) ,
196+ self . expected_row_group_matched_by_statistics,
197+ "mismatched row_groups_matched_statistics" ,
198+ ) ;
199+ assert_eq ! (
200+ output. row_groups_pruned_statistics( ) ,
201+ self . expected_row_group_pruned_by_statistics,
202+ "mismatched row_groups_pruned_statistics" ,
203+ ) ;
204+ assert_eq ! (
205+ output. files_ranges_pruned_statistics( ) ,
206+ self . expected_files_pruned_by_statistics,
207+ "mismatched files_ranges_pruned_statistics" ,
208+ ) ;
209+ assert_eq ! (
210+ output. row_groups_matched_bloom_filter( ) ,
211+ self . expected_row_group_matched_by_bloom_filter,
212+ "mismatched row_groups_matched_bloom_filter" ,
213+ ) ;
214+ assert_eq ! (
215+ output. row_groups_pruned_bloom_filter( ) ,
216+ self . expected_row_group_pruned_by_bloom_filter,
217+ "mismatched row_groups_pruned_bloom_filter" ,
218+ ) ;
219+ assert_eq ! (
220+ output. limit_pruned_row_groups( ) ,
221+ self . expected_limit_pruned_row_groups,
222+ "mismatched limit_pruned_row_groups" ,
223+ ) ;
146224 assert_eq ! (
147225 output. result_rows,
148226 self . expected_rows,
@@ -287,11 +365,16 @@ async fn prune_disabled() {
287365 let expected_rows = 10 ;
288366 let config = SessionConfig :: new ( ) . with_parquet_pruning ( false ) ;
289367
290- let output =
291- ContextWithParquet :: with_config ( Scenario :: Timestamps , RowGroup ( 5 ) , config)
292- . await
293- . query ( query)
294- . await ;
368+ let output = ContextWithParquet :: with_config (
369+ Scenario :: Timestamps ,
370+ RowGroup ( 5 ) ,
371+ config,
372+ None ,
373+ None ,
374+ )
375+ . await
376+ . query ( query)
377+ . await ;
295378 println ! ( "{}" , output. description( ) ) ;
296379
297380 // This should not prune any
@@ -1634,3 +1717,56 @@ async fn test_bloom_filter_decimal_dict() {
16341717 . test_row_group_prune ( )
16351718 . await ;
16361719}
1720+
1721+ // Helper function to create a batch with a single Int32 column.
1722+ fn make_i32_batch (
1723+ name : & str ,
1724+ values : Vec < i32 > ,
1725+ ) -> datafusion_common:: error:: Result < RecordBatch > {
1726+ let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new( name, DataType :: Int32 , false ) ] ) ) ;
1727+ let array: ArrayRef = Arc :: new ( Int32Array :: from ( values) ) ;
1728+ RecordBatch :: try_new ( schema, vec ! [ array] ) . map_err ( DataFusionError :: from)
1729+ }
1730+
1731+ #[ tokio:: test]
1732+ async fn test_limit_pruning ( ) -> datafusion_common:: error:: Result < ( ) > {
1733+ // Scenario: Simple integer column, multiple row groups
1734+ // Query: SELECT c1 FROM t WHERE c1 > 0 LIMIT 2
1735+ // We expect 2 rows in total.
1736+
1737+ // Row Group 0: c1 = [1, 2] -> Fully matched, 2 rows
1738+ // Row Group 1: c1 = [3, 4] -> Fully matched, 2 rows
1739+ // Row Group 2: c1 = [5, 6] -> Fully matched, 2 rows
1740+ // Row Group 3: c1 = [-1, 0] -> Pruned by statistics, 0 rows
1741+
1742+ // If limit = 2, and RG0 is fully matched and has 2 rows, we should
1743+ // only scan RG0 and prune other row groups (RG1, RG2, RG3)
1744+ // RG3 is pruned by statistics. RG1 and RG2 are pruned by limit.
1745+ // So 3 row groups are effectively pruned due to limit pruning.
1746+
1747+ let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new( "c1" , DataType :: Int32 , false ) ] ) ) ;
1748+ let query = "explain verbose SELECT c1 FROM t WHERE c1 > 0 LIMIT 2" ;
1749+
1750+ let batches = vec ! [
1751+ make_i32_batch( "c1" , vec![ 1 , 2 ] ) ?, // RG0: Fully matched, 2 rows
1752+ make_i32_batch( "c1" , vec![ 3 , 4 ] ) ?, // RG1: Fully matched, 2 rows
1753+ make_i32_batch( "c1" , vec![ 5 , 6 ] ) ?, // RG2: Fully matched, 2 rows
1754+ make_i32_batch( "c1" , vec![ -1 , 0 ] ) ?, // RG3: Pruned by statistics, 0 rows
1755+ ] ;
1756+
1757+ RowGroupPruningTest :: new ( )
1758+ . with_scenario ( Scenario :: Int ) // Assuming Scenario::Int can handle this data
1759+ . with_query ( query)
1760+ . with_expected_errors ( Some ( 0 ) )
1761+ . with_expected_rows ( 2 )
1762+ . with_pruned_files ( Some ( 0 ) )
1763+ . with_matched_by_bloom_filter ( Some ( 0 ) )
1764+ . with_pruned_by_bloom_filter ( Some ( 0 ) )
1765+ . with_matched_by_stats ( Some ( 3 ) ) // RG0, RG1, RG2 are matched by stats (c1 > 0)
1766+ . with_pruned_by_stats ( Some ( 1 ) ) // RG3 is pruned by stats (c1 = [-1, 0] does not satisfy c1 > 0)
1767+ // .with_limit_pruned_row_groups(Some(2)) // RG1, RG2 are pruned by limit. (RG3 is already pruned by stats)
1768+ . test_row_group_prune_with_custom_data ( schema, batches)
1769+ . await ;
1770+
1771+ Ok ( ( ) )
1772+ }
0 commit comments