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

sql: add precision support for TIME/TIMETZ #42668

Merged
merged 1 commit into from
Dec 3, 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
3 changes: 3 additions & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -2092,7 +2092,10 @@ character_without_length ::=

const_datetime ::=
'DATE'
| 'TIME' opt_timezone
| 'TIME' '(' iconst32 ')' opt_timezone
| 'TIMETZ'
| 'TIMETZ' '(' iconst32 ')'
| 'TIMESTAMP' opt_timezone
| 'TIMESTAMP' '(' iconst32 ')' opt_timezone
| 'TIMESTAMPTZ'
Expand Down
2 changes: 1 addition & 1 deletion pkg/ccl/changefeedccl/avro.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func columnDescToAvroSchema(colDesc *sqlbase.ColumnDescriptor) (*avroSchemaField
return d.(*tree.DTimeTZ).TimeTZ.String(), nil
}
schema.decodeFn = func(x interface{}) (tree.Datum, error) {
return tree.ParseDTimeTZ(nil, x.(string))
return tree.ParseDTimeTZ(nil, x.(string), time.Microsecond)
}
case types.TimestampFamily:
avroType = avroLogicalType{
Expand Down
63 changes: 63 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/time
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,66 @@ SELECT extract('day' from time '12:00:00')

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

subtest precision_tests

query error precision 7 out of range
select '1:00:00.001':::TIME(7)

statement ok
CREATE TABLE time_precision_test (
id integer PRIMARY KEY,
t TIME(5)
)

statement ok
INSERT INTO time_precision_test VALUES
(1,'12:00:00.123456+03:00'),
(2,'12:00:00.12345+03:00'),
(3,'12:00:00.1234+03:00'),
(4,'12:00:00.123+03:00'),
(5,'12:00:00.12+03:00'),
(6,'12:00:00.1+03:00'),
(7,'12:00:00+03:00')

query IT
SELECT * FROM time_precision_test ORDER BY id ASC
----
1 0000-01-01 12:00:00.12346 +0000 UTC
2 0000-01-01 12:00:00.12345 +0000 UTC
3 0000-01-01 12:00:00.1234 +0000 UTC
4 0000-01-01 12:00:00.123 +0000 UTC
5 0000-01-01 12:00:00.12 +0000 UTC
6 0000-01-01 12:00:00.1 +0000 UTC
7 0000-01-01 12:00:00 +0000 UTC

query TT
select column_name, data_type FROM [SHOW COLUMNS FROM time_precision_test] ORDER BY column_name
----
id INT8
t TIME(5)

statement ok
ALTER TABLE time_precision_test ALTER COLUMN t TYPE time(6)

statement ok
INSERT INTO time_precision_test VALUES
(100,'12:00:00.123456+03:00')

query IT
SELECT * FROM time_precision_test ORDER BY id ASC
----
1 0000-01-01 12:00:00.12346 +0000 UTC
2 0000-01-01 12:00:00.12345 +0000 UTC
3 0000-01-01 12:00:00.1234 +0000 UTC
4 0000-01-01 12:00:00.123 +0000 UTC
5 0000-01-01 12:00:00.12 +0000 UTC
6 0000-01-01 12:00:00.1 +0000 UTC
7 0000-01-01 12:00:00 +0000 UTC
100 0000-01-01 12:00:00.123456 +0000 UTC

query TT
select column_name, data_type FROM [SHOW COLUMNS FROM time_precision_test] ORDER BY column_name
----
id INT8
t TIME(6)
63 changes: 63 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/timetz
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,66 @@ SELECT '2001-01-01 11:00+04:00'::timestamptz::timetz

statement ok
SET TIME ZONE UTC

subtest precision_tests

query error precision 7 out of range
select '1:00:00.001':::TIMETZ(7)

statement ok
CREATE TABLE timetz_precision_test (
id integer PRIMARY KEY,
t TIMETZ(5)
)

statement ok
INSERT INTO timetz_precision_test VALUES
(1,'12:00:00.123456+03:00'),
(2,'12:00:00.12345+03:00'),
(3,'12:00:00.1234+03:00'),
(4,'12:00:00.123+03:00'),
(5,'12:00:00.12+03:00'),
(6,'12:00:00.1+03:00'),
(7,'12:00:00+03:00')

query IT
SELECT * FROM timetz_precision_test ORDER BY id ASC
----
1 0000-01-01 12:00:00.12346 +0300 +0300
2 0000-01-01 12:00:00.12345 +0300 +0300
3 0000-01-01 12:00:00.1234 +0300 +0300
4 0000-01-01 12:00:00.123 +0300 +0300
5 0000-01-01 12:00:00.12 +0300 +0300
6 0000-01-01 12:00:00.1 +0300 +0300
7 0000-01-01 12:00:00 +0300 +0300

query TT
select column_name, data_type FROM [SHOW COLUMNS FROM timetz_precision_test] ORDER BY column_name
----
id INT8
t TIMETZ(5)

statement ok
ALTER TABLE timetz_precision_test ALTER COLUMN t TYPE timetz(6)

statement ok
INSERT INTO timetz_precision_test VALUES
(100,'12:00:00.123456+03:00')

query IT
SELECT * FROM timetz_precision_test ORDER BY id ASC
----
1 0000-01-01 12:00:00.12346 +0300 +0300
2 0000-01-01 12:00:00.12345 +0300 +0300
3 0000-01-01 12:00:00.1234 +0300 +0300
4 0000-01-01 12:00:00.123 +0300 +0300
5 0000-01-01 12:00:00.12 +0300 +0300
6 0000-01-01 12:00:00.1 +0300 +0300
7 0000-01-01 12:00:00 +0300 +0300
100 0000-01-01 12:00:00.123456 +0300 +0300

query TT
select column_name, data_type FROM [SHOW COLUMNS FROM timetz_precision_test] ORDER BY column_name
----
id INT8
t TIMETZ(6)
18 changes: 11 additions & 7 deletions pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func TestParse(t *testing.T) {
{`CREATE TABLE a (b SERIAL8)`},
{`CREATE TABLE a (b TIME)`},
{`CREATE TABLE a (b TIMETZ)`},
{`CREATE TABLE a (b TIME(3))`},
{`CREATE TABLE a (b TIMETZ(3))`},
{`CREATE TABLE a (b UUID)`},
{`CREATE TABLE a (b INET)`},
{`CREATE TABLE a (b "char")`},
Expand Down Expand Up @@ -1469,11 +1471,20 @@ func TestParse2(t *testing.T) {
{`SELECT CAST(1 AS "_int8")`, `SELECT CAST(1 AS INT8[])`},
{`SELECT SERIAL8 'foo', 'foo'::SERIAL8`, `SELECT INT8 'foo', 'foo'::INT8`},

{`SELECT 'a'::TIMESTAMP(3)`, `SELECT 'a'::TIMESTAMP(3)`},
{`SELECT 'a'::TIMESTAMP(3) WITHOUT TIME ZONE`, `SELECT 'a'::TIMESTAMP(3)`},
{`SELECT 'a'::TIMESTAMPTZ(3)`, `SELECT 'a'::TIMESTAMPTZ(3)`},
{`SELECT 'a'::TIMESTAMP(3) WITH TIME ZONE`, `SELECT 'a'::TIMESTAMPTZ(3)`},
{`SELECT TIMESTAMP(3) 'a'`, `SELECT TIMESTAMP(3) 'a'`},
{`SELECT TIMESTAMPTZ(3) 'a'`, `SELECT TIMESTAMPTZ(3) 'a'`},

{`SELECT 'a'::TIME(3)`, `SELECT 'a'::TIME(3)`},
{`SELECT 'a'::TIME(3) WITHOUT TIME ZONE`, `SELECT 'a'::TIME(3)`},
{`SELECT 'a'::TIMETZ(3)`, `SELECT 'a'::TIMETZ(3)`},
{`SELECT 'a'::TIME(3) WITH TIME ZONE`, `SELECT 'a'::TIMETZ(3)`},
{`SELECT TIME(3) 'a'`, `SELECT TIME(3) 'a'`},
{`SELECT TIMETZ(3) 'a'`, `SELECT TIMETZ(3) 'a'`},

{`SELECT 'a' FROM t@{FORCE_INDEX=bar}`, `SELECT 'a' FROM t@bar`},
{`SELECT 'a' FROM t@{ASC,FORCE_INDEX=idx}`, `SELECT 'a' FROM t@{FORCE_INDEX=idx,ASC}`},

Expand Down Expand Up @@ -3127,13 +3138,6 @@ func TestUnimplementedSyntax(t *testing.T) {
{`SELECT 'a'::INTERVAL SECOND(123)`, 32564, `interval second`},
{`SELECT INTERVAL(3) 'a'`, 32564, ``},

{`SELECT 'a'::TIME(123)`, 32565, ``},
{`SELECT 'a'::TIME(123) WITHOUT TIME ZONE`, 32565, ``},
{`SELECT 'a'::TIMETZ(123)`, 26097, `type with precision`},
{`SELECT 'a'::TIME(123) WITH TIME ZONE`, 32565, ``},
{`SELECT TIME(3) 'a'`, 32565, ``},
{`SELECT TIMETZ(3) 'a'`, 26097, `type with precision`},

{`SELECT a(b) 'c'`, 0, `a(...) SCONST`},
{`SELECT (a,b) OVERLAPS (c,d)`, 0, `overlaps`},
{`SELECT UNIQUE (SELECT b)`, 0, `UNIQUE predicate`},
Expand Down
28 changes: 22 additions & 6 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -7326,19 +7326,35 @@ const_datetime:
}
| TIME opt_timezone
{
if $2.bool() { return unimplementedWithIssueDetail(sqllex, 26097, "type") }
$$.val = types.Time
if $2.bool() {
$$.val = types.TimeTZ
} else {
$$.val = types.Time
}
}
| TIME '(' iconst32 ')' opt_timezone
{
prec := $3.int32()
if prec != 6 {
return unimplementedWithIssue(sqllex, 32565)
if prec < 0 || prec > 6 {
sqllex.Error(fmt.Sprintf("precision %d out of range", prec))
return 1
}
if $5.bool() {
$$.val = types.MakeTimeTZ(prec)
} else {
$$.val = types.MakeTime(prec)
}
$$.val = types.MakeTime(prec)
}
| TIMETZ { $$.val = types.TimeTZ }
| TIMETZ '(' ICONST ')' { return unimplementedWithIssueDetail(sqllex, 26097, "type with precision") }
| TIMETZ '(' iconst32 ')'
{
prec := $3.int32()
if prec < 0 || prec > 6 {
sqllex.Error(fmt.Sprintf("precision %d out of range", prec))
return 1
}
$$.val = types.MakeTimeTZ(prec)
}
| TIMESTAMP opt_timezone
{
if $2.bool() {
Expand Down
4 changes: 2 additions & 2 deletions pkg/sql/pgwire/pgwirebase/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ func DecodeOidDatum(
}
return d, nil
case oid.T_time:
d, err := tree.ParseDTime(nil, string(b))
d, err := tree.ParseDTime(nil, string(b), time.Microsecond)
if err != nil {
return nil, pgerror.Newf(pgcode.Syntax, "could not parse string %q as time", b)
}
return d, nil
case oid.T_timetz:
d, err := tree.ParseDTimeTZ(ctx, string(b))
d, err := tree.ParseDTimeTZ(ctx, string(b), time.Microsecond)
if err != nil {
return nil, pgerror.Newf(pgcode.Syntax, "could not parse string %q as timetz", b)
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/schemachange/alter_column_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ var classifiers = map[types.Family]map[types.Family]classifier{
types.TimestampFamily: classifierPrecision,
types.TimestampTZFamily: classifierPrecision,
},
types.TimeFamily: {
types.TimeFamily: classifierPrecision,
},
types.TimeTZFamily: {
types.TimeTZFamily: classifierPrecision,
},
}

// classifierHardestOf creates a composite classifier that returns the
Expand Down
18 changes: 16 additions & 2 deletions pkg/sql/schemachange/alter_column_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ func TestColumnConversions(t *testing.T) {
"STRING(5)": {
"BYTES": ColumnConversionTrivial,
},

"TIME": {
"TIME": ColumnConversionTrivial,
"TIME(5)": ColumnConversionValidate,
},
"TIMETZ": {
"TIMETZ": ColumnConversionTrivial,
"TIMETZ(5)": ColumnConversionValidate,
},
"TIMESTAMP": {
"TIMESTAMPTZ": ColumnConversionTrivial,
"TIMESTAMP": ColumnConversionTrivial,
Expand Down Expand Up @@ -224,9 +233,11 @@ func TestColumnConversions(t *testing.T) {

case types.TimeFamily,
types.TimestampFamily,
types.TimestampTZFamily:
types.TimestampTZFamily,
types.TimeTZFamily:

const timeOnly = "15:04:05"
const timeOnlyWithZone = "15:04:05 -0700"
const noZone = "2006-01-02 15:04:05"
const withZone = "2006-01-02 15:04:05 -0700"

Expand All @@ -238,6 +249,8 @@ func TestColumnConversions(t *testing.T) {
fromFmt = noZone
case types.TimestampTZFamily:
fromFmt = withZone
case types.TimeTZFamily:
fromFmt = timeOnlyWithZone
}

// Always use a non-UTC zone for this test
Expand All @@ -255,7 +268,8 @@ func TestColumnConversions(t *testing.T) {
case
types.TimeFamily,
types.TimestampFamily,
types.TimestampTZFamily:
types.TimestampTZFamily,
types.TimeTZFamily:
// We're going to re-parse the text as though we're in UTC
// so that we can drop the TZ info.
if parsed, err := time.ParseInLocation(fromFmt, now, time.UTC); err == nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/sem/builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func TestExtractStringFromTimeTZ(t *testing.T) {

for _, tc := range testCases {
t.Run(fmt.Sprintf("%s_%s", tc.timeSpan, tc.timeTZString), func(t *testing.T) {
timeTZ, err := tree.ParseDTimeTZ(nil, tc.timeTZString)
timeTZ, err := tree.ParseDTimeTZ(nil, tc.timeTZString, time.Microsecond)
assert.NoError(t, err)

datum, err := extractStringFromTimeTZ(timeTZ, tc.timeSpan)
Expand Down
10 changes: 9 additions & 1 deletion pkg/sql/sem/tree/constant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,14 @@ func mustParseDDate(t *testing.T, s string) tree.Datum {
return d
}
func mustParseDTime(t *testing.T, s string) tree.Datum {
d, err := tree.ParseDTime(nil, s)
d, err := tree.ParseDTime(nil, s, time.Microsecond)
if err != nil {
t.Fatal(err)
}
return d
}
func mustParseDTimeTZ(t *testing.T, s string) tree.Datum {
d, err := tree.ParseDTimeTZ(nil, s, time.Microsecond)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -270,6 +277,7 @@ var parseFuncs = map[*types.T]func(*testing.T, string) tree.Datum{
types.Bool: mustParseDBool,
types.Date: mustParseDDate,
types.Time: mustParseDTime,
types.TimeTZ: mustParseDTimeTZ,
types.Timestamp: mustParseDTimestamp,
types.TimestampTZ: mustParseDTimestampTZ,
types.Interval: mustParseDInterval,
Expand Down
Loading