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 }