diff --git a/go.mod b/go.mod index d877f21d821..bb651454391 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/hashicorp/memberlist v0.1.4 // indirect github.com/hashicorp/serf v0.0.0-20161207011743-d3a67ab21bc8 // indirect github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 - github.com/jinzhu/gorm v1.9.12 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect github.com/klauspost/crc32 v1.2.0 // indirect github.com/klauspost/pgzip v1.2.0 diff --git a/go/test/endtoend/cluster/cluster_process.go b/go/test/endtoend/cluster/cluster_process.go index 99fe4e835bf..b73a37a18c3 100644 --- a/go/test/endtoend/cluster/cluster_process.go +++ b/go/test/endtoend/cluster/cluster_process.go @@ -221,12 +221,12 @@ func (cluster *LocalProcessCluster) StartKeyspace(keyspace Keyspace, shardNames // Start Mysqlctl process log.Info(fmt.Sprintf("Starting mysqlctl for table uid %d, mysql port %d", tablet.TabletUID, tablet.MySQLPort)) tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory) - if proc, err := tablet.MysqlctlProcess.StartProcess(); err != nil { + proc, err := tablet.MysqlctlProcess.StartProcess() + if err != nil { log.Error(err.Error()) return err - } else { - mysqlctlProcessList = append(mysqlctlProcessList, proc) } + mysqlctlProcessList = append(mysqlctlProcessList, proc) // start vttablet process tablet.VttabletProcess = VttabletProcessInstance(tablet.HTTPPort, @@ -573,7 +573,7 @@ func (cluster *LocalProcessCluster) GetVttabletInstance(tabletType string, UID i } } -// GetVttabletInstance creates a new vttablet object +// GetVtprocessInstanceFromVttablet creates a new vttablet object func (cluster *LocalProcessCluster) GetVtprocessInstanceFromVttablet(tablet *Vttablet, shardName string, ksName string) *VttabletProcess { return VttabletProcessInstance(tablet.HTTPPort, tablet.GrpcPort, diff --git a/go/test/endtoend/cluster/cluster_util.go b/go/test/endtoend/cluster/cluster_util.go index 01161eb2572..40c1f97ac42 100644 --- a/go/test/endtoend/cluster/cluster_util.go +++ b/go/test/endtoend/cluster/cluster_util.go @@ -45,22 +45,21 @@ func GetMasterPosition(t *testing.T, vttablet Vttablet, hostname string) (string return pos, gtID } -// Verify total number of rows in a tablet +// VerifyRowsInTablet Verify total number of rows in a tablet func VerifyRowsInTablet(t *testing.T, vttablet *Vttablet, ksName string, expectedRows int) { timeout := time.Now().Add(10 * time.Second) for time.Now().Before(timeout) { qr, err := vttablet.VttabletProcess.QueryTablet("select * from vt_insert_test", ksName, true) assert.Nil(t, err) - if len(qr.Rows) != expectedRows { - time.Sleep(300 * time.Millisecond) - } else { + if len(qr.Rows) == expectedRows { return } + time.Sleep(300 * time.Millisecond) } assert.Fail(t, "expected rows not found.") } -// Verify Local Metadata of a tablet +// VerifyLocalMetadata Verify Local Metadata of a tablet func VerifyLocalMetadata(t *testing.T, tablet *Vttablet, ksName string, shardName string, cell string) { qr, err := tablet.VttabletProcess.QueryTablet("select * from _vt.local_metadata", ksName, false) assert.Nil(t, err) @@ -74,7 +73,7 @@ func VerifyLocalMetadata(t *testing.T, tablet *Vttablet, ksName string, shardNam } } -//Lists back preset in shard +// ListBackups Lists back preset in shard func (cluster LocalProcessCluster) ListBackups(shardKsName string) ([]string, error) { output, err := cluster.VtctlclientProcess.ExecuteCommandWithOutput("ListBackups", shardKsName) if err != nil { diff --git a/go/test/endtoend/cluster/mysqlctl_process.go b/go/test/endtoend/cluster/mysqlctl_process.go index 7a90992aa00..d7bf2f3e020 100644 --- a/go/test/endtoend/cluster/mysqlctl_process.go +++ b/go/test/endtoend/cluster/mysqlctl_process.go @@ -56,11 +56,11 @@ func (mysqlctl *MysqlctlProcess) InitDb() (err error) { // Start executes mysqlctl command to start mysql instance func (mysqlctl *MysqlctlProcess) Start() (err error) { - if tmpProcess, err := mysqlctl.StartProcess(); err != nil { + tmpProcess, err := mysqlctl.StartProcess() + if err != nil { return err - } else { - return tmpProcess.Wait() } + return tmpProcess.Wait() } // StartProcess starts the mysqlctl and returns the process reference @@ -78,19 +78,20 @@ func (mysqlctl *MysqlctlProcess) StartProcess() (*exec.Cmd, error) { if mysqlctl.InitMysql { tmpProcess.Args = append(tmpProcess.Args, "init", "-init_db_sql_file", mysqlctl.InitDBFile) - } else { - tmpProcess.Args = append(tmpProcess.Args, "start") } + tmpProcess.Args = append(tmpProcess.Args, "start") + return tmpProcess, tmpProcess.Start() } // Stop executes mysqlctl command to stop mysql instance func (mysqlctl *MysqlctlProcess) Stop() (err error) { - if tmpProcess, err := mysqlctl.StopProcess(); err != nil { + tmpProcess, err := mysqlctl.StopProcess() + if err != nil { return err - } else { - return tmpProcess.Wait() } + return tmpProcess.Wait() + } // StopProcess executes mysqlctl command to stop mysql instance and returns process reference diff --git a/go/test/endtoend/mysqlserver/main_test.go b/go/test/endtoend/mysqlserver/main_test.go index a97e51f7698..61929101ebc 100644 --- a/go/test/endtoend/mysqlserver/main_test.go +++ b/go/test/endtoend/mysqlserver/main_test.go @@ -24,8 +24,6 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" - - _ "github.com/jinzhu/gorm/dialects/mysql" ) var ( diff --git a/go/test/endtoend/preparestmt/main_test.go b/go/test/endtoend/preparestmt/main_test.go new file mode 100644 index 00000000000..c8712407f57 --- /dev/null +++ b/go/test/endtoend/preparestmt/main_test.go @@ -0,0 +1,270 @@ +/* +Copyright 2019 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 preparestmt + +import ( + "database/sql" + "flag" + "fmt" + "os" + "strings" + "testing" + + "vitess.io/vitess/go/test/endtoend/cluster" + + "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" +) + +// tableData is a temporary structure to hold selected data. +type tableData struct { + Msg string + Data string + TextCol string +} + +// DBInfo information about the database. +type DBInfo struct { + Username string + Password string + Host string + Port uint + KeyspaceName string + Params []string +} + +func init() { + dbInfo.KeyspaceName = keyspaceName + dbInfo.Username = "testuser1" + dbInfo.Password = "testpassword1" + dbInfo.Params = []string{ + "charset=utf8", + "parseTime=True", + "loc=Local", + } +} + +var ( + clusterInstance *cluster.LocalProcessCluster + dbInfo DBInfo + hostname = "localhost" + keyspaceName = "test_keyspace" + testingID = 1 + tableName = "vt_prepare_stmt_test" + cell = "zone1" + mysqlAuthServerStatic = "mysql_auth_server_static.json" + jsonExample = `{ + "quiz": { + "sport": { + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriors", + "Huston Rocket" + ], + "answer": "Huston Rocket" + } + }, + "maths": { + "q1": { + "question": "5 + 7 = ?", + "options": [ + "10", + "11", + "12", + "13" + ], + "answer": "12" + }, + "q2": { + "question": "12 - 8 = ?", + "options": [ + "1", + "2", + "3", + "4" + ], + "answer": "4" + } + } + } + }` + sqlSchema = `create table ` + tableName + ` ( + id bigint auto_increment, + msg varchar(64), + keyspace_id bigint(20) unsigned NOT NULL, + tinyint_unsigned TINYINT, + bool_signed BOOL, + smallint_unsigned SMALLINT, + mediumint_unsigned MEDIUMINT, + int_unsigned INT, + float_unsigned FLOAT(10,2), + double_unsigned DOUBLE(16,2), + decimal_unsigned DECIMAL, + t_date DATE, + t_datetime DATETIME, + t_time TIME, + t_timestamp TIMESTAMP, + c8 bit(8) DEFAULT NULL, + c16 bit(16) DEFAULT NULL, + c24 bit(24) DEFAULT NULL, + c32 bit(32) DEFAULT NULL, + c40 bit(40) DEFAULT NULL, + c48 bit(48) DEFAULT NULL, + c56 bit(56) DEFAULT NULL, + c63 bit(63) DEFAULT NULL, + c64 bit(64) DEFAULT NULL, + json_col JSON, + text_col TEXT, + data longblob, + primary key (id) + ) Engine=InnoDB` +) + +func TestMain(m *testing.M) { + flag.Parse() + + exitcode, err := func() (int, error) { + clusterInstance = cluster.NewCluster(cell, hostname) + + defer clusterInstance.Teardown() + + // Start topo server + if err := clusterInstance.StartTopo(); err != nil { + return 1, err + } + + // create auth server config + SQLConfig := `{ + "testuser1": { + "Password": "testpassword1", + "UserData": "vtgate client 1" + } + }` + if err := createConfig(mysqlAuthServerStatic, SQLConfig); err != nil { + return 1, err + } + + // add extra arguments + clusterInstance.VtGateExtraArgs = []string{ + "-mysql_auth_server_impl", "static", + "-mysql_server_query_timeout", "1s", + "-mysql_auth_server_static_file", clusterInstance.TmpDirectory + "/" + mysqlAuthServerStatic, + "-mysql_server_version", "8.0.16-7", + } + + // Start keyspace + keyspace := &cluster.Keyspace{ + Name: keyspaceName, + SchemaSQL: sqlSchema, + } + if err := clusterInstance.StartUnshardedKeyspace(*keyspace, 1, false); err != nil { + return 1, err + } + + // Start vtgate + if err := clusterInstance.StartVtgate(); err != nil { + return 1, err + } + + dbInfo.Host = clusterInstance.Hostname + dbInfo.Port = uint(clusterInstance.VtgateMySQLPort) + + return m.Run(), nil + }() + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } else { + os.Exit(exitcode) + } + +} + +// ConnectionString generates the connection string using dbinfo. +func (db DBInfo) ConnectionString(params ...string) string { + return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", db.Username, db.Password, db.Host, + db.Port, db.KeyspaceName, strings.Join(append(db.Params, params...), "&")) +} + +// createConfig creates a config file in TmpDir in vtdataroot and writes the given data. +func createConfig(name, data string) error { + // creating new file + f, err := os.Create(clusterInstance.TmpDirectory + "/" + name) + if err != nil { + return err + } + + if data == "" { + return nil + } + + // write the given data + _, err = fmt.Fprint(f, data) + return err +} + +// Connect will connect the vtgate through mysql protocol. +func Connect(t *testing.T, params ...string) *sql.DB { + dbo, err := sql.Open("mysql", dbInfo.ConnectionString(params...)) + require.Nil(t, err) + return dbo +} + +// execWithError executes the prepared query, and validates the error_code. +func execWithError(t *testing.T, dbo *sql.DB, errorCodes []uint16, stmt string, params ...interface{}) { + _, err := dbo.Exec(stmt, params...) + require.NotNilf(t, err, "error expected, got nil") + require.Contains(t, errorCodes, err.(*mysql.MySQLError).Number) +} + +// exec executes the query using the params. +func exec(t *testing.T, dbo *sql.DB, stmt string, params ...interface{}) { + require.Nil(t, execErr(dbo, stmt, params...)) +} + +// execErr executes the query and returns an error if one occurs. +func execErr(dbo *sql.DB, stmt string, params ...interface{}) *mysql.MySQLError { + if _, err := dbo.Exec(stmt, params...); err != nil { + return err.(*mysql.MySQLError) + } + return nil +} + +// selectWhere select the row corresponding to the where condition. +func selectWhere(t *testing.T, dbo *sql.DB, where string, params ...interface{}) []tableData { + var out []tableData + // prepare query + qry := "SELECT msg, data, text_col FROM " + tableName + if where != "" { + qry += " WHERE (" + where + ")" + } + + // execute query + r, err := dbo.Query(qry, params...) + require.Nil(t, err) + + // prepare result + for r.Next() { + var t tableData + r.Scan(&t.Msg, &t.Data, &t.TextCol) + out = append(out, t) + } + return out +} diff --git a/go/test/endtoend/preparestmt/stmt_methods_test.go b/go/test/endtoend/preparestmt/stmt_methods_test.go new file mode 100644 index 00000000000..b1aae1f71f1 --- /dev/null +++ b/go/test/endtoend/preparestmt/stmt_methods_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2019 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 preparestmt + +import ( + "database/sql" + "fmt" + "testing" + "time" + + "github.com/icrowley/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelect simple select the data without any condition. +func TestSelect(t *testing.T) { + dbo := Connect(t) + defer dbo.Close() + selectWhere(t, dbo, "") +} + +// TestInsertUpdateDelete validates all insert, update and +// delete method on prepared statements. +func TestInsertUpdateDelete(t *testing.T) { + + dbo := Connect(t) + defer dbo.Close() + // prepare insert statement + insertStmt := `insert into ` + tableName + ` values( ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);` + + textValue := fake.FullName() + largeComment := fake.Paragraph() + + // inserting multiple rows into test table + for i := 1; i <= 100; i++ { + // preparing value for the insert testing + insertValue := []interface{}{ + i, fmt.Sprint(i) + "21", i * 100, + 127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5, + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + time.Now(), + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + 1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, textValue, largeComment, + } + exec(t, dbo, insertStmt, insertValue...) + + } + // validate inserted data count + testcount(t, dbo, 100) + + // select data with id 1 and validate the data accordingly + // validate row count + data := selectWhere(t, dbo, "id = ?", testingID) + assert.Equal(t, 1, len(data)) + + // validate value of msg column in data + assert.Equal(t, fmt.Sprintf("%d21", testingID), data[0].Msg) + + // testing record update + updateRecord(t, dbo) + + // testing record deletion + deleteRecord(t, dbo) + + // testing recontion and deleted data validation + reconnectAndTest(t) +} + +// testcount validates inserted rows count with expected count. +func testcount(t *testing.T, dbo *sql.DB, except int) { + r, err := dbo.Query("SELECT count(1) FROM " + tableName) + require.Nil(t, err) + + r.Next() + var i int + err = r.Scan(&i) + assert.Nil(t, err) + assert.Equal(t, except, i) +} + +// TestAutoIncColumns test insertion of row without passing +// the value of auto increment columns (here it is id). +func TestAutoIncColumns(t *testing.T) { + dbo := Connect(t) + defer dbo.Close() + // insert a row without id + insertStmt := "INSERT INTO " + tableName + ` ( + msg,keyspace_id,tinyint_unsigned,bool_signed,smallint_unsigned, + mediumint_unsigned,int_unsigned,float_unsigned,double_unsigned, + decimal_unsigned,t_date,t_datetime,t_time,t_timestamp,c8,c16,c24, + c32,c40,c48,c56,c63,c64,json_col,text_col,data) VALUES (?, ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);` + insertValue := []interface{}{ + "21", 0, + 127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5, + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + time.Now(), + time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC), + 1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, fake.DomainName(), fake.Paragraph(), + } + + exec(t, dbo, insertStmt, insertValue...) +} + +// deleteRecord test deletion operation corresponds to the testingID. +func deleteRecord(t *testing.T, dbo *sql.DB) { + // delete the record with id 1 + exec(t, dbo, "DELETE FROM "+tableName+" WHERE id = ?;", testingID) + + data := selectWhere(t, dbo, "id = ?", testingID) + assert.Equal(t, 0, len(data)) + +} + +// updateRecord test update operation corresponds to the testingID. +func updateRecord(t *testing.T, dbo *sql.DB) { + // update the record with id 1 + updateData := "new data value" + updateTextCol := "new text col value" + updateQuery := "update " + tableName + " set data = ? , text_col = ? where id = ?;" + + exec(t, dbo, updateQuery, updateData, updateTextCol, testingID) + + // validate the updated value + // validate row count + data := selectWhere(t, dbo, "id = ?", testingID) + assert.Equal(t, 1, len(data)) + + // validate value of msg column in data + assert.Equal(t, updateData, data[0].Data) + assert.Equal(t, updateTextCol, data[0].TextCol) + +} + +// reconnectAndTest creates new connection with database and validate. +func reconnectAndTest(t *testing.T) { + // reconnect and try to select the record with id 1 + dbo := Connect(t) + defer dbo.Close() + data := selectWhere(t, dbo, "id = ?", testingID) + assert.Equal(t, 0, len(data)) + +} + +// TestWrongTableName query database using invalid +// tablename and validate error. +func TestWrongTableName(t *testing.T) { + dbo := Connect(t) + defer dbo.Close() + execWithError(t, dbo, []uint16{1105}, "select * from teseting_table;") +} diff --git a/test/config.json b/test/config.json index 44e9dd0db81..e6904a4004e 100644 --- a/test/config.json +++ b/test/config.json @@ -123,15 +123,6 @@ "RetryMax": 0, "Tags": [] }, - "prepared_statement": { - "File": "prepared_statement_test.py", - "Args": [], - "Command": [], - "Manual": false, - "Shard": 5, - "RetryMax": 0, - "Tags": [] - }, "python_client": { "File": "python_client_test.py", "Args": [], @@ -270,6 +261,15 @@ "RetryMax": 0, "Tags": [] }, + "prepare_statement": { + "File": "stmt_methods_test.go", + "Args": ["vitess.io/vitess/go/test/endtoend/preparestmt"], + "Command": [], + "Manual": false, + "Shard": 12, + "RetryMax": 0, + "Tags": [] + }, "mysql_server": { "File": "mysql_server_test.go", "Args": ["vitess.io/vitess/go/test/endtoend/mysqlserver"],