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 }