diff --git a/sa/type-converter.go b/sa/type-converter.go index 2ffb5bc1bc10..d7d92eb79422 100644 --- a/sa/type-converter.go +++ b/sa/type-converter.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "time" "github.com/go-jose/go-jose/v4" @@ -35,6 +36,18 @@ func (tc BoulderTypeConverter) ToDb(val interface{}) (interface{}, error) { return string(t), nil case core.OCSPStatus: return string(t), nil + // Time types get truncated to the nearest second. Given our DB schema, + // only seconds are stored anyhow. Avoiding sending queries with sub-second + // precision may help the query planner avoid pathological cases when + // querying against indexes on time fields (#5437). + case time.Time: + return t.Truncate(time.Second), nil + case *time.Time: + if t == nil { + return nil, nil + } + newT := t.Truncate(time.Second) + return &newT, nil default: return val, nil } diff --git a/sa/type-converter_test.go b/sa/type-converter_test.go index c0849e759e27..8ca7d35d1994 100644 --- a/sa/type-converter_test.go +++ b/sa/type-converter_test.go @@ -3,6 +3,7 @@ package sa import ( "encoding/json" "testing" + "time" "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/identifier" @@ -151,3 +152,26 @@ func TestStringSlice(t *testing.T) { test.AssertNotError(t, err, "failed to scanner.Binder") test.AssertMarshaledEquals(t, au, out) } + +func TestTimeTruncate(t *testing.T) { + tc := BoulderTypeConverter{} + preciseTime := time.Date(2024, 06, 20, 00, 00, 00, 999999999, time.UTC) + dbTime, err := tc.ToDb(preciseTime) + test.AssertNotError(t, err, "Could not ToDb") + dbTimeT, ok := dbTime.(time.Time) + test.Assert(t, ok, "Could not convert dbTime to time.Time") + test.Assert(t, dbTimeT.Nanosecond() == 0, "Nanosecond not truncated") + + dbTimePtr, err := tc.ToDb(&preciseTime) + test.AssertNotError(t, err, "Could not ToDb") + dbTimePtrT, ok := dbTimePtr.(*time.Time) + test.Assert(t, ok, "Could not convert dbTimePtr to *time.Time") + test.Assert(t, dbTimePtrT.Nanosecond() == 0, "Nanosecond not truncated") + + var dbTimePtrNil *time.Time + shouldBeNil, err := tc.ToDb(dbTimePtrNil) + test.AssertNotError(t, err, "Could not ToDb") + if shouldBeNil != nil { + t.Errorf("Expected nil, got %v", shouldBeNil) + } +}