github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/plan_opt.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 sql 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/security" 17 "github.com/cockroachdb/cockroach/pkg/settings" 18 "github.com/cockroachdb/cockroach/pkg/sql/opt/exec" 19 "github.com/cockroachdb/cockroach/pkg/sql/opt/exec/execbuilder" 20 "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" 21 "github.com/cockroachdb/cockroach/pkg/sql/opt/optbuilder" 22 "github.com/cockroachdb/cockroach/pkg/sql/opt/xform" 23 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 25 "github.com/cockroachdb/cockroach/pkg/sql/querycache" 26 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 27 "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" 28 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 29 "github.com/cockroachdb/cockroach/pkg/util/log" 30 "github.com/cockroachdb/errors" 31 ) 32 33 var queryCacheEnabled = settings.RegisterBoolSetting( 34 "sql.query_cache.enabled", "enable the query cache", true, 35 ) 36 37 // prepareUsingOptimizer builds a memo for a prepared statement and populates 38 // the following stmt.Prepared fields: 39 // - Columns 40 // - Types 41 // - AnonymizedStr 42 // - Memo (for reuse during exec, if appropriate). 43 func (p *planner) prepareUsingOptimizer(ctx context.Context) (planFlags, error) { 44 stmt := p.stmt 45 46 opc := &p.optPlanningCtx 47 opc.reset() 48 49 stmt.Prepared.AnonymizedStr = anonymizeStmt(stmt.AST) 50 51 switch stmt.AST.(type) { 52 case *tree.AlterIndex, *tree.AlterTable, *tree.AlterSequence, 53 *tree.BeginTransaction, 54 *tree.CommentOnColumn, *tree.CommentOnDatabase, *tree.CommentOnIndex, *tree.CommentOnTable, 55 *tree.CommitTransaction, 56 *tree.CopyFrom, *tree.CreateDatabase, *tree.CreateIndex, *tree.CreateView, 57 *tree.CreateSequence, 58 *tree.CreateStats, 59 *tree.Deallocate, *tree.Discard, *tree.DropDatabase, *tree.DropIndex, 60 *tree.DropTable, *tree.DropView, *tree.DropSequence, 61 *tree.Execute, 62 *tree.Grant, *tree.GrantRole, 63 *tree.Prepare, 64 *tree.ReleaseSavepoint, *tree.RenameColumn, *tree.RenameDatabase, 65 *tree.RenameIndex, *tree.RenameTable, *tree.Revoke, *tree.RevokeRole, 66 *tree.RollbackToSavepoint, *tree.RollbackTransaction, 67 *tree.Savepoint, *tree.SetTransaction, *tree.SetTracing, *tree.SetSessionAuthorizationDefault, 68 *tree.SetSessionCharacteristics: 69 // These statements do not have result columns and do not support placeholders 70 // so there is no need to do anything during prepare. 71 // 72 // Some of these statements (like BeginTransaction) aren't supported by the 73 // optbuilder so they would error out. Others (like CreateIndex) have planning 74 // code that can introduce unnecessary txn retries (because of looking up 75 // descriptors and such). 76 return opc.flags, nil 77 78 case *tree.ExplainAnalyzeDebug: 79 // This statement returns result columns but does not support placeholders, 80 // and we don't want to do anything during prepare. 81 if len(p.semaCtx.Placeholders.Types) != 0 { 82 return 0, errors.Errorf("%s does not support placeholders", stmt.AST.StatementTag()) 83 } 84 stmt.Prepared.Columns = sqlbase.ExplainAnalyzeDebugColumns 85 return opc.flags, nil 86 } 87 88 if opc.useCache { 89 cachedData, ok := p.execCfg.QueryCache.Find(&p.queryCacheSession, stmt.SQL) 90 if ok && cachedData.PrepareMetadata != nil { 91 pm := cachedData.PrepareMetadata 92 // Check that the type hints match (the type hints affect type checking). 93 if !pm.TypeHints.Equals(p.semaCtx.Placeholders.TypeHints) { 94 opc.log(ctx, "query cache hit but type hints don't match") 95 } else { 96 isStale, err := cachedData.Memo.IsStale(ctx, p.EvalContext(), &opc.catalog) 97 if err != nil { 98 return 0, err 99 } 100 if !isStale { 101 opc.log(ctx, "query cache hit (prepare)") 102 opc.flags.Set(planFlagOptCacheHit) 103 stmt.Prepared.AnonymizedStr = pm.AnonymizedStr 104 stmt.Prepared.Columns = pm.Columns 105 stmt.Prepared.Types = pm.Types 106 stmt.Prepared.Memo = cachedData.Memo 107 return opc.flags, nil 108 } 109 opc.log(ctx, "query cache hit but memo is stale (prepare)") 110 } 111 } else if ok { 112 opc.log(ctx, "query cache hit but there is no prepare metadata") 113 } else { 114 opc.log(ctx, "query cache miss") 115 } 116 opc.flags.Set(planFlagOptCacheMiss) 117 } 118 119 memo, err := opc.buildReusableMemo(ctx) 120 if err != nil { 121 return 0, err 122 } 123 124 md := memo.Metadata() 125 physical := memo.RootProps() 126 resultCols := make(sqlbase.ResultColumns, len(physical.Presentation)) 127 for i, col := range physical.Presentation { 128 resultCols[i].Name = col.Alias 129 resultCols[i].Typ = md.ColumnMeta(col.ID).Type 130 if err := checkResultType(resultCols[i].Typ); err != nil { 131 return 0, err 132 } 133 } 134 135 // Verify that all placeholder types have been set. 136 if err := p.semaCtx.Placeholders.Types.AssertAllSet(); err != nil { 137 return 0, err 138 } 139 140 stmt.Prepared.Columns = resultCols 141 stmt.Prepared.Types = p.semaCtx.Placeholders.Types 142 if opc.allowMemoReuse { 143 stmt.Prepared.Memo = memo 144 if opc.useCache { 145 // execPrepare sets the PrepareMetadata.InferredTypes field after this 146 // point. However, once the PrepareMetadata goes into the cache, it 147 // can't be modified without causing race conditions. So make a copy of 148 // it now. 149 // TODO(radu): Determine if the extra object allocation is really 150 // necessary. 151 pm := stmt.Prepared.PrepareMetadata 152 cachedData := querycache.CachedData{ 153 SQL: stmt.SQL, 154 Memo: memo, 155 PrepareMetadata: &pm, 156 } 157 p.execCfg.QueryCache.Add(&p.queryCacheSession, &cachedData) 158 } 159 } 160 return opc.flags, nil 161 } 162 163 // makeOptimizerPlan generates a plan using the cost-based optimizer. 164 // On success, it populates p.curPlan. 165 func (p *planner) makeOptimizerPlan(ctx context.Context) error { 166 stmt := p.stmt 167 168 opc := &p.optPlanningCtx 169 opc.reset() 170 171 execMemo, err := opc.buildExecMemo(ctx) 172 if err != nil { 173 return err 174 } 175 176 // Build the plan tree. 177 root := execMemo.RootExpr() 178 var ( 179 plan exec.Plan 180 bld *execbuilder.Builder 181 ) 182 if mode := p.SessionData().ExperimentalDistSQLPlanningMode; mode != sessiondata.ExperimentalDistSQLPlanningOff { 183 bld = execbuilder.New(newDistSQLSpecExecFactory(), execMemo, &opc.catalog, root, p.EvalContext()) 184 plan, err = bld.Build() 185 if err != nil { 186 if mode == sessiondata.ExperimentalDistSQLPlanningAlways && 187 p.stmt.AST.StatementTag() == "SELECT" { 188 // We do not fallback to the old path because experimental 189 // planning is set to 'always' and we have a SELECT statement, 190 // so we return an error. 191 // We use a simple heuristic to check whether the statement is 192 // a SELECT, and the reasoning behind it is that we want to be 193 // able to run certain statement types (e.g. SET) regardless of 194 // whether they are supported by the new factory. 195 // TODO(yuzefovich): update this once we support more than just 196 // SELECT statements (see #47473). 197 return err 198 } 199 // We will fallback to the old path. 200 bld = nil 201 } 202 } 203 if bld == nil { 204 // If bld is non-nil, then experimental planning has succeeded and has 205 // already created a plan. 206 bld = execbuilder.New(newExecFactory(p), execMemo, &opc.catalog, root, p.EvalContext()) 207 plan, err = bld.Build() 208 if err != nil { 209 return err 210 } 211 } 212 213 result := plan.(*planTop) 214 result.mem = execMemo 215 result.catalog = &opc.catalog 216 result.stmt = stmt 217 result.flags = opc.flags 218 if bld.IsDDL { 219 result.flags.Set(planFlagIsDDL) 220 } 221 222 cols := result.main.planColumns() 223 if stmt.ExpectedTypes != nil { 224 if !stmt.ExpectedTypes.TypesEqual(cols) { 225 return pgerror.New(pgcode.FeatureNotSupported, "cached plan must not change result type") 226 } 227 } 228 229 p.curPlan = *result 230 231 return nil 232 } 233 234 type optPlanningCtx struct { 235 p *planner 236 237 // catalog is initialized once, and reset for each query. This allows the 238 // catalog objects to be reused across queries in the same session. 239 catalog optCatalog 240 241 // -- Fields below are reinitialized for each query --- 242 243 optimizer xform.Optimizer 244 245 // When set, we are allowed to reuse a memo, or store a memo for later reuse. 246 allowMemoReuse bool 247 248 // When set, we consult and update the query cache. Never set if 249 // allowMemoReuse is false. 250 useCache bool 251 252 flags planFlags 253 } 254 255 // init performs one-time initialization of the planning context; reset() must 256 // also be called before each use. 257 func (opc *optPlanningCtx) init(p *planner) { 258 opc.p = p 259 opc.catalog.init(p) 260 } 261 262 // reset initializes the planning context for the statement in the planner. 263 func (opc *optPlanningCtx) reset() { 264 p := opc.p 265 opc.catalog.reset() 266 opc.optimizer.Init(p.EvalContext(), &opc.catalog) 267 opc.flags = 0 268 269 // We only allow memo caching for SELECT/INSERT/UPDATE/DELETE. We could 270 // support it for all statements in principle, but it would increase the 271 // surface of potential issues (conditions we need to detect to invalidate a 272 // cached memo). 273 switch p.stmt.AST.(type) { 274 case *tree.ParenSelect, *tree.Select, *tree.SelectClause, *tree.UnionClause, *tree.ValuesClause, 275 *tree.Insert, *tree.Update, *tree.Delete, *tree.CannedOptPlan: 276 // If the current transaction has uncommitted DDL statements, we cannot rely 277 // on descriptor versions for detecting a "stale" memo. This is because 278 // descriptor versions are bumped at most once per transaction, even if there 279 // are multiple DDL operations; and transactions can be aborted leading to 280 // potential reuse of versions. To avoid these issues, we prevent saving a 281 // memo (for prepare) or reusing a saved memo (for execute). 282 opc.allowMemoReuse = !p.Tables().HasUncommittedTables() 283 opc.useCache = opc.allowMemoReuse && queryCacheEnabled.Get(&p.execCfg.Settings.SV) 284 285 if _, isCanned := p.stmt.AST.(*tree.CannedOptPlan); isCanned { 286 // It's unsafe to use the cache, since PREPARE AS OPT PLAN doesn't track 287 // dependencies and check permissions. 288 opc.useCache = false 289 } 290 291 default: 292 opc.allowMemoReuse = false 293 opc.useCache = false 294 } 295 } 296 297 func (opc *optPlanningCtx) log(ctx context.Context, msg string) { 298 if log.VDepth(1, 1) { 299 log.InfofDepth(ctx, 1, "%s: %s", log.Safe(msg), opc.p.stmt) 300 } else { 301 log.Event(ctx, msg) 302 } 303 } 304 305 // buildReusableMemo builds the statement into a memo that can be stored for 306 // prepared statements and can later be used as a starting point for 307 // optimization. The returned memo is fully detached from the planner and can be 308 // used with reuseMemo independently and concurrently by multiple threads. 309 func (opc *optPlanningCtx) buildReusableMemo(ctx context.Context) (_ *memo.Memo, _ error) { 310 p := opc.p 311 312 _, isCanned := opc.p.stmt.AST.(*tree.CannedOptPlan) 313 if isCanned { 314 if !p.EvalContext().SessionData.AllowPrepareAsOptPlan { 315 return nil, pgerror.New(pgcode.InsufficientPrivilege, 316 "PREPARE AS OPT PLAN is a testing facility that should not be used directly", 317 ) 318 } 319 320 if p.SessionData().User != security.RootUser { 321 return nil, pgerror.New(pgcode.InsufficientPrivilege, 322 "PREPARE AS OPT PLAN may only be used by root", 323 ) 324 } 325 } 326 327 if p.SessionData().SaveTablesPrefix != "" && p.SessionData().User != security.RootUser { 328 return nil, pgerror.New(pgcode.InsufficientPrivilege, 329 "sub-expression tables creation may only be used by root", 330 ) 331 } 332 333 // Build the Memo (optbuild) and apply normalization rules to it. If the 334 // query contains placeholders, values are not assigned during this phase, 335 // as that only happens during the EXECUTE phase. If the query does not 336 // contain placeholders, then also apply exploration rules to the Memo so 337 // that there's even less to do during the EXECUTE phase. 338 // 339 f := opc.optimizer.Factory() 340 bld := optbuilder.New(ctx, &p.semaCtx, p.EvalContext(), &opc.catalog, f, opc.p.stmt.AST) 341 bld.KeepPlaceholders = true 342 if err := bld.Build(); err != nil { 343 return nil, err 344 } 345 346 if bld.DisableMemoReuse { 347 opc.allowMemoReuse = false 348 opc.useCache = false 349 } 350 351 if isCanned { 352 if f.Memo().HasPlaceholders() { 353 // We don't support placeholders inside the canned plan. The main reason 354 // is that they would be invisible to the parser (which is reports the 355 // number of placeholders, used to initialize the relevant structures). 356 return nil, pgerror.Newf(pgcode.Syntax, 357 "placeholders are not supported with PREPARE AS OPT PLAN") 358 } 359 // With a canned plan, the memo is already optimized. 360 } else { 361 // If the memo doesn't have placeholders, then fully optimize it, since 362 // it can be reused without further changes to build the execution tree. 363 if !f.Memo().HasPlaceholders() { 364 if _, err := opc.optimizer.Optimize(); err != nil { 365 return nil, err 366 } 367 } 368 } 369 370 // Detach the prepared memo from the factory and transfer its ownership 371 // to the prepared statement. DetachMemo will re-initialize the optimizer 372 // to an empty memo. 373 return opc.optimizer.DetachMemo(), nil 374 } 375 376 // reuseMemo returns an optimized memo using a cached memo as a starting point. 377 // 378 // The cached memo is not modified; it is safe to call reuseMemo on the same 379 // cachedMemo from multiple threads concurrently. 380 // 381 // The returned memo is only safe to use in one thread, during execution of the 382 // current statement. 383 func (opc *optPlanningCtx) reuseMemo(cachedMemo *memo.Memo) (*memo.Memo, error) { 384 if !cachedMemo.HasPlaceholders() { 385 // If there are no placeholders, the query was already fully optimized 386 // (see buildReusableMemo). 387 return cachedMemo, nil 388 } 389 f := opc.optimizer.Factory() 390 // Finish optimization by assigning any remaining placeholders and 391 // applying exploration rules. Reinitialize the optimizer and construct a 392 // new memo that is copied from the prepared memo, but with placeholders 393 // assigned. 394 if err := f.AssignPlaceholders(cachedMemo); err != nil { 395 return nil, err 396 } 397 if _, err := opc.optimizer.Optimize(); err != nil { 398 return nil, err 399 } 400 return f.Memo(), nil 401 } 402 403 // buildExecMemo creates a fully optimized memo, possibly reusing a previously 404 // cached memo as a starting point. 405 // 406 // The returned memo is only safe to use in one thread, during execution of the 407 // current statement. 408 func (opc *optPlanningCtx) buildExecMemo(ctx context.Context) (_ *memo.Memo, _ error) { 409 prepared := opc.p.stmt.Prepared 410 p := opc.p 411 if opc.allowMemoReuse && prepared != nil && prepared.Memo != nil { 412 // We are executing a previously prepared statement and a reusable memo is 413 // available. 414 415 // If the prepared memo has been invalidated by schema or other changes, 416 // re-prepare it. 417 if isStale, err := prepared.Memo.IsStale(ctx, p.EvalContext(), &opc.catalog); err != nil { 418 return nil, err 419 } else if isStale { 420 prepared.Memo, err = opc.buildReusableMemo(ctx) 421 opc.log(ctx, "rebuilding cached memo") 422 if err != nil { 423 return nil, err 424 } 425 } 426 opc.log(ctx, "reusing cached memo") 427 memo, err := opc.reuseMemo(prepared.Memo) 428 return memo, err 429 } 430 431 if opc.useCache { 432 // Consult the query cache. 433 cachedData, ok := p.execCfg.QueryCache.Find(&p.queryCacheSession, opc.p.stmt.SQL) 434 if ok { 435 if isStale, err := cachedData.Memo.IsStale(ctx, p.EvalContext(), &opc.catalog); err != nil { 436 return nil, err 437 } else if isStale { 438 cachedData.Memo, err = opc.buildReusableMemo(ctx) 439 if err != nil { 440 return nil, err 441 } 442 // Update the plan in the cache. If the cache entry had PrepareMetadata 443 // populated, it may no longer be valid. 444 cachedData.PrepareMetadata = nil 445 p.execCfg.QueryCache.Add(&p.queryCacheSession, &cachedData) 446 opc.log(ctx, "query cache hit but needed update") 447 opc.flags.Set(planFlagOptCacheMiss) 448 } else { 449 opc.log(ctx, "query cache hit") 450 opc.flags.Set(planFlagOptCacheHit) 451 } 452 memo, err := opc.reuseMemo(cachedData.Memo) 453 return memo, err 454 } 455 opc.flags.Set(planFlagOptCacheMiss) 456 opc.log(ctx, "query cache miss") 457 } 458 459 // We are executing a statement for which there is no reusable memo 460 // available. 461 f := opc.optimizer.Factory() 462 bld := optbuilder.New(ctx, &p.semaCtx, p.EvalContext(), &opc.catalog, f, opc.p.stmt.AST) 463 if err := bld.Build(); err != nil { 464 return nil, err 465 } 466 if _, isCanned := opc.p.stmt.AST.(*tree.CannedOptPlan); !isCanned { 467 if _, err := opc.optimizer.Optimize(); err != nil { 468 return nil, err 469 } 470 } 471 472 // If this statement doesn't have placeholders, add it to the cache. Note 473 // that non-prepared statements from pgwire clients cannot have 474 // placeholders. 475 if opc.useCache && !bld.HadPlaceholders && !bld.DisableMemoReuse { 476 memo := opc.optimizer.DetachMemo() 477 cachedData := querycache.CachedData{ 478 SQL: opc.p.stmt.SQL, 479 Memo: memo, 480 } 481 p.execCfg.QueryCache.Add(&p.queryCacheSession, &cachedData) 482 opc.log(ctx, "query cache add") 483 return memo, nil 484 } 485 486 return f.Memo(), nil 487 }