From d67fc067a1bd55f3fe5d636735093a3479580664 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Thu, 17 Aug 2023 11:57:14 -0700 Subject: [PATCH 1/2] Reinstate PR 1944 --- .github/workflows/test.yml | 2 +- engine.go | 6 +- engine_test.go | 4 +- enginetest/enginetests.go | 10 +- enginetest/memory_engine_test.go | 73 ++++- enginetest/queries/alter_table_queries.go | 2 +- enginetest/queries/create_table_queries.go | 144 ++++++++- .../queries/information_schema_queries.go | 8 +- enginetest/queries/queries.go | 8 + enginetest/queries/script_queries.go | 2 +- enginetest/testdata.go | 41 --- server/handler_test.go | 6 +- .../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 | 26 +- sql/expression/function/date_format.go | 2 +- 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/type.go | 1 + sql/types/conversion.go | 34 ++- sql/types/datetime.go | 57 +++- sql/types/datetime_test.go | 281 +++++++++++------- 34 files changed, 548 insertions(+), 246 deletions(-) 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: diff --git a/engine.go b/engine.go index 0a90bd544c..686b5363a6 100644 --- a/engine.go +++ b/engine.go @@ -314,7 +314,11 @@ func bindingsToExprs(bindings map[string]*querypb.BindVariable) (map[string]sql. } 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()) + 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/enginetest/enginetests.go b/enginetest/enginetests.go index c8221d949e..b3d1b0e763 100644 --- a/enginetest/enginetests.go +++ b/enginetest/enginetests.go @@ -2167,8 +2167,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) } } @@ -5613,8 +5613,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() @@ -5836,14 +5836,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/memory_engine_test.go b/enginetest/memory_engine_test.go index 16aa8a78d7..4dd7723f76 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" @@ -186,7 +187,77 @@ 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{} + var scripts = []queries.ScriptTest{ + { + 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{ + { + 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"}}, + }, + { + 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, 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"}}, + }, + { + 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, 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"}}, + }, + { + 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, 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", + }, + }, + }, + } for _, test := range scripts { harness := enginetest.NewMemoryHarness("", 1, testNumPartitions, true, nil) 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/create_table_queries.go b/enginetest/queries/create_table_queries.go index 16fe737b8a..53d41bcd9f 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,\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)`, @@ -145,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 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))`, @@ -246,6 +248,144 @@ var CreateTableScriptTests = []ScriptTest{ }, }, }, + { + 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{{"t1", + "CREATE TABLE `t1` (\n" + + " `pk` int NOT NULL,\n" + + " `d` datetime,\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", + }, + }, + }, + { + 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,\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/enginetest/queries/information_schema_queries.go b/enginetest/queries/information_schema_queries.go index 51e2e0e1f9..58c6506477 100644 --- a/enginetest/queries/information_schema_queries.go +++ b/enginetest/queries/information_schema_queries.go @@ -1027,8 +1027,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", "", nil, ""}, + {"about", "date_updated", nil, "YES", "timestamp", "", nil, ""}, {"about", "url_key", nil, "NO", "varchar(255)", "UNI", 255, ""}, }, }, @@ -1150,7 +1150,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", @@ -1170,7 +1170,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/queries.go b/enginetest/queries/queries.go index 53a67b271e..a932f106bc 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -10394,3 +10394,11 @@ var IndexPrefixQueries = []ScriptTest{ }, }, } + +func MustParseTime(layout, value string) time.Time { + parsed, err := time.Parse(layout, value) + if err != nil { + panic(err) + } + return parsed +} diff --git a/enginetest/queries/script_queries.go b/enginetest/queries/script_queries.go index 9f15002a1e..5c14d9e775 100644 --- a/enginetest/queries/script_queries.go +++ b/enginetest/queries/script_queries.go @@ -1877,7 +1877,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{ diff --git a/enginetest/testdata.go b/enginetest/testdata.go index b242d7cef1..05d1ccb6ed 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" @@ -206,42 +204,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 dob(year, month, day int) time.Time { - return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) -} 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/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..81f8bead9a 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 @@ -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 } @@ -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.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/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 9e11b5e6b9..bae5de3812 100644 --- a/sql/expression/function/str_to_date.go +++ b/sql/expression/function/str_to_date.go @@ -47,7 +47,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/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/conversion.go b/sql/types/conversion.go index 1f1cfd0400..b91730928d 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: @@ -316,7 +316,7 @@ func ColumnTypeToType(ct *sqlparser.ColumnType) (sql.Type, error) { case "year": return Year, nil case "date": - return Date, nil + return CreateDatetimeType(sqltypes.Date, 0) case "time": if ct.Length != nil { length, err := strconv.ParseInt(string(ct.Length.Val), 10, 64) @@ -334,9 +334,35 @@ 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": - 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 + } + + if precision > 6 || precision < 0 { + return nil, fmt.Errorf("DATETIME supports precision from 0 to 6") + } + } + + 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 8cd3b5d331..77b255eb57 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -15,6 +15,7 @@ package types import ( + "fmt" "math" "reflect" "time" @@ -77,42 +78,55 @@ var ( zeroTime = time.Unix(-62167219200, 0).UTC() // Date is a date with day, month and year. - Date = MustCreateDatetimeType(sqltypes.Date) - // Datetime is a date and a time - Datetime = MustCreateDatetimeType(sqltypes.Datetime) - // Timestamp is an UNIX timestamp. - Timestamp = MustCreateDatetimeType(sqltypes.Timestamp) + Date = MustCreateDatetimeType(sqltypes.Date, 0) + // 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 a UNIX timestamp with default precision (no fractional seconds). + 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 } 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: + if precision < 0 || precision > 6 { + return nil, fmt.Errorf("precision must be between 0 and 6, got %d", precision) + } return datetimeType{ - baseType: baseType, + baseType: baseType, + precision: precision, }, nil } return nil, sql.ErrInvalidBaseType.New(baseType.String(), "datetime") } // MustCreateDatetimeType is the same as CreateDatetimeType except it panics on errors. -func MustCreateDatetimeType(baseType query.Type) sql.DatetimeType { - dt, err := CreateDatetimeType(baseType) +func MustCreateDatetimeType(baseType query.Type, precision int) sql.DatetimeType { + dt, err := CreateDatetimeType(baseType, precision) if err != nil { panic(err) } 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 { @@ -161,6 +175,12 @@ 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, +} + func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) { if v == nil { return time.Time{}, nil @@ -175,6 +195,11 @@ func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) { 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: if res.Year() < 0 || res.Year() > 9999 { @@ -336,7 +361,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. @@ -390,9 +415,15 @@ func (t datetimeType) String() string { case sqltypes.Date: return "date" case sqltypes.Datetime: - return "datetime(6)" + if t.precision > 0 { + return fmt.Sprintf("datetime(%d)", t.precision) + } + return "datetime" case sqltypes.Timestamp: - return "timestamp(6)" + 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 2e63a326bb..5847df728e 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" @@ -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), @@ -74,14 +81,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 +137,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 { @@ -142,12 +149,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 +170,97 @@ 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}, + {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}, + {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}, + + {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, 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}, + {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}, - {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}, + {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}, + + {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, 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}, + {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}, @@ -227,40 +282,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 +330,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 { @@ -323,9 +378,13 @@ func TestDatetimeString(t *testing.T) { typ sql.Type expectedStr string }{ - {MustCreateDatetimeType(sqltypes.Date), "date"}, - {MustCreateDatetimeType(sqltypes.Datetime), "datetime(6)"}, - {MustCreateDatetimeType(sqltypes.Timestamp), "timestamp(6)"}, + {MustCreateDatetimeType(sqltypes.Date, 0), "date"}, + {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"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 3), "timestamp(3)"}, + {MustCreateDatetimeType(sqltypes.Timestamp, 6), "timestamp(6)"}, } for _, test := range tests { @@ -337,10 +396,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 29b6ebf63a6c96bc8e29918ee1f867618974a5d3 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Thu, 17 Aug 2023 12:00:01 -0700 Subject: [PATCH 2/2] Datetime precisoin parse tests --- sql/planbuilder/parse_test.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/sql/planbuilder/parse_test.go b/sql/planbuilder/parse_test.go index e6e3dd4bba..20703de8f0 100644 --- a/sql/planbuilder/parse_test.go +++ b/sql/planbuilder/parse_test.go @@ -1830,18 +1830,26 @@ func TestParseColumnTypeString(t *testing.T) { "TIMESTAMP", types.Timestamp, }, - //{ - // "TIMESTAMP(6)", - // types.TimestampMaxPrecision, - //}, + { + "TIMESTAMP(3)", + types.MustCreateDatetimeType(sqltypes.Timestamp, 3), + }, + { + "TIMESTAMP(6)", + types.TimestampMaxPrecision, + }, + { + "DATETIME(3)", + types.MustCreateDatetimeType(sqltypes.Datetime, 3), + }, { "DATETIME", types.Datetime, }, - //{ - // "DATETIME(6)", - // types.DatetimeMaxPrecision, - //}, + { + "DATETIME(6)", + types.DatetimeMaxPrecision, + }, } for _, test := range tests {