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 }