squirreling 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/expression/batch.js +16 -1
- package/src/expression/binary.js +7 -1
- package/src/expression/regexp.js +39 -18
package/package.json
CHANGED
package/src/expression/batch.js
CHANGED
|
@@ -4,6 +4,7 @@ import { yieldToEventLoop } from '../execute/yield.js'
|
|
|
4
4
|
import { isStringFunc } from '../validation/functions.js'
|
|
5
5
|
import { ColumnNotFoundError } from '../validation/tables.js'
|
|
6
6
|
import { applyBinaryOp } from './binary.js'
|
|
7
|
+
import { evaluateRegexpLike } from './regexp.js'
|
|
7
8
|
import { applyCast, evaluateJsonExtract } from './scalar.js'
|
|
8
9
|
import { evaluateStringFunc } from './strings.js'
|
|
9
10
|
|
|
@@ -260,6 +261,15 @@ function compileFunctionKernel(node, state) {
|
|
|
260
261
|
return evaluateJsonExtract({ funcName, node, args, rowIndex: streamRowIndex + 1 })
|
|
261
262
|
}
|
|
262
263
|
}
|
|
264
|
+
if (funcName === 'REGEXP_LIKE') {
|
|
265
|
+
const cache = node.args[1]?.type === 'literal' ? {} : undefined
|
|
266
|
+
return function regexpLikeValue(vectors, rowIndex, streamRowIndex) {
|
|
267
|
+
const args = arguments_.map(function argumentValue(argument) {
|
|
268
|
+
return argument(vectors, rowIndex, streamRowIndex)
|
|
269
|
+
})
|
|
270
|
+
return evaluateRegexpLike({ node, args, rowIndex: streamRowIndex + 1, cache })
|
|
271
|
+
}
|
|
272
|
+
}
|
|
263
273
|
if (!isStringFunc(funcName)) return undefined
|
|
264
274
|
return function stringFunctionValue(vectors, rowIndex, streamRowIndex) {
|
|
265
275
|
const args = arguments_.map(function argumentValue(argument) {
|
|
@@ -292,7 +302,9 @@ function compileFunctionEvaluator(node, columns) {
|
|
|
292
302
|
}
|
|
293
303
|
}
|
|
294
304
|
if (funcName !== 'NULLIF' && funcName !== 'JSON_VALUE' && funcName !== 'JSON_QUERY' &&
|
|
295
|
-
funcName !== 'JSON_EXTRACT' && funcName !== 'JSON_EXTRACT_STRING' &&
|
|
305
|
+
funcName !== 'JSON_EXTRACT' && funcName !== 'JSON_EXTRACT_STRING' && funcName !== 'REGEXP_LIKE' &&
|
|
306
|
+
!isStringFunc(funcName)) return undefined
|
|
307
|
+
const regexpCache = funcName === 'REGEXP_LIKE' && node.args[1]?.type === 'literal' ? {} : undefined
|
|
296
308
|
return {
|
|
297
309
|
async evaluate(context) {
|
|
298
310
|
const vectors = await Promise.all(arguments_.map(function evaluateArgument(argument) {
|
|
@@ -304,6 +316,9 @@ function compileFunctionEvaluator(node, columns) {
|
|
|
304
316
|
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY' || funcName === 'JSON_EXTRACT' || funcName === 'JSON_EXTRACT_STRING') {
|
|
305
317
|
return evaluateJsonExtract({ funcName, node, args, rowIndex: streamRowIndex + 1 })
|
|
306
318
|
}
|
|
319
|
+
if (funcName === 'REGEXP_LIKE') {
|
|
320
|
+
return evaluateRegexpLike({ node, args, rowIndex: streamRowIndex + 1, cache: regexpCache })
|
|
321
|
+
}
|
|
307
322
|
return evaluateStringFunc({ funcName, node, args, rowIndex: streamRowIndex + 1 })
|
|
308
323
|
})
|
|
309
324
|
},
|
package/src/expression/binary.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { stringify } from '../execute/utils.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @import { BinaryOp, SqlPrimitive } from '../types.js'
|
|
3
5
|
*/
|
|
@@ -68,7 +70,11 @@ export function applyBinaryOp(op, a, b) {
|
|
|
68
70
|
if (op === '>=') return a >= b
|
|
69
71
|
|
|
70
72
|
if (op === 'LIKE') {
|
|
71
|
-
|
|
73
|
+
// Objects, arrays, and Dates stringify as JSON, the same coercion as
|
|
74
|
+
// CAST(x AS VARCHAR), so `x LIKE p` and `CAST(x AS VARCHAR) LIKE p` agree.
|
|
75
|
+
// String() would collapse every object to '[object Object]', making LIKE
|
|
76
|
+
// a silent never-match on JSON-typed columns.
|
|
77
|
+
const str = typeof a === 'object' ? stringify(a) : String(a)
|
|
72
78
|
const pattern = String(b)
|
|
73
79
|
const regexPattern = pattern
|
|
74
80
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
package/src/expression/regexp.js
CHANGED
|
@@ -79,24 +79,7 @@ export function evaluateRegexpFunc({ funcName, node, args, rowIndex }) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (funcName === 'REGEXP_MATCHES' || funcName === 'REGEXP_LIKE') {
|
|
82
|
-
|
|
83
|
-
const pattern = args[1]
|
|
84
|
-
if (str == null || pattern == null) return null
|
|
85
|
-
const strVal = String(str)
|
|
86
|
-
const patternStr = String(pattern)
|
|
87
|
-
|
|
88
|
-
let regex
|
|
89
|
-
try {
|
|
90
|
-
regex = new RegExp(patternStr)
|
|
91
|
-
} catch (/** @type {any} */ error) {
|
|
92
|
-
throw new ArgValueError({
|
|
93
|
-
...node,
|
|
94
|
-
message: `invalid regex pattern: ${error.message}`,
|
|
95
|
-
rowIndex,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return regex.test(strVal)
|
|
82
|
+
return evaluateRegexpLike({ node, args, rowIndex })
|
|
100
83
|
}
|
|
101
84
|
|
|
102
85
|
if (funcName === 'REGEXP_REPLACE') {
|
|
@@ -168,3 +151,41 @@ export function evaluateRegexpFunc({ funcName, node, args, rowIndex }) {
|
|
|
168
151
|
|
|
169
152
|
throw new Error(`Unsupported regexp function: ${funcName}`)
|
|
170
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Evaluates REGEXP_LIKE/REGEXP_MATCHES with an optional single-pattern cache.
|
|
157
|
+
* Batch kernels use the cache for literal patterns so the RegExp is compiled
|
|
158
|
+
* once, while scalar and dynamic-pattern evaluation retain row-local behavior.
|
|
159
|
+
* Compilation stays lazy so a null string still returns null without validating
|
|
160
|
+
* an otherwise invalid pattern, matching the scalar evaluator.
|
|
161
|
+
*
|
|
162
|
+
* @param {Object} options
|
|
163
|
+
* @param {FunctionNode} options.node
|
|
164
|
+
* @param {SqlPrimitive[]} options.args
|
|
165
|
+
* @param {number} [options.rowIndex]
|
|
166
|
+
* @param {{ pattern?: string, regex?: RegExp }} [options.cache]
|
|
167
|
+
* @returns {SqlPrimitive}
|
|
168
|
+
*/
|
|
169
|
+
export function evaluateRegexpLike({ node, args, rowIndex, cache }) {
|
|
170
|
+
const string = args[0]
|
|
171
|
+
const pattern = args[1]
|
|
172
|
+
if (string == null || pattern == null) return null
|
|
173
|
+
const patternString = String(pattern)
|
|
174
|
+
let regex = cache?.pattern === patternString ? cache.regex : undefined
|
|
175
|
+
if (!regex) {
|
|
176
|
+
try {
|
|
177
|
+
regex = new RegExp(patternString)
|
|
178
|
+
} catch (/** @type {any} */ error) {
|
|
179
|
+
throw new ArgValueError({
|
|
180
|
+
...node,
|
|
181
|
+
message: `invalid regex pattern: ${error.message}`,
|
|
182
|
+
rowIndex,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
if (cache) {
|
|
186
|
+
cache.pattern = patternString
|
|
187
|
+
cache.regex = regex
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return regex.test(String(string))
|
|
191
|
+
}
|