From b5884fa6c4fca8d3af5352bb435a757057afde9c Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 13:47:07 -0700 Subject: [PATCH 01/18] Adding precision to datetime --- engine.go | 2 +- enginetest/memory_engine_test.go | 19 ++++++++++++++++--- enginetest/queries/create_table_queries.go | 12 ++++++++++++ sql/types/conversion.go | 10 +++++++++- sql/types/datetime.go | 8 +++++--- sql/types/datetime_test.go | 12 ++++++------ 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/engine.go b/engine.go index 85f1da6331..439cb2b98d 100644 --- a/engine.go +++ b/engine.go @@ -351,7 +351,7 @@ func bindingsToExprs(bindings map[string]*query.BindVariable) (map[string]sql.Ex } res[k] = expression.NewLiteral(v, t) case v.Type() == sqltypes.Date || v.Type() == sqltypes.Datetime || v.Type() == sqltypes.Timestamp: - t, err := types.CreateDatetimeType(v.Type()) + t, err := types.CreateDatetimeType(v.Type(), 6) if err != nil { return nil, err } diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index 8cfa039e4d..5599942a6b 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -275,11 +275,24 @@ func TestSingleQueryPrepared(t *testing.T) { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - t.Skip() - var scripts = []queries.ScriptTest{} + // t.Skip() + var scripts = []queries.ScriptTest{ + { + Name: "datetime default precision", + SetUpScript: []string{ + "CREATE TABLE t1 (pk int primary key, d datetime)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "show create table t1", + Expected: []sql.Row{}, + }, + }, + }, + } for _, test := range scripts { - harness := enginetest.NewMemoryHarness("", 1, testNumPartitions, true, nil).WithVersion(sql.VersionExperimental) + harness := enginetest.NewMemoryHarness("", 1, testNumPartitions, true, nil) engine, err := harness.NewEngine(t) if err != nil { panic(err) diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index 75a4862076..3da19ca74f 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -246,6 +246,18 @@ var CreateTableScriptTests = []ScriptTest{ }, }, }, + { + Name: "datetime default precision", + SetUpScript: []string{ + "CREATE TABLE t1 (pk int primary key, d datetime)", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "show create table t1", + Expected: []sql.Row{}, + }, + }, + }, } var CreateTableAutoIncrementTests = []ScriptTest{ diff --git a/sql/types/conversion.go b/sql/types/conversion.go index 1f1cfd0400..5d8570b5fb 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -316,7 +316,15 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { case "year": return Year, nil case "date": - return Date, nil + precision := int64(0) + if ct.Length != nil { + var err error + precision, err = strconv.ParseInt(string(ct.Length.Val), 10, 64) + if err != nil { + return nil, err + } + } + return CreateDatetimeType(sqltypes.Date, int(precision)) case "time": if ct.Length != nil { length, err := strconv.ParseInt(string(ct.Length.Val), 10, 64) diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 8cd3b5d331..b4e1b50440 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -15,6 +15,7 @@ package types import ( + "fmt" "math" "reflect" "time" @@ -88,13 +89,14 @@ var ( type datetimeType struct { baseType query.Type + precision int } var _ sql.DatetimeType = datetimeType{} var _ sql.CollationCoercible = datetimeType{} // CreateDatetimeType creates a Type dealing with all temporal types that are not TIME nor YEAR. -func CreateDatetimeType(baseType query.Type) (sql.DatetimeType, error) { +func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, error) { switch baseType { case sqltypes.Date, sqltypes.Datetime, sqltypes.Timestamp: return datetimeType{ @@ -106,7 +108,7 @@ func CreateDatetimeType(baseType query.Type) (sql.DatetimeType, error) { // MustCreateDatetimeType is the same as CreateDatetimeType except it panics on errors. func MustCreateDatetimeType(baseType query.Type) sql.DatetimeType { - dt, err := CreateDatetimeType(baseType) + dt, err := CreateDatetimeType(baseType, 0) if err != nil { panic(err) } @@ -390,7 +392,7 @@ func (t datetimeType) String() string { case sqltypes.Date: return "date" case sqltypes.Datetime: - return "datetime(6)" + return fmt.Sprintf("datetime(%d)", t.precision) case sqltypes.Timestamp: return "timestamp(6)" default: diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index 2e63a326bb..e491388bc1 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -20,7 +20,7 @@ import ( "testing" "time" - "github.com/dolthub/vitess/go/sqltypes" + sqltypes "github.com/dolthub/vitess/go/sqltypes" "github.com/dolthub/vitess/go/vt/proto/query" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -74,14 +74,14 @@ func TestDatetimeCreate(t *testing.T) { expectedType datetimeType expectedErr bool }{ - {sqltypes.Date, datetimeType{sqltypes.Date}, false}, - {sqltypes.Datetime, datetimeType{sqltypes.Datetime}, false}, - {sqltypes.Timestamp, datetimeType{sqltypes.Timestamp}, false}, + {baseType: sqltypes.Date, expectedType: datetimeType{baseType: sqltypes.Date}}, + {baseType: sqltypes.Datetime, expectedType: datetimeType{baseType: sqltypes.Datetime}}, + {baseType: sqltypes.Timestamp, expectedType: datetimeType{baseType: sqltypes.Timestamp}}, } for _, test := range tests { t.Run(fmt.Sprintf("%v", test.baseType), func(t *testing.T) { - typ, err := CreateDatetimeType(test.baseType) + typ, err := CreateDatetimeType(test.baseType, 0) if test.expectedErr { assert.Error(t, err) } else { @@ -130,7 +130,7 @@ func TestDatetimeCreateInvalidBaseTypes(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprintf("%v", test.baseType), func(t *testing.T) { - typ, err := CreateDatetimeType(test.baseType) + typ, err := CreateDatetimeType(test.baseType, 0) if test.expectedErr { assert.Error(t, err) } else { From 28982ca5fe9950b43eea31792c0275d0fb63bed2 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 14:01:20 -0700 Subject: [PATCH 02/18] Datetime precision test (failing) --- enginetest/memory_engine_test.go | 18 ++++++++++++++++-- enginetest/testdata.go | 10 +++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index 5599942a6b..1d7b6d0a0c 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -18,6 +18,7 @@ import ( "fmt" "log" "testing" + "time" "github.com/dolthub/go-mysql-server/enginetest" "github.com/dolthub/go-mysql-server/enginetest/queries" @@ -278,14 +279,27 @@ func TestSingleScript(t *testing.T) { // t.Skip() var scripts = []queries.ScriptTest{ { - Name: "datetime default precision", + Name: "datetime precision", SetUpScript: []string{ "CREATE TABLE t1 (pk int primary key, d datetime)", }, Assertions: []queries.ScriptTestAssertion{ { Query: "show create table t1", - Expected: []sql.Row{}, + Expected: []sql.Row{{"t1", + "CREATE TABLE `t1` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t1 order by pk", + Expected: []sql.Row{{1, enginetest.MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, }, }, diff --git a/enginetest/testdata.go b/enginetest/testdata.go index 891d0d4e58..497954108b 100644 --- a/enginetest/testdata.go +++ b/enginetest/testdata.go @@ -228,6 +228,10 @@ func createNativeIndexes(t *testing.T, harness Harness, e *sqle.Engine) error { return nil } -func dob(year, month, day int) time.Time { - return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) -} +func MustParseTime(layout, value string) time.Time { + parsed, err := time.Parse(layout, value) + if err != nil { + panic(err) + } + return parsed +} \ No newline at end of file From 3cb9545409dca79f132233a3a7ee63dfc8fce605 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 15:30:18 -0700 Subject: [PATCH 03/18] Working precision for datetimes --- enginetest/memory_engine_test.go | 36 ++++++++++++++++++++++++++++++++ sql/types/conversion.go | 21 ++++++++++--------- sql/types/datetime.go | 10 +++++++++ sql/types/datetime_test.go | 4 +++- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index 1d7b6d0a0c..9ed11ada99 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -282,6 +282,8 @@ func TestSingleScript(t *testing.T) { Name: "datetime precision", SetUpScript: []string{ "CREATE TABLE t1 (pk int primary key, d datetime)", + "CREATE TABLE t2 (pk int primary key, d datetime(3))", + "CREATE TABLE t3 (pk int primary key, d datetime(6))", }, Assertions: []queries.ScriptTestAssertion{ { @@ -301,6 +303,40 @@ func TestSingleScript(t *testing.T) { Query: "select * from t1 order by pk", Expected: []sql.Row{{1, enginetest.MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, + { + Query: "show create table t2", + Expected: []sql.Row{{"t2", + "CREATE TABLE `t2` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t2 order by pk", + Expected: []sql.Row{{1, enginetest.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, + }, + { + Query: "show create table t3", + Expected: []sql.Row{{"t3", + "CREATE TABLE `t3` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t3 order by pk", + Expected: []sql.Row{{1, enginetest.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, + }, }, }, } diff --git a/sql/types/conversion.go b/sql/types/conversion.go index 5d8570b5fb..031a250b1d 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -316,15 +316,7 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { case "year": return Year, nil case "date": - precision := int64(0) - if ct.Length != nil { - var err error - precision, err = strconv.ParseInt(string(ct.Length.Val), 10, 64) - if err != nil { - return nil, err - } - } - return CreateDatetimeType(sqltypes.Date, int(precision)) + return CreateDatetimeType(sqltypes.Date, 0) case "time": if ct.Length != nil { length, err := strconv.ParseInt(string(ct.Length.Val), 10, 64) @@ -344,7 +336,16 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { case "timestamp": return Timestamp, nil case "datetime": - return Datetime, nil + precision := int64(0) + if ct.Length != nil { + var err error + precision, err = strconv.ParseInt(string(ct.Length.Val), 10, 64) + if err != nil { + return nil, err + } + } + + return CreateDatetimeType(sqltypes.Datetime, int(precision)) case "enum": collation, err := sql.ParseCollation(&ct.Charset, &ct.Collate, ct.BinaryCollate) if err != nil { diff --git a/sql/types/datetime.go b/sql/types/datetime.go index b4e1b50440..ab3bc14aef 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -101,6 +101,7 @@ func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, e case sqltypes.Date, sqltypes.Datetime, sqltypes.Timestamp: return datetimeType{ baseType: baseType, + precision: precision, }, nil } return nil, sql.ErrInvalidBaseType.New(baseType.String(), "datetime") @@ -163,6 +164,10 @@ func (t datetimeType) Convert(v interface{}) (interface{}, sql.ConvertInRange, e return res, sql.InRange, nil } +var precisionConversion = [7]int { + 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, +} + func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) { if v == nil { return time.Time{}, nil @@ -176,6 +181,11 @@ func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) { if res.Equal(zeroTime) { return zeroTime, nil } + + // Truncate the date to the precision of this type + truncationDuration := time.Second + truncationDuration /= time.Duration(precisionConversion[t.precision]) + res = res.Truncate(truncationDuration) switch t.baseType { case sqltypes.Date: diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index e491388bc1..4848f080cd 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -324,7 +324,9 @@ func TestDatetimeString(t *testing.T) { expectedStr string }{ {MustCreateDatetimeType(sqltypes.Date), "date"}, - {MustCreateDatetimeType(sqltypes.Datetime), "datetime(6)"}, + {MustCreateDatetimeType(sqltypes.Datetime), "datetime(0)"}, + {datetimeType{baseType: sqltypes.Datetime, precision: 3}, "datetime(3)"}, + {datetimeType{baseType: sqltypes.Datetime, precision: 6}, "datetime(6)"}, {MustCreateDatetimeType(sqltypes.Timestamp), "timestamp(6)"}, } From 4ba125b65307fdef2a646f13c30492d9002bb681 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 16:00:25 -0700 Subject: [PATCH 04/18] Bounds checking, moved test code around --- enginetest/memory_engine_test.go | 14 ++++- enginetest/queries/create_table_queries.go | 65 +++++++++++++++++++++- enginetest/queries/queries.go | 8 +++ enginetest/testdata.go | 45 --------------- sql/types/conversion.go | 4 ++ 5 files changed, 85 insertions(+), 51 deletions(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index 9ed11ada99..f9f6dd0c90 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -301,7 +301,7 @@ func TestSingleScript(t *testing.T) { }, { Query: "select * from t1 order by pk", - Expected: []sql.Row{{1, enginetest.MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, + Expected: []sql.Row{{1, queries.MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, { Query: "show create table t2", @@ -318,7 +318,7 @@ func TestSingleScript(t *testing.T) { }, { Query: "select * from t2 order by pk", - Expected: []sql.Row{{1, enginetest.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, + Expected: []sql.Row{{1, queries.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, }, { Query: "show create table t3", @@ -335,7 +335,15 @@ func TestSingleScript(t *testing.T) { }, { Query: "select * from t3 order by pk", - Expected: []sql.Row{{1, enginetest.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, + Expected: []sql.Row{{1, queries.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, + }, + { + Query: "create table t4 (pk int primary key, d datetime(-1))", + ExpectedErr: sql.ErrSyntaxError, + }, + { + Query: "create table t4 (pk int primary key, d datetime(7))", + ExpectedErrStr: "DATETIME supports precision from 0 to 6", }, }, }, diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index 3da19ca74f..b0d895c3d1 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -15,6 +15,8 @@ package queries import ( + "time" + "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" ) @@ -30,7 +32,7 @@ var CreateTableQueries = []WriteQueryTest{ WriteQuery: `CREATE TABLE t1 (a INTEGER, b TEXT, c DATE, d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL, b1 BOOL, b2 BOOLEAN NOT NULL, g DATETIME, h CHAR(40))`, ExpectedWriteResult: []sql.Row{{types.NewOkResult(0)}}, SelectQuery: "SHOW CREATE TABLE t1", - ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp(6),\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime(6),\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp(6),\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime(0),\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { WriteQuery: `CREATE TABLE t1 (a INTEGER NOT NULL PRIMARY KEY, b VARCHAR(10) NOT NULL)`, @@ -247,14 +249,71 @@ var CreateTableScriptTests = []ScriptTest{ }, }, { - Name: "datetime default precision", + Name: "datetime precision", SetUpScript: []string{ "CREATE TABLE t1 (pk int primary key, d datetime)", + "CREATE TABLE t2 (pk int primary key, d datetime(3))", + "CREATE TABLE t3 (pk int primary key, d datetime(6))", }, Assertions: []ScriptTestAssertion{ { Query: "show create table t1", - Expected: []sql.Row{}, + Expected: []sql.Row{{"t1", + "CREATE TABLE `t1` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t1 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, + }, + { + Query: "show create table t2", + Expected: []sql.Row{{"t2", + "CREATE TABLE `t2` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t2 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, + }, + { + Query: "show create table t3", + Expected: []sql.Row{{"t3", + "CREATE TABLE `t3` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t3 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, + }, + { + Query: "create table t4 (pk int primary key, d datetime(-1))", + ExpectedErr: sql.ErrSyntaxError, + }, + { + Query: "create table t4 (pk int primary key, d datetime(7))", + ExpectedErrStr: "DATETIME supports precision from 0 to 6", }, }, }, diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 68bbe10133..5bbe7b24e3 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -10376,3 +10376,11 @@ var IndexPrefixQueries = []ScriptTest{ }, }, } + +func MustParseTime(layout, value string) time.Time { + parsed, err := time.Parse(layout, value) + if err != nil { + panic(err) + } + return parsed +} \ No newline at end of file diff --git a/enginetest/testdata.go b/enginetest/testdata.go index 497954108b..a65084979a 100644 --- a/enginetest/testdata.go +++ b/enginetest/testdata.go @@ -16,11 +16,9 @@ package enginetest import ( "testing" - "time" "github.com/stretchr/testify/require" - sqle "github.com/dolthub/go-mysql-server" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/mysql_db" "github.com/dolthub/go-mysql-server/sql/types" @@ -192,46 +190,3 @@ func DeleteRows(t *testing.T, ctx *sql.Context, table sql.DeletableTable, rows . } require.NoError(t, deleter.Close(ctx)) } - -func setAutoIncrementValue(t *testing.T, ctx *sql.Context, table sql.AutoIncrementTable, val uint64) { - setter := table.AutoIncrementSetter(ctx) - require.NoError(t, setter.SetAutoIncrementValue(ctx, val)) - require.NoError(t, setter.Close(ctx)) -} - -func createNativeIndexes(t *testing.T, harness Harness, e *sqle.Engine) error { - createIndexes := []string{ - "create unique index mytable_s on mytable (s)", - "create index mytable_i_s on mytable (i,s)", - "create index othertable_s2 on othertable (s2)", - "create index othertable_s2_i2 on othertable (s2,i2)", - "create index floattable_f on floattable (f64)", - "create index niltable_i2 on niltable (i2)", - "create index people_l_f on people (last_name,first_name)", - "create index datetime_table_d on datetime_table (date_col)", - "create index datetime_table_dt on datetime_table (datetime_col)", - "create index datetime_table_ts on datetime_table (timestamp_col)", - "create index one_pk_two_idx_1 on one_pk_two_idx (v1)", - "create index one_pk_two_idx_2 on one_pk_two_idx (v1, v2)", - "create index one_pk_three_idx_idx on one_pk_three_idx (v1, v2, v3)", - } - - for _, q := range createIndexes { - ctx := NewContext(harness) - sch, iter, err := e.Query(ctx, q) - require.NoError(t, err) - - _, err = sql.RowIterToRows(ctx, sch, iter) - require.NoError(t, err) - } - - return nil -} - -func MustParseTime(layout, value string) time.Time { - parsed, err := time.Parse(layout, value) - if err != nil { - panic(err) - } - return parsed -} \ No newline at end of file diff --git a/sql/types/conversion.go b/sql/types/conversion.go index 031a250b1d..fa8c3a7199 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -343,6 +343,10 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { if err != nil { return nil, err } + + if precision > 6 || precision < 0 { + return nil, fmt.Errorf("DATETIME supports precision from 0 to 6") + } } return CreateDatetimeType(sqltypes.Datetime, int(precision)) From 0d30e0740c48a74e96e09272a940b21d0f60960f Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 16:28:34 -0700 Subject: [PATCH 05/18] Timestamp precision support --- enginetest/queries/create_table_queries.go | 73 +++++++++++++++++++++- sql/types/conversion.go | 15 ++++- sql/types/datetime.go | 4 +- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index b0d895c3d1..56390d693a 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -32,7 +32,7 @@ var CreateTableQueries = []WriteQueryTest{ WriteQuery: `CREATE TABLE t1 (a INTEGER, b TEXT, c DATE, d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL, b1 BOOL, b2 BOOLEAN NOT NULL, g DATETIME, h CHAR(40))`, ExpectedWriteResult: []sql.Row{{types.NewOkResult(0)}}, SelectQuery: "SHOW CREATE TABLE t1", - ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp(6),\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime(0),\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp(0),\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime(0),\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { WriteQuery: `CREATE TABLE t1 (a INTEGER NOT NULL PRIMARY KEY, b VARCHAR(10) NOT NULL)`, @@ -147,7 +147,7 @@ var CreateTableQueries = []WriteQueryTest{ )`, ExpectedWriteResult: []sql.Row{{types.NewOkResult(0)}}, SelectQuery: "SHOW CREATE TABLE td", - ExpectedSelect: []sql.Row{sql.Row{"td", "CREATE TABLE `td` (\n `pk` int NOT NULL,\n `col2` int NOT NULL DEFAULT '2',\n `col3` double NOT NULL DEFAULT (round(-1.58,0)),\n `col4` varchar(10) DEFAULT 'new row',\n `col5` float DEFAULT '33.33',\n `col6` int DEFAULT NULL,\n `col7` timestamp(6) DEFAULT (NOW()),\n `col8` bigint DEFAULT (NOW()),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + ExpectedSelect: []sql.Row{sql.Row{"td", "CREATE TABLE `td` (\n `pk` int NOT NULL,\n `col2` int NOT NULL DEFAULT '2',\n `col3` double NOT NULL DEFAULT (round(-1.58,0)),\n `col4` varchar(10) DEFAULT 'new row',\n `col5` float DEFAULT '33.33',\n `col6` int DEFAULT NULL,\n `col7` timestamp(0) DEFAULT (NOW()),\n `col8` bigint DEFAULT (NOW()),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { WriteQuery: `CREATE TABLE t1 (i int PRIMARY KEY, j varchar(MAX))`, @@ -317,6 +317,75 @@ var CreateTableScriptTests = []ScriptTest{ }, }, }, + { + Name: "timestamp precision", + SetUpScript: []string{ + "CREATE TABLE t1 (pk int primary key, d timestamp)", + "CREATE TABLE t2 (pk int primary key, d timestamp(3))", + "CREATE TABLE t3 (pk int primary key, d timestamp(6))", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "show create table t1", + Expected: []sql.Row{{"t1", + "CREATE TABLE `t1` (\n" + + " `pk` int NOT NULL,\n" + + " `d` timestamp(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t1 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, + }, + { + Query: "show create table t2", + Expected: []sql.Row{{"t2", + "CREATE TABLE `t2` (\n" + + " `pk` int NOT NULL,\n" + + " `d` timestamp(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t2 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, + }, + { + Query: "show create table t3", + Expected: []sql.Row{{"t3", + "CREATE TABLE `t3` (\n" + + " `pk` int NOT NULL,\n" + + " `d` timestamp(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + { + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from t3 order by pk", + Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, + }, + { + Query: "create table t4 (pk int primary key, d TIMESTAMP(-1))", + ExpectedErr: sql.ErrSyntaxError, + }, + { + Query: "create table t4 (pk int primary key, d TIMESTAMP(7))", + ExpectedErrStr: "TIMESTAMP supports precision from 0 to 6", + }, + }, + }, } var CreateTableAutoIncrementTests = []ScriptTest{ diff --git a/sql/types/conversion.go b/sql/types/conversion.go index fa8c3a7199..54a90c5aa8 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -334,7 +334,20 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { } return Time, nil case "timestamp": - return Timestamp, nil + precision := int64(0) + if ct.Length != nil { + var err error + precision, err = strconv.ParseInt(string(ct.Length.Val), 10, 64) + if err != nil { + return nil, err + } + + if precision > 6 || precision < 0 { + return nil, fmt.Errorf("TIMESTAMP supports precision from 0 to 6") + } + } + + return CreateDatetimeType(sqltypes.Timestamp, int(precision)) case "datetime": precision := int64(0) if ct.Length != nil { diff --git a/sql/types/datetime.go b/sql/types/datetime.go index ab3bc14aef..b5e341aaca 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -164,6 +164,8 @@ func (t datetimeType) Convert(v interface{}) (interface{}, sql.ConvertInRange, e return res, sql.InRange, nil } +// precisionConversion is a conversion ratio to divide time.Second by to truncate the appropriate amount for the +// precision of a type with time info var precisionConversion = [7]int { 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, } @@ -404,7 +406,7 @@ func (t datetimeType) String() string { case sqltypes.Datetime: return fmt.Sprintf("datetime(%d)", t.precision) case sqltypes.Timestamp: - return "timestamp(6)" + return fmt.Sprintf("timestamp(%d)", t.precision) default: panic(sql.ErrInvalidBaseType.New(t.baseType.String(), "datetime")) } From 3c9e421773a08d43c01d9a181d5231eedd57c0ed Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 15 Aug 2023 17:09:51 -0700 Subject: [PATCH 06/18] Fix tests broken by new default precision --- enginetest/enginetests.go | 8 ++++---- enginetest/queries/alter_table_queries.go | 2 +- enginetest/queries/information_schema_queries.go | 8 ++++---- enginetest/queries/script_queries.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/enginetest/enginetests.go b/enginetest/enginetests.go index abadae07da..57dd7a6c6b 100644 --- a/enginetest/enginetests.go +++ b/enginetest/enginetests.go @@ -5601,8 +5601,8 @@ func TestColumnDefaults(t *testing.T, harness Harness) { e.Query(ctx, "set @@session.time_zone='SYSTEM';") // TODO: NOW() and CURRENT_TIMESTAMP() are supposed to be the same function in MySQL, but we have two different // implementations with slightly different behavior. - TestQueryWithContext(t, ctx, e, harness, "CREATE TABLE t10(pk BIGINT PRIMARY KEY, v1 DATETIME DEFAULT NOW(), v2 DATETIME DEFAULT CURRENT_TIMESTAMP(),"+ - "v3 TIMESTAMP DEFAULT NOW(), v4 TIMESTAMP DEFAULT CURRENT_TIMESTAMP())", []sql.Row{{types.NewOkResult(0)}}, nil, nil) + TestQueryWithContext(t, ctx, e, harness, "CREATE TABLE t10(pk BIGINT PRIMARY KEY, v1 DATETIME(6) DEFAULT NOW(), v2 DATETIME(6) DEFAULT CURRENT_TIMESTAMP(),"+ + "v3 TIMESTAMP(6) DEFAULT NOW(), v4 TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP())", []sql.Row{{types.NewOkResult(0)}}, nil, nil) // truncating time to microseconds for compatibility with integrators who may store more precision (go gives nanos) now := time.Now().Truncate(time.Microsecond).UTC() @@ -5824,14 +5824,14 @@ func TestColumnDefaults(t *testing.T, harness Harness) { }) t.Run("Column defaults with functions", func(t *testing.T) { - TestQueryWithContext(t, ctx, e, harness, "CREATE TABLE t33(pk varchar(100) DEFAULT (replace(UUID(), '-', '')), v1 timestamp DEFAULT now(), v2 varchar(100), primary key (pk))", []sql.Row{{types.NewOkResult(0)}}, nil, nil) + TestQueryWithContext(t, ctx, e, harness, "CREATE TABLE t33(pk varchar(100) DEFAULT (replace(UUID(), '-', '')), v1 timestamp(6) DEFAULT now(), v2 varchar(100), primary key (pk))", []sql.Row{{types.NewOkResult(0)}}, nil, nil) TestQueryWithContext(t, ctx, e, harness, "insert into t33 (v2) values ('abc')", []sql.Row{{types.NewOkResult(1)}}, nil, nil) TestQueryWithContext(t, ctx, e, harness, "select count(*) from t33", []sql.Row{{1}}, nil, nil) RunQuery(t, e, harness, "alter table t33 add column name varchar(100)") RunQuery(t, e, harness, "alter table t33 rename column v1 to v1_new") RunQuery(t, e, harness, "alter table t33 rename column name to name2") RunQuery(t, e, harness, "alter table t33 drop column name2") - RunQuery(t, e, harness, "alter table t33 add column v3 datetime default CURRENT_TIMESTAMP()") + RunQuery(t, e, harness, "alter table t33 add column v3 datetime(6) default CURRENT_TIMESTAMP()") TestQueryWithContext(t, ctx, e, harness, "desc t33", []sql.Row{ {"pk", "varchar(100)", "NO", "PRI", "(replace(uuid(), '-', ''))", "DEFAULT_GENERATED"}, diff --git a/enginetest/queries/alter_table_queries.go b/enginetest/queries/alter_table_queries.go index f9c7886dc5..62b0a129bb 100755 --- a/enginetest/queries/alter_table_queries.go +++ b/enginetest/queries/alter_table_queries.go @@ -48,7 +48,7 @@ var AlterTableScripts = []ScriptTest{ // https://github.com/dolthub/dolt/issues/6206 Name: "alter table containing column default value expressions", SetUpScript: []string{ - "create table t (pk int primary key, col1 timestamp default current_timestamp(), col2 varchar(1000), index idx1 (pk, col1));", + "create table t (pk int primary key, col1 timestamp(6) default current_timestamp(), col2 varchar(1000), index idx1 (pk, col1));", }, Assertions: []ScriptTestAssertion{ { diff --git a/enginetest/queries/information_schema_queries.go b/enginetest/queries/information_schema_queries.go index 6b9194b3ca..7a230e6f0f 100644 --- a/enginetest/queries/information_schema_queries.go +++ b/enginetest/queries/information_schema_queries.go @@ -972,8 +972,8 @@ FROM INFORMATION_SCHEMA.TRIGGERS WHERE trigger_schema = 'mydb'`, {"about", "id", nil, "NO", "int unsigned", "UNI", nil, "auto_increment"}, {"about", "uuid", nil, "NO", "char(36)", "PRI", 36, ""}, {"about", "status", "draft", "NO", "varchar(255)", "", 255, ""}, - {"about", "date_created", nil, "YES", "timestamp(6)", "", nil, ""}, - {"about", "date_updated", nil, "YES", "timestamp(6)", "", nil, ""}, + {"about", "date_created", nil, "YES", "timestamp(0)", "", nil, ""}, + {"about", "date_updated", nil, "YES", "timestamp(0)", "", nil, ""}, {"about", "url_key", nil, "NO", "varchar(255)", "UNI", 255, ""}, }, }, @@ -1095,7 +1095,7 @@ bit_2 bit(2) DEFAULT 2, some_blob blob DEFAULT ("abc"), char_1 char(1) DEFAULT "A", some_date date DEFAULT "2022-02-22", -date_time datetime DEFAULT "2022-02-22 22:22:21", +date_time datetime(6) DEFAULT "2022-02-22 22:22:21", decimal_52 decimal(5,2) DEFAULT "994.45", some_double double DEFAULT "1.1", some_enum enum('s','m','l') DEFAULT "s", @@ -1115,7 +1115,7 @@ some_set set('one','two') DEFAULT "one,two", small_int smallint DEFAULT "5", some_text text DEFAULT ("abc"), time_6 time(6) DEFAULT "11:59:59.000010", -time_stamp timestamp DEFAULT (CURRENT_TIMESTAMP()), +time_stamp timestamp(6) DEFAULT (CURRENT_TIMESTAMP()), tiny_blob tinyblob DEFAULT ("abc"), tiny_int tinyint DEFAULT "4", tiny_text tinytext DEFAULT ("abc"), diff --git a/enginetest/queries/script_queries.go b/enginetest/queries/script_queries.go index f41a53e8ac..972222d790 100644 --- a/enginetest/queries/script_queries.go +++ b/enginetest/queries/script_queries.go @@ -1859,7 +1859,7 @@ var ScriptTests = []ScriptTest{ "create table t2(c int primary key, d varchar(10))", "alter table t2 add constraint t2du unique (d)", "alter table t2 add constraint fk1 foreign key (d) references t1 (b)", - "create table t3 (a int, b varchar(100), c datetime, primary key (b,a))", + "create table t3 (a int, b varchar(100), c datetime(6), primary key (b,a))", "create table t4 (a int default (floor(1)), b int default (coalesce(a, 10)))", }, Assertions: []ScriptTestAssertion{ From 31af09c96c135639e42f809b83eae141d3b056f1 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 10:43:15 -0700 Subject: [PATCH 07/18] PR feedback and test fixes --- engine.go | 6 +++++- engine_test.go | 4 ++-- server/handler_test.go | 6 +++--- sql/types/datetime.go | 20 +++++++++++++------- sql/types/datetime_test.go | 12 ++++++------ 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/engine.go b/engine.go index 439cb2b98d..c4ab50b170 100644 --- a/engine.go +++ b/engine.go @@ -351,7 +351,11 @@ func bindingsToExprs(bindings map[string]*query.BindVariable) (map[string]sql.Ex } res[k] = expression.NewLiteral(v, t) case v.Type() == sqltypes.Date || v.Type() == sqltypes.Datetime || v.Type() == sqltypes.Timestamp: - t, err := types.CreateDatetimeType(v.Type(), 6) + precision := 6 + if v.Type() == sqltypes.Date { + precision = 0 + } + t, err := types.CreateDatetimeType(v.Type(), precision) if err != nil { return nil, err } diff --git a/engine_test.go b/engine_test.go index f3c1c2b74a..0986f10d5e 100755 --- a/engine_test.go +++ b/engine_test.go @@ -126,8 +126,8 @@ func TestBindingsToExprs(t *testing.T) { "bit": expression.NewLiteral(uint64(0x0f), types.MustCreateBitType(types.BitTypeMaxBits)), "date": expression.NewLiteral(time.Date(2020, time.Month(10), 20, 0, 0, 0, 0, time.UTC), types.Date), "year": expression.NewLiteral(int16(2020), types.Year), - "datetime": expression.NewLiteral(time.Date(2020, time.Month(10), 20, 12, 0, 0, 0, time.UTC), types.Datetime), - "timestamp": expression.NewLiteral(time.Date(2020, time.Month(10), 20, 12, 0, 0, 0, time.UTC), types.Timestamp), + "datetime": expression.NewLiteral(time.Date(2020, time.Month(10), 20, 12, 0, 0, 0, time.UTC), types.MustCreateDatetimeType(query.Type_DATETIME, 6)), + "timestamp": expression.NewLiteral(time.Date(2020, time.Month(10), 20, 12, 0, 0, 0, time.UTC), types.MustCreateDatetimeType(query.Type_TIMESTAMP, 6)), }, false, }, diff --git a/server/handler_test.go b/server/handler_test.go index cfc55dda50..b68fc1f0eb 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -671,9 +671,9 @@ func TestSchemaToFields(t *testing.T) { {Name: "bit12", Type: types.MustCreateBitType(12)}, // Dates - {Name: "datetime", Type: types.MustCreateDatetimeType(sqltypes.Datetime)}, - {Name: "timestamp", Type: types.MustCreateDatetimeType(sqltypes.Timestamp)}, - {Name: "date", Type: types.MustCreateDatetimeType(sqltypes.Date)}, + {Name: "datetime", Type: types.MustCreateDatetimeType(sqltypes.Datetime, 0)}, + {Name: "timestamp", Type: types.MustCreateDatetimeType(sqltypes.Timestamp, 0)}, + {Name: "date", Type: types.MustCreateDatetimeType(sqltypes.Date, 0)}, {Name: "time", Type: types.Time}, {Name: "year", Type: types.Year}, diff --git a/sql/types/datetime.go b/sql/types/datetime.go index b5e341aaca..ad9ae86543 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -78,11 +78,11 @@ var ( zeroTime = time.Unix(-62167219200, 0).UTC() // Date is a date with day, month and year. - Date = MustCreateDatetimeType(sqltypes.Date) + Date = MustCreateDatetimeType(sqltypes.Date, 0) // Datetime is a date and a time - Datetime = MustCreateDatetimeType(sqltypes.Datetime) + Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0) // Timestamp is an UNIX timestamp. - Timestamp = MustCreateDatetimeType(sqltypes.Timestamp) + Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) datetimeValueType = reflect.TypeOf(time.Time{}) ) @@ -108,8 +108,8 @@ func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, e } // MustCreateDatetimeType is the same as CreateDatetimeType except it panics on errors. -func MustCreateDatetimeType(baseType query.Type) sql.DatetimeType { - dt, err := CreateDatetimeType(baseType, 0) +func MustCreateDatetimeType(baseType query.Type, precision int) sql.DatetimeType { + dt, err := CreateDatetimeType(baseType, precision) if err != nil { panic(err) } @@ -404,9 +404,15 @@ func (t datetimeType) String() string { case sqltypes.Date: return "date" case sqltypes.Datetime: - return fmt.Sprintf("datetime(%d)", t.precision) + if t.precision > 0 { + return fmt.Sprintf("datetime(%d)", t.precision) + } + return "datetime" case sqltypes.Timestamp: - return fmt.Sprintf("timestamp(%d)", t.precision) + if t.precision > 0 { + return fmt.Sprintf("timestamp(%d)", t.precision) + } + return "timestamp" default: panic(sql.ErrInvalidBaseType.New(t.baseType.String(), "datetime")) } diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index 4848f080cd..6bfa76b498 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -323,11 +323,11 @@ func TestDatetimeString(t *testing.T) { typ sql.Type expectedStr string }{ - {MustCreateDatetimeType(sqltypes.Date), "date"}, - {MustCreateDatetimeType(sqltypes.Datetime), "datetime(0)"}, + {MustCreateDatetimeType(sqltypes.Date, 0), "date"}, + {MustCreateDatetimeType(sqltypes.Datetime, 0), "datetime(0)"}, {datetimeType{baseType: sqltypes.Datetime, precision: 3}, "datetime(3)"}, {datetimeType{baseType: sqltypes.Datetime, precision: 6}, "datetime(6)"}, - {MustCreateDatetimeType(sqltypes.Timestamp), "timestamp(6)"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 0), "timestamp(6)"}, } for _, test := range tests { @@ -339,10 +339,10 @@ func TestDatetimeString(t *testing.T) { } func TestDatetimeZero(t *testing.T) { - _, ok := MustCreateDatetimeType(sqltypes.Date).Zero().(time.Time) + _, ok := MustCreateDatetimeType(sqltypes.Date, 0).Zero().(time.Time) require.True(t, ok) - _, ok = MustCreateDatetimeType(sqltypes.Datetime).Zero().(time.Time) + _, ok = MustCreateDatetimeType(sqltypes.Datetime, 0).Zero().(time.Time) require.True(t, ok) - _, ok = MustCreateDatetimeType(sqltypes.Timestamp).Zero().(time.Time) + _, ok = MustCreateDatetimeType(sqltypes.Timestamp, 0).Zero().(time.Time) require.True(t, ok) } From bc9a98c10f2ed66deefad720568254e8691f7126 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 10:51:02 -0700 Subject: [PATCH 08/18] Fixed date format eval --- sql/expression/function/date_format.go | 2 +- sql/types/datetime.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sql/expression/function/date_format.go b/sql/expression/function/date_format.go index 3dfee21659..3c805a931e 100644 --- a/sql/expression/function/date_format.go +++ b/sql/expression/function/date_format.go @@ -292,7 +292,7 @@ func (f *DateFormat) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - timeVal, _, err := types.Datetime.Convert(left) + timeVal, _, err := types.DatetimeMaxPrecision.Convert(left) if err != nil { return nil, err diff --git a/sql/types/datetime.go b/sql/types/datetime.go index ad9ae86543..70080b7323 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -79,8 +79,10 @@ var ( // Date is a date with day, month and year. Date = MustCreateDatetimeType(sqltypes.Date, 0) - // Datetime is a date and a time + // Datetime is a date and a time with default precision (no fractional seconds). Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0) + // DatetimeMaxPrecision is a date and a time with maximum precision + DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, 6) // Timestamp is an UNIX timestamp. Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) @@ -99,6 +101,9 @@ var _ sql.CollationCoercible = datetimeType{} func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, error) { switch baseType { case sqltypes.Date, sqltypes.Datetime, sqltypes.Timestamp: + if precision < 0 || precision > 6 { + return nil, fmt.Errorf("precision must be between 0 and 6, got %d", precision) + } return datetimeType{ baseType: baseType, precision: precision, From 3eb951785455eb84224ceaeb837d1978056e9fbd Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 11:17:54 -0700 Subject: [PATCH 09/18] Fixed many static references to types.Datetime with either a proper type check or the max precision version --- .../resolve_external_stored_procedures.go | 4 ++-- sql/expression/arithmetic.go | 5 +++-- sql/expression/case.go | 2 +- sql/expression/case_test.go | 2 +- sql/expression/comparison.go | 2 +- sql/expression/convert.go | 4 ++-- sql/expression/function/convert_tz.go | 6 ++--- sql/expression/function/date.go | 22 +++++++++---------- sql/expression/function/date_format_test.go | 2 +- sql/expression/function/extract.go | 2 +- sql/expression/function/function_test.go | 4 ++-- sql/expression/function/greatest_least.go | 8 ++++--- sql/expression/function/str_to_date.go | 2 +- sql/expression/function/time.go | 14 ++++++------ sql/expression/function/timediff.go | 14 ++++++------ sql/expression/function/timediff_test.go | 12 +++++----- sql/expression/in_test.go | 4 ++-- sql/parse/parse_test.go | 8 +++++++ sql/types/conversion.go | 2 +- sql/types/datetime.go | 8 ++++--- 20 files changed, 70 insertions(+), 57 deletions(-) diff --git a/sql/analyzer/resolve_external_stored_procedures.go b/sql/analyzer/resolve_external_stored_procedures.go index 2a48d7ac5f..30e4026ba5 100644 --- a/sql/analyzer/resolve_external_stored_procedures.go +++ b/sql/analyzer/resolve_external_stored_procedures.go @@ -51,7 +51,7 @@ var ( reflect.TypeOf(bool(false)): types.Int8, reflect.TypeOf(string("")): types.LongText, reflect.TypeOf([]byte{}): types.LongBlob, - reflect.TypeOf(time.Time{}): types.Datetime, + reflect.TypeOf(time.Time{}): types.DatetimeMaxPrecision, reflect.TypeOf(decimal.Decimal{}): types.InternalDecimalType, } // externalStoredProcedurePointerTypes maps a pointer type to a sql.Type for external stored procedures. @@ -71,7 +71,7 @@ var ( reflect.TypeOf((*bool)(nil)): types.Int8, reflect.TypeOf((*string)(nil)): types.LongText, reflect.TypeOf((*[]byte)(nil)): types.LongBlob, - reflect.TypeOf((*time.Time)(nil)): types.Datetime, + reflect.TypeOf((*time.Time)(nil)): types.DatetimeMaxPrecision, reflect.TypeOf((*decimal.Decimal)(nil)): types.InternalDecimalType, } ) diff --git a/sql/expression/arithmetic.go b/sql/expression/arithmetic.go index d6ba1edef5..ec2bc3d877 100644 --- a/sql/expression/arithmetic.go +++ b/sql/expression/arithmetic.go @@ -119,7 +119,7 @@ func (a *Arithmetic) DebugString() string { // IsNullable implements the sql.Expression interface. func (a *Arithmetic) IsNullable() bool { - if a.Type() == types.Timestamp || a.Type() == types.Datetime { + if types.IsDatetimeType(a.Type()) || types.IsTimestampType(a.Type()) { return true } @@ -140,7 +140,8 @@ func (a *Arithmetic) Type() sql.Type { // applies for + and - ops if isInterval(a.Left) || isInterval(a.Right) { - return types.Datetime + // TODO: we might need to truncate precision here + return types.DatetimeMaxPrecision } if types.IsTime(lTyp) && types.IsTime(rTyp) { diff --git a/sql/expression/case.go b/sql/expression/case.go index 6ed27116b5..1e5f69db00 100644 --- a/sql/expression/case.go +++ b/sql/expression/case.go @@ -62,7 +62,7 @@ func combinedCaseBranchType(left, right sql.Type) sql.Type { if left == right { return left } - return types.Datetime + return types.DatetimeMaxPrecision } if types.IsNumber(left) && types.IsNumber(right) { if left == types.Float64 || right == types.Float64 { diff --git a/sql/expression/case_test.go b/sql/expression/case_test.go index 4525a90482..68033649e9 100644 --- a/sql/expression/case_test.go +++ b/sql/expression/case_test.go @@ -241,7 +241,7 @@ func TestCaseType(t *testing.T) { { "date and timestamp becomes datetime", caseExpr(NewLiteral("2020-04-07", types.Date), NewLiteral("2020-04-07T00:00:00Z", types.Timestamp)), - types.Datetime, + types.DatetimeMaxPrecision, }, } diff --git a/sql/expression/comparison.go b/sql/expression/comparison.go index 98fdb9aa13..fad6183b6c 100644 --- a/sql/expression/comparison.go +++ b/sql/expression/comparison.go @@ -171,7 +171,7 @@ func (c *comparison) castLeftAndRight(left, right interface{}) (interface{}, int return nil, nil, nil, err } - return l, r, types.Datetime, nil + return l, r, types.DatetimeMaxPrecision, nil } if types.IsBinaryType(leftType) || types.IsBinaryType(rightType) { diff --git a/sql/expression/convert.go b/sql/expression/convert.go index 5898d01a2e..0863ddd18f 100644 --- a/sql/expression/convert.go +++ b/sql/expression/convert.go @@ -120,7 +120,7 @@ func (c *Convert) Type() sql.Type { case ConvertToDate: return types.Date case ConvertToDatetime: - return types.Datetime + return types.DatetimeMaxPrecision case ConvertToDecimal: if c.cachedDecimalType == nil { c.cachedDecimalType = createConvertedDecimalType(c.typeLength, c.typeScale, true) @@ -285,7 +285,7 @@ func convertValue(val interface{}, castTo string, originType sql.Type, typeLengt if !(isTime || isString || isBinary) { return nil, nil } - d, _, err := types.Datetime.Convert(val) + d, _, err := types.DatetimeMaxPrecision.Convert(val) if err != nil { return nil, err } diff --git a/sql/expression/function/convert_tz.go b/sql/expression/function/convert_tz.go index 0ee54ec711..2a6e0c84e1 100644 --- a/sql/expression/function/convert_tz.go +++ b/sql/expression/function/convert_tz.go @@ -62,7 +62,7 @@ func (c *ConvertTz) String() string { // Type implements the sql.Expression interface. func (c *ConvertTz) Type() sql.Type { - return types.Datetime + return types.DatetimeMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -93,7 +93,7 @@ func (c *ConvertTz) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } // If either the date, or the timezones/offsets are not correct types we return NULL. - datetime, err := types.Datetime.ConvertWithoutRangeCheck(dt) + datetime, err := types.DatetimeMaxPrecision.ConvertWithoutRangeCheck(dt) if err != nil { return nil, nil } @@ -121,7 +121,7 @@ func (c *ConvertTz) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - return types.Datetime.ConvertWithoutRangeCheck(converted) + return types.DatetimeMaxPrecision.ConvertWithoutRangeCheck(converted) } // Children implements the sql.Expression interface. diff --git a/sql/expression/function/date.go b/sql/expression/function/date.go index db97bf79b2..86d6c50d5c 100644 --- a/sql/expression/function/date.go +++ b/sql/expression/function/date.go @@ -108,7 +108,7 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - date, _, err := types.Datetime.Convert(val) + date, _, err := types.DatetimeMaxPrecision.Convert(val) if err != nil { ctx.Warn(1292, err.Error()) return nil, nil @@ -203,7 +203,7 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - date, _, err = types.Datetime.Convert(date) + date, _, err = types.DatetimeMaxPrecision.Convert(date) if err != nil { ctx.Warn(1292, err.Error()) return nil, nil @@ -325,7 +325,7 @@ func (t *DatetimeConversion) String() string { } func (t *DatetimeConversion) Type() sql.Type { - return types.Datetime + return types.DatetimeMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -342,7 +342,7 @@ func (t *DatetimeConversion) Eval(ctx *sql.Context, r sql.Row) (interface{}, err if err != nil { return nil, err } - ret, _, err := types.Datetime.Convert(e) + ret, _, err := types.DatetimeMaxPrecision.Convert(e) return ret, err } @@ -440,7 +440,7 @@ func (ut *UnixTimestamp) Eval(ctx *sql.Context, row sql.Row) (interface{}, error return nil, nil } - date, _, err = types.Datetime.Convert(date) + date, _, err = types.DatetimeMaxPrecision.Convert(date) if err != nil { // If we aren't able to convert the value to a date, return 0 and set // a warning to match MySQL's behavior @@ -473,7 +473,7 @@ var _ sql.FunctionExpression = (*FromUnixtime)(nil) var _ sql.CollationCoercible = (*FromUnixtime)(nil) func NewFromUnixtime(arg sql.Expression) sql.Expression { - return &FromUnixtime{NewUnaryFunc(arg, "FROM_UNIXTIME", types.Datetime)} + return &FromUnixtime{NewUnaryFunc(arg, "FROM_UNIXTIME", types.DatetimeMaxPrecision)} } // Description implements sql.FunctionExpression @@ -575,11 +575,11 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ // set type flags isInputDate := inputType == types.Date isInputTime := inputType == types.Time - isInputDatetime := inputType == types.Datetime || inputType == types.Timestamp + isInputDatetime := types.IsDatetimeType(inputType) || types.IsTimestampType(inputType) // result is Datetime if expression is Datetime or Timestamp if isInputDatetime { - return types.Datetime + return types.DatetimeMaxPrecision } // determine what kind of interval we're dealing with @@ -598,7 +598,7 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ if isInputDate { if isHmsInterval || isMixedInterval { // if interval contains time components, result is Datetime - return types.Datetime + return types.DatetimeMaxPrecision } else { // otherwise result is Date return types.Date @@ -609,7 +609,7 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ if isInputTime { if isYmdInterval || isMixedInterval { // if interval contains date components, result is Datetime - return types.Datetime + return types.DatetimeMaxPrecision } else { // otherwise result is Time return types.Time @@ -623,7 +623,7 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ return types.Date } else { // otherwise result is Datetime - return types.Datetime + return types.DatetimeMaxPrecision } } diff --git a/sql/expression/function/date_format_test.go b/sql/expression/function/date_format_test.go index 6aab6c6ee2..75bd97a417 100644 --- a/sql/expression/function/date_format_test.go +++ b/sql/expression/function/date_format_test.go @@ -160,7 +160,7 @@ func TestWeekYearFormatting(t *testing.T) { func TestDateFormatEval(t *testing.T) { dt := time.Date(2020, 2, 3, 4, 5, 6, 7000, time.UTC) - dateLit := expression.NewLiteral(dt, types.Datetime) + dateLit := expression.NewLiteral(dt, types.DatetimeMaxPrecision) format := expression.NewLiteral("%Y-%m-%d %H:%i:%s.%f", types.Text) nullLiteral := expression.NewLiteral(nil, types.Null) diff --git a/sql/expression/function/extract.go b/sql/expression/function/extract.go index 491cf8021f..7d28f1cb99 100644 --- a/sql/expression/function/extract.go +++ b/sql/expression/function/extract.go @@ -100,7 +100,7 @@ func (td *Extract) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - right, err = types.Datetime.ConvertWithoutRangeCheck(right) + right, err = types.DatetimeMaxPrecision.ConvertWithoutRangeCheck(right) if err != nil { ctx.Warn(1292, err.Error()) return nil, nil diff --git a/sql/expression/function/function_test.go b/sql/expression/function/function_test.go index d5033793de..c7fed8a712 100644 --- a/sql/expression/function/function_test.go +++ b/sql/expression/function/function_test.go @@ -82,7 +82,7 @@ func assertResultType(t *testing.T, expectedType sql.Type, result interface{}) { case string: assert.True(t, types.IsText(expectedType)) case time.Time: - assert.Equal(t, expectedType, types.Datetime) + assert.Equal(t, expectedType, types.DatetimeMaxPrecision) case bool: assert.Equal(t, expectedType, types.Boolean) case []byte: @@ -317,7 +317,7 @@ func toLiteralExpression(input interface{}) *expression.Literal { case string: return expression.NewLiteral(val, types.Text) case time.Time: - return expression.NewLiteral(val, types.Datetime) + return expression.NewLiteral(val, types.DatetimeMaxPrecision) case []byte: return expression.NewLiteral(string(val), types.Blob) default: diff --git a/sql/expression/function/greatest_least.go b/sql/expression/function/greatest_least.go index f2854ced62..ff8883c08d 100644 --- a/sql/expression/function/greatest_least.go +++ b/sql/expression/function/greatest_least.go @@ -125,13 +125,15 @@ func compEval( } + if types.IsDatetimeType(returnType) { + return selectedTime, nil + } + switch returnType { case types.Int64: return int64(selectedNum), nil case types.LongText: return selectedString, nil - case types.Datetime: - return selectedTime, nil } // sql.Float64 @@ -189,7 +191,7 @@ func compRetType(args ...sql.Expression) (sql.Type, error) { } else if allInt { return types.Int64, nil } else if allDatetime { - return types.Datetime, nil + return types.DatetimeMaxPrecision, nil } else { return types.Float64, nil } diff --git a/sql/expression/function/str_to_date.go b/sql/expression/function/str_to_date.go index aae6b157b2..1161f381c3 100644 --- a/sql/expression/function/str_to_date.go +++ b/sql/expression/function/str_to_date.go @@ -46,7 +46,7 @@ func (s StrToDate) String() string { // Type returns the expression type. func (s StrToDate) Type() sql.Type { - return types.Datetime + return types.DatetimeMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 301a02d2c0..09044aa46c 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -48,9 +48,9 @@ func getDate(ctx *sql.Context, return nil, nil } - date, err := types.Datetime.ConvertWithoutRangeCheck(val) + date, err := types.DatetimeMaxPrecision.ConvertWithoutRangeCheck(val) if err != nil { - date = types.Datetime.Zero().(time.Time) + date = types.DatetimeMaxPrecision.Zero().(time.Time) } return date, nil @@ -902,7 +902,7 @@ func (n *Now) Description() string { // Type implements the sql.Expression interface. func (n *Now) Type() sql.Type { - return types.Datetime + return types.DatetimeMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -1031,7 +1031,7 @@ func (ut *UTCTimestamp) Description() string { // Type implements the sql.Expression interface. func (ut *UTCTimestamp) Type() sql.Type { - return types.Datetime + return types.DatetimeMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -1149,7 +1149,7 @@ func (dtf *UnaryDatetimeFunc) EvalChild(ctx *sql.Context, row sql.Row) (interfac return nil, nil } - ret, _, err := types.Datetime.Convert(val) + ret, _, err := types.DatetimeMaxPrecision.Convert(val) return ret, err } @@ -1463,7 +1463,7 @@ func (c *CurrTimestamp) String() string { return fmt.Sprintf("CURRENT_TIMESTAMP(%s)", c.Args[0].String()) } -func (c *CurrTimestamp) Type() sql.Type { return types.Datetime } +func (c *CurrTimestamp) Type() sql.Type { return types.DatetimeMaxPrecision } func (c *CurrTimestamp) IsNullable() bool { for _, arg := range c.Args { @@ -1598,7 +1598,7 @@ func (t *Time) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { } // convert to date - date, err := types.Datetime.ConvertWithoutRangeCheck(v) + date, err := types.DatetimeMaxPrecision.ConvertWithoutRangeCheck(v) if err == nil { h, m, s := date.Clock() us := date.Nanosecond() / 1000 diff --git a/sql/expression/function/timediff.go b/sql/expression/function/timediff.go index 53f87fbb25..67e21f751f 100644 --- a/sql/expression/function/timediff.go +++ b/sql/expression/function/timediff.go @@ -76,7 +76,7 @@ func (td *TimeDiff) WithChildren(children ...sql.Expression) (sql.Expression, er } func convToDateOrTime(val interface{}) (interface{}, error) { - date, _, err := types.Datetime.Convert(val) + date, _, err := types.DatetimeMaxPrecision.Convert(val) if err == nil { return date, nil } @@ -205,13 +205,13 @@ func (d *DateDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - expr1, _, err = types.Datetime.Convert(expr1) + expr1, _, err = types.DatetimeMaxPrecision.Convert(expr1) if err != nil { return nil, err } expr1str := expr1.(time.Time).String()[:10] - expr1, _, _ = types.Datetime.Convert(expr1str) + expr1, _, _ = types.DatetimeMaxPrecision.Convert(expr1str) expr2, err := d.Right.Eval(ctx, row) if err != nil { @@ -221,13 +221,13 @@ func (d *DateDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - expr2, _, err = types.Datetime.Convert(expr2) + expr2, _, err = types.DatetimeMaxPrecision.Convert(expr2) if err != nil { return nil, err } expr2str := expr2.(time.Time).String()[:10] - expr2, _, _ = types.Datetime.Convert(expr2str) + expr2, _, _ = types.DatetimeMaxPrecision.Convert(expr2str) date1 := expr1.(time.Time) date2 := expr2.(time.Time) @@ -322,12 +322,12 @@ func (t *TimestampDiff) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return nil, nil } - expr1, _, err = types.Datetime.Convert(expr1) + expr1, _, err = types.DatetimeMaxPrecision.Convert(expr1) if err != nil { return nil, err } - expr2, _, err = types.Datetime.Convert(expr2) + expr2, _, err = types.DatetimeMaxPrecision.Convert(expr2) if err != nil { return nil, err } diff --git a/sql/expression/function/timediff_test.go b/sql/expression/function/timediff_test.go index ad9f5baf2c..0998b9a394 100644 --- a/sql/expression/function/timediff_test.go +++ b/sql/expression/function/timediff_test.go @@ -74,7 +74,7 @@ func TestTimeDiff(t *testing.T) { { "valid mismatch", expression.NewLiteral(time.Date(2008, time.December, 29, 1, 1, 1, 2, time.Local), types.Timestamp), - expression.NewLiteral(time.Date(2008, time.December, 30, 1, 1, 1, 2, time.Local), types.Datetime), + expression.NewLiteral(time.Date(2008, time.December, 30, 1, 1, 1, 2, time.Local), types.DatetimeMaxPrecision), toTimespan("-24:00:00"), false, }, @@ -108,8 +108,8 @@ func TestTimeDiff(t *testing.T) { }, { "datetime types", - expression.NewLiteral(time.Date(2008, time.December, 29, 0, 0, 0, 0, time.Local), types.Datetime), - expression.NewLiteral(time.Date(2008, time.December, 30, 0, 0, 0, 0, time.Local), types.Datetime), + expression.NewLiteral(time.Date(2008, time.December, 29, 0, 0, 0, 0, time.Local), types.DatetimeMaxPrecision), + expression.NewLiteral(time.Date(2008, time.December, 30, 0, 0, 0, 0, time.Local), types.DatetimeMaxPrecision), toTimespan("-24:00:00"), false, }, @@ -122,7 +122,7 @@ func TestTimeDiff(t *testing.T) { }, { "datetime string mix types", - expression.NewLiteral(time.Date(2008, time.December, 29, 0, 0, 0, 0, time.UTC), types.Datetime), + expression.NewLiteral(time.Date(2008, time.December, 29, 0, 0, 0, 0, time.UTC), types.DatetimeMaxPrecision), expression.NewLiteral("2008-12-30 00:00:00", types.Text), toTimespan("-24:00:00"), false, @@ -175,7 +175,7 @@ func TestDateDiff(t *testing.T) { expected interface{} err *errors.Kind }{ - {"time and text types, ", types.Datetime, types.Text, sql.NewRow(dt, "2019-12-28"), int64(3), nil}, + {"time and text types, ", types.DatetimeMaxPrecision, types.Text, sql.NewRow(dt, "2019-12-28"), int64(3), nil}, {"text types, diff day, less than 24 hours time diff", types.Text, types.Text, sql.NewRow("2007-12-31 23:58:59", "2007-12-30 23:59:59"), int64(1), nil}, {"text types, same day, 23:59:59 time diff", types.Text, types.Text, sql.NewRow("2007-12-30 23:59:59", "2007-12-30 00:00:00"), int64(0), nil}, {"text types, diff day, 1 min time diff", types.Text, types.Text, sql.NewRow("2007-12-31 00:00:59", "2007-12-30 23:59:59"), int64(1), nil}, @@ -224,7 +224,7 @@ func TestTimestampDiff(t *testing.T) { }{ {"invalid unit", types.Text, types.Text, types.Text, sql.NewRow("MILLISECOND", "2007-12-30 23:59:59", "2007-12-31 00:00:00"), nil, true}, {"microsecond", types.Text, types.Text, types.Text, sql.NewRow("MICROSECOND", "2007-12-30 23:59:59", "2007-12-31 00:00:00"), int64(1000000), false}, - {"microsecond - small number", types.Text, types.Datetime, types.Datetime, sql.NewRow("MICROSECOND", + {"microsecond - small number", types.Text, types.DatetimeMaxPrecision, types.DatetimeMaxPrecision, sql.NewRow("MICROSECOND", time.Date(2017, 11, 12, 16, 16, 25, 2*int(time.Microsecond), time.Local), time.Date(2017, 11, 12, 16, 16, 25, 333*int(time.Microsecond), time.Local)), int64(331), false}, {"microsecond - negative", types.Text, types.Text, types.Text, sql.NewRow("SQL_TSI_MICROSECOND", "2017-11-12 16:16:25.000022 +0000 UTC", "2017-11-12 16:16:25.000000 +0000 UTC"), int64(-22), false}, diff --git a/sql/expression/in_test.go b/sql/expression/in_test.go index d090c1b9c4..c23e76e90c 100644 --- a/sql/expression/in_test.go +++ b/sql/expression/in_test.go @@ -173,7 +173,7 @@ func TestInTuple(t *testing.T) { }, { name: "date on right side; non-dates on left", - left: expression.NewLiteral(time.Now(), types.Datetime), + left: expression.NewLiteral(time.Now(), types.DatetimeMaxPrecision), right: expression.NewTuple( expression.NewLiteral("hi", types.TinyText), expression.NewLiteral("bye", types.TinyText), @@ -539,7 +539,7 @@ func TestHashInTuple(t *testing.T) { }, { name: "date on right side; non-dates on left", - left: expression.NewLiteral(time.Now(), types.Datetime), + left: expression.NewLiteral(time.Now(), types.DatetimeMaxPrecision), right: expression.NewTuple( expression.NewLiteral("hi", types.TinyText), expression.NewLiteral("bye", types.TinyText), diff --git a/sql/parse/parse_test.go b/sql/parse/parse_test.go index 211f441ee4..79b3c084c6 100644 --- a/sql/parse/parse_test.go +++ b/sql/parse/parse_test.go @@ -5576,10 +5576,18 @@ func TestParseColumnTypeString(t *testing.T) { "TIMESTAMP", types.Timestamp, }, + { + "TIMESTAMP(6)", + types.TimestampMaxPrecision, + }, { "DATETIME", types.Datetime, }, + { + "DATETIME(6)", + types.DatetimeMaxPrecision, + }, } for _, test := range tests { diff --git a/sql/types/conversion.go b/sql/types/conversion.go index 54a90c5aa8..d8ebeb7df9 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -61,7 +61,7 @@ func ApproximateTypeFromValue(val interface{}) sql.Type { case Timespan, time.Duration: return Time case time.Time: - return Datetime + return DatetimeMaxPrecision case float32: return Float32 case float64: diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 70080b7323..25ef63f69e 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -83,9 +83,11 @@ var ( Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0) // DatetimeMaxPrecision is a date and a time with maximum precision DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, 6) - // Timestamp is an UNIX timestamp. + // Timestamp is an UNIX timestamp with default precision (no fractional seconds). Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) - + // TimestampMaxPrecision is an UNIX timestamp with maximum precision + TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, 6) + datetimeValueType = reflect.TypeOf(time.Time{}) ) @@ -355,7 +357,7 @@ func (t datetimeType) MaxTextResponseByteLength(_ *sql.Context) uint32 { // Promote implements the Type interface. func (t datetimeType) Promote() sql.Type { - return Datetime + return DatetimeMaxPrecision } // SQL implements Type interface. From 05bb1783fe1680bbab94758eb8edae1ba08b2bc8 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 11:47:02 -0700 Subject: [PATCH 10/18] Fixed datetime conversion tests --- sql/types/datetime_test.go | 213 +++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 103 deletions(-) diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index 6bfa76b498..ba0133b1bd 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -142,12 +142,13 @@ func TestDatetimeCreateInvalidBaseTypes(t *testing.T) { } func TestDatetimeConvert(t *testing.T) { - tests := []struct { + type testcase struct { typ sql.Type val interface{} expectedVal interface{} expectedErr bool - }{ + } + tests := []testcase { {Date, nil, nil, false}, {Date, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), time.Date(2012, 12, 12, 0, 0, 0, 0, time.UTC), false}, @@ -162,50 +163,54 @@ func TestDatetimeConvert(t *testing.T) { {Date, "20100603", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, {Date, "20100603121212", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, nil, nil, false}, - {Datetime, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), - time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), false}, - {Datetime, "2010-06-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, "2010-6-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, "2010-06-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, "2010-6-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:12:12.000012", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, - {Datetime, "2010-06-03T12:12:12Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-06-03T12:12:12.000012Z", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, - {Datetime, "20100603", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Datetime, "20100603121212", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-6-3 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-6-13 12:12:12", time.Date(2010, 6, 13, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-10-3 12:12:12", time.Date(2010, 10, 3, 12, 12, 12, 0, time.UTC), false}, - {Datetime, "2010-10-3 12:12:2", time.Date(2010, 10, 3, 12, 12, 2, 0, time.UTC), false}, - {Datetime, "2010-10-3 12:2:2", time.Date(2010, 10, 3, 12, 2, 2, 0, time.UTC), false}, + {DatetimeMaxPrecision, nil, nil, false}, + {DatetimeMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12345, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-6-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-6-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:12:12.000012", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03T12:12:12Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03T12:12:12.000012Z", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, + {DatetimeMaxPrecision, "20100603", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "20100603121212", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-6-3 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-6-13 12:12:12", time.Date(2010, 6, 13, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-10-3 12:12:12", time.Date(2010, 10, 3, 12, 12, 12, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-10-3 12:12:2", time.Date(2010, 10, 3, 12, 12, 2, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-10-3 12:2:2", time.Date(2010, 10, 3, 12, 2, 2, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:3", time.Date(2010, 6, 3, 12, 3, 0, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:.", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:5", time.Date(2010, 6, 3, 12, 34, 5, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:56", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:56.", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, - {Datetime, "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 700000000, time.UTC), false}, - {Datetime, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 780000000, time.UTC), false}, - {Datetime, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 789000000, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:3", time.Date(2010, 6, 3, 12, 3, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:.", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:5", time.Date(2010, 6, 3, 12, 34, 5, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:56", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:56.", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 700000000, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 780000000, time.UTC), false}, + {DatetimeMaxPrecision, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 789000000, time.UTC), false}, - {Timestamp, nil, nil, false}, - {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), - time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), false}, - {Timestamp, "2010-06-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, "2010-6-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, "2010-6-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, "2010-06-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, "2010-06-03 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Timestamp, "2010-06-03 12:12:12.000012", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, - {Timestamp, "2010-06-03T12:12:12Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Timestamp, "2010-06-03T12:12:12.000012Z", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, - {Timestamp, "20100603", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, "20100603121212", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, - {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC).UTC().String(), time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), false}, + {TimestampMaxPrecision, nil, nil, false}, + {TimestampMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {TimestampMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12345, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-6-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-6-03", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-3", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-03 12:12:12", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-03 12:12:12.000012", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-03T12:12:12Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {TimestampMaxPrecision, "2010-06-03T12:12:12.000012Z", time.Date(2010, 6, 3, 12, 12, 12, 12000, time.UTC), false}, + {TimestampMaxPrecision, "20100603", time.Date(2010, 6, 3, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, "20100603121212", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {TimestampMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12345, time.UTC).UTC().String(), time.Date(2012, 12, 12, 12, 12, 12, 12000, time.UTC), false}, {Date, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, "0500-01-01 00:00:00", time.Date(500, 1, 1, 0, 0, 0, 0, time.UTC), false}, @@ -227,40 +232,40 @@ func TestDatetimeConvert(t *testing.T) { {Date, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, []byte{0}, nil, true}, - {Datetime, "0500-01-01 01:01:01", time.Date(500, 1, 1, 1, 1, 1, 0, time.UTC), false}, - {Datetime, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, time.Date(10000, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, - {Datetime, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Datetime, []byte{0}, nil, true}, + {DatetimeMaxPrecision, "0500-01-01 01:01:01", time.Date(500, 1, 1, 1, 1, 1, 0, time.UTC), false}, + {DatetimeMaxPrecision, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, time.Date(10000, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, + {DatetimeMaxPrecision, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {DatetimeMaxPrecision, []byte{0}, nil, true}, - {Timestamp, time.Date(1960, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, - {Timestamp, "1970-01-01 00:00:00", nil, true}, - {Timestamp, "1970-01-01 00:00:01", time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), false}, - {Timestamp, time.Date(2040, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, - {Timestamp, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, - {Timestamp, []byte{0}, nil, true}, + {TimestampMaxPrecision, time.Date(1960, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, + {TimestampMaxPrecision, "1970-01-01 00:00:00", nil, true}, + {TimestampMaxPrecision, "1970-01-01 00:00:01", time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), false}, + {TimestampMaxPrecision, time.Date(2040, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, + {TimestampMaxPrecision, int(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, int64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint8(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint16(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, uint64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, float32(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, float64(0), time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, + {TimestampMaxPrecision, []byte{0}, nil, true}, {Date, int(1), nil, true}, {Date, int8(1), nil, true}, @@ -275,31 +280,31 @@ func TestDatetimeConvert(t *testing.T) { {Date, float32(1), nil, true}, {Date, float64(1), nil, true}, - {Datetime, int(1), nil, true}, - {Datetime, int8(1), nil, true}, - {Datetime, int16(1), nil, true}, - {Datetime, int32(1), nil, true}, - {Datetime, int64(1), nil, true}, - {Datetime, uint(1), nil, true}, - {Datetime, uint8(1), nil, true}, - {Datetime, uint16(1), nil, true}, - {Datetime, uint32(1), nil, true}, - {Datetime, uint64(1), nil, true}, - {Datetime, float32(1), nil, true}, - {Datetime, float64(1), nil, true}, + {DatetimeMaxPrecision, int(1), nil, true}, + {DatetimeMaxPrecision, int8(1), nil, true}, + {DatetimeMaxPrecision, int16(1), nil, true}, + {DatetimeMaxPrecision, int32(1), nil, true}, + {DatetimeMaxPrecision, int64(1), nil, true}, + {DatetimeMaxPrecision, uint(1), nil, true}, + {DatetimeMaxPrecision, uint8(1), nil, true}, + {DatetimeMaxPrecision, uint16(1), nil, true}, + {DatetimeMaxPrecision, uint32(1), nil, true}, + {DatetimeMaxPrecision, uint64(1), nil, true}, + {DatetimeMaxPrecision, float32(1), nil, true}, + {DatetimeMaxPrecision, float64(1), nil, true}, - {Timestamp, int(1), nil, true}, - {Timestamp, int8(1), nil, true}, - {Timestamp, int16(1), nil, true}, - {Timestamp, int32(1), nil, true}, - {Timestamp, int64(1), nil, true}, - {Timestamp, uint(1), nil, true}, - {Timestamp, uint8(1), nil, true}, - {Timestamp, uint16(1), nil, true}, - {Timestamp, uint32(1), nil, true}, - {Timestamp, uint64(1), nil, true}, - {Timestamp, float32(1), nil, true}, - {Timestamp, float64(1), nil, true}, + {TimestampMaxPrecision, int(1), nil, true}, + {TimestampMaxPrecision, int8(1), nil, true}, + {TimestampMaxPrecision, int16(1), nil, true}, + {TimestampMaxPrecision, int32(1), nil, true}, + {TimestampMaxPrecision, int64(1), nil, true}, + {TimestampMaxPrecision, uint(1), nil, true}, + {TimestampMaxPrecision, uint8(1), nil, true}, + {TimestampMaxPrecision, uint16(1), nil, true}, + {TimestampMaxPrecision, uint32(1), nil, true}, + {TimestampMaxPrecision, uint64(1), nil, true}, + {TimestampMaxPrecision, float32(1), nil, true}, + {TimestampMaxPrecision, float64(1), nil, true}, } for _, test := range tests { @@ -324,10 +329,12 @@ func TestDatetimeString(t *testing.T) { expectedStr string }{ {MustCreateDatetimeType(sqltypes.Date, 0), "date"}, - {MustCreateDatetimeType(sqltypes.Datetime, 0), "datetime(0)"}, + {MustCreateDatetimeType(sqltypes.Datetime, 0), "datetime"}, {datetimeType{baseType: sqltypes.Datetime, precision: 3}, "datetime(3)"}, {datetimeType{baseType: sqltypes.Datetime, precision: 6}, "datetime(6)"}, - {MustCreateDatetimeType(sqltypes.Timestamp, 0), "timestamp(6)"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 0), "timestamp"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "timestamp(3)"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 6), "timestamp(6)"}, } for _, test := range tests { From 56851a285c1ef06fb39d3bcdeeb270036f3a2492 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 11:54:39 -0700 Subject: [PATCH 11/18] More datetime truncation tests --- sql/types/datetime_test.go | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index ba0133b1bd..46bf83634a 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -183,7 +183,6 @@ func TestDatetimeConvert(t *testing.T) { {DatetimeMaxPrecision, "2010-10-3 12:12:12", time.Date(2010, 10, 3, 12, 12, 12, 0, time.UTC), false}, {DatetimeMaxPrecision, "2010-10-3 12:12:2", time.Date(2010, 10, 3, 12, 12, 2, 0, time.UTC), false}, {DatetimeMaxPrecision, "2010-10-3 12:2:2", time.Date(2010, 10, 3, 12, 2, 2, 0, time.UTC), false}, - {DatetimeMaxPrecision, "2010-06-03 12:3", time.Date(2010, 6, 3, 12, 3, 0, 0, time.UTC), false}, {DatetimeMaxPrecision, "2010-06-03 12:34", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, {DatetimeMaxPrecision, "2010-06-03 12:34:", time.Date(2010, 6, 3, 12, 34, 0, 0, time.UTC), false}, @@ -195,6 +194,28 @@ func TestDatetimeConvert(t *testing.T) { {DatetimeMaxPrecision, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 780000000, time.UTC), false}, {DatetimeMaxPrecision, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 789000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), nil, nil, false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 12:12:12.123456", time.Date(2010, 6, 3, 12, 12, 12, 123000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03T12:12:12.123456Z", time.Date(2010, 6, 3, 12, 12, 12, 123000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 700000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 780000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 789000000, time.UTC), false}, + + {Datetime, nil, nil, false}, + {Datetime, time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {Datetime, time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {Datetime, "2010-06-03 12:12:12.123456", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {Datetime, "2010-06-03T12:12:12.123456Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {Datetime, "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {Datetime, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {Datetime, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {TimestampMaxPrecision, nil, nil, false}, {TimestampMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, @@ -212,6 +233,28 @@ func TestDatetimeConvert(t *testing.T) { {TimestampMaxPrecision, "20100603121212", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, {TimestampMaxPrecision, time.Date(2012, 12, 12, 12, 12, 12, 12345, time.UTC).UTC().String(), time.Date(2012, 12, 12, 12, 12, 12, 12000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), nil, nil, false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 12000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "2010-06-03 12:12:12.123456", time.Date(2010, 6, 3, 12, 12, 12, 123000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "2010-06-03T12:12:12.123456Z", time.Date(2010, 6, 3, 12, 12, 12, 123000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 700000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 780000000, time.UTC), false}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 789000000, time.UTC), false}, + + {Timestamp, nil, nil, false}, + {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12345678, time.UTC), + time.Date(2012, 12, 12, 12, 12, 12, 0, time.UTC), false}, + {Timestamp, "2010-06-03 12:12:12.123456", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {Timestamp, "2010-06-03T12:12:12.123456Z", time.Date(2010, 6, 3, 12, 12, 12, 0, time.UTC), false}, + {Timestamp, "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {Timestamp, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {Timestamp, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, + {Date, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, "0500-01-01 00:00:00", time.Date(500, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, time.Date(10000, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, From efff2ad4a8314989f554e15ff0f3ebcb45aa88a5 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 12:08:43 -0700 Subject: [PATCH 12/18] Replace timestamp with max precision where appropriate --- sql/expression/function/date.go | 4 ++-- sql/types/datetime.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sql/expression/function/date.go b/sql/expression/function/date.go index 86d6c50d5c..81f8bead9a 100644 --- a/sql/expression/function/date.go +++ b/sql/expression/function/date.go @@ -259,7 +259,7 @@ func (t *TimestampConversion) String() string { } func (t *TimestampConversion) Type() sql.Type { - return types.Timestamp + return types.TimestampMaxPrecision } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -276,7 +276,7 @@ func (t *TimestampConversion) Eval(ctx *sql.Context, r sql.Row) (interface{}, er if err != nil { return nil, err } - ret, _, err := types.Timestamp.Convert(e) + ret, _, err := types.TimestampMaxPrecision.Convert(e) return ret, err } diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 25ef63f69e..379f04574b 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -83,9 +83,9 @@ var ( Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0) // DatetimeMaxPrecision is a date and a time with maximum precision DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, 6) - // Timestamp is an UNIX timestamp with default precision (no fractional seconds). + // Timestamp is a UNIX timestamp with default precision (no fractional seconds). Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) - // TimestampMaxPrecision is an UNIX timestamp with maximum precision + // TimestampMaxPrecision is a UNIX timestamp with maximum precision TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, 6) datetimeValueType = reflect.TypeOf(time.Time{}) From 55c14f0033764a2f64bb588ffccc2a3838cfcb66 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 12:12:20 -0700 Subject: [PATCH 13/18] More comparison tests --- sql/types/datetime_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index 46bf83634a..ff4f6b9207 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -51,6 +51,13 @@ func TestDatetimeCompare(t *testing.T) { "2010-06-03", 1}, {Datetime, "2010-06-03 06:03:11", time.Date(2010, 6, 3, 6, 3, 11, 0, time.UTC), 0}, + {Datetime, "2010-06-03 06:03:11", "2010-06-03 06:03:11", 0}, + {DatetimeMaxPrecision, "2010-06-03 06:03:11.123456", "2010-06-03 06:03:11", 1}, + {DatetimeMaxPrecision, "2010-06-03 06:03:11", "2010-06-03 06:03:11.123456", -1}, + {DatetimeMaxPrecision, "2010-06-03 06:03:11.123456111", "2010-06-03 06:03:11.123456333", 0}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 06:03:11.123", "2010-06-03 06:03:11", 1}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 06:03:11", "2010-06-03 06:03:11.123", -1}, + {MustCreateDatetimeType(sqltypes.Datetime, 3), "2010-06-03 06:03:11.123456", "2010-06-03 06:03:11.123789", 0}, {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), time.Date(2012, 12, 12, 12, 24, 24, 24, time.UTC), -1}, {Timestamp, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), From ea9a37d4ad4062a720b3ef11eff54f0b9e680d6f Mon Sep 17 00:00:00 2001 From: zachmu Date: Wed, 16 Aug 2023 19:14:10 +0000 Subject: [PATCH 14/18] [ga-format-pr] Run ./format_repo.sh to fix formatting --- engine.go | 2 +- enginetest/memory_engine_test.go | 42 ++++++------ enginetest/queries/create_table_queries.go | 80 +++++++++++----------- enginetest/queries/queries.go | 2 +- sql/types/conversion.go | 2 +- sql/types/datetime.go | 10 +-- sql/types/datetime_test.go | 4 +- 7 files changed, 71 insertions(+), 71 deletions(-) diff --git a/engine.go b/engine.go index c4ab50b170..be1b9018bd 100644 --- a/engine.go +++ b/engine.go @@ -351,7 +351,7 @@ func bindingsToExprs(bindings map[string]*query.BindVariable) (map[string]sql.Ex } res[k] = expression.NewLiteral(v, t) case v.Type() == sqltypes.Date || v.Type() == sqltypes.Datetime || v.Type() == sqltypes.Timestamp: - precision := 6 + precision := 6 if v.Type() == sqltypes.Date { precision = 0 } diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index f9f6dd0c90..d187475d37 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -288,61 +288,61 @@ func TestSingleScript(t *testing.T) { Assertions: []queries.ScriptTestAssertion{ { Query: "show create table t1", - Expected: []sql.Row{{"t1", + Expected: []sql.Row{{"t1", "CREATE TABLE `t1` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(0),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t1 order by pk", + Query: "select * from t1 order by pk", Expected: []sql.Row{{1, queries.MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, { Query: "show create table t2", Expected: []sql.Row{{"t2", "CREATE TABLE `t2` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(3),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t2 order by pk", + Query: "select * from t2 order by pk", Expected: []sql.Row{{1, queries.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, }, { Query: "show create table t3", Expected: []sql.Row{{"t3", "CREATE TABLE `t3` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(6),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t3 order by pk", + Query: "select * from t3 order by pk", Expected: []sql.Row{{1, queries.MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, }, { - Query: "create table t4 (pk int primary key, d datetime(-1))", + Query: "create table t4 (pk int primary key, d datetime(-1))", ExpectedErr: sql.ErrSyntaxError, }, { - Query: "create table t4 (pk int primary key, d datetime(7))", + Query: "create table t4 (pk int primary key, d datetime(7))", ExpectedErrStr: "DATETIME supports precision from 0 to 6", }, }, diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index 56390d693a..4de49867f3 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -260,59 +260,59 @@ var CreateTableScriptTests = []ScriptTest{ Query: "show create table t1", Expected: []sql.Row{{"t1", "CREATE TABLE `t1` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(0),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t1 order by pk", + Query: "select * from t1 order by pk", Expected: []sql.Row{{1, MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, { Query: "show create table t2", Expected: []sql.Row{{"t2", "CREATE TABLE `t2` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(3),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t2 order by pk", + Query: "select * from t2 order by pk", Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, }, { Query: "show create table t3", Expected: []sql.Row{{"t3", "CREATE TABLE `t3` (\n" + - " `pk` int NOT NULL,\n" + - " `d` datetime(6),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` datetime(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t3 order by pk", + Query: "select * from t3 order by pk", Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, }, { - Query: "create table t4 (pk int primary key, d datetime(-1))", + Query: "create table t4 (pk int primary key, d datetime(-1))", ExpectedErr: sql.ErrSyntaxError, }, { - Query: "create table t4 (pk int primary key, d datetime(7))", + Query: "create table t4 (pk int primary key, d datetime(7))", ExpectedErrStr: "DATETIME supports precision from 0 to 6", }, }, @@ -329,59 +329,59 @@ var CreateTableScriptTests = []ScriptTest{ Query: "show create table t1", Expected: []sql.Row{{"t1", "CREATE TABLE `t1` (\n" + - " `pk` int NOT NULL,\n" + - " `d` timestamp(0),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` timestamp(0),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t1 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t1 order by pk", + Query: "select * from t1 order by pk", Expected: []sql.Row{{1, MustParseTime(time.DateTime, "2020-01-01 00:00:00")}}, }, { Query: "show create table t2", Expected: []sql.Row{{"t2", "CREATE TABLE `t2` (\n" + - " `pk` int NOT NULL,\n" + - " `d` timestamp(3),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` timestamp(3),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t2 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t2 order by pk", + Query: "select * from t2 order by pk", Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123000000Z")}}, }, { Query: "show create table t3", Expected: []sql.Row{{"t3", "CREATE TABLE `t3` (\n" + - " `pk` int NOT NULL,\n" + - " `d` timestamp(6),\n" + - " PRIMARY KEY (`pk`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + " `pk` int NOT NULL,\n" + + " `d` timestamp(6),\n" + + " PRIMARY KEY (`pk`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { - Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", + Query: "insert into t3 values (1, '2020-01-01 00:00:00.123456')", Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "select * from t3 order by pk", + Query: "select * from t3 order by pk", Expected: []sql.Row{{1, MustParseTime(time.RFC3339Nano, "2020-01-01T00:00:00.123456000Z")}}, }, { - Query: "create table t4 (pk int primary key, d TIMESTAMP(-1))", + Query: "create table t4 (pk int primary key, d TIMESTAMP(-1))", ExpectedErr: sql.ErrSyntaxError, }, { - Query: "create table t4 (pk int primary key, d TIMESTAMP(7))", + Query: "create table t4 (pk int primary key, d TIMESTAMP(7))", ExpectedErrStr: "TIMESTAMP supports precision from 0 to 6", }, }, diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 5bbe7b24e3..4a18a2d48b 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -10383,4 +10383,4 @@ func MustParseTime(layout, value string) time.Time { panic(err) } return parsed -} \ No newline at end of file +} diff --git a/sql/types/conversion.go b/sql/types/conversion.go index d8ebeb7df9..b91730928d 100644 --- a/sql/types/conversion.go +++ b/sql/types/conversion.go @@ -356,7 +356,7 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { if err != nil { return nil, err } - + if precision > 6 || precision < 0 { return nil, fmt.Errorf("DATETIME supports precision from 0 to 6") } diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 379f04574b..0e9d58570f 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -87,12 +87,12 @@ var ( Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0) // TimestampMaxPrecision is a UNIX timestamp with maximum precision TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, 6) - + datetimeValueType = reflect.TypeOf(time.Time{}) ) type datetimeType struct { - baseType query.Type + baseType query.Type precision int } @@ -107,7 +107,7 @@ func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, e return nil, fmt.Errorf("precision must be between 0 and 6, got %d", precision) } return datetimeType{ - baseType: baseType, + baseType: baseType, precision: precision, }, nil } @@ -173,7 +173,7 @@ func (t datetimeType) Convert(v interface{}) (interface{}, sql.ConvertInRange, e // precisionConversion is a conversion ratio to divide time.Second by to truncate the appropriate amount for the // precision of a type with time info -var precisionConversion = [7]int { +var precisionConversion = [7]int{ 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, } @@ -190,7 +190,7 @@ func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) { if res.Equal(zeroTime) { return zeroTime, nil } - + // Truncate the date to the precision of this type truncationDuration := time.Second truncationDuration /= time.Duration(precisionConversion[t.precision]) diff --git a/sql/types/datetime_test.go b/sql/types/datetime_test.go index ff4f6b9207..5847df728e 100644 --- a/sql/types/datetime_test.go +++ b/sql/types/datetime_test.go @@ -155,7 +155,7 @@ func TestDatetimeConvert(t *testing.T) { expectedVal interface{} expectedErr bool } - tests := []testcase { + tests := []testcase{ {Date, nil, nil, false}, {Date, time.Date(2012, 12, 12, 12, 12, 12, 12, time.UTC), time.Date(2012, 12, 12, 0, 0, 0, 0, time.UTC), false}, @@ -261,7 +261,7 @@ func TestDatetimeConvert(t *testing.T) { {Timestamp, "2010-06-03 12:34:56.7", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, {Timestamp, "2010-06-03 12:34:56.78", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, {Timestamp, "2010-06-03 12:34:56.789", time.Date(2010, 6, 3, 12, 34, 56, 0, time.UTC), false}, - + {Date, "0000-01-01 00:00:00", time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, "0500-01-01 00:00:00", time.Date(500, 1, 1, 0, 0, 0, 0, time.UTC), false}, {Date, time.Date(10000, 1, 1, 1, 1, 1, 1, time.UTC), nil, true}, From b53b3b26604fb304bdc9bbc34fb53b489271f14c Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 12:23:57 -0700 Subject: [PATCH 15/18] reskip test --- enginetest/memory_engine_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enginetest/memory_engine_test.go b/enginetest/memory_engine_test.go index d187475d37..4720c72ef7 100644 --- a/enginetest/memory_engine_test.go +++ b/enginetest/memory_engine_test.go @@ -276,7 +276,7 @@ func TestSingleQueryPrepared(t *testing.T) { // Convenience test for debugging a single query. Unskip and set to the desired query. func TestSingleScript(t *testing.T) { - // t.Skip() + t.Skip() var scripts = []queries.ScriptTest{ { Name: "datetime precision", From dafc165446f2217049e80f419978a66174964aab Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 12:24:34 -0700 Subject: [PATCH 16/18] Upgrade tests to go 1.20 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 913844ec1c..869148e56f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: test: strategy: matrix: - go-version: [1.19.x] + go-version: [1.20.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: From cc7f8709b9846fd728a2399ed936b77c8b5facab Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 12:43:44 -0700 Subject: [PATCH 17/18] fixed tests --- enginetest/enginetests.go | 2 +- enginetest/queries/create_table_queries.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enginetest/enginetests.go b/enginetest/enginetests.go index 57dd7a6c6b..d9a42a9612 100644 --- a/enginetest/enginetests.go +++ b/enginetest/enginetests.go @@ -2164,8 +2164,8 @@ func TestCreateTable(t *testing.T, harness Harness) { {"val", "int", "YES", "", "NULL", ""}}, nil, nil) }) - t.Skip("primary key lengths are not stored properly") for _, tt := range queries.BrokenCreateTableQueries { + t.Skip("primary key lengths are not stored properly") RunWriteQueryTest(t, harness, tt) } } diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index 4de49867f3..98f0649af8 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -32,7 +32,7 @@ var CreateTableQueries = []WriteQueryTest{ WriteQuery: `CREATE TABLE t1 (a INTEGER, b TEXT, c DATE, d TIMESTAMP, e VARCHAR(20), f BLOB NOT NULL, b1 BOOL, b2 BOOLEAN NOT NULL, g DATETIME, h CHAR(40))`, ExpectedWriteResult: []sql.Row{{types.NewOkResult(0)}}, SelectQuery: "SHOW CREATE TABLE t1", - ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp(0),\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime(0),\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + ExpectedSelect: []sql.Row{sql.Row{"t1", "CREATE TABLE `t1` (\n `a` int,\n `b` text,\n `c` date,\n `d` timestamp,\n `e` varchar(20),\n `f` blob NOT NULL,\n `b1` tinyint,\n `b2` tinyint NOT NULL,\n `g` datetime,\n `h` char(40)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { WriteQuery: `CREATE TABLE t1 (a INTEGER NOT NULL PRIMARY KEY, b VARCHAR(10) NOT NULL)`, @@ -147,7 +147,7 @@ var CreateTableQueries = []WriteQueryTest{ )`, ExpectedWriteResult: []sql.Row{{types.NewOkResult(0)}}, SelectQuery: "SHOW CREATE TABLE td", - ExpectedSelect: []sql.Row{sql.Row{"td", "CREATE TABLE `td` (\n `pk` int NOT NULL,\n `col2` int NOT NULL DEFAULT '2',\n `col3` double NOT NULL DEFAULT (round(-1.58,0)),\n `col4` varchar(10) DEFAULT 'new row',\n `col5` float DEFAULT '33.33',\n `col6` int DEFAULT NULL,\n `col7` timestamp(0) DEFAULT (NOW()),\n `col8` bigint DEFAULT (NOW()),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + ExpectedSelect: []sql.Row{sql.Row{"td", "CREATE TABLE `td` (\n `pk` int NOT NULL,\n `col2` int NOT NULL DEFAULT '2',\n `col3` double NOT NULL DEFAULT (round(-1.58,0)),\n `col4` varchar(10) DEFAULT 'new row',\n `col5` float DEFAULT '33.33',\n `col6` int DEFAULT NULL,\n `col7` timestamp DEFAULT (NOW()),\n `col8` bigint DEFAULT (NOW()),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { WriteQuery: `CREATE TABLE t1 (i int PRIMARY KEY, j varchar(MAX))`, @@ -261,7 +261,7 @@ var CreateTableScriptTests = []ScriptTest{ Expected: []sql.Row{{"t1", "CREATE TABLE `t1` (\n" + " `pk` int NOT NULL,\n" + - " `d` datetime(0),\n" + + " `d` datetime,\n" + " PRIMARY KEY (`pk`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, @@ -330,7 +330,7 @@ var CreateTableScriptTests = []ScriptTest{ Expected: []sql.Row{{"t1", "CREATE TABLE `t1` (\n" + " `pk` int NOT NULL,\n" + - " `d` timestamp(0),\n" + + " `d` timestamp,\n" + " PRIMARY KEY (`pk`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, From 66633a74d07b08e6c8ccc678cbe8a98ee08a8bbf Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Wed, 16 Aug 2023 13:07:45 -0700 Subject: [PATCH 18/18] Added needed Precision method to sql.DatetimeType, fixed another test --- enginetest/queries/information_schema_queries.go | 4 ++-- sql/type.go | 1 + sql/types/datetime.go | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/enginetest/queries/information_schema_queries.go b/enginetest/queries/information_schema_queries.go index 7a230e6f0f..65a428feb8 100644 --- a/enginetest/queries/information_schema_queries.go +++ b/enginetest/queries/information_schema_queries.go @@ -972,8 +972,8 @@ FROM INFORMATION_SCHEMA.TRIGGERS WHERE trigger_schema = 'mydb'`, {"about", "id", nil, "NO", "int unsigned", "UNI", nil, "auto_increment"}, {"about", "uuid", nil, "NO", "char(36)", "PRI", 36, ""}, {"about", "status", "draft", "NO", "varchar(255)", "", 255, ""}, - {"about", "date_created", nil, "YES", "timestamp(0)", "", nil, ""}, - {"about", "date_updated", nil, "YES", "timestamp(0)", "", nil, ""}, + {"about", "date_created", nil, "YES", "timestamp", "", nil, ""}, + {"about", "date_updated", nil, "YES", "timestamp", "", nil, ""}, {"about", "url_key", nil, "NO", "varchar(255)", "UNI", 255, ""}, }, }, diff --git a/sql/type.go b/sql/type.go index 00738c5247..dbc49585ec 100644 --- a/sql/type.go +++ b/sql/type.go @@ -145,6 +145,7 @@ type DatetimeType interface { ConvertWithoutRangeCheck(v interface{}) (time.Time, error) MaximumTime() time.Time MinimumTime() time.Time + Precision() int } // YearType represents the YEAR type. diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 0e9d58570f..77b255eb57 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -123,6 +123,10 @@ func MustCreateDatetimeType(baseType query.Type, precision int) sql.DatetimeType return dt } +func (t datetimeType) Precision() int { + return t.precision +} + // Compare implements Type interface. func (t datetimeType) Compare(a interface{}, b interface{}) (int, error) { if hasNulls, res := CompareNulls(a, b); hasNulls {