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  }