Skip to content

Commit

Permalink
sql: Add support for timestamp objects with precision
Browse files Browse the repository at this point in the history
We now support an optional precision value for timestamp
objects.

Fixes: cockroachdb#32098

Release note (sql change): Implements sql support for timestamp
objects to have an optional precision value
  • Loading branch information
rohany committed Jun 4, 2019
1 parent 6621937 commit 824ec01
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 32 deletions.
30 changes: 30 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/timestamp
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions pkg/sql/sem/tree/col_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down
22 changes: 15 additions & 7 deletions pkg/sql/sem/tree/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/sem/tree/parse_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 18 additions & 18 deletions pkg/sql/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
Expand Down
6 changes: 2 additions & 4 deletions pkg/sql/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down

0 comments on commit 824ec01

Please sign in to comment.