github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optbuilder/with.go (about)

     1  // Copyright 2019 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 optbuilder
    12  
    13  import (
    14  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    15  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
    16  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/errors"
    21  )
    22  
    23  func (b *Builder) processWiths(
    24  	with *tree.With, inScope *scope, buildStmt func(inScope *scope) *scope,
    25  ) *scope {
    26  	inScope = b.buildCTEs(with, inScope)
    27  	prevAtRoot := inScope.atRoot
    28  	inScope.atRoot = false
    29  	outScope := buildStmt(inScope)
    30  	inScope.atRoot = prevAtRoot
    31  	return outScope
    32  }
    33  
    34  func (b *Builder) buildCTE(
    35  	cte *tree.CTE, inScope *scope, isRecursive bool,
    36  ) (memo.RelExpr, physical.Presentation) {
    37  	if !isRecursive {
    38  		cteScope := b.buildStmt(cte.Stmt, nil /* desiredTypes */, inScope)
    39  		cteScope.removeHiddenCols()
    40  		b.dropOrderingAndExtraCols(cteScope)
    41  		return cteScope.expr, b.getCTECols(cteScope, cte.Name)
    42  	}
    43  
    44  	// WITH RECURSIVE queries are always of the form:
    45  	//
    46  	//   WITH RECURSIVE name(cols) AS (
    47  	//     initial_query
    48  	//     UNION ALL
    49  	//     recursive_query
    50  	//   )
    51  	//
    52  	// Recursive CTE evaluation (paraphrased from postgres docs):
    53  	//  1. Evaluate the initial query; emit the results and also save them in
    54  	//     a "working" table.
    55  	//  2. So long as the working table is not empty:
    56  	//     * evaluate the recursive query, substituting the current contents of
    57  	//       the working table for the recursive self-reference.
    58  	//     * emit all resulting rows, and save them as the next iteration's
    59  	//       working table.
    60  	//
    61  	// Note however, that a non-recursive CTE can be used even when RECURSIVE is
    62  	// specified (particularly useful when there are multiple CTEs defined).
    63  	// Handling this while having decent error messages is tricky.
    64  
    65  	// Generate an id for the recursive CTE reference. This is the id through
    66  	// which the recursive expression refers to the current working table
    67  	// (via WithScan).
    68  	withID := b.factory.Memo().NextWithID()
    69  
    70  	// cteScope allows recursive references to this CTE.
    71  	cteScope := inScope.push()
    72  	cteSrc := &cteSource{
    73  		id:   withID,
    74  		name: cte.Name,
    75  	}
    76  	cteScope.ctes = map[string]*cteSource{cte.Name.Alias.String(): cteSrc}
    77  
    78  	initial, recursive, isUnionAll, ok := b.splitRecursiveCTE(cte.Stmt)
    79  	// We don't currently support the UNION form (only UNION ALL).
    80  	if !ok || !isUnionAll {
    81  		// Build this as a non-recursive CTE, but throw a proper error message if it
    82  		// does have a recursive reference.
    83  		cteSrc.onRef = func() {
    84  			if !ok {
    85  				panic(pgerror.Newf(
    86  					pgcode.Syntax,
    87  					"recursive query %q does not have the form non-recursive-term UNION ALL recursive-term",
    88  					cte.Name.Alias,
    89  				))
    90  			} else {
    91  				panic(unimplementedWithIssueDetailf(
    92  					46642, "",
    93  					"recursive query %q uses UNION which is not implemented (only UNION ALL is supported)",
    94  					cte.Name.Alias,
    95  				))
    96  			}
    97  		}
    98  		return b.buildCTE(cte, cteScope, false /* recursive */)
    99  	}
   100  
   101  	// Set up an error if the initial part has a recursive reference.
   102  	cteSrc.onRef = func() {
   103  		panic(pgerror.Newf(
   104  			pgcode.Syntax,
   105  			"recursive reference to query %q must not appear within its non-recursive term",
   106  			cte.Name.Alias,
   107  		))
   108  	}
   109  	// If the initial statement contains CTEs, we don't want the Withs hoisted
   110  	// above the recursive CTE.
   111  	b.pushWithFrame()
   112  	initialScope := b.buildStmt(initial, nil /* desiredTypes */, cteScope)
   113  	b.popWithFrame(initialScope)
   114  
   115  	initialScope.removeHiddenCols()
   116  	b.dropOrderingAndExtraCols(initialScope)
   117  
   118  	// The properties of the binding are tricky: the recursive expression is
   119  	// invoked repeatedly and these must hold each time. We can't use the initial
   120  	// expression's properties directly, as those only hold the first time the
   121  	// recursive query is executed. We can't really say too much about what the
   122  	// working table contains, except that it has at least one row (the recursive
   123  	// query is never invoked with an empty working table).
   124  	bindingProps := &props.Relational{}
   125  	bindingProps.OutputCols = initialScope.colSet()
   126  	bindingProps.Cardinality = props.AnyCardinality.AtLeast(props.OneCardinality)
   127  	// We don't really know the input row count, except for the first time we run
   128  	// the recursive query. We don't have anything better though.
   129  	bindingProps.Stats.RowCount = initialScope.expr.Relational().Stats.RowCount
   130  	cteSrc.bindingProps = bindingProps
   131  
   132  	cteSrc.cols = b.getCTECols(initialScope, cte.Name)
   133  
   134  	outScope := inScope.push()
   135  
   136  	initialTypes := initialScope.makeColumnTypes()
   137  
   138  	// Synthesize new output columns (because they contain values from both the
   139  	// initial and the recursive relations). These columns will also be used to
   140  	// refer to the working table (from the recursive query); we can't use the
   141  	// initial columns directly because they might contain duplicate IDs (e.g.
   142  	// consider initial query SELECT 0, 0).
   143  	for i, c := range cteSrc.cols {
   144  		newCol := b.synthesizeColumn(outScope, c.Alias, initialTypes[i], nil /* expr */, nil /* scalar */)
   145  		cteSrc.cols[i].ID = newCol.id
   146  	}
   147  
   148  	// We want to check if the recursive query is actually recursive. This is for
   149  	// annoying cases like `SELECT 1 UNION ALL SELECT 2`.
   150  	numRefs := 0
   151  	cteSrc.onRef = func() {
   152  		numRefs++
   153  	}
   154  
   155  	// If the recursive statement contains CTEs, we don't want the Withs hoisted
   156  	// above the recursive CTE.
   157  	b.pushWithFrame()
   158  	recursiveScope := b.buildStmt(recursive, initialTypes /* desiredTypes */, cteScope)
   159  	b.popWithFrame(recursiveScope)
   160  
   161  	if numRefs == 0 {
   162  		// Build this as a non-recursive CTE.
   163  		cteScope := b.buildSetOp(tree.UnionOp, false /* all */, inScope, initialScope, recursiveScope)
   164  		return cteScope.expr, b.getCTECols(cteScope, cte.Name)
   165  	}
   166  
   167  	if numRefs != 1 {
   168  		// We disallow multiple recursive references for consistency with postgres.
   169  		panic(pgerror.Newf(
   170  			pgcode.Syntax,
   171  			"recursive reference to query %q must not appear more than once",
   172  			cte.Name.Alias,
   173  		))
   174  	}
   175  
   176  	recursiveScope.removeHiddenCols()
   177  	b.dropOrderingAndExtraCols(recursiveScope)
   178  
   179  	// We allow propagation of types from the initial query to the recursive
   180  	// query.
   181  	_, propagateToRight := b.checkTypesMatch(initialScope, recursiveScope,
   182  		false, /* tolerateUnknownLeft */
   183  		true,  /* tolerateUnknownRight */
   184  		"UNION",
   185  	)
   186  	if propagateToRight {
   187  		recursiveScope = b.propagateTypes(recursiveScope /* dst */, initialScope /* src */)
   188  	}
   189  
   190  	private := memo.RecursiveCTEPrivate{
   191  		Name:          string(cte.Name.Alias),
   192  		WithID:        withID,
   193  		InitialCols:   colsToColList(initialScope.cols),
   194  		RecursiveCols: colsToColList(recursiveScope.cols),
   195  		OutCols:       colsToColList(outScope.cols),
   196  	}
   197  
   198  	expr := b.factory.ConstructRecursiveCTE(initialScope.expr, recursiveScope.expr, &private)
   199  	return expr, cteSrc.cols
   200  }
   201  
   202  // getCTECols returns a presentation for the scope, renaming the columns to
   203  // those provided in the AliasClause (if any). Throws an error if there is a
   204  // mismatch in the number of columns.
   205  func (b *Builder) getCTECols(cteScope *scope, name tree.AliasClause) physical.Presentation {
   206  	presentation := cteScope.makePresentation()
   207  
   208  	if len(presentation) == 0 {
   209  		err := pgerror.Newf(
   210  			pgcode.FeatureNotSupported,
   211  			"WITH clause %q does not return any columns",
   212  			tree.ErrString(&name),
   213  		)
   214  		panic(errors.WithHint(err, "missing RETURNING clause?"))
   215  	}
   216  
   217  	if name.Cols == nil {
   218  		return presentation
   219  	}
   220  
   221  	if len(presentation) != len(name.Cols) {
   222  		panic(pgerror.Newf(
   223  			pgcode.InvalidColumnReference,
   224  			"source %q has %d columns available but %d columns specified",
   225  			name.Alias, len(presentation), len(name.Cols),
   226  		))
   227  	}
   228  	for i := range presentation {
   229  		presentation[i].Alias = string(name.Cols[i])
   230  	}
   231  	return presentation
   232  }
   233  
   234  // splitRecursiveCTE splits a CTE statement of the form
   235  //   initial_query UNION ALL recursive_query
   236  // into the initial and recursive parts. If the statement is not of this form,
   237  // returns ok=false.
   238  func (b *Builder) splitRecursiveCTE(
   239  	stmt tree.Statement,
   240  ) (initial, recursive *tree.Select, isUnionAll bool, ok bool) {
   241  	sel, ok := stmt.(*tree.Select)
   242  	// The form above doesn't allow for "outer" WITH, ORDER BY, or LIMIT
   243  	// clauses.
   244  	if !ok || sel.With != nil || sel.OrderBy != nil || sel.Limit != nil {
   245  		return nil, nil, false, false
   246  	}
   247  	union, ok := sel.Select.(*tree.UnionClause)
   248  	if !ok || union.Type != tree.UnionOp {
   249  		return nil, nil, false, false
   250  	}
   251  	return union.Left, union.Right, union.All, true
   252  }