github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/memo/expr_format.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
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"sort"
    18  	"strings"
    19  	"unicode"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    27  	"github.com/cockroachdb/cockroach/pkg/util/treeprinter"
    28  	"github.com/cockroachdb/errors"
    29  )
    30  
    31  // ScalarFmtInterceptor is a callback that can be set to a custom formatting
    32  // function. If the function returns a non-empty string, the normal formatting
    33  // code is bypassed.
    34  var ScalarFmtInterceptor func(f *ExprFmtCtx, expr opt.ScalarExpr) string
    35  
    36  // ExprFmtFlags controls which properties of the expression are shown in
    37  // formatted output.
    38  type ExprFmtFlags int
    39  
    40  const (
    41  	// ExprFmtShowAll shows all properties of the expression.
    42  	ExprFmtShowAll ExprFmtFlags = 0
    43  
    44  	// ExprFmtHideMiscProps does not show outer columns, row cardinality, provided
    45  	// orderings, side effects, or error text in the output.
    46  	ExprFmtHideMiscProps ExprFmtFlags = 1 << (iota - 1)
    47  
    48  	// ExprFmtHideConstraints does not show inferred constraints in the output.
    49  	ExprFmtHideConstraints
    50  
    51  	// ExprFmtHideFuncDeps does not show functional dependencies in the output.
    52  	ExprFmtHideFuncDeps
    53  
    54  	// ExprFmtHideRuleProps does not show rule-specific properties in the output.
    55  	ExprFmtHideRuleProps
    56  
    57  	// ExprFmtHideStats does not show statistics in the output.
    58  	ExprFmtHideStats
    59  
    60  	// ExprFmtHideCost does not show expression cost in the output.
    61  	ExprFmtHideCost
    62  
    63  	// ExprFmtHideQualifications removes the qualification from column labels
    64  	// (except when a shortened name would be ambiguous).
    65  	ExprFmtHideQualifications
    66  
    67  	// ExprFmtHideScalars removes subtrees that contain only scalars and replaces
    68  	// them with the SQL expression (if possible).
    69  	ExprFmtHideScalars
    70  
    71  	// ExprFmtHidePhysProps hides all required physical properties, except for
    72  	// Presentation (see ExprFmtHideColumns).
    73  	ExprFmtHidePhysProps
    74  
    75  	// ExprFmtHideTypes hides type information from columns and scalar
    76  	// expressions.
    77  	ExprFmtHideTypes
    78  
    79  	// ExprFmtHideNotNull hides the !null specifier from columns.
    80  	ExprFmtHideNotNull
    81  
    82  	// ExprFmtHideColumns removes column information.
    83  	ExprFmtHideColumns
    84  
    85  	// ExprFmtHideAll shows only the basic structure of the expression.
    86  	// Note: this flag should be used judiciously, as its meaning changes whenever
    87  	// we add more flags.
    88  	ExprFmtHideAll ExprFmtFlags = (1 << iota) - 1
    89  )
    90  
    91  // HasFlags tests whether the given flags are all set.
    92  func (f ExprFmtFlags) HasFlags(subset ExprFmtFlags) bool {
    93  	return f&subset == subset
    94  }
    95  
    96  // FormatExpr returns a string representation of the given expression, formatted
    97  // according to the specified flags.
    98  func FormatExpr(e opt.Expr, flags ExprFmtFlags, mem *Memo, catalog cat.Catalog) string {
    99  	if catalog == nil {
   100  		// Automatically hide qualifications if we have no catalog.
   101  		flags |= ExprFmtHideQualifications
   102  	}
   103  	f := MakeExprFmtCtx(flags, mem, catalog)
   104  	f.FormatExpr(e)
   105  	return f.Buffer.String()
   106  }
   107  
   108  // ExprFmtCtx is passed as context to expression formatting functions, which
   109  // need to know the formatting flags and memo in order to format. In addition,
   110  // a reusable bytes buffer avoids unnecessary allocations.
   111  type ExprFmtCtx struct {
   112  	Buffer *bytes.Buffer
   113  
   114  	// Flags controls how the expression is formatted.
   115  	Flags ExprFmtFlags
   116  
   117  	// Memo must contain any expression that is formatted.
   118  	Memo *Memo
   119  
   120  	// Catalog must be set unless the ExprFmtHideQualifications flag is set.
   121  	Catalog cat.Catalog
   122  
   123  	// nameGen is used to generate a unique name for each relational
   124  	// subexpression when Memo.saveTablesPrefix is non-empty. These names
   125  	// correspond to the tables that would be saved if the query were run
   126  	// with the session variable `save_tables_prefix` set to the same value.
   127  	nameGen *ExprNameGenerator
   128  }
   129  
   130  // MakeExprFmtCtx creates an expression formatting context from a new buffer.
   131  func MakeExprFmtCtx(flags ExprFmtFlags, mem *Memo, catalog cat.Catalog) ExprFmtCtx {
   132  	return MakeExprFmtCtxBuffer(&bytes.Buffer{}, flags, mem, catalog)
   133  }
   134  
   135  // MakeExprFmtCtxBuffer creates an expression formatting context from an
   136  // existing buffer.
   137  func MakeExprFmtCtxBuffer(
   138  	buf *bytes.Buffer, flags ExprFmtFlags, mem *Memo, catalog cat.Catalog,
   139  ) ExprFmtCtx {
   140  	var nameGen *ExprNameGenerator
   141  	if mem != nil && mem.saveTablesPrefix != "" {
   142  		nameGen = NewExprNameGenerator(mem.saveTablesPrefix)
   143  	}
   144  	return ExprFmtCtx{Buffer: buf, Flags: flags, Memo: mem, Catalog: catalog, nameGen: nameGen}
   145  }
   146  
   147  // HasFlags tests whether the given flags are all set.
   148  func (f *ExprFmtCtx) HasFlags(subset ExprFmtFlags) bool {
   149  	return f.Flags.HasFlags(subset)
   150  }
   151  
   152  // FormatExpr constructs a treeprinter view of the given expression for testing
   153  // and debugging, according to the flags in this context.
   154  func (f *ExprFmtCtx) FormatExpr(e opt.Expr) {
   155  	tp := treeprinter.New()
   156  	f.formatExpr(e, tp)
   157  	f.Buffer.Reset()
   158  	f.Buffer.WriteString(tp.String())
   159  }
   160  
   161  func (f *ExprFmtCtx) space() {
   162  	f.Buffer.WriteByte(' ')
   163  }
   164  
   165  func (f *ExprFmtCtx) formatExpr(e opt.Expr, tp treeprinter.Node) {
   166  	scalar, ok := e.(opt.ScalarExpr)
   167  	if ok {
   168  		f.formatScalar(scalar, tp)
   169  	} else {
   170  		f.formatRelational(e.(RelExpr), tp)
   171  	}
   172  }
   173  
   174  func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
   175  	md := f.Memo.Metadata()
   176  	relational := e.Relational()
   177  	required := e.RequiredPhysical()
   178  	if required == nil {
   179  		// required can be nil before optimization has taken place.
   180  		required = physical.MinRequired
   181  	}
   182  
   183  	// Special cases for merge-join and lookup-join: we want the type of the join
   184  	// to show up first.
   185  	f.Buffer.Reset()
   186  	switch t := e.(type) {
   187  	case *MergeJoinExpr:
   188  		fmt.Fprintf(f.Buffer, "%v (merge)", t.JoinType)
   189  
   190  	case *LookupJoinExpr:
   191  		fmt.Fprintf(f.Buffer, "%v (lookup", t.JoinType)
   192  		FormatPrivate(f, e.Private(), required)
   193  		f.Buffer.WriteByte(')')
   194  
   195  	case *GeoLookupJoinExpr:
   196  		fmt.Fprintf(f.Buffer, "%v (geo-lookup", t.JoinType)
   197  		FormatPrivate(f, e.Private(), required)
   198  		f.Buffer.WriteByte(')')
   199  
   200  	case *ZigzagJoinExpr:
   201  		fmt.Fprintf(f.Buffer, "%v (zigzag", opt.InnerJoinOp)
   202  		FormatPrivate(f, e.Private(), required)
   203  		f.Buffer.WriteByte(')')
   204  
   205  	case *ScanExpr, *IndexJoinExpr, *ShowTraceForSessionExpr,
   206  		*InsertExpr, *UpdateExpr, *UpsertExpr, *DeleteExpr, *SequenceSelectExpr,
   207  		*WindowExpr, *OpaqueRelExpr, *OpaqueMutationExpr, *OpaqueDDLExpr,
   208  		*AlterTableSplitExpr, *AlterTableUnsplitExpr, *AlterTableUnsplitAllExpr,
   209  		*AlterTableRelocateExpr, *ControlJobsExpr, *CancelQueriesExpr,
   210  		*CancelSessionsExpr, *CreateViewExpr, *ExportExpr:
   211  		fmt.Fprintf(f.Buffer, "%v", e.Op())
   212  		FormatPrivate(f, e.Private(), required)
   213  
   214  	case *SortExpr:
   215  		if t.InputOrdering.Any() {
   216  			fmt.Fprintf(f.Buffer, "%v", e.Op())
   217  		} else {
   218  			fmt.Fprintf(f.Buffer, "%v (segmented)", e.Op())
   219  		}
   220  
   221  	case *WithExpr:
   222  		fmt.Fprintf(f.Buffer, "%v &%d", e.Op(), t.ID)
   223  		if t.Name != "" {
   224  			fmt.Fprintf(f.Buffer, " (%s)", t.Name)
   225  		}
   226  
   227  	case *WithScanExpr:
   228  		fmt.Fprintf(f.Buffer, "%v &%d", e.Op(), t.With)
   229  		if t.Name != "" {
   230  			fmt.Fprintf(f.Buffer, " (%s)", t.Name)
   231  		}
   232  
   233  	default:
   234  		fmt.Fprintf(f.Buffer, "%v", e.Op())
   235  		if opt.IsJoinNonApplyOp(t) {
   236  			// All join ops that weren't handled above execute as a hash join.
   237  			if leftEqCols, _ := ExtractJoinEqualityColumns(
   238  				e.Child(0).(RelExpr).Relational().OutputCols,
   239  				e.Child(1).(RelExpr).Relational().OutputCols,
   240  				*e.Child(2).(*FiltersExpr),
   241  			); len(leftEqCols) == 0 {
   242  				// The case where there are no equality columns is executed as a
   243  				// degenerate case of hash join; let's be explicit about that.
   244  				f.Buffer.WriteString(" (cross)")
   245  			} else {
   246  				f.Buffer.WriteString(" (hash)")
   247  			}
   248  		}
   249  	}
   250  
   251  	tp = tp.Child(f.Buffer.String())
   252  
   253  	if f.nameGen != nil {
   254  		name := f.nameGen.GenerateName(e.Op())
   255  		tp.Childf("save-table-name: %s", name)
   256  	}
   257  
   258  	var colList opt.ColList
   259  	// Special handling to improve the columns display for certain ops.
   260  	switch t := e.(type) {
   261  	case *ProjectExpr:
   262  		// We want the synthesized column IDs to map 1-to-1 to the projections,
   263  		// and the pass-through columns at the end.
   264  
   265  		// Get the list of columns from the ProjectionsOp, which has the natural
   266  		// order.
   267  		for i := range t.Projections {
   268  			colList = append(colList, t.Projections[i].Col)
   269  		}
   270  
   271  		// Add pass-through columns.
   272  		t.Passthrough.ForEach(func(i opt.ColumnID) {
   273  			colList = append(colList, i)
   274  		})
   275  
   276  	case *ValuesExpr:
   277  		colList = t.Cols
   278  
   279  	case *UnionExpr, *IntersectExpr, *ExceptExpr,
   280  		*UnionAllExpr, *IntersectAllExpr, *ExceptAllExpr:
   281  		colList = e.Private().(*SetPrivate).OutCols
   282  
   283  	default:
   284  		// Fall back to writing output columns in column id order.
   285  		colList = opt.ColSetToList(e.Relational().OutputCols)
   286  	}
   287  
   288  	f.formatColumns(e, tp, colList, required.Presentation)
   289  
   290  	switch t := e.(type) {
   291  	// Special-case handling for GroupBy private; print grouping columns
   292  	// and internal ordering in addition to full set of columns.
   293  	case *GroupByExpr, *ScalarGroupByExpr, *DistinctOnExpr, *EnsureDistinctOnExpr,
   294  		*UpsertDistinctOnExpr, *EnsureUpsertDistinctOnExpr:
   295  		private := e.Private().(*GroupingPrivate)
   296  		if !f.HasFlags(ExprFmtHideColumns) && !private.GroupingCols.Empty() {
   297  			f.formatColList(e, tp, "grouping columns:", opt.ColSetToList(private.GroupingCols))
   298  		}
   299  		if !f.HasFlags(ExprFmtHidePhysProps) && !private.Ordering.Any() {
   300  			tp.Childf("internal-ordering: %s", private.Ordering)
   301  		}
   302  		if !f.HasFlags(ExprFmtHideMiscProps) && private.ErrorOnDup != "" {
   303  			tp.Childf("error: \"%s\"", private.ErrorOnDup)
   304  		}
   305  
   306  	case *LimitExpr:
   307  		if !f.HasFlags(ExprFmtHidePhysProps) && !t.Ordering.Any() {
   308  			tp.Childf("internal-ordering: %s", t.Ordering)
   309  		}
   310  
   311  	case *OffsetExpr:
   312  		if !f.HasFlags(ExprFmtHidePhysProps) && !t.Ordering.Any() {
   313  			tp.Childf("internal-ordering: %s", t.Ordering)
   314  		}
   315  
   316  	case *Max1RowExpr:
   317  		if !f.HasFlags(ExprFmtHideMiscProps) {
   318  			tp.Childf("error: \"%s\"", t.ErrorText)
   319  		}
   320  
   321  	// Special-case handling for set operators to show the left and right
   322  	// input columns that correspond to the output columns.
   323  	case *UnionExpr, *IntersectExpr, *ExceptExpr,
   324  		*UnionAllExpr, *IntersectAllExpr, *ExceptAllExpr:
   325  		if !f.HasFlags(ExprFmtHideColumns) {
   326  			private := e.Private().(*SetPrivate)
   327  			f.formatColList(e, tp, "left columns:", private.LeftCols)
   328  			f.formatColList(e, tp, "right columns:", private.RightCols)
   329  		}
   330  
   331  	case *ScanExpr:
   332  		if t.IsCanonical() {
   333  			// For the canonical scan, show the expressions attached to the TableMeta.
   334  			tab := md.TableMeta(t.Table)
   335  			if tab.Constraints != nil {
   336  				c := tp.Childf("check constraint expressions")
   337  				for i := 0; i < tab.Constraints.ChildCount(); i++ {
   338  					f.formatExpr(tab.Constraints.Child(i), c)
   339  				}
   340  			}
   341  			if len(tab.ComputedCols) > 0 {
   342  				c := tp.Childf("computed column expressions")
   343  				cols := make(opt.ColList, 0, len(tab.ComputedCols))
   344  				for col := range tab.ComputedCols {
   345  					cols = append(cols, col)
   346  				}
   347  				sort.Slice(cols, func(i, j int) bool {
   348  					return cols[i] < cols[j]
   349  				})
   350  				for _, col := range cols {
   351  					f.Buffer.Reset()
   352  					f.formatExpr(tab.ComputedCols[col], c.Child(f.ColumnString(col)))
   353  				}
   354  			}
   355  		}
   356  		if c := t.Constraint; c != nil {
   357  			if c.IsContradiction() {
   358  				tp.Childf("constraint: contradiction")
   359  			} else if c.Spans.Count() == 1 {
   360  				tp.Childf("constraint: %s: %s", c.Columns.String(), c.Spans.Get(0).String())
   361  			} else {
   362  				n := tp.Childf("constraint: %s", c.Columns.String())
   363  				for i := 0; i < c.Spans.Count(); i++ {
   364  					n.Child(c.Spans.Get(i).String())
   365  				}
   366  			}
   367  		}
   368  		if t.HardLimit.IsSet() {
   369  			tp.Childf("limit: %s", t.HardLimit)
   370  		}
   371  		if !t.Flags.Empty() {
   372  			if t.Flags.NoIndexJoin {
   373  				tp.Childf("flags: no-index-join")
   374  			} else if t.Flags.ForceIndex {
   375  				idx := md.Table(t.Table).Index(t.Flags.Index)
   376  				dir := ""
   377  				switch t.Flags.Direction {
   378  				case tree.DefaultDirection:
   379  				case tree.Ascending:
   380  					dir = ",fwd"
   381  				case tree.Descending:
   382  					dir = ",rev"
   383  				}
   384  				tp.Childf("flags: force-index=%s%s", idx.Name(), dir)
   385  			}
   386  		}
   387  		if t.Locking != nil {
   388  			strength := ""
   389  			switch t.Locking.Strength {
   390  			case tree.ForNone:
   391  			case tree.ForKeyShare:
   392  				strength = "for-key-share"
   393  			case tree.ForShare:
   394  				strength = "for-share"
   395  			case tree.ForNoKeyUpdate:
   396  				strength = "for-no-key-update"
   397  			case tree.ForUpdate:
   398  				strength = "for-update"
   399  			default:
   400  				panic(errors.AssertionFailedf("unexpected strength"))
   401  			}
   402  			wait := ""
   403  			switch t.Locking.WaitPolicy {
   404  			case tree.LockWaitBlock:
   405  			case tree.LockWaitSkip:
   406  				wait = ",skip-locked"
   407  			case tree.LockWaitError:
   408  				wait = ",nowait"
   409  			default:
   410  				panic(errors.AssertionFailedf("unexpected wait policy"))
   411  			}
   412  			tp.Childf("locking: %s%s", strength, wait)
   413  		}
   414  
   415  	case *LookupJoinExpr:
   416  		if !t.Flags.Empty() {
   417  			tp.Childf("flags: %s", t.Flags.String())
   418  		}
   419  		idxCols := make(opt.ColList, len(t.KeyCols))
   420  		idx := md.Table(t.Table).Index(t.Index)
   421  		for i := range idxCols {
   422  			idxCols[i] = t.Table.ColumnID(idx.Column(i).Ordinal)
   423  		}
   424  		if !f.HasFlags(ExprFmtHideColumns) {
   425  			tp.Childf("key columns: %v = %v", t.KeyCols, idxCols)
   426  		}
   427  		if t.LookupColsAreTableKey {
   428  			tp.Childf("lookup columns are key")
   429  		}
   430  
   431  	case *GeoLookupJoinExpr:
   432  		if !t.Flags.Empty() {
   433  			tp.Childf("flags: %s", t.Flags.String())
   434  		}
   435  		tp.Childf("geo-relationship: %v", t.GeoRelationshipType)
   436  
   437  	case *ZigzagJoinExpr:
   438  		if !f.HasFlags(ExprFmtHideColumns) {
   439  			tp.Childf("eq columns: %v = %v", t.LeftEqCols, t.RightEqCols)
   440  			leftVals := make([]tree.Datum, len(t.LeftFixedCols))
   441  			rightVals := make([]tree.Datum, len(t.RightFixedCols))
   442  			// FixedVals is always going to be a ScalarListExpr, containing tuples,
   443  			// containing one ScalarListExpr, containing ConstExprs.
   444  			for i := range t.LeftFixedCols {
   445  				leftVals[i] = ExtractConstDatum(t.FixedVals[0].Child(0).Child(i))
   446  			}
   447  			for i := range t.RightFixedCols {
   448  				rightVals[i] = ExtractConstDatum(t.FixedVals[1].Child(0).Child(i))
   449  			}
   450  			tp.Childf("left fixed columns: %v = %v", t.LeftFixedCols, leftVals)
   451  			tp.Childf("right fixed columns: %v = %v", t.RightFixedCols, rightVals)
   452  		}
   453  
   454  	case *MergeJoinExpr:
   455  		if !t.Flags.Empty() {
   456  			tp.Childf("flags: %s", t.Flags.String())
   457  		}
   458  		if !f.HasFlags(ExprFmtHidePhysProps) {
   459  			tp.Childf("left ordering: %s", t.LeftEq)
   460  			tp.Childf("right ordering: %s", t.RightEq)
   461  		}
   462  
   463  	case *InsertExpr:
   464  		if !f.HasFlags(ExprFmtHideColumns) {
   465  			if len(colList) == 0 {
   466  				tp.Child("columns: <none>")
   467  			}
   468  			f.formatMutationCols(e, tp, "insert-mapping:", t.InsertCols, t.Table)
   469  			f.formatColList(e, tp, "check columns:", t.CheckCols)
   470  			f.formatColList(e, tp, "partial index pred columns:", t.IndexPredicateCols)
   471  			f.formatMutationCommon(tp, &t.MutationPrivate)
   472  		}
   473  
   474  	case *UpdateExpr:
   475  		if !f.HasFlags(ExprFmtHideColumns) {
   476  			if len(colList) == 0 {
   477  				tp.Child("columns: <none>")
   478  			}
   479  			f.formatColList(e, tp, "fetch columns:", t.FetchCols)
   480  			f.formatMutationCols(e, tp, "update-mapping:", t.UpdateCols, t.Table)
   481  			f.formatColList(e, tp, "check columns:", t.CheckCols)
   482  			f.formatMutationCommon(tp, &t.MutationPrivate)
   483  		}
   484  
   485  	case *UpsertExpr:
   486  		if !f.HasFlags(ExprFmtHideColumns) {
   487  			if len(colList) == 0 {
   488  				tp.Child("columns: <none>")
   489  			}
   490  			if t.CanaryCol != 0 {
   491  				tp.Childf("canary column: %d", t.CanaryCol)
   492  				f.formatColList(e, tp, "fetch columns:", t.FetchCols)
   493  				f.formatMutationCols(e, tp, "insert-mapping:", t.InsertCols, t.Table)
   494  				f.formatMutationCols(e, tp, "update-mapping:", t.UpdateCols, t.Table)
   495  				f.formatMutationCols(e, tp, "return-mapping:", t.ReturnCols, t.Table)
   496  			} else {
   497  				f.formatMutationCols(e, tp, "upsert-mapping:", t.InsertCols, t.Table)
   498  			}
   499  			f.formatColList(e, tp, "check columns:", t.CheckCols)
   500  			f.formatMutationCommon(tp, &t.MutationPrivate)
   501  		}
   502  
   503  	case *DeleteExpr:
   504  		if !f.HasFlags(ExprFmtHideColumns) {
   505  			if len(colList) == 0 {
   506  				tp.Child("columns: <none>")
   507  			}
   508  			f.formatColList(e, tp, "fetch columns:", t.FetchCols)
   509  			f.formatMutationCommon(tp, &t.MutationPrivate)
   510  		}
   511  
   512  	case *WithExpr:
   513  		if t.Mtr.Set {
   514  			if t.Mtr.Materialize {
   515  				tp.Child("materialized")
   516  			} else {
   517  				tp.Child("not-materialized")
   518  			}
   519  		}
   520  
   521  	case *WithScanExpr:
   522  		if !f.HasFlags(ExprFmtHideColumns) {
   523  			child := tp.Child("mapping:")
   524  			for i := range t.InCols {
   525  				f.Buffer.Reset()
   526  				f.space()
   527  				f.formatCol("" /* label */, t.InCols[i], opt.ColSet{} /* notNullCols */)
   528  				f.Buffer.WriteString(" => ")
   529  				f.formatCol("" /* label */, t.OutCols[i], opt.ColSet{} /* notNullCols */)
   530  				child.Child(f.Buffer.String())
   531  			}
   532  		}
   533  
   534  	case *CreateTableExpr:
   535  		tp.Child(t.Syntax.String())
   536  
   537  	case *CreateViewExpr:
   538  		tp.Child(t.ViewQuery)
   539  
   540  		f.Buffer.Reset()
   541  		f.Buffer.WriteString("columns:")
   542  		for _, col := range t.Columns {
   543  			f.space()
   544  			f.formatCol(col.Alias, col.ID, opt.ColSet{} /* notNullCols */)
   545  		}
   546  		tp.Child(f.Buffer.String())
   547  
   548  		n := tp.Child("dependencies")
   549  		for _, dep := range t.Deps {
   550  			f.Buffer.Reset()
   551  			name := dep.DataSource.Name()
   552  			f.Buffer.WriteString(name.String())
   553  			if dep.SpecificIndex {
   554  				fmt.Fprintf(f.Buffer, "@%s", dep.DataSource.(cat.Table).Index(dep.Index).Name())
   555  			}
   556  			if !dep.ColumnOrdinals.Empty() {
   557  				fmt.Fprintf(f.Buffer, " [columns: %s]", dep.ColumnOrdinals)
   558  			}
   559  			n.Child(f.Buffer.String())
   560  		}
   561  
   562  	case *ExportExpr:
   563  		tp.Childf("format: %s", t.FileFormat)
   564  
   565  	case *ExplainExpr:
   566  		// ExplainPlan is the default, don't show it.
   567  		m := ""
   568  		if t.Options.Mode != tree.ExplainPlan {
   569  			m = strings.ToLower(t.Options.Mode.String())
   570  		}
   571  		if t.Options.Flags[tree.ExplainFlagVerbose] {
   572  			if m != "" {
   573  				m += ", "
   574  			}
   575  			m += "verbose"
   576  		}
   577  		if m != "" {
   578  			tp.Childf("mode: %s", m)
   579  		}
   580  
   581  	case *RecursiveCTEExpr:
   582  		if !f.HasFlags(ExprFmtHideColumns) {
   583  			tp.Childf("working table binding: &%d", t.WithID)
   584  			f.formatColList(e, tp, "initial columns:", t.InitialCols)
   585  			f.formatColList(e, tp, "recursive columns:", t.RecursiveCols)
   586  		}
   587  
   588  	default:
   589  		if opt.IsJoinOp(t) {
   590  			p := t.Private().(*JoinPrivate)
   591  			if !p.Flags.Empty() {
   592  				tp.Childf("flags: %s", p.Flags.String())
   593  			}
   594  		}
   595  	}
   596  
   597  	if !f.HasFlags(ExprFmtHideMiscProps) {
   598  		if !relational.OuterCols.Empty() {
   599  			tp.Childf("outer: %s", relational.OuterCols.String())
   600  		}
   601  		if relational.Cardinality != props.AnyCardinality {
   602  			// Suppress cardinality for Scan ops if it's redundant with Limit field.
   603  			if scan, ok := e.(*ScanExpr); !ok || !scan.HardLimit.IsSet() {
   604  				tp.Childf("cardinality: %s", relational.Cardinality)
   605  			}
   606  		}
   607  
   608  		f.Buffer.Reset()
   609  		writeFlag := func(name string) {
   610  			if f.Buffer.Len() != 0 {
   611  				f.Buffer.WriteString(", ")
   612  			}
   613  			f.Buffer.WriteString(name)
   614  		}
   615  
   616  		if !relational.VolatilitySet.IsLeakProof() {
   617  			writeFlag(relational.VolatilitySet.String())
   618  		}
   619  		if relational.CanHaveSideEffects {
   620  			writeFlag("side-effects")
   621  		}
   622  		if relational.CanMutate {
   623  			writeFlag("mutations")
   624  		}
   625  		if relational.HasPlaceholder {
   626  			writeFlag("has-placeholder")
   627  		}
   628  
   629  		if f.Buffer.Len() != 0 {
   630  			tp.Child(f.Buffer.String())
   631  		}
   632  	}
   633  
   634  	if !f.HasFlags(ExprFmtHideStats) {
   635  		tp.Childf("stats: %s", &relational.Stats)
   636  	}
   637  
   638  	if !f.HasFlags(ExprFmtHideCost) {
   639  		cost := e.Cost()
   640  		if cost != 0 {
   641  			tp.Childf("cost: %.9g", cost)
   642  		}
   643  	}
   644  
   645  	// Format functional dependencies.
   646  	if !f.HasFlags(ExprFmtHideFuncDeps) {
   647  		// Show the key separately from the rest of the FDs.
   648  		if key, ok := relational.FuncDeps.StrictKey(); ok {
   649  			tp.Childf("key: %s", key)
   650  		} else if key, ok := relational.FuncDeps.LaxKey(); ok {
   651  			tp.Childf("lax-key: %s", key)
   652  		}
   653  		if fdStr := relational.FuncDeps.StringOnlyFDs(); fdStr != "" {
   654  			tp.Childf("fd: %s", fdStr)
   655  		}
   656  	}
   657  
   658  	if !f.HasFlags(ExprFmtHidePhysProps) {
   659  		if !required.Ordering.Any() {
   660  			if f.HasFlags(ExprFmtHideMiscProps) {
   661  				tp.Childf("ordering: %s", required.Ordering.String())
   662  			} else {
   663  				// Show the provided ordering as well, unless it's exactly the same.
   664  				provided := e.ProvidedPhysical().Ordering
   665  				reqStr := required.Ordering.String()
   666  				provStr := provided.String()
   667  				if provStr == reqStr {
   668  					tp.Childf("ordering: %s", required.Ordering.String())
   669  				} else {
   670  					tp.Childf("ordering: %s [actual: %s]", required.Ordering.String(), provided.String())
   671  				}
   672  			}
   673  		}
   674  		if required.LimitHint != 0 {
   675  			tp.Childf("limit hint: %.2f", required.LimitHint)
   676  		}
   677  	}
   678  
   679  	if !f.HasFlags(ExprFmtHideRuleProps) {
   680  		r := &relational.Rule
   681  		if !r.PruneCols.Empty() {
   682  			tp.Childf("prune: %s", r.PruneCols.String())
   683  		}
   684  		if !r.RejectNullCols.Empty() {
   685  			tp.Childf("reject-nulls: %s", r.RejectNullCols.String())
   686  		}
   687  		if len(r.InterestingOrderings) > 0 {
   688  			tp.Childf("interesting orderings: %s", r.InterestingOrderings.String())
   689  		}
   690  		if r.JoinSize > 1 {
   691  			tp.Childf("join-size: %d", r.JoinSize)
   692  		}
   693  		switch e.Op() {
   694  		case opt.InnerJoinOp, opt.LeftJoinOp, opt.FullJoinOp:
   695  			if s := r.MultiplicityProps.String(); (r.Available&props.MultiplicityProps) != 0 && s != "" {
   696  				tp.Childf("multiplicity: %s", s)
   697  			}
   698  		}
   699  		if withUses := relational.Shared.Rule.WithUses; len(withUses) > 0 {
   700  			n := tp.Childf("cte-uses")
   701  			ids := make([]opt.WithID, 0, len(withUses))
   702  			for id := range withUses {
   703  				ids = append(ids, id)
   704  			}
   705  			sort.Slice(ids, func(i, j int) bool {
   706  				return ids[i] < ids[j]
   707  			})
   708  			for _, id := range ids {
   709  				info := withUses[id]
   710  				n.Childf("&%d: count=%d used-columns=%s", id, info.Count, info.UsedCols)
   711  			}
   712  		}
   713  	}
   714  
   715  	switch t := e.(type) {
   716  	case *CreateTableExpr:
   717  		// Do not print dummy input expression if there was no AS clause.
   718  		if !t.Syntax.As() {
   719  			return
   720  		}
   721  	}
   722  
   723  	for i, n := 0, e.ChildCount(); i < n; i++ {
   724  		f.formatExpr(e.Child(i), tp)
   725  	}
   726  }
   727  
   728  func (f *ExprFmtCtx) formatScalar(scalar opt.ScalarExpr, tp treeprinter.Node) {
   729  	switch scalar.Op() {
   730  	case opt.ProjectionsOp, opt.AggregationsOp, opt.FKChecksOp, opt.KVOptionsOp:
   731  		// Omit empty lists (except filters).
   732  		if scalar.ChildCount() == 0 {
   733  			return
   734  		}
   735  
   736  	case opt.FiltersOp:
   737  		// Show empty Filters expression as "filters (true)".
   738  		if scalar.ChildCount() == 0 {
   739  			tp.Child("filters (true)")
   740  			return
   741  		}
   742  
   743  	case opt.IfErrOp:
   744  		f.Buffer.Reset()
   745  		fmt.Fprintf(f.Buffer, "%v", scalar.Op())
   746  		f.FormatScalarProps(scalar)
   747  
   748  		tp = tp.Child(f.Buffer.String())
   749  
   750  		f.formatExpr(scalar.Child(0), tp)
   751  		if scalar.Child(1).ChildCount() > 0 {
   752  			f.formatExpr(scalar.Child(1), tp.Child("else"))
   753  		}
   754  		if scalar.Child(2).ChildCount() > 0 {
   755  			f.formatExpr(scalar.Child(2), tp.Child("err-code"))
   756  		}
   757  
   758  		return
   759  
   760  	case opt.AggFilterOp:
   761  		f.Buffer.Reset()
   762  		fmt.Fprintf(f.Buffer, "%v", scalar.Op())
   763  		f.FormatScalarProps(scalar)
   764  		tp = tp.Child(f.Buffer.String())
   765  
   766  		f.formatExpr(scalar.Child(0), tp)
   767  		f.formatExpr(scalar.Child(1), tp.Child("filter"))
   768  
   769  		return
   770  
   771  	case opt.ScalarListOp:
   772  		// Don't show scalar-list as a separate node, as it's redundant with its
   773  		// parent.
   774  		for i, n := 0, scalar.ChildCount(); i < n; i++ {
   775  			f.formatExpr(scalar.Child(i), tp)
   776  		}
   777  		return
   778  	}
   779  
   780  	// Omit various list items from the output, but show some of their properties
   781  	// along with the properties of their child.
   782  	var scalarProps []string
   783  	switch scalar.Op() {
   784  	case opt.FiltersItemOp, opt.ProjectionsItemOp, opt.AggregationsItemOp,
   785  		opt.ZipItemOp, opt.WindowsItemOp:
   786  
   787  		emitProp := func(format string, args ...interface{}) {
   788  			scalarProps = append(scalarProps, fmt.Sprintf(format, args...))
   789  		}
   790  		switch item := scalar.(type) {
   791  		case *ProjectionsItem:
   792  			if !f.HasFlags(ExprFmtHideColumns) {
   793  				emitProp("as=%s", f.ColumnString(item.Col))
   794  			}
   795  
   796  		case *AggregationsItem:
   797  			if !f.HasFlags(ExprFmtHideColumns) {
   798  				emitProp("as=%s", f.ColumnString(item.Col))
   799  			}
   800  
   801  		case *ZipItem:
   802  			// TODO(radu): show the item.Cols
   803  
   804  		case *WindowsItem:
   805  			if !f.HasFlags(ExprFmtHideColumns) {
   806  				emitProp("as=%s", f.ColumnString(item.Col))
   807  			}
   808  			// Only show the frame if it differs from the default.
   809  			def := WindowFrame{
   810  				Mode:           tree.RANGE,
   811  				StartBoundType: tree.UnboundedPreceding,
   812  				EndBoundType:   tree.CurrentRow,
   813  				FrameExclusion: tree.NoExclusion,
   814  			}
   815  			if item.Frame != def {
   816  				emitProp("frame=%q", item.Frame.String())
   817  			}
   818  		}
   819  
   820  		scalarProps = append(scalarProps, f.scalarPropsStrings(scalar)...)
   821  		scalar = scalar.Child(0).(opt.ScalarExpr)
   822  
   823  	default:
   824  		scalarProps = f.scalarPropsStrings(scalar)
   825  	}
   826  
   827  	var intercepted bool
   828  	f.Buffer.Reset()
   829  	if f.HasFlags(ExprFmtHideScalars) && ScalarFmtInterceptor != nil {
   830  		if str := ScalarFmtInterceptor(f, scalar); str != "" {
   831  			f.Buffer.WriteString(str)
   832  			intercepted = true
   833  		}
   834  	}
   835  	if !intercepted {
   836  		fmt.Fprintf(f.Buffer, "%v", scalar.Op())
   837  		f.formatScalarPrivate(scalar)
   838  	}
   839  	if len(scalarProps) != 0 {
   840  		f.Buffer.WriteString(" [")
   841  		f.Buffer.WriteString(strings.Join(scalarProps, ", "))
   842  		f.Buffer.WriteByte(']')
   843  	}
   844  	tp = tp.Child(f.Buffer.String())
   845  
   846  	if !intercepted {
   847  		for i, n := 0, scalar.ChildCount(); i < n; i++ {
   848  			f.formatExpr(scalar.Child(i), tp)
   849  		}
   850  	}
   851  }
   852  
   853  // scalarPropsStrings returns a slice of strings, each describing a property;
   854  // for example:
   855  //   {"type=bool", "outer=(1)", "constraints=(/1: [/1 - /1]; tight)"}
   856  func (f *ExprFmtCtx) scalarPropsStrings(scalar opt.ScalarExpr) []string {
   857  	typ := scalar.DataType()
   858  	if typ == nil {
   859  		if scalar.Op() == opt.FKChecksItemOp || scalar.Op() == opt.KVOptionsItemOp {
   860  			// These are not true scalars and have no properties.
   861  			return nil
   862  		}
   863  		// Don't panic if scalar properties don't yet exist when printing
   864  		// expression.
   865  		return []string{"type=undefined"}
   866  	}
   867  
   868  	var res []string
   869  	emitProp := func(format string, args ...interface{}) {
   870  		res = append(res, fmt.Sprintf(format, args...))
   871  	}
   872  	if !f.HasFlags(ExprFmtHideTypes) && typ.Family() != types.AnyFamily {
   873  		emitProp("type=%s", typ)
   874  	}
   875  	if propsExpr, ok := scalar.(ScalarPropsExpr); ok {
   876  		scalarProps := propsExpr.ScalarProps()
   877  		if !f.HasFlags(ExprFmtHideMiscProps) {
   878  			if !scalarProps.OuterCols.Empty() {
   879  				emitProp("outer=%s", scalarProps.OuterCols)
   880  			}
   881  			if !scalarProps.VolatilitySet.IsLeakProof() {
   882  				emitProp(scalarProps.VolatilitySet.String())
   883  			}
   884  			if scalarProps.CanHaveSideEffects {
   885  				emitProp("side-effects")
   886  			}
   887  			if scalarProps.HasCorrelatedSubquery {
   888  				emitProp("correlated-subquery")
   889  			} else if scalarProps.HasSubquery {
   890  				emitProp("subquery")
   891  			}
   892  		}
   893  
   894  		if !f.HasFlags(ExprFmtHideConstraints) {
   895  			if scalarProps.Constraints != nil && !scalarProps.Constraints.IsUnconstrained() {
   896  				var tight string
   897  				if scalarProps.TightConstraints {
   898  					tight = "; tight"
   899  				}
   900  				emitProp("constraints=(%s%s)", scalarProps.Constraints, tight)
   901  			}
   902  		}
   903  
   904  		if !f.HasFlags(ExprFmtHideFuncDeps) && !scalarProps.FuncDeps.Empty() {
   905  			emitProp("fd=%s", scalarProps.FuncDeps)
   906  		}
   907  	}
   908  	return res
   909  }
   910  
   911  // FormatScalarProps writes out a string representation of the scalar
   912  // properties (with a preceding space); for example:
   913  //  " [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)]"
   914  func (f *ExprFmtCtx) FormatScalarProps(scalar opt.ScalarExpr) {
   915  	props := f.scalarPropsStrings(scalar)
   916  	if len(props) != 0 {
   917  		f.Buffer.WriteString(" [")
   918  		f.Buffer.WriteString(strings.Join(props, ", "))
   919  		f.Buffer.WriteByte(']')
   920  	}
   921  }
   922  
   923  func (f *ExprFmtCtx) formatScalarPrivate(scalar opt.ScalarExpr) {
   924  	var private interface{}
   925  	switch t := scalar.(type) {
   926  	case *NullExpr, *TupleExpr, *CollateExpr:
   927  		// Private is redundant with logical type property.
   928  		private = nil
   929  
   930  	case *AnyExpr:
   931  		// We don't want to show the OriginalExpr; just show Cmp.
   932  		private = t.Cmp
   933  
   934  	case *ArrayFlattenExpr:
   935  		if t.Input.Relational().OutputCols.Len() != 1 {
   936  			fmt.Fprintf(f.Buffer, " col=%v", t.RequestedCol)
   937  		}
   938  
   939  	case *SubqueryExpr, *ExistsExpr:
   940  		// We don't want to show the OriginalExpr.
   941  		private = nil
   942  
   943  	case *CastExpr:
   944  		private = t.Typ.SQLString()
   945  
   946  	case *KVOptionsItem:
   947  		fmt.Fprintf(f.Buffer, " %s", t.Key)
   948  
   949  	case *FKChecksItem:
   950  		origin := f.Memo.metadata.TableMeta(t.OriginTable)
   951  		referenced := f.Memo.metadata.TableMeta(t.ReferencedTable)
   952  		var fk cat.ForeignKeyConstraint
   953  		if t.FKOutbound {
   954  			fk = origin.Table.OutboundForeignKey(t.FKOrdinal)
   955  		} else {
   956  			fk = referenced.Table.InboundForeignKey(t.FKOrdinal)
   957  		}
   958  		// Print the FK as:
   959  		//   child(a,b) -> parent(a,b)
   960  		//
   961  		// TODO(radu): maybe flip these if we are deleting from the parent (i.e.
   962  		// FKOutbound=false)?
   963  		fmt.Fprintf(f.Buffer, ": %s(", origin.Alias.ObjectName)
   964  		for i := 0; i < fk.ColumnCount(); i++ {
   965  			if i > 0 {
   966  				f.Buffer.WriteByte(',')
   967  			}
   968  			col := origin.Table.Column(fk.OriginColumnOrdinal(origin.Table, i))
   969  			f.Buffer.WriteString(string(col.ColName()))
   970  		}
   971  		fmt.Fprintf(f.Buffer, ") -> %s(", referenced.Alias.ObjectName)
   972  		for i := 0; i < fk.ColumnCount(); i++ {
   973  			if i > 0 {
   974  				f.Buffer.WriteByte(',')
   975  			}
   976  			col := referenced.Table.Column(fk.ReferencedColumnOrdinal(referenced.Table, i))
   977  			f.Buffer.WriteString(string(col.ColName()))
   978  		}
   979  		f.Buffer.WriteByte(')')
   980  
   981  	default:
   982  		private = scalar.Private()
   983  	}
   984  
   985  	if private != nil {
   986  		f.Buffer.WriteRune(':')
   987  		FormatPrivate(f, private, &physical.Required{})
   988  	}
   989  }
   990  
   991  func (f *ExprFmtCtx) formatColumns(
   992  	nd RelExpr, tp treeprinter.Node, cols opt.ColList, presentation physical.Presentation,
   993  ) {
   994  	if f.HasFlags(ExprFmtHideColumns) {
   995  		return
   996  	}
   997  	if presentation.Any() {
   998  		f.formatColList(nd, tp, "columns:", cols)
   999  		return
  1000  	}
  1001  
  1002  	// When a particular column presentation is required of the expression, then
  1003  	// print columns using that information. Include information about columns
  1004  	// that are hidden by the presentation separately.
  1005  	hidden := cols.ToSet()
  1006  	notNullCols := nd.Relational().NotNullCols
  1007  	f.Buffer.Reset()
  1008  	f.Buffer.WriteString("columns:")
  1009  	for _, col := range presentation {
  1010  		hidden.Remove(col.ID)
  1011  		f.space()
  1012  		f.formatCol(col.Alias, col.ID, notNullCols)
  1013  	}
  1014  	if !hidden.Empty() {
  1015  		f.Buffer.WriteString("  [hidden:")
  1016  		for _, col := range cols {
  1017  			if hidden.Contains(col) {
  1018  				f.space()
  1019  				f.formatCol("" /* label */, col, notNullCols)
  1020  			}
  1021  		}
  1022  		f.Buffer.WriteString("]")
  1023  	}
  1024  	tp.Child(f.Buffer.String())
  1025  }
  1026  
  1027  // formatColList constructs a new treeprinter child containing the specified
  1028  // list of columns formatted using the formatCol method.
  1029  func (f *ExprFmtCtx) formatColList(
  1030  	nd RelExpr, tp treeprinter.Node, heading string, colList opt.ColList,
  1031  ) {
  1032  	if len(colList) > 0 {
  1033  		notNullCols := nd.Relational().NotNullCols
  1034  		f.Buffer.Reset()
  1035  		f.Buffer.WriteString(heading)
  1036  		for _, col := range colList {
  1037  			if col != 0 {
  1038  				f.space()
  1039  				f.formatCol("" /* label */, col, notNullCols)
  1040  			}
  1041  		}
  1042  		tp.Child(f.Buffer.String())
  1043  	}
  1044  }
  1045  
  1046  // formatMutationCols adds a new treeprinter child for each non-zero column in the
  1047  // given list. Each child shows how the column will be mutated, with the id of
  1048  // the "before" and "after" columns, similar to this:
  1049  //
  1050  //   a:1 => x:4
  1051  //
  1052  func (f *ExprFmtCtx) formatMutationCols(
  1053  	nd RelExpr, tp treeprinter.Node, heading string, colList opt.ColList, tabID opt.TableID,
  1054  ) {
  1055  	if len(colList) == 0 {
  1056  		return
  1057  	}
  1058  
  1059  	tpChild := tp.Child(heading)
  1060  	for i, col := range colList {
  1061  		if col != 0 {
  1062  			tpChild.Child(fmt.Sprintf("%s => %s", f.ColumnString(col), f.ColumnString(tabID.ColumnID(i))))
  1063  		}
  1064  	}
  1065  }
  1066  
  1067  // formatMutationCommon shows the MutationPrivate fields that format the same
  1068  // for all types of mutations.
  1069  func (f *ExprFmtCtx) formatMutationCommon(tp treeprinter.Node, p *MutationPrivate) {
  1070  	if p.WithID != 0 {
  1071  		tp.Childf("input binding: &%d", p.WithID)
  1072  	}
  1073  	if p.FKFallback {
  1074  		tp.Childf("fk-fallback")
  1075  	}
  1076  	if len(p.FKCascades) > 0 {
  1077  		c := tp.Childf("cascades")
  1078  		for i := range p.FKCascades {
  1079  			c.Child(p.FKCascades[i].FKName)
  1080  		}
  1081  	}
  1082  }
  1083  
  1084  // ColumnString returns the column in the same format as formatColSimple.
  1085  func (f *ExprFmtCtx) ColumnString(id opt.ColumnID) string {
  1086  	var buf bytes.Buffer
  1087  	f.formatColSimpleToBuffer(&buf, "" /* label */, id)
  1088  	return buf.String()
  1089  }
  1090  
  1091  // formatColSimple outputs the specified column into the context's buffer using the
  1092  // following format:
  1093  //   label:id
  1094  //
  1095  // The :id part is omitted if the formatting flags include ExprFmtHideColumns.
  1096  //
  1097  // If a label is given, then it is used. Otherwise, a "best effort" label is
  1098  // used from query metadata.
  1099  func (f *ExprFmtCtx) formatColSimple(label string, id opt.ColumnID) {
  1100  	f.formatColSimpleToBuffer(f.Buffer, label, id)
  1101  }
  1102  
  1103  func (f *ExprFmtCtx) formatColSimpleToBuffer(buf *bytes.Buffer, label string, id opt.ColumnID) {
  1104  	if label == "" {
  1105  		if f.Memo != nil {
  1106  			md := f.Memo.metadata
  1107  			fullyQualify := !f.HasFlags(ExprFmtHideQualifications)
  1108  			label = md.QualifiedAlias(id, fullyQualify, f.Catalog)
  1109  		} else {
  1110  			label = fmt.Sprintf("unknown%d", id)
  1111  		}
  1112  	}
  1113  
  1114  	if !isSimpleColumnName(label) {
  1115  		// Add quotations around the column name if it is not composed of simple
  1116  		// ASCII characters.
  1117  		label = "\"" + label + "\""
  1118  	}
  1119  
  1120  	buf.WriteString(label)
  1121  	if !f.HasFlags(ExprFmtHideColumns) {
  1122  		buf.WriteByte(':')
  1123  		fmt.Fprintf(buf, "%d", id)
  1124  	}
  1125  }
  1126  
  1127  // formatCol outputs the specified column into the context's buffer using the
  1128  // following format:
  1129  //   label:id(type)
  1130  //
  1131  // If the column is not nullable, then this is the format:
  1132  //   label:id(type!null)
  1133  //
  1134  // Some of the components can be omitted depending on formatting flags.
  1135  //
  1136  // If a label is given, then it is used. Otherwise, a "best effort" label is
  1137  // used from query metadata.
  1138  func (f *ExprFmtCtx) formatCol(label string, id opt.ColumnID, notNullCols opt.ColSet) {
  1139  	f.formatColSimple(label, id)
  1140  	parenOpen := false
  1141  	if !f.HasFlags(ExprFmtHideTypes) && f.Memo != nil {
  1142  		f.Buffer.WriteByte('(')
  1143  		parenOpen = true
  1144  		f.Buffer.WriteString(f.Memo.metadata.ColumnMeta(id).Type.String())
  1145  	}
  1146  	if !f.HasFlags(ExprFmtHideNotNull) && notNullCols.Contains(id) {
  1147  		f.Buffer.WriteString("!null")
  1148  	}
  1149  	if parenOpen {
  1150  		f.Buffer.WriteByte(')')
  1151  	}
  1152  }
  1153  
  1154  // ScanIsReverseFn is a callback that is used to figure out if a scan needs to
  1155  // happen in reverse (the code lives in the ordering package, and depending on
  1156  // that directly would be a dependency loop).
  1157  var ScanIsReverseFn func(md *opt.Metadata, s *ScanPrivate, required *physical.OrderingChoice) bool
  1158  
  1159  // FormatPrivate outputs a description of the private to f.Buffer.
  1160  func FormatPrivate(f *ExprFmtCtx, private interface{}, physProps *physical.Required) {
  1161  	if private == nil {
  1162  		return
  1163  	}
  1164  	switch t := private.(type) {
  1165  	case *opt.ColumnID:
  1166  		f.space()
  1167  		f.formatColSimple("" /* label */, *t)
  1168  
  1169  	case *opt.ColList:
  1170  		for _, col := range *t {
  1171  			f.space()
  1172  			f.formatColSimple("" /* label */, col)
  1173  		}
  1174  
  1175  	case *TupleOrdinal:
  1176  		fmt.Fprintf(f.Buffer, " %d", *t)
  1177  
  1178  	case *ScanPrivate:
  1179  		// Don't output name of index if it's the primary index.
  1180  		tab := f.Memo.metadata.Table(t.Table)
  1181  		if t.Index == cat.PrimaryIndex {
  1182  			fmt.Fprintf(f.Buffer, " %s", tableAlias(f, t.Table))
  1183  		} else {
  1184  			fmt.Fprintf(f.Buffer, " %s@%s", tableAlias(f, t.Table), tab.Index(t.Index).Name())
  1185  		}
  1186  		if ScanIsReverseFn(f.Memo.Metadata(), t, &physProps.Ordering) {
  1187  			f.Buffer.WriteString(",rev")
  1188  		}
  1189  
  1190  	case *SequenceSelectPrivate:
  1191  		seq := f.Memo.metadata.Sequence(t.Sequence)
  1192  		fmt.Fprintf(f.Buffer, " %s", seq.Name())
  1193  
  1194  	case *MutationPrivate:
  1195  		fmt.Fprintf(f.Buffer, " %s", tableAlias(f, t.Table))
  1196  
  1197  	case *OrdinalityPrivate:
  1198  		if !t.Ordering.Any() {
  1199  			fmt.Fprintf(f.Buffer, " ordering=%s", t.Ordering)
  1200  		}
  1201  
  1202  	case *GroupingPrivate:
  1203  		fmt.Fprintf(f.Buffer, " cols=%s", t.GroupingCols.String())
  1204  		if !t.Ordering.Any() {
  1205  			fmt.Fprintf(f.Buffer, ",ordering=%s", t.Ordering)
  1206  		}
  1207  
  1208  	case *IndexJoinPrivate:
  1209  		tab := f.Memo.metadata.Table(t.Table)
  1210  		fmt.Fprintf(f.Buffer, " %s", tab.Name())
  1211  
  1212  	case *LookupJoinPrivate:
  1213  		tab := f.Memo.metadata.Table(t.Table)
  1214  		if t.Index == cat.PrimaryIndex {
  1215  			fmt.Fprintf(f.Buffer, " %s", tab.Name())
  1216  		} else {
  1217  			fmt.Fprintf(f.Buffer, " %s@%s", tab.Name(), tab.Index(t.Index).Name())
  1218  		}
  1219  
  1220  	case *GeoLookupJoinPrivate:
  1221  		tab := f.Memo.metadata.Table(t.Table)
  1222  		fmt.Fprintf(f.Buffer, " %s@%s", tab.Name(), tab.Index(t.Index).Name())
  1223  
  1224  	case *ValuesPrivate:
  1225  		fmt.Fprintf(f.Buffer, " id=v%d", t.ID)
  1226  
  1227  	case *ZigzagJoinPrivate:
  1228  		leftTab := f.Memo.metadata.Table(t.LeftTable)
  1229  		rightTab := f.Memo.metadata.Table(t.RightTable)
  1230  		fmt.Fprintf(f.Buffer, " %s", leftTab.Name())
  1231  		if t.LeftIndex != cat.PrimaryIndex {
  1232  			fmt.Fprintf(f.Buffer, "@%s", leftTab.Index(t.LeftIndex).Name())
  1233  		}
  1234  		fmt.Fprintf(f.Buffer, " %s", rightTab.Name())
  1235  		if t.RightIndex != cat.PrimaryIndex {
  1236  			fmt.Fprintf(f.Buffer, "@%s", rightTab.Index(t.RightIndex).Name())
  1237  		}
  1238  
  1239  	case *MergeJoinPrivate:
  1240  		fmt.Fprintf(f.Buffer, " %s,%s,%s", t.JoinType, t.LeftEq, t.RightEq)
  1241  
  1242  	case *FunctionPrivate:
  1243  		fmt.Fprintf(f.Buffer, " %s", t.Name)
  1244  
  1245  	case *WindowsItemPrivate:
  1246  		fmt.Fprintf(f.Buffer, " frame=%q", &t.Frame)
  1247  
  1248  	case *WindowPrivate:
  1249  		fmt.Fprintf(f.Buffer, " partition=%s", t.Partition)
  1250  		if !t.Ordering.Any() {
  1251  			fmt.Fprintf(f.Buffer, " ordering=%s", t.Ordering)
  1252  		}
  1253  
  1254  	case *physical.OrderingChoice:
  1255  		if !t.Any() {
  1256  			fmt.Fprintf(f.Buffer, " ordering=%s", t)
  1257  		}
  1258  
  1259  	case *OpaqueRelPrivate:
  1260  		f.space()
  1261  		f.Buffer.WriteString(t.Metadata.String())
  1262  
  1263  	case *AlterTableSplitPrivate:
  1264  		tab := f.Memo.metadata.Table(t.Table)
  1265  		if t.Index == cat.PrimaryIndex {
  1266  			fmt.Fprintf(f.Buffer, " %s", tableAlias(f, t.Table))
  1267  		} else {
  1268  			fmt.Fprintf(f.Buffer, " %s@%s", tableAlias(f, t.Table), tab.Index(t.Index).Name())
  1269  		}
  1270  
  1271  	case *AlterTableRelocatePrivate:
  1272  		FormatPrivate(f, &t.AlterTableSplitPrivate, nil)
  1273  		if t.RelocateLease {
  1274  			f.Buffer.WriteString(" [lease]")
  1275  		}
  1276  
  1277  	case *ControlJobsPrivate:
  1278  		fmt.Fprintf(f.Buffer, " (%s)", tree.JobCommandToStatement[t.Command])
  1279  
  1280  	case *CancelPrivate:
  1281  		if t.IfExists {
  1282  			f.Buffer.WriteString(" [if-exists]")
  1283  		}
  1284  
  1285  	case *CreateViewPrivate:
  1286  		schema := f.Memo.Metadata().Schema(t.Schema)
  1287  		fmt.Fprintf(f.Buffer, " %s.%s", schema.Name(), t.ViewName)
  1288  
  1289  	case *JoinPrivate:
  1290  		// Nothing to show; flags are shown separately.
  1291  
  1292  	case *ExplainPrivate, *opt.ColSet, *SetPrivate, *types.T, *ExportPrivate:
  1293  		// Don't show anything, because it's mostly redundant.
  1294  
  1295  	default:
  1296  		fmt.Fprintf(f.Buffer, " %v", private)
  1297  	}
  1298  }
  1299  
  1300  // tableAlias returns the alias for a table to be used for pretty-printing.
  1301  func tableAlias(f *ExprFmtCtx, tabID opt.TableID) string {
  1302  	tabMeta := f.Memo.metadata.TableMeta(tabID)
  1303  	if f.HasFlags(ExprFmtHideQualifications) {
  1304  		return tabMeta.Alias.String()
  1305  	}
  1306  	tn, err := f.Catalog.FullyQualifiedName(context.TODO(), tabMeta.Table)
  1307  	if err != nil {
  1308  		panic(err)
  1309  	}
  1310  	return tn.FQString()
  1311  }
  1312  
  1313  // isSimpleColumnName returns true if the given label consists of only ASCII
  1314  // letters, numbers, underscores, quotation marks, and periods ("."). It is
  1315  // used to determine whether to enclose a column name in quotation marks for
  1316  // nicer display.
  1317  func isSimpleColumnName(label string) bool {
  1318  	for i, r := range label {
  1319  		if r > unicode.MaxASCII {
  1320  			return false
  1321  		}
  1322  
  1323  		if i == 0 {
  1324  			if r != '"' && !unicode.IsLetter(r) {
  1325  				// The first character must be a letter or quotation mark.
  1326  				return false
  1327  			}
  1328  		} else if r != '.' && r != '_' && r != '"' && !unicode.IsNumber(r) && !unicode.IsLetter(r) {
  1329  			return false
  1330  		}
  1331  	}
  1332  	return true
  1333  }