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 }