diff --git a/.github/workflows/cluster_endtoend_mysql80.yml b/.github/workflows/cluster_endtoend_mysql80.yml new file mode 100644 index 00000000000..a38816ec512 --- /dev/null +++ b/.github/workflows/cluster_endtoend_mysql80.yml @@ -0,0 +1,50 @@ +# DO NOT MODIFY: THIS FILE IS GENERATED USING "make generate_ci_workflows" + +name: Cluster (mysql80) +on: [push, pull_request] +jobs: + + build: + name: Run endtoend tests on Cluster (mysql80) + runs-on: ubuntu-20.04 + + steps: + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.15 + + - name: Tune the OS + run: | + echo '1024 65535' | sudo tee -a /proc/sys/net/ipv4/ip_local_port_range + + # TEMPORARY WHILE GITHUB FIXES THIS https://github.com/actions/virtual-environments/issues/3185 + - name: Add the current IP address, long hostname and short hostname record to /etc/hosts file + run: | + echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts + # DON'T FORGET TO REMOVE CODE ABOVE WHEN ISSUE IS ADRESSED! + + - name: Check out code + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + sudo apt-get update + sudo apt-get install -y mysql-server mysql-client make unzip g++ etcd curl git wget eatmydata + sudo service mysql stop + sudo service etcd stop + sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ + sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld + go mod download + + wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb + sudo apt-get install -y gnupg2 + sudo dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb + sudo apt-get update + sudo apt-get install percona-xtrabackup-24 + + - name: Run cluster endtoend test + timeout-minutes: 30 + run: | + source build.env + eatmydata -- go run test.go -docker=false -print-log -follow -shard mysql80 diff --git a/go/test/endtoend/vtgate/main_test.go b/go/test/endtoend/vtgate/main_test.go index e3c4817a800..0629e1e06a7 100644 --- a/go/test/endtoend/vtgate/main_test.go +++ b/go/test/endtoend/vtgate/main_test.go @@ -418,6 +418,7 @@ func TestMain(m *testing.M) { return 1 } + clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, "-enable_system_settings=true") // Start vtgate err = clusterInstance.StartVtgate() if err != nil { diff --git a/go/test/endtoend/vtgate/misc_test.go b/go/test/endtoend/vtgate/misc_test.go index 042c5062205..fa3ef7870bd 100644 --- a/go/test/endtoend/vtgate/misc_test.go +++ b/go/test/endtoend/vtgate/misc_test.go @@ -595,6 +595,51 @@ func TestSubQueryOnTopOfSubQuery(t *testing.T) { assertMatches(t, conn, "select id1 from t1 where id1 not in (select id3 from t2) and id2 in (select id4 from t2) order by id1", `[[INT64(3)] [INT64(4)]]`) } +func TestFunctionInDefault(t *testing.T) { + defer cluster.PanicHandler(t) + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + defer conn.Close() + + // set the sql mode ALLOW_INVALID_DATES + exec(t, conn, `SET sql_mode = 'ALLOW_INVALID_DATES'`) + + _, err = conn.ExecuteFetch(`create table function_default (x varchar(25) DEFAULT (TRIM(" check ")))`, 1000, true) + // this query fails because mysql57 does not support functions in default clause + require.Error(t, err) + + // verify that currenet_timestamp and it's aliases work as default values + exec(t, conn, `create table function_default ( +ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +dt2 DATETIME DEFAULT CURRENT_TIMESTAMP, +ts3 TIMESTAMP DEFAULT 0, +dt3 DATETIME DEFAULT 0, +ts4 TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP, +dt4 DATETIME DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP, +ts5 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +ts6 TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP, +dt5 DATETIME ON UPDATE CURRENT_TIMESTAMP, +dt6 DATETIME NOT NULL ON UPDATE CURRENT_TIMESTAMP, +ts7 TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), +ts8 TIMESTAMP DEFAULT NOW(), +ts9 TIMESTAMP DEFAULT LOCALTIMESTAMP, +ts10 TIMESTAMP DEFAULT LOCALTIME, +ts11 TIMESTAMP DEFAULT LOCALTIMESTAMP(), +ts12 TIMESTAMP DEFAULT LOCALTIME() +)`) + exec(t, conn, "drop table function_default") + + _, err = conn.ExecuteFetch(`create table function_default (ts TIMESTAMP DEFAULT UTC_TIMESTAMP)`, 1000, true) + // this query fails because utc_timestamp is not supported in default clause + require.Error(t, err) + + exec(t, conn, `create table function_default (x varchar(25) DEFAULT "check")`) + exec(t, conn, "drop table function_default") +} + func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) { t.Helper() qr := exec(t, conn, query) diff --git a/go/test/endtoend/vtgate/mysql80/main_test.go b/go/test/endtoend/vtgate/mysql80/main_test.go new file mode 100644 index 00000000000..e3dfa1319c5 --- /dev/null +++ b/go/test/endtoend/vtgate/mysql80/main_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vtgate + +import ( + "flag" + "os" + "testing" + + "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/test/endtoend/cluster" +) + +var ( + clusterInstance *cluster.LocalProcessCluster + vtParams mysql.ConnParams + KeyspaceName = "ks" + Cell = "test" +) + +func TestMain(m *testing.M) { + defer cluster.PanicHandler(nil) + flag.Parse() + + exitCode := func() int { + clusterInstance = cluster.NewCluster(Cell, "localhost") + defer clusterInstance.Teardown() + + // Start topo server + err := clusterInstance.StartTopo() + if err != nil { + return 1 + } + + // Start keyspace + keyspace := &cluster.Keyspace{ + Name: KeyspaceName, + } + err = clusterInstance.StartUnshardedKeyspace(*keyspace, 0, false) + if err != nil { + return 1 + } + + clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, "-enable_system_settings=true") + // Start vtgate + err = clusterInstance.StartVtgate() + if err != nil { + return 1 + } + vtParams = mysql.ConnParams{ + Host: clusterInstance.Hostname, + Port: clusterInstance.VtgateMySQLPort, + } + return m.Run() + }() + os.Exit(exitCode) +} diff --git a/go/test/endtoend/vtgate/mysql80/misc_test.go b/go/test/endtoend/vtgate/mysql80/misc_test.go new file mode 100644 index 00000000000..0f614535dc1 --- /dev/null +++ b/go/test/endtoend/vtgate/mysql80/misc_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vtgate + +import ( + "context" + "testing" + + "vitess.io/vitess/go/test/endtoend/cluster" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/sqltypes" +) + +func TestFunctionInDefault(t *testing.T) { + defer cluster.PanicHandler(t) + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + defer conn.Close() + + // set the sql mode ALLOW_INVALID_DATES + exec(t, conn, `SET sql_mode = 'ALLOW_INVALID_DATES'`) + + exec(t, conn, `create table function_default (x varchar(25) DEFAULT (TRIM(" check ")))`) + exec(t, conn, "drop table function_default") + + exec(t, conn, `create table function_default ( +ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +dt2 DATETIME DEFAULT CURRENT_TIMESTAMP, +ts3 TIMESTAMP DEFAULT 0, +dt3 DATETIME DEFAULT 0, +ts4 TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP, +dt4 DATETIME DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP, +ts5 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +ts6 TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP, +dt5 DATETIME ON UPDATE CURRENT_TIMESTAMP, +dt6 DATETIME NOT NULL ON UPDATE CURRENT_TIMESTAMP, +ts7 TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), +ts8 TIMESTAMP DEFAULT NOW(), +ts9 TIMESTAMP DEFAULT LOCALTIMESTAMP, +ts10 TIMESTAMP DEFAULT LOCALTIME, +ts11 TIMESTAMP DEFAULT LOCALTIMESTAMP(), +ts12 TIMESTAMP DEFAULT LOCALTIME() +)`) + exec(t, conn, "drop table function_default") + + // this query works because utc_timestamp will get parenthesised before reaching MySQL. However, this syntax is not supported in MySQL 8.0 + exec(t, conn, `create table function_default (ts TIMESTAMP DEFAULT UTC_TIMESTAMP)`) + exec(t, conn, "drop table function_default") + + exec(t, conn, `create table function_default (x varchar(25) DEFAULT "check")`) + exec(t, conn, "drop table function_default") +} + +func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result { + t.Helper() + qr, err := conn.ExecuteFetch(query, 1000, true) + require.NoError(t, err, "for query: "+query) + return qr +} diff --git a/go/vt/sqlparser/ast_format.go b/go/vt/sqlparser/ast_format.go index 6cfda1e4d06..d35f8ff519c 100644 --- a/go/vt/sqlparser/ast_format.go +++ b/go/vt/sqlparser/ast_format.go @@ -464,7 +464,14 @@ func (ct *ColumnType) Format(buf *TrackedBuffer) { } } if ct.Options.Default != nil { - buf.astPrintf(ct, " %s %v", keywordStrings[DEFAULT], ct.Options.Default) + buf.astPrintf(ct, " %s", keywordStrings[DEFAULT]) + _, isLiteral := ct.Options.Default.(*Literal) + _, isNullVal := ct.Options.Default.(*NullVal) + if isLiteral || isNullVal || isExprAliasForCurrentTimeStamp(ct.Options.Default) { + buf.astPrintf(ct, " %v", ct.Options.Default) + } else { + buf.astPrintf(ct, " (%v)", ct.Options.Default) + } } if ct.Options.OnUpdate != nil { buf.astPrintf(ct, " %s %s %v", keywordStrings[ON], keywordStrings[UPDATE], ct.Options.OnUpdate) diff --git a/go/vt/sqlparser/ast_format_fast.go b/go/vt/sqlparser/ast_format_fast.go index 5746f5106c8..536875f07c4 100644 --- a/go/vt/sqlparser/ast_format_fast.go +++ b/go/vt/sqlparser/ast_format_fast.go @@ -664,8 +664,16 @@ func (ct *ColumnType) formatFast(buf *TrackedBuffer) { if ct.Options.Default != nil { buf.WriteByte(' ') buf.WriteString(keywordStrings[DEFAULT]) - buf.WriteByte(' ') - ct.Options.Default.formatFast(buf) + _, isLiteral := ct.Options.Default.(*Literal) + _, isNullVal := ct.Options.Default.(*NullVal) + if isLiteral || isNullVal || isExprAliasForCurrentTimeStamp(ct.Options.Default) { + buf.WriteByte(' ') + ct.Options.Default.formatFast(buf) + } else { + buf.WriteString(" (") + ct.Options.Default.formatFast(buf) + buf.WriteByte(')') + } } if ct.Options.OnUpdate != nil { buf.WriteByte(' ') diff --git a/go/vt/sqlparser/ast_funcs.go b/go/vt/sqlparser/ast_funcs.go index f59d4804a3a..5ad474ba693 100644 --- a/go/vt/sqlparser/ast_funcs.go +++ b/go/vt/sqlparser/ast_funcs.go @@ -1301,6 +1301,17 @@ func (node *ColName) CompliantName(suffix string) string { return node.Name.CompliantName() + suffix } +// isExprAliasForCurrentTimeStamp returns true if the Expr provided is an alias for CURRENT_TIMESTAMP +func isExprAliasForCurrentTimeStamp(expr Expr) bool { + switch node := expr.(type) { + case *FuncExpr: + return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime") + case *CurTimeFuncExpr: + return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime") + } + return false +} + // AtCount represents the '@' count in ColIdent type AtCount int diff --git a/go/vt/sqlparser/parse_test.go b/go/vt/sqlparser/parse_test.go index 96a23114341..053281e05ab 100644 --- a/go/vt/sqlparser/parse_test.go +++ b/go/vt/sqlparser/parse_test.go @@ -1114,6 +1114,15 @@ var ( }, { input: "create table `a`", output: "create table a", + }, { + input: "create table function_default (x varchar(25) default (trim(' check ')))", + output: "create table function_default (\n\tx varchar(25) default (trim(' check '))\n)", + }, { + input: "create table function_default (x varchar(25) default (((trim(' check ')))))", + output: "create table function_default (\n\tx varchar(25) default (trim(' check '))\n)", + }, { + input: "create table function_default3 (x bool DEFAULT (true AND false));", + output: "create table function_default3 (\n\tx bool default (true and false)\n)", }, { input: "create table a (\n\t`a` int\n)", output: "create table a (\n\ta int\n)", @@ -2830,11 +2839,11 @@ func TestCreateTable(t *testing.T) { " time5 timestamp(4) default utc_timestamp(4) on update utc_timestamp(4)\n" + ")", output: "create table t (\n" + - " time1 timestamp default utc_timestamp(),\n" + - " time2 timestamp default utc_timestamp(),\n" + - " time3 timestamp default utc_timestamp() on update utc_timestamp(),\n" + - " time4 timestamp default utc_timestamp() on update utc_timestamp(),\n" + - " time5 timestamp(4) default utc_timestamp(4) on update utc_timestamp(4)\n" + + " time1 timestamp default (utc_timestamp()),\n" + + " time2 timestamp default (utc_timestamp()),\n" + + " time3 timestamp default (utc_timestamp()) on update utc_timestamp(),\n" + + " time4 timestamp default (utc_timestamp()) on update utc_timestamp(),\n" + + " time5 timestamp(4) default (utc_timestamp(4)) on update utc_timestamp(4)\n" + ")", }, { // test utc_time with and without () @@ -2846,11 +2855,11 @@ func TestCreateTable(t *testing.T) { " time5 timestamp(5) default utc_time(5) on update utc_time(5)\n" + ")", output: "create table t (\n" + - " time1 timestamp default utc_time(),\n" + - " time2 timestamp default utc_time(),\n" + - " time3 timestamp default utc_time() on update utc_time(),\n" + - " time4 timestamp default utc_time() on update utc_time(),\n" + - " time5 timestamp(5) default utc_time(5) on update utc_time(5)\n" + + " time1 timestamp default (utc_time()),\n" + + " time2 timestamp default (utc_time()),\n" + + " time3 timestamp default (utc_time()) on update utc_time(),\n" + + " time4 timestamp default (utc_time()) on update utc_time(),\n" + + " time5 timestamp(5) default (utc_time(5)) on update utc_time(5)\n" + ")", }, { // test utc_date with and without () @@ -2861,10 +2870,10 @@ func TestCreateTable(t *testing.T) { " time4 timestamp default utc_date() on update utc_date()\n" + ")", output: "create table t (\n" + - " time1 timestamp default utc_date(),\n" + - " time2 timestamp default utc_date(),\n" + - " time3 timestamp default utc_date() on update utc_date(),\n" + - " time4 timestamp default utc_date() on update utc_date()\n" + + " time1 timestamp default (utc_date()),\n" + + " time2 timestamp default (utc_date()),\n" + + " time3 timestamp default (utc_date()) on update utc_date(),\n" + + " time4 timestamp default (utc_date()) on update utc_date()\n" + ")", }, { // test localtime with and without () @@ -2907,10 +2916,10 @@ func TestCreateTable(t *testing.T) { " time4 timestamp default current_date() on update current_date()\n" + ")", output: "create table t (\n" + - " time1 timestamp default current_date(),\n" + - " time2 timestamp default current_date(),\n" + - " time3 timestamp default current_date() on update current_date(),\n" + - " time4 timestamp default current_date() on update current_date()\n" + + " time1 timestamp default (current_date()),\n" + + " time2 timestamp default (current_date()),\n" + + " time3 timestamp default (current_date()) on update current_date(),\n" + + " time4 timestamp default (current_date()) on update current_date()\n" + ")", }, { // test current_time with and without () @@ -2922,11 +2931,11 @@ func TestCreateTable(t *testing.T) { " time5 timestamp(2) default current_time(2) on update current_time(2)\n" + ")", output: "create table t (\n" + - " time1 timestamp default current_time(),\n" + - " time2 timestamp default current_time(),\n" + - " time3 timestamp default current_time() on update current_time(),\n" + - " time4 timestamp default current_time() on update current_time(),\n" + - " time5 timestamp(2) default current_time(2) on update current_time(2)\n" + + " time1 timestamp default (current_time()),\n" + + " time2 timestamp default (current_time()),\n" + + " time3 timestamp default (current_time()) on update current_time(),\n" + + " time4 timestamp default (current_time()) on update current_time(),\n" + + " time5 timestamp(2) default (current_time(2)) on update current_time(2)\n" + ")", }, } diff --git a/go/vt/vtgate/planbuilder/testdata/ddl_cases.txt b/go/vt/vtgate/planbuilder/testdata/ddl_cases.txt index 091f0bc8dd2..2b0e600bee6 100644 --- a/go/vt/vtgate/planbuilder/testdata/ddl_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/ddl_cases.txt @@ -385,3 +385,18 @@ "TempTable": true } } + +# create table with function as a default value +"create table function_default (x varchar(25) DEFAULT (TRIM(' check ')))" +{ + "QueryType": "DDL", + "Original": "create table function_default (x varchar(25) DEFAULT (TRIM(' check ')))", + "Instructions": { + "OperatorType": "DDL", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "Query": "create table function_default (\n\tx varchar(25) default (TRIM(' check '))\n)" + } +} diff --git a/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt b/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt index b0d571b0156..e7ec2cd817d 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt +++ b/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt @@ -939,3 +939,17 @@ options:PassthroughDMLs "TableName": "", "FullQuery": "call getAllTheThings()" } + +# create table with function as a default value +"create table function_default (x varchar(25) DEFAULT (TRIM(' check ')))" +{ + "PlanID": "DDL", + "TableName": "", + "Permissions": [ + { + "TableName": "function_default", + "Role": 2 + } + ], + "FullQuery": "create table function_default (\n\tx varchar(25) default (TRIM(' check '))\n)" +} diff --git a/test/ci_workflow_gen.go b/test/ci_workflow_gen.go index 02c6f7f8a54..799ae4b58eb 100644 --- a/test/ci_workflow_gen.go +++ b/test/ci_workflow_gen.go @@ -64,6 +64,7 @@ var ( "onlineddl_declarative", "tabletmanager_throttler", "tabletmanager_throttler_custom_config", + "mysql80", } // TODO: currently some percona tools including xtrabackup are installed on all clusters, we can possibly optimize // this by only installing them in the required clusters @@ -72,6 +73,9 @@ var ( "18", "24", } + clustersRequiringUbuntu20 = []string{ + "mysql80", + } ) type unitTest struct { @@ -81,6 +85,7 @@ type unitTest struct { type clusterTest struct { Name, Shard string MakeTools, InstallXtraBackup bool + Ubuntu20 bool } func mergeBlankLines(buf *bytes.Buffer) string { @@ -146,6 +151,13 @@ func generateClusterWorkflows() { break } } + ubuntu20Clusters := canonnizeList(clustersRequiringUbuntu20) + for _, ubuntu20Cluster := range ubuntu20Clusters { + if ubuntu20Cluster == cluster { + test.Ubuntu20 = true + break + } + } path := fmt.Sprintf("%s/cluster_endtoend_%s.yml", workflowConfigDir, cluster) generateWorkflowFile(clusterTestTemplate, path, test) diff --git a/test/config.json b/test/config.json index b33c0dca1ca..6dd53b80770 100644 --- a/test/config.json +++ b/test/config.json @@ -544,6 +544,15 @@ "RetryMax": 0, "Tags": [] }, + "vtgate_mysql80": { + "File": "unused.go", + "Args": ["vitess.io/vitess/go/test/endtoend/vtgate/mysql80"], + "Command": [], + "Manual": false, + "Shard": "mysql80", + "RetryMax": 0, + "Tags": [] + }, "vtgate_sequence": { "File": "unused.go", "Args": ["vitess.io/vitess/go/test/endtoend/vtgate/sequence"], diff --git a/test/templates/cluster_endtoend_test.tpl b/test/templates/cluster_endtoend_test.tpl index 7a6548a71a0..8b5dccaa004 100644 --- a/test/templates/cluster_endtoend_test.tpl +++ b/test/templates/cluster_endtoend_test.tpl @@ -4,7 +4,7 @@ jobs: build: name: Run endtoend tests on {{.Name}} - runs-on: ubuntu-18.04 + {{if .Ubuntu20}}runs-on: ubuntu-20.04{{else}}runs-on: ubuntu-18.04{{end}} steps: - name: Set up Go