From 824ec01e030bf869c509c6907a41c35b8dd57dbc Mon Sep 17 00:00:00 2001 From: Rohan Yadav Date: Wed, 29 May 2019 13:57:04 -0400 Subject: [PATCH] sql: Add support for timestamp objects with precision We now support an optional precision value for timestamp objects. Fixes: #32098 Release note (sql change): Implements sql support for timestamp objects to have an optional precision value --- .../logictest/testdata/logic_test/timestamp | 30 ++++++++++++++++ pkg/sql/parser/sql.y | 6 ++-- pkg/sql/sem/tree/col_types_test.go | 4 +++ pkg/sql/sem/tree/eval.go | 22 ++++++++---- pkg/sql/sem/tree/parse_string.go | 6 ++++ pkg/sql/types/types.go | 36 +++++++++---------- pkg/sql/types/types_test.go | 6 ++-- 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/timestamp b/pkg/sql/logictest/testdata/logic_test/timestamp index 6347bedf2ce9..6bcaaceb7541 100644 --- a/pkg/sql/logictest/testdata/logic_test/timestamp +++ b/pkg/sql/logictest/testdata/logic_test/timestamp @@ -15,3 +15,33 @@ query T SELECT '2000-05-05 10:00:00+03':::TIMESTAMP FROM a ---- 2000-05-05 10:00:00 +0000 +0000 + +query T +select '1-1-18 1:00:00.001':::TIMESTAMP(0) +---- +2001-01-18 01:00:00 +0000 +0000 + +query T +select '1-1-18 1:00:00.001':::TIMESTAMP(6) +---- +2001-01-18 01:00:00.001 +0000 +0000 + +query T +select '1-1-18 1:00:00.001':::TIMESTAMP +---- +2001-01-18 01:00:00.001 +0000 +0000 + +query T +select '1-1-18 1:00:00.001-8':::TIMESTAMPTZ(0) +---- +2001-01-18 09:00:00 +0000 UTC + +query T +select '1-1-18 1:00:00.001-8':::TIMESTAMPTZ(6) +---- +2001-01-18 09:00:00.001 +0000 UTC + +query T +select '1-1-18 1:00:00.001-8':::TIMESTAMPTZ +---- +2001-01-18 09:00:00.001 +0000 UTC \ No newline at end of file diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index e713782accd1..42980795182a 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -6868,8 +6868,8 @@ const_datetime: | TIMESTAMP '(' iconst32 ')' opt_timezone { prec := $3.int32() - if prec != 6 { - return unimplementedWithIssue(sqllex, 32098) + if !(prec == 6 || prec == 0) { + return unimplementedWithIssue(sqllex, 32098) } if $5.bool() { $$.val = types.MakeTimestampTZ(prec) @@ -6884,7 +6884,7 @@ const_datetime: | TIMESTAMPTZ '(' iconst32 ')' { prec := $3.int32() - if prec != 6 { + if !(prec == 6 || prec == 0) { return unimplementedWithIssue(sqllex, 32098) } $$.val = types.MakeTimestampTZ(prec) diff --git a/pkg/sql/sem/tree/col_types_test.go b/pkg/sql/sem/tree/col_types_test.go index d036c83316fa..91556400d569 100644 --- a/pkg/sql/sem/tree/col_types_test.go +++ b/pkg/sql/sem/tree/col_types_test.go @@ -48,7 +48,11 @@ func TestParseColumnType(t *testing.T) { {"JSONB", types.Jsonb}, {"TIME", types.Time}, {"TIMESTAMP", types.Timestamp}, + {"TIMESTAMP(0)", types.MakeTimestamp(0)}, + {"TIMESTAMP(6)", types.MakeTimestamp(6)}, {"TIMESTAMPTZ", types.TimestampTZ}, + {"TIMESTAMPTZ(0)", types.MakeTimestampTZ(0)}, + {"TIMESTAMPTZ(6)", types.MakeTimestampTZ(6)}, {"INTERVAL", types.Interval}, {"STRING", types.String}, {"CHAR", types.MakeChar(1)}, diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go index 1ee2a581d3ee..9d7b26d29310 100644 --- a/pkg/sql/sem/tree/eval.go +++ b/pkg/sql/sem/tree/eval.go @@ -3363,14 +3363,18 @@ func PerformCast(ctx *EvalContext, d Datum, t *types.T) (Datum, error) { case types.TimestampFamily: // TODO(knz): Timestamp from float, decimal. + prec := time.Microsecond + if t.Precision() == 0 { + prec = time.Second + } switch d := d.(type) { case *DString: - return ParseDTimestamp(ctx, string(*d), time.Microsecond) + return ParseDTimestamp(ctx, string(*d), prec) case *DCollatedString: - return ParseDTimestamp(ctx, d.Contents, time.Microsecond) + return ParseDTimestamp(ctx, d.Contents, prec) case *DDate: t, err := d.ToTime() - return MakeDTimestamp(t, time.Microsecond), err + return MakeDTimestamp(t, prec), err case *DInt: return MakeDTimestamp(timeutil.Unix(int64(*d), 0), time.Second), nil case *DTimestamp: @@ -3382,20 +3386,24 @@ func PerformCast(ctx *EvalContext, d Datum, t *types.T) (Datum, error) { case types.TimestampTZFamily: // TODO(knz): TimestampTZ from float, decimal. + prec := time.Microsecond + if t.Precision() == 0 { + prec = time.Second + } switch d := d.(type) { case *DString: - return ParseDTimestampTZ(ctx, string(*d), time.Microsecond) + return ParseDTimestampTZ(ctx, string(*d), prec) case *DCollatedString: - return ParseDTimestampTZ(ctx, d.Contents, time.Microsecond) + return ParseDTimestampTZ(ctx, d.Contents, prec) case *DDate: t, err := d.ToTime() _, before := t.Zone() _, after := t.In(ctx.GetLocation()).Zone() - return MakeDTimestampTZ(t.Add(time.Duration(before-after)*time.Second), time.Microsecond), err + return MakeDTimestampTZ(t.Add(time.Duration(before-after)*time.Second), prec), err case *DTimestamp: _, before := d.Time.Zone() _, after := d.Time.In(ctx.GetLocation()).Zone() - return MakeDTimestampTZ(d.Time.Add(time.Duration(before-after)*time.Second), time.Microsecond), nil + return MakeDTimestampTZ(d.Time.Add(time.Duration(before-after)*time.Second), prec), nil case *DInt: return MakeDTimestampTZ(timeutil.Unix(int64(*d), 0), time.Second), nil case *DTimestampTZ: diff --git a/pkg/sql/sem/tree/parse_string.go b/pkg/sql/sem/tree/parse_string.go index a0e623a63fa7..637b49adf8dc 100644 --- a/pkg/sql/sem/tree/parse_string.go +++ b/pkg/sql/sem/tree/parse_string.go @@ -83,8 +83,14 @@ func parseStringAs(t *types.T, s string, ctx ParseTimeContext) (Datum, error) { case types.TimeFamily: return ParseDTime(ctx, s) case types.TimestampFamily: + if t.Precision() == 0 { + return ParseDTimestamp(ctx, s, time.Second) + } return ParseDTimestamp(ctx, s, time.Microsecond) case types.TimestampTZFamily: + if t.Precision() == 0 { + return ParseDTimestampTZ(ctx, s, time.Second) + } return ParseDTimestampTZ(ctx, s, time.Microsecond) case types.UuidFamily: return ParseDUuidFromString(s) diff --git a/pkg/sql/types/types.go b/pkg/sql/types/types.go index e660cf8f998b..66198cb45a25 100644 --- a/pkg/sql/types/types.go +++ b/pkg/sql/types/types.go @@ -256,9 +256,8 @@ var ( // precision. For example: // // YYYY-MM-DD HH:MM:SS.ssssss - // Timestamp = &T{InternalType: InternalType{ - Family: TimestampFamily, Oid: oid.T_timestamp, Locale: &emptyLocale}} + Family: TimestampFamily, Precision: -1, Oid: oid.T_timestamp, Locale: &emptyLocale}} // TimestampTZ is the type of a value specifying year, month, day, hour, // minute, and second, as well as an associated timezone. By default, it has @@ -267,7 +266,7 @@ var ( // YYYY-MM-DD HH:MM:SS.ssssss+-ZZ:ZZ // TimestampTZ = &T{InternalType: InternalType{ - Family: TimestampTZFamily, Oid: oid.T_timestamptz, Locale: &emptyLocale}} + Family: TimestampTZFamily, Precision: -1, Oid: oid.T_timestamptz, Locale: &emptyLocale}} // Interval is the type of a value describing a duration of time. By default, // it has microsecond precision. @@ -648,27 +647,21 @@ func MakeTime(precision int32) *T { // MakeTimestamp constructs a new instance of a TIMESTAMP type that has at most // the given number of fractional second digits. func MakeTimestamp(precision int32) *T { - if precision == 0 { - return Timestamp - } - if precision != 6 { - panic(pgerror.AssertionFailedf("precision %d is not currently supported", precision)) + if precision == 0 || precision == 6 { + return &T{InternalType: InternalType{ + Family: TimestampFamily, Oid: oid.T_timestamp, Precision: precision, Locale: &emptyLocale}} } - return &T{InternalType: InternalType{ - Family: TimestampFamily, Oid: oid.T_timestamp, Precision: precision, Locale: &emptyLocale}} + panic(pgerror.AssertionFailedf("precision %d is not currently supported", precision)) } // MakeTimestampTZ constructs a new instance of a TIMESTAMPTZ type that has at // most the given number of fractional second digits. func MakeTimestampTZ(precision int32) *T { - if precision == 0 { - return TimestampTZ - } - if precision != 6 { - panic(pgerror.AssertionFailedf("precision %d is not currently supported", precision)) + if precision == 0 || precision == 6 { + return &T{InternalType: InternalType{ + Family: TimestampTZFamily, Oid: oid.T_timestamptz, Precision: precision, Locale: &emptyLocale}} } - return &T{InternalType: InternalType{ - Family: TimestampTZFamily, Oid: oid.T_timestamptz, Precision: precision, Locale: &emptyLocale}} + panic(pgerror.AssertionFailedf("precision %d is not currently supported", precision)) } // MakeArray constructs a new instance of an ArrayFamily type with the given @@ -775,6 +768,7 @@ func (t *T) Width() int32 { // TIMESTAMP : max # fractional second digits // TIMESTAMPTZ: max # fractional second digits // +// For TIMESTAMP and TIMESTAMP TZ, the precision field is -1 for a default precision value of 6. // Precision is always 0 for other types. func (t *T) Precision() int32 { return t.InternalType.Precision @@ -1092,7 +1086,13 @@ func (t *T) SQLString() string { case JsonFamily: // Only binary JSON is currently supported. return "JSONB" - case TimeFamily, TimestampFamily, TimestampTZFamily: + case TimestampFamily, TimestampTZFamily: + if t.Precision() != -1 { + return fmt.Sprintf("%s(%d)", strings.ToUpper(t.Name()), t.Precision()) + } + // This is the timestamp with the default precision value + return strings.ToUpper(t.Name()) + case TimeFamily: if t.Precision() > 0 { return fmt.Sprintf("%s(%d)", strings.ToUpper(t.Name()), t.Precision()) } diff --git a/pkg/sql/types/types_test.go b/pkg/sql/types/types_test.go index e6aa3500ff73..9ff9d0b60aa2 100644 --- a/pkg/sql/types/types_test.go +++ b/pkg/sql/types/types_test.go @@ -223,17 +223,15 @@ func TestTypes(t *testing.T) { {MakeTime(6), MakeScalar(TimeFamily, oid.T_time, 6, 0, emptyLocale)}, // TIMESTAMP - {MakeTimestamp(0), Timestamp}, {MakeTimestamp(0), &T{InternalType: InternalType{ - Family: TimestampFamily, Oid: oid.T_timestamp, Locale: &emptyLocale}}}, + Family: TimestampFamily, Precision: 0, Oid: oid.T_timestamp, Locale: &emptyLocale}}}, {MakeTimestamp(6), &T{InternalType: InternalType{ Family: TimestampFamily, Oid: oid.T_timestamp, Precision: 6, Locale: &emptyLocale}}}, {MakeTimestamp(6), MakeScalar(TimestampFamily, oid.T_timestamp, 6, 0, emptyLocale)}, // TIMESTAMPTZ - {MakeTimestampTZ(0), TimestampTZ}, {MakeTimestampTZ(0), &T{InternalType: InternalType{ - Family: TimestampTZFamily, Oid: oid.T_timestamptz, Locale: &emptyLocale}}}, + Family: TimestampTZFamily, Precision: 0, Oid: oid.T_timestamptz, Locale: &emptyLocale}}}, {MakeTimestampTZ(6), &T{InternalType: InternalType{ Family: TimestampTZFamily, Oid: oid.T_timestamptz, Precision: 6, Locale: &emptyLocale}}}, {MakeTimestampTZ(6), MakeScalar(TimestampTZFamily, oid.T_timestamptz, 6, 0, emptyLocale)},