diff --git a/docs/generated/sql/aggregates.md b/docs/generated/sql/aggregates.md index 9472b97395ca..d552fd4e4cac 100644 --- a/docs/generated/sql/aggregates.md +++ b/docs/generated/sql/aggregates.md @@ -47,6 +47,8 @@ max(arg1: string) → string

Identifies the maximum selected value.

+max(arg1: time) → time

Identifies the maximum selected value.

+
max(arg1: timestamp) → timestamp

Identifies the maximum selected value.

max(arg1: timestamptz) → timestamptz

Identifies the maximum selected value.

@@ -75,6 +77,8 @@
min(arg1: string) → string

Identifies the minimum selected value.

+min(arg1: time) → time

Identifies the minimum selected value.

+
min(arg1: timestamp) → timestamp

Identifies the minimum selected value.

min(arg1: timestamptz) → timestamptz

Identifies the minimum selected value.

diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index 52151a7c0fde..413761bec9d5 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -21,6 +21,8 @@
array_append(array: string[], elem: string) → string[]

Appends elem to array, returning the result.

+array_append(array: time[], elem: time) → time[]

Appends elem to array, returning the result.

+
array_append(array: timestamp[], elem: timestamp) → timestamp[]

Appends elem to array, returning the result.

array_append(array: timestamptz[], elem: timestamptz) → timestamptz[]

Appends elem to array, returning the result.

@@ -47,6 +49,8 @@
array_cat(left: string[], right: string[]) → string[]

Appends two arrays.

+array_cat(left: time[], right: time[]) → time[]

Appends two arrays.

+
array_cat(left: timestamp[], right: timestamp[]) → timestamp[]

Appends two arrays.

array_cat(left: timestamptz[], right: timestamptz[]) → timestamptz[]

Appends two arrays.

@@ -77,6 +81,8 @@
array_position(array: string[], elem: string) → int

Return the index of the first occurrence of elem in array.

+array_position(array: time[], elem: time) → int

Return the index of the first occurrence of elem in array.

+
array_position(array: timestamp[], elem: timestamp) → int

Return the index of the first occurrence of elem in array.

array_position(array: timestamptz[], elem: timestamptz) → int

Return the index of the first occurrence of elem in array.

@@ -103,6 +109,8 @@
array_positions(array: string[], elem: string) → string[]

Returns and array of indexes of all occurrences of elem in array.

+array_positions(array: time[], elem: time) → time[]

Returns and array of indexes of all occurrences of elem in array.

+
array_positions(array: timestamp[], elem: timestamp) → timestamp[]

Returns and array of indexes of all occurrences of elem in array.

array_positions(array: timestamptz[], elem: timestamptz) → timestamptz[]

Returns and array of indexes of all occurrences of elem in array.

@@ -129,6 +137,8 @@
array_prepend(elem: string, array: string[]) → string[]

Prepends elem to array, returning the result.

+array_prepend(elem: time, array: time[]) → time[]

Prepends elem to array, returning the result.

+
array_prepend(elem: timestamp, array: timestamp[]) → timestamp[]

Prepends elem to array, returning the result.

array_prepend(elem: timestamptz, array: timestamptz[]) → timestamptz[]

Prepends elem to array, returning the result.

@@ -155,6 +165,8 @@
array_remove(array: string[], elem: string) → string[]

Remove from array all elements equal to elem.

+array_remove(array: time[], elem: time) → time[]

Remove from array all elements equal to elem.

+
array_remove(array: timestamp[], elem: timestamp) → timestamp[]

Remove from array all elements equal to elem.

array_remove(array: timestamptz[], elem: timestamptz) → timestamptz[]

Remove from array all elements equal to elem.

@@ -181,6 +193,8 @@
array_replace(array: string[], toreplace: string, replacewith: string) → string[]

Replace all occurrences of toreplace in array with replacewith.

+array_replace(array: time[], toreplace: time, replacewith: time) → time[]

Replace all occurrences of toreplace in array with replacewith.

+
array_replace(array: timestamp[], toreplace: timestamp, replacewith: timestamp) → timestamp[]

Replace all occurrences of toreplace in array with replacewith.

array_replace(array: timestamptz[], toreplace: timestamptz, replacewith: timestamptz) → timestamptz[]

Replace all occurrences of toreplace in array with replacewith.

@@ -237,6 +251,10 @@ significant than element to zero (or one, for day and month)

Compatible elements: year, quarter, month, week, hour, minute, second, millisecond, microsecond.

+date_trunc(element: string, input: time) → time

Truncates input to precision element. Sets all fields that are less +significant than element to zero.

+

Compatible elements: hour, minute, second, millisecond, microsecond.

+
date_trunc(element: string, input: timestamp) → timestamp

Truncates input to precision element. Sets all fields that are less significant than element to zero (or one, for day and month)

Compatible elements: year, quarter, month, week, hour, minute, second, @@ -259,6 +277,9 @@ millisecond, microsecond.

Compatible elements: year, quarter, month, week, dayofweek, dayofyear, hour, minute, second, millisecond, microsecond, epoch

+extract(element: string, input: time) → int

Extracts element from input.

+

Compatible elements: hour, minute, second, millisecond, microsecond, epoch

+
extract(element: string, input: timestamp) → int

Extracts element from input.

Compatible elements: year, quarter, month, week, dayofweek, dayofyear, hour, minute, second, millisecond, microsecond, epoch

diff --git a/docs/generated/sql/operators.md b/docs/generated/sql/operators.md index d8fcbc193a7e..f45eb703093d 100644 --- a/docs/generated/sql/operators.md +++ b/docs/generated/sql/operators.md @@ -47,6 +47,7 @@ +intint date + intdate date + intervaltimestamptz +date + timetimestamp decimal + decimaldecimal decimal + intdecimal float + floatfloat @@ -55,8 +56,11 @@ int + intint interval + datetimestamptz interval + intervalinterval +interval + timetime interval + timestamptimestamp interval + timestamptztimestamptz +time + datetimestamp +time + intervaltime timestamp + intervaltimestamp timestamptz + intervaltimestamptz @@ -70,6 +74,7 @@ date - dateint date - intdate date - intervaltimestamptz +date - timetimestamp decimal - decimaldecimal decimal - intdecimal float - floatfloat @@ -78,6 +83,8 @@ interval - intervalinterval jsonb - intjsonb jsonb - stringjsonb +time - intervaltime +time - timeinterval timestamp - intervaltimestamp timestamp - timestampinterval timestamp - timestamptzinterval @@ -138,6 +145,7 @@ int < intbool interval < intervalbool string < stringbool +time < timebool timestamp < datebool timestamp < timestampbool timestamp < timestamptzbool @@ -173,6 +181,7 @@ int <= intbool interval <= intervalbool string <= stringbool +time <= timebool timestamp <= datebool timestamp <= timestampbool timestamp <= timestamptzbool @@ -216,6 +225,8 @@ oid = oidbool string = stringbool string[] = string[]bool +time = timebool +time[] = time[]bool timestamp = datebool timestamp = timestampbool timestamp = timestamptzbool @@ -268,6 +279,7 @@ jsonb IN tuplebool oid IN tuplebool string IN tuplebool +time IN tuplebool timestamp IN tuplebool timestamptz IN tuplebool tuple IN tuplebool @@ -335,6 +347,9 @@ string || string[]string[] string[] || stringstring[] string[] || string[]string[] +time || time[]time[] +time[] || timetime[] +time[] || time[]time[] timestamp || timestamp[]timestamp[] timestamp[] || timestamptimestamp[] timestamp[] || timestamp[]timestamp[] diff --git a/pkg/acceptance/testdata/java/src/main/java/MainTest.java b/pkg/acceptance/testdata/java/src/main/java/MainTest.java index eb65087c896a..b0d3ca68dbb3 100644 --- a/pkg/acceptance/testdata/java/src/main/java/MainTest.java +++ b/pkg/acceptance/testdata/java/src/main/java/MainTest.java @@ -6,6 +6,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -127,6 +128,15 @@ public void testDecimal() throws Exception { Assert.assertEquals("1E+1", bigdec.toString()); } + @Test + public void testTime() throws Exception { + PreparedStatement stmt = conn.prepareStatement("SELECT '01:02:03.456'::TIME"); + ResultSet rs = stmt.executeQuery(); + rs.next(); + String actual = new SimpleDateFormat("HH:mm:ss.SSS").format(rs.getTime(1)); + Assert.assertEquals("01:02:03.456", actual); + } + @Test public void testUUID() throws Exception { UUID uuid = UUID.randomUUID(); diff --git a/pkg/cli/dump.go b/pkg/cli/dump.go index 3ac49bdb4dbe..1d13961e65d9 100644 --- a/pkg/cli/dump.go +++ b/pkg/cli/dump.go @@ -28,6 +28,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -460,6 +461,9 @@ func dumpTableData(w io.Writer, conn *sqlConn, clusterTS string, md tableMetadat switch ct { case "DATE": d = tree.NewDDateFromTime(t, time.UTC) + case "TIME": + // pq awkwardly represents TIME as a time.Time with date 0000-01-01. + d = tree.MakeDTime(timeofday.FromTime(t)) case "TIMESTAMP": d = tree.MakeDTimestamp(t, time.Nanosecond) case "TIMESTAMP WITH TIME ZONE": diff --git a/pkg/cli/dump_test.go b/pkg/cli/dump_test.go index 12f07a98e371..21a6645fd54a 100644 --- a/pkg/cli/dump_test.go +++ b/pkg/cli/dump_test.go @@ -53,7 +53,8 @@ func TestDumpRow(t *testing.T) { s string, b bytes, d date, - t timestamp, + t time, + ts timestamp, n interval, o bool, e decimal, @@ -64,7 +65,7 @@ func TestDumpRow(t *testing.T) { e1 decimal(2), e2 decimal(2, 1), s1 string(1), - FAMILY "primary" (i, f, d, t, n, o, u, ip, ary, tz, e1, e2, s1, rowid), + FAMILY "primary" (i, f, d, t, ts, n, o, u, ip, ary, tz, e1, e2, s1, rowid), FAMILY fam_1_s (s), FAMILY fam_2_b (b), FAMILY fam_3_e (e) @@ -73,7 +74,10 @@ func TestDumpRow(t *testing.T) { 1, 2.3, 'striiing', - '\x613162326333', '2016-03-26', '2016-01-25 10:10:10' , + '\x613162326333', + '2016-03-26', + '01:02:03.456', + '2016-01-25 10:10:10', '2h30m30s', true, 1.2345, @@ -109,7 +113,8 @@ CREATE TABLE t ( s STRING NULL, b BYTES NULL, d DATE NULL, - t TIMESTAMP NULL, + t TIME NULL, + ts TIMESTAMP NULL, n INTERVAL NULL, o BOOL NULL, e DECIMAL NULL, @@ -120,18 +125,18 @@ CREATE TABLE t ( e1 DECIMAL(2) NULL, e2 DECIMAL(2,1) NULL, s1 STRING(1) NULL, - FAMILY "primary" (i, f, d, t, n, o, u, ip, ary, tz, e1, e2, s1, rowid), + FAMILY "primary" (i, f, d, t, ts, n, o, u, ip, ary, tz, e1, e2, s1, rowid), FAMILY fam_1_s (s), FAMILY fam_2_b (b), FAMILY fam_3_e (e) ); -INSERT INTO t (i, f, s, b, d, t, n, o, e, u, ip, ary, tz, e1, e2, s1) VALUES - (1, 2.3, 'striiing', '\x613162326333', '2016-03-26', '2016-01-25 10:10:10+00:00', '2h30m30s', true, 1.2345, 'e9716c74-2638-443d-90ed-ffde7bea7d1d', '192.168.0.1', ARRAY['hello':::STRING,'world':::STRING], '2016-01-25 10:10:10+00:00', 3, 4.5, 's'), - (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), - (NULL, '+Inf', NULL, NULL, NULL, NULL, NULL, NULL, 'Infinity', NULL, NULL, NULL, NULL, NULL, NULL, NULL), - (NULL, '-Inf', NULL, NULL, NULL, NULL, NULL, NULL, '-Infinity', NULL, NULL, NULL, NULL, NULL, NULL, NULL), - (NULL, 'NaN', NULL, NULL, NULL, NULL, NULL, NULL, 'NaN', NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO t (i, f, s, b, d, t, ts, n, o, e, u, ip, ary, tz, e1, e2, s1) VALUES + (1, 2.3, 'striiing', '\x613162326333', '2016-03-26', '01:02:03.456', '2016-01-25 10:10:10+00:00', '2h30m30s', true, 1.2345, 'e9716c74-2638-443d-90ed-ffde7bea7d1d', '192.168.0.1', ARRAY['hello':::STRING,'world':::STRING], '2016-01-25 10:10:10+00:00', 3, 4.5, 's'), + (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), + (NULL, '+Inf', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Infinity', NULL, NULL, NULL, NULL, NULL, NULL, NULL), + (NULL, '-Inf', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '-Infinity', NULL, NULL, NULL, NULL, NULL, NULL, NULL), + (NULL, 'NaN', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'NaN', NULL, NULL, NULL, NULL, NULL, NULL, NULL); ` if out != expect { diff --git a/pkg/cmd/docgen/funcs.go b/pkg/cmd/docgen/funcs.go index 9134ba0a4e7f..18cf5f7ff32c 100644 --- a/pkg/cmd/docgen/funcs.go +++ b/pkg/cmd/docgen/funcs.go @@ -254,7 +254,7 @@ func linkTypeName(s string) string { s = strings.TrimSuffix(s, "[]") switch s { case "int", "decimal", "float", "bool", "date", "timestamp", "interval", "string", "bytes", - "inet", "uuid", "collatedstring": + "inet", "uuid", "collatedstring", "time": s = fmt.Sprintf("%s", s, name) } return s diff --git a/pkg/cmd/generate-binary/main.go b/pkg/cmd/generate-binary/main.go index f4297c489611..55723ca0b9a0 100644 --- a/pkg/cmd/generate-binary/main.go +++ b/pkg/cmd/generate-binary/main.go @@ -105,6 +105,7 @@ var defaultVals = map[string][]string{ "timestamp": timestampInputs, "timestamptz": timestampInputs, "date": dateInputs, + "time": timeInputs, "inet": inetInputs, } @@ -179,6 +180,12 @@ var dateInputs = []string{ "1996-02-29", } +var timeInputs = []string{ + "00:00:00", + "12:00:00.000001", + "23:59:59.999999", +} + var inetInputs = []string{ "0.0.0.0", "0.0.0.0/20", diff --git a/pkg/internal/rsg/rsg.go b/pkg/internal/rsg/rsg.go index 7d0f689b9261..3d0fce20c554 100644 --- a/pkg/internal/rsg/rsg.go +++ b/pkg/internal/rsg/rsg.go @@ -27,6 +27,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/duration" "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/syncutil" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uuid" ) @@ -130,6 +131,14 @@ func (r *RSG) generate(root string, depth int) []string { return ret } +// Int63n returns a random int64 in [0,n). +func (r *RSG) Int63n(n int64) int64 { + r.lock.Lock() + v := r.src.Int63n(n) + r.lock.Unlock() + return v +} + // Intn returns a random int. func (r *RSG) Intn(n int) int { r.lock.Lock() @@ -236,6 +245,10 @@ func (r *RSG) GenerateRandomArg(typ types.T) string { i -= r.Int63() d := tree.NewDDate(tree.DDate(i)) v = fmt.Sprintf(`'%s'`, d) + case types.Time: + i := r.Int63n(int64(timeofday.Max)) + d := tree.MakeDTime(timeofday.FromInt(i)) + v = fmt.Sprintf(`'%s'`, d) case types.Interval: d := duration.Duration{Nanos: r.Int63()} v = fmt.Sprintf(`'%s'`, &tree.DInterval{Duration: d}) diff --git a/pkg/sql/coltypes/aliases.go b/pkg/sql/coltypes/aliases.go index a5f19199877f..65985f6a943b 100644 --- a/pkg/sql/coltypes/aliases.go +++ b/pkg/sql/coltypes/aliases.go @@ -67,6 +67,9 @@ var ( // Date is an immutable T instance. Date = &TDate{} + // Time is an immutable T instance. + Time = &TTime{} + // Timestamp is an immutable T instance. Timestamp = &TTimestamp{} // TimestampWithTZ is an immutable T instance. diff --git a/pkg/sql/coltypes/conv.go b/pkg/sql/coltypes/conv.go index bf3c0ac29b36..04ff989a7522 100644 --- a/pkg/sql/coltypes/conv.go +++ b/pkg/sql/coltypes/conv.go @@ -90,6 +90,8 @@ func DatumTypeToColumnType(t types.T) (T, error) { return INet, nil case types.Date: return Date, nil + case types.Time: + return Time, nil case types.String: return String, nil case types.Name: @@ -142,6 +144,8 @@ func CastTargetToDatumType(t CastTargetType) types.T { return types.Bytes case *TDate: return types.Date + case *TTime: + return types.Time case *TTimestamp: return types.Timestamp case *TTimestampTZ: diff --git a/pkg/sql/coltypes/interface.go b/pkg/sql/coltypes/interface.go index c70ee323107d..da3af38d3196 100644 --- a/pkg/sql/coltypes/interface.go +++ b/pkg/sql/coltypes/interface.go @@ -52,6 +52,7 @@ func (*TInt) columnType() {} func (*TFloat) columnType() {} func (*TDecimal) columnType() {} func (*TDate) columnType() {} +func (*TTime) columnType() {} func (*TTimestamp) columnType() {} func (*TTimestampTZ) columnType() {} func (*TInterval) columnType() {} @@ -72,6 +73,7 @@ func (*TInt) castTargetType() {} func (*TFloat) castTargetType() {} func (*TDecimal) castTargetType() {} func (*TDate) castTargetType() {} +func (*TTime) castTargetType() {} func (*TTimestamp) castTargetType() {} func (*TTimestampTZ) castTargetType() {} func (*TInterval) castTargetType() {} @@ -91,6 +93,7 @@ func (node *TInt) String() string { return ColTypeAsString(node) } func (node *TFloat) String() string { return ColTypeAsString(node) } func (node *TDecimal) String() string { return ColTypeAsString(node) } func (node *TDate) String() string { return ColTypeAsString(node) } +func (node *TTime) String() string { return ColTypeAsString(node) } func (node *TTimestamp) String() string { return ColTypeAsString(node) } func (node *TTimestampTZ) String() string { return ColTypeAsString(node) } func (node *TInterval) String() string { return ColTypeAsString(node) } diff --git a/pkg/sql/coltypes/timedate.go b/pkg/sql/coltypes/timedate.go index 1593a4677513..ef19eb09b2a8 100644 --- a/pkg/sql/coltypes/timedate.go +++ b/pkg/sql/coltypes/timedate.go @@ -28,6 +28,14 @@ func (node *TDate) Format(buf *bytes.Buffer, f lex.EncodeFlags) { buf.WriteString("DATE") } +// TTime represents a TIME type. +type TTime struct{} + +// Format implements the ColTypeFormatter interface. +func (node *TTime) Format(buf *bytes.Buffer, f lex.EncodeFlags) { + buf.WriteString("TIME") +} + // TTimestamp represents a TIMESTAMP type. type TTimestamp struct{} diff --git a/pkg/sql/copy_in_test.go b/pkg/sql/copy_in_test.go index 778ed308e43a..d39da6ee1e4b 100644 --- a/pkg/sql/copy_in_test.go +++ b/pkg/sql/copy_in_test.go @@ -28,8 +28,8 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" - "github.com/cockroachdb/cockroach/pkg/util/duration" "github.com/cockroachdb/cockroach/pkg/util/leaktest" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/lib/pq" ) @@ -49,7 +49,8 @@ func TestCopyNullInfNaN(t *testing.T) { s STRING NULL, b BYTES NULL, d DATE NULL, - t TIMESTAMP NULL, + t TIME NULL, + ts TIMESTAMP NULL, n INTERVAL NULL, o BOOL NULL, e DECIMAL NULL, @@ -72,10 +73,10 @@ func TestCopyNullInfNaN(t *testing.T) { } input := [][]interface{}{ - {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, - {nil, math.Inf(1), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, - {nil, math.Inf(-1), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, - {nil, math.NaN(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, math.Inf(1), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, math.Inf(-1), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, math.NaN(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}, } for _, in := range input { @@ -142,7 +143,8 @@ func TestCopyRandom(t *testing.T) { i INT, f FLOAT, e DECIMAL, - t TIMESTAMP, + t TIME, + ts TIMESTAMP, s STRING, b BYTES, u UUID, @@ -158,17 +160,20 @@ func TestCopyRandom(t *testing.T) { t.Fatal(err) } - stmt, err := txn.Prepare(pq.CopyInSchema("d", "t", "id", "n", "o", "i", "f", "e", "t", "s", "b", "u", "ip", "tz")) + stmt, err := txn.Prepare(pq.CopyInSchema("d", "t", "id", "n", "o", "i", "f", "e", "t", "ts", "s", "b", "u", "ip", "tz")) if err != nil { t.Fatal(err) } rng := rand.New(rand.NewSource(0)) types := []sqlbase.ColumnType_SemanticType{ + sqlbase.ColumnType_INT, + sqlbase.ColumnType_INTERVAL, sqlbase.ColumnType_BOOL, sqlbase.ColumnType_INT, sqlbase.ColumnType_FLOAT, sqlbase.ColumnType_DECIMAL, + sqlbase.ColumnType_TIME, sqlbase.ColumnType_TIMESTAMP, sqlbase.ColumnType_STRING, sqlbase.ColumnType_BYTES, @@ -180,21 +185,17 @@ func TestCopyRandom(t *testing.T) { var inputs [][]interface{} for i := 0; i < 100; i++ { - row := make([]interface{}, len(types)+2) - row[0] = strconv.Itoa(i) - - sign := 1 - rng.Int63n(2)*2 - d := duration.Duration{ - Months: sign * rng.Int63n(1000), - Days: sign * rng.Int63n(1000), - Nanos: sign * rng.Int63(), - } - - row[1] = d.String() + row := make([]interface{}, len(types)) for j, t := range types { - d := sqlbase.RandDatum(rng, sqlbase.ColumnType{SemanticType: t}, false) - ds := tree.AsStringWithFlags(d, tree.FmtBareStrings) - row[j+2] = ds + var ds string + if j == 0 { + // Special handling for ID field + ds = strconv.Itoa(i) + } else { + d := sqlbase.RandDatum(rng, sqlbase.ColumnType{SemanticType: t}, false) + ds = tree.AsStringWithFlags(d, tree.FmtBareStrings) + } + row[j] = ds } _, err = stmt.Exec(row...) if err != nil { @@ -237,7 +238,12 @@ func TestCopyRandom(t *testing.T) { case []byte: ds = string(d) case time.Time: - dt := tree.MakeDTimestamp(d, time.Microsecond) + var dt tree.NodeFormatter + if types[i] == sqlbase.ColumnType_TIME { + dt = tree.MakeDTime(timeofday.FromTime(d)) + } else { + dt = tree.MakeDTimestamp(d, time.Microsecond) + } ds = tree.AsStringWithFlags(dt, tree.FmtBareStrings) } if !reflect.DeepEqual(in[i], ds) { diff --git a/pkg/sql/executor.go b/pkg/sql/executor.go index 0117901ad005..edf4ead477e6 100644 --- a/pkg/sql/executor.go +++ b/pkg/sql/executor.go @@ -2348,6 +2348,7 @@ func checkResultType(typ types.T) error { case types.Bytes: case types.String: case types.Date: + case types.Time: case types.Timestamp: case types.TimestampTZ: case types.Interval: diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 2bf23fbfdde8..e2b285140c56 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -689,9 +689,11 @@ oid typname typnamespace typowner typlen typbyval typtype 1041 _inet 1782195457 NULL -1 false b 1043 varchar 1782195457 NULL -1 false b 1082 date 1782195457 NULL 8 true b +1083 time 1782195457 NULL 8 true b 1114 timestamp 1782195457 NULL 24 true b 1115 _timestamp 1782195457 NULL -1 false b 1182 _date 1782195457 NULL -1 false b +1183 _time 1782195457 NULL -1 false b 1184 timestamptz 1782195457 NULL 24 true b 1185 _timestamptz 1782195457 NULL -1 false b 1186 interval 1782195457 NULL 24 true b @@ -741,9 +743,11 @@ oid typname typcategory typispreferred typisdefined typdelim typreli 1041 _inet A false true , 0 869 0 1043 varchar S false true , 0 0 1015 1082 date D false true , 0 0 1182 +1083 time D false true , 0 0 1183 1114 timestamp D false true , 0 0 1115 1115 _timestamp A false true , 0 1114 0 1182 _date A false true , 0 1082 0 +1183 _time A false true , 0 1083 0 1184 timestamptz D false true , 0 0 1185 1185 _timestamptz A false true , 0 1184 0 1186 interval T false true , 0 0 1187 @@ -793,9 +797,11 @@ oid typname typinput typoutput typreceive typsend 1041 _inet array_in array_out array_recv array_send 0 0 0 1043 varchar varcharin varcharout varcharrecv varcharsend 0 0 0 1082 date date_in date_out date_recv date_send 0 0 0 +1083 time time_in time_out time_recv time_send 0 0 0 1114 timestamp timestamp_in timestamp_out timestamp_recv timestamp_send 0 0 0 1115 _timestamp array_in array_out array_recv array_send 0 0 0 1182 _date array_in array_out array_recv array_send 0 0 0 +1183 _time array_in array_out array_recv array_send 0 0 0 1184 timestamptz timestamptz_in timestamptz_out timestamptz_recv timestamptz_send 0 0 0 1185 _timestamptz array_in array_out array_recv array_send 0 0 0 1186 interval interval_in interval_out interval_recv interval_send 0 0 0 @@ -845,9 +851,11 @@ oid typname typalign typstorage typnotnull typbasetype typtypmod 1041 _inet NULL NULL false 0 -1 1043 varchar NULL NULL false 0 -1 1082 date NULL NULL false 0 -1 +1083 time NULL NULL false 0 -1 1114 timestamp NULL NULL false 0 -1 1115 _timestamp NULL NULL false 0 -1 1182 _date NULL NULL false 0 -1 +1183 _time NULL NULL false 0 -1 1184 timestamptz NULL NULL false 0 -1 1185 _timestamptz NULL NULL false 0 -1 1186 interval NULL NULL false 0 -1 @@ -897,9 +905,11 @@ oid typname typndims typcollation typdefaultbin typdefault typacl 1041 _inet 0 0 NULL NULL NULL 1043 varchar 0 1661428263 NULL NULL NULL 1082 date 0 0 NULL NULL NULL +1083 time 0 0 NULL NULL NULL 1114 timestamp 0 0 NULL NULL NULL 1115 _timestamp 0 0 NULL NULL NULL 1182 _date 0 0 NULL NULL NULL +1183 _time 0 0 NULL NULL NULL 1184 timestamptz 0 0 NULL NULL NULL 1185 _timestamptz 0 0 NULL NULL NULL 1186 interval 0 0 NULL NULL NULL diff --git a/pkg/sql/logictest/testdata/logic_test/time b/pkg/sql/logictest/testdata/logic_test/time new file mode 100644 index 000000000000..c12a000ce034 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/time @@ -0,0 +1,314 @@ +# LogicTest: default parallel-stmts distsql + +# Note that the odd '0000-01-01 hh:mi:ss +0000 UTC' result format is an +# artifact of how pq displays TIMEs. + +query T +SELECT '12:00:00':::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '12:00:00.456':::TIME; +---- +0000-01-01 12:00:00.456 +0000 UTC + +query T +SELECT '00:00:00':::TIME; +---- +0000-01-01 00:00:00 +0000 UTC + +query T +SELECT '23:59:59.999999':::TIME; +---- +0000-01-01 23:59:59.999999 +0000 UTC + +statement error could not parse +SELECT '24:00:00':::TIME; + +# Timezone should be ignored. +query T +SELECT '12:00:00-08:00':::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT TIME '12:00:00'; +---- +0000-01-01 12:00:00 +0000 UTC + +# Casting + +query T +SELECT '12:00:00'::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +select '12:00:00':::STRING::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '12:00:00' COLLATE de::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '2017-01-01 12:00:00':::TIMESTAMP::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '2017-01-01 12:00:00-05':::TIMESTAMPTZ::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '12h':::INTERVAL::TIME; +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '12:00:00':::TIME::INTERVAL; +---- +12h + +query T +SELECT '12:00:00':::TIME::STRING; +---- +12:00:00 + +# Comparison + +query B +SELECT '12:00:00':::TIME = '12:00:00':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME = '12:00:00.000000':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME = '12:00:00.000001':::TIME +---- +false + +query B +SELECT '12:00:00':::TIME < '12:00:00.000001':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME < '12:00:00':::TIME +---- +false + +query B +SELECT '12:00:00':::TIME < '11:59:59.999999':::TIME +---- +false + +query B +SELECT '12:00:00':::TIME > '11:59:59.999999':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME > '12:00:00':::TIME +---- +false + +query B +SELECT '12:00:00':::TIME > '12:00:00.000001':::TIME +---- +false + +query B +SELECT '12:00:00':::TIME <= '12:00:00':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME >= '12:00:00':::TIME +---- +true + +query B +SELECT '12:00:00':::TIME IN ('12:00:00'); +---- +true + +query B +SELECT '12:00:00':::TIME IN ('00:00:00'); +---- +false + +# Arithmetic + +query T +SELECT '12:00:00':::TIME + '1s':::INTERVAL +---- +0000-01-01 12:00:01 +0000 UTC + +query T +SELECT '23:59:59':::TIME + '1s':::INTERVAL +---- +0000-01-01 00:00:00 +0000 UTC + +query T +SELECT '12:00:00':::TIME + '1d':::INTERVAL +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '1s':::INTERVAL + '12:00:00':::TIME +---- +0000-01-01 12:00:01 +0000 UTC + +query T +SELECT '12:00:00':::TIME - '1s':::INTERVAL +---- +0000-01-01 11:59:59 +0000 UTC + +query T +SELECT '00:00:00':::TIME - '1s':::INTERVAL +---- +0000-01-01 23:59:59 +0000 UTC + +query T +SELECT '12:00:00':::TIME - '1d':::INTERVAL +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT '12:00:00':::TIME - '11:59:59':::TIME +---- +1s + +query T +SELECT '11:59:59':::TIME - '12:00:00':::TIME +---- +-1s + +query T +SELECT '2017-01-01':::DATE + '12:00:00':::TIME +---- +2017-01-01 12:00:00 +0000 +0000 + +query T +SELECT '12:00:00':::TIME + '2017-01-01':::DATE +---- +2017-01-01 12:00:00 +0000 +0000 + +query T +SELECT '2017-01-01':::DATE - '12:00:00':::TIME +---- +2016-12-31 12:00:00 +0000 +0000 + +# Storage + +statement ok +CREATE TABLE times (t time PRIMARY KEY) + +statement ok +INSERT INTO times VALUES + ('00:00:00'), + ('00:00:00.000001'), + ('11:59:59.999999'), + ('12:00:00'), + ('12:00:00.000001'), + ('23:59:59.999999') + +query T +SELECT * FROM times ORDER BY t +---- +0000-01-01 00:00:00 +0000 UTC +0000-01-01 00:00:00.000001 +0000 UTC +0000-01-01 11:59:59.999999 +0000 UTC +0000-01-01 12:00:00 +0000 UTC +0000-01-01 12:00:00.000001 +0000 UTC +0000-01-01 23:59:59.999999 +0000 UTC + +statement ok +CREATE TABLE arrays (times TIME[]) + +statement ok +INSERT INTO arrays VALUES + (ARRAY[]), + (ARRAY['00:00:00']), + (ARRAY['00:00:00', '12:00:00.000001']), + ('{13:00:00}'::TIME[]) + +query T rowsort +SELECT * FROM arrays +---- +{} +{00:00:00} +{00:00:00,12:00:00.000001} +{13:00:00} + +# Built-ins + +query T +SELECT date_trunc('hour', time '12:01:02.345678') +---- +0000-01-01 12:00:00 +0000 UTC + +query T +SELECT date_trunc('minute', time '12:01:02.345678') +---- +0000-01-01 12:01:00 +0000 UTC + +query T +SELECT date_trunc('second', time '12:01:02.345678') +---- +0000-01-01 12:01:02 +0000 UTC + +query T +SELECT date_trunc('millisecond', time '12:01:02.345678') +---- +0000-01-01 12:01:02.345 +0000 UTC + +query T +SELECT date_trunc('microsecond', time '12:01:02.345678') +---- +0000-01-01 12:01:02.345678 +0000 UTC + +query error pgcode 22023 date_trunc\(\): unsupported timespan: day +SELECT date_trunc('day', time '12:01:02.345') + +query I +SELECT extract(hour from time '12:01:02.345678') +---- +12 + +query I +SELECT extract(minute from time '12:01:02.345678') +---- +1 + +query I +SELECT extract(second from time '12:01:02.345678') +---- +2 + +query I +SELECT extract(millisecond from time '12:01:02.345678') +---- +345 + +query I +SELECT extract(microsecond from time '12:01:02.345678') +---- +345678 + +query I +SELECT extract(epoch from time '12:00:00') +---- +43200 + +query error pgcode 22023 extract\(\): unsupported timespan: day +SELECT extract(day from time '12:00:00') diff --git a/pkg/sql/parser/parse.go b/pkg/sql/parser/parse.go index 9e04eec1c9c8..bdda7e115968 100644 --- a/pkg/sql/parser/parse.go +++ b/pkg/sql/parser/parse.go @@ -158,6 +158,8 @@ func ParseStringAs(t types.T, s string, evalCtx *tree.EvalContext) (tree.Datum, d, err = tree.ParseDInterval(s) case types.String: d = tree.NewDString(s) + case types.Time: + d, err = tree.ParseDTime(s) case types.Timestamp: d, err = tree.ParseDTimestamp(s, time.Microsecond) case types.TimestampTZ: diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 02ca3bc2dc28..ff2a0a610fc4 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -98,6 +98,7 @@ func TestParse(t *testing.T) { {`CREATE TABLE a (b SERIAL)`}, {`CREATE TABLE a (b SMALLSERIAL)`}, {`CREATE TABLE a (b BIGSERIAL)`}, + {`CREATE TABLE a (b TIME)`}, {`CREATE TABLE a (b UUID)`}, {`CREATE TABLE a (b INET)`}, {`CREATE TABLE a (b INT NULL)`}, @@ -509,6 +510,7 @@ func TestParse(t *testing.T) { {`SELECT REAL 'foo'`}, {`SELECT DECIMAL 'foo'`}, {`SELECT DATE 'foo'`}, + {`SELECT TIME 'foo'`}, {`SELECT TIMESTAMP 'foo'`}, {`SELECT TIMESTAMP WITH TIME ZONE 'foo'`}, {`SELECT CHAR 'foo'`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index f6898378414f..379d33ae6765 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -5083,6 +5083,14 @@ const_datetime: { $$.val = coltypes.Date } +| TIME + { + $$.val = coltypes.Time + } +| TIME WITHOUT TIME ZONE + { + $$.val = coltypes.Time + } | TIMESTAMP { $$.val = coltypes.Timestamp diff --git a/pkg/sql/pg_catalog.go b/pkg/sql/pg_catalog.go index d5dd498a46f9..50cc50a6a01a 100644 --- a/pkg/sql/pg_catalog.go +++ b/pkg/sql/pg_catalog.go @@ -1552,6 +1552,7 @@ var datumToTypeCategory = map[reflect.Type]*tree.DString{ reflect.TypeOf(types.Bool): typCategoryBoolean, reflect.TypeOf(types.Bytes): typCategoryUserDefined, reflect.TypeOf(types.Date): typCategoryDateTime, + reflect.TypeOf(types.Time): typCategoryDateTime, reflect.TypeOf(types.Float): typCategoryNumeric, reflect.TypeOf(types.Int): typCategoryNumeric, reflect.TypeOf(types.Interval): typCategoryTimespan, diff --git a/pkg/sql/pgwire/binary_test.go b/pkg/sql/pgwire/binary_test.go index 9a81278e2a62..82c63bc55e3a 100644 --- a/pkg/sql/pgwire/binary_test.go +++ b/pkg/sql/pgwire/binary_test.go @@ -128,6 +128,39 @@ func TestBinaryDate(t *testing.T) { }) } +func TestBinaryTime(t *testing.T) { + defer leaktest.AfterTest(t)() + testBinaryDatumType(t, "time", func(val string) tree.Datum { + d, err := tree.ParseDTime(val) + if err != nil { + t.Fatal(err) + } + return d + }) +} + +func TestBinaryUuid(t *testing.T) { + defer leaktest.AfterTest(t)() + testBinaryDatumType(t, "uuid", func(val string) tree.Datum { + u, err := tree.ParseDUuidFromString(val) + if err != nil { + t.Fatal(err) + } + return u + }) +} + +func TestBinaryInet(t *testing.T) { + defer leaktest.AfterTest(t)() + testBinaryDatumType(t, "inet", func(val string) tree.Datum { + ipAddr, err := tree.ParseDIPAddrFromINetString(val) + if err != nil { + t.Fatal(err) + } + return ipAddr + }) +} + func TestBinaryIntArray(t *testing.T) { defer leaktest.AfterTest(t)() buf := writeBuffer{bytecount: metric.NewCounter(metric.Metadata{})} @@ -220,25 +253,3 @@ func TestRandomBinaryDecimal(t *testing.T) { evalCtx.Stop(context.Background()) } } - -func TestBinaryUuid(t *testing.T) { - defer leaktest.AfterTest(t)() - testBinaryDatumType(t, "uuid", func(val string) tree.Datum { - u, err := tree.ParseDUuidFromString(val) - if err != nil { - t.Fatal(err) - } - return u - }) -} - -func TestBinaryInet(t *testing.T) { - defer leaktest.AfterTest(t)() - testBinaryDatumType(t, "inet", func(val string) tree.Datum { - ipAddr, err := tree.ParseDIPAddrFromINetString(val) - if err != nil { - t.Fatal(err) - } - return ipAddr - }) -} diff --git a/pkg/sql/pgwire/pgwire_test.go b/pkg/sql/pgwire/pgwire_test.go index e17bb832d4f2..c8c316053ea5 100644 --- a/pkg/sql/pgwire/pgwire_test.go +++ b/pkg/sql/pgwire/pgwire_test.go @@ -858,6 +858,9 @@ func TestPGPreparedQuery(t *testing.T) { "SELECT $1::INET": { baseTest.SetArgs("192.168.0.1/32").Results("192.168.0.1"), }, + "SELECT $1::TIME": { + baseTest.SetArgs("12:00:00").Results("0000-01-01T12:00:00Z"), + }, "SELECT $1:::FLOAT[]": { baseTest.SetArgs("{}").Results("{}"), baseTest.SetArgs("{1.0,2.0,3.0}").Results("{1.0,2.0,3.0}"), diff --git a/pkg/sql/pgwire/testdata/time_test.json b/pkg/sql/pgwire/testdata/time_test.json new file mode 100644 index 000000000000..10c5042d69c1 --- /dev/null +++ b/pkg/sql/pgwire/testdata/time_test.json @@ -0,0 +1,14 @@ +[ + { + "In": "00:00:00", + "Expect": [0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0] + }, + { + "In": "12:00:00.000001", + "Expect": [0, 0, 0, 8, 0, 0, 0, 10, 14, 235, 176, 1] + }, + { + "In": "23:59:59.999999", + "Expect": [0, 0, 0, 8, 0, 0, 0, 20, 29, 215, 95, 255] + } +] diff --git a/pkg/sql/pgwire/types.go b/pkg/sql/pgwire/types.go index 5a513f17fbf4..978f53f14daf 100644 --- a/pkg/sql/pgwire/types.go +++ b/pkg/sql/pgwire/types.go @@ -34,6 +34,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/duration" "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uint128" "github.com/lib/pq" @@ -157,6 +158,12 @@ func (b *writeBuffer) writeTextDatum(ctx context.Context, d tree.Datum, sessionL b.putInt32(int32(len(s))) b.write(s) + case *tree.DTime: + // Start at offset 4 because `putInt32` clobbers the first 4 bytes. + s := formatTime(timeofday.TimeOfDay(*v), b.putbuf[4:4]) + b.putInt32(int32(len(s))) + b.write(s) + case *tree.DTimestamp: // Start at offset 4 because `putInt32` clobbers the first 4 bytes. s := formatTs(v.Time, nil, b.putbuf[4:4]) @@ -373,6 +380,10 @@ func (b *writeBuffer) writeBinaryDatum( b.putInt32(4) b.putInt32(dateToPgBinary(v)) + case *tree.DTime: + b.putInt32(8) + b.putInt64(int64(*v)) + case *tree.DArray: if v.ParamTyp.FamilyEqual(types.AnyArray) { b.setError(errors.New("unsupported binary serialization of multidimensional arrays")) @@ -402,9 +413,17 @@ func (b *writeBuffer) writeBinaryDatum( } } -const pgTimeStampFormatNoOffset = "2006-01-02 15:04:05.999999" +const pgTimeFormat = "15:04:05.999999" +const pgTimeStampFormatNoOffset = "2006-01-02 " + pgTimeFormat const pgTimeStampFormat = pgTimeStampFormatNoOffset + "-07:00" +// formatTime formats t into a format lib/pq understands, appending to the +// provided tmp buffer and reallocating if needed. The function will then return +// the resulting buffer. +func formatTime(t timeofday.TimeOfDay, tmp []byte) []byte { + return t.ToTime().AppendFormat(tmp, pgTimeFormat) +} + // formatTs formats t with an optional offset into a format lib/pq understands, // appending to the provided tmp buffer and reallocating if needed. The function // will then return the resulting buffer. formatTs is mostly cribbed from @@ -593,6 +612,12 @@ func decodeOidDatum(id oid.Oid, code formatCode, b []byte) (tree.Datum, error) { } daysSinceEpoch := ts.Unix() / secondsInDay return tree.NewDDate(tree.DDate(daysSinceEpoch)), nil + case oid.T_time: + d, err := tree.ParseDTime(string(b)) + if err != nil { + return nil, errors.Errorf("could not parse string %q as time", b) + } + return d, nil case oid.T_interval: d, err := tree.ParseDInterval(string(b)) if err != nil { @@ -793,6 +818,12 @@ func decodeOidDatum(id oid.Oid, code formatCode, b []byte) (tree.Datum, error) { } i := int32(binary.BigEndian.Uint32(b)) return pgBinaryToDate(i), nil + case oid.T_time: + if len(b) < 8 { + return nil, errors.Errorf("time requires 8 bytes for binary format") + } + i := int64(binary.BigEndian.Uint64(b)) + return tree.MakeDTime(timeofday.TimeOfDay(i)), nil case oid.T_uuid: u, err := tree.ParseDUuidFromBytes(b) if err != nil { diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index bca9b4c38c2a..c940462deed0 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -47,6 +47,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/cockroach/pkg/util/syncutil" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uuid" "github.com/pkg/errors" @@ -1197,6 +1198,18 @@ CockroachDB supports the following flags: "Compatible elements: year, quarter, month, week, dayofweek, dayofyear,\n" + "hour, minute, second, millisecond, microsecond, epoch", }, + tree.Builtin{ + Types: tree.ArgTypes{{"element", types.String}, {"input", types.Time}}, + ReturnType: tree.FixedReturnType(types.Int), + Category: categoryDateAndTime, + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + fromTime := args[1].(*tree.DTime) + timeSpan := strings.ToLower(string(tree.MustBeDString(args[0]))) + return extractStringFromTime(fromTime, timeSpan) + }, + Info: "Extracts `element` from `input`.\n\n" + + "Compatible elements: hour, minute, second, millisecond, microsecond, epoch", + }, }, "extract_duration": { @@ -1267,6 +1280,19 @@ CockroachDB supports the following flags: "Compatible elements: year, quarter, month, week, hour, minute, second,\n" + "millisecond, microsecond.", }, + tree.Builtin{ + Types: tree.ArgTypes{{"element", types.String}, {"input", types.Time}}, + ReturnType: tree.FixedReturnType(types.Time), + Category: categoryDateAndTime, + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + timeSpan := strings.ToLower(string(tree.MustBeDString(args[0]))) + fromTime := args[1].(*tree.DTime) + return truncateTime(fromTime, timeSpan) + }, + Info: "Truncates `input` to precision `element`. Sets all fields that are less\n" + + "significant than `element` to zero.\n\n" + + "Compatible elements: hour, minute, second, millisecond, microsecond.", + }, tree.Builtin{ Types: tree.ArgTypes{{"element", types.String}, {"input", types.TimestampTZ}}, ReturnType: tree.FixedReturnType(types.TimestampTZ), @@ -2828,6 +2854,30 @@ func arrayLower(arr *tree.DArray, dim int64) tree.Datum { return arrayLower(a, dim-1) } +const microsPerMilli = 1000 + +func extractStringFromTime(fromTime *tree.DTime, timeSpan string) (tree.Datum, error) { + t := timeofday.TimeOfDay(*fromTime) + switch timeSpan { + case "hour", "hours": + return tree.NewDInt(tree.DInt(t.Hour())), nil + case "minute", "minutes": + return tree.NewDInt(tree.DInt(t.Minute())), nil + case "second", "seconds": + return tree.NewDInt(tree.DInt(t.Second())), nil + case "millisecond", "milliseconds": + return tree.NewDInt(tree.DInt(t.Microsecond() / microsPerMilli)), nil + case "microsecond", "microseconds": + return tree.NewDInt(tree.DInt(t.Microsecond())), nil + case "epoch": + seconds := time.Duration(t) * time.Microsecond / time.Second + return tree.NewDInt(tree.DInt(int64(seconds))), nil + default: + return nil, pgerror.NewErrorf( + pgerror.CodeInvalidParameterValueError, "unsupported timespan: %s", timeSpan) + } +} + func extractStringFromTimestamp( _ *tree.EvalContext, fromTime time.Time, timeSpan string, ) (tree.Datum, error) { @@ -2878,6 +2928,35 @@ func extractStringFromTimestamp( } } +func truncateTime(fromTime *tree.DTime, timeSpan string) (tree.Datum, error) { + t := timeofday.TimeOfDay(*fromTime) + hour := t.Hour() + min := t.Minute() + sec := t.Second() + micro := t.Microsecond() + + minTrunc := 0 + secTrunc := 0 + microTrunc := 0 + + switch timeSpan { + case "hour", "hours": + min, sec, micro = minTrunc, secTrunc, microTrunc + case "minute", "minutes": + sec, micro = secTrunc, microTrunc + case "second", "seconds": + micro = microTrunc + case "millisecond", "milliseconds": + // This a PG extension not supported in MySQL. + micro = (micro / microsPerMilli) * microsPerMilli + case "microsecond", "microseconds": + default: + return nil, pgerror.NewErrorf(pgerror.CodeInvalidParameterValueError, "unsupported timespan: %s", timeSpan) + } + + return tree.MakeDTime(timeofday.New(hour, min, sec, micro)), nil +} + func truncateTimestamp( _ *tree.EvalContext, fromTime time.Time, timeSpan string, ) (tree.Datum, error) { diff --git a/pkg/sql/sem/builtins/pg_builtins.go b/pkg/sql/sem/builtins/pg_builtins.go index 824e779dde15..8ebb5ed03566 100644 --- a/pkg/sql/sem/builtins/pg_builtins.go +++ b/pkg/sql/sem/builtins/pg_builtins.go @@ -37,6 +37,7 @@ const notUsableInfo = "Not usable; exposed only for compatibility with PostgreSQ var typeBuiltinsHaveUnderscore = map[oid.Oid]struct{}{ types.Any.Oid(): {}, types.Date.Oid(): {}, + types.Time.Oid(): {}, types.Decimal.Oid(): {}, types.Interval.Oid(): {}, types.JSON.Oid(): {}, diff --git a/pkg/sql/sem/tests/col_types_test.go b/pkg/sql/sem/tests/col_types_test.go index efbdcbe068da..1634648f9732 100644 --- a/pkg/sql/sem/tests/col_types_test.go +++ b/pkg/sql/sem/tests/col_types_test.go @@ -55,6 +55,7 @@ func TestParseColumnType(t *testing.T) { {"UUID", &coltypes.TUUID{}}, {"INET", &coltypes.TIPAddr{Name: "INET"}}, {"DATE", &coltypes.TDate{}}, + {"TIME", &coltypes.TTime{}}, {"TIMESTAMP", &coltypes.TTimestamp{}}, {"TIMESTAMP WITH TIME ZONE", &coltypes.TTimestampTZ{}}, {"INTERVAL", &coltypes.TInterval{}}, diff --git a/pkg/sql/sem/tests/datum_test.go b/pkg/sql/sem/tests/datum_test.go index 1e129f15b1ed..de47b1154999 100644 --- a/pkg/sql/sem/tests/datum_test.go +++ b/pkg/sql/sem/tests/datum_test.go @@ -24,6 +24,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/parser" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sem/types" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" ) func prepareExpr(t *testing.T, datumExpr string) tree.TypedExpr { @@ -97,6 +98,14 @@ func TestDatumOrdering(t *testing.T) { {`'2006-01-02 03:04:05.123123':::timestamp`, `'2006-01-02 03:04:05.123122+00:00'`, `'2006-01-02 03:04:05.123124+00:00'`, noMin, noMax}, + // Times + {`'00:00:00':::time`, valIsMin, `'00:00:00.000001'`, + `'00:00:00'`, `'23:59:59.999999'`}, + {`'12:00:00':::time`, `'11:59:59.999999'`, `'12:00:00.000001'`, + `'00:00:00'`, `'23:59:59.999999'`}, + {`'23:59:59.999999':::time`, `'23:59:59.999998'`, valIsMax, + `'00:00:00'`, `'23:59:59.999999'`}, + // Intervals {`'1 day':::interval`, noPrev, noNext, `'-768614336404564650y-8mon-9223372036854775808d-2562047h-47m-16s-854ms-775µs-808ns'`, @@ -403,6 +412,46 @@ func TestParseDDate(t *testing.T) { } } +func TestParseDTime(t *testing.T) { + // Since ParseDTime mostly delegates parsing logic to ParseDTimestamp, we only test a subset of + // the timestamp test cases. + testData := []struct { + str string + expected timeofday.TimeOfDay + }{ + {"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)}, + } + for _, td := range testData { + actual, err := tree.ParseDTime(td.str) + if err != nil { + t.Errorf("unexpected error while parsing TIME %s: %s", td.str, err) + continue + } + if *actual != tree.DTime(td.expected) { + t.Errorf("TIME %s: got %s, expected %s", td.str, actual, td.expected) + } + } +} + +func TestParseDTimeError(t *testing.T) { + testData := []string{ + "", + "foo", + "01", + "2001-02-03 04:05:06", + "24:00:00", + } + for _, s := range testData { + actual, _ := tree.ParseDTime(s) + if actual != nil { + t.Errorf("TIME %s: got %s, expected error", s, actual) + } + } +} + func TestParseDTimestamp(t *testing.T) { testData := []struct { str string diff --git a/pkg/sql/sem/tests/eval_test.go b/pkg/sql/sem/tests/eval_test.go index cbd1f3316964..6a530ac57a09 100644 --- a/pkg/sql/sem/tests/eval_test.go +++ b/pkg/sql/sem/tests/eval_test.go @@ -184,6 +184,14 @@ func TestEval(t *testing.T) { {`'2015-10-01'::date <= '2015-10-02'::date`, `true`}, {`'2015-10-01'::date > '2015-10-02'::date`, `false`}, {`'2015-10-01'::date >= '2015-10-02'::date`, `false`}, + {`'12:00:00'::time = '12:00:01'::time`, `false`}, + {`'12:00:00'::time = '12:00:00'::time`, `true`}, + {`'12:00:00.000000'::time = '12:00:00'::time`, `true`}, + {`'12:00:00'::time != '12:00:01'::time`, `true`}, + {`'12:00:00'::time < '12:00:01'::time`, `true`}, + {`'12:00:00'::time <= '12:00:01'::time`, `true`}, + {`'12:00:00'::time > '12:00:01'::time`, `false`}, + {`'12:00:00'::time >= '12:00:01'::time`, `false`}, {`'2015-10-01'::timestamp = '2015-10-02'::timestamp`, `false`}, {`'2015-10-01'::timestamp != '2015-10-02'::timestamp`, `true`}, {`'2015-10-01'::timestamp < '2015-10-02'::timestamp`, `true`}, @@ -253,6 +261,12 @@ func TestEval(t *testing.T) { {`NULL = '2015-10-01'::date`, `NULL`}, {`NULL < '2015-10-01'::date`, `NULL`}, {`NULL <= '2015-10-01'::date`, `NULL`}, + {`'12:00:00'::time = NULL`, `NULL`}, + {`'12:00:00'::time < NULL`, `NULL`}, + {`'12:00:00'::time <= NULL`, `NULL`}, + {`NULL = '12:00:00'::time`, `NULL`}, + {`NULL < '12:00:00'::time`, `NULL`}, + {`NULL <= '12:00:00'::time`, `NULL`}, {`'2015-10-01'::timestamp = NULL`, `NULL`}, {`'2015-10-01'::timestamp < NULL`, `NULL`}, {`'2015-10-01'::timestamp <= NULL`, `NULL`}, @@ -420,6 +434,7 @@ func TestEval(t *testing.T) { {`b'hello' IS OF (STRING)`, `false`}, {`b'hello' IS OF (BYTES)`, `true`}, {`'2012-09-21'::date IS OF (DATE)`, `true`}, + {`'12:00:00'::time IS OF (TIME)`, `true`}, {`'2010-09-28 12:00:00.1'::timestamp IS OF (TIMESTAMP)`, `true`}, {`'34h'::interval IS OF (INTERVAL)`, `true`}, {`'P1Y2M10DT2H29M'::interval IS OF (INTERVAL)`, `true`}, @@ -465,6 +480,7 @@ func TestEval(t *testing.T) { {`2.0 IN (2.1, 2.2, 2.3)`, `false`}, {`'a0' IN ('a'||0::char, 'b'||1::char, 'c'||2::char)`, `true`}, {`'2012-09-21'::date IN ('2012-09-21'::date)`, `true`}, + {`'12:00:00'::time IN ('12:00:00'::time)`, `true`}, {`'2010-09-28 12:00:00.1'::timestamp IN ('2010-09-28 12:00:00.1'::timestamp)`, `true`}, {`'34h'::interval IN ('34h'::interval)`, `true`}, {`(1,2) IN ((0+1,1+1), (3,4), (5,6))`, `true`}, @@ -619,6 +635,10 @@ func TestEval(t *testing.T) { {`'2010-09-28'::date::text`, `'2010-09-28'`}, {`('2010-09-28'::date)::date`, `'2010-09-28'`}, {`'2010-09-28T12:00:00Z'::date`, `'2010-09-28'`}, + {`time '12:00:00'`, `'12:00:00'`}, + {`CAST('12:00:00' AS time)`, `'12:00:00'`}, + {`'12:00:00'::time`, `'12:00:00'`}, + {`'12:00:00'::time::text`, `'12:00:00'`}, {`timestamp '2010-09-28'`, `'2010-09-28 00:00:00+00:00'`}, {`CAST('2010-09-28' AS timestamp)`, `'2010-09-28 00:00:00+00:00'`}, {`'2010-09-28'::timestamp`, `'2010-09-28 00:00:00+00:00'`}, @@ -648,6 +668,9 @@ func TestEval(t *testing.T) { {`3 + '2010-09-28'::date`, `'2010-10-01'`}, {`'2010-09-28'::date - 3`, `'2010-09-25'`}, {`'2010-09-28'::date - '2010-10-21'::date`, `-23`}, + {`'12:00:00'::time + '1s'::interval`, `'12:00:01'`}, + {`'1s'::interval + '12:00:00'::time`, `'12:00:01'`}, + {`'12:00:01'::time - '12:00:00'::time`, `'1s'`}, {`'2010-09-28 12:00:00.1-04:00'::timestamp + '12h2m'::interval`, `'2010-09-29 04:02:00.1+00:00'`}, {`'12h2m'::interval + '2010-09-28 12:00:00.1-04:00'::timestamp`, `'2010-09-29 04:02:00.1+00:00'`}, {`'12 hours 2 minutes'::interval + '2010-09-28 12:00:00.1-04:00'::timestamp`, `'2010-09-29 04:02:00.1+00:00'`}, @@ -678,6 +701,7 @@ func TestEval(t *testing.T) { {`ANNOTATE_TYPE('s', string)`, `'s'`}, {`ANNOTATE_TYPE('s', bytes)`, `'\x73'`}, {`ANNOTATE_TYPE('2010-09-28', date)`, `'2010-09-28'`}, + {`ANNOTATE_TYPE('12:00:00', time)`, `'12:00:00'`}, {`ANNOTATE_TYPE('PT12H2M', interval)`, `'12h2m'`}, {`ANNOTATE_TYPE('2 02:12', interval)`, `'2d2h12m'`}, {`ANNOTATE_TYPE('2 02:12:34', interval)`, `'2d2h12m34s'`}, @@ -706,6 +730,12 @@ func TestEval(t *testing.T) { {`extract(week from '2010-01-14'::date)`, `2`}, {`extract(dayofweek from '2010-09-28'::date)`, `2`}, {`extract(quarter from '2010-09-28'::date)`, `3`}, + // Extract from times. + {`extract(hour from '12:00:00'::time)`, `12`}, + {`extract(minute from '12:30:00'::time)`, `30`}, + {`extract(second from '12:00:30'::time)`, `30`}, + {`extract(millisecond from '12:00:00.123456'::time)`, `123`}, + {`extract(microsecond from '12:00:00.123456'::time)`, `123456`}, // Extract from timestamps. {`extract(year from '2010-09-28 12:13:14.1+00:00'::timestamp)`, `2010`}, {`extract(year from '2010-09-28 12:13:14.1+00:00'::timestamp)`, `2010`}, @@ -1060,6 +1090,7 @@ func TestEvalError(t *testing.T) { `could not parse "baz" as type decimal`}, {`'2010-09-28 12:00:00.1q'::date`, `could not parse "2010-09-28 12:00:00.1q" as type date`}, + {`'12:00:00q'::time`, `could not parse "12:00:00q" as type time`}, {`'2010-09-28 12:00.1 MST'::timestamp`, `could not parse "2010-09-28 12:00.1 MST" as type timestamp`}, {`'abcd'::interval`, diff --git a/pkg/sql/sem/tree/constant.go b/pkg/sql/sem/tree/constant.go index 1faef9546b67..457a340c9059 100644 --- a/pkg/sql/sem/tree/constant.go +++ b/pkg/sql/sem/tree/constant.go @@ -386,6 +386,7 @@ var ( types.Float, types.Decimal, types.Date, + types.Time, types.Timestamp, types.TimestampTZ, types.Interval, @@ -469,6 +470,8 @@ func (expr *StrVal) ResolveAsType(ctx *SemaContext, typ types.T) (Datum, error) return ParseDDecimal(expr.s) case types.Date: return ParseDDate(expr.s, ctx.getLocation()) + case types.Time: + return ParseDTime(expr.s) case types.INet: return ParseDIPAddrFromINetString(expr.s) case types.JSON: diff --git a/pkg/sql/sem/tree/datum.go b/pkg/sql/sem/tree/datum.go index 85f2ba5e9f03..ec8386a22c24 100644 --- a/pkg/sql/sem/tree/datum.go +++ b/pkg/sql/sem/tree/datum.go @@ -44,6 +44,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/json" "github.com/cockroachdb/cockroach/pkg/util/stringencoding" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uint128" "github.com/cockroachdb/cockroach/pkg/util/uuid" @@ -1410,6 +1411,104 @@ func (d *DDate) Size() uintptr { return unsafe.Sizeof(*d) } +// DTime is the time Datum. +type DTime timeofday.TimeOfDay + +// MakeDTime creates a DTime from a TimeOfDay. +func MakeDTime(t timeofday.TimeOfDay) *DTime { + d := DTime(t) + return &d +} + +// ParseDTime parses and returns the *DTime Datum value represented by the +// provided string, or an error if parsing is unsuccessful. +func ParseDTime(s string) (*DTime, error) { + t, err := parseTimestampInLocation("1970-01-01 "+s, time.UTC, types.Time) + if err != nil { + // Build our own error message to avoid exposing the dummy date. + return nil, makeParseError(s, types.Time, nil) + } + return MakeDTime(timeofday.FromTime(t)), nil +} + +// ResolvedType implements the TypedExpr interface. +func (*DTime) ResolvedType() types.T { + return types.Time +} + +// Compare implements the Datum interface. +func (d *DTime) Compare(ctx *EvalContext, other Datum) int { + if other == DNull { + // NULL is less than any non-NULL value. + return 1 + } + v, ok := other.(*DTime) + if !ok { + panic(makeUnsupportedComparisonMessage(d, other)) + } + if *d < *v { + return -1 + } + if *v < *d { + return 1 + } + return 0 +} + +// Prev implements the Datum interface. +func (d *DTime) Prev(_ *EvalContext) (Datum, bool) { + prev := *d - 1 + return &prev, true +} + +// Next implements the Datum interface. +func (d *DTime) Next(_ *EvalContext) (Datum, bool) { + next := *d + 1 + return &next, true +} + +var dTimeMin = MakeDTime(timeofday.Min) +var dTimeMax = MakeDTime(timeofday.Max) + +// IsMax implements the Datum interface. +func (d *DTime) IsMax(_ *EvalContext) bool { + return *d == *dTimeMax +} + +// IsMin implements the Datum interface. +func (d *DTime) IsMin(_ *EvalContext) bool { + return *d == *dTimeMin +} + +// Max implements the Datum interface. +func (d *DTime) Max(_ *EvalContext) (Datum, bool) { + return dTimeMax, true +} + +// Min implements the Datum interface. +func (d *DTime) Min(_ *EvalContext) (Datum, bool) { + return dTimeMin, true +} + +// AmbiguousFormat implements the Datum interface. +func (*DTime) AmbiguousFormat() bool { return false } + +// Format implements the NodeFormatter interface. +func (d *DTime) Format(buf *bytes.Buffer, f FmtFlags) { + if !f.encodeFlags.BareStrings { + buf.WriteByte('\'') + } + buf.WriteString(timeofday.TimeOfDay(*d).String()) + if !f.encodeFlags.BareStrings { + buf.WriteByte('\'') + } +} + +// Size implements the Datum interface. +func (d *DTime) Size() uintptr { + return unsafe.Sizeof(*d) +} + // DTimestamp is the timestamp Datum. type DTimestamp struct { time.Time @@ -2985,6 +3084,7 @@ var baseDatumTypeSizes = map[types.T]struct { types.String: {unsafe.Sizeof(DString("")), variableSize}, types.Bytes: {unsafe.Sizeof(DBytes("")), variableSize}, types.Date: {unsafe.Sizeof(DDate(0)), fixedSize}, + types.Time: {unsafe.Sizeof(DTime(0)), fixedSize}, types.Timestamp: {unsafe.Sizeof(DTimestamp{}), fixedSize}, types.TimestampTZ: {unsafe.Sizeof(DTimestampTZ{}), fixedSize}, types.Interval: {unsafe.Sizeof(DInterval{}), fixedSize}, diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go index 910c3790f40e..fa4fbe54866d 100644 --- a/pkg/sql/sem/tree/eval.go +++ b/pkg/sql/sem/tree/eval.go @@ -39,6 +39,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/cockroach/pkg/util/json" "github.com/cockroachdb/cockroach/pkg/util/mon" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uuid" ) @@ -469,6 +470,44 @@ var BinOps = map[BinaryOperator]binOpOverload{ return NewDDate(DDate(MustBeDInt(left)) + *right.(*DDate)), nil }, }, + BinOp{ + LeftType: types.Date, + RightType: types.Time, + ReturnType: types.Timestamp, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + d := MakeDTimestampTZFromDate(time.UTC, left.(*DDate)) + t := time.Duration(*right.(*DTime)) * time.Microsecond + return MakeDTimestamp(d.Add(t), time.Microsecond), nil + }, + }, + BinOp{ + LeftType: types.Time, + RightType: types.Date, + ReturnType: types.Timestamp, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + d := MakeDTimestampTZFromDate(time.UTC, right.(*DDate)) + t := time.Duration(*left.(*DTime)) * time.Microsecond + return MakeDTimestamp(d.Add(t), time.Microsecond), nil + }, + }, + BinOp{ + LeftType: types.Time, + RightType: types.Interval, + ReturnType: types.Time, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + t := timeofday.TimeOfDay(*left.(*DTime)) + return MakeDTime(t.Add(right.(*DInterval).Duration)), nil + }, + }, + BinOp{ + LeftType: types.Interval, + RightType: types.Time, + ReturnType: types.Time, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + t := timeofday.TimeOfDay(*right.(*DTime)) + return MakeDTime(t.Add(left.(*DInterval).Duration)), nil + }, + }, BinOp{ LeftType: types.Timestamp, RightType: types.Interval, @@ -611,6 +650,27 @@ var BinOps = map[BinaryOperator]binOpOverload{ return NewDInt(DInt(*left.(*DDate) - *right.(*DDate))), nil }, }, + BinOp{ + LeftType: types.Date, + RightType: types.Time, + ReturnType: types.Timestamp, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + d := MakeDTimestampTZFromDate(time.UTC, left.(*DDate)) + t := time.Duration(*right.(*DTime)) * time.Microsecond + return MakeDTimestamp(d.Add(-1*t), time.Microsecond), nil + }, + }, + BinOp{ + LeftType: types.Time, + RightType: types.Time, + ReturnType: types.Interval, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + t1 := timeofday.TimeOfDay(*left.(*DTime)) + t2 := timeofday.TimeOfDay(*right.(*DTime)) + diff := timeofday.Difference(t1, t2) + return &DInterval{Duration: diff}, nil + }, + }, BinOp{ LeftType: types.Timestamp, RightType: types.Timestamp, @@ -647,6 +707,15 @@ var BinOps = map[BinaryOperator]binOpOverload{ return &DInterval{Duration: duration.Duration{Nanos: nanos}}, nil }, }, + BinOp{ + LeftType: types.Time, + RightType: types.Interval, + ReturnType: types.Time, + fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) { + t := timeofday.TimeOfDay(*left.(*DTime)) + return MakeDTime(t.Add(right.(*DInterval).Duration.Mul(-1))), nil + }, + }, BinOp{ LeftType: types.Timestamp, RightType: types.Interval, @@ -1352,6 +1421,11 @@ var CmpOps = map[ComparisonOperator]cmpOpOverload{ RightType: types.Date, fn: cmpOpScalarEQFn, }, + CmpOp{ + LeftType: types.Time, + RightType: types.Time, + fn: cmpOpScalarEQFn, + }, CmpOp{ LeftType: types.Timestamp, RightType: types.Timestamp, @@ -1497,6 +1571,11 @@ var CmpOps = map[ComparisonOperator]cmpOpOverload{ RightType: types.Date, fn: cmpOpScalarLTFn, }, + CmpOp{ + LeftType: types.Time, + RightType: types.Time, + fn: cmpOpScalarLTFn, + }, CmpOp{ LeftType: types.Timestamp, RightType: types.Timestamp, @@ -1632,6 +1711,11 @@ var CmpOps = map[ComparisonOperator]cmpOpOverload{ RightType: types.Date, fn: cmpOpScalarLEFn, }, + CmpOp{ + LeftType: types.Time, + RightType: types.Time, + fn: cmpOpScalarLEFn, + }, CmpOp{ LeftType: types.Timestamp, RightType: types.Timestamp, @@ -1705,6 +1789,7 @@ var CmpOps = map[ComparisonOperator]cmpOpOverload{ makeEvalTupleIn(types.FamCollatedString), makeEvalTupleIn(types.Bytes), makeEvalTupleIn(types.Date), + makeEvalTupleIn(types.Time), makeEvalTupleIn(types.Timestamp), makeEvalTupleIn(types.TimestampTZ), makeEvalTupleIn(types.Interval), @@ -2611,7 +2696,7 @@ func performCast(ctx *EvalContext, d Datum, t coltypes.CastTargetType) (Datum, e switch t := d.(type) { case *DBool, *DInt, *DFloat, *DDecimal, dNull: s = d.String() - case *DTimestamp, *DTimestampTZ, *DDate: + case *DTimestamp, *DTimestampTZ, *DDate, *DTime: s = AsStringWithFlags(d, FmtBareStrings) case *DInterval: // When converting an interval to string, we need a string representation @@ -2703,6 +2788,22 @@ func performCast(ctx *EvalContext, d Datum, t coltypes.CastTargetType) (Datum, e return NewDDateFromTime(d.Time, time.UTC), nil } + case *coltypes.TTime: + switch d := d.(type) { + case *DString: + return ParseDTime(string(*d)) + case *DCollatedString: + return ParseDTime(d.Contents) + case *DTime: + return d, nil + case *DTimestamp: + return MakeDTime(timeofday.FromTime(d.Time)), nil + case *DTimestampTZ: + return MakeDTime(timeofday.FromTime(d.Time)), nil + case *DInterval: + return MakeDTime(timeofday.Min.Add(d.Duration)), nil + } + case *coltypes.TTimestamp: // TODO(knz): Timestamp from float, decimal. switch d := d.(type) { @@ -2750,6 +2851,8 @@ func performCast(ctx *EvalContext, d Datum, t coltypes.CastTargetType) (Datum, e case *DInt: // An integer duration represents a duration in microseconds. return &DInterval{Duration: duration.Duration{Nanos: int64(*v) * 1000}}, nil + case *DTime: + return &DInterval{Duration: duration.Duration{Nanos: int64(*v) * 1000}}, nil case *DInterval: return d, nil } @@ -3269,6 +3372,11 @@ func (t *DDate) Eval(_ *EvalContext) (Datum, error) { return t, nil } +// Eval implements the TypedExpr interface. +func (t *DTime) Eval(_ *EvalContext) (Datum, error) { + return t, nil +} + // Eval implements the TypedExpr interface. func (t *DFloat) Eval(_ *EvalContext) (Datum, error) { return t, nil diff --git a/pkg/sql/sem/tree/expr.go b/pkg/sql/sem/tree/expr.go index e969fcfaefd9..5bf7f599c8cc 100644 --- a/pkg/sql/sem/tree/expr.go +++ b/pkg/sql/sem/tree/expr.go @@ -1124,11 +1124,12 @@ var ( decimalCastTypes = []types.T{types.Null, types.Bool, types.Int, types.Float, types.Decimal, types.String, types.FamCollatedString, types.Timestamp, types.TimestampTZ, types.Date, types.Interval} stringCastTypes = []types.T{types.Null, types.Bool, types.Int, types.Float, types.Decimal, types.String, types.FamCollatedString, - types.Bytes, types.Timestamp, types.TimestampTZ, types.Interval, types.UUID, types.Date, types.Oid, types.INet} + types.Bytes, types.Timestamp, types.TimestampTZ, types.Interval, types.UUID, types.Date, types.Time, types.Oid, types.INet} bytesCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Bytes, types.UUID} dateCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Date, types.Timestamp, types.TimestampTZ, types.Int} + timeCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Time, types.Timestamp, types.TimestampTZ, types.Interval} timestampCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Date, types.Timestamp, types.TimestampTZ, types.Int} - intervalCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Int, types.Interval} + intervalCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Int, types.Time, types.Interval} oidCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Int, types.Oid} uuidCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.Bytes, types.UUID} inetCastTypes = []types.T{types.Null, types.String, types.FamCollatedString, types.INet} @@ -1153,6 +1154,8 @@ func validCastTypes(t types.T) []types.T { return bytesCastTypes case types.Date: return dateCastTypes + case types.Time: + return timeCastTypes case types.Timestamp, types.TimestampTZ: return timestampCastTypes case types.Interval: @@ -1273,6 +1276,7 @@ func (node *Datums) String() string { return AsString(node) } func (node *DBool) String() string { return AsString(node) } func (node *DBytes) String() string { return AsString(node) } func (node *DDate) String() string { return AsString(node) } +func (node *DTime) String() string { return AsString(node) } func (node *DDecimal) String() string { return AsString(node) } func (node *DFloat) String() string { return AsString(node) } func (node *DInt) String() string { return AsString(node) } diff --git a/pkg/sql/sem/tree/parse_array.go b/pkg/sql/sem/tree/parse_array.go index 749d42646db8..de16cd40bc62 100644 --- a/pkg/sql/sem/tree/parse_array.go +++ b/pkg/sql/sem/tree/parse_array.go @@ -169,6 +169,8 @@ func StringToColType(s string) (coltypes.T, error) { return coltypes.INet, nil case "DATE": return coltypes.Date, nil + case "TIME": + return coltypes.Time, nil case "STRING": return coltypes.String, nil case "NAME": diff --git a/pkg/sql/sem/tree/type_check.go b/pkg/sql/sem/tree/type_check.go index 5af42ab19ed2..189978e52c1e 100644 --- a/pkg/sql/sem/tree/type_check.go +++ b/pkg/sql/sem/tree/type_check.go @@ -280,7 +280,7 @@ func (expr *CastExpr) TypeCheck(ctx *SemaContext, _ types.T) (TypedExpr, error) // If the type doesn't have any possible parameters (like length, // precision), the CastExpr becomes a no-op and can be elided. switch expr.Type.(type) { - case *coltypes.TBool, *coltypes.TDate, *coltypes.TTimestamp, *coltypes.TTimestampTZ, + case *coltypes.TBool, *coltypes.TDate, *coltypes.TTime, *coltypes.TTimestamp, *coltypes.TTimestampTZ, *coltypes.TInterval, *coltypes.TBytes: return expr.Expr.TypeCheck(ctx, returnType) } @@ -914,6 +914,10 @@ func (d *DIPAddr) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { retu // identity function for Datum. func (d *DDate) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } +// TypeCheck implements the Expr interface. It is implemented as an idempotent +// identity function for Datum. +func (d *DTime) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } + // TypeCheck implements the Expr interface. It is implemented as an idempotent // identity function for Datum. func (d *DTimestamp) TypeCheck(_ *SemaContext, _ types.T) (TypedExpr, error) { return d, nil } diff --git a/pkg/sql/sem/tree/walk.go b/pkg/sql/sem/tree/walk.go index 4d4a2b0751a6..846a877ea0b4 100644 --- a/pkg/sql/sem/tree/walk.go +++ b/pkg/sql/sem/tree/walk.go @@ -522,6 +522,9 @@ func (expr *DBytes) Walk(_ Visitor) Expr { return expr } // Walk implements the Expr interface. func (expr *DDate) Walk(_ Visitor) Expr { return expr } +// Walk implements the Expr interface. +func (expr *DTime) Walk(_ Visitor) Expr { return expr } + // Walk implements the Expr interface. func (expr *DFloat) Walk(_ Visitor) Expr { return expr } diff --git a/pkg/sql/sem/types/oid.go b/pkg/sql/sem/types/oid.go index 5a044ebb9366..0365ae4abb2e 100644 --- a/pkg/sql/sem/types/oid.go +++ b/pkg/sql/sem/types/oid.go @@ -67,6 +67,8 @@ var OidToType = map[oid.Oid]T{ oid.T__bytea: TArray{Bytes}, oid.T_date: Date, oid.T__date: TArray{Date}, + oid.T_time: Time, + oid.T__time: TArray{Time}, oid.T_float4: typeFloat4, oid.T__float4: TArray{typeFloat4}, oid.T_float8: Float, @@ -134,6 +136,7 @@ var aliasedOidToName = map[oid.Oid]string{ oid.T__bool: "_bool", oid.T__bytea: "_bytea", oid.T__date: "_date", + oid.T__time: "_time", oid.T__interval: "_interval", oid.T__name: "_name", oid.T__numeric: "_numeric", @@ -160,6 +163,7 @@ var oidToArrayOid = map[oid.Oid]oid.Oid{ oid.T_inet: oid.T__inet, oid.T_varchar: oid.T__varchar, oid.T_date: oid.T__date, + oid.T_time: oid.T__time, oid.T_timestamp: oid.T__timestamp, oid.T_timestamptz: oid.T__timestamptz, oid.T_interval: oid.T__interval, diff --git a/pkg/sql/sem/types/types.go b/pkg/sql/sem/types/types.go index 00ac978f7359..5aa433869fb1 100644 --- a/pkg/sql/sem/types/types.go +++ b/pkg/sql/sem/types/types.go @@ -62,6 +62,8 @@ var ( Bytes T = tBytes{} // Date is the type of a DDate. Can be compared with ==. Date T = tDate{} + // Time is the type of a DTime. Can be compared with ==. + Time T = tTime{} // Timestamp is the type of a DTimestamp. Can be compared with ==. Timestamp T = tTimestamp{} // TimestampTZ is the type of a DTimestampTZ. Can be compared with ==. @@ -89,6 +91,7 @@ var ( String, Bytes, Date, + Time, Timestamp, TimestampTZ, Interval, @@ -228,6 +231,15 @@ func (tDate) Oid() oid.Oid { return oid.T_date } func (tDate) SQLName() string { return "date" } func (tDate) IsAmbiguous() bool { return false } +type tTime struct{} + +func (tTime) String() string { return "time" } +func (tTime) Equivalent(other T) bool { return UnwrapType(other) == Time || other == Any } +func (tTime) FamilyEqual(other T) bool { return UnwrapType(other) == Time } +func (tTime) Oid() oid.Oid { return oid.T_time } +func (tTime) SQLName() string { return "time" } +func (tTime) IsAmbiguous() bool { return false } + type tTimestamp struct{} func (tTimestamp) String() string { return "timestamp" } diff --git a/pkg/sql/sqlbase/structured.go b/pkg/sql/sqlbase/structured.go index aee70329c4e1..9d311622e286 100644 --- a/pkg/sql/sqlbase/structured.go +++ b/pkg/sql/sqlbase/structured.go @@ -1545,7 +1545,7 @@ func upperBoundColumnValueEncodedSize(col ColumnDescriptor) (int, bool) { switch col.Type.SemanticType { case ColumnType_BOOL: typ = encoding.True - case ColumnType_INT, ColumnType_DATE, ColumnType_TIMESTAMP, + case ColumnType_INT, ColumnType_DATE, ColumnType_TIME, ColumnType_TIMESTAMP, ColumnType_TIMESTAMPTZ, ColumnType_OID: typ, size = encoding.Int, int(col.Type.Width) case ColumnType_FLOAT: @@ -2198,6 +2198,8 @@ func DatumTypeToColumnSemanticType(ptyp types.T) (ColumnType_SemanticType, error return ColumnType_NAME, nil case types.Date: return ColumnType_DATE, nil + case types.Time: + return ColumnType_TIME, nil case types.Timestamp: return ColumnType_TIMESTAMP, nil case types.TimestampTZ: @@ -2266,6 +2268,8 @@ func columnSemanticTypeToDatumType(c *ColumnType, k ColumnType_SemanticType) typ return types.Bytes case ColumnType_DATE: return types.Date + case ColumnType_TIME: + return types.Time case ColumnType_TIMESTAMP: return types.Timestamp case ColumnType_TIMESTAMPTZ: diff --git a/pkg/sql/sqlbase/structured.pb.go b/pkg/sql/sqlbase/structured.pb.go index 2f3370cea85b..98904f29cadd 100644 --- a/pkg/sql/sqlbase/structured.pb.go +++ b/pkg/sql/sqlbase/structured.pb.go @@ -75,6 +75,7 @@ const ( ColumnType_UUID ColumnType_SemanticType = 14 ColumnType_ARRAY ColumnType_SemanticType = 15 ColumnType_INET ColumnType_SemanticType = 16 + ColumnType_TIME ColumnType_SemanticType = 17 ColumnType_INT2VECTOR ColumnType_SemanticType = 200 ) @@ -96,6 +97,7 @@ var ColumnType_SemanticType_name = map[int32]string{ 14: "UUID", 15: "ARRAY", 16: "INET", + 17: "TIME", 200: "INT2VECTOR", } var ColumnType_SemanticType_value = map[string]int32{ @@ -116,6 +118,7 @@ var ColumnType_SemanticType_value = map[string]int32{ "UUID": 14, "ARRAY": 15, "INET": 16, + "TIME": 17, "INT2VECTOR": 200, } @@ -6771,170 +6774,170 @@ var ( func init() { proto.RegisterFile("sql/sqlbase/structured.proto", fileDescriptorStructured) } var fileDescriptorStructured = []byte{ - // 2627 bytes of a gzipped FileDescriptorProto + // 2633 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcb, 0x73, 0xe3, 0xc6, 0xd1, 0x17, 0xf8, 0x04, 0x1b, 0x7c, 0x40, 0xb3, 0x0f, 0x73, 0x69, 0x7f, 0x92, 0x96, 0x7e, 0x7c, 0xb2, 0x63, 0x53, 0x6b, 0xad, 0x93, 0x72, 0x39, 0x89, 0x2b, 0x7c, 0x40, 0xbb, 0xd0, 0x52, 0xa4, 0x16, 0xa4, 0xb4, 0x59, 0x57, 0x12, 0x16, 0x48, 0x8c, 0x24, 0x78, 0x41, 0x80, 0x0b, 0x80, 0xb2, 0x78, 0xcb, 0x29, 0xe5, 0x63, 0x2a, 0x39, 0xe4, 0xe6, 0x72, 0xf9, 0x9a, 0x6b, 0x0e, 0x39, 0xe4, - 0x0f, 0xd8, 0x63, 0x8e, 0x39, 0xa9, 0x12, 0xe5, 0x92, 0x9c, 0x53, 0x95, 0xc3, 0x9e, 0x52, 0x33, - 0x98, 0x01, 0x41, 0x51, 0x94, 0xa5, 0xdd, 0x1b, 0xd0, 0x3d, 0xfd, 0x43, 0x77, 0x4f, 0xf7, 0x6f, - 0x7a, 0x00, 0x6f, 0x79, 0xcf, 0xad, 0x0d, 0xef, 0xb9, 0xd5, 0xd7, 0x3d, 0xbc, 0xe1, 0xf9, 0xee, - 0x78, 0xe0, 0x8f, 0x5d, 0x6c, 0x54, 0x46, 0xae, 0xe3, 0x3b, 0xe8, 0xd6, 0xc0, 0x19, 0x3c, 0x73, - 0x1d, 0x7d, 0x70, 0x54, 0xf1, 0x9e, 0x5b, 0x15, 0xb6, 0xae, 0x54, 0x1c, 0xfb, 0xa6, 0xb5, 0x71, - 0x64, 0x0d, 0x36, 0x7c, 0x73, 0x88, 0x3d, 0x5f, 0x1f, 0x8e, 0x02, 0x83, 0xd2, 0x9b, 0x51, 0xb8, - 0x91, 0x6b, 0x1e, 0x9b, 0x16, 0x3e, 0xc4, 0x4c, 0x79, 0xf3, 0xd0, 0x39, 0x74, 0xe8, 0xe3, 0x06, - 0x79, 0x0a, 0xa4, 0xe5, 0xff, 0x24, 0x01, 0xea, 0x8e, 0x35, 0x1e, 0xda, 0xdd, 0xc9, 0x08, 0xa3, - 0xa7, 0x90, 0xf3, 0xf0, 0x50, 0xb7, 0x7d, 0x73, 0xd0, 0xf3, 0x27, 0x23, 0x5c, 0x14, 0xd6, 0x84, - 0xf5, 0xfc, 0x66, 0xa5, 0x72, 0xa1, 0x2b, 0x95, 0xa9, 0x65, 0xa5, 0xc3, 0xcc, 0xc8, 0x4b, 0x2d, - 0xf1, 0xe2, 0x74, 0x75, 0x49, 0xcb, 0x7a, 0x11, 0x19, 0x2a, 0x41, 0xf2, 0x2b, 0xd3, 0xf0, 0x8f, - 0x8a, 0xb1, 0x35, 0x61, 0x3d, 0xc9, 0x96, 0x04, 0x22, 0x54, 0x86, 0xcc, 0xc8, 0xc5, 0x03, 0xd3, - 0x33, 0x1d, 0xbb, 0x18, 0x8f, 0xe8, 0xa7, 0x62, 0xf4, 0x3e, 0xc8, 0xba, 0xeb, 0xea, 0x93, 0x9e, - 0x61, 0x0e, 0xb1, 0x4d, 0x44, 0x5e, 0x31, 0xb1, 0x16, 0x5f, 0x4f, 0x6a, 0x05, 0x2a, 0x6f, 0x84, - 0x62, 0x74, 0x1b, 0x52, 0x96, 0x33, 0xd0, 0x2d, 0x5c, 0x4c, 0xae, 0x09, 0xeb, 0x19, 0x8d, 0xbd, - 0xa1, 0x7d, 0xc8, 0x1e, 0x9b, 0x9e, 0xd9, 0xb7, 0x70, 0x10, 0x5c, 0x8a, 0x06, 0xf7, 0xd1, 0xf7, - 0x07, 0xb7, 0x1f, 0x58, 0x45, 0x62, 0x93, 0x8e, 0xa7, 0x22, 0xb4, 0x07, 0xf9, 0xc0, 0xb5, 0x81, - 0x63, 0xfb, 0xd8, 0xf6, 0xbd, 0x62, 0xfa, 0x55, 0xd2, 0xa6, 0xe5, 0x28, 0x4a, 0x9d, 0x81, 0x94, - 0xff, 0x2d, 0x40, 0x36, 0xaa, 0x47, 0x22, 0x24, 0x6a, 0xed, 0x76, 0x53, 0x5e, 0x42, 0x69, 0x88, - 0xab, 0xad, 0xae, 0x2c, 0xa0, 0x0c, 0x24, 0xb7, 0x9a, 0xed, 0x6a, 0x57, 0x8e, 0x21, 0x09, 0xd2, - 0x0d, 0xa5, 0xae, 0xee, 0x54, 0x9b, 0x72, 0x9c, 0x2c, 0x6d, 0x54, 0xbb, 0x8a, 0x9c, 0x40, 0x39, - 0xc8, 0x74, 0xd5, 0x1d, 0xa5, 0xd3, 0xad, 0xee, 0xec, 0xca, 0x49, 0x94, 0x05, 0x51, 0x6d, 0x75, - 0x15, 0x6d, 0xbf, 0xda, 0x94, 0x53, 0x08, 0x20, 0xd5, 0xe9, 0x6a, 0x6a, 0xeb, 0x81, 0x9c, 0x26, - 0x50, 0xb5, 0xa7, 0x5d, 0xa5, 0x23, 0x8b, 0xa8, 0x00, 0x52, 0x68, 0xd3, 0xfd, 0x42, 0xce, 0x20, - 0x04, 0xf9, 0x7a, 0xbb, 0xd9, 0xac, 0x76, 0x95, 0x06, 0x5b, 0x0f, 0xe4, 0x13, 0xad, 0xea, 0x8e, - 0x22, 0x4b, 0xc4, 0x9b, 0xb6, 0xda, 0x90, 0xb3, 0x54, 0xb4, 0xd7, 0x6c, 0xca, 0x39, 0xf2, 0xb4, - 0xb7, 0xa7, 0x36, 0xe4, 0x3c, 0x81, 0xad, 0x6a, 0x5a, 0xf5, 0xa9, 0x5c, 0x20, 0x42, 0xb5, 0xa5, - 0x74, 0x65, 0x19, 0x15, 0x00, 0xd4, 0x56, 0x77, 0x73, 0x5f, 0xa9, 0x77, 0xdb, 0x9a, 0xfc, 0x42, - 0x28, 0x1f, 0x80, 0x14, 0x49, 0x32, 0x05, 0x6a, 0xb7, 0x14, 0x79, 0x89, 0x44, 0x45, 0xfc, 0x7d, - 0xa0, 0x68, 0xb2, 0x40, 0x9c, 0xef, 0xec, 0x54, 0x9b, 0x4d, 0x12, 0x7b, 0x8c, 0x38, 0x5f, 0x53, - 0x1f, 0x90, 0xe7, 0x38, 0x71, 0xa1, 0xa6, 0x76, 0xe5, 0x04, 0xb1, 0xd4, 0x94, 0x6a, 0x53, 0x4e, - 0xa2, 0x1b, 0x50, 0x68, 0xb4, 0xf7, 0x6a, 0x4d, 0xa5, 0xb7, 0xab, 0x29, 0x75, 0xb5, 0xd3, 0x6e, - 0xc9, 0xa9, 0xcf, 0x12, 0xff, 0xfa, 0x76, 0x55, 0x28, 0xff, 0x37, 0x0e, 0x37, 0xb6, 0x1c, 0x17, - 0x9b, 0x87, 0xf6, 0x23, 0x3c, 0xd1, 0xf0, 0x01, 0x76, 0xb1, 0x3d, 0xc0, 0x68, 0x0d, 0x92, 0xbe, - 0xde, 0xb7, 0x82, 0xb2, 0xcf, 0xd5, 0x80, 0x6c, 0xf5, 0xcb, 0xd3, 0xd5, 0x98, 0xda, 0xd0, 0x02, - 0x05, 0x7a, 0x17, 0x92, 0xa6, 0x6d, 0xe0, 0x13, 0x5a, 0xc5, 0xb9, 0x5a, 0x81, 0xad, 0x48, 0xab, - 0x44, 0x48, 0x96, 0x51, 0x2d, 0x2a, 0x42, 0xc2, 0xd6, 0x87, 0x98, 0xd6, 0x72, 0x86, 0x95, 0x0c, - 0x95, 0xa0, 0x47, 0x20, 0x1e, 0xeb, 0x96, 0x69, 0x98, 0xfe, 0xa4, 0x98, 0xa0, 0x55, 0xf2, 0xfe, - 0xc2, 0x2a, 0xb1, 0x3d, 0xdf, 0xd5, 0x4d, 0xdb, 0xdf, 0x67, 0x06, 0x0c, 0x28, 0x04, 0x40, 0xf7, - 0x60, 0xd9, 0x3b, 0xd2, 0x5d, 0x6c, 0xf4, 0x46, 0x2e, 0x3e, 0x30, 0x4f, 0x7a, 0x16, 0xb6, 0x69, - 0xcd, 0xf3, 0xfe, 0x29, 0x04, 0xea, 0x5d, 0xaa, 0x6d, 0x62, 0x1b, 0x75, 0x21, 0xe3, 0xd8, 0x3d, - 0x03, 0x5b, 0xd8, 0xe7, 0xf5, 0xff, 0xf1, 0x82, 0xef, 0x5f, 0x90, 0xa0, 0x4a, 0x75, 0xe0, 0x9b, - 0x8e, 0xcd, 0xfd, 0x70, 0xec, 0x06, 0x05, 0x62, 0xa8, 0xe3, 0x91, 0xa1, 0xfb, 0x98, 0xd5, 0xfe, - 0xeb, 0xa0, 0xee, 0x51, 0xa0, 0xf2, 0x63, 0x48, 0x05, 0x1a, 0x52, 0xc3, 0xad, 0x76, 0xaf, 0x5a, - 0xef, 0xaa, 0xed, 0x96, 0xbc, 0x44, 0xca, 0x40, 0x53, 0x48, 0x1d, 0xd6, 0xbb, 0xac, 0x28, 0x94, - 0x6e, 0x8f, 0x16, 0x5e, 0x8c, 0x94, 0x2e, 0x79, 0x6b, 0x28, 0x5b, 0xd5, 0xbd, 0x26, 0xa9, 0x0c, - 0x09, 0xd2, 0xf5, 0x6a, 0xa7, 0x5e, 0x6d, 0x28, 0x72, 0xa2, 0xfc, 0x9b, 0x18, 0xc8, 0x41, 0xf7, - 0x35, 0xb0, 0x37, 0x70, 0xcd, 0x91, 0xef, 0xb8, 0xe1, 0x66, 0x09, 0x73, 0x9b, 0xf5, 0x1e, 0xc4, - 0x4c, 0x83, 0x6d, 0xf5, 0x6d, 0x22, 0x3f, 0xa3, 0xc5, 0xf0, 0xf2, 0x74, 0x55, 0x0c, 0x50, 0xd4, - 0x86, 0x16, 0x33, 0x0d, 0xf4, 0x63, 0x48, 0x50, 0x42, 0x21, 0xdb, 0x2d, 0x6d, 0xde, 0xfd, 0xde, - 0xb6, 0xe7, 0x1f, 0x21, 0x46, 0x68, 0x0d, 0x44, 0x7b, 0x6c, 0x59, 0xb4, 0xee, 0x48, 0x45, 0x88, - 0x3c, 0x11, 0x5c, 0x8a, 0xee, 0x42, 0xd6, 0xc0, 0x07, 0xfa, 0xd8, 0xf2, 0x7b, 0xf8, 0x64, 0xe4, - 0x32, 0x56, 0x93, 0x98, 0x4c, 0x39, 0x19, 0xb9, 0xe8, 0x2d, 0x48, 0x1d, 0x99, 0x86, 0x81, 0x6d, - 0xba, 0xa9, 0x1c, 0x82, 0xc9, 0xb6, 0x13, 0xa2, 0x28, 0x67, 0xb6, 0x13, 0x62, 0x46, 0x86, 0xed, - 0x84, 0x98, 0x96, 0xc5, 0xf2, 0xd7, 0x31, 0xb8, 0x1d, 0xf8, 0xb3, 0xa5, 0x0f, 0x4d, 0x6b, 0xf2, - 0xba, 0xe9, 0x08, 0x50, 0x58, 0x3a, 0xee, 0x42, 0x76, 0x40, 0xb1, 0x7b, 0xc4, 0xcc, 0x2b, 0xc6, - 0xd7, 0xe2, 0xc4, 0xdf, 0x40, 0xd6, 0x22, 0x22, 0xf4, 0x29, 0x00, 0x5b, 0x62, 0x1a, 0x01, 0x8f, - 0xe7, 0x6a, 0x77, 0xce, 0x4e, 0x57, 0x33, 0x3c, 0xaf, 0xde, 0x4c, 0x92, 0x33, 0xc1, 0x62, 0xd5, - 0xf0, 0x50, 0x1b, 0x96, 0x79, 0x32, 0x42, 0x04, 0x9a, 0x91, 0x5c, 0xed, 0x6d, 0xe6, 0x53, 0xa1, - 0x11, 0x2c, 0xe0, 0xe6, 0x33, 0x50, 0x05, 0x63, 0x46, 0x69, 0x94, 0xff, 0x18, 0x83, 0x9b, 0xaa, - 0xed, 0x63, 0xd7, 0xc2, 0xfa, 0x31, 0x8e, 0x24, 0xe2, 0xe7, 0x90, 0xd1, 0xed, 0x01, 0xf6, 0x7c, - 0xc7, 0xf5, 0x8a, 0xc2, 0x5a, 0x7c, 0x5d, 0xda, 0xfc, 0x64, 0xc1, 0xd6, 0x5e, 0x64, 0x5f, 0xa9, - 0x32, 0x63, 0x7e, 0x96, 0x85, 0x60, 0xa5, 0x3f, 0x0b, 0x20, 0x72, 0x2d, 0xba, 0x07, 0x22, 0xe5, - 0x16, 0x12, 0x47, 0xc0, 0x3b, 0xb7, 0x58, 0x1c, 0xe9, 0x2e, 0x91, 0x53, 0xff, 0x09, 0x05, 0xa5, - 0xe9, 0x32, 0xd5, 0x40, 0x3f, 0x04, 0x91, 0xd2, 0x4c, 0x2f, 0xdc, 0x8d, 0x12, 0xb7, 0x60, 0x3c, - 0x14, 0xa5, 0xa4, 0x34, 0x5d, 0xab, 0x1a, 0xa8, 0x7e, 0x11, 0x5b, 0xc4, 0xa9, 0xfd, 0x1b, 0x3c, - 0x73, 0x9d, 0x59, 0xbe, 0x98, 0x23, 0x90, 0xf2, 0x5f, 0xe2, 0x70, 0x7b, 0x57, 0x77, 0x7d, 0x93, - 0x34, 0xa6, 0x69, 0x1f, 0x46, 0xf2, 0xf5, 0x2e, 0x48, 0xf6, 0x78, 0xc8, 0x76, 0xc5, 0x63, 0xb1, - 0x04, 0xb1, 0x83, 0x3d, 0x1e, 0x06, 0x09, 0xf7, 0x50, 0x13, 0x12, 0x96, 0xe9, 0xf9, 0xc5, 0x18, - 0xcd, 0xe8, 0xe6, 0x82, 0x8c, 0x5e, 0xfc, 0x8d, 0x4a, 0xd3, 0xf4, 0x7c, 0x5e, 0x93, 0x04, 0x05, - 0xb5, 0x21, 0xe9, 0xea, 0xf6, 0x21, 0xa6, 0x45, 0x26, 0x6d, 0xde, 0xbf, 0x1e, 0x9c, 0x46, 0x4c, - 0xf9, 0x2c, 0x42, 0x71, 0x4a, 0x7f, 0x10, 0x20, 0x41, 0xbe, 0x72, 0x49, 0x1f, 0xdc, 0x86, 0xd4, - 0xb1, 0x6e, 0x8d, 0xb1, 0x47, 0x63, 0xc8, 0x6a, 0xec, 0x0d, 0xfd, 0x12, 0x0a, 0xde, 0xb8, 0x3f, - 0x8a, 0x7c, 0x8a, 0x31, 0xc2, 0x47, 0xd7, 0xf2, 0x2a, 0xe4, 0xee, 0x59, 0xac, 0x52, 0x0d, 0x92, - 0xd4, 0xdf, 0x4b, 0x3c, 0x5b, 0x05, 0x69, 0x3c, 0x1a, 0x61, 0xb7, 0xd7, 0x77, 0xc6, 0x76, 0x50, - 0x1c, 0x59, 0x0d, 0xa8, 0xa8, 0x46, 0x24, 0xe5, 0xdf, 0x8b, 0x50, 0xa0, 0x85, 0x71, 0xa5, 0x86, - 0x7f, 0x37, 0xd2, 0xf0, 0xb7, 0x66, 0x1a, 0x3e, 0xac, 0x2e, 0xd2, 0xef, 0x6f, 0x41, 0x6a, 0x6c, - 0x9b, 0xcf, 0xc7, 0x01, 0x01, 0x86, 0xe4, 0x13, 0xc8, 0xe6, 0xd8, 0x20, 0x31, 0xcf, 0x06, 0x1f, - 0x02, 0x22, 0xad, 0x80, 0x7b, 0x33, 0x0b, 0x93, 0x74, 0xa1, 0x4c, 0x35, 0xf5, 0x85, 0xdc, 0x91, - 0xba, 0x06, 0x77, 0x3c, 0x04, 0x19, 0x9f, 0xf8, 0xae, 0xde, 0x8b, 0xd8, 0xa7, 0xa9, 0xfd, 0xca, - 0xd9, 0xe9, 0x6a, 0x5e, 0x21, 0xba, 0x8b, 0x41, 0xf2, 0x38, 0xa2, 0x33, 0xc8, 0x56, 0x2f, 0x33, - 0x0c, 0xc3, 0x74, 0x31, 0x3d, 0xa5, 0xbc, 0xa2, 0xb8, 0x16, 0x5f, 0xcf, 0x6f, 0xde, 0x5b, 0xc8, - 0x11, 0x33, 0x69, 0xaf, 0x34, 0xb8, 0xa1, 0x26, 0x07, 0x50, 0xa1, 0xc0, 0x43, 0x8f, 0x41, 0x3a, - 0x08, 0x0e, 0xca, 0xde, 0x33, 0x3c, 0x29, 0x66, 0x68, 0x15, 0x7d, 0x70, 0xf5, 0x23, 0x95, 0xb7, - 0xdd, 0x41, 0xa8, 0x42, 0x7b, 0x90, 0x73, 0xb9, 0xda, 0xe8, 0xf5, 0x27, 0x45, 0xa0, 0x0d, 0x73, - 0x7d, 0xd0, 0xec, 0x14, 0xa6, 0x36, 0x41, 0x8f, 0x01, 0xcc, 0x90, 0xfc, 0x8a, 0x12, 0x75, 0xf4, - 0x07, 0xd7, 0x60, 0x49, 0xee, 0xe9, 0x14, 0x04, 0x3d, 0x81, 0xfc, 0xf4, 0x8d, 0xba, 0x9a, 0x7d, - 0x45, 0x57, 0x73, 0x11, 0x9c, 0xda, 0x04, 0x75, 0xe1, 0xe6, 0xc0, 0x19, 0x8e, 0x1c, 0xcf, 0xf4, - 0x71, 0xb4, 0x04, 0x72, 0xb4, 0x04, 0xca, 0x67, 0xa7, 0xab, 0xa8, 0xce, 0xf5, 0x17, 0x97, 0x01, - 0x1a, 0x9c, 0xd3, 0x07, 0x45, 0x35, 0x53, 0xbc, 0x04, 0x31, 0x3f, 0x2d, 0xaa, 0xce, 0xb4, 0x7c, - 0xe7, 0x8a, 0x2a, 0x52, 0xda, 0x04, 0xe9, 0x09, 0x64, 0x67, 0xc8, 0xa3, 0xf0, 0xea, 0xe4, 0x31, - 0x03, 0x54, 0x5e, 0x81, 0x4c, 0x58, 0x5c, 0x64, 0x54, 0xae, 0x76, 0xea, 0xf2, 0x12, 0xbd, 0x23, - 0x28, 0x9d, 0xba, 0x2c, 0x94, 0x7f, 0x9d, 0x00, 0x34, 0x85, 0xd8, 0x19, 0xfb, 0x3a, 0x5d, 0x59, - 0x85, 0x54, 0x10, 0x13, 0xa5, 0x06, 0x69, 0xf3, 0xff, 0x2f, 0x1d, 0x6c, 0xa6, 0x00, 0x0f, 0x97, - 0x34, 0x66, 0x88, 0x3e, 0x8f, 0xce, 0xcb, 0xd2, 0xe6, 0x7b, 0x57, 0xeb, 0x8d, 0x87, 0x4b, 0x7c, - 0x90, 0x7e, 0x04, 0x49, 0xcf, 0x27, 0x53, 0x65, 0x9c, 0x4e, 0x95, 0x1b, 0x0b, 0xec, 0xe7, 0x9d, - 0xaf, 0x74, 0x88, 0x19, 0xa7, 0x76, 0x8a, 0x81, 0x9e, 0x40, 0x26, 0xec, 0x56, 0x36, 0x7c, 0xdf, - 0xbf, 0x3a, 0x60, 0x98, 0x41, 0x7e, 0x9e, 0x87, 0x58, 0xa8, 0x0a, 0xd2, 0x90, 0x2d, 0x9b, 0x4e, - 0x23, 0x6b, 0x8c, 0x30, 0x81, 0x23, 0x50, 0xe2, 0x8c, 0xbc, 0x69, 0xc0, 0x8d, 0x54, 0xa3, 0xfc, - 0x33, 0x48, 0x52, 0x8f, 0xc9, 0xbc, 0xba, 0xd7, 0x7a, 0xd4, 0x6a, 0x3f, 0x21, 0x93, 0x6e, 0x01, - 0xa4, 0x86, 0xd2, 0x54, 0xba, 0x4a, 0xaf, 0xdd, 0x6a, 0x3e, 0x95, 0x05, 0x74, 0x07, 0x6e, 0x31, - 0x41, 0xb5, 0xd5, 0xe8, 0x3d, 0xd1, 0x54, 0xae, 0x8a, 0x95, 0xd7, 0xa3, 0x9b, 0x3c, 0xbd, 0x40, - 0x91, 0xed, 0x6e, 0x34, 0x64, 0x81, 0x6e, 0xb7, 0xd6, 0xde, 0x95, 0x63, 0xb5, 0x2c, 0x80, 0x11, - 0xc6, 0xb7, 0x9d, 0x10, 0x53, 0x72, 0xba, 0xfc, 0xa7, 0x1b, 0x50, 0xa0, 0xe3, 0xc6, 0x95, 0x0e, - 0x86, 0x35, 0x7a, 0x30, 0x04, 0xb3, 0x83, 0x3c, 0x73, 0x30, 0xc4, 0xd8, 0x99, 0x70, 0x1f, 0x32, - 0x23, 0xdd, 0xc5, 0xb6, 0x4f, 0x12, 0x92, 0x98, 0x19, 0x19, 0xc5, 0x5d, 0xaa, 0x08, 0x97, 0x8b, - 0xc1, 0x42, 0x95, 0x18, 0xa5, 0x8f, 0xb1, 0x4b, 0xff, 0x02, 0x04, 0x39, 0xbc, 0xc3, 0xee, 0x57, - 0xcb, 0x53, 0xaf, 0xf6, 0x83, 0x05, 0x1a, 0x5f, 0x89, 0xde, 0x06, 0x18, 0x8f, 0x7a, 0xdc, 0x2e, - 0x3a, 0xfe, 0x66, 0xc6, 0x23, 0xb6, 0x1a, 0xed, 0xc2, 0xf2, 0xd0, 0x31, 0xcc, 0x03, 0x73, 0x10, - 0xec, 0x92, 0x6f, 0x0e, 0x83, 0x9b, 0x8a, 0xb4, 0xf9, 0x7f, 0x91, 0x12, 0x18, 0xfb, 0xa6, 0x55, - 0x39, 0xb2, 0x06, 0x95, 0x2e, 0xff, 0xb5, 0xc2, 0xa0, 0xe4, 0xa8, 0x35, 0x51, 0xa2, 0x07, 0x90, - 0xe6, 0x93, 0x8e, 0x48, 0xe9, 0xe9, 0xaa, 0xdd, 0xc1, 0x10, 0xb9, 0x35, 0xda, 0x82, 0xbc, 0x8d, - 0x4f, 0xa2, 0xd3, 0x6c, 0x66, 0xa6, 0x7e, 0xb2, 0x2d, 0x7c, 0x72, 0xf1, 0x28, 0x9b, 0xb5, 0xa7, - 0x1a, 0x03, 0x3d, 0x86, 0xdc, 0xc8, 0x35, 0x87, 0xba, 0x3b, 0xe9, 0x05, 0x2d, 0x07, 0xd7, 0x69, - 0xb9, 0x90, 0x37, 0x02, 0x08, 0xaa, 0x45, 0x5b, 0x10, 0x0c, 0x8f, 0xd8, 0x2b, 0x4a, 0x34, 0xc6, - 0xeb, 0x81, 0x71, 0x63, 0x54, 0x83, 0x1c, 0x0d, 0x31, 0x9c, 0x5a, 0xb3, 0x34, 0xc2, 0x15, 0x16, - 0xa1, 0x44, 0x22, 0xbc, 0x60, 0x72, 0x95, 0xec, 0x50, 0x6e, 0xa0, 0x6d, 0x80, 0xf0, 0x97, 0x16, - 0xa1, 0xec, 0xcb, 0x4e, 0xc4, 0x5d, 0xbe, 0x70, 0xea, 0x92, 0x16, 0xb1, 0x46, 0x3b, 0x90, 0xe1, - 0xad, 0x17, 0x70, 0xb5, 0xb4, 0xf0, 0x16, 0x3e, 0x4f, 0x04, 0xbc, 0xb8, 0x42, 0x04, 0xd4, 0x82, - 0xa4, 0x85, 0x75, 0x0f, 0x33, 0xc2, 0xfe, 0x74, 0x01, 0xd4, 0xb9, 0xf6, 0xaa, 0x74, 0x06, 0x47, - 0x78, 0xa8, 0xd7, 0x8f, 0xc8, 0x4c, 0xd7, 0x24, 0xf6, 0x5a, 0x00, 0x83, 0x5a, 0x20, 0xd3, 0x74, - 0x45, 0x39, 0x45, 0xa6, 0x19, 0x7b, 0x87, 0x65, 0x2c, 0x4f, 0x32, 0xb6, 0x90, 0x57, 0x68, 0x3d, - 0x85, 0xef, 0x06, 0xfa, 0x09, 0xe4, 0x0f, 0x1c, 0x77, 0xa8, 0xfb, 0x61, 0x97, 0x2c, 0x4f, 0x47, - 0xba, 0x97, 0xa7, 0xab, 0xb9, 0x2d, 0xaa, 0xe5, 0x9d, 0x95, 0x3b, 0x88, 0xbe, 0xa2, 0x87, 0x9c, - 0x82, 0x6f, 0x50, 0xc6, 0xfc, 0xf0, 0xaa, 0xd1, 0xcd, 0xf3, 0x6f, 0x0b, 0x52, 0x83, 0x23, 0x3c, - 0x78, 0xe6, 0x15, 0x6f, 0xd2, 0x9c, 0xff, 0xe8, 0x8a, 0x50, 0x75, 0x62, 0x34, 0xfd, 0x1d, 0xa2, - 0x31, 0x14, 0xf4, 0x18, 0xd2, 0x2e, 0x0e, 0x66, 0xc5, 0x5b, 0x14, 0xf0, 0xe3, 0x2b, 0x02, 0x6a, - 0xd4, 0x4a, 0xb5, 0x0f, 0x1c, 0x5e, 0xa9, 0x0c, 0x07, 0xb5, 0x41, 0x3c, 0x20, 0x57, 0x59, 0x13, - 0x7b, 0xc5, 0xdb, 0x14, 0xf3, 0xf2, 0xdf, 0x83, 0xe7, 0x6f, 0xcf, 0xfc, 0xee, 0xce, 0x41, 0xc2, - 0xee, 0xa6, 0x82, 0x09, 0xd9, 0xc9, 0x37, 0xe6, 0xbb, 0x9b, 0xdf, 0x9e, 0x67, 0x6e, 0xd2, 0xb4, - 0xbb, 0xd9, 0x9b, 0x41, 0x58, 0xee, 0xd8, 0xc4, 0x5f, 0xf5, 0x9e, 0x8f, 0xb1, 0x3b, 0x29, 0x16, - 0x23, 0x8c, 0x9c, 0x21, 0xf2, 0xc7, 0x44, 0x8c, 0x3e, 0x86, 0x8c, 0x81, 0x47, 0xd8, 0x36, 0xbc, - 0xb6, 0x5d, 0xbc, 0x43, 0x67, 0x90, 0x1b, 0x64, 0x30, 0x6e, 0x70, 0x21, 0x63, 0xdc, 0xe9, 0x2a, - 0xf4, 0x25, 0x64, 0x83, 0x17, 0x6c, 0xb4, 0xed, 0xda, 0xa4, 0x58, 0xa2, 0x41, 0xdf, 0xbb, 0x72, - 0x22, 0xf9, 0xc0, 0x75, 0x93, 0xc7, 0xd3, 0x88, 0xa0, 0x69, 0x33, 0xd8, 0xe8, 0x17, 0x90, 0xe5, - 0x25, 0xbd, 0xed, 0xf4, 0xbd, 0xe2, 0x9b, 0x97, 0xde, 0x00, 0xcf, 0x7f, 0x6b, 0x67, 0x6a, 0xca, - 0xc9, 0x2a, 0x8a, 0x56, 0xfa, 0x4e, 0x80, 0xe5, 0xb9, 0x96, 0x42, 0xbf, 0x82, 0xb4, 0xed, 0x18, - 0x91, 0xcb, 0xb5, 0xc2, 0x1c, 0x4d, 0xb5, 0x1c, 0x23, 0xb8, 0x5b, 0xdf, 0x3f, 0x34, 0xfd, 0xa3, - 0x71, 0xbf, 0x32, 0x70, 0x86, 0x1b, 0xa1, 0x2b, 0x46, 0x7f, 0xfa, 0xbc, 0x31, 0x7a, 0x76, 0xb8, - 0x41, 0x9f, 0x46, 0xfd, 0x4a, 0x60, 0xa6, 0xa5, 0x08, 0xaa, 0x6a, 0xa0, 0x8f, 0xa0, 0x80, 0x4f, - 0x46, 0xa6, 0x1b, 0x39, 0x56, 0xc8, 0xa8, 0x13, 0x67, 0x2e, 0xe6, 0xa7, 0x4a, 0x72, 0x6a, 0x94, - 0x7e, 0x27, 0x40, 0xe1, 0x5c, 0x39, 0x93, 0x63, 0x96, 0xfe, 0xd6, 0x99, 0x39, 0x66, 0x89, 0x24, - 0x3c, 0x80, 0x63, 0x97, 0xfe, 0x46, 0x8c, 0xbf, 0xe6, 0x6f, 0xc4, 0x92, 0x05, 0x30, 0xed, 0x08, - 0xf4, 0x53, 0xc8, 0x39, 0x96, 0xd1, 0x9b, 0x9e, 0xde, 0xc2, 0xf4, 0x28, 0x26, 0x64, 0xdd, 0xb6, - 0x8c, 0x73, 0x07, 0xb8, 0xe4, 0x84, 0x22, 0x03, 0xad, 0x82, 0x48, 0xcc, 0xe7, 0xfc, 0x4e, 0x3b, - 0x96, 0x41, 0xee, 0x6f, 0xa5, 0x6f, 0x04, 0xc8, 0x44, 0x7f, 0xb9, 0xc6, 0xc2, 0x4f, 0x5c, 0x3c, - 0x49, 0xbc, 0xe2, 0xdf, 0x8e, 0xd9, 0x5b, 0x62, 0xfc, 0xea, 0xb7, 0xc4, 0xd2, 0x31, 0x48, 0x91, - 0x5a, 0x3b, 0x3f, 0xdc, 0x09, 0xd7, 0x1f, 0xee, 0xd0, 0x3b, 0x90, 0xfa, 0xd2, 0xe9, 0xf3, 0x00, - 0xe2, 0xb5, 0x1c, 0xb3, 0x4e, 0x6e, 0x3b, 0x7d, 0xb5, 0xa1, 0x25, 0xbf, 0x74, 0xfa, 0xaa, 0x51, - 0x7e, 0x8f, 0x8f, 0x80, 0x00, 0xa9, 0xdd, 0xbd, 0x5a, 0x53, 0xad, 0x5f, 0x38, 0xbe, 0x7d, 0x96, - 0xf8, 0xfa, 0xdb, 0x55, 0x61, 0x3b, 0x21, 0x22, 0xf9, 0x46, 0xf9, 0x3b, 0x01, 0x50, 0x43, 0xf7, - 0x75, 0xb2, 0xd5, 0xd7, 0x98, 0xdc, 0x62, 0x97, 0xe4, 0x7b, 0xf6, 0xa0, 0x8d, 0xbf, 0xce, 0x41, - 0x1b, 0xb8, 0x5a, 0xfe, 0x46, 0x00, 0x88, 0x38, 0xf7, 0x79, 0xf4, 0x2f, 0xfb, 0xe2, 0x99, 0xe2, - 0x5c, 0xff, 0x93, 0x3b, 0x41, 0xf0, 0x0f, 0xfe, 0x01, 0x88, 0x06, 0x0b, 0x99, 0x5d, 0x2b, 0x16, - 0x1e, 0xde, 0x73, 0x99, 0x79, 0xb8, 0xa4, 0x85, 0xc6, 0xb5, 0x34, 0x24, 0xc7, 0xb6, 0xe9, 0xd8, - 0x1f, 0x7c, 0x02, 0x68, 0xbe, 0x4d, 0x50, 0x0e, 0x32, 0xf4, 0x59, 0xf7, 0xb1, 0x11, 0xcc, 0xe2, - 0x7b, 0xf6, 0x71, 0x28, 0x10, 0x6a, 0x77, 0x5f, 0xfc, 0x63, 0x65, 0xe9, 0xc5, 0xd9, 0x8a, 0xf0, - 0xd7, 0xb3, 0x15, 0xe1, 0x6f, 0x67, 0x2b, 0xc2, 0xdf, 0xcf, 0x56, 0x84, 0xdf, 0xfe, 0x73, 0x65, - 0xe9, 0x8b, 0x34, 0x73, 0xe0, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x83, 0x67, 0x35, 0xa6, 0xe1, - 0x1b, 0x00, 0x00, + 0x0f, 0xd8, 0x63, 0x8e, 0x39, 0xa9, 0x12, 0xe5, 0x92, 0x7b, 0xaa, 0x72, 0xd8, 0xaa, 0x54, 0xa5, + 0x66, 0x30, 0x03, 0x82, 0xa2, 0x28, 0x4b, 0xbb, 0x37, 0xa0, 0x7b, 0xfa, 0x87, 0xee, 0x9e, 0xee, + 0xdf, 0xf4, 0x00, 0xde, 0xf2, 0x9e, 0x5b, 0x1b, 0xde, 0x73, 0xab, 0xaf, 0x7b, 0x78, 0xc3, 0xf3, + 0xdd, 0xf1, 0xc0, 0x1f, 0xbb, 0xd8, 0xa8, 0x8c, 0x5c, 0xc7, 0x77, 0xd0, 0xad, 0x81, 0x33, 0x78, + 0xe6, 0x3a, 0xfa, 0xe0, 0xa8, 0xe2, 0x3d, 0xb7, 0x2a, 0x6c, 0x5d, 0xa9, 0x38, 0xf6, 0x4d, 0x6b, + 0xe3, 0xc8, 0x1a, 0x6c, 0xf8, 0xe6, 0x10, 0x7b, 0xbe, 0x3e, 0x1c, 0x05, 0x06, 0xa5, 0x37, 0xa3, + 0x70, 0x23, 0xd7, 0x3c, 0x36, 0x2d, 0x7c, 0x88, 0x99, 0xf2, 0xe6, 0xa1, 0x73, 0xe8, 0xd0, 0xc7, + 0x0d, 0xf2, 0x14, 0x48, 0xcb, 0xff, 0x4d, 0x02, 0xd4, 0x1d, 0x6b, 0x3c, 0xb4, 0xbb, 0x93, 0x11, + 0x46, 0x4f, 0x21, 0xe7, 0xe1, 0xa1, 0x6e, 0xfb, 0xe6, 0xa0, 0xe7, 0x4f, 0x46, 0xb8, 0x28, 0xac, + 0x09, 0xeb, 0xf9, 0xcd, 0x4a, 0xe5, 0x42, 0x57, 0x2a, 0x53, 0xcb, 0x4a, 0x87, 0x99, 0x91, 0x97, + 0x5a, 0xe2, 0xc5, 0xe9, 0xea, 0x92, 0x96, 0xf5, 0x22, 0x32, 0x54, 0x82, 0xe4, 0x57, 0xa6, 0xe1, + 0x1f, 0x15, 0x63, 0x6b, 0xc2, 0x7a, 0x92, 0x2d, 0x09, 0x44, 0xa8, 0x0c, 0x99, 0x91, 0x8b, 0x07, + 0xa6, 0x67, 0x3a, 0x76, 0x31, 0x1e, 0xd1, 0x4f, 0xc5, 0xe8, 0x7d, 0x90, 0x75, 0xd7, 0xd5, 0x27, + 0x3d, 0xc3, 0x1c, 0x62, 0x9b, 0x88, 0xbc, 0x62, 0x62, 0x2d, 0xbe, 0x9e, 0xd4, 0x0a, 0x54, 0xde, + 0x08, 0xc5, 0xe8, 0x36, 0xa4, 0x2c, 0x67, 0xa0, 0x5b, 0xb8, 0x98, 0x5c, 0x13, 0xd6, 0x33, 0x1a, + 0x7b, 0x43, 0xfb, 0x90, 0x3d, 0x36, 0x3d, 0xb3, 0x6f, 0xe1, 0x20, 0xb8, 0x14, 0x0d, 0xee, 0xa3, + 0xef, 0x0f, 0x6e, 0x3f, 0xb0, 0x8a, 0xc4, 0x26, 0x1d, 0x4f, 0x45, 0x68, 0x0f, 0xf2, 0x81, 0x6b, + 0x03, 0xc7, 0xf6, 0xb1, 0xed, 0x7b, 0xc5, 0xf4, 0xab, 0xa4, 0x4d, 0xcb, 0x51, 0x94, 0x3a, 0x03, + 0x29, 0xff, 0x5b, 0x80, 0x6c, 0x54, 0x8f, 0x44, 0x48, 0xd4, 0xda, 0xed, 0xa6, 0xbc, 0x84, 0xd2, + 0x10, 0x57, 0x5b, 0x5d, 0x59, 0x40, 0x19, 0x48, 0x6e, 0x35, 0xdb, 0xd5, 0xae, 0x1c, 0x43, 0x12, + 0xa4, 0x1b, 0x4a, 0x5d, 0xdd, 0xa9, 0x36, 0xe5, 0x38, 0x59, 0xda, 0xa8, 0x76, 0x15, 0x39, 0x81, + 0x72, 0x90, 0xe9, 0xaa, 0x3b, 0x4a, 0xa7, 0x5b, 0xdd, 0xd9, 0x95, 0x93, 0x28, 0x0b, 0xa2, 0xda, + 0xea, 0x2a, 0xda, 0x7e, 0xb5, 0x29, 0xa7, 0x10, 0x40, 0xaa, 0xd3, 0xd5, 0xd4, 0xd6, 0x03, 0x39, + 0x4d, 0xa0, 0x6a, 0x4f, 0xbb, 0x4a, 0x47, 0x16, 0x51, 0x01, 0xa4, 0xd0, 0xa6, 0xfb, 0x85, 0x9c, + 0x41, 0x08, 0xf2, 0xf5, 0x76, 0xb3, 0x59, 0xed, 0x2a, 0x0d, 0xb6, 0x1e, 0xc8, 0x27, 0x5a, 0xd5, + 0x1d, 0x45, 0x96, 0x88, 0x37, 0x6d, 0xb5, 0x21, 0x67, 0xa9, 0x68, 0xaf, 0xd9, 0x94, 0x73, 0xe4, + 0x69, 0x6f, 0x4f, 0x6d, 0xc8, 0x79, 0x02, 0x5b, 0xd5, 0xb4, 0xea, 0x53, 0xb9, 0x40, 0x84, 0x6a, + 0x4b, 0xe9, 0xca, 0x32, 0x79, 0x22, 0x1f, 0x90, 0x97, 0x51, 0x01, 0x40, 0x6d, 0x75, 0x37, 0xf7, + 0x95, 0x7a, 0xb7, 0xad, 0xc9, 0x2f, 0x84, 0xf2, 0x01, 0x48, 0x91, 0x74, 0x53, 0xc8, 0x76, 0x4b, + 0x91, 0x97, 0x48, 0x7c, 0xc4, 0xf3, 0x07, 0x8a, 0x26, 0x0b, 0x24, 0x8c, 0xce, 0x4e, 0xb5, 0xd9, + 0x24, 0x59, 0x88, 0x91, 0x30, 0x6a, 0xea, 0x03, 0xf2, 0x1c, 0x27, 0xce, 0xd4, 0xd4, 0xae, 0x9c, + 0x20, 0x96, 0x9a, 0x52, 0x6d, 0xca, 0x49, 0x74, 0x03, 0x0a, 0x8d, 0xf6, 0x5e, 0xad, 0xa9, 0xf4, + 0x76, 0x35, 0xa5, 0xae, 0x76, 0xda, 0x2d, 0x39, 0xf5, 0x59, 0xe2, 0x5f, 0xdf, 0xae, 0x0a, 0xe5, + 0xff, 0xc4, 0xe1, 0xc6, 0x96, 0xe3, 0x62, 0xf3, 0xd0, 0x7e, 0x84, 0x27, 0x1a, 0x3e, 0xc0, 0x2e, + 0xb6, 0x07, 0x18, 0xad, 0x41, 0xd2, 0xd7, 0xfb, 0x56, 0xd0, 0x00, 0xb9, 0x1a, 0x90, 0x4d, 0x7f, + 0x79, 0xba, 0x1a, 0x53, 0x1b, 0x5a, 0xa0, 0x40, 0xef, 0x42, 0xd2, 0xb4, 0x0d, 0x7c, 0x42, 0xeb, + 0x39, 0x57, 0x2b, 0xb0, 0x15, 0x69, 0x95, 0x08, 0xc9, 0x32, 0xaa, 0x45, 0x45, 0x48, 0xd8, 0xfa, + 0x10, 0xd3, 0xaa, 0xce, 0xb0, 0xe2, 0xa1, 0x12, 0xf4, 0x08, 0xc4, 0x63, 0xdd, 0x32, 0x0d, 0xd3, + 0x9f, 0x14, 0x13, 0xb4, 0x5e, 0xde, 0x5f, 0x58, 0x2f, 0xb6, 0xe7, 0xbb, 0xba, 0x69, 0xfb, 0xfb, + 0xcc, 0x80, 0x01, 0x85, 0x00, 0xe8, 0x1e, 0x2c, 0x7b, 0x47, 0xba, 0x8b, 0x8d, 0xde, 0xc8, 0xc5, + 0x07, 0xe6, 0x49, 0xcf, 0xc2, 0x36, 0xad, 0x7e, 0xde, 0x49, 0x85, 0x40, 0xbd, 0x4b, 0xb5, 0x4d, + 0x6c, 0xa3, 0x2e, 0x64, 0x1c, 0xbb, 0x67, 0x60, 0x0b, 0xfb, 0xbc, 0x13, 0x3e, 0x5e, 0xf0, 0xfd, + 0x0b, 0x12, 0x54, 0xa9, 0x0e, 0x7c, 0xd3, 0xb1, 0xb9, 0x1f, 0x8e, 0xdd, 0xa0, 0x40, 0x0c, 0x75, + 0x3c, 0x32, 0x74, 0x1f, 0xb3, 0x2e, 0x78, 0x1d, 0xd4, 0x3d, 0x0a, 0x54, 0x7e, 0x0c, 0xa9, 0x40, + 0x43, 0xaa, 0xb9, 0xd5, 0xee, 0x55, 0xeb, 0x5d, 0xb5, 0xdd, 0x92, 0x97, 0x48, 0x19, 0x68, 0x0a, + 0xa9, 0xc8, 0x7a, 0x97, 0x15, 0x85, 0xd2, 0xed, 0xd1, 0x12, 0x8c, 0x91, 0x22, 0x26, 0x6f, 0x0d, + 0x65, 0xab, 0xba, 0xd7, 0x24, 0x95, 0x21, 0x41, 0xba, 0x5e, 0xed, 0xd4, 0xab, 0x0d, 0x45, 0x4e, + 0x94, 0x7f, 0x13, 0x03, 0x39, 0xe8, 0xc3, 0x06, 0xf6, 0x06, 0xae, 0x39, 0xf2, 0x1d, 0x37, 0xdc, + 0x2c, 0x61, 0x6e, 0xb3, 0xde, 0x83, 0x98, 0x69, 0xb0, 0xad, 0xbe, 0x4d, 0xe4, 0x67, 0xb4, 0x18, + 0x5e, 0x9e, 0xae, 0x8a, 0x01, 0x8a, 0xda, 0xd0, 0x62, 0xa6, 0x81, 0x7e, 0x0c, 0x09, 0x4a, 0x2d, + 0x64, 0xbb, 0xa5, 0xcd, 0xbb, 0xdf, 0x4b, 0x00, 0xfc, 0x23, 0xc4, 0x08, 0xad, 0x81, 0x68, 0x8f, + 0x2d, 0x8b, 0xd6, 0x1d, 0xa9, 0x08, 0x91, 0x27, 0x82, 0x4b, 0xd1, 0x5d, 0xc8, 0x1a, 0xf8, 0x40, + 0x1f, 0x5b, 0x7e, 0x0f, 0x9f, 0x8c, 0x5c, 0xc6, 0x6f, 0x12, 0x93, 0x29, 0x27, 0x23, 0x17, 0xbd, + 0x05, 0xa9, 0x23, 0xd3, 0x30, 0xb0, 0x4d, 0x37, 0x95, 0x43, 0x30, 0xd9, 0x76, 0x42, 0x14, 0xe5, + 0xcc, 0x76, 0x42, 0xcc, 0xc8, 0xb0, 0x9d, 0x10, 0xd3, 0xb2, 0x58, 0xfe, 0x3a, 0x06, 0xb7, 0x03, + 0x7f, 0xb6, 0xf4, 0xa1, 0x69, 0x4d, 0x5e, 0x37, 0x1d, 0x01, 0x0a, 0x4b, 0xc7, 0x5d, 0xc8, 0x0e, + 0x28, 0x76, 0x8f, 0x98, 0x79, 0xc5, 0xf8, 0x5a, 0x9c, 0xf8, 0x1b, 0xc8, 0x5a, 0x44, 0x84, 0x3e, + 0x05, 0x60, 0x4b, 0x4c, 0x23, 0x60, 0xf4, 0x5c, 0xed, 0xce, 0xd9, 0xe9, 0x6a, 0x86, 0xe7, 0xd5, + 0x9b, 0x49, 0x72, 0x26, 0x58, 0xac, 0x1a, 0x1e, 0x6a, 0xc3, 0x32, 0x4f, 0x46, 0x88, 0x40, 0x33, + 0x92, 0xab, 0xbd, 0xcd, 0x7c, 0x2a, 0x34, 0x82, 0x05, 0xdc, 0x7c, 0x06, 0xaa, 0x60, 0xcc, 0x28, + 0x8d, 0xf2, 0x1f, 0x63, 0x70, 0x53, 0xb5, 0x7d, 0xec, 0x5a, 0x58, 0x3f, 0xc6, 0x91, 0x44, 0xfc, + 0x1c, 0x32, 0xba, 0x3d, 0xc0, 0x9e, 0xef, 0xb8, 0x5e, 0x51, 0x58, 0x8b, 0xaf, 0x4b, 0x9b, 0x9f, + 0x2c, 0xd8, 0xda, 0x8b, 0xec, 0x2b, 0x55, 0x66, 0xcc, 0x4f, 0xb5, 0x10, 0xac, 0xf4, 0x67, 0x01, + 0x44, 0xae, 0x45, 0xf7, 0x40, 0xa4, 0xdc, 0x42, 0xe2, 0x08, 0x78, 0xe7, 0x16, 0x8b, 0x23, 0xdd, + 0x25, 0x72, 0xea, 0x3f, 0xa1, 0xa0, 0x34, 0x5d, 0xa6, 0x1a, 0xe8, 0x87, 0x20, 0x52, 0x9a, 0xe9, + 0x85, 0xbb, 0x51, 0xe2, 0x16, 0x8c, 0x87, 0xa2, 0x94, 0x94, 0xa6, 0x6b, 0x55, 0x03, 0xd5, 0x2f, + 0x62, 0x8b, 0x38, 0xb5, 0x7f, 0x83, 0x67, 0xae, 0x33, 0xcb, 0x17, 0x73, 0x04, 0x52, 0xfe, 0x4b, + 0x1c, 0x6e, 0xef, 0xea, 0xae, 0x6f, 0x92, 0xc6, 0x34, 0xed, 0xc3, 0x48, 0xbe, 0xde, 0x05, 0xc9, + 0x1e, 0x0f, 0xd9, 0xae, 0x78, 0x2c, 0x96, 0x20, 0x76, 0xb0, 0xc7, 0xc3, 0x20, 0xe1, 0x1e, 0x6a, + 0x42, 0xc2, 0x32, 0x3d, 0xbf, 0x18, 0xa3, 0x19, 0xdd, 0x5c, 0x90, 0xd1, 0x8b, 0xbf, 0x51, 0x69, + 0x9a, 0x9e, 0xcf, 0x6b, 0x92, 0xa0, 0xa0, 0x36, 0x24, 0x5d, 0xdd, 0x3e, 0xc4, 0xb4, 0xc8, 0xa4, + 0xcd, 0xfb, 0xd7, 0x83, 0xd3, 0x88, 0x29, 0x9f, 0x4a, 0x28, 0x4e, 0xe9, 0x0f, 0x02, 0x24, 0xc8, + 0x57, 0x2e, 0xe9, 0x83, 0xdb, 0x90, 0x3a, 0xd6, 0xad, 0x31, 0xf6, 0x68, 0x0c, 0x59, 0x8d, 0xbd, + 0xa1, 0x5f, 0x42, 0xc1, 0x1b, 0xf7, 0x47, 0x91, 0x4f, 0x31, 0x46, 0xf8, 0xe8, 0x5a, 0x5e, 0x85, + 0xdc, 0x3d, 0x8b, 0x55, 0xaa, 0x41, 0x92, 0xfa, 0x7b, 0x89, 0x67, 0xab, 0x20, 0x8d, 0x47, 0x23, + 0xec, 0xf6, 0xfa, 0xce, 0xd8, 0x0e, 0x8a, 0x23, 0xab, 0x01, 0x15, 0xd5, 0x88, 0xa4, 0xfc, 0x7b, + 0x11, 0x0a, 0xb4, 0x30, 0xae, 0xd4, 0xf0, 0xef, 0x46, 0x1a, 0xfe, 0xd6, 0x4c, 0xc3, 0x87, 0xd5, + 0x45, 0xfa, 0xfd, 0x2d, 0x48, 0x8d, 0x6d, 0xf3, 0xf9, 0x38, 0x20, 0xc0, 0x90, 0x7c, 0x02, 0xd9, + 0x1c, 0x1b, 0x24, 0xe6, 0xd9, 0xe0, 0x43, 0x40, 0xa4, 0x15, 0x70, 0x6f, 0x66, 0x61, 0x92, 0x2e, + 0x94, 0xa9, 0xa6, 0xbe, 0x90, 0x3b, 0x52, 0xd7, 0xe0, 0x8e, 0x87, 0x20, 0xe3, 0x13, 0xdf, 0xd5, + 0x7b, 0x11, 0xfb, 0x34, 0xb5, 0x5f, 0x39, 0x3b, 0x5d, 0xcd, 0x2b, 0x44, 0x77, 0x31, 0x48, 0x1e, + 0x47, 0x74, 0x06, 0xd9, 0xea, 0x65, 0x86, 0x61, 0x98, 0x2e, 0xa6, 0xa7, 0x94, 0x57, 0x14, 0xd7, + 0xe2, 0xeb, 0xf9, 0xcd, 0x7b, 0x0b, 0x39, 0x62, 0x26, 0xed, 0x95, 0x06, 0x37, 0xd4, 0xe4, 0x00, + 0x2a, 0x14, 0x78, 0xe8, 0x31, 0x48, 0x07, 0xc1, 0x41, 0xd9, 0x7b, 0x86, 0x27, 0xc5, 0x0c, 0xad, + 0xa2, 0x0f, 0xae, 0x7e, 0xa4, 0xf2, 0xb6, 0x3b, 0x08, 0x55, 0x68, 0x0f, 0x72, 0x2e, 0x57, 0x1b, + 0xbd, 0xfe, 0xa4, 0x08, 0xb4, 0x61, 0xae, 0x0f, 0x9a, 0x9d, 0xc2, 0xd4, 0x26, 0xe8, 0x31, 0x80, + 0x19, 0x92, 0x5f, 0x51, 0xa2, 0x8e, 0xfe, 0xe0, 0x1a, 0x2c, 0xc9, 0x3d, 0x9d, 0x82, 0xa0, 0x27, + 0x90, 0x9f, 0xbe, 0x51, 0x57, 0xb3, 0xaf, 0xe8, 0x6a, 0x2e, 0x82, 0x53, 0x9b, 0xa0, 0x2e, 0xdc, + 0x1c, 0x38, 0xc3, 0x91, 0xe3, 0x99, 0x3e, 0x8e, 0x96, 0x40, 0x8e, 0x96, 0x40, 0xf9, 0xec, 0x74, + 0x15, 0xd5, 0xb9, 0xfe, 0xe2, 0x32, 0x40, 0x83, 0x73, 0xfa, 0xa0, 0xa8, 0x66, 0x8a, 0x97, 0x20, + 0xe6, 0xa7, 0x45, 0xd5, 0x99, 0x96, 0xef, 0x5c, 0x51, 0x45, 0x4a, 0x9b, 0x20, 0x3d, 0x81, 0xec, + 0x0c, 0x79, 0x14, 0x5e, 0x9d, 0x3c, 0x66, 0x80, 0xca, 0x2b, 0x90, 0x09, 0x8b, 0x8b, 0x8c, 0xca, + 0xd5, 0x4e, 0x5d, 0x5e, 0xa2, 0xb7, 0x05, 0xa5, 0x53, 0x97, 0x85, 0xf2, 0xaf, 0x13, 0x80, 0xa6, + 0x10, 0x3b, 0x63, 0x5f, 0xa7, 0x2b, 0xab, 0x90, 0x0a, 0x62, 0xa2, 0xd4, 0x20, 0x6d, 0xfe, 0xff, + 0xa5, 0x83, 0xcd, 0x14, 0xe0, 0xe1, 0x92, 0xc6, 0x0c, 0xd1, 0xe7, 0xd1, 0x79, 0x59, 0xda, 0x7c, + 0xef, 0x6a, 0xbd, 0xf1, 0x70, 0x89, 0x0f, 0xd2, 0x8f, 0x20, 0xe9, 0xf9, 0x64, 0xaa, 0x8c, 0xd3, + 0xa9, 0x72, 0x63, 0x81, 0xfd, 0xbc, 0xf3, 0x95, 0x0e, 0x31, 0xe3, 0xd4, 0x4e, 0x31, 0xd0, 0x13, + 0xc8, 0x84, 0xdd, 0xca, 0x86, 0xef, 0xfb, 0x57, 0x07, 0x0c, 0x33, 0xc8, 0xcf, 0xf3, 0x10, 0x0b, + 0x55, 0x41, 0x1a, 0xb2, 0x65, 0xd3, 0x69, 0x64, 0x8d, 0x11, 0x26, 0x70, 0x04, 0x4a, 0x9c, 0x91, + 0x37, 0x0d, 0xb8, 0x91, 0x6a, 0x94, 0x7f, 0x06, 0x49, 0xea, 0x31, 0x99, 0x57, 0xf7, 0x5a, 0x8f, + 0x5a, 0xed, 0x27, 0x64, 0xd2, 0x2d, 0x80, 0xd4, 0x50, 0x9a, 0x4a, 0x57, 0xe9, 0xb5, 0x5b, 0xcd, + 0xa7, 0xb2, 0x80, 0xee, 0xc0, 0x2d, 0x26, 0xa8, 0xb6, 0x1a, 0xbd, 0x27, 0x9a, 0xca, 0x55, 0xb1, + 0xf2, 0x7a, 0x74, 0x93, 0xa7, 0x17, 0x28, 0xb2, 0xdd, 0x8d, 0x86, 0x2c, 0xd0, 0xed, 0xd6, 0xda, + 0xbb, 0x72, 0xac, 0x96, 0x05, 0x30, 0xc2, 0xf8, 0xb6, 0x13, 0x62, 0x4a, 0x4e, 0x97, 0xff, 0x74, + 0x03, 0x0a, 0x74, 0xdc, 0xb8, 0xd2, 0xc1, 0xb0, 0x46, 0x0f, 0x86, 0x60, 0x76, 0x90, 0x67, 0x0e, + 0x86, 0x18, 0x3b, 0x13, 0xee, 0x43, 0x66, 0xa4, 0xbb, 0xd8, 0xf6, 0x49, 0x42, 0x12, 0x33, 0x23, + 0xa3, 0xb8, 0x4b, 0x15, 0xe1, 0x72, 0x31, 0x58, 0xa8, 0x12, 0xa3, 0xf4, 0x31, 0x76, 0xe9, 0xff, + 0x80, 0x20, 0x87, 0x77, 0xd8, 0xfd, 0x6a, 0x79, 0xea, 0xd5, 0x7e, 0xb0, 0x40, 0xe3, 0x2b, 0xd1, + 0xdb, 0x00, 0xe3, 0x51, 0x8f, 0xdb, 0x45, 0xc7, 0xdf, 0xcc, 0x78, 0xc4, 0x56, 0xa3, 0x5d, 0x58, + 0x1e, 0x3a, 0x86, 0x79, 0x60, 0x0e, 0x82, 0x5d, 0xf2, 0xcd, 0x61, 0x70, 0x53, 0x91, 0x36, 0xff, + 0x2f, 0x52, 0x02, 0x63, 0xdf, 0xb4, 0x2a, 0x47, 0xd6, 0xa0, 0xd2, 0xe5, 0x3f, 0x59, 0x18, 0x94, + 0x1c, 0xb5, 0x26, 0x4a, 0xf4, 0x00, 0xd2, 0x7c, 0xd2, 0x11, 0x29, 0x3d, 0x5d, 0xb5, 0x3b, 0x18, + 0x22, 0xb7, 0x46, 0x5b, 0x90, 0xb7, 0xf1, 0x49, 0x74, 0x9a, 0xcd, 0xcc, 0xd4, 0x4f, 0xb6, 0x85, + 0x4f, 0x2e, 0x1e, 0x65, 0xb3, 0xf6, 0x54, 0x63, 0xa0, 0xc7, 0x90, 0x1b, 0xb9, 0xe6, 0x50, 0x77, + 0x27, 0xbd, 0xa0, 0xe5, 0xe0, 0x3a, 0x2d, 0x17, 0xf2, 0x46, 0x00, 0x41, 0xb5, 0x68, 0x0b, 0x82, + 0xe1, 0x11, 0x7b, 0x45, 0x89, 0xc6, 0x78, 0x3d, 0x30, 0x6e, 0x8c, 0x6a, 0x90, 0xa3, 0x21, 0x86, + 0x53, 0x6b, 0x96, 0x46, 0xb8, 0xc2, 0x22, 0x94, 0x48, 0x84, 0x17, 0x4c, 0xae, 0x92, 0x1d, 0xca, + 0x0d, 0xb4, 0x0d, 0x10, 0xfe, 0xdc, 0x22, 0x94, 0x7d, 0xd9, 0x89, 0xb8, 0xcb, 0x17, 0x4e, 0x5d, + 0xd2, 0x22, 0xd6, 0x68, 0x07, 0x32, 0xbc, 0xf5, 0x02, 0xae, 0x96, 0x16, 0xde, 0xc2, 0xe7, 0x89, + 0x80, 0x17, 0x57, 0x88, 0x80, 0x5a, 0x90, 0xb4, 0xb0, 0xee, 0x61, 0x46, 0xd8, 0x9f, 0x2e, 0x80, + 0x3a, 0xd7, 0x5e, 0x95, 0xce, 0xe0, 0x08, 0x0f, 0xf5, 0xfa, 0x11, 0x99, 0xe9, 0x9a, 0xc4, 0x5e, + 0x0b, 0x60, 0x50, 0x0b, 0x64, 0x9a, 0xae, 0x28, 0xa7, 0xc8, 0x34, 0x63, 0xef, 0xb0, 0x8c, 0xe5, + 0x49, 0xc6, 0x16, 0xf2, 0x0a, 0xad, 0xa7, 0xf0, 0xdd, 0x40, 0x3f, 0x81, 0xfc, 0x81, 0xe3, 0x0e, + 0x75, 0x3f, 0xec, 0x92, 0xe5, 0xe9, 0x48, 0xf7, 0xf2, 0x74, 0x35, 0xb7, 0x45, 0xb5, 0xbc, 0xb3, + 0x72, 0x07, 0xd1, 0x57, 0xf4, 0x90, 0x53, 0xf0, 0x0d, 0xca, 0x98, 0x1f, 0x5e, 0x35, 0xba, 0x79, + 0xfe, 0x6d, 0x41, 0x6a, 0x70, 0x84, 0x07, 0xcf, 0xbc, 0xe2, 0x4d, 0x9a, 0xf3, 0x1f, 0x5d, 0x11, + 0xaa, 0x4e, 0x8c, 0xa6, 0xbf, 0x43, 0x34, 0x86, 0x82, 0x1e, 0x43, 0xda, 0xc5, 0xc1, 0xac, 0x78, + 0x8b, 0x02, 0x7e, 0x7c, 0x45, 0x40, 0x8d, 0x5a, 0xa9, 0xf6, 0x81, 0xc3, 0x2b, 0x95, 0xe1, 0xa0, + 0x36, 0x88, 0x07, 0xe4, 0x2a, 0x6b, 0x62, 0xaf, 0x78, 0x9b, 0x62, 0x5e, 0xfe, 0xa3, 0xf0, 0xfc, + 0xed, 0x99, 0xdf, 0xdd, 0x39, 0x48, 0xd8, 0xdd, 0x54, 0x30, 0x21, 0x3b, 0xf9, 0xc6, 0x7c, 0x77, + 0xf3, 0xdb, 0xf3, 0xcc, 0x4d, 0x9a, 0x76, 0x37, 0x7b, 0x33, 0x08, 0xcb, 0x1d, 0x9b, 0xf8, 0xab, + 0xde, 0xf3, 0x31, 0x76, 0x27, 0xc5, 0x62, 0x84, 0x91, 0x33, 0x44, 0xfe, 0x98, 0x88, 0xd1, 0xc7, + 0x90, 0x31, 0xf0, 0x08, 0xdb, 0x86, 0xd7, 0xb6, 0x8b, 0x77, 0xe8, 0x0c, 0x72, 0x83, 0x0c, 0xc6, + 0x0d, 0x2e, 0x64, 0x8c, 0x3b, 0x5d, 0x85, 0xbe, 0x84, 0x6c, 0xf0, 0x82, 0x8d, 0xb6, 0x5d, 0x9b, + 0x14, 0x4b, 0x34, 0xe8, 0x7b, 0x57, 0x4e, 0x24, 0x1f, 0xb8, 0x6e, 0xf2, 0x78, 0x1a, 0x11, 0x34, + 0x6d, 0x06, 0x1b, 0xfd, 0x02, 0xb2, 0xbc, 0xa4, 0xb7, 0x9d, 0xbe, 0x57, 0x7c, 0xf3, 0xd2, 0x1b, + 0xe0, 0xf9, 0x6f, 0xed, 0x4c, 0x4d, 0x39, 0x59, 0x45, 0xd1, 0x4a, 0xdf, 0x09, 0xb0, 0x3c, 0xd7, + 0x52, 0xe8, 0x57, 0x90, 0xb6, 0x1d, 0x23, 0x72, 0xb9, 0x56, 0x98, 0xa3, 0xa9, 0x96, 0x63, 0x04, + 0x77, 0xeb, 0xfb, 0x87, 0xa6, 0x7f, 0x34, 0xee, 0x57, 0x06, 0xce, 0x70, 0x23, 0x74, 0xc5, 0xe8, + 0x4f, 0x9f, 0x37, 0x46, 0xcf, 0x0e, 0x37, 0xe8, 0xd3, 0xa8, 0x5f, 0x09, 0xcc, 0xb4, 0x14, 0x41, + 0x55, 0x0d, 0xf4, 0x11, 0x14, 0xf0, 0xc9, 0xc8, 0x74, 0x23, 0xc7, 0x0a, 0x19, 0x75, 0xe2, 0xcc, + 0xc5, 0xfc, 0x54, 0x49, 0x4e, 0x8d, 0xd2, 0xef, 0x04, 0x28, 0x9c, 0x2b, 0x67, 0x72, 0xcc, 0xd2, + 0xdf, 0x3a, 0x33, 0xc7, 0x2c, 0x91, 0x84, 0x07, 0x70, 0xec, 0xd2, 0xdf, 0x88, 0xf1, 0xd7, 0xfc, + 0x8d, 0x58, 0xb2, 0x00, 0xa6, 0x1d, 0x81, 0x7e, 0x0a, 0x39, 0xc7, 0x32, 0x7a, 0xd3, 0xd3, 0x5b, + 0x98, 0x1e, 0xc5, 0x84, 0xac, 0xdb, 0x96, 0x71, 0xee, 0x00, 0x97, 0x9c, 0x50, 0x64, 0xa0, 0x55, + 0x10, 0x89, 0xf9, 0x9c, 0xdf, 0x69, 0xc7, 0x32, 0xc8, 0xfd, 0xad, 0xf4, 0x8d, 0x00, 0x99, 0xe8, + 0x2f, 0xd7, 0x58, 0xf8, 0x89, 0x8b, 0x27, 0x89, 0x57, 0xfc, 0xdb, 0x31, 0x7b, 0x4b, 0x8c, 0x5f, + 0xfd, 0x96, 0x58, 0x3a, 0x06, 0x29, 0x52, 0x6b, 0xe7, 0x87, 0x3b, 0xe1, 0xfa, 0xc3, 0x1d, 0x7a, + 0x07, 0x52, 0x5f, 0x3a, 0x7d, 0x1e, 0x40, 0xbc, 0x96, 0x63, 0xd6, 0xc9, 0x6d, 0xa7, 0xaf, 0x36, + 0xb4, 0xe4, 0x97, 0x4e, 0x5f, 0x35, 0xca, 0xef, 0xf1, 0x11, 0x10, 0x20, 0xb5, 0xbb, 0x57, 0x6b, + 0xaa, 0xf5, 0x0b, 0xc7, 0xb7, 0xcf, 0x12, 0x5f, 0x7f, 0xbb, 0x2a, 0x6c, 0x27, 0x44, 0x24, 0xdf, + 0x28, 0x7f, 0x27, 0x00, 0x6a, 0xe8, 0xbe, 0x4e, 0xb6, 0xfa, 0x1a, 0x93, 0x5b, 0xec, 0x92, 0x7c, + 0xcf, 0x1e, 0xb4, 0xf1, 0xd7, 0x39, 0x68, 0x03, 0x57, 0xcb, 0xdf, 0x08, 0x00, 0x11, 0xe7, 0x3e, + 0x8f, 0xfe, 0x65, 0x5f, 0x3c, 0x53, 0x9c, 0xeb, 0x7f, 0x72, 0x27, 0x08, 0xfe, 0xc1, 0x3f, 0x00, + 0xd1, 0x60, 0x21, 0xb3, 0x6b, 0xc5, 0xc2, 0xc3, 0x7b, 0x2e, 0x33, 0x0f, 0x97, 0xb4, 0xd0, 0xb8, + 0x96, 0x86, 0xe4, 0xd8, 0x36, 0x1d, 0xfb, 0x83, 0x4f, 0x00, 0xcd, 0xb7, 0x09, 0xca, 0x41, 0x86, + 0x3e, 0xeb, 0x3e, 0x36, 0x82, 0x59, 0x7c, 0xcf, 0x3e, 0x0e, 0x05, 0x42, 0xed, 0xee, 0x8b, 0x7f, + 0xac, 0x2c, 0xbd, 0x38, 0x5b, 0x11, 0xfe, 0x7a, 0xb6, 0x22, 0xfc, 0xed, 0x6c, 0x45, 0xf8, 0xfb, + 0xd9, 0x8a, 0xf0, 0xdb, 0x7f, 0xae, 0x2c, 0x7d, 0x91, 0x66, 0x0e, 0xfc, 0x2f, 0x00, 0x00, 0xff, + 0xff, 0xc4, 0x92, 0xa3, 0x9f, 0xeb, 0x1b, 0x00, 0x00, } diff --git a/pkg/sql/sqlbase/structured.proto b/pkg/sql/sqlbase/structured.proto index b9ba9c6b9107..598a2489cf8c 100644 --- a/pkg/sql/sqlbase/structured.proto +++ b/pkg/sql/sqlbase/structured.proto @@ -54,6 +54,7 @@ message ColumnType { UUID = 14; ARRAY = 15; INET = 16; + TIME = 17; INT2VECTOR = 200; } diff --git a/pkg/sql/sqlbase/table.go b/pkg/sql/sqlbase/table.go index 0dec7c9daf81..67e6c7c535ba 100644 --- a/pkg/sql/sqlbase/table.go +++ b/pkg/sql/sqlbase/table.go @@ -142,6 +142,7 @@ func MakeColumnDefDescs( col.Type.Width, col.Type.Precision) } case *coltypes.TDate: + case *coltypes.TTime: case *coltypes.TTimestamp: case *coltypes.TTimestampTZ: case *coltypes.TInterval: @@ -526,6 +527,11 @@ func EncodeTableKey(b []byte, val tree.Datum, dir encoding.Direction) ([]byte, e return encoding.EncodeVarintAscending(b, int64(*t)), nil } return encoding.EncodeVarintDescending(b, int64(*t)), nil + case *tree.DTime: + if dir == encoding.Ascending { + return encoding.EncodeVarintAscending(b, int64(*t)), nil + } + return encoding.EncodeVarintDescending(b, int64(*t)), nil case *tree.DTimestamp: if dir == encoding.Ascending { return encoding.EncodeTimeAscending(b, t.Time), nil @@ -610,6 +616,8 @@ func EncodeTableValue( return encoding.EncodeBytesValue(appendTo, uint32(colID), []byte(*t)), nil case *tree.DDate: return encoding.EncodeIntValue(appendTo, uint32(colID), int64(*t)), nil + case *tree.DTime: + return encoding.EncodeIntValue(appendTo, uint32(colID), int64(*t)), nil case *tree.DTimestamp: return encoding.EncodeTimeValue(appendTo, uint32(colID), t.Time), nil case *tree.DTimestampTZ: @@ -934,6 +942,7 @@ type DatumAlloc struct { dbytesAlloc []tree.DBytes ddecimalAlloc []tree.DDecimal ddateAlloc []tree.DDate + dtimeAlloc []tree.DTime dtimestampAlloc []tree.DTimestamp dtimestampTzAlloc []tree.DTimestampTZ dintervalAlloc []tree.DInterval @@ -1021,6 +1030,18 @@ func (a *DatumAlloc) NewDDate(v tree.DDate) *tree.DDate { return r } +// NewDTime allocates a DTime. +func (a *DatumAlloc) NewDTime(v tree.DTime) *tree.DTime { + buf := &a.dtimeAlloc + if len(*buf) == 0 { + *buf = make([]tree.DTime, datumAllocSize) + } + r := &(*buf)[0] + *r = v + *buf = (*buf)[1:] + return r +} + // NewDTimestamp allocates a DTimestamp. func (a *DatumAlloc) NewDTimestamp(v tree.DTimestamp) *tree.DTimestamp { buf := &a.dtimestampAlloc @@ -1174,6 +1195,14 @@ func DecodeTableKey( rkey, t, err = encoding.DecodeVarintDescending(key) } return a.NewDDate(tree.DDate(t)), rkey, err + case types.Time: + var t int64 + if dir == encoding.Ascending { + rkey, t, err = encoding.DecodeVarintAscending(key) + } else { + rkey, t, err = encoding.DecodeVarintDescending(key) + } + return a.NewDTime(tree.DTime(t)), rkey, err case types.Timestamp: var t time.Time if dir == encoding.Ascending { @@ -1389,6 +1418,12 @@ func decodeUntaggedDatum(a *DatumAlloc, t types.T, buf []byte) (tree.Datum, []by return nil, b, err } return a.NewDDate(tree.DDate(data)), b, nil + case types.Time: + b, data, err := encoding.DecodeUntaggedIntValue(buf) + if err != nil { + return nil, b, err + } + return a.NewDTime(tree.DTime(data)), b, nil case types.Timestamp: b, data, err := encoding.DecodeUntaggedTimeValue(buf) if err != nil { @@ -1635,6 +1670,11 @@ func MarshalColumnValue(col ColumnDescriptor, val tree.Datum) (roachpb.Value, er r.SetInt(int64(*v)) return r, nil } + case ColumnType_TIME: + if v, ok := val.(*tree.DTime); ok { + r.SetInt(int64(*v)) + return r, nil + } case ColumnType_TIMESTAMP: if v, ok := val.(*tree.DTimestamp); ok { r.SetTime(v.Time) @@ -1776,6 +1816,8 @@ func parserTypeToEncodingType(t types.T) (encoding.Type, error) { return encoding.Bytes, nil case types.Timestamp, types.TimestampTZ, types.Date: return encoding.Time, nil + case types.Time: + return encoding.Int, nil case types.Interval: return encoding.Duration, nil case types.Bool: @@ -1812,6 +1854,8 @@ func encodeArrayElement(b []byte, d tree.Datum) ([]byte, error) { return encoding.EncodeUntaggedDecimalValue(b, &t.Decimal), nil case *tree.DDate: return encoding.EncodeUntaggedIntValue(b, int64(*t)), nil + case *tree.DTime: + return encoding.EncodeUntaggedIntValue(b, int64(*t)), nil case *tree.DTimestamp: return encoding.EncodeUntaggedTimeValue(b, t.Time), nil case *tree.DTimestampTZ: @@ -1882,6 +1926,12 @@ func UnmarshalColumnValue(a *DatumAlloc, typ ColumnType, value roachpb.Value) (t return nil, err } return a.NewDDate(tree.DDate(v)), nil + case ColumnType_TIME: + v, err := value.GetInt() + if err != nil { + return nil, err + } + return a.NewDTime(tree.DTime(v)), nil case ColumnType_TIMESTAMP: v, err := value.GetTime() if err != nil { diff --git a/pkg/sql/sqlbase/table_test.go b/pkg/sql/sqlbase/table_test.go index 48c055f5b690..df1a0aa775f7 100644 --- a/pkg/sql/sqlbase/table_test.go +++ b/pkg/sql/sqlbase/table_test.go @@ -18,6 +18,7 @@ import ( "bytes" "reflect" "testing" + "time" "github.com/pkg/errors" "golang.org/x/net/context" @@ -29,6 +30,8 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/encoding" "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/randutil" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" + "github.com/cockroachdb/cockroach/pkg/util/timeutil" ) type indexKeyTest struct { @@ -418,6 +421,26 @@ func TestMarshalColumnValue(t *testing.T) { return }(), }, + { + kind: ColumnType_DATE, + datum: tree.NewDDate(314159), + exp: func() (v roachpb.Value) { v.SetInt(314159); return }(), + }, + { + kind: ColumnType_TIME, + datum: tree.MakeDTime(timeofday.FromInt(314159)), + exp: func() (v roachpb.Value) { v.SetInt(314159); return }(), + }, + { + kind: ColumnType_TIMESTAMP, + datum: tree.MakeDTimestamp(timeutil.Unix(314159, 1000), time.Microsecond), + exp: func() (v roachpb.Value) { v.SetTime(timeutil.Unix(314159, 1000)); return }(), + }, + { + kind: ColumnType_TIMESTAMPTZ, + datum: tree.MakeDTimestampTZ(timeutil.Unix(314159, 1000), time.Microsecond), + exp: func() (v roachpb.Value) { v.SetTime(timeutil.Unix(314159, 1000)); return }(), + }, { kind: ColumnType_STRING, datum: tree.NewDString("testing123"), diff --git a/pkg/sql/sqlbase/testutils.go b/pkg/sql/sqlbase/testutils.go index c9622a17d55f..fac79dac15e3 100644 --- a/pkg/sql/sqlbase/testutils.go +++ b/pkg/sql/sqlbase/testutils.go @@ -28,6 +28,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/duration" "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/cockroach/pkg/util/timeofday" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/cockroach/pkg/util/uuid" ) @@ -85,6 +86,8 @@ func RandDatum(rng *rand.Rand, typ ColumnType, nullOk bool) tree.Datum { return d case ColumnType_DATE: return tree.NewDDate(tree.DDate(rng.Intn(10000))) + case ColumnType_TIME: + return tree.MakeDTime(timeofday.Random(rng)) case ColumnType_TIMESTAMP: return &tree.DTimestamp{Time: timeutil.Unix(rng.Int63n(1000000), rng.Int63n(1000000))} case ColumnType_INTERVAL: diff --git a/pkg/sql/table_test.go b/pkg/sql/table_test.go index f60c9e077b81..f86c2d23e379 100644 --- a/pkg/sql/table_test.go +++ b/pkg/sql/table_test.go @@ -90,6 +90,11 @@ func TestMakeTableDescColumns(t *testing.T) { sqlbase.ColumnType{SemanticType: sqlbase.ColumnType_DATE}, true, }, + { + "TIME", + sqlbase.ColumnType{SemanticType: sqlbase.ColumnType_TIME}, + true, + }, { "TIMESTAMP", sqlbase.ColumnType{SemanticType: sqlbase.ColumnType_TIMESTAMP}, diff --git a/pkg/util/timeofday/time_of_day.go b/pkg/util/timeofday/time_of_day.go new file mode 100644 index 000000000000..55bd47090be1 --- /dev/null +++ b/pkg/util/timeofday/time_of_day.go @@ -0,0 +1,133 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package timeofday + +import ( + "math/rand" + "time" + + "fmt" + + "strings" + + "github.com/cockroachdb/cockroach/pkg/util/duration" + "github.com/cockroachdb/cockroach/pkg/util/timeutil" +) + +// TimeOfDay represents a time of day (no date), stored as microseconds since +// midnight. +type TimeOfDay int64 + +const ( + // Min is the minimum TimeOfDay value (midnight). + Min = TimeOfDay(0) + // Max is the maximum TimeOfDay value (1 microsecond before midnight). + Max = TimeOfDay(microsecondsPerDay - 1) + + microsecondsPerSecond = 1e6 + microsecondsPerMinute = 60 * microsecondsPerSecond + microsecondsPerHour = 60 * microsecondsPerMinute + microsecondsPerDay = 24 * microsecondsPerHour + nanosPerMicro = 1000 + secondsPerDay = 24 * 60 * 60 +) + +// New creates a TimeOfDay representing the specified time. +func New(hour, min, sec, micro int) TimeOfDay { + hours := time.Duration(hour) * time.Hour + minutes := time.Duration(min) * time.Minute + seconds := time.Duration(sec) * time.Second + micros := time.Duration(micro) * time.Microsecond + return FromInt(int64((hours + minutes + seconds + micros) / time.Microsecond)) +} + +func (t TimeOfDay) String() string { + micros := t.Microsecond() + if micros > 0 { + s := fmt.Sprintf("%02d:%02d:%02d.%06d", t.Hour(), t.Minute(), t.Second(), micros) + return strings.TrimRight(s, "0") + } + return fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()) +} + +// FromInt constructs a TimeOfDay from an int64, representing microseconds since +// midnight. Inputs outside the range [0, microsecondsPerDay) are modded as +// appropriate. +func FromInt(i int64) TimeOfDay { + return TimeOfDay(positiveMod(i, microsecondsPerDay)) +} + +// positive_mod returns x mod y in the range [0, y). (Go's modulo operator +// preserves sign.) +func positiveMod(x, y int64) int64 { + if x < 0 { + return x%y + y + } + return x % y +} + +// FromTime constructs a TimeOfDay from a time.Time, ignoring the date and time zone. +func FromTime(t time.Time) TimeOfDay { + // Adjust for timezone offset so it won't affect the time. This is necessary + // at times, like when casting from a TIMESTAMP WITH TIME ZONE. + _, offset := t.Zone() + unixSeconds := t.Unix() + int64(offset) + + nanos := (unixSeconds%secondsPerDay)*int64(time.Second) + int64(t.Nanosecond()) + return FromInt(nanos / nanosPerMicro) +} + +// ToTime converts a TimeOfDay to a time.Time, using the Unix epoch as the date. +func (t TimeOfDay) ToTime() time.Time { + return timeutil.Unix(0, int64(t)*nanosPerMicro) +} + +// Random generates a random TimeOfDay. +func Random(rng *rand.Rand) TimeOfDay { + return TimeOfDay(rng.Int63n(microsecondsPerDay)) +} + +// Add adds a Duration to a TimeOfDay, wrapping into the next day if necessary. +func (t TimeOfDay) Add(d duration.Duration) TimeOfDay { + return FromInt(int64(t) + d.Nanos/nanosPerMicro) +} + +// Difference returns the interval between t1 and t2, which may be negative. +func Difference(t1 TimeOfDay, t2 TimeOfDay) duration.Duration { + return duration.Duration{Nanos: int64(t1-t2) * nanosPerMicro} +} + +// Hour returns the hour specified by t, in the range [0, 23]. +func (t TimeOfDay) Hour() int { + return int(int64(t)%microsecondsPerDay) / microsecondsPerHour +} + +// Minute returns the minute offset within the hour specified by t, in the +// range [0, 59]. +func (t TimeOfDay) Minute() int { + return int(int64(t)%microsecondsPerHour) / microsecondsPerMinute +} + +// Second returns the second offset within the minute specified by t, in the +// range [0, 59]. +func (t TimeOfDay) Second() int { + return int(int64(t)%microsecondsPerMinute) / microsecondsPerSecond +} + +// Microsecond returns the microsecond offset within the second specified by t, +// in the range [0, 999999]. +func (t TimeOfDay) Microsecond() int { + return int(int64(t) % microsecondsPerSecond) +} diff --git a/pkg/util/timeofday/time_of_day_test.go b/pkg/util/timeofday/time_of_day_test.go new file mode 100644 index 000000000000..e8c5c9ea5270 --- /dev/null +++ b/pkg/util/timeofday/time_of_day_test.go @@ -0,0 +1,115 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +package timeofday + +import ( + "fmt" + "testing" + "time" + + "github.com/cockroachdb/cockroach/pkg/util/duration" +) + +func TestString(t *testing.T) { + expected := "01:02:03.456789" + actual := New(1, 2, 3, 456789).String() + if actual != expected { + t.Errorf("expected %s, got %s", expected, actual) + } + testData := []struct { + t TimeOfDay + exp string + }{ + {New(1, 2, 3, 0), "01:02:03"}, + {New(1, 2, 3, 456000), "01:02:03.456"}, + {New(1, 2, 3, 456789), "01:02:03.456789"}, + } + for i, td := range testData { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + actual := td.t.String() + if actual != td.exp { + t.Errorf("expected %s, got %s", td.exp, actual) + } + }) + } +} + +func TestFromAndToTime(t *testing.T) { + testData := []struct { + s string + exp string + }{ + {"0000-01-01T00:00:00Z", "1970-01-01T00:00:00Z"}, + {"2017-01-01T12:00:00.5Z", "1970-01-01T12:00:00.5Z"}, + {"9999-12-31T23:59:59.999999Z", "1970-01-01T23:59:59.999999Z"}, + {"2017-01-01T12:00:00-05:00", "1970-01-01T12:00:00Z"}, + } + for _, td := range testData { + t.Run(td.s, func(t *testing.T) { + fromTime, err := time.Parse(time.RFC3339Nano, td.s) + if err != nil { + t.Fatal(err) + } + actual := FromTime(fromTime).ToTime().Format(time.RFC3339Nano) + if actual != td.exp { + t.Errorf("expected %s, got %s", td.exp, actual) + } + }) + } +} + +func TestAdd(t *testing.T) { + testData := []struct { + t TimeOfDay + micros int64 + exp TimeOfDay + }{ + {New(12, 0, 0, 0), 1, New(12, 0, 0, 1)}, + {New(12, 0, 0, 0), microsecondsPerDay, New(12, 0, 0, 0)}, + {Max, 1, Min}, + {Min, -1, Max}, + } + for _, td := range testData { + d := duration.Duration{Nanos: td.micros * nanosPerMicro} + t.Run(fmt.Sprintf("%s,%s", td.t, d), func(t *testing.T) { + actual := td.t.Add(d) + if actual != td.exp { + t.Errorf("expected %s, got %s", td.exp, actual) + } + }) + } +} + +func TestDifference(t *testing.T) { + testData := []struct { + t1 TimeOfDay + t2 TimeOfDay + expMicros int64 + }{ + {New(0, 0, 0, 0), New(0, 0, 0, 0), 0}, + {New(0, 0, 0, 1), New(0, 0, 0, 0), 1}, + {New(0, 0, 0, 0), New(0, 0, 0, 1), -1}, + {Max, Min, microsecondsPerDay - 1}, + {Min, Max, -1 * (microsecondsPerDay - 1)}, + } + for _, td := range testData { + t.Run(fmt.Sprintf("%s,%s", td.t1, td.t2), func(t *testing.T) { + actual := Difference(td.t1, td.t2).Nanos / nanosPerMicro + if actual != td.expMicros { + t.Errorf("expected %d, got %d", td.expMicros, actual) + } + }) + } +}