github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/optgen/exprgen/custom_funcs.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 exprgen
    12  
    13  import (
    14  	"fmt"
    15  	"strings"
    16  	"unicode"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/opt/norm"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    24  )
    25  
    26  type customFuncs struct {
    27  	f   *norm.Factory
    28  	mem *memo.Memo
    29  	cat cat.Catalog
    30  }
    31  
    32  // NewColumn creates a new column in the metadata.
    33  func (c *customFuncs) NewColumn(name, typeStr string) opt.ColumnID {
    34  	typ, err := ParseType(typeStr)
    35  	if err != nil {
    36  		panic(exprGenErr{err})
    37  	}
    38  	return c.f.Metadata().AddColumn(name, typ)
    39  }
    40  
    41  // LookupColumn looks up a column that was already specified in the expression
    42  // so far (either via NewColumn or by using a table).
    43  func (c *customFuncs) LookupColumn(name string) opt.ColumnID {
    44  	md := c.f.Metadata()
    45  
    46  	var res opt.ColumnID
    47  	for colID := opt.ColumnID(1); int(colID) <= md.NumColumns(); colID++ {
    48  		if md.ColumnMeta(colID).Alias == name {
    49  			if res != 0 {
    50  				panic(errorf("ambiguous column %s", name))
    51  			}
    52  			res = colID
    53  		}
    54  	}
    55  	if res == 0 {
    56  		panic(errorf("unknown column %s", name))
    57  	}
    58  	return res
    59  }
    60  
    61  // ColList creates a ColList from a comma-separated list of column names,
    62  // looking up each column.
    63  func (c *customFuncs) ColList(cols string) opt.ColList {
    64  	strs := strings.Split(cols, ",")
    65  	res := make(opt.ColList, len(strs))
    66  	for i, col := range strs {
    67  		res[i] = c.LookupColumn(col)
    68  	}
    69  	return res
    70  }
    71  
    72  // ColSet creates a ColSet from a comma-separated list of column names, looking
    73  // up each column.
    74  func (c *customFuncs) ColSet(cols string) opt.ColSet {
    75  	return c.ColList(cols).ToSet()
    76  }
    77  
    78  // MinPhysProps returns the singleton minimum set of physical properties.
    79  func (c *customFuncs) MinPhysProps() *physical.Required {
    80  	return physical.MinRequired
    81  }
    82  
    83  // MakePhysProps returns a set of physical properties corresponding to the
    84  // input presentation and OrderingChoice.
    85  func (c *customFuncs) MakePhysProps(
    86  	p physical.Presentation, o physical.OrderingChoice,
    87  ) *physical.Required {
    88  	return c.mem.InternPhysicalProps(&physical.Required{
    89  		Presentation: p,
    90  		Ordering:     o,
    91  	})
    92  }
    93  
    94  // ExplainOptions creates a tree.ExplainOptions from a comma-separated list of
    95  // options.
    96  func (c *customFuncs) ExplainOptions(opts string) tree.ExplainOptions {
    97  	explain, err := tree.MakeExplain(strings.Split(opts, ","), &tree.Select{})
    98  	if err != nil {
    99  		panic(exprGenErr{err})
   100  	}
   101  	return explain.(*tree.Explain).ExplainOptions
   102  }
   103  
   104  // Var creates a VariableOp for the given column. It allows (Var "name") as a
   105  // shorthand for (Variable (LookupColumn "name")).
   106  func (c *customFuncs) Var(colName string) opt.ScalarExpr {
   107  	return c.f.ConstructVariable(c.LookupColumn(colName))
   108  }
   109  
   110  // FindTable looks up a table in the metadata without creating it.
   111  // This is required to construct operators like IndexJoin which must
   112  // reference the same table multiple times.
   113  func (c *customFuncs) FindTable(name string) opt.TableID {
   114  	tables := c.mem.Metadata().AllTables()
   115  
   116  	var res opt.TableID
   117  	for i := range tables {
   118  		if string(tables[i].Table.Name()) == name {
   119  			if res != 0 {
   120  				panic(errorf("ambiguous table %q", name))
   121  			}
   122  			res = tables[i].MetaID
   123  		}
   124  	}
   125  	if res == 0 {
   126  		panic(errorf("couldn't find table with name %q", name))
   127  	}
   128  	return res
   129  }
   130  
   131  // Ordering parses a string like "+a,-b" into an Ordering.
   132  func (c *customFuncs) Ordering(str string) opt.Ordering {
   133  	defer func() {
   134  		if r := recover(); r != nil {
   135  			panic(errorf("could not parse Ordering \"%s\"", str))
   136  		}
   137  	}()
   138  	return physical.ParseOrdering(c.substituteCols(str))
   139  }
   140  
   141  // OrderingChoice parses a string like "+a,-(b|c)" into an OrderingChoice.
   142  func (c *customFuncs) OrderingChoice(str string) physical.OrderingChoice {
   143  	defer func() {
   144  		if r := recover(); r != nil {
   145  			panic(errorf("could not parse OrderingChoice \"%s\"", str))
   146  		}
   147  	}()
   148  	return physical.ParseOrderingChoice(c.substituteCols(str))
   149  }
   150  
   151  // substituteCols extracts every word (sequence of letters, numbers, and
   152  // underscores) from the string, looks up the column with that name, and
   153  // replaces the string with the column ID. E.g.: "+a,+b" -> "+1,+2".
   154  func (c *customFuncs) substituteCols(str string) string {
   155  	var b strings.Builder
   156  	lastPos := -1
   157  	maybeEmit := func(curPos int) {
   158  		if lastPos != -1 {
   159  			col := str[lastPos:curPos]
   160  			fmt.Fprintf(&b, "%d", c.LookupColumn(col))
   161  		}
   162  		lastPos = -1
   163  	}
   164  	for i, r := range str {
   165  		if unicode.IsLetter(r) || r == '_' || unicode.IsNumber(r) {
   166  			if lastPos == -1 {
   167  				lastPos = i
   168  			}
   169  			continue
   170  		}
   171  		maybeEmit(i)
   172  		b.WriteRune(r)
   173  	}
   174  	maybeEmit(len(str))
   175  	return b.String()
   176  }
   177  
   178  // MakeLookupJoin is a wrapper around ConstructLookupJoin that swaps the order
   179  // of the private and the filters. This is useful because the expressions are
   180  // evaluated in order, and we want to be able to refer to the lookup columns in
   181  // the ON expression. For example:
   182  //
   183  //   (MakeLookupJoin
   184  //     (Scan [ (Table "def") (Cols "d,e") ])
   185  //     [ (JoinType "left-join") (Table "abc") (Index "abc@ab") (KeyCols "a") (Cols "a,b") ]
   186  //     [ (Gt (Var "a") (Var "e")) ]
   187  //   )
   188  //
   189  // If the order of the last two was swapped, we wouldn't be able to look up
   190  // column a.
   191  func (c *customFuncs) MakeLookupJoin(
   192  	input memo.RelExpr, lookupJoinPrivate *memo.LookupJoinPrivate, on memo.FiltersExpr,
   193  ) memo.RelExpr {
   194  	return c.f.ConstructLookupJoin(input, on, lookupJoinPrivate)
   195  }
   196  
   197  // Sort adds a sort enforcer which sorts according to the ordering that will be
   198  // required by its parent.
   199  func (c *customFuncs) Sort(input memo.RelExpr) memo.RelExpr {
   200  	return &memo.SortExpr{Input: input}
   201  }
   202  
   203  // rootSentinel is used as the root value when Root is used.
   204  type rootSentinel struct {
   205  	expr     memo.RelExpr
   206  	required *physical.Required
   207  }
   208  
   209  // Presentation converts a ColList to a Presentation.
   210  func (c *customFuncs) Presentation(cols opt.ColList) physical.Presentation {
   211  	res := make(physical.Presentation, len(cols))
   212  	for i := range cols {
   213  		res[i].ID = cols[i]
   214  		res[i].Alias = c.mem.Metadata().ColumnMeta(cols[i]).Alias
   215  	}
   216  	return res
   217  }
   218  
   219  // NoOrdering returns the empty OrderingChoice.
   220  func (c *customFuncs) NoOrdering() physical.OrderingChoice {
   221  	return physical.OrderingChoice{}
   222  }
   223  
   224  // Root can be used only at the top level on an expression, to annotate the
   225  // root with a presentation and/or required ordering. The operator must be able
   226  // to provide the ordering. For example:
   227  //   (Root
   228  //     ( ... )
   229  //     (Presentation "a,b")
   230  //     (OrderingChoice "+a")
   231  //   )
   232  func (c *customFuncs) Root(
   233  	root memo.RelExpr, presentation physical.Presentation, ordering physical.OrderingChoice,
   234  ) *rootSentinel {
   235  	props := &physical.Required{
   236  		Presentation: presentation,
   237  		Ordering:     ordering,
   238  	}
   239  	return &rootSentinel{expr: root, required: props}
   240  }