diff --git a/cmd/explaintest/r/topn_push_down.result b/cmd/explaintest/r/topn_push_down.result index 9f6dcbfdfdaa0..c9caf5fdefe1b 100644 --- a/cmd/explaintest/r/topn_push_down.result +++ b/cmd/explaintest/r/topn_push_down.result @@ -4,7 +4,7 @@ CREATE TABLE `tr` ( `domain_type` tinyint(4) NOT NULL, `business_type` tinyint(4) NOT NULL, `trade_type` tinyint(4) NOT NULL DEFAULT '1', -`trade_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +`trade_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `trade_status` tinyint(4) NOT NULL DEFAULT '0', `trade_pay_status` tinyint(4) NOT NULL DEFAULT '0', `delivery_type` tinyint(4) NOT NULL DEFAULT '0', @@ -24,10 +24,10 @@ CREATE TABLE `tr` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL, -`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, -`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, -`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), +`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, @@ -66,10 +66,10 @@ CREATE TABLE `p` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL DEFAULT '1', -`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, -`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, -`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), +`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, @@ -117,10 +117,10 @@ CREATE TABLE `te` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL, -`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +`client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, -`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, -`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), +`server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, diff --git a/cmd/explaintest/t/topn_push_down.test b/cmd/explaintest/t/topn_push_down.test index e716040a34777..a6e9c1be9f610 100644 --- a/cmd/explaintest/t/topn_push_down.test +++ b/cmd/explaintest/t/topn_push_down.test @@ -4,7 +4,7 @@ CREATE TABLE `tr` ( `domain_type` tinyint(4) NOT NULL, `business_type` tinyint(4) NOT NULL, `trade_type` tinyint(4) NOT NULL DEFAULT '1', - `trade_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `trade_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `trade_status` tinyint(4) NOT NULL DEFAULT '0', `trade_pay_status` tinyint(4) NOT NULL DEFAULT '0', `delivery_type` tinyint(4) NOT NULL DEFAULT '0', @@ -24,10 +24,10 @@ CREATE TABLE `tr` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL, - `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, - `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, @@ -68,10 +68,10 @@ CREATE TABLE `p` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL DEFAULT '1', - `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, - `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, @@ -121,10 +121,10 @@ CREATE TABLE `te` ( `device_identy` varchar(36) NOT NULL, `uuid` varchar(32) NOT NULL, `status_flag` tinyint(4) NOT NULL, - `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + `client_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `client_update_time` timestamp(3) NULL DEFAULT NULL, - `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `server_create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `server_update_time` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `creator_id` bigint(20) DEFAULT NULL, `creator_name` varchar(32) DEFAULT NULL, `updator_id` bigint(20) DEFAULT NULL, diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index ae10477282998..d59cd96c2e862 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -924,6 +924,21 @@ func (s *testIntegrationSuite) TestDropAutoIncrement(c *C) { tk.MustExec("set @@tidb_allow_remove_auto_inc = off") } +func (s *testIntegrationSuite) TestInvalidDefaultOrOnUpdateClause(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create database if not exists test") + tk.MustExec("use test") + + sql := "create table t2 (a datetime(2) default current_timestamp(3))" + assertErrorCode(c, tk, sql, mysql.ErrInvalidDefault) + sql = "create table t2 (a datetime(2) default current_timestamp(2) on update current_timestamp)" + assertErrorCode(c, tk, sql, mysql.ErrInvalidOnUpdate) + sql = "create table t2 (a datetime default current_timestamp on update current_timestamp(2))" + assertErrorCode(c, tk, sql, mysql.ErrInvalidOnUpdate) + sql = "create table t2 (a datetime(2) default current_timestamp(2) on update current_timestamp(3))" + assertErrorCode(c, tk, sql, mysql.ErrInvalidOnUpdate) +} + func (s *testIntegrationSuite) TestBitDefaultValue(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("create database if not exists test") diff --git a/ddl/ddl.go b/ddl/ddl.go index 88679f82e0512..0f63ff235b0b6 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -130,6 +130,8 @@ var ( errBlobCantHaveDefault = terror.ClassDDL.New(codeBlobCantHaveDefault, mysql.MySQLErrName[mysql.ErrBlobCantHaveDefault]) errTooLongIndexComment = terror.ClassDDL.New(codeErrTooLongIndexComment, mysql.MySQLErrName[mysql.ErrTooLongIndexComment]) + // ErrInvalidDefaultValue returns for invalid default value for columns. + ErrInvalidDefaultValue = terror.ClassDDL.New(codeInvalidDefaultValue, mysql.MySQLErrName[mysql.ErrInvalidDefault]) // ErrDupKeyName returns for duplicated key name ErrDupKeyName = terror.ClassDDL.New(codeDupKeyName, "duplicate key name") // ErrInvalidDBState returns for invalid database state. @@ -679,6 +681,7 @@ func init() { codeFileNotFound: mysql.ErrFileNotFound, codeErrorOnRename: mysql.ErrErrorOnRename, codeBadField: mysql.ErrBadField, + codeInvalidDefaultValue: mysql.ErrInvalidDefault, codeInvalidUseOfNull: mysql.ErrInvalidUseOfNull, codeUnsupportedOnGeneratedColumn: mysql.ErrUnsupportedOnGeneratedColumn, codeGeneratedColumnNonPrior: mysql.ErrGeneratedColumnNonPrior, diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index e2a8f29967700..305b9578f377c 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -519,6 +519,14 @@ func columnDefToCol(ctx sessionctx.Context, offset int, colDef *ast.ColumnDef, o col.Flag |= mysql.UniqueKeyFlag } case ast.ColumnOptionDefaultValue: + if col.Tp == mysql.TypeTimestamp || col.Tp == mysql.TypeDatetime { + switch x := v.Expr.(type) { + case *ast.FuncCallExpr: + if x.FnName.L == ast.CurrentTimestamp && !expression.IsValidCurrentTimestampExpr(v.Expr, colDef.Tp) { + return nil, nil, ErrInvalidDefaultValue.GenWithStackByArgs(col.Name) + } + } + } hasDefaultValue, err = setDefaultValue(ctx, col, v) if err != nil { return nil, nil, errors.Trace(err) @@ -527,7 +535,7 @@ func columnDefToCol(ctx sessionctx.Context, offset int, colDef *ast.ColumnDef, o case ast.ColumnOptionOnUpdate: // TODO: Support other time functions. if col.Tp == mysql.TypeTimestamp || col.Tp == mysql.TypeDatetime { - if !expression.IsCurrentTimestampExpr(v.Expr) { + if !expression.IsValidCurrentTimestampExpr(v.Expr, colDef.Tp) { return nil, nil, ErrInvalidOnUpdate.GenWithStackByArgs(col.Name) } } else { @@ -2003,7 +2011,7 @@ func setDefaultAndComment(ctx sessionctx.Context, col *table.Column, options []* case ast.ColumnOptionOnUpdate: // TODO: Support other time functions. if col.Tp == mysql.TypeTimestamp || col.Tp == mysql.TypeDatetime { - if !expression.IsCurrentTimestampExpr(opt.Expr) { + if !expression.IsValidCurrentTimestampExpr(opt.Expr, &col.FieldType) { return ErrInvalidOnUpdate.GenWithStackByArgs(col.Name) } } else { diff --git a/executor/show.go b/executor/show.go index 704045f35b545..6859c5c80a764 100644 --- a/executor/show.go +++ b/executor/show.go @@ -601,6 +601,7 @@ func (e *ShowExec) fetchShowCreateTable() error { } if mysql.HasOnUpdateNowFlag(col.Flag) { buf.WriteString(" ON UPDATE CURRENT_TIMESTAMP") + buf.WriteString(table.OptionalFsp(&col.FieldType)) } } if len(col.Comment) > 0 { diff --git a/executor/show_test.go b/executor/show_test.go index 0b691a04f5661..377583a75baf3 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -574,6 +574,7 @@ func (s *testSuite) TestShow2(c *C) { c_timestamp timestamp, c_timestamp_default timestamp default current_timestamp, c_timestamp_default_3 timestamp(3) default current_timestamp(3), + c_timestamp_default_4 timestamp(3) default current_timestamp(3) on update current_timestamp(3), c_blob blob, c_tinyblob tinyblob, c_mediumblob mediumblob, @@ -609,6 +610,7 @@ func (s *testSuite) TestShow2(c *C) { "[c_timestamp timestamp YES select,insert,update,references ]\n" + "[c_timestamp_default timestamp YES CURRENT_TIMESTAMP select,insert,update,references ]\n" + "[c_timestamp_default_3 timestamp(3) YES CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + + "[c_timestamp_default_4 timestamp(3) YES CURRENT_TIMESTAMP(3) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + "[c_blob blob YES select,insert,update,references ]\n" + "[c_tinyblob tinyblob YES select,insert,update,references ]\n" + "[c_mediumblob mediumblob YES select,insert,update,references ]\n" + @@ -741,7 +743,9 @@ func (s *testSuite) TestShowCreateTable(c *C) { "`b` timestamp(3) default current_timestamp(3),\n" + "`c` datetime default current_timestamp,\n" + "`d` datetime(4) default current_timestamp(4),\n" + - "`e` varchar(20) default 'cUrrent_tImestamp')") + "`e` varchar(20) default 'cUrrent_tImestamp',\n" + + "`f` datetime(2) default current_timestamp(2) on update current_timestamp(2),\n" + + "`g` timestamp(2) default current_timestamp(2) on update current_timestamp(2))") tk.MustQuery("show create table `t`").Check(testutil.RowsWithSep("|", ""+ "t CREATE TABLE `t` (\n"+ @@ -749,7 +753,9 @@ func (s *testSuite) TestShowCreateTable(c *C) { " `b` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),\n"+ " `c` datetime DEFAULT CURRENT_TIMESTAMP,\n"+ " `d` datetime(4) DEFAULT CURRENT_TIMESTAMP(4),\n"+ - " `e` varchar(20) DEFAULT 'cUrrent_tImestamp'\n"+ + " `e` varchar(20) DEFAULT 'cUrrent_tImestamp',\n"+ + " `f` datetime(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2),\n"+ + " `g` timestamp(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2)\n"+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", )) tk.MustExec("drop table t") diff --git a/expression/helper.go b/expression/helper.go index b3c6dfd8ba520..dc38b6f611228 100644 --- a/expression/helper.go +++ b/expression/helper.go @@ -35,12 +35,24 @@ func boolToInt64(v bool) int64 { return 0 } -// IsCurrentTimestampExpr returns whether e is CurrentTimestamp expression. -func IsCurrentTimestampExpr(e ast.ExprNode) bool { - if fn, ok := e.(*ast.FuncCallExpr); ok && fn.FnName.L == ast.CurrentTimestamp { - return true +// IsValidCurrentTimestampExpr returns true if exprNode is a valid CurrentTimestamp expression. +// Here `valid` means it is consistent with the given fieldType's Decimal. +func IsValidCurrentTimestampExpr(exprNode ast.ExprNode, fieldType *types.FieldType) bool { + fn, isFuncCall := exprNode.(*ast.FuncCallExpr) + if !isFuncCall || fn.FnName.L != ast.CurrentTimestamp { + return false } - return false + + containsArg := len(fn.Args) > 0 + // Fsp represents fractional seconds precision. + containsFsp := fieldType != nil && fieldType.Decimal > 0 + var isConsistent bool + if containsArg { + v, ok := fn.Args[0].(*driver.ValueExpr) + isConsistent = ok && fieldType != nil && v.Datum.GetInt64() == int64(fieldType.Decimal) + } + + return (containsArg && isConsistent) || (!containsArg && !containsFsp) } // GetTimeValue gets the time value with type tp. diff --git a/expression/helper_test.go b/expression/helper_test.go index e0acecfdf817b..420cfa6e55a67 100644 --- a/expression/helper_test.go +++ b/expression/helper_test.go @@ -23,6 +23,7 @@ import ( "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/types" + driver "github.com/pingcap/tidb/types/parser_driver" "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/testleak" ) @@ -108,11 +109,28 @@ func (s *testExpressionSuite) TestGetTimeValue(c *C) { func (s *testExpressionSuite) TestIsCurrentTimestampExpr(c *C) { defer testleak.AfterTest(c)() - v := IsCurrentTimestampExpr(ast.NewValueExpr("abc")) - c.Assert(v, IsFalse) + buildTimestampFuncCallExpr := func(i int64) *ast.FuncCallExpr { + var args []ast.ExprNode + if i != 0 { + args = []ast.ExprNode{&driver.ValueExpr{Datum: types.NewIntDatum(i)}} + } + return &ast.FuncCallExpr{FnName: model.NewCIStr("CURRENT_TIMESTAMP"), Args: args} + } - v = IsCurrentTimestampExpr(&ast.FuncCallExpr{FnName: model.NewCIStr("CURRENT_TIMESTAMP")}) + v := IsValidCurrentTimestampExpr(ast.NewValueExpr("abc"), nil) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(0), nil) + c.Assert(v, IsTrue) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(3), &types.FieldType{Decimal: 3}) c.Assert(v, IsTrue) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(1), &types.FieldType{Decimal: 3}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(0), &types.FieldType{Decimal: 3}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(2), &types.FieldType{Decimal: 0}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(2), nil) + c.Assert(v, IsFalse) } func (s *testExpressionSuite) TestCurrentTimestampTimeZone(c *C) { diff --git a/infoschema/tables_test.go b/infoschema/tables_test.go index 95658f2ad9ca7..5c859d95d8f6d 100644 --- a/infoschema/tables_test.go +++ b/infoschema/tables_test.go @@ -285,23 +285,27 @@ func (s *testTableSuite) TestCurrentTimestampAsDefault(c *C) { c_timestamp_default_3 timestamp(3) default current_timestamp(3), c_varchar_default varchar(20) default "current_timestamp", c_varchar_default_3 varchar(20) default "current_timestamp(3)", + c_varchar_default_on_update datetime default current_timestamp on update current_timestamp, + c_varchar_default_on_update_fsp datetime(3) default current_timestamp(3) on update current_timestamp(3), c_varchar_default_with_case varchar(20) default "cUrrent_tImestamp" );`) - tk.MustQuery(`SELECT column_name, column_default + tk.MustQuery(`SELECT column_name, column_default, extra FROM information_schema.COLUMNS WHERE table_schema = "default_time_test" AND table_name = "default_time_table" ORDER BY column_name`, ).Check(testkit.Rows( - "c_datetime ", - "c_datetime_default CURRENT_TIMESTAMP", - "c_datetime_default_2 CURRENT_TIMESTAMP(2)", - "c_timestamp ", - "c_timestamp_default CURRENT_TIMESTAMP", - "c_timestamp_default_3 CURRENT_TIMESTAMP(3)", - "c_varchar_default current_timestamp", - "c_varchar_default_3 current_timestamp(3)", - "c_varchar_default_with_case cUrrent_tImestamp", + "c_datetime ", + "c_datetime_default CURRENT_TIMESTAMP ", + "c_datetime_default_2 CURRENT_TIMESTAMP(2) ", + "c_timestamp ", + "c_timestamp_default CURRENT_TIMESTAMP ", + "c_timestamp_default_3 CURRENT_TIMESTAMP(3) ", + "c_varchar_default current_timestamp ", + "c_varchar_default_3 current_timestamp(3) ", + "c_varchar_default_on_update CURRENT_TIMESTAMP DEFAULT_GENERATED on update CURRENT_TIMESTAMP", + "c_varchar_default_on_update_fsp CURRENT_TIMESTAMP(3) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3)", + "c_varchar_default_with_case cUrrent_tImestamp ", )) tk.MustExec("DROP DATABASE default_time_test") } diff --git a/table/column.go b/table/column.go index 5119d9c3b5b72..8a79ce5b5703b 100644 --- a/table/column.go +++ b/table/column.go @@ -20,6 +20,7 @@ package table import ( "context" "fmt" + "strconv" "strings" "time" "unicode/utf8" @@ -272,7 +273,7 @@ func NewColDesc(col *Column) *ColDesc { } else if mysql.HasOnUpdateNowFlag(col.Flag) { //in order to match the rules of mysql 8.0.16 version //see https://github.com/pingcap/tidb/issues/10337 - extra = "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + extra = "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + OptionalFsp(&col.FieldType) } else if col.IsGenerated() { if col.GeneratedStored { extra = "STORED GENERATED" @@ -500,3 +501,12 @@ func GetZeroValue(col *model.ColumnInfo) types.Datum { } return d } + +// OptionalFsp convert a FieldType.Decimal to string. +func OptionalFsp(fieldType *types.FieldType) string { + fsp := fieldType.Decimal + if fsp == 0 { + return "" + } + return "(" + strconv.Itoa(fsp) + ")" +}