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  }