@@ -20,9 +20,14 @@ impl SetTransactionScanner {
2020 log_segment : & LogSegment ,
2121 application_id : & str ,
2222 engine : & dyn Engine ,
23+ expiration_timestamp : Option < i64 > ,
2324 ) -> DeltaResult < Option < SetTransaction > > {
24- let mut transactions =
25- scan_application_transactions ( log_segment, Some ( application_id) , engine) ?;
25+ let mut transactions = scan_application_transactions (
26+ log_segment,
27+ Some ( application_id) ,
28+ engine,
29+ expiration_timestamp,
30+ ) ?;
2631 Ok ( transactions. remove ( application_id) )
2732 }
2833
@@ -34,8 +39,9 @@ impl SetTransactionScanner {
3439 pub ( crate ) fn get_all (
3540 log_segment : & LogSegment ,
3641 engine : & dyn Engine ,
42+ expiration_timestamp : Option < i64 > ,
3743 ) -> DeltaResult < SetTransactionMap > {
38- scan_application_transactions ( log_segment, None , engine)
44+ scan_application_transactions ( log_segment, None , engine, expiration_timestamp )
3945 }
4046}
4147
@@ -46,8 +52,10 @@ fn scan_application_transactions(
4652 log_segment : & LogSegment ,
4753 application_id : Option < & str > ,
4854 engine : & dyn Engine ,
55+ expiration_timestamp : Option < i64 > ,
4956) -> DeltaResult < SetTransactionMap > {
50- let mut visitor = SetTransactionVisitor :: new ( application_id. map ( |s| s. to_owned ( ) ) ) ;
57+ let mut visitor =
58+ SetTransactionVisitor :: new ( application_id. map ( |s| s. to_owned ( ) ) , expiration_timestamp) ;
5159 // If a specific id is requested then we can terminate log replay early as soon as it was
5260 // found. If all ids are requested then we are forced to replay the entire log.
5361 for maybe_data in replay_for_app_ids ( log_segment, engine) ? {
@@ -91,8 +99,10 @@ mod tests {
9199
92100 use super :: * ;
93101 use crate :: engine:: sync:: SyncEngine ;
102+ use crate :: utils:: test_utils:: parse_json_batch;
94103 use crate :: Table ;
95104
105+ use crate :: arrow:: array:: StringArray ;
96106 use itertools:: Itertools ;
97107
98108 fn get_latest_transactions (
@@ -108,8 +118,8 @@ mod tests {
108118 let log_segment = snapshot. log_segment ( ) ;
109119
110120 (
111- SetTransactionScanner :: get_all ( log_segment, & engine) . unwrap ( ) ,
112- SetTransactionScanner :: get_one ( log_segment, app_id, & engine) . unwrap ( ) ,
121+ SetTransactionScanner :: get_all ( log_segment, & engine, None ) . unwrap ( ) ,
122+ SetTransactionScanner :: get_one ( log_segment, app_id, & engine, None ) . unwrap ( ) ,
113123 )
114124 }
115125
@@ -165,4 +175,45 @@ mod tests {
165175 . unwrap ( ) ;
166176 assert_eq ! ( data. len( ) , 2 ) ;
167177 }
178+
179+ #[ test]
180+ fn test_txn_retention_filtering ( ) {
181+ let path = std:: fs:: canonicalize ( PathBuf :: from ( "./tests/data/app-txn-with-last-updated/" ) ) ;
182+ let url = url:: Url :: from_directory_path ( path. unwrap ( ) ) . unwrap ( ) ;
183+ let engine = SyncEngine :: new ( ) ;
184+
185+ let table = Table :: new ( url) ;
186+ let snapshot = table. snapshot ( & engine, None ) . unwrap ( ) ;
187+ let log_segment = snapshot. log_segment ( ) ;
188+
189+ // Test with no retention (should get all transactions)
190+ let all_txns = SetTransactionScanner :: get_all ( log_segment, & engine, None ) . unwrap ( ) ;
191+ assert_eq ! ( all_txns. len( ) , 4 ) ;
192+
193+ // Test with retention that filters out old transactions
194+ let expiration_timestamp = Some ( 100 ) ; // Very old timestamp
195+ let filtered_txns =
196+ SetTransactionScanner :: get_all ( log_segment, & engine, expiration_timestamp) . unwrap ( ) ;
197+
198+ // Exact count depends on test data
199+ assert ! ( filtered_txns. len( ) <= all_txns. len( ) ) ;
200+ }
201+
202+ #[ test]
203+ fn test_visitor_retention_with_null_last_updated ( ) {
204+ let json_strings: StringArray = vec ! [
205+ r#"{"txn":{"appId":"app_with_time","version":1,"lastUpdated":100}}"# ,
206+ r#"{"txn":{"appId":"app_without_time","version":2}}"# ,
207+ ]
208+ . into ( ) ;
209+ let batch = parse_json_batch ( json_strings) ;
210+
211+ let mut visitor = SetTransactionVisitor :: new ( None , Some ( 1000 ) ) ;
212+ visitor. visit_rows_of ( batch. as_ref ( ) ) . unwrap ( ) ;
213+
214+ // app_with_last_updated should be filtered out (100 < 1000)
215+ // app_without_last_updated should be kept
216+ assert_eq ! ( visitor. set_transactions. len( ) , 1 ) ;
217+ assert ! ( visitor. set_transactions. contains_key( "app_without_time" ) ) ;
218+ }
168219}
0 commit comments