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  }