Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release-19.2: sql: fix parsing of 0000-01-01 as Time/TimeTZ #42918

Merged
merged 1 commit into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/time
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,16 @@ SELECT extract(epoch from time '12:00:00')

query error pgcode 22023 extract\(\): unsupported timespan: day
SELECT extract(day from time '12:00:00')

subtest regression_42749

# cast to string to prove it is 24:00
query T
SELECT '0000-01-01 24:00:00'::time::string
----
24:00:00

query T
SELECT '2001-01-01 01:24:00'::time
----
0000-01-01 01:24:00 +0000 UTC
8 changes: 5 additions & 3 deletions pkg/sql/sem/tree/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ var (
DZero = NewDInt(0)

// DTimeRegex is a compiled regex for parsing the 24:00 time value
DTimeRegex = regexp.MustCompile("^24:00($|(:00$)|(:00.0+$))")
DTimeRegex = regexp.MustCompile(`^([0-9-]*(\s|T))?\s*24:00(:00(.0+)?)?\s*$`)
)

// Datum represents a SQL value.
Expand Down Expand Up @@ -1864,13 +1864,15 @@ func MakeDTime(t timeofday.TimeOfDay) *DTime {
func ParseDTime(ctx ParseTimeContext, s string) (*DTime, error) {
now := relativeParseTime(ctx)

// special case on 24:00 and 24:00:00 as the parser
// Special case on 24:00 and 24:00:00 as the parser
// does not handle these correctly.
if DTimeRegex.MatchString(s) {
return MakeDTime(timeofday.Time2400), nil
}

t, err := pgdate.ParseTime(now, 0 /* mode */, s)
s = timeutil.ReplaceLibPQTimePrefix(s)

t, err := pgdate.ParseTime(now, pgdate.ParseModeYMD, s)
if err != nil {
// Build our own error message to avoid exposing the dummy date.
return nil, makeParseError(s, types.Time, nil)
Expand Down
9 changes: 9 additions & 0 deletions pkg/sql/sem/tree/datum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,13 +485,22 @@ func TestParseDTime(t *testing.T) {
str string
expected timeofday.TimeOfDay
}{
{" 04:05:06 ", timeofday.New(4, 5, 6, 0)},
{"04:05:06", timeofday.New(4, 5, 6, 0)},
{"04:05:06.000001", timeofday.New(4, 5, 6, 1)},
{"04:05:06-07", timeofday.New(4, 5, 6, 0)},
{"4:5:6", timeofday.New(4, 5, 6, 0)},
{"24:00:00", timeofday.Time2400},
{"24:00:00.000", timeofday.Time2400},
{"24:00:00.000000", timeofday.Time2400},
{"0000-01-01 04:05:06", timeofday.New(4, 5, 6, 0)},
{"2001-01-01 04:05:06", timeofday.New(4, 5, 6, 0)},
{"0000-01-01T04:05:06", timeofday.New(4, 5, 6, 0)},
{"0000-01-01T24:00:00", timeofday.Time2400},
{"0000-01-01 24:00:00", timeofday.Time2400},
{"0000-01-01 24:00:00.0", timeofday.Time2400},
{" 24:00:00.0", timeofday.Time2400},
{" 24:00:00.0 ", timeofday.Time2400},
}
for _, td := range testData {
actual, err := tree.ParseDTime(nil, td.str)
Expand Down
13 changes: 13 additions & 0 deletions pkg/util/timeutil/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package timeutil

import (
"math"
"strings"
"time"
)

Expand All @@ -20,6 +21,9 @@ import (
// assuming any bound on the clock drift.
const ClocklessMaxOffset = math.MaxInt64

// LibPQTimePrefix is the prefix lib/pq prints time-type datatypes with.
const LibPQTimePrefix = "0000-01-01"

// Since returns the time elapsed since t.
// It is shorthand for Now().Sub(t).
func Since(t time.Time) time.Duration {
Expand Down Expand Up @@ -72,3 +76,12 @@ func SleepUntil(untilNanos int64, currentTimeNanos func() int64) {
time.Sleep(d)
}
}

// ReplaceLibPQTimePrefix replaces unparsable lib/pq dates used for timestamps
// (0000-01-01) with timestamps that can be parsed by date libraries.
func ReplaceLibPQTimePrefix(s string) string {
if strings.HasPrefix(s, LibPQTimePrefix) {
return "1970-01-01" + s[len(LibPQTimePrefix):]
}
return s
}
7 changes: 7 additions & 0 deletions pkg/util/timeutil/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"math/rand"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestUnixMicros(t *testing.T) {
Expand Down Expand Up @@ -63,3 +65,8 @@ func TestUnixMicrosRounding(t *testing.T) {
}
}
}

func TestReplaceLibPQTimePrefix(t *testing.T) {
assert.Equal(t, "1970-02-02 11:00", ReplaceLibPQTimePrefix("1970-02-02 11:00"))
assert.Equal(t, "1970-01-01 11:00", ReplaceLibPQTimePrefix("0000-01-01 11:00"))
}