vitess.io/vitess@v0.16.2/go/vt/sidecardb/sidecardb_test.go (about)

     1  /*
     2  Copyright 2023 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 sidecardb
    18  
    19  import (
    20  	"context"
    21  	"expvar"
    22  	"fmt"
    23  	"sort"
    24  	"strings"
    25  	"testing"
    26  
    27  	"vitess.io/vitess/go/vt/sqlparser"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"vitess.io/vitess/go/stats"
    32  
    33  	"vitess.io/vitess/go/mysql/fakesqldb"
    34  	"vitess.io/vitess/go/sqltypes"
    35  )
    36  
    37  // TestInitErrors validates that the schema init error stats are being correctly set
    38  func TestInitErrors(t *testing.T) {
    39  	ctx := context.Background()
    40  
    41  	db := fakesqldb.New(t)
    42  	defer db.Close()
    43  	AddSchemaInitQueries(db, false)
    44  	db.AddQuery("use dbname", &sqltypes.Result{})
    45  	sqlMode := sqltypes.MakeTestResult(sqltypes.MakeTestFields(
    46  		"sql_mode",
    47  		"varchar"),
    48  		"ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION",
    49  	)
    50  	db.AddQuery("select @@session.sql_mode as sql_mode", sqlMode)
    51  	db.AddQueryPattern("set @@session.sql_mode=.*", &sqltypes.Result{})
    52  
    53  	ddlErrorCount.Set(0)
    54  	ddlCount.Set(0)
    55  
    56  	cp := db.ConnParams()
    57  	conn, err := cp.Connect(ctx)
    58  	require.NoError(t, err)
    59  
    60  	type schemaError struct {
    61  		tableName  string
    62  		errorValue string
    63  	}
    64  
    65  	// simulate two errors during table creation to validate error stats
    66  	schemaErrors := []schemaError{
    67  		{"vreplication_log", "vreplication_log error"},
    68  		{"copy_state", "copy_state error"},
    69  	}
    70  
    71  	exec := func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) {
    72  		if useDB {
    73  			if _, err := conn.ExecuteFetch(UseSidecarDatabaseQuery, maxRows, true); err != nil {
    74  				return nil, err
    75  			}
    76  		}
    77  
    78  		// simulate errors for the table creation DDLs applied for tables specified in schemaErrors
    79  		stmt, err := sqlparser.Parse(query)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		createTable, ok := stmt.(*sqlparser.CreateTable)
    84  		if ok {
    85  			for _, e := range schemaErrors {
    86  				if strings.EqualFold(e.tableName, createTable.Table.Name.String()) {
    87  					return nil, fmt.Errorf(e.errorValue)
    88  				}
    89  			}
    90  		}
    91  		return conn.ExecuteFetch(query, maxRows, true)
    92  	}
    93  
    94  	require.Equal(t, int64(0), GetDDLCount())
    95  	err = Init(ctx, exec)
    96  	require.NoError(t, err)
    97  	require.Equal(t, int64(len(sidecarTables)-len(schemaErrors)), GetDDLCount())
    98  	require.Equal(t, int64(len(schemaErrors)), GetDDLErrorCount())
    99  
   100  	var want []string
   101  	for _, e := range schemaErrors {
   102  		want = append(want, e.errorValue)
   103  	}
   104  	// sort expected and reported errors for easy comparison
   105  	sort.Strings(want)
   106  	got := GetDDLErrorHistory()
   107  	sort.Slice(got, func(i, j int) bool {
   108  		return got[i].tableName < got[j].tableName
   109  	})
   110  	var gotErrors string
   111  	stats.Register(func(name string, v expvar.Var) {
   112  		if name == StatsKeyErrors {
   113  			gotErrors = v.String()
   114  		}
   115  	})
   116  
   117  	// for DDL errors, validate both the internal data structure and the stats endpoint
   118  	for i := range want {
   119  		if !strings.Contains(got[i].err.Error(), want[i]) {
   120  			require.FailNowf(t, "incorrect schema error", "got %s, want %s", got[i], want[i])
   121  		}
   122  		if !strings.Contains(gotErrors, want[i]) {
   123  			require.FailNowf(t, "schema error not published", "got %s, want %s", gotErrors, want[i])
   124  		}
   125  	}
   126  }
   127  
   128  // test the logic that confirms that the user defined schema's table name and qualifier are valid
   129  func TestValidateSchema(t *testing.T) {
   130  	type testCase struct {
   131  		testName  string
   132  		name      string
   133  		schema    string
   134  		mustError bool
   135  	}
   136  	testCases := []testCase{
   137  		{"valid", "t1", "create table if not exists _vt.t1(i int)", false},
   138  		{"no if not exists", "t1", "create table _vt.t1(i int)", true},
   139  		{"invalid table name", "t2", "create table if not exists _vt.t1(i int)", true},
   140  		{"invalid table name", "t1", "create table if not exists _vt.t2(i int)", true},
   141  		{"invalid qualifier", "t1", "create table if not exists vt_product.t1(i int)", true},
   142  		{"invalid qualifier", "t1", "create table if not exists t1(i int)", true},
   143  	}
   144  	for _, tc := range testCases {
   145  		t.Run(tc.testName, func(t *testing.T) {
   146  			_, err := validateSchemaDefinition(tc.name, tc.schema)
   147  			if tc.mustError {
   148  				require.Error(t, err)
   149  			} else {
   150  				require.NoError(t, err)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  // TestAlterTableAlgorithm confirms that we use ALGORITHM=COPY during alter tables
   157  func TestAlterTableAlgorithm(t *testing.T) {
   158  	type testCase struct {
   159  		testName      string
   160  		tableName     string
   161  		currentSchema string
   162  		desiredSchema string
   163  	}
   164  	testCases := []testCase{
   165  		{"add column", "t1", "create table if not exists _vt.t1(i int)", "create table if not exists _vt.t1(i int, i1 int)"},
   166  		{"modify column", "t1", "create table if not exists _vt.t1(i int)", "create table if not exists _vt.t(i float)"},
   167  	}
   168  	si := &schemaInit{}
   169  	copyAlgo := sqlparser.AlgorithmValue("COPY")
   170  	for _, tc := range testCases {
   171  		t.Run(tc.testName, func(t *testing.T) {
   172  			diff, err := si.findTableSchemaDiff(tc.tableName, tc.currentSchema, tc.desiredSchema)
   173  			require.NoError(t, err)
   174  			stmt, err := sqlparser.Parse(diff)
   175  			require.NoError(t, err)
   176  			alterTable, ok := stmt.(*sqlparser.AlterTable)
   177  			require.True(t, ok)
   178  			require.NotNil(t, alterTable)
   179  			var alterAlgo sqlparser.AlterOption
   180  			for i, opt := range alterTable.AlterOptions {
   181  				if _, ok := opt.(sqlparser.AlgorithmValue); ok {
   182  					alterAlgo = alterTable.AlterOptions[i]
   183  				}
   184  			}
   185  			require.Equal(t, copyAlgo, alterAlgo)
   186  		})
   187  	}
   188  }
   189  
   190  // Tests various non-error code paths in sidecardb
   191  func TestMiscSidecarDB(t *testing.T) {
   192  	ctx := context.Background()
   193  
   194  	db := fakesqldb.New(t)
   195  	defer db.Close()
   196  	AddSchemaInitQueries(db, false)
   197  	db.AddQuery("use dbname", &sqltypes.Result{})
   198  	db.AddQueryPattern("set @@session.sql_mode=.*", &sqltypes.Result{})
   199  
   200  	cp := db.ConnParams()
   201  	conn, err := cp.Connect(ctx)
   202  	require.NoError(t, err)
   203  	exec := func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) {
   204  		if useDB {
   205  			if _, err := conn.ExecuteFetch(UseSidecarDatabaseQuery, maxRows, true); err != nil {
   206  				return nil, err
   207  			}
   208  		}
   209  		return conn.ExecuteFetch(query, maxRows, true)
   210  	}
   211  
   212  	// tests init on empty db
   213  	ddlErrorCount.Set(0)
   214  	ddlCount.Set(0)
   215  	require.Equal(t, int64(0), GetDDLCount())
   216  	err = Init(ctx, exec)
   217  	require.NoError(t, err)
   218  	require.Equal(t, int64(len(sidecarTables)), GetDDLCount())
   219  
   220  	// tests init on already inited db
   221  	AddSchemaInitQueries(db, true)
   222  	err = Init(ctx, exec)
   223  	require.NoError(t, err)
   224  	require.Equal(t, int64(len(sidecarTables)), GetDDLCount())
   225  
   226  	// tests misc paths not covered above
   227  	si := &schemaInit{
   228  		ctx:  ctx,
   229  		exec: exec,
   230  	}
   231  	result := sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   232  		"Database",
   233  		"varchar"),
   234  		"currentDB",
   235  	)
   236  	db.AddQuery(SelectCurrentDatabaseQuery, result)
   237  
   238  	currentDB, err := si.setCurrentDatabase("dbname")
   239  	require.NoError(t, err)
   240  	require.Equal(t, "currentDB", currentDB)
   241  
   242  	require.False(t, MatchesInitQuery("abc"))
   243  	require.True(t, MatchesInitQuery(SelectCurrentDatabaseQuery))
   244  	require.True(t, MatchesInitQuery("CREATE TABLE IF NOT EXISTS `_vt`.vreplication"))
   245  }