github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/memo/memo_test.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package memo_test
    12  
    13  import (
    14  	"context"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/lex"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    21  	opttestutils "github.com/cockroachdb/cockroach/pkg/sql/opt/testutils"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/opt/testutils/opttester"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/opt/testutils/testcat"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/opt/xform"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    26  	"github.com/cockroachdb/cockroach/pkg/testutils"
    27  	"github.com/cockroachdb/datadriven"
    28  )
    29  
    30  func TestMemo(t *testing.T) {
    31  	flags := memo.ExprFmtHideCost | memo.ExprFmtHideRuleProps | memo.ExprFmtHideQualifications |
    32  		memo.ExprFmtHideStats
    33  	runDataDrivenTest(t, "testdata/memo", flags)
    34  }
    35  
    36  func TestFormat(t *testing.T) {
    37  	runDataDrivenTest(t, "testdata/format", memo.ExprFmtShowAll)
    38  }
    39  
    40  func TestLogicalProps(t *testing.T) {
    41  	flags := memo.ExprFmtHideCost | memo.ExprFmtHideQualifications | memo.ExprFmtHideStats
    42  	runDataDrivenTest(t, "testdata/logprops/", flags)
    43  }
    44  
    45  func TestStats(t *testing.T) {
    46  	flags := memo.ExprFmtHideCost | memo.ExprFmtHideRuleProps | memo.ExprFmtHideQualifications |
    47  		memo.ExprFmtHideScalars
    48  	runDataDrivenTest(t, "testdata/stats/", flags)
    49  }
    50  
    51  func TestStatsQuality(t *testing.T) {
    52  	flags := memo.ExprFmtHideCost | memo.ExprFmtHideRuleProps | memo.ExprFmtHideQualifications |
    53  		memo.ExprFmtHideScalars
    54  	runDataDrivenTest(t, "testdata/stats_quality/", flags)
    55  }
    56  
    57  func TestMemoInit(t *testing.T) {
    58  	catalog := testcat.New()
    59  	_, err := catalog.ExecuteDDL("CREATE TABLE abc (a INT PRIMARY KEY, b INT, c STRING, INDEX (c))")
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  
    64  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
    65  
    66  	var o xform.Optimizer
    67  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT * FROM abc WHERE $1=10")
    68  
    69  	o.Init(&evalCtx, catalog)
    70  	if !o.Memo().IsEmpty() {
    71  		t.Fatal("memo should be empty")
    72  	}
    73  	if o.Memo().MemoryEstimate() != 0 {
    74  		t.Fatal("memory estimate should be 0")
    75  	}
    76  	if o.Memo().RootExpr() != nil {
    77  		t.Fatal("root expression should be nil")
    78  	}
    79  	if o.Memo().RootProps() != nil {
    80  		t.Fatal("root props should be nil")
    81  	}
    82  }
    83  
    84  func TestMemoIsStale(t *testing.T) {
    85  	catalog := testcat.New()
    86  	_, err := catalog.ExecuteDDL("CREATE TABLE abc (a INT PRIMARY KEY, b INT, c STRING, INDEX (c))")
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	_, err = catalog.ExecuteDDL("CREATE VIEW abcview AS SELECT a, b, c FROM abc")
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	// Revoke access to the underlying table. The user should retain indirect
    96  	// access via the view.
    97  	catalog.Table(tree.NewTableName("t", "abc")).Revoked = true
    98  
    99  	// Initialize context with starting values.
   100  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
   101  	evalCtx.SessionData.Database = "t"
   102  
   103  	var o xform.Optimizer
   104  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT a, b+1 FROM abcview WHERE c='foo'")
   105  	o.Memo().Metadata().AddSchema(catalog.Schema())
   106  
   107  	ctx := context.Background()
   108  	stale := func() {
   109  		t.Helper()
   110  		if isStale, err := o.Memo().IsStale(ctx, &evalCtx, catalog); err != nil {
   111  			t.Fatal(err)
   112  		} else if !isStale {
   113  			t.Errorf("memo should be stale")
   114  		}
   115  
   116  		// If we did not initialize the Memo's copy of a SessionData setting, the
   117  		// tests as written still pass if the default value is 0. To detect this, we
   118  		// create a new memo with the changed setting and verify it's not stale.
   119  		var o2 xform.Optimizer
   120  		opttestutils.BuildQuery(t, &o2, catalog, &evalCtx, "SELECT a, b+1 FROM abcview WHERE c='foo'")
   121  
   122  		if isStale, err := o2.Memo().IsStale(ctx, &evalCtx, catalog); err != nil {
   123  			t.Fatal(err)
   124  		} else if isStale {
   125  			t.Errorf("memo should not be stale")
   126  		}
   127  	}
   128  
   129  	notStale := func() {
   130  		t.Helper()
   131  		if isStale, err := o.Memo().IsStale(ctx, &evalCtx, catalog); err != nil {
   132  			t.Fatal(err)
   133  		} else if isStale {
   134  			t.Errorf("memo should not be stale")
   135  		}
   136  	}
   137  
   138  	notStale()
   139  
   140  	// Stale location.
   141  	evalCtx.SessionData.DataConversion.Location = time.FixedZone("PST", -8*60*60)
   142  	stale()
   143  	evalCtx.SessionData.DataConversion.Location = time.UTC
   144  	notStale()
   145  
   146  	// Stale bytes encode format.
   147  	evalCtx.SessionData.DataConversion.BytesEncodeFormat = lex.BytesEncodeBase64
   148  	stale()
   149  	evalCtx.SessionData.DataConversion.BytesEncodeFormat = lex.BytesEncodeHex
   150  	notStale()
   151  
   152  	// Stale extra float digits.
   153  	evalCtx.SessionData.DataConversion.ExtraFloatDigits = 2
   154  	stale()
   155  	evalCtx.SessionData.DataConversion.ExtraFloatDigits = 0
   156  	notStale()
   157  
   158  	// Stale reorder joins limit.
   159  	evalCtx.SessionData.ReorderJoinsLimit = 4
   160  	stale()
   161  	evalCtx.SessionData.ReorderJoinsLimit = 0
   162  	notStale()
   163  
   164  	// Stale zig zag join enable.
   165  	evalCtx.SessionData.ZigzagJoinEnabled = true
   166  	stale()
   167  	evalCtx.SessionData.ZigzagJoinEnabled = false
   168  	notStale()
   169  
   170  	// Stale optimizer FK Checks planning enable.
   171  	evalCtx.SessionData.OptimizerFKChecks = true
   172  	stale()
   173  	evalCtx.SessionData.OptimizerFKChecks = false
   174  	notStale()
   175  
   176  	// Stale optimizer FK Cascades planning enable.
   177  	evalCtx.SessionData.OptimizerFKCascades = true
   178  	stale()
   179  	evalCtx.SessionData.OptimizerFKCascades = false
   180  	notStale()
   181  
   182  	// Stale optimizer histogram usage enable.
   183  	evalCtx.SessionData.OptimizerUseHistograms = true
   184  	stale()
   185  	evalCtx.SessionData.OptimizerUseHistograms = false
   186  	notStale()
   187  
   188  	// Stale optimizer multi-col stats usage enable.
   189  	evalCtx.SessionData.OptimizerUseMultiColStats = true
   190  	stale()
   191  	evalCtx.SessionData.OptimizerUseMultiColStats = false
   192  	notStale()
   193  
   194  	// Stale safe updates.
   195  	evalCtx.SessionData.SafeUpdates = true
   196  	stale()
   197  	evalCtx.SessionData.SafeUpdates = false
   198  	notStale()
   199  
   200  	// Stale data sources and schema. Create new catalog so that data sources are
   201  	// recreated and can be modified independently.
   202  	catalog = testcat.New()
   203  	_, err = catalog.ExecuteDDL("CREATE TABLE abc (a INT PRIMARY KEY, b INT, c STRING, INDEX (c))")
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	_, err = catalog.ExecuteDDL("CREATE VIEW abcview AS SELECT a, b, c FROM abc")
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  
   212  	// User no longer has access to view.
   213  	catalog.View(tree.NewTableName("t", "abcview")).Revoked = true
   214  	_, err = o.Memo().IsStale(ctx, &evalCtx, catalog)
   215  	if exp := "user does not have privilege"; !testutils.IsError(err, exp) {
   216  		t.Fatalf("expected %q error, but got %+v", exp, err)
   217  	}
   218  	catalog.View(tree.NewTableName("t", "abcview")).Revoked = false
   219  	notStale()
   220  
   221  	// Table ID changes.
   222  	catalog.Table(tree.NewTableName("t", "abc")).TabID = 1
   223  	stale()
   224  	catalog.Table(tree.NewTableName("t", "abc")).TabID = 53
   225  	notStale()
   226  
   227  	// Table Version changes.
   228  	catalog.Table(tree.NewTableName("t", "abc")).TabVersion = 1
   229  	stale()
   230  	catalog.Table(tree.NewTableName("t", "abc")).TabVersion = 0
   231  	notStale()
   232  }
   233  
   234  // TestStatsAvailable tests that the statisticsBuilder correctly identifies
   235  // for each expression whether statistics were available on the base table.
   236  // This test is here (instead of statistics_builder_test.go) to avoid import
   237  // cycles.
   238  func TestStatsAvailable(t *testing.T) {
   239  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
   240  
   241  	catalog := testcat.New()
   242  	if _, err := catalog.ExecuteDDL(
   243  		"CREATE TABLE t (a INT, b INT)",
   244  	); err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	var o xform.Optimizer
   249  
   250  	testNotAvailable := func(expr memo.RelExpr) {
   251  		traverseExpr(expr, func(e memo.RelExpr) {
   252  			if e.Relational().Stats.Available {
   253  				t.Fatal("stats should not be available")
   254  			}
   255  		})
   256  	}
   257  
   258  	// Stats should not be available for any expression.
   259  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT * FROM t WHERE a=1")
   260  	testNotAvailable(o.Memo().RootExpr().(memo.RelExpr))
   261  
   262  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT sum(a), b FROM t GROUP BY b")
   263  	testNotAvailable(o.Memo().RootExpr().(memo.RelExpr))
   264  
   265  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx,
   266  		"SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.a AND t1.b = 5",
   267  	)
   268  	testNotAvailable(o.Memo().RootExpr().(memo.RelExpr))
   269  
   270  	if _, err := catalog.ExecuteDDL(
   271  		`ALTER TABLE t INJECT STATISTICS '[
   272  		{
   273  			"columns": ["a"],
   274  			"created_at": "2018-01-01 1:00:00.00000+00:00",
   275  			"row_count": 1000,
   276  			"distinct_count": 500
   277  		},
   278  		{
   279  			"columns": ["b"],
   280  			"created_at": "2018-01-01 1:30:00.00000+00:00",
   281  			"row_count": 1000,
   282  			"distinct_count": 500
   283  		}
   284  	]'`); err != nil {
   285  		t.Fatal(err)
   286  	}
   287  
   288  	testAvailable := func(expr memo.RelExpr) {
   289  		traverseExpr(expr, func(e memo.RelExpr) {
   290  			if !e.Relational().Stats.Available {
   291  				t.Fatal("stats should be available")
   292  			}
   293  		})
   294  	}
   295  
   296  	// Stats should be available for all expressions.
   297  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT * FROM t WHERE a=1")
   298  	testAvailable(o.Memo().RootExpr().(memo.RelExpr))
   299  
   300  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx, "SELECT sum(a), b FROM t GROUP BY b")
   301  	testAvailable(o.Memo().RootExpr().(memo.RelExpr))
   302  
   303  	opttestutils.BuildQuery(t, &o, catalog, &evalCtx,
   304  		"SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.a AND t1.b = 5",
   305  	)
   306  	testAvailable(o.Memo().RootExpr().(memo.RelExpr))
   307  }
   308  
   309  // traverseExpr is a helper function to recursively traverse a relational
   310  // expression and apply a function to the root as well as each relational
   311  // child.
   312  func traverseExpr(expr memo.RelExpr, f func(memo.RelExpr)) {
   313  	f(expr)
   314  	for i, n := 0, expr.ChildCount(); i < n; i++ {
   315  		if child, ok := expr.Child(i).(memo.RelExpr); ok {
   316  			traverseExpr(child, f)
   317  		}
   318  	}
   319  }
   320  
   321  // runDataDrivenTest runs data-driven testcases of the form
   322  //   <command>
   323  //   <SQL statement>
   324  //   ----
   325  //   <expected results>
   326  //
   327  // See OptTester.Handle for supported commands.
   328  func runDataDrivenTest(t *testing.T, path string, fmtFlags memo.ExprFmtFlags) {
   329  	datadriven.Walk(t, path, func(t *testing.T, path string) {
   330  		catalog := testcat.New()
   331  		datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
   332  			tester := opttester.New(catalog, d.Input)
   333  			tester.Flags.ExprFormat = fmtFlags
   334  			return tester.RunCommand(t, d)
   335  		})
   336  	})
   337  }