vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/plan_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 planbuilder 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "math/rand" 25 "os" 26 "path/filepath" 27 "runtime/debug" 28 "strings" 29 "testing" 30 31 "github.com/nsf/jsondiff" 32 "github.com/stretchr/testify/require" 33 34 "vitess.io/vitess/go/vt/servenv" 35 36 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 37 38 "vitess.io/vitess/go/test/utils" 39 vschemapb "vitess.io/vitess/go/vt/proto/vschema" 40 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 41 42 "vitess.io/vitess/go/mysql/collations" 43 "vitess.io/vitess/go/vt/vtgate/semantics" 44 45 "vitess.io/vitess/go/vt/vterrors" 46 47 "vitess.io/vitess/go/sqltypes" 48 "vitess.io/vitess/go/vt/key" 49 "vitess.io/vitess/go/vt/sqlparser" 50 "vitess.io/vitess/go/vt/topo/topoproto" 51 "vitess.io/vitess/go/vt/vtgate/engine" 52 "vitess.io/vitess/go/vt/vtgate/vindexes" 53 54 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 55 ) 56 57 // hashIndex is a functional, unique Vindex. 58 type hashIndex struct{ name string } 59 60 func (v *hashIndex) String() string { return v.name } 61 func (*hashIndex) Cost() int { return 1 } 62 func (*hashIndex) IsUnique() bool { return true } 63 func (*hashIndex) NeedsVCursor() bool { return false } 64 func (*hashIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { 65 return []bool{}, nil 66 } 67 func (*hashIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { 68 return nil, nil 69 } 70 71 func newHashIndex(name string, _ map[string]string) (vindexes.Vindex, error) { 72 return &hashIndex{name: name}, nil 73 } 74 75 // lookupIndex is a unique Vindex, and satisfies Lookup. 76 type lookupIndex struct{ name string } 77 78 func (v *lookupIndex) String() string { return v.name } 79 func (*lookupIndex) Cost() int { return 2 } 80 func (*lookupIndex) IsUnique() bool { return true } 81 func (*lookupIndex) NeedsVCursor() bool { return false } 82 func (*lookupIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { 83 return []bool{}, nil 84 } 85 func (*lookupIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { 86 return nil, nil 87 } 88 func (*lookupIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { 89 return nil 90 } 91 func (*lookupIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { 92 return nil 93 } 94 func (*lookupIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { 95 return nil 96 } 97 98 func newLookupIndex(name string, _ map[string]string) (vindexes.Vindex, error) { 99 return &lookupIndex{name: name}, nil 100 } 101 102 var _ vindexes.Lookup = (*lookupIndex)(nil) 103 104 // nameLkpIndex satisfies Lookup, NonUnique. 105 type nameLkpIndex struct{ name string } 106 107 func (v *nameLkpIndex) String() string { return v.name } 108 func (*nameLkpIndex) Cost() int { return 3 } 109 func (*nameLkpIndex) IsUnique() bool { return false } 110 func (*nameLkpIndex) NeedsVCursor() bool { return false } 111 func (*nameLkpIndex) AllowBatch() bool { return true } 112 func (*nameLkpIndex) AutoCommitEnabled() bool { return false } 113 func (*nameLkpIndex) GetCommitOrder() vtgatepb.CommitOrder { return vtgatepb.CommitOrder_NORMAL } 114 func (*nameLkpIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { 115 return []bool{}, nil 116 } 117 func (*nameLkpIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { 118 return nil, nil 119 } 120 func (*nameLkpIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { 121 return nil 122 } 123 func (*nameLkpIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { 124 return nil 125 } 126 func (*nameLkpIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { 127 return nil 128 } 129 func (v *nameLkpIndex) Query() (string, []string) { 130 return "select name, keyspace_id from name_user_vdx where name in ::name", []string{"name"} 131 } 132 func (*nameLkpIndex) MapResult([]sqltypes.Value, []*sqltypes.Result) ([]key.Destination, error) { 133 return nil, nil 134 } 135 136 func newNameLkpIndex(name string, _ map[string]string) (vindexes.Vindex, error) { 137 return &nameLkpIndex{name: name}, nil 138 } 139 140 var _ vindexes.Vindex = (*nameLkpIndex)(nil) 141 var _ vindexes.Lookup = (*nameLkpIndex)(nil) 142 var _ vindexes.LookupPlanable = (*nameLkpIndex)(nil) 143 144 // costlyIndex satisfies Lookup, NonUnique. 145 type costlyIndex struct{ name string } 146 147 func (v *costlyIndex) String() string { return v.name } 148 func (*costlyIndex) Cost() int { return 10 } 149 func (*costlyIndex) IsUnique() bool { return false } 150 func (*costlyIndex) NeedsVCursor() bool { return false } 151 func (*costlyIndex) Verify(context.Context, vindexes.VCursor, []sqltypes.Value, [][]byte) ([]bool, error) { 152 return []bool{}, nil 153 } 154 func (*costlyIndex) Map(ctx context.Context, vcursor vindexes.VCursor, ids []sqltypes.Value) ([]key.Destination, error) { 155 return nil, nil 156 } 157 func (*costlyIndex) Create(context.Context, vindexes.VCursor, [][]sqltypes.Value, [][]byte, bool) error { 158 return nil 159 } 160 func (*costlyIndex) Delete(context.Context, vindexes.VCursor, [][]sqltypes.Value, []byte) error { 161 return nil 162 } 163 func (*costlyIndex) Update(context.Context, vindexes.VCursor, []sqltypes.Value, []byte, []sqltypes.Value) error { 164 return nil 165 } 166 167 func newCostlyIndex(name string, _ map[string]string) (vindexes.Vindex, error) { 168 return &costlyIndex{name: name}, nil 169 } 170 171 var _ vindexes.Vindex = (*costlyIndex)(nil) 172 var _ vindexes.Lookup = (*costlyIndex)(nil) 173 174 // multiColIndex satisfies multi column vindex. 175 type multiColIndex struct { 176 name string 177 } 178 179 func newMultiColIndex(name string, _ map[string]string) (vindexes.Vindex, error) { 180 return &multiColIndex{name: name}, nil 181 } 182 183 var _ vindexes.MultiColumn = (*multiColIndex)(nil) 184 185 func (m *multiColIndex) String() string { return m.name } 186 187 func (m *multiColIndex) Cost() int { return 1 } 188 189 func (m *multiColIndex) IsUnique() bool { return true } 190 191 func (m *multiColIndex) NeedsVCursor() bool { return false } 192 193 func (m *multiColIndex) Map(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { 194 return nil, nil 195 } 196 197 func (m *multiColIndex) Verify(ctx context.Context, vcursor vindexes.VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { 198 return []bool{}, nil 199 } 200 201 func (m *multiColIndex) PartialVindex() bool { 202 return true 203 } 204 205 func init() { 206 vindexes.Register("hash_test", newHashIndex) 207 vindexes.Register("lookup_test", newLookupIndex) 208 vindexes.Register("name_lkp_test", newNameLkpIndex) 209 vindexes.Register("costly", newCostlyIndex) 210 vindexes.Register("multiCol_test", newMultiColIndex) 211 } 212 213 func makeTestOutput(t *testing.T) string { 214 testOutputTempDir := utils.MakeTestOutput(t, "testdata", "plan_test") 215 216 return testOutputTempDir 217 } 218 219 func TestPlan(t *testing.T) { 220 vschemaWrapper := &vschemaWrapper{ 221 v: loadSchema(t, "vschemas/schema.json", true), 222 sysVarEnabled: true, 223 } 224 testOutputTempDir := makeTestOutput(t) 225 226 // You will notice that some tests expect user.Id instead of user.id. 227 // This is because we now pre-create vindex columns in the symbol 228 // table, which come from vschema. In the test vschema, 229 // the column is named as Id. This is to make sure that 230 // column names are case-preserved, but treated as 231 // case-insensitive even if they come from the vschema. 232 testFile(t, "aggr_cases.json", testOutputTempDir, vschemaWrapper, false) 233 testFile(t, "dml_cases.json", testOutputTempDir, vschemaWrapper, false) 234 testFile(t, "from_cases.json", testOutputTempDir, vschemaWrapper, false) 235 testFile(t, "filter_cases.json", testOutputTempDir, vschemaWrapper, false) 236 testFile(t, "postprocess_cases.json", testOutputTempDir, vschemaWrapper, false) 237 testFile(t, "select_cases.json", testOutputTempDir, vschemaWrapper, false) 238 testFile(t, "symtab_cases.json", testOutputTempDir, vschemaWrapper, false) 239 testFile(t, "unsupported_cases.json", testOutputTempDir, vschemaWrapper, false) 240 testFile(t, "vindex_func_cases.json", testOutputTempDir, vschemaWrapper, false) 241 testFile(t, "wireup_cases.json", testOutputTempDir, vschemaWrapper, false) 242 testFile(t, "memory_sort_cases.json", testOutputTempDir, vschemaWrapper, false) 243 testFile(t, "use_cases.json", testOutputTempDir, vschemaWrapper, false) 244 testFile(t, "set_cases.json", testOutputTempDir, vschemaWrapper, false) 245 testFile(t, "union_cases.json", testOutputTempDir, vschemaWrapper, false) 246 testFile(t, "large_union_cases.json", testOutputTempDir, vschemaWrapper, false) 247 testFile(t, "transaction_cases.json", testOutputTempDir, vschemaWrapper, false) 248 testFile(t, "lock_cases.json", testOutputTempDir, vschemaWrapper, false) 249 testFile(t, "large_cases.json", testOutputTempDir, vschemaWrapper, false) 250 testFile(t, "ddl_cases_no_default_keyspace.json", testOutputTempDir, vschemaWrapper, false) 251 testFile(t, "flush_cases_no_default_keyspace.json", testOutputTempDir, vschemaWrapper, false) 252 testFile(t, "show_cases_no_default_keyspace.json", testOutputTempDir, vschemaWrapper, false) 253 testFile(t, "stream_cases.json", testOutputTempDir, vschemaWrapper, false) 254 testFile(t, "info_schema80_cases.json", testOutputTempDir, vschemaWrapper, false) 255 testFile(t, "reference_cases.json", testOutputTempDir, vschemaWrapper, false) 256 testFile(t, "vexplain_cases.json", testOutputTempDir, vschemaWrapper, false) 257 } 258 259 func TestSystemTables57(t *testing.T) { 260 // first we move everything to use 5.7 logic 261 servenv.SetMySQLServerVersionForTest("5.7") 262 defer servenv.SetMySQLServerVersionForTest("") 263 vschemaWrapper := &vschemaWrapper{v: loadSchema(t, "vschemas/schema.json", true)} 264 testOutputTempDir := makeTestOutput(t) 265 testFile(t, "info_schema57_cases.json", testOutputTempDir, vschemaWrapper, false) 266 } 267 268 func TestSysVarSetDisabled(t *testing.T) { 269 vschemaWrapper := &vschemaWrapper{ 270 v: loadSchema(t, "vschemas/schema.json", true), 271 sysVarEnabled: false, 272 } 273 274 testFile(t, "set_sysvar_disabled_cases.json", makeTestOutput(t), vschemaWrapper, false) 275 } 276 277 func TestViews(t *testing.T) { 278 vschemaWrapper := &vschemaWrapper{ 279 v: loadSchema(t, "vschemas/schema.json", true), 280 enableViews: true, 281 } 282 283 testFile(t, "view_cases.json", makeTestOutput(t), vschemaWrapper, false) 284 } 285 286 func TestOne(t *testing.T) { 287 vschema := &vschemaWrapper{ 288 v: loadSchema(t, "vschemas/schema.json", true), 289 } 290 291 testFile(t, "onecase.json", "", vschema, false) 292 } 293 294 func TestOneWithMainAsDefault(t *testing.T) { 295 vschema := &vschemaWrapper{ 296 v: loadSchema(t, "vschemas/schema.json", true), 297 keyspace: &vindexes.Keyspace{ 298 Name: "main", 299 Sharded: false, 300 }, 301 } 302 303 testFile(t, "onecase.json", "", vschema, false) 304 } 305 306 func TestOneWithSecondUserAsDefault(t *testing.T) { 307 vschema := &vschemaWrapper{ 308 v: loadSchema(t, "vschemas/schema.json", true), 309 keyspace: &vindexes.Keyspace{ 310 Name: "second_user", 311 Sharded: true, 312 }, 313 } 314 315 testFile(t, "onecase.json", "", vschema, false) 316 } 317 318 func TestOneWithUserAsDefault(t *testing.T) { 319 vschema := &vschemaWrapper{ 320 v: loadSchema(t, "vschemas/schema.json", true), 321 keyspace: &vindexes.Keyspace{ 322 Name: "user", 323 Sharded: true, 324 }, 325 } 326 327 testFile(t, "onecase.json", "", vschema, false) 328 } 329 330 func TestOneWithTPCHVSchema(t *testing.T) { 331 vschema := &vschemaWrapper{ 332 v: loadSchema(t, "vschemas/tpch_schema.json", true), 333 sysVarEnabled: true, 334 } 335 336 testFile(t, "onecase.json", "", vschema, false) 337 } 338 339 func TestRubyOnRailsQueries(t *testing.T) { 340 vschemaWrapper := &vschemaWrapper{ 341 v: loadSchema(t, "vschemas/rails_schema.json", true), 342 sysVarEnabled: true, 343 } 344 345 testFile(t, "rails_cases.json", makeTestOutput(t), vschemaWrapper, false) 346 } 347 348 func TestOLTP(t *testing.T) { 349 vschemaWrapper := &vschemaWrapper{ 350 v: loadSchema(t, "vschemas/oltp_schema.json", true), 351 sysVarEnabled: true, 352 } 353 354 testFile(t, "oltp_cases.json", makeTestOutput(t), vschemaWrapper, false) 355 } 356 357 func TestTPCC(t *testing.T) { 358 vschemaWrapper := &vschemaWrapper{ 359 v: loadSchema(t, "vschemas/tpcc_schema.json", true), 360 sysVarEnabled: true, 361 } 362 363 testFile(t, "tpcc_cases.json", makeTestOutput(t), vschemaWrapper, false) 364 } 365 366 func TestTPCH(t *testing.T) { 367 vschemaWrapper := &vschemaWrapper{ 368 v: loadSchema(t, "vschemas/tpch_schema.json", true), 369 sysVarEnabled: true, 370 } 371 372 testFile(t, "tpch_cases.json", makeTestOutput(t), vschemaWrapper, false) 373 } 374 375 func BenchmarkOLTP(b *testing.B) { 376 benchmarkWorkload(b, "oltp") 377 } 378 379 func BenchmarkTPCC(b *testing.B) { 380 benchmarkWorkload(b, "tpcc") 381 } 382 383 func BenchmarkTPCH(b *testing.B) { 384 benchmarkWorkload(b, "tpch") 385 } 386 387 func benchmarkWorkload(b *testing.B, name string) { 388 vschemaWrapper := &vschemaWrapper{ 389 v: loadSchema(b, name+"vschemas/_schema.json", true), 390 sysVarEnabled: true, 391 } 392 393 testCases := readJSONTests(name + "_cases.json") 394 b.ResetTimer() 395 for _, version := range plannerVersions { 396 b.Run(version.String(), func(b *testing.B) { 397 benchmarkPlanner(b, version, testCases, vschemaWrapper) 398 }) 399 } 400 } 401 402 func TestBypassPlanningShardTargetFromFile(t *testing.T) { 403 vschema := &vschemaWrapper{ 404 v: loadSchema(t, "vschemas/schema.json", true), 405 keyspace: &vindexes.Keyspace{ 406 Name: "main", 407 Sharded: false, 408 }, 409 tabletType: topodatapb.TabletType_PRIMARY, 410 dest: key.DestinationShard("-80")} 411 412 testFile(t, "bypass_shard_cases.json", makeTestOutput(t), vschema, false) 413 } 414 func TestBypassPlanningKeyrangeTargetFromFile(t *testing.T) { 415 keyRange, _ := key.ParseShardingSpec("-") 416 417 vschema := &vschemaWrapper{ 418 v: loadSchema(t, "vschemas/schema.json", true), 419 keyspace: &vindexes.Keyspace{ 420 Name: "main", 421 Sharded: false, 422 }, 423 tabletType: topodatapb.TabletType_PRIMARY, 424 dest: key.DestinationExactKeyRange{KeyRange: keyRange[0]}, 425 } 426 427 testFile(t, "bypass_keyrange_cases.json", makeTestOutput(t), vschema, false) 428 } 429 430 func TestWithDefaultKeyspaceFromFile(t *testing.T) { 431 // We are testing this separately so we can set a default keyspace 432 vschema := &vschemaWrapper{ 433 v: loadSchema(t, "vschemas/schema.json", true), 434 keyspace: &vindexes.Keyspace{ 435 Name: "main", 436 Sharded: false, 437 }, 438 tabletType: topodatapb.TabletType_PRIMARY, 439 } 440 441 testOutputTempDir := makeTestOutput(t) 442 testFile(t, "alterVschema_cases.json", testOutputTempDir, vschema, false) 443 testFile(t, "ddl_cases.json", testOutputTempDir, vschema, false) 444 testFile(t, "migration_cases.json", testOutputTempDir, vschema, false) 445 testFile(t, "flush_cases.json", testOutputTempDir, vschema, false) 446 testFile(t, "show_cases.json", testOutputTempDir, vschema, false) 447 testFile(t, "call_cases.json", testOutputTempDir, vschema, false) 448 } 449 450 func TestWithDefaultKeyspaceFromFileSharded(t *testing.T) { 451 // We are testing this separately so we can set a default keyspace 452 vschema := &vschemaWrapper{ 453 v: loadSchema(t, "vschemas/schema.json", true), 454 keyspace: &vindexes.Keyspace{ 455 Name: "second_user", 456 Sharded: true, 457 }, 458 tabletType: topodatapb.TabletType_PRIMARY, 459 } 460 461 testOutputTempDir := makeTestOutput(t) 462 testFile(t, "select_cases_with_default.json", testOutputTempDir, vschema, false) 463 } 464 465 func TestWithUserDefaultKeyspaceFromFileSharded(t *testing.T) { 466 // We are testing this separately so we can set a default keyspace 467 vschema := &vschemaWrapper{ 468 v: loadSchema(t, "vschemas/schema.json", true), 469 keyspace: &vindexes.Keyspace{ 470 Name: "user", 471 Sharded: true, 472 }, 473 tabletType: topodatapb.TabletType_PRIMARY, 474 } 475 476 testOutputTempDir := makeTestOutput(t) 477 testFile(t, "select_cases_with_user_as_default.json", testOutputTempDir, vschema, false) 478 } 479 480 func TestWithSystemSchemaAsDefaultKeyspace(t *testing.T) { 481 // We are testing this separately so we can set a default keyspace 482 vschema := &vschemaWrapper{ 483 v: loadSchema(t, "vschemas/schema.json", true), 484 keyspace: &vindexes.Keyspace{Name: "information_schema"}, 485 tabletType: topodatapb.TabletType_PRIMARY, 486 } 487 488 testFile(t, "sysschema_default.json", makeTestOutput(t), vschema, false) 489 } 490 491 func TestOtherPlanningFromFile(t *testing.T) { 492 // We are testing this separately so we can set a default keyspace 493 vschema := &vschemaWrapper{ 494 v: loadSchema(t, "vschemas/schema.json", true), 495 keyspace: &vindexes.Keyspace{ 496 Name: "main", 497 Sharded: false, 498 }, 499 tabletType: topodatapb.TabletType_PRIMARY, 500 } 501 502 testOutputTempDir := makeTestOutput(t) 503 testFile(t, "other_read_cases.json", testOutputTempDir, vschema, false) 504 testFile(t, "other_admin_cases.json", testOutputTempDir, vschema, false) 505 } 506 507 func loadSchema(t testing.TB, filename string, setCollation bool) *vindexes.VSchema { 508 formal, err := vindexes.LoadFormal(locateFile(filename)) 509 if err != nil { 510 t.Fatal(err) 511 } 512 vschema := vindexes.BuildVSchema(formal) 513 if err != nil { 514 t.Fatal(err) 515 } 516 for _, ks := range vschema.Keyspaces { 517 if ks.Error != nil { 518 t.Fatal(ks.Error) 519 } 520 521 // adding view in user keyspace 522 if ks.Keyspace.Name == "user" { 523 if err = vschema.AddView(ks.Keyspace.Name, 524 "user_details_view", 525 "select user.id, user_extra.col from user join user_extra on user.id = user_extra.user_id"); err != nil { 526 t.Fatal(err) 527 } 528 } 529 530 // setting a default value to all the text columns in the tables of this keyspace 531 // so that we can "simulate" a real case scenario where the vschema is aware of 532 // columns' collations. 533 if setCollation { 534 for _, table := range ks.Tables { 535 for i, col := range table.Columns { 536 if sqltypes.IsText(col.Type) { 537 table.Columns[i].CollationName = "latin1_swedish_ci" 538 } 539 } 540 } 541 } 542 } 543 return vschema 544 } 545 546 var _ plancontext.VSchema = (*vschemaWrapper)(nil) 547 548 type vschemaWrapper struct { 549 v *vindexes.VSchema 550 keyspace *vindexes.Keyspace 551 tabletType topodatapb.TabletType 552 dest key.Destination 553 sysVarEnabled bool 554 version plancontext.PlannerVersion 555 enableViews bool 556 } 557 558 func (vw *vschemaWrapper) IsShardRoutingEnabled() bool { 559 return false 560 } 561 562 func (vw *vschemaWrapper) GetVSchema() *vindexes.VSchema { 563 return vw.v 564 } 565 566 func (vw *vschemaWrapper) GetSrvVschema() *vschemapb.SrvVSchema { 567 return &vschemapb.SrvVSchema{ 568 Keyspaces: map[string]*vschemapb.Keyspace{ 569 "user": { 570 Sharded: true, 571 Vindexes: map[string]*vschemapb.Vindex{}, 572 Tables: map[string]*vschemapb.Table{ 573 "user": {}, 574 }, 575 }, 576 }, 577 } 578 } 579 580 func (vw *vschemaWrapper) ConnCollation() collations.ID { 581 return collations.CollationUtf8ID 582 } 583 584 func (vw *vschemaWrapper) PlannerWarning(_ string) { 585 } 586 587 func (vw *vschemaWrapper) ForeignKeyMode() string { 588 return "allow" 589 } 590 591 func (vw *vschemaWrapper) AllKeyspace() ([]*vindexes.Keyspace, error) { 592 if vw.keyspace == nil { 593 return nil, vterrors.VT13001("keyspace not available") 594 } 595 return []*vindexes.Keyspace{vw.keyspace}, nil 596 } 597 598 // FindKeyspace implements the VSchema interface 599 func (vw *vschemaWrapper) FindKeyspace(keyspace string) (*vindexes.Keyspace, error) { 600 if vw.keyspace == nil { 601 return nil, vterrors.VT13001("keyspace not available") 602 } 603 if vw.keyspace.Name == keyspace { 604 return vw.keyspace, nil 605 } 606 return nil, nil 607 } 608 609 func (vw *vschemaWrapper) Planner() plancontext.PlannerVersion { 610 return vw.version 611 } 612 613 // SetPlannerVersion implements the ContextVSchema interface 614 func (vw *vschemaWrapper) SetPlannerVersion(v plancontext.PlannerVersion) { 615 vw.version = v 616 } 617 618 func (vw *vschemaWrapper) GetSemTable() *semantics.SemTable { 619 return nil 620 } 621 622 func (vw *vschemaWrapper) KeyspaceExists(keyspace string) bool { 623 if vw.keyspace != nil { 624 return vw.keyspace.Name == keyspace 625 } 626 return false 627 } 628 629 func (vw *vschemaWrapper) SysVarSetEnabled() bool { 630 return vw.sysVarEnabled 631 } 632 633 func (vw *vschemaWrapper) TargetDestination(qualifier string) (key.Destination, *vindexes.Keyspace, topodatapb.TabletType, error) { 634 var keyspaceName string 635 if vw.keyspace != nil { 636 keyspaceName = vw.keyspace.Name 637 } 638 if vw.dest == nil && qualifier != "" { 639 keyspaceName = qualifier 640 } 641 if keyspaceName == "" { 642 return nil, nil, 0, vterrors.VT03007() 643 } 644 keyspace := vw.v.Keyspaces[keyspaceName] 645 if keyspace == nil { 646 return nil, nil, 0, vterrors.VT05003(keyspaceName) 647 } 648 return vw.dest, keyspace.Keyspace, vw.tabletType, nil 649 650 } 651 652 func (vw *vschemaWrapper) TabletType() topodatapb.TabletType { 653 return vw.tabletType 654 } 655 656 func (vw *vschemaWrapper) Destination() key.Destination { 657 return vw.dest 658 } 659 660 func (vw *vschemaWrapper) FindTable(tab sqlparser.TableName) (*vindexes.Table, string, topodatapb.TabletType, key.Destination, error) { 661 destKeyspace, destTabletType, destTarget, err := topoproto.ParseDestination(tab.Qualifier.String(), topodatapb.TabletType_PRIMARY) 662 if err != nil { 663 return nil, destKeyspace, destTabletType, destTarget, err 664 } 665 table, err := vw.v.FindTable(destKeyspace, tab.Name.String()) 666 if err != nil { 667 return nil, destKeyspace, destTabletType, destTarget, err 668 } 669 return table, destKeyspace, destTabletType, destTarget, nil 670 } 671 672 func (vw *vschemaWrapper) FindView(tab sqlparser.TableName) sqlparser.SelectStatement { 673 destKeyspace, _, _, err := topoproto.ParseDestination(tab.Qualifier.String(), topodatapb.TabletType_PRIMARY) 674 if err != nil { 675 return nil 676 } 677 return vw.v.FindView(destKeyspace, tab.Name.String()) 678 } 679 680 func (vw *vschemaWrapper) FindTableOrVindex(tab sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) { 681 destKeyspace, destTabletType, destTarget, err := topoproto.ParseDestination(tab.Qualifier.String(), topodatapb.TabletType_PRIMARY) 682 if err != nil { 683 return nil, nil, destKeyspace, destTabletType, destTarget, err 684 } 685 if destKeyspace == "" { 686 destKeyspace = vw.getActualKeyspace() 687 } 688 table, vindex, err := vw.v.FindTableOrVindex(destKeyspace, tab.Name.String(), topodatapb.TabletType_PRIMARY) 689 if err != nil { 690 return nil, nil, destKeyspace, destTabletType, destTarget, err 691 } 692 return table, vindex, destKeyspace, destTabletType, destTarget, nil 693 } 694 695 func (vw *vschemaWrapper) getActualKeyspace() string { 696 if vw.keyspace == nil { 697 return "" 698 } 699 if !sqlparser.SystemSchema(vw.keyspace.Name) { 700 return vw.keyspace.Name 701 } 702 ks, err := vw.AnyKeyspace() 703 if err != nil { 704 return "" 705 } 706 return ks.Name 707 } 708 709 func (vw *vschemaWrapper) DefaultKeyspace() (*vindexes.Keyspace, error) { 710 return vw.v.Keyspaces["main"].Keyspace, nil 711 } 712 713 func (vw *vschemaWrapper) AnyKeyspace() (*vindexes.Keyspace, error) { 714 return vw.DefaultKeyspace() 715 } 716 717 func (vw *vschemaWrapper) FirstSortedKeyspace() (*vindexes.Keyspace, error) { 718 return vw.v.Keyspaces["main"].Keyspace, nil 719 } 720 721 func (vw *vschemaWrapper) TargetString() string { 722 return "targetString" 723 } 724 725 func (vw *vschemaWrapper) WarnUnshardedOnly(_ string, _ ...any) { 726 727 } 728 729 func (vw *vschemaWrapper) ErrorIfShardedF(keyspace *vindexes.Keyspace, _, errFmt string, params ...any) error { 730 if keyspace.Sharded { 731 return fmt.Errorf(errFmt, params...) 732 } 733 return nil 734 } 735 736 func (vw *vschemaWrapper) currentDb() string { 737 ksName := "" 738 if vw.keyspace != nil { 739 ksName = vw.keyspace.Name 740 } 741 return ksName 742 } 743 744 func (vw *vschemaWrapper) FindRoutedShard(keyspace, shard string) (string, error) { 745 return "", nil 746 } 747 748 func (vw *vschemaWrapper) IsViewsEnabled() bool { 749 return vw.enableViews 750 } 751 752 type ( 753 planTest struct { 754 Comment string `json:"comment,omitempty"` 755 Query string `json:"query,omitempty"` 756 Plan json.RawMessage `json:"plan,omitempty"` 757 V3Plan json.RawMessage `json:"v3-plan,omitempty"` 758 Gen4Plan json.RawMessage `json:"gen4-plan,omitempty"` 759 } 760 ) 761 762 func testFile(t *testing.T, filename, tempDir string, vschema *vschemaWrapper, render bool) { 763 opts := jsondiff.DefaultConsoleOptions() 764 765 t.Run(filename, func(t *testing.T) { 766 var expected []planTest 767 var outFirstPlanner string 768 for _, tcase := range readJSONTests(filename) { 769 if tcase.V3Plan == nil { 770 tcase.V3Plan = tcase.Plan 771 tcase.Gen4Plan = tcase.Plan 772 } 773 current := planTest{} 774 testName := tcase.Comment 775 if testName == "" { 776 testName = tcase.Query 777 } 778 if tcase.Query == "" { 779 continue 780 } 781 t.Run(fmt.Sprintf("V3: %s", testName), func(t *testing.T) { 782 vschema.version = V3 783 plan, err := TestBuilder(tcase.Query, vschema, vschema.currentDb()) 784 if render && plan != nil { 785 viz, err := engine.GraphViz(plan.Instructions) 786 if err == nil { 787 _ = viz.Render() 788 } 789 } 790 out := getPlanOrErrorOutput(err, plan) 791 792 compare, s := jsondiff.Compare(tcase.V3Plan, []byte(out), &opts) 793 if compare != jsondiff.FullMatch { 794 t.Errorf("V3 - %s\nDiff:\n%s\n[%s] \n[%s]", filename, s, tcase.V3Plan, out) 795 } 796 797 outFirstPlanner = out 798 current.Comment = testName 799 current.Query = tcase.Query 800 }) 801 802 vschema.version = Gen4 803 out, err := getPlanOutput(tcase, vschema, render) 804 if err != nil && len(tcase.Gen4Plan) == 0 && strings.HasPrefix(err.Error(), "gen4 does not yet support") { 805 continue 806 } 807 808 // our expectation for the new planner on this query is one of three 809 // - it produces the same plan as V3 - this is shown using empty brackets: {\n} 810 // - it produces a different but accepted plan - this is shown using the accepted plan 811 // - or it produces a different plan that has not yet been accepted, or it fails to produce a plan 812 // this is shown by not having any info at all after the result for the V3 planner 813 // with this last expectation, it is an error if the Gen4 planner 814 // produces the same plan as the V3 planner does 815 t.Run(fmt.Sprintf("Gen4: %s", testName), func(t *testing.T) { 816 compare, s := jsondiff.Compare(tcase.Gen4Plan, []byte(out), &opts) 817 if compare != jsondiff.FullMatch { 818 t.Errorf("Gen4 - %s\nDiff:\n%s\n[%s] \n[%s]", filename, s, tcase.Gen4Plan, out) 819 } 820 821 if outFirstPlanner == out { 822 current.Plan = []byte(out) 823 } else { 824 current.V3Plan = []byte(outFirstPlanner) 825 current.Gen4Plan = []byte(out) 826 } 827 }) 828 expected = append(expected, current) 829 } 830 if tempDir != "" { 831 name := strings.TrimSuffix(filename, filepath.Ext(filename)) 832 name = filepath.Join(tempDir, name+".json") 833 file, err := os.Create(name) 834 require.NoError(t, err) 835 enc := json.NewEncoder(file) 836 enc.SetEscapeHTML(false) 837 enc.SetIndent("", " ") 838 err = enc.Encode(expected) 839 if err != nil { 840 require.NoError(t, err) 841 } 842 } 843 }) 844 } 845 846 func readJSONTests(filename string) []planTest { 847 var output []planTest 848 file, err := os.Open(locateFile(filename)) 849 if err != nil { 850 panic(err) 851 } 852 dec := json.NewDecoder(file) 853 err = dec.Decode(&output) 854 if err != nil { 855 panic(err) 856 } 857 return output 858 } 859 860 func getPlanOutput(tcase planTest, vschema *vschemaWrapper, render bool) (out string, err error) { 861 defer func() { 862 if r := recover(); r != nil { 863 out = fmt.Sprintf("panicked: %v\n%s", r, string(debug.Stack())) 864 } 865 }() 866 plan, err := TestBuilder(tcase.Query, vschema, vschema.currentDb()) 867 if render && plan != nil { 868 viz, err := engine.GraphViz(plan.Instructions) 869 if err == nil { 870 _ = viz.Render() 871 } 872 } 873 out = getPlanOrErrorOutput(err, plan) 874 return out, err 875 } 876 877 func getPlanOrErrorOutput(err error, plan *engine.Plan) string { 878 if err != nil { 879 return "\"" + err.Error() + "\"" 880 } 881 b := new(bytes.Buffer) 882 enc := json.NewEncoder(b) 883 enc.SetEscapeHTML(false) 884 enc.SetIndent("", " ") 885 err = enc.Encode(plan) 886 if err != nil { 887 panic(err) 888 } 889 return b.String() 890 } 891 892 func locateFile(name string) string { 893 return "testdata/" + name 894 } 895 896 var benchMarkFiles = []string{"from_cases.json", "filter_cases.json", "large_cases.json", "aggr_cases.json", "select_cases.json", "union_cases.json"} 897 898 func BenchmarkPlanner(b *testing.B) { 899 vschema := &vschemaWrapper{ 900 v: loadSchema(b, "vschemas/schema.json", true), 901 sysVarEnabled: true, 902 } 903 for _, filename := range benchMarkFiles { 904 testCases := readJSONTests(filename) 905 b.Run(filename+"-v3", func(b *testing.B) { 906 benchmarkPlanner(b, V3, testCases, vschema) 907 }) 908 b.Run(filename+"-gen4", func(b *testing.B) { 909 benchmarkPlanner(b, Gen4, testCases, vschema) 910 }) 911 b.Run(filename+"-gen4left2right", func(b *testing.B) { 912 benchmarkPlanner(b, Gen4Left2Right, testCases, vschema) 913 }) 914 } 915 } 916 917 func BenchmarkSemAnalysis(b *testing.B) { 918 vschema := &vschemaWrapper{ 919 v: loadSchema(b, "vschemas/schema.json", true), 920 sysVarEnabled: true, 921 } 922 923 for i := 0; i < b.N; i++ { 924 for _, filename := range benchMarkFiles { 925 for _, tc := range readJSONTests(filename) { 926 exerciseAnalyzer(tc.Query, vschema.currentDb(), vschema) 927 } 928 } 929 } 930 } 931 932 func exerciseAnalyzer(query, database string, s semantics.SchemaInformation) { 933 defer func() { 934 // if analysis panics, let's just continue. this is just a benchmark 935 recover() 936 }() 937 938 ast, err := sqlparser.Parse(query) 939 if err != nil { 940 return 941 } 942 sel, ok := ast.(sqlparser.SelectStatement) 943 if !ok { 944 return 945 } 946 947 _, _ = semantics.Analyze(sel, database, s) 948 } 949 950 func BenchmarkSelectVsDML(b *testing.B) { 951 vschema := &vschemaWrapper{ 952 v: loadSchema(b, "vschemas/schema.json", true), 953 sysVarEnabled: true, 954 version: V3, 955 } 956 957 dmlCases := readJSONTests("dml_cases.json") 958 selectCases := readJSONTests("select_cases.json") 959 960 rand.Shuffle(len(dmlCases), func(i, j int) { 961 dmlCases[i], dmlCases[j] = dmlCases[j], dmlCases[i] 962 }) 963 964 rand.Shuffle(len(selectCases), func(i, j int) { 965 selectCases[i], selectCases[j] = selectCases[j], selectCases[i] 966 }) 967 968 b.Run("DML (random sample, N=32)", func(b *testing.B) { 969 benchmarkPlanner(b, V3, dmlCases[:32], vschema) 970 }) 971 972 b.Run("Select (random sample, N=32)", func(b *testing.B) { 973 benchmarkPlanner(b, V3, selectCases[:32], vschema) 974 }) 975 } 976 977 func benchmarkPlanner(b *testing.B, version plancontext.PlannerVersion, testCases []planTest, vschema *vschemaWrapper) { 978 b.ReportAllocs() 979 for n := 0; n < b.N; n++ { 980 for _, tcase := range testCases { 981 if len(tcase.Gen4Plan) > 0 { 982 vschema.version = version 983 _, _ = TestBuilder(tcase.Query, vschema, vschema.currentDb()) 984 } 985 } 986 } 987 }