@@ -578,6 +578,16 @@ class Database(metaclass=abc.ABCMeta):
578578 def Now (self ) -> rdfvalue .RDFDatetime :
579579 """Retrieves current time as reported by the database."""
580580
581+ # Different DB engines might make different assumptions about what a valid
582+ # minimal timestamp is.
583+ # For example, MySQL doesn't handle sub second fractional timestamps well:
584+ # Per https://dev.mysql.com/doc/refman/8.0/en/datetime.html:
585+ # "the range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to
586+ # '2038-01-19 03:14:07.999999'".
587+ @abc .abstractmethod
588+ def MinTimestamp (self ) -> rdfvalue .RDFDatetime :
589+ """Returns minimal timestamp allowed by the DB."""
590+
581591 @abc .abstractmethod
582592 def WriteArtifact (self , artifact ):
583593 """Writes new artifact to the database.
@@ -3062,6 +3072,9 @@ def __init__(self, delegate: Database):
30623072 def Now (self ) -> rdfvalue .RDFDatetime :
30633073 return self .delegate .Now ()
30643074
3075+ def MinTimestamp (self ) -> rdfvalue .RDFDatetime :
3076+ return self .delegate .MinTimestamp ()
3077+
30653078 def WriteArtifact (self , artifact ):
30663079 precondition .AssertType (artifact , rdf_artifacts .Artifact )
30673080 if not artifact .name :
@@ -3185,7 +3198,7 @@ def WriteClientSnapshotHistory(self, clients):
31853198 def ReadClientSnapshotHistory (self , client_id , timerange = None ):
31863199 precondition .ValidateClientId (client_id )
31873200 if timerange is not None :
3188- _ValidateTimeRange (timerange )
3201+ self . _ValidateTimeRange (timerange )
31893202
31903203 return self .delegate .ReadClientSnapshotHistory (
31913204 client_id , timerange = timerange )
@@ -3205,7 +3218,7 @@ def ReadClientStartupInfo(self,
32053218 def ReadClientStartupInfoHistory (self , client_id , timerange = None ):
32063219 precondition .ValidateClientId (client_id )
32073220 if timerange is not None :
3208- _ValidateTimeRange (timerange )
3221+ self . _ValidateTimeRange (timerange )
32093222
32103223 return self .delegate .ReadClientStartupInfoHistory (
32113224 client_id , timerange = timerange )
@@ -3250,7 +3263,7 @@ def ListClientsForKeywords(
32503263 keywords = set (keywords )
32513264
32523265 if start_time :
3253- _ValidateTimestamp (start_time )
3266+ self . _ValidateTimestamp (start_time )
32543267
32553268 result = self .delegate .ListClientsForKeywords (
32563269 keywords , start_time = start_time )
@@ -3324,12 +3337,12 @@ def ReadClientStats(
33243337 if min_timestamp is None :
33253338 min_timestamp = rdfvalue .RDFDatetime .Now () - CLIENT_STATS_RETENTION
33263339 else :
3327- _ValidateTimestamp (min_timestamp )
3340+ self . _ValidateTimestamp (min_timestamp )
33283341
33293342 if max_timestamp is None :
33303343 max_timestamp = rdfvalue .RDFDatetime .Now ()
33313344 else :
3332- _ValidateTimestamp (max_timestamp )
3345+ self . _ValidateTimestamp (max_timestamp )
33333346
33343347 return self .delegate .ReadClientStats (client_id , min_timestamp ,
33353348 max_timestamp )
@@ -3475,7 +3488,7 @@ def ReadPathInfo(self, client_id, path_type, components, timestamp=None):
34753488 _ValidatePathComponents (components )
34763489
34773490 if timestamp is not None :
3478- _ValidateTimestamp (timestamp )
3491+ self . _ValidateTimestamp (timestamp )
34793492
34803493 return self .delegate .ReadPathInfo (
34813494 client_id , path_type , components , timestamp = timestamp )
@@ -3525,7 +3538,7 @@ def FindPathInfoByPathID(self, client_id, path_type, path_id, timestamp=None):
35253538 precondition .ValidateClientId (client_id )
35263539
35273540 if timestamp is not None :
3528- _ValidateTimestamp (timestamp )
3541+ self . _ValidateTimestamp (timestamp )
35293542
35303543 return self .delegate .FindPathInfoByPathID ( # pytype: disable=attribute-error
35313544 client_id , path_type , path_id , timestamp = timestamp )
@@ -3596,7 +3609,7 @@ def WriteUserNotification(self, notification):
35963609 def ReadUserNotifications (self , username , state = None , timerange = None ):
35973610 _ValidateUsername (username )
35983611 if timerange is not None :
3599- _ValidateTimeRange (timerange )
3612+ self . _ValidateTimeRange (timerange )
36003613 if state is not None :
36013614 _ValidateNotificationState (state )
36023615
@@ -3759,7 +3772,7 @@ def ReadCronJobRuns(self, job_id):
37593772 return self .delegate .ReadCronJobRuns (job_id )
37603773
37613774 def DeleteOldCronJobRuns (self , cutoff_timestamp ):
3762- _ValidateTimestamp (cutoff_timestamp )
3775+ self . _ValidateTimestamp (cutoff_timestamp )
37633776 return self .delegate .DeleteOldCronJobRuns (cutoff_timestamp )
37643777
37653778 def WriteHashBlobReferences (self , references_by_hash ):
@@ -3880,10 +3893,10 @@ def UpdateFlow(self,
38803893 precondition .AssertType (client_crash_info , rdf_client .ClientCrash )
38813894 if processing_since != Database .unchanged :
38823895 if processing_since is not None :
3883- _ValidateTimestamp (processing_since )
3896+ self . _ValidateTimestamp (processing_since )
38843897 if processing_deadline != Database .unchanged :
38853898 if processing_deadline is not None :
3886- _ValidateTimestamp (processing_deadline )
3899+ self . _ValidateTimestamp (processing_deadline )
38873900 return self .delegate .UpdateFlow (
38883901 client_id ,
38893902 flow_id ,
@@ -4487,6 +4500,33 @@ def ReadBlobEncryptionKeys(
44874500
44884501 return self .delegate .ReadBlobEncryptionKeys (blob_ids )
44894502
4503+ # Minimal allowed timestamp is DB-specific. Thus the validation code for
4504+ # timestamps is DB-specific as well.
4505+ def _ValidateTimeRange (
4506+ self , timerange : Tuple [rdfvalue .RDFDatetime , rdfvalue .RDFDatetime ]
4507+ ):
4508+ """Parses a timerange argument and always returns non-None timerange."""
4509+ if len (timerange ) != 2 :
4510+ raise ValueError ("Timerange should be a sequence with 2 items." )
4511+
4512+ (start , end ) = timerange
4513+ precondition .AssertOptionalType (start , rdfvalue .RDFDatetime )
4514+ precondition .AssertOptionalType (end , rdfvalue .RDFDatetime )
4515+ if start is not None :
4516+ self ._ValidateTimestamp (start )
4517+ if end is not None :
4518+ self ._ValidateTimestamp (end )
4519+
4520+ # Minimal allowed timestamp is DB-specific. Thus the validation code for
4521+ # timestamps is DB-specific as well.
4522+ def _ValidateTimestamp (self , timestamp : rdfvalue .RDFDatetime ):
4523+ precondition .AssertType (timestamp , rdfvalue .RDFDatetime )
4524+ if timestamp < self .delegate .MinTimestamp ():
4525+ raise ValueError (
4526+ "Timestamp is less than the minimal timestamp allowed by the DB: "
4527+ f"{ timestamp } < { self .delegate .MinTimestamp ()} ."
4528+ )
4529+
44904530
44914531def _ValidateEnumType (value , expected_enum_type ):
44924532 if value not in expected_enum_type .reverse_enum :
@@ -4606,35 +4646,10 @@ def _ValidateNotificationState(notification_state):
46064646 raise ValueError ("notification_state can't be STATE_UNSET" )
46074647
46084648
4609- def _ValidateTimeRange (timerange ):
4610- """Parses a timerange argument and always returns non-None timerange."""
4611- if len (timerange ) != 2 :
4612- raise ValueError ("Timerange should be a sequence with 2 items." )
4613-
4614- (start , end ) = timerange
4615- precondition .AssertOptionalType (start , rdfvalue .RDFDatetime )
4616- precondition .AssertOptionalType (end , rdfvalue .RDFDatetime )
4617-
4618-
4619- def _ValidateClosedTimeRange (time_range ):
4620- """Checks that a time-range has both start and end timestamps set."""
4621- time_range_start , time_range_end = time_range
4622- _ValidateTimestamp (time_range_start )
4623- _ValidateTimestamp (time_range_end )
4624- if time_range_start > time_range_end :
4625- raise ValueError ("Invalid time-range: %d > %d." %
4626- (time_range_start .AsMicrosecondsSinceEpoch (),
4627- time_range_end .AsMicrosecondsSinceEpoch ()))
4628-
4629-
46304649def _ValidateDuration (duration ):
46314650 precondition .AssertType (duration , rdfvalue .Duration )
46324651
46334652
4634- def _ValidateTimestamp (timestamp ):
4635- precondition .AssertType (timestamp , rdfvalue .RDFDatetime )
4636-
4637-
46384653def _ValidateClientPathID (client_path_id ):
46394654 precondition .AssertType (client_path_id , rdf_objects .ClientPathID )
46404655
0 commit comments