18
18
19
19
use std:: collections:: BTreeMap ;
20
20
use std:: num:: NonZeroU32 ;
21
+ use std:: sync:: Mutex ;
21
22
22
23
use chrono:: { DateTime , Duration , Utc } ;
23
24
use serde_json:: map:: Map ;
@@ -27,6 +28,9 @@ use thiserror::Error;
27
28
28
29
use crate :: parseable:: PARSEABLE ;
29
30
31
+ // Global variable to track the first timestamp encountered during validation
32
+ static REFERENCE_TIMESTAMP : Mutex < Option < DateTime < Utc > > > = Mutex :: new ( None ) ;
33
+
30
34
#[ derive( Error , Debug ) ]
31
35
pub enum JsonFlattenError {
32
36
#[ error( "Cannot flatten this JSON" ) ]
@@ -45,8 +49,12 @@ pub enum JsonFlattenError {
45
49
FieldNotString ( String ) ,
46
50
#[ error( "Field {0} is not in the correct datetime format" ) ]
47
51
InvalidDatetimeFormat ( String ) ,
48
- #[ error( "Field {0} value is more than {1} days old" ) ]
49
- TimestampTooOld ( String , i64 ) ,
52
+ #[ error( "Field {0} value '{2}' is more than {1} days old" ) ]
53
+ TimestampTooOld ( String , i64 , DateTime < Utc > ) ,
54
+ #[ error(
55
+ "Field {0} timestamp '{2}' is more than {1} hours older than reference timestamp '{3}'"
56
+ ) ]
57
+ TimestampTooOldRelative ( String , i64 , DateTime < Utc > , DateTime < Utc > ) ,
50
58
#[ error( "Expected object in array of objects" ) ]
51
59
ExpectedObjectInArray ,
52
60
#[ error( "Found non-object element while flattening array of objects" ) ]
@@ -169,14 +177,43 @@ pub fn validate_time_partition(
169
177
partition_key. to_owned ( ) ,
170
178
) ) ;
171
179
} ;
172
- let cutoff_date = Utc :: now ( ) . naive_utc ( ) - Duration :: days ( limit_days) ;
173
- if parsed_timestamp. naive_utc ( ) >= cutoff_date {
174
- Ok ( ( ) )
175
- } else {
176
- Err ( JsonFlattenError :: TimestampTooOld (
177
- partition_key. to_owned ( ) ,
178
- limit_days,
179
- ) )
180
+
181
+ // Access the global reference timestamp and handle poisoning
182
+ let mut reference_timestamp = REFERENCE_TIMESTAMP
183
+ . lock ( )
184
+ . unwrap_or_else ( |p| p. into_inner ( ) ) ;
185
+
186
+ match * reference_timestamp {
187
+ None => {
188
+ // First timestamp encountered - validate against cutoff date
189
+ let cutoff_ts = Utc :: now ( ) - Duration :: days ( limit_days) ;
190
+ if parsed_timestamp >= cutoff_ts {
191
+ // Set the reference timestamp
192
+ * reference_timestamp = Some ( parsed_timestamp) ;
193
+ Ok ( ( ) )
194
+ } else {
195
+ Err ( JsonFlattenError :: TimestampTooOld (
196
+ partition_key. to_owned ( ) ,
197
+ limit_days,
198
+ parsed_timestamp,
199
+ ) )
200
+ }
201
+ }
202
+ Some ( ref_timestamp) => {
203
+ // Subsequent timestamps - validate they're not more than configured hours older than reference
204
+ let max_age_hours = PARSEABLE . options . event_max_chunk_age as i64 ;
205
+ let max_age_before_ref = ref_timestamp - Duration :: hours ( max_age_hours) ;
206
+ if parsed_timestamp >= max_age_before_ref {
207
+ Ok ( ( ) )
208
+ } else {
209
+ Err ( JsonFlattenError :: TimestampTooOldRelative (
210
+ partition_key. to_owned ( ) ,
211
+ max_age_hours,
212
+ parsed_timestamp,
213
+ ref_timestamp,
214
+ ) )
215
+ }
216
+ }
180
217
}
181
218
}
182
219
0 commit comments