diff --git a/enginetest/enginetests.go b/enginetest/enginetests.go index c7b4db29b7..ed9ba7241b 100644 --- a/enginetest/enginetests.go +++ b/enginetest/enginetests.go @@ -4379,7 +4379,7 @@ func TestColumnDefaults(t *testing.T, harness Harness) { return nil }) TestQueryWithContext(t, ctx, e, harness, "select * from t10 order by 1", []sql.Row{ - {1, now, now.Truncate(time.Second), now, now.Truncate(time.Second)}, + {1, now.Truncate(time.Second), now.Truncate(time.Second), now.Truncate(time.Second), now.Truncate(time.Second)}, }, nil, nil) }) diff --git a/enginetest/queries/alter_table_queries.go b/enginetest/queries/alter_table_queries.go index 4777580d87..af0094ccf3 100644 --- a/enginetest/queries/alter_table_queries.go +++ b/enginetest/queries/alter_table_queries.go @@ -141,7 +141,7 @@ var AlterTableScripts = []ScriptTest{ }, { Query: "show create table t;", - Expected: []sql.Row{{"t", "CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` timestamp(6) DEFAULT (CURRENT_TIMESTAMP()),\n `col2` varchar(1000),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`pk`,`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + Expected: []sql.Row{{"t", "CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` timestamp(6) DEFAULT CURRENT_TIMESTAMP,\n `col2` varchar(1000),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`pk`,`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { Query: "alter table t alter column col2 SET DEFAULT 'FOO!';", @@ -149,7 +149,7 @@ var AlterTableScripts = []ScriptTest{ }, { Query: "show create table t;", - Expected: []sql.Row{{"t", "CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` timestamp(6) DEFAULT (CURRENT_TIMESTAMP()),\n `col2` varchar(1000) DEFAULT 'FOO!',\n PRIMARY KEY (`pk`),\n KEY `idx1` (`pk`,`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + Expected: []sql.Row{{"t", "CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` timestamp(6) DEFAULT CURRENT_TIMESTAMP,\n `col2` varchar(1000) DEFAULT 'FOO!',\n PRIMARY KEY (`pk`),\n KEY `idx1` (`pk`,`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, }, { Query: "alter table t drop index idx1;", diff --git a/enginetest/queries/column_default_queries.go b/enginetest/queries/column_default_queries.go index 8a194f8044..d927a7c96a 100644 --- a/enginetest/queries/column_default_queries.go +++ b/enginetest/queries/column_default_queries.go @@ -485,9 +485,9 @@ var ColumnDefaultTests = []ScriptTest{ Query: "desc t33", Expected: []sql.Row{ {"pk", "varchar(100)", "NO", "PRI", "(replace(uuid(), '-', ''))", "DEFAULT_GENERATED"}, - {"v1_new", "timestamp(6)", "YES", "", "(NOW())", "DEFAULT_GENERATED"}, + {"v1_new", "timestamp(6)", "YES", "", "CURRENT_TIMESTAMP", "DEFAULT_GENERATED"}, {"v2", "varchar(100)", "YES", "", "NULL", ""}, - {"v3", "datetime(6)", "YES", "", "(CURRENT_TIMESTAMP())", "DEFAULT_GENERATED"}, + {"v3", "datetime(6)", "YES", "", "CURRENT_TIMESTAMP", "DEFAULT_GENERATED"}, }, }, { diff --git a/enginetest/queries/create_table_queries.go b/enginetest/queries/create_table_queries.go index e3537ef715..9e802095a9 100644 --- a/enginetest/queries/create_table_queries.go +++ b/enginetest/queries/create_table_queries.go @@ -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 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 CURRENT_TIMESTAMP,\n `col8` bigint DEFAULT CURRENT_TIMESTAMP,\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))`, diff --git a/enginetest/queries/update_queries.go b/enginetest/queries/update_queries.go index 72a268b3ec..28c5ee22e7 100644 --- a/enginetest/queries/update_queries.go +++ b/enginetest/queries/update_queries.go @@ -780,6 +780,26 @@ var OnUpdateExprScripts = []ScriptTest{ Query: "create table tt (i int, d date on update current_timestamp)", ExpectedErr: sql.ErrInvalidOnUpdate, }, + { + Query: "create table tt (i int, ts timestamp on update now(1))", + ExpectedErr: sql.ErrInvalidOnUpdate, + }, + { + Query: "create table tt (i int, ts timestamp on update current_timestamp(1))", + ExpectedErr: sql.ErrInvalidOnUpdate, + }, + { + Query: "create table tt (i int, ts timestamp on update current_timestamp(100))", + ExpectedErr: sql.ErrInvalidOnUpdate, + }, + { + Query: "create table tt (i int, ts timestamp on update localtime(1))", + ExpectedErr: sql.ErrInvalidOnUpdate, + }, + { + Query: "create table tt (i int, ts timestamp on update localtimestamp(1))", + ExpectedErr: sql.ErrInvalidOnUpdate, + }, { Query: "alter table t modify column ts timestamp on update (5)", ExpectedErrStr: "syntax error at position 53 near 'update'", @@ -792,6 +812,10 @@ var OnUpdateExprScripts = []ScriptTest{ Query: "alter table t modify column t date on update current_timestamp", ExpectedErr: sql.ErrInvalidOnUpdate, }, + { + Query: "select current_timestamp(i) from t", + ExpectedErrStr: "syntax error at position 27 near 'i'", + }, }, }, { @@ -806,7 +830,7 @@ var OnUpdateExprScripts = []ScriptTest{ Expected: []sql.Row{ {"t", "CREATE TABLE `t` (\n" + " `i` int,\n" + - " `ts` timestamp DEFAULT 0 ON UPDATE (CURRENT_TIMESTAMP())\n" + + " `ts` timestamp DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, }, }, @@ -877,7 +901,7 @@ var OnUpdateExprScripts = []ScriptTest{ Expected: []sql.Row{ {"t", "CREATE TABLE `t` (\n" + " `i` int,\n" + - " `ts` timestamp DEFAULT (CURRENT_TIMESTAMP()) ON UPDATE (CURRENT_TIMESTAMP())\n" + + " `ts` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, }, }, @@ -961,7 +985,7 @@ var OnUpdateExprScripts = []ScriptTest{ Expected: []sql.Row{ {"t", "CREATE TABLE `t` (\n" + " `i` int,\n" + - " `ts` timestamp DEFAULT 0 ON UPDATE (CURRENT_TIMESTAMP())\n" + + " `ts` timestamp DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, }, }, @@ -1029,8 +1053,8 @@ var OnUpdateExprScripts = []ScriptTest{ Expected: []sql.Row{ {"t", "CREATE TABLE `t` (\n" + " `i` int NOT NULL,\n" + - " `ts` timestamp DEFAULT 0 ON UPDATE (CURRENT_TIMESTAMP()),\n" + - " `dt` datetime DEFAULT 0 ON UPDATE (CURRENT_TIMESTAMP()),\n" + + " `ts` timestamp DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP,\n" + + " `dt` datetime DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP,\n" + " PRIMARY KEY (`i`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, }, @@ -1215,4 +1239,252 @@ var OnUpdateExprScripts = []ScriptTest{ }, }, }, + { + Name: "now() synonyms", + SetUpScript: []string{ + "create table t (i int, ts timestamp);", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "create table t1 (i int, ts timestamp on update now())", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t1;", + Expected: []sql.Row{ + {"t1", "CREATE TABLE `t1` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t2 (i int, ts timestamp on update now(0))", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t2;", + Expected: []sql.Row{ + {"t2", "CREATE TABLE `t2` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t3 (i int, ts timestamp on update localtime)", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t3;", + Expected: []sql.Row{ + {"t3", "CREATE TABLE `t3` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t4 (i int, ts timestamp on update localtime())", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t4;", + Expected: []sql.Row{ + {"t4", "CREATE TABLE `t4` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t5 (i int, ts timestamp on update localtime(0))", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t5;", + Expected: []sql.Row{ + {"t5", "CREATE TABLE `t5` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t6 (i int, ts timestamp on update localtimestamp)", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t6;", + Expected: []sql.Row{ + {"t6", "CREATE TABLE `t6` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t7 (i int, ts timestamp on update localtimestamp())", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t7;", + Expected: []sql.Row{ + {"t7", "CREATE TABLE `t7` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "create table t8 (i int, ts timestamp on update localtimestamp(0))", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t8;", + Expected: []sql.Row{ + {"t8", "CREATE TABLE `t8` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update now()", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update now(0)", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtime", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtime()", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtime(0)", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtimestamp", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtimestamp()", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "alter table t modify column ts timestamp on update localtimestamp(0)", + Expected: []sql.Row{ + {types.NewOkResult(0)}, + }, + }, + { + Query: "show create table t;", + Expected: []sql.Row{ + {"t", "CREATE TABLE `t` (\n" + + " `i` int,\n" + + " `ts` timestamp ON UPDATE CURRENT_TIMESTAMP\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + }, + }, } diff --git a/go.mod b/go.mod index bec35d9da4..4d2974e515 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e github.com/dolthub/jsonpath v0.0.2-0.20230525180605-8dc13778fd72 github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 - github.com/dolthub/vitess v0.0.0-20231220170902-3729729e89b1 + github.com/dolthub/vitess v0.0.0-20240103005833-78fa0d3c4815 github.com/go-kit/kit v0.10.0 github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d github.com/gocraft/dbr/v2 v2.7.2 diff --git a/go.sum b/go.sum index 7bde8852bd..25e2553151 100644 --- a/go.sum +++ b/go.sum @@ -58,10 +58,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20230525180605-8dc13778fd72 h1:NfWmngMi1CYU github.com/dolthub/jsonpath v0.0.2-0.20230525180605-8dc13778fd72/go.mod h1:ZWUdY4iszqRQ8OcoXClkxiAVAoWoK3cq0Hvv4ddGRuM= github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE= github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= -github.com/dolthub/vitess v0.0.0-20231207010700-88fb35413580 h1:OSp1g3tRBMVIyxza4LN20rZ6yYEKqjf5hNNisVg/Lns= -github.com/dolthub/vitess v0.0.0-20231207010700-88fb35413580/go.mod h1:IwjNXSQPymrja5pVqmfnYdcy7Uv7eNJNBPK/MEh9OOw= -github.com/dolthub/vitess v0.0.0-20231220170902-3729729e89b1 h1:7uygH2yYhLka7Slzugf8i9rjQv5EDDphGbofawKdILg= -github.com/dolthub/vitess v0.0.0-20231220170902-3729729e89b1/go.mod h1:IwjNXSQPymrja5pVqmfnYdcy7Uv7eNJNBPK/MEh9OOw= +github.com/dolthub/vitess v0.0.0-20240103005833-78fa0d3c4815 h1:3mVQUpOVp6pLTE2kgvn8yolJ8SgTgyWEgVNIoVd/Hko= +github.com/dolthub/vitess v0.0.0-20240103005833-78fa0d3c4815/go.mod h1:IwjNXSQPymrja5pVqmfnYdcy7Uv7eNJNBPK/MEh9OOw= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= diff --git a/sql/columndefault.go b/sql/columndefault.go index edc42e0f14..c9184f912e 100644 --- a/sql/columndefault.go +++ b/sql/columndefault.go @@ -144,11 +144,17 @@ func (e *ColumnDefaultValue) String() string { // https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html // The default value specified in a DEFAULT clause can be a literal constant or an expression. With one exception, // enclose expression default values within parentheses to distinguish them from literal constant default values. + str := e.Expr.String() if e.Literal { - return e.Expr.String() - } else { - return fmt.Sprintf("(%s)", e.Expr.String()) + return str + } + + // There's a special case for NOW() + if str == "NOW()" || str == "NOW(0)" { + return "CURRENT_TIMESTAMP" } + + return fmt.Sprintf("(%s)", str) } func (e *ColumnDefaultValue) DebugString() string { diff --git a/sql/expression/function/registry.go b/sql/expression/function/registry.go index b3c9a1fedd..6aa5dbcf4e 100644 --- a/sql/expression/function/registry.go +++ b/sql/expression/function/registry.go @@ -64,10 +64,10 @@ var BuiltIns = []sql.Function{ sql.Function1{Name: "crc32", Fn: NewCrc32}, sql.NewFunction0("curdate", NewCurrDate), sql.NewFunction0("current_date", NewCurrentDate), - sql.NewFunction0("current_time", NewCurrentTime), - sql.FunctionN{Name: "current_timestamp", Fn: NewCurrTimestamp}, + sql.FunctionN{Name: "current_time", Fn: NewCurrTime}, + sql.FunctionN{Name: "current_timestamp", Fn: NewNow}, sql.NewFunction0("current_user", NewCurrentUser), - sql.NewFunction0("curtime", NewCurrTime), + sql.FunctionN{Name: "curtime", Fn: NewCurrTime}, sql.Function0{Name: "database", Fn: NewDatabase}, sql.Function1{Name: "date", Fn: NewDate}, sql.FunctionN{Name: "datetime", Fn: NewDatetime}, @@ -148,6 +148,8 @@ var BuiltIns = []sql.Function{ sql.Function1{Name: "length", Fn: NewLength}, sql.Function1{Name: "ln", Fn: NewLogBaseFunc(float64(math.E))}, sql.Function1{Name: "load_file", Fn: NewLoadFile}, + sql.FunctionN{Name: "localtime", Fn: NewNow}, + sql.FunctionN{Name: "localtimestamp", Fn: NewNow}, sql.FunctionN{Name: "locate", Fn: NewLocate}, sql.FunctionN{Name: "log", Fn: NewLog}, sql.Function1{Name: "log10", Fn: NewLogBaseFunc(float64(10))}, diff --git a/sql/expression/function/time.go b/sql/expression/function/time.go index 375ace8255..5a03c8db08 100644 --- a/sql/expression/function/time.go +++ b/sql/expression/function/time.go @@ -819,9 +819,11 @@ var ( dayOfYear = datePartFunc((time.Time).YearDay) ) +const maxCurrTimestampPrecision = 6 + // Now is a function that returns the current time. type Now struct { - precision *int + prec sql.Expression } func (n *Now) IsNonDeterministic() bool { @@ -833,33 +835,12 @@ var _ sql.CollationCoercible = (*Now)(nil) // NewNow returns a new Now node. func NewNow(args ...sql.Expression) (sql.Expression, error) { - var precision *int - if len(args) > 1 { - return nil, sql.ErrInvalidArgumentNumber.New("TIMESTAMP", 1, len(args)) - } else if len(args) == 1 { - argType := args[0].Type().Promote() - if argType != types.Int64 && argType != types.Uint64 { - return nil, sql.ErrInvalidType.New(args[0].Type().String()) - } - // todo: making a context here is expensive - val, err := args[0].Eval(sql.NewEmptyContext(), nil) - if err != nil { - return nil, err - } - precisionArg, _, err := types.Int32.Convert(val) - - if err != nil { - return nil, err - } - - n := int(precisionArg.(int32)) - if n < 0 || n > 6 { - return nil, sql.ErrValueOutOfRange.New("precision", "now") - } - precision = &n + n := &Now{} + // parser should make it impossible to pass in more than one argument + if len(args) > 0 { + n.prec = args[0] } - - return &Now{precision}, nil + return n, nil } func subSecondPrecision(t time.Time, precision int) string { @@ -910,53 +891,108 @@ func (*Now) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, return sql.Collation_binary, 5 } +// String implements the sql.Expression interface. func (n *Now) String() string { - if n.precision == nil { + if n.prec == nil { return "NOW()" } - return fmt.Sprintf("NOW(%d)", *n.precision) + return fmt.Sprintf("NOW(%s)", n.prec.String()) } // IsNullable implements the sql.Expression interface. func (n *Now) IsNullable() bool { return false } // Resolved implements the sql.Expression interface. -func (n *Now) Resolved() bool { return true } +func (n *Now) Resolved() bool { + if n.prec == nil { + return true + } + return n.prec.Resolved() +} // Children implements the sql.Expression interface. -func (n *Now) Children() []sql.Expression { return nil } +func (n *Now) Children() []sql.Expression { + if n.prec == nil { + return nil + } + return []sql.Expression{n.prec} +} // Eval implements the sql.Expression interface. -func (n *Now) Eval(ctx *sql.Context, _ sql.Row) (interface{}, error) { +func (n *Now) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { + // Cannot evaluate with nil context if ctx == nil { return nil, fmt.Errorf("cannot Eval Now with nil context") } - t := ctx.QueryTime() - // TODO: Now should return a string formatted depending on context. This code handles string formatting - // and should be enabled at the time we fix the return type - /*s, err := formatDate("%Y-%m-%d %H:%i:%s", t) + + // The timestamp must be in the session time zone + sessionTimeZone, err := SessionTimeZone(ctx) if err != nil { return nil, err } - if n.precision != nil { - s += subSecondPrecision(t, *n.precision) - }*/ - sessionTimeZone, err := SessionTimeZone(ctx) + // If no arguments, just return with 0 precision + // The way the parser is implemented 0 should always be passed in; have this here just in case + if n.prec == nil { + t, ok := gmstime.ConvertTimeZone(ctx.QueryTime(), gmstime.SystemTimezoneOffset(), sessionTimeZone) + if !ok { + return nil, fmt.Errorf("invalid time zone: %s", sessionTimeZone) + } + tt := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.UTC) + return tt, nil + } + + // Should syntax error before this; check anyway + if types.IsNull(n.prec) { + return nil, ErrTimeUnexpectedlyNil.New(n.FunctionName()) + } + + // Evaluate precision + prec, err := n.prec.Eval(ctx, row) if err != nil { return nil, err } - newTime, b := gmstime.ConvertTimeZone(t, gmstime.SystemTimezoneOffset(), sessionTimeZone) - if !b { + // Should syntax error before this; check anyway + if prec == nil { + return nil, ErrTimeUnexpectedlyNil.New(n.FunctionName()) + } + + // Must receive integer + // Should syntax error before this; check anyway + fsp, ok := types.CoalesceInt(prec) + if !ok { + return nil, sql.ErrInvalidArgumentType.New(n.FunctionName()) + } + + // Parse and return answer + if fsp > maxCurrTimestampPrecision { + return nil, ErrTooHighPrecision.New(fsp, n.FunctionName(), maxCurrTimestampPrecision) + } else if fsp < 0 { + // Should syntax error before this; check anyway + return nil, sql.ErrInvalidArgumentType.New(n.FunctionName()) + } + + // Get the timestamp + t, ok := gmstime.ConvertTimeZone(ctx.QueryTime(), gmstime.SystemTimezoneOffset(), sessionTimeZone) + if !ok { return nil, fmt.Errorf("invalid time zone: %s", sessionTimeZone) } - // Return a new Time with the location set to UTC so that any comparisons in SQL - // will only be done on the datetime portion and not the timezone. - return time.Date(newTime.Year(), newTime.Month(), newTime.Day(), - newTime.Hour(), newTime.Minute(), newTime.Second(), newTime.Nanosecond(), time.UTC), nil + // Calculate precision + precision := 1 + for i := 0; i < 9-fsp; i++ { + precision *= 10 + } + + // Round down nano based on precision + nano := precision * (t.Nanosecond() / precision) + + // Generate a new timestamp + tt := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), nano, t.Location()) + + return tt, nil } // WithChildren implements the Expression interface. @@ -1062,8 +1098,9 @@ func (ut *UTCTimestamp) Children() []sql.Expression { return nil } // Eval implements the sql.Expression interface. func (ut *UTCTimestamp) Eval(ctx *sql.Context, _ sql.Row) (interface{}, error) { t := ctx.QueryTime() - // TODO: Now should return a string formatted depending on context. This code handles string formatting - return t.UTC(), nil + // TODO: UTC Timestamp needs to also handle precision arguments + tt := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()) + return tt.UTC(), nil } // WithChildren implements the Expression interface. @@ -1409,200 +1446,96 @@ func (m *WeekOfYear) WithChildren(children ...sql.Expression) (sql.Expression, e } type CurrTime struct { - NoArgFunc + prec sql.Expression } func (c CurrTime) IsNonDeterministic() bool { return true } -var _ sql.FunctionExpression = CurrTime{} -var _ sql.CollationCoercible = CurrTime{} - -// Description implements sql.FunctionExpression -func (c CurrTime) Description() string { - return "returns the current time." -} +var _ sql.FunctionExpression = (*CurrTime)(nil) +var _ sql.CollationCoercible = (*CurrTime)(nil) -// CollationCoercibility implements the interface sql.CollationCoercible. -func (CurrTime) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { - return sql.Collation_binary, 5 -} - -func NewCurrTime() sql.Expression { - return CurrTime{ - NoArgFunc: NoArgFunc{"curtime", types.LongText}, +func NewCurrTime(args ...sql.Expression) (sql.Expression, error) { + c := &CurrTime{} + // parser should make it impossible to pass in more than one argument + if len(args) > 0 { + c.prec = args[0] } + return c, nil } -func NewCurrentTime() sql.Expression { - return CurrTime{ - NoArgFunc: NoArgFunc{"current_time", types.LongText}, - } -} - -func currTimeLogic(ctx *sql.Context, _ sql.Row) (interface{}, error) { - newNow, err := NewNow() - if err != nil { - return nil, err - } - - result, err := newNow.Eval(ctx, nil) - if err != nil { - return nil, err - } - - if t, ok := result.(time.Time); ok { - return fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()), nil - } else { - return nil, fmt.Errorf("unexpected type %T for NOW() result", result) - } -} - -// Eval implements sql.Expression -func (c CurrTime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - return currTimeLogic(ctx, row) -} - -// WithChildren implements sql.Expression -func (c CurrTime) WithChildren(children ...sql.Expression) (sql.Expression, error) { - return NoArgFuncWithChildren(c, children) -} - -const maxCurrTimestampPrecision = 6 - -type CurrTimestamp struct { - Args []sql.Expression +// FunctionName implements sql.FunctionExpression +func (c *CurrTime) FunctionName() string { + return "current_time" } -func (c *CurrTimestamp) IsNonDeterministic() bool { - return true +// Description implements sql.FunctionExpression +func (c *CurrTime) Description() string { + return "returns the current time." } -var _ sql.FunctionExpression = (*CurrTimestamp)(nil) -var _ sql.CollationCoercible = (*CurrTimestamp)(nil) - -// FunctionName implements sql.FunctionExpression -func (c *CurrTimestamp) FunctionName() string { - return "current_timestamp" +// Type implements the sql.Expression interface. +func (c *CurrTime) Type() sql.Type { + return types.Time } // CollationCoercibility implements the interface sql.CollationCoercible. -func (*CurrTimestamp) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { +func (*CurrTime) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { return sql.Collation_binary, 5 } -// Description implements sql.FunctionExpression -func (c *CurrTimestamp) Description() string { - return "returns the current date and time." -} - -func NewCurrTimestamp(args ...sql.Expression) (sql.Expression, error) { - return &CurrTimestamp{args}, nil -} - -func (c *CurrTimestamp) String() string { - if len(c.Args) == 0 { - return "CURRENT_TIMESTAMP()" +// String implements the sql.Expression interface. +func (c *CurrTime) String() string { + if c.prec == nil { + return "CURRENT_TIME()" } - return fmt.Sprintf("CURRENT_TIMESTAMP(%s)", c.Args[0].String()) -} - -func (c *CurrTimestamp) Type() sql.Type { return types.DatetimeMaxPrecision } -func (c *CurrTimestamp) IsNullable() bool { - for _, arg := range c.Args { - if arg.IsNullable() { - return true - } - } - return false + return fmt.Sprintf("CURRENT_TIME(%s)", c.prec.String()) } -func (c *CurrTimestamp) Resolved() bool { - for _, arg := range c.Args { - if !arg.Resolved() { - return false - } - } - return true -} +// IsNullable implements the sql.Expression interface. +func (c *CurrTime) IsNullable() bool { return false } -func (c *CurrTimestamp) WithChildren(children ...sql.Expression) (sql.Expression, error) { - if len(children) != len(c.Args) { - return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), len(c.Args)) +// Resolved implements the sql.Expression interface. +func (c *CurrTime) Resolved() bool { + if c.prec == nil { + return true } - return NewCurrTimestamp(children...) -} - -func (c *CurrTimestamp) Children() []sql.Expression { - return c.Args + return c.prec.Resolved() } -func (c *CurrTimestamp) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { - // If no arguments, just return with 0 precision - if len(c.Args) == 0 { - t := ctx.QueryTime() - _t := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()) - return _t, nil - } - - // If argument is null - if c.Args[0] == nil { - return nil, ErrTimeUnexpectedlyNil.New(c.FunctionName()) +// Children implements the sql.Expression interface. +func (c *CurrTime) Children() []sql.Expression { + if c.prec == nil { + return nil } + return []sql.Expression{c.prec} +} - // Evaluate value - val, err := c.Args[0].Eval(ctx, row) +// Eval implements sql.Expression +func (c *CurrTime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { + newNow, err := NewNow(c.prec) if err != nil { return nil, err } - // If null, throw syntax error - if val == nil { - return nil, ErrTimeUnexpectedlyNil.New(c.FunctionName()) - } - - // Must receive integer, all other types throw syntax error - fsp := 0 - switch val.(type) { - case int: - fsp = val.(int) - case int8: - fsp = int(val.(int8)) - case int16: - fsp = int(val.(int16)) - case int32: - fsp = int(val.(int32)) - case int64: - fsp = int(val.(int64)) - default: - return nil, sql.ErrInvalidArgumentType.New(c.FunctionName()) - } - - // Parse and return answer - if fsp > maxCurrTimestampPrecision { - return nil, ErrTooHighPrecision.New(fsp, c.FunctionName(), maxCurrTimestampPrecision) - } else if fsp < 0 { - return nil, sql.ErrInvalidArgumentType.New(c.FunctionName()) + result, err := newNow.Eval(ctx, row) + if err != nil { + return nil, err } - // Get the timestamp - t := ctx.QueryTime() - - // Calculate precision - prec := 1 - for i := 0; i < 9-fsp; i++ { - prec *= 10 + if t, ok := result.(time.Time); ok { + // TODO: this is wrong, we need to include nanoseconds + return fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second()), nil + } else { + return nil, fmt.Errorf("unexpected type %T for NOW() result", result) } +} - // Round down nano based on precision - nano := prec * (t.Nanosecond() / prec) - - // Generate a new timestamp - _t := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), nano, t.Location()) - - return _t, nil +// WithChildren implements sql.Expression +func (c *CurrTime) WithChildren(children ...sql.Expression) (sql.Expression, error) { + return NoArgFuncWithChildren(c, children) } // Time is a function takes the Time part out from a datetime expression. diff --git a/sql/expression/function/time_test.go b/sql/expression/function/time_test.go index 92c6c790a8..37cca73ae9 100644 --- a/sql/expression/function/time_test.go +++ b/sql/expression/function/time_test.go @@ -414,76 +414,6 @@ func TestCalcWeek(t *testing.T) { require.EqualValues(t, w, 53) } -func TestNow(t *testing.T) { - date := time.Date(2018, time.December, 2, 16, 25, 0, 0, time.Local) - testNowFunc := func() time.Time { - return date - } - - var ctx *sql.Context - err := sql.RunWithNowFunc(testNowFunc, func() error { - ctx = sql.NewEmptyContext() - return nil - }) - require.NoError(t, err) - - tests := []struct { - args []sql.Expression - result time.Time - expectErr bool - }{ - { - args: nil, - result: date, - expectErr: false, - }, - { - args: []sql.Expression{expression.NewLiteral(0, types.Int8)}, - result: date, - expectErr: false, - }, - { - args: []sql.Expression{expression.NewLiteral(0, types.Int64)}, - result: date, - expectErr: false, - }, - { - args: []sql.Expression{expression.NewLiteral(6, types.Uint8)}, - result: date, - expectErr: false, - }, - { - args: []sql.Expression{expression.NewLiteral(7, types.Int8)}, - result: time.Time{}, - expectErr: true, - }, - { - args: []sql.Expression{expression.NewLiteral(-1, types.Int8)}, - result: time.Time{}, - expectErr: true, - }, - { - args: []sql.Expression{expression.NewConvert(expression.NewLiteral("2020-10-10 01:02:03", types.Text), expression.ConvertToDatetime)}, - result: time.Time{}, - expectErr: true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprint(test.args), func(t *testing.T) { - ut, err := NewNow(test.args...) - if !test.expectErr { - require.NoError(t, err) - val, err := ut.Eval(ctx, nil) - require.NoError(t, err) - assert.Equal(t, test.result, val) - } else { - assert.Error(t, err) - } - }) - } -} - func TestUTCTimestamp(t *testing.T) { date := time.Date(2018, time.December, 2, 16, 25, 0, 0, time.Local) testNowFunc := func() time.Time { @@ -584,17 +514,17 @@ func TestDate(t *testing.T) { } } -func TestCurrentTimestamp(t *testing.T) { - f, _ := NewCurrTimestamp(expression.NewGetField(0, types.LongText, "foo", false)) +func TestNow(t *testing.T) { + f, _ := NewNow(expression.NewGetField(0, types.LongText, "foo", false)) date := time.Date( - 2021, // year - 1, // month - 1, // day - 8, // hour - 30, // min - 15, // sec - 12345678, // nsec - time.UTC, // location (UTC) + 2021, // year + 1, // month + 1, // day + 8, // hour + 30, // min + 15, // sec + 123456789, // nsec + time.UTC, // location (UTC) ) testCases := []struct { @@ -604,15 +534,15 @@ func TestCurrentTimestamp(t *testing.T) { err bool }{ {"null date", sql.NewRow(nil), nil, true}, - {"different int type", sql.NewRow(int8(0)), time.Date(2021, 1, 1, 8, 30, 15, 0, time.UTC), false}, + {"different int type", sql.NewRow(int8(0)), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 0, time.UTC), false}, {"precision of -1", sql.NewRow(-1), nil, true}, - {"precision of 0", sql.NewRow(0), time.Date(2021, 1, 1, 8, 30, 15, 0, time.UTC), false}, - {"precision of 1 trailing 0s are trimmed", sql.NewRow(1), time.Date(2021, 1, 1, 8, 30, 15, 0, time.UTC), false}, - {"precision of 2", sql.NewRow(2), time.Date(2021, 1, 1, 8, 30, 15, 10000000, time.UTC), false}, - {"precision of 3", sql.NewRow(3), time.Date(2021, 1, 1, 8, 30, 15, 12000000, time.UTC), false}, - {"precision of 4", sql.NewRow(4), time.Date(2021, 1, 1, 8, 30, 15, 12300000, time.UTC), false}, - {"precision of 5", sql.NewRow(5), time.Date(2021, 1, 1, 8, 30, 15, 12340000, time.UTC), false}, - {"precision of 6", sql.NewRow(6), time.Date(2021, 1, 1, 8, 30, 15, 12345000, time.UTC), false}, + {"precision of 0", sql.NewRow(0), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 0, time.UTC), false}, + {"precision of 1", sql.NewRow(1), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 100000000, time.UTC), false}, + {"precision of 2", sql.NewRow(2), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 120000000, time.UTC), false}, + {"precision of 3", sql.NewRow(3), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 123000000, time.UTC), false}, + {"precision of 4", sql.NewRow(4), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 123400000, time.UTC), false}, + {"precision of 5", sql.NewRow(5), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 123450000, time.UTC), false}, + {"precision of 6", sql.NewRow(6), time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 123456000, time.UTC), false}, {"precision of 7 which is too high", sql.NewRow(7), nil, true}, {"incorrect type", sql.NewRow("notanint"), nil, true}, } diff --git a/sql/planbuilder/ddl.go b/sql/planbuilder/ddl.go index 56f6affc4d..d7ecf04c28 100644 --- a/sql/planbuilder/ddl.go +++ b/sql/planbuilder/ddl.go @@ -925,10 +925,10 @@ func (b *Builder) buildDefaultExpression(inScope *scope, defaultExpr ast.Expr) * if f, ok := parsedExpr.(*expression.UnresolvedFunction); ok { // Datetime and Timestamp columns allow now and current_timestamp to not be enclosed in parens, // but they still need to be treated as function expressions - if f.Name() == "now" || f.Name() == "current_timestamp" { + switch strings.ToLower(f.Name()) { + case "now", "current_timestamp", "localtime", "localtimestamp": isLiteral = false - } else { - // All other functions must *always* be enclosed in parens + default: err := sql.ErrSyntaxError.New("column default function expressions must be enclosed in parentheses") b.handleErr(err) } @@ -996,6 +996,40 @@ func (b *Builder) buildExternalCreateIndex(inScope *scope, ddl *ast.DDL) (outSco return } +// validateOnUpdateExprs ensures that the Time functions used for OnUpdate for columns is correct +func validateOnUpdateExprs(col *sql.Column) error { + if col.OnUpdate == nil { + return nil + } + if !(types.IsDatetimeType(col.Type) || types.IsTimestampType(col.Type)) { + return sql.ErrInvalidOnUpdate.New(col.Name) + } + now, ok := col.OnUpdate.Expr.(*function.Now) + if !ok { + return nil + } + children := now.Children() + if len(children) == 0 { + return nil + } + lit, isLit := children[0].(*expression.Literal) + if !isLit { + return nil + } + val, err := lit.Eval(nil, nil) + if err != nil { + return err + } + prec, ok := types.CoalesceInt(val) + if !ok { + return sql.ErrInvalidOnUpdate.New(col.Name) + } + if prec != 0 { + return sql.ErrInvalidOnUpdate.New(col.Name) + } + return nil +} + // TableSpecToSchema creates a sql.Schema from a parsed TableSpec func (b *Builder) tableSpecToSchema(inScope, outScope *scope, db sql.Database, tableName string, tableSpec *ast.TableSpec, forceInvalidCollation bool) (sql.PrimaryKeySchema, sql.CollationID) { // todo: somewhere downstream updates an ALTER MODIY column's type collation @@ -1088,8 +1122,9 @@ func (b *Builder) tableSpecToSchema(inScope, outScope *scope, db sql.Database, t for i, onUpdateExpr := range updates { schema[i].OnUpdate = b.convertDefaultExpression(outScope, onUpdateExpr, schema[i].Type, schema[i].Nullable) - if schema[i].OnUpdate != nil && !(types.IsDatetimeType(schema[i].Type) || types.IsTimestampType(schema[i].Type)) { - b.handleErr(sql.ErrInvalidOnUpdate.New(schema[i].Name)) + err := validateOnUpdateExprs(schema[i]) + if err != nil { + b.handleErr(err) } } @@ -1352,7 +1387,7 @@ func (b *Builder) convertDefaultExpression(inScope *scope, defaultExpr ast.Expr, } else if !isParenthesized { if _, ok := resExpr.(sql.FunctionExpression); ok { switch resExpr.(type) { - case *function.Now, *function.CurrTimestamp: + case *function.Now: // Datetime and Timestamp columns allow now and current_timestamp to not be enclosed in parens, // but they still need to be treated as function expressions isLiteral = false diff --git a/sql/planbuilder/scalar.go b/sql/planbuilder/scalar.go index 0b79eae897..8d0fef9729 100644 --- a/sql/planbuilder/scalar.go +++ b/sql/planbuilder/scalar.go @@ -59,14 +59,6 @@ func (b *Builder) buildScalar(inScope *scope, e ast.Expr) sql.Expression { } len := b.buildScalar(inScope, v.To) return &function.Substring{Str: name, Start: start, Len: len} - case *ast.CurTimeFuncExpr: - fsp := b.buildScalar(inScope, v.Fsp) - - if inScope.parent.activeSubquery != nil { - inScope.parent.activeSubquery.markVolatile() - } - - return &function.CurrTimestamp{Args: []sql.Expression{fsp}} case *ast.TrimExpr: pat := b.buildScalar(inScope, v.Pattern) str := b.buildScalar(inScope, v.Str) diff --git a/sql/types/number.go b/sql/types/number.go index 108880cd25..607a4628d4 100644 --- a/sql/types/number.go +++ b/sql/types/number.go @@ -1490,3 +1490,29 @@ func isString(v interface{}) bool { return false } } + +// CoalesceInt converts a int8/int16/... to int +func CoalesceInt(val interface{}) (int, bool) { + switch v := val.(type) { + case int: + return v, true + case int8: + return int(v), true + case int16: + return int(v), true + case int32: + return int(v), true + case int64: + return int(v), true + case uint8: + return int(v), true + case uint16: + return int(v), true + case uint32: + return int(v), true + case uint64: + return int(v), true + default: + return 0, false + } +}