github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colexec/like_ops.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 colexec
    12  
    13  import (
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/colmem"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    20  	"github.com/cockroachdb/errors"
    21  )
    22  
    23  // likeOpType is an enum that describes all of the different variants of LIKE
    24  // that we support.
    25  type likeOpType int
    26  
    27  const (
    28  	likeConstant likeOpType = iota + 1
    29  	likeConstantNegate
    30  	likeNeverMatch
    31  	likeAlwaysMatch
    32  	likeSuffix
    33  	likeSuffixNegate
    34  	likePrefix
    35  	likePrefixNegate
    36  	likeRegexp
    37  	likeRegexpNegate
    38  )
    39  
    40  func getLikeOperatorType(pattern string, negate bool) (likeOpType, string, error) {
    41  	if pattern == "" {
    42  		if negate {
    43  			return likeConstantNegate, "", nil
    44  		}
    45  		return likeConstant, "", nil
    46  	}
    47  	if pattern == "%" {
    48  		if negate {
    49  			return likeNeverMatch, "", nil
    50  		}
    51  		return likeAlwaysMatch, "", nil
    52  	}
    53  	if len(pattern) > 1 && !strings.ContainsAny(pattern[1:len(pattern)-1], "_%") {
    54  		// There are no wildcards in the middle of the string, so we only need to
    55  		// use a regular expression if both the first and last characters are
    56  		// wildcards.
    57  		firstChar := pattern[0]
    58  		lastChar := pattern[len(pattern)-1]
    59  		if !isWildcard(firstChar) && !isWildcard(lastChar) {
    60  			// No wildcards, so this is just an exact string match.
    61  			if negate {
    62  				return likeConstantNegate, pattern, nil
    63  			}
    64  			return likeConstant, pattern, nil
    65  		}
    66  		if firstChar == '%' && !isWildcard(lastChar) {
    67  			suffix := pattern[1:]
    68  			if negate {
    69  				return likeSuffixNegate, suffix, nil
    70  			}
    71  			return likeSuffix, suffix, nil
    72  		}
    73  		if lastChar == '%' && !isWildcard(firstChar) {
    74  			prefix := pattern[:len(pattern)-1]
    75  			if negate {
    76  				return likePrefixNegate, prefix, nil
    77  			}
    78  			return likePrefix, prefix, nil
    79  		}
    80  	}
    81  	// Default (slow) case: execute as a regular expression match.
    82  	if negate {
    83  		return likeRegexpNegate, pattern, nil
    84  	}
    85  	return likeRegexp, pattern, nil
    86  }
    87  
    88  // GetLikeOperator returns a selection operator which applies the specified LIKE
    89  // pattern, or NOT LIKE if the negate argument is true. The implementation
    90  // varies depending on the complexity of the pattern.
    91  func GetLikeOperator(
    92  	ctx *tree.EvalContext, input colexecbase.Operator, colIdx int, pattern string, negate bool,
    93  ) (colexecbase.Operator, error) {
    94  	likeOpType, pattern, err := getLikeOperatorType(pattern, negate)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	pat := []byte(pattern)
    99  	base := selConstOpBase{
   100  		OneInputNode: NewOneInputNode(input),
   101  		colIdx:       colIdx,
   102  	}
   103  	switch likeOpType {
   104  	case likeConstant:
   105  		return &selEQBytesBytesConstOp{
   106  			selConstOpBase: base,
   107  			constArg:       pat,
   108  		}, nil
   109  	case likeConstantNegate:
   110  		return &selNEBytesBytesConstOp{
   111  			selConstOpBase: base,
   112  			constArg:       pat,
   113  		}, nil
   114  	case likeNeverMatch:
   115  		// Use an empty not-prefix operator to get correct NULL behavior.
   116  		return &selNotPrefixBytesBytesConstOp{
   117  			selConstOpBase: base,
   118  			constArg:       []byte{},
   119  		}, nil
   120  	case likeAlwaysMatch:
   121  		// Use an empty prefix operator to get correct NULL behavior.
   122  		return &selPrefixBytesBytesConstOp{
   123  			selConstOpBase: base,
   124  			constArg:       []byte{},
   125  		}, nil
   126  	case likeSuffix:
   127  		return &selSuffixBytesBytesConstOp{
   128  			selConstOpBase: base,
   129  			constArg:       pat,
   130  		}, nil
   131  	case likeSuffixNegate:
   132  		return &selNotSuffixBytesBytesConstOp{
   133  			selConstOpBase: base,
   134  			constArg:       pat,
   135  		}, nil
   136  	case likePrefix:
   137  		return &selPrefixBytesBytesConstOp{
   138  			selConstOpBase: base,
   139  			constArg:       pat,
   140  		}, nil
   141  	case likePrefixNegate:
   142  		return &selNotPrefixBytesBytesConstOp{
   143  			selConstOpBase: base,
   144  			constArg:       pat,
   145  		}, nil
   146  	case likeRegexp:
   147  		re, err := tree.ConvertLikeToRegexp(ctx, pattern, false, '\\')
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  		return &selRegexpBytesBytesConstOp{
   152  			selConstOpBase: base,
   153  			constArg:       re,
   154  		}, nil
   155  	case likeRegexpNegate:
   156  		re, err := tree.ConvertLikeToRegexp(ctx, pattern, false, '\\')
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		return &selNotRegexpBytesBytesConstOp{
   161  			selConstOpBase: base,
   162  			constArg:       re,
   163  		}, nil
   164  	default:
   165  		return nil, errors.AssertionFailedf("unsupported like op type %d", likeOpType)
   166  	}
   167  }
   168  
   169  func isWildcard(c byte) bool {
   170  	return c == '%' || c == '_'
   171  }
   172  
   173  // GetLikeProjectionOperator returns a projection operator which projects the
   174  // result of the specified LIKE pattern, or NOT LIKE if the negate argument is
   175  // true. The implementation varies depending on the complexity of the pattern.
   176  func GetLikeProjectionOperator(
   177  	allocator *colmem.Allocator,
   178  	ctx *tree.EvalContext,
   179  	input colexecbase.Operator,
   180  	colIdx int,
   181  	resultIdx int,
   182  	pattern string,
   183  	negate bool,
   184  ) (colexecbase.Operator, error) {
   185  	likeOpType, pattern, err := getLikeOperatorType(pattern, negate)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	pat := []byte(pattern)
   190  	input = newVectorTypeEnforcer(allocator, input, types.Bool, resultIdx)
   191  	base := projConstOpBase{
   192  		OneInputNode: NewOneInputNode(input),
   193  		allocator:    allocator,
   194  		colIdx:       colIdx,
   195  		outputIdx:    resultIdx,
   196  	}
   197  	switch likeOpType {
   198  	case likeConstant:
   199  		return &projEQBytesBytesConstOp{
   200  			projConstOpBase: base,
   201  			constArg:        pat,
   202  		}, nil
   203  	case likeConstantNegate:
   204  		return &projNEBytesBytesConstOp{
   205  			projConstOpBase: base,
   206  			constArg:        pat,
   207  		}, nil
   208  	case likeNeverMatch:
   209  		// Use an empty not-prefix operator to get correct NULL behavior.
   210  		return &projNotPrefixBytesBytesConstOp{
   211  			projConstOpBase: base,
   212  			constArg:        []byte{},
   213  		}, nil
   214  	case likeAlwaysMatch:
   215  		// Use an empty prefix operator to get correct NULL behavior.
   216  		return &projPrefixBytesBytesConstOp{
   217  			projConstOpBase: base,
   218  			constArg:        []byte{},
   219  		}, nil
   220  	case likeSuffix:
   221  		return &projSuffixBytesBytesConstOp{
   222  			projConstOpBase: base,
   223  			constArg:        pat,
   224  		}, nil
   225  	case likeSuffixNegate:
   226  		return &projNotSuffixBytesBytesConstOp{
   227  			projConstOpBase: base,
   228  			constArg:        pat,
   229  		}, nil
   230  	case likePrefix:
   231  		return &projPrefixBytesBytesConstOp{
   232  			projConstOpBase: base,
   233  			constArg:        pat,
   234  		}, nil
   235  	case likePrefixNegate:
   236  		return &projNotPrefixBytesBytesConstOp{
   237  			projConstOpBase: base,
   238  			constArg:        pat,
   239  		}, nil
   240  	case likeRegexp:
   241  		re, err := tree.ConvertLikeToRegexp(ctx, pattern, false, '\\')
   242  		if err != nil {
   243  			return nil, err
   244  		}
   245  		return &projRegexpBytesBytesConstOp{
   246  			projConstOpBase: base,
   247  			constArg:        re,
   248  		}, nil
   249  	case likeRegexpNegate:
   250  		re, err := tree.ConvertLikeToRegexp(ctx, pattern, false, '\\')
   251  		if err != nil {
   252  			return nil, err
   253  		}
   254  		return &projNotRegexpBytesBytesConstOp{
   255  			projConstOpBase: base,
   256  			constArg:        re,
   257  		}, nil
   258  	default:
   259  		return nil, errors.AssertionFailedf("unsupported like op type %d", likeOpType)
   260  	}
   261  }