vitess.io/vitess@v0.16.2/go/mysql/endtoend/query_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package endtoend 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "strings" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 29 "vitess.io/vitess/go/mysql" 30 "vitess.io/vitess/go/mysql/collations" 31 "vitess.io/vitess/go/sqltypes" 32 33 querypb "vitess.io/vitess/go/vt/proto/query" 34 ) 35 36 const ( 37 charsetName = "utf8mb4" 38 ) 39 40 func columnSize(cs collations.ID, size uint32) uint32 { 41 // utf8_general_ci results in smaller max column sizes because MySQL 5.7 is silly 42 if collations.Local().LookupByID(cs).Charset().Name() == "utf8mb3" { 43 return size * 3 / 4 44 } 45 return size 46 } 47 48 // Test the SQL query part of the API. 49 func TestQueries(t *testing.T) { 50 ctx := context.Background() 51 conn, err := mysql.Connect(ctx, &connParams) 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 // Try a simple error case. 57 _, err = conn.ExecuteFetch("select * from aa", 1000, true) 58 if err == nil || !strings.Contains(err.Error(), "Table 'vttest.aa' doesn't exist") { 59 t.Fatalf("expected error but got: %v", err) 60 } 61 62 // Try a simple DDL. 63 result, err := conn.ExecuteFetch("create table a(id int, name varchar(128), primary key(id))", 0, false) 64 require.NoError(t, err, "create table failed: %v", err) 65 assert.Equal(t, uint64(0), result.RowsAffected, "create table returned RowsAffected %v, was expecting 0", result.RowsAffected) 66 67 // Try a simple insert. 68 result, err = conn.ExecuteFetch("insert into a(id, name) values(10, 'nice name')", 1000, true) 69 require.NoError(t, err, "insert failed: %v", err) 70 71 if result.RowsAffected != 1 || len(result.Rows) != 0 { 72 t.Errorf("unexpected result for insert: %v", result) 73 } 74 75 // And re-read what we inserted. 76 result, err = conn.ExecuteFetch("select * from a", 1000, true) 77 require.NoError(t, err, "insert failed: %v", err) 78 79 collID := getDefaultCollationID() 80 expectedResult := &sqltypes.Result{ 81 Fields: []*querypb.Field{ 82 { 83 Name: "id", 84 Type: querypb.Type_INT32, 85 Table: "a", 86 OrgTable: "a", 87 Database: "vttest", 88 OrgName: "id", 89 ColumnLength: 11, 90 Charset: collations.CollationBinaryID, 91 Flags: uint32(querypb.MySqlFlag_NOT_NULL_FLAG | 92 querypb.MySqlFlag_PRI_KEY_FLAG | 93 querypb.MySqlFlag_PART_KEY_FLAG | 94 querypb.MySqlFlag_NUM_FLAG), 95 }, 96 { 97 Name: "name", 98 Type: querypb.Type_VARCHAR, 99 Table: "a", 100 OrgTable: "a", 101 Database: "vttest", 102 OrgName: "name", 103 ColumnLength: columnSize(collID, 512), 104 Charset: uint32(collID), 105 }, 106 }, 107 Rows: [][]sqltypes.Value{ 108 { 109 sqltypes.MakeTrusted(querypb.Type_INT32, []byte("10")), 110 sqltypes.MakeTrusted(querypb.Type_VARCHAR, []byte("nice name")), 111 }, 112 }, 113 } 114 if !result.Equal(expectedResult) { 115 // MySQL 5.7 is adding the NO_DEFAULT_VALUE_FLAG to Flags. 116 expectedResult.Fields[0].Flags |= uint32(querypb.MySqlFlag_NO_DEFAULT_VALUE_FLAG) 117 assert.True(t, result.Equal(expectedResult), "unexpected result for select, got:\n%v\nexpected:\n%v\n", result, expectedResult) 118 119 } 120 121 // Insert a few rows. 122 for i := 0; i < 100; i++ { 123 result, err := conn.ExecuteFetch(fmt.Sprintf("insert into a(id, name) values(%v, 'nice name %v')", 1000+i, i), 1000, true) 124 require.NoError(t, err, "ExecuteFetch(%v) failed: %v", i, err) 125 assert.Equal(t, uint64(1), result.RowsAffected, "insert into returned RowsAffected %v, was expecting 1", result.RowsAffected) 126 127 } 128 129 // And use a streaming query to read them back. 130 // Do it twice to make sure state is reset properly. 131 readRowsUsingStream(t, conn, 101) 132 readRowsUsingStream(t, conn, 101) 133 134 // And drop the table. 135 result, err = conn.ExecuteFetch("drop table a", 0, false) 136 require.NoError(t, err, "drop table failed: %v", err) 137 assert.Equal(t, uint64(0), result.RowsAffected, "insert into returned RowsAffected %v, was expecting 0", result.RowsAffected) 138 139 } 140 141 func TestLargeQueries(t *testing.T) { 142 ctx := context.Background() 143 conn, err := mysql.Connect(ctx, &connParams) 144 if err != nil { 145 t.Fatal(err) 146 } 147 148 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 149 randString := func(n int) string { 150 b := make([]byte, n) 151 for i := range b { 152 b[i] = letterBytes[rand.Intn(len(letterBytes))] 153 } 154 return string(b) 155 } 156 157 for i := 0; i < 2; i++ { 158 for j := -2; j < 2; j++ { 159 expectedString := randString((i+1)*mysql.MaxPacketSize + j) 160 161 result, err := conn.ExecuteFetch(fmt.Sprintf("select \"%s\"", expectedString), -1, true) 162 require.NoError(t, err, "ExecuteFetch failed: %v", err) 163 164 if len(result.Rows) != 1 || len(result.Rows[0]) != 1 || result.Rows[0][0].IsNull() { 165 t.Fatalf("ExecuteFetch on large query returned poorly-formed result. " + 166 "Expected single row single column string.") 167 } 168 require.Equal(t, expectedString, result.Rows[0][0].ToString(), "Result row was incorrect. Suppressing large string") 169 170 } 171 } 172 } 173 174 func readRowsUsingStream(t *testing.T, conn *mysql.Conn, expectedCount int) { 175 // Start the streaming query. 176 if err := conn.ExecuteStreamFetch("select * from a"); err != nil { 177 t.Fatalf("ExecuteStreamFetch failed: %v", err) 178 } 179 180 // Check the fields. 181 collID := getDefaultCollationID() 182 expectedFields := []*querypb.Field{ 183 { 184 Name: "id", 185 Type: querypb.Type_INT32, 186 Table: "a", 187 OrgTable: "a", 188 Database: "vttest", 189 OrgName: "id", 190 ColumnLength: 11, 191 Charset: collations.CollationBinaryID, 192 Flags: uint32(querypb.MySqlFlag_NOT_NULL_FLAG | 193 querypb.MySqlFlag_PRI_KEY_FLAG | 194 querypb.MySqlFlag_PART_KEY_FLAG | 195 querypb.MySqlFlag_NUM_FLAG), 196 }, 197 { 198 Name: "name", 199 Type: querypb.Type_VARCHAR, 200 Table: "a", 201 OrgTable: "a", 202 Database: "vttest", 203 OrgName: "name", 204 ColumnLength: columnSize(collID, 512), 205 Charset: uint32(collID), 206 }, 207 } 208 fields, err := conn.Fields() 209 require.NoError(t, err, "Fields failed: %v", err) 210 211 if !sqltypes.FieldsEqual(fields, expectedFields) { 212 // MySQL 5.7 is adding the NO_DEFAULT_VALUE_FLAG to Flags. 213 expectedFields[0].Flags |= uint32(querypb.MySqlFlag_NO_DEFAULT_VALUE_FLAG) 214 require.True(t, sqltypes.FieldsEqual(fields, expectedFields), "fields are not right, got:\n%v\nexpected:\n%v", fields, expectedFields) 215 216 } 217 218 // Read the rows. 219 count := 0 220 for { 221 row, err := conn.FetchNext(nil) 222 require.NoError(t, err, "FetchNext failed: %v", err) 223 224 if row == nil { 225 // We're done. 226 break 227 } 228 require.Equal(t, 2, len(row), "Unexpected row found: %v", row) 229 230 count++ 231 } 232 assert.Equal(t, expectedCount, count, "Got unexpected count %v for query, was expecting %v", count, expectedCount) 233 234 conn.CloseResult() 235 } 236 237 func doTestWarnings(t *testing.T, disableClientDeprecateEOF bool) { 238 ctx := context.Background() 239 240 connParams.DisableClientDeprecateEOF = disableClientDeprecateEOF 241 242 conn, err := mysql.Connect(ctx, &connParams) 243 expectNoError(t, err) 244 defer conn.Close() 245 246 result, err := conn.ExecuteFetch("create table a(id int, val int not null, primary key(id))", 0, false) 247 require.NoError(t, err, "create table failed: %v", err) 248 assert.Equal(t, uint64(0), result.RowsAffected, "create table returned RowsAffected %v, was expecting 0", result.RowsAffected) 249 250 // Disable strict mode 251 _, err = conn.ExecuteFetch("set session sql_mode=''", 0, false) 252 require.NoError(t, err, "disable strict mode failed: %v", err) 253 254 // Try a simple insert with a null value 255 result, warnings, err := conn.ExecuteFetchWithWarningCount("insert into a(id) values(10)", 1000, true) 256 require.NoError(t, err, "insert failed: %v", err) 257 258 assert.Equal(t, uint64(1), result.RowsAffected, "unexpected rows affected by insert; result: %v", result) 259 assert.Equal(t, 0, len(result.Rows), "unexpected row count in result for insert: %v", result) 260 assert.Equal(t, uint16(1), warnings, "unexpected result for warnings: %v", warnings) 261 262 _, err = conn.ExecuteFetch("drop table a", 0, false) 263 require.NoError(t, err, "create table failed: %v", err) 264 265 } 266 267 func TestWarningsDeprecateEOF(t *testing.T) { 268 doTestWarnings(t, false) 269 } 270 271 func TestWarningsNoDeprecateEOF(t *testing.T) { 272 doTestWarnings(t, true) 273 } 274 275 func TestSysInfo(t *testing.T) { 276 ctx := context.Background() 277 conn, err := mysql.Connect(ctx, &connParams) 278 require.NoError(t, err) 279 defer conn.Close() 280 281 _, err = conn.ExecuteFetch("drop table if exists `a`", 1000, true) 282 require.NoError(t, err) 283 284 _, err = conn.ExecuteFetch(fmt.Sprintf("CREATE TABLE `a` (`one` int NOT NULL,`two` int NOT NULL,PRIMARY KEY (`one`,`two`)) ENGINE=InnoDB DEFAULT CHARSET=%s", 285 charsetName), 1000, true) 286 require.NoError(t, err) 287 defer conn.ExecuteFetch("drop table `a`", 1000, true) 288 289 qr, err := conn.ExecuteFetch(`SELECT 290 column_name column_name, 291 data_type data_type, 292 column_type full_data_type, 293 character_maximum_length character_maximum_length, 294 numeric_precision numeric_precision, 295 numeric_scale numeric_scale, 296 datetime_precision datetime_precision, 297 column_default column_default, 298 is_nullable is_nullable, 299 extra extra, 300 table_name table_name 301 FROM information_schema.columns 302 WHERE table_schema = 'vttest' and table_name = 'a' 303 ORDER BY ordinal_position`, 1000, true) 304 require.NoError(t, err) 305 require.Equal(t, 2, len(qr.Rows)) 306 307 // is_nullable 308 assert.Equal(t, `VARCHAR("NO")`, qr.Rows[0][8].String()) 309 assert.Equal(t, `VARCHAR("NO")`, qr.Rows[1][8].String()) 310 311 // table_name 312 // This can be either a VARCHAR or a VARBINARY. On Linux and MySQL 8, the 313 // string is tagged with a binary encoding, so it is VARBINARY. 314 // On case-insensitive filesystems, it's a VARCHAR. 315 assert.Contains(t, []string{`VARBINARY("a")`, `VARCHAR("a")`}, qr.Rows[0][10].String()) 316 assert.Contains(t, []string{`VARBINARY("a")`, `VARCHAR("a")`}, qr.Rows[1][10].String()) 317 318 assert.EqualValues(t, sqltypes.Uint64, qr.Fields[4].Type) 319 assert.EqualValues(t, querypb.Type_UINT64, qr.Rows[0][4].Type()) 320 } 321 322 func getDefaultCollationID() collations.ID { 323 collationHandler := collations.Local() 324 collation := collationHandler.DefaultCollationForCharset(charsetName) 325 return collation.ID() 326 }