vitess.io/vitess@v0.16.2/go/vt/vtctl/workflow/vexec/query_planner_test.go (about)

     1  /*
     2  Copyright 2021 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 vexec
    18  
    19  import (
    20  	"errors"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"vitess.io/vitess/go/vt/sqlparser"
    27  	"vitess.io/vitess/go/vt/vtctl/workflow/vexec/testutil"
    28  )
    29  
    30  func TestVReplicationQueryPlanner_PlanQuery(t *testing.T) {
    31  	t.Parallel()
    32  
    33  	tests := []struct {
    34  		name  string
    35  		query string
    36  		err   error
    37  	}{
    38  		{
    39  			name:  "basic select",
    40  			query: "SELECT id FROM _vt.vreplication",
    41  			err:   nil,
    42  		},
    43  		{
    44  			name:  "insert not supported",
    45  			query: "INSERT INTO _vt.vreplication (id) VALUES (1)",
    46  			err:   ErrUnsupportedQuery,
    47  		},
    48  		{
    49  			name:  "basic update",
    50  			query: "UPDATE _vt.vreplication SET workflow = 'my workflow'",
    51  			err:   nil,
    52  		},
    53  		{
    54  			name:  "basic delete",
    55  			query: "DELETE FROM _vt.vreplication",
    56  			err:   nil,
    57  		},
    58  		{
    59  			name:  "other query",
    60  			query: "CREATE TABLE foo (id INT(11) PRIMARY KEY NOT NULL) ENGINE=InnoDB",
    61  			err:   ErrUnsupportedQuery,
    62  		},
    63  	}
    64  
    65  	planner := NewVReplicationQueryPlanner(nil, "", "")
    66  
    67  	for _, tt := range tests {
    68  		tt := tt
    69  
    70  		t.Run(tt.name, func(t *testing.T) {
    71  			t.Parallel()
    72  
    73  			stmt := testutil.StatementFromString(t, tt.query)
    74  
    75  			_, err := planner.PlanQuery(stmt)
    76  			if tt.err != nil {
    77  				assert.True(t, errors.Is(err, tt.err), "expected err of type %v, got %v", tt.err, err)
    78  
    79  				return
    80  			}
    81  
    82  			assert.NoError(t, err)
    83  		})
    84  	}
    85  }
    86  
    87  func TestVReplicationQueryPlanner_planSelect(t *testing.T) {
    88  	t.Parallel()
    89  
    90  	tests := []struct {
    91  		name                 string
    92  		query                string
    93  		expectedPlannedQuery string
    94  	}{
    95  		{
    96  			name:                 "simple select",
    97  			query:                "SELECT id FROM _vt.vreplication WHERE id > 10",
    98  			expectedPlannedQuery: "SELECT id FROM _vt.vreplication WHERE id > 10 AND db_name = 'vt_testkeyspace' AND workflow = 'testworkflow'",
    99  		},
   100  		{
   101  			name:                 "select with workflow and dbname columns already in WHERE",
   102  			query:                "SELECT id FROM _vt.vreplication WHERE id > 10 AND db_name = 'vt_testkeyspace' AND workflow = 'testworkflow'",
   103  			expectedPlannedQuery: "SELECT id FROM _vt.vreplication WHERE id > 10 AND db_name = 'vt_testkeyspace' AND workflow = 'testworkflow'",
   104  		},
   105  		{
   106  			// In this case, the QueryParams for the planner (which have
   107  			// workflow = "testworkflow"; db_name = "vt_testkeyspace") are
   108  			// ignored because the WHERE clause was explicit.
   109  			name:                 "select with workflow and dbname columns with different values",
   110  			query:                "SELECT id FROM _vt.vreplication WHERE id > 10 AND db_name = 'different_keyspace' AND workflow = 'otherworkflow'",
   111  			expectedPlannedQuery: "SELECT id FROM _vt.vreplication WHERE id > 10 AND db_name = 'different_keyspace' AND workflow = 'otherworkflow'",
   112  		},
   113  	}
   114  
   115  	planner := NewVReplicationQueryPlanner(nil, "testworkflow", "vt_testkeyspace")
   116  
   117  	for _, tt := range tests {
   118  		tt := tt
   119  
   120  		t.Run(tt.name, func(t *testing.T) {
   121  			t.Parallel()
   122  
   123  			stmt := testutil.StatementFromString(t, tt.query)
   124  			qp, err := planner.PlanQuery(stmt)
   125  
   126  			assert.NoError(t, err)
   127  			fixedqp, ok := qp.(*FixedQueryPlan)
   128  			require.True(t, ok, "VReplicationQueryPlanner should always return a FixedQueryPlan from PlanQuery, got %T", qp)
   129  			assert.Equal(t, testutil.ParsedQueryFromString(t, tt.expectedPlannedQuery), fixedqp.ParsedQuery)
   130  		})
   131  	}
   132  }
   133  
   134  func TestVReplicationQueryPlanner_planUpdate(t *testing.T) {
   135  	t.Parallel()
   136  
   137  	tests := []struct {
   138  		name                 string
   139  		planner              *VReplicationQueryPlanner
   140  		query                string
   141  		expectedPlannedQuery string
   142  		expectedErr          error
   143  	}{
   144  		{
   145  			name:                 "simple update",
   146  			planner:              NewVReplicationQueryPlanner(nil, "testworkflow", "vt_testkeyspace"),
   147  			query:                "UPDATE _vt.vreplication SET state = 'Running'",
   148  			expectedPlannedQuery: "UPDATE _vt.vreplication SET state = 'Running' WHERE db_name = 'vt_testkeyspace' AND workflow = 'testworkflow'",
   149  			expectedErr:          nil,
   150  		},
   151  		{
   152  			name:        "including an ORDER BY is an error",
   153  			planner:     NewVReplicationQueryPlanner(nil, "", ""),
   154  			query:       "UPDATE _vt.vreplication SET state = 'Running' ORDER BY id DESC",
   155  			expectedErr: ErrUnsupportedQueryConstruct,
   156  		},
   157  		{
   158  			name:        "including a LIMIT is an error",
   159  			planner:     NewVReplicationQueryPlanner(nil, "", ""),
   160  			query:       "UPDATE _vt.vreplication SET state = 'Running' LIMIT 5",
   161  			expectedErr: ErrUnsupportedQueryConstruct,
   162  		},
   163  		{
   164  			name:        "cannot update id column",
   165  			planner:     NewVReplicationQueryPlanner(nil, "", "vt_testkeyspace"),
   166  			query:       "UPDATE _vt.vreplication SET id = 5",
   167  			expectedErr: ErrCannotUpdateImmutableColumn,
   168  		},
   169  	}
   170  
   171  	for _, tt := range tests {
   172  		tt := tt
   173  
   174  		t.Run(tt.name, func(t *testing.T) {
   175  			t.Parallel()
   176  
   177  			stmt := testutil.StatementFromString(t, tt.query)
   178  
   179  			qp, err := tt.planner.PlanQuery(stmt)
   180  			if tt.expectedErr != nil {
   181  				assert.True(t, errors.Is(err, tt.expectedErr), "expected err of type %q, got %q", tt.expectedErr, err)
   182  
   183  				return
   184  			}
   185  
   186  			fixedqp, ok := qp.(*FixedQueryPlan)
   187  			require.True(t, ok, "VReplicationQueryPlanner should always return a FixedQueryPlan from PlanQuery, got %T", qp)
   188  			assert.Equal(t, testutil.ParsedQueryFromString(t, tt.expectedPlannedQuery), fixedqp.ParsedQuery)
   189  		})
   190  	}
   191  }
   192  
   193  func TestVReplicationQueryPlanner_planDelete(t *testing.T) {
   194  	t.Parallel()
   195  
   196  	tests := []struct {
   197  		name                 string
   198  		query                string
   199  		expectedPlannedQuery string
   200  		expectedErr          error
   201  	}{
   202  		{
   203  			name:                 "simple delete",
   204  			query:                "DELETE FROM _vt.vreplication WHERE id = 1",
   205  			expectedPlannedQuery: "DELETE FROM _vt.vreplication WHERE id = 1 AND db_name = 'vt_testkeyspace'",
   206  			expectedErr:          nil,
   207  		},
   208  		{
   209  			name:        "DELETE with USING clause is not supported",
   210  			query:       "DELETE FROM _vt.vreplication, _vt.schema_migrations USING _vt.vreplication INNER JOIN _vt.schema_migrations",
   211  			expectedErr: ErrUnsupportedQueryConstruct,
   212  		},
   213  		{
   214  			name:        "DELETE with a PARTITION clause is not supported",
   215  			query:       "DELETE FROM _vt.vreplication PARTITION (p1)",
   216  			expectedErr: ErrUnsupportedQueryConstruct,
   217  		},
   218  		{
   219  			name:        "DELETE with ORDER BY is not supported",
   220  			query:       "DELETE FROM _vt.vreplication ORDER BY id DESC",
   221  			expectedErr: ErrUnsupportedQueryConstruct,
   222  		},
   223  		{
   224  			name:        "DELETE with LIMIT is not supported",
   225  			query:       "DELETE FROM _vt.vreplication LIMIT 5",
   226  			expectedErr: ErrUnsupportedQueryConstruct,
   227  		},
   228  	}
   229  
   230  	planner := NewVReplicationQueryPlanner(nil, "", "vt_testkeyspace")
   231  
   232  	for _, tt := range tests {
   233  		tt := tt
   234  
   235  		t.Run(tt.name, func(t *testing.T) {
   236  			t.Parallel()
   237  
   238  			stmt := testutil.StatementFromString(t, tt.query)
   239  
   240  			qp, err := planner.PlanQuery(stmt)
   241  			if tt.expectedErr != nil {
   242  				assert.True(t, errors.Is(err, tt.expectedErr), "expected err of type %q, got %q", tt.expectedErr, err)
   243  
   244  				return
   245  			}
   246  
   247  			fixedqp, ok := qp.(*FixedQueryPlan)
   248  			require.True(t, ok, "VReplicationQueryPlanner should always return a FixedQueryPlan from PlanQuery, got %T", qp)
   249  			assert.Equal(t, testutil.ParsedQueryFromString(t, tt.expectedPlannedQuery), fixedqp.ParsedQuery)
   250  		})
   251  	}
   252  }
   253  
   254  func TestVReplicationLogQueryPlanner(t *testing.T) {
   255  	t.Parallel()
   256  
   257  	t.Run("planSelect", func(t *testing.T) {
   258  		t.Parallel()
   259  
   260  		tests := []struct {
   261  			name            string
   262  			targetStreamIDs map[string][]int64
   263  			query           string
   264  			assertion       func(t *testing.T, plan QueryPlan)
   265  			shouldErr       bool
   266  		}{
   267  			{
   268  				targetStreamIDs: map[string][]int64{
   269  					"a": {1, 2},
   270  				},
   271  				query: "select * from _vt.vreplication_log",
   272  				assertion: func(t *testing.T, plan QueryPlan) {
   273  					t.Helper()
   274  					qp, ok := plan.(*PerTargetQueryPlan)
   275  					if !ok {
   276  						require.FailNow(t, "failed type check", "expected plan to be PerTargetQueryPlan, got %T: %v", plan, plan)
   277  					}
   278  
   279  					expected := map[string]string{
   280  						"a": "select * from _vt.vreplication_log where vrepl_id in (1, 2)",
   281  					}
   282  					assertQueryMapsMatch(t, expected, qp.ParsedQueries)
   283  				},
   284  			},
   285  			{
   286  				targetStreamIDs: map[string][]int64{
   287  					"a": nil,
   288  				},
   289  				query: "select * from _vt.vreplication_log",
   290  				assertion: func(t *testing.T, plan QueryPlan) {
   291  					t.Helper()
   292  					qp, ok := plan.(*PerTargetQueryPlan)
   293  					if !ok {
   294  						require.FailNow(t, "failed type check", "expected plan to be PerTargetQueryPlan, got %T: %v", plan, plan)
   295  					}
   296  
   297  					expected := map[string]string{
   298  						"a": "select * from _vt.vreplication_log where 1 != 1",
   299  					}
   300  					assertQueryMapsMatch(t, expected, qp.ParsedQueries)
   301  				},
   302  			},
   303  			{
   304  				targetStreamIDs: map[string][]int64{
   305  					"a": {1},
   306  				},
   307  				query: "select * from _vt.vreplication_log",
   308  				assertion: func(t *testing.T, plan QueryPlan) {
   309  					t.Helper()
   310  					qp, ok := plan.(*PerTargetQueryPlan)
   311  					if !ok {
   312  						require.FailNow(t, "failed type check", "expected plan to be PerTargetQueryPlan, got %T: %v", plan, plan)
   313  					}
   314  
   315  					expected := map[string]string{
   316  						"a": "select * from _vt.vreplication_log where vrepl_id = 1",
   317  					}
   318  					assertQueryMapsMatch(t, expected, qp.ParsedQueries)
   319  				},
   320  			},
   321  			{
   322  				query: "select * from _vt.vreplication_log where vrepl_id = 1",
   323  				assertion: func(t *testing.T, plan QueryPlan) {
   324  					t.Helper()
   325  					qp, ok := plan.(*FixedQueryPlan)
   326  					if !ok {
   327  						require.FailNow(t, "failed type check", "expected plan to be FixedQueryPlan, got %T: %v", plan, plan)
   328  					}
   329  
   330  					assert.Equal(t, "select * from _vt.vreplication_log where vrepl_id = 1", qp.ParsedQuery.Query)
   331  				},
   332  			},
   333  			{
   334  				targetStreamIDs: map[string][]int64{
   335  					"a": {1, 2},
   336  				},
   337  				query: "select * from _vt.vreplication_log where foo = 'bar'",
   338  				assertion: func(t *testing.T, plan QueryPlan) {
   339  					t.Helper()
   340  					qp, ok := plan.(*PerTargetQueryPlan)
   341  					if !ok {
   342  						require.FailNow(t, "failed type check", "expected plan to be PerTargetQueryPlan, got %T: %v", plan, plan)
   343  					}
   344  
   345  					expected := map[string]string{
   346  						"a": "select * from _vt.vreplication_log where vrepl_id in (1, 2) and foo = 'bar'",
   347  					}
   348  					assertQueryMapsMatch(t, expected, qp.ParsedQueries)
   349  				},
   350  			},
   351  		}
   352  
   353  		for _, tt := range tests {
   354  			tt := tt
   355  
   356  			t.Run(tt.name, func(t *testing.T) {
   357  				t.Parallel()
   358  
   359  				planner := NewVReplicationLogQueryPlanner(nil, tt.targetStreamIDs)
   360  				stmt, err := sqlparser.Parse(tt.query)
   361  				require.NoError(t, err, "could not parse query %q", tt.query)
   362  				qp, err := planner.planSelect(stmt.(*sqlparser.Select))
   363  				if tt.shouldErr {
   364  					assert.Error(t, err)
   365  					return
   366  				}
   367  
   368  				tt.assertion(t, qp)
   369  			})
   370  		}
   371  	})
   372  }
   373  
   374  func assertQueryMapsMatch(t *testing.T, expected map[string]string, actual map[string]*sqlparser.ParsedQuery, msgAndArgs ...any) {
   375  	t.Helper()
   376  
   377  	actualQueryMap := make(map[string]string, len(actual))
   378  	for k, v := range actual {
   379  		actualQueryMap[k] = v.Query
   380  	}
   381  
   382  	assert.Equal(t, expected, actualQueryMap, msgAndArgs...)
   383  }