squirreling 0.7.8 → 0.7.10
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/README.md +1 -0
- package/package.json +5 -5
- package/src/backend/dataSource.js +1 -1
- package/src/execute/expression.js +47 -11
- package/src/execute/having.js +46 -12
- package/src/index.d.ts +14 -1
- package/src/parse/functions.js +23 -2
- package/src/parse/joins.js +3 -9
- package/src/parse/tokenize.js +1 -0
- package/src/types.d.ts +2 -1
- package/src/validation.js +5 -2
- package/src/validationErrors.js +2 -0
package/README.md
CHANGED
|
@@ -104,4 +104,5 @@ Squirreling mostly follows the SQL standard. The following features are supporte
|
|
|
104
104
|
- Date: `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `INTERVAL`
|
|
105
105
|
- Json: `JSON_VALUE`, `JSON_QUERY`, `JSON_OBJECT`
|
|
106
106
|
- Regex: `REGEXP_SUBSTR`, `REGEXP_REPLACE`
|
|
107
|
+
- Conditional: `COALESCE`, `NULLIF`
|
|
107
108
|
- User-defined functions (UDFs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
4
4
|
"description": "Squirreling SQL Engine",
|
|
5
5
|
"author": "Hyperparam",
|
|
6
6
|
"homepage": "https://hyperparam.app",
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "25.0
|
|
41
|
-
"@vitest/coverage-v8": "4.0.
|
|
40
|
+
"@types/node": "25.2.0",
|
|
41
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
42
42
|
"eslint": "9.39.2",
|
|
43
|
-
"eslint-plugin-jsdoc": "62.
|
|
43
|
+
"eslint-plugin-jsdoc": "62.5.0",
|
|
44
44
|
"typescript": "5.9.3",
|
|
45
|
-
"vitest": "4.0.
|
|
45
|
+
"vitest": "4.0.18"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -123,10 +123,20 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
123
123
|
})
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// Apply FILTER clause if present
|
|
127
|
+
let filteredRows = rows
|
|
128
|
+
if (node.filter) {
|
|
129
|
+
filteredRows = []
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
const passes = await evaluateExpr({ node: node.filter, row, tables, functions })
|
|
132
|
+
if (passes) filteredRows.push(row)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
// Check for star argument (COUNT(*))
|
|
127
137
|
if (node.args.length === 1 && node.args[0].type === 'identifier' && node.args[0].name === '*') {
|
|
128
138
|
if (funcName === 'COUNT') {
|
|
129
|
-
return
|
|
139
|
+
return filteredRows.length
|
|
130
140
|
}
|
|
131
141
|
throw aggregateError({
|
|
132
142
|
funcName,
|
|
@@ -139,15 +149,15 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
139
149
|
if (funcName === 'COUNT') {
|
|
140
150
|
if (node.distinct) {
|
|
141
151
|
const seen = new Set()
|
|
142
|
-
for (const
|
|
143
|
-
const v = await evaluateExpr({ node: argNode, row
|
|
152
|
+
for (const row of filteredRows) {
|
|
153
|
+
const v = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
144
154
|
if (v != null) seen.add(v)
|
|
145
155
|
}
|
|
146
156
|
return seen.size
|
|
147
157
|
}
|
|
148
158
|
let count = 0
|
|
149
|
-
for (const
|
|
150
|
-
const v = await evaluateExpr({ node: argNode, row
|
|
159
|
+
for (const row of filteredRows) {
|
|
160
|
+
const v = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
151
161
|
if (v != null) count++
|
|
152
162
|
}
|
|
153
163
|
return count
|
|
@@ -161,8 +171,8 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
161
171
|
/** @type {number | null} */
|
|
162
172
|
let max = null
|
|
163
173
|
|
|
164
|
-
for (const
|
|
165
|
-
const raw = await evaluateExpr({ node: argNode, row
|
|
174
|
+
for (const row of filteredRows) {
|
|
175
|
+
const raw = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
166
176
|
if (raw == null) continue
|
|
167
177
|
const num = Number(raw)
|
|
168
178
|
if (!Number.isFinite(num)) continue
|
|
@@ -184,13 +194,32 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
184
194
|
if (funcName === 'MAX') return max
|
|
185
195
|
}
|
|
186
196
|
|
|
197
|
+
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
198
|
+
const values = []
|
|
199
|
+
for (const row of filteredRows) {
|
|
200
|
+
const raw = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
201
|
+
if (raw == null) continue
|
|
202
|
+
const num = Number(raw)
|
|
203
|
+
if (!Number.isFinite(num)) continue
|
|
204
|
+
values.push(num)
|
|
205
|
+
}
|
|
206
|
+
const n = values.length
|
|
207
|
+
if (n === 0) return null
|
|
208
|
+
if (funcName === 'STDDEV_SAMP' && n === 1) return null
|
|
209
|
+
|
|
210
|
+
const mean = values.reduce((a, b) => a + b, 0) / n
|
|
211
|
+
const squaredDiffs = values.reduce((acc, val) => acc + (val - mean) ** 2, 0)
|
|
212
|
+
const divisor = funcName === 'STDDEV_SAMP' ? n - 1 : n
|
|
213
|
+
return Math.sqrt(squaredDiffs / divisor)
|
|
214
|
+
}
|
|
215
|
+
|
|
187
216
|
if (funcName === 'JSON_ARRAYAGG') {
|
|
188
217
|
/** @type {SqlPrimitive[]} */
|
|
189
218
|
const values = []
|
|
190
219
|
if (node.distinct) {
|
|
191
220
|
const seen = new Set()
|
|
192
|
-
for (const
|
|
193
|
-
const v = await evaluateExpr({ node: argNode, row
|
|
221
|
+
for (const row of filteredRows) {
|
|
222
|
+
const v = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
194
223
|
const key = stringify(v)
|
|
195
224
|
if (!seen.has(key)) {
|
|
196
225
|
seen.add(key)
|
|
@@ -198,8 +227,8 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
198
227
|
}
|
|
199
228
|
}
|
|
200
229
|
} else {
|
|
201
|
-
for (const
|
|
202
|
-
const v = await evaluateExpr({ node: argNode, row
|
|
230
|
+
for (const row of filteredRows) {
|
|
231
|
+
const v = await evaluateExpr({ node: argNode, row, tables, functions })
|
|
203
232
|
values.push(v)
|
|
204
233
|
}
|
|
205
234
|
}
|
|
@@ -239,6 +268,13 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
239
268
|
return null
|
|
240
269
|
}
|
|
241
270
|
|
|
271
|
+
if (funcName === 'NULLIF') {
|
|
272
|
+
// NULLIF(a, b) returns null if a = b, otherwise returns a
|
|
273
|
+
const val1 = await evaluateExpr({ node: node.args[0], row, tables, functions, rowIndex, rows })
|
|
274
|
+
const val2 = await evaluateExpr({ node: node.args[1], row, tables, functions, rowIndex, rows })
|
|
275
|
+
return val1 == val2 ? null : val1
|
|
276
|
+
}
|
|
277
|
+
|
|
242
278
|
if (funcName === 'CURRENT_DATE') {
|
|
243
279
|
return new Date().toISOString().split('T')[0]
|
|
244
280
|
}
|
package/src/execute/having.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function evaluateHavingExpr({ expr, row, group, tables, functions }
|
|
|
28
28
|
const funcName = expr.name.toUpperCase()
|
|
29
29
|
if (isAggregateFunc(funcName)) {
|
|
30
30
|
// Evaluate aggregate function on the group
|
|
31
|
-
return Boolean(await evaluateAggregateFunction({ funcName, args: expr.args, group, tables, functions }))
|
|
31
|
+
return Boolean(await evaluateAggregateFunction({ funcName, args: expr.args, filter: expr.filter, group, tables, functions }))
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -78,7 +78,7 @@ function evaluateHavingValue({ expr, context, group, tables, functions }) {
|
|
|
78
78
|
if (expr.type === 'function') {
|
|
79
79
|
const funcName = expr.name.toUpperCase()
|
|
80
80
|
if (isAggregateFunc(funcName)) {
|
|
81
|
-
return evaluateAggregateFunction({ funcName, args: expr.args, group, tables, functions })
|
|
81
|
+
return evaluateAggregateFunction({ funcName, args: expr.args, filter: expr.filter, group, tables, functions })
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -96,19 +96,30 @@ function evaluateHavingValue({ expr, context, group, tables, functions }) {
|
|
|
96
96
|
* @param {Object} options
|
|
97
97
|
* @param {AggregateFunc} options.funcName - aggregate function name
|
|
98
98
|
* @param {ExprNode[]} options.args - function arguments
|
|
99
|
+
* @param {ExprNode} [options.filter] - optional FILTER clause expression
|
|
99
100
|
* @param {AsyncRow[]} options.group - the group of rows
|
|
100
101
|
* @param {Record<string, AsyncDataSource>} options.tables
|
|
101
102
|
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
102
103
|
* @returns {Promise<SqlPrimitive>} the aggregate result
|
|
103
104
|
*/
|
|
104
|
-
async function evaluateAggregateFunction({ funcName, args, group, tables, functions }) {
|
|
105
|
+
async function evaluateAggregateFunction({ funcName, args, filter, group, tables, functions }) {
|
|
106
|
+
// Apply FILTER clause if present
|
|
107
|
+
let filteredGroup = group
|
|
108
|
+
if (filter) {
|
|
109
|
+
filteredGroup = []
|
|
110
|
+
for (const row of group) {
|
|
111
|
+
const passes = await evaluateExpr({ node: filter, row, tables, functions })
|
|
112
|
+
if (passes) filteredGroup.push(row)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
if (funcName === 'COUNT') {
|
|
106
117
|
if (args.length === 1 && args[0].type === 'identifier' && args[0].name === '*') {
|
|
107
|
-
return
|
|
118
|
+
return filteredGroup.length
|
|
108
119
|
}
|
|
109
120
|
// COUNT(column) - count non-null values
|
|
110
121
|
let count = 0
|
|
111
|
-
for (const row of
|
|
122
|
+
for (const row of filteredGroup) {
|
|
112
123
|
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
113
124
|
if (val != null) count++
|
|
114
125
|
}
|
|
@@ -117,17 +128,21 @@ async function evaluateAggregateFunction({ funcName, args, group, tables, functi
|
|
|
117
128
|
|
|
118
129
|
if (funcName === 'SUM') {
|
|
119
130
|
let sum = 0
|
|
120
|
-
|
|
131
|
+
let hasValue = false
|
|
132
|
+
for (const row of filteredGroup) {
|
|
121
133
|
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
122
|
-
if (val != null)
|
|
134
|
+
if (val != null) {
|
|
135
|
+
sum += Number(val)
|
|
136
|
+
hasValue = true
|
|
137
|
+
}
|
|
123
138
|
}
|
|
124
|
-
return sum
|
|
139
|
+
return hasValue ? sum : null
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
if (funcName === 'AVG') {
|
|
128
143
|
let sum = 0
|
|
129
144
|
let count = 0
|
|
130
|
-
for (const row of
|
|
145
|
+
for (const row of filteredGroup) {
|
|
131
146
|
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
132
147
|
if (val != null) {
|
|
133
148
|
sum += Number(val)
|
|
@@ -139,7 +154,7 @@ async function evaluateAggregateFunction({ funcName, args, group, tables, functi
|
|
|
139
154
|
|
|
140
155
|
if (funcName === 'MIN') {
|
|
141
156
|
let min = null
|
|
142
|
-
for (const row of
|
|
157
|
+
for (const row of filteredGroup) {
|
|
143
158
|
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
144
159
|
if (val != null && (min == null || val < min)) {
|
|
145
160
|
min = val
|
|
@@ -150,7 +165,7 @@ async function evaluateAggregateFunction({ funcName, args, group, tables, functi
|
|
|
150
165
|
|
|
151
166
|
if (funcName === 'MAX') {
|
|
152
167
|
let max = null
|
|
153
|
-
for (const row of
|
|
168
|
+
for (const row of filteredGroup) {
|
|
154
169
|
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
155
170
|
if (val != null && (max == null || val > max)) {
|
|
156
171
|
max = val
|
|
@@ -159,10 +174,29 @@ async function evaluateAggregateFunction({ funcName, args, group, tables, functi
|
|
|
159
174
|
return max
|
|
160
175
|
}
|
|
161
176
|
|
|
177
|
+
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
178
|
+
const values = []
|
|
179
|
+
for (const row of filteredGroup) {
|
|
180
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
181
|
+
if (val == null) continue
|
|
182
|
+
const num = Number(val)
|
|
183
|
+
if (!Number.isFinite(num)) continue
|
|
184
|
+
values.push(num)
|
|
185
|
+
}
|
|
186
|
+
const n = values.length
|
|
187
|
+
if (n === 0) return null
|
|
188
|
+
if (funcName === 'STDDEV_SAMP' && n === 1) return null
|
|
189
|
+
|
|
190
|
+
const mean = values.reduce((a, b) => a + b, 0) / n
|
|
191
|
+
const squaredDiffs = values.reduce((acc, val) => acc + (val - mean) ** 2, 0)
|
|
192
|
+
const divisor = funcName === 'STDDEV_SAMP' ? n - 1 : n
|
|
193
|
+
return Math.sqrt(squaredDiffs / divisor)
|
|
194
|
+
}
|
|
195
|
+
|
|
162
196
|
throw unknownFunctionError({
|
|
163
197
|
funcName,
|
|
164
198
|
positionStart: 0,
|
|
165
199
|
positionEnd: 0,
|
|
166
|
-
validFunctions: 'COUNT, SUM, AVG, MIN, MAX',
|
|
200
|
+
validFunctions: 'COUNT, SUM, AVG, MIN, MAX, STDDEV_SAMP, STDDEV_POP',
|
|
167
201
|
})
|
|
168
202
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { AsyncDataSource, AsyncRow, ExecuteSqlOptions, ParseSqlOptions, SelectStatement, SqlPrimitive, Token } from './types.js'
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
AsyncCells,
|
|
4
|
+
AsyncDataSource,
|
|
5
|
+
AsyncRow,
|
|
6
|
+
ExecuteSqlOptions,
|
|
7
|
+
ExprNode,
|
|
8
|
+
ParseSqlOptions,
|
|
9
|
+
QueryHints,
|
|
10
|
+
ScanOptions,
|
|
11
|
+
SelectStatement,
|
|
12
|
+
SqlPrimitive,
|
|
13
|
+
Token,
|
|
14
|
+
UserDefinedFunction,
|
|
15
|
+
} from './types.js'
|
|
3
16
|
|
|
4
17
|
/**
|
|
5
18
|
* Executes a SQL SELECT query against an array of data rows
|
package/src/parse/functions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { argCountParseError } from '../parseErrors.js'
|
|
2
|
-
import { validateFunctionArgCount } from '../validation.js'
|
|
1
|
+
import { argCountParseError, syntaxError } from '../parseErrors.js'
|
|
2
|
+
import { isAggregateFunc, validateFunctionArgCount } from '../validation.js'
|
|
3
3
|
import { parseExpression } from './expression.js'
|
|
4
4
|
import { consume, current, expect, lastPosition, match } from './state.js'
|
|
5
5
|
|
|
@@ -52,6 +52,26 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
52
52
|
|
|
53
53
|
expect(state, 'paren', ')')
|
|
54
54
|
|
|
55
|
+
// Check for FILTER clause (only valid for aggregate functions)
|
|
56
|
+
/** @type {ExprNode | undefined} */
|
|
57
|
+
let filter
|
|
58
|
+
if (current(state).type === 'keyword' && current(state).value === 'FILTER') {
|
|
59
|
+
const funcNameUpper = funcName.toUpperCase()
|
|
60
|
+
if (!isAggregateFunc(funcNameUpper)) {
|
|
61
|
+
throw syntaxError({
|
|
62
|
+
expected: 'aggregate function for FILTER clause',
|
|
63
|
+
received: `FILTER on non-aggregate function "${funcName}"`,
|
|
64
|
+
positionStart: current(state).positionStart,
|
|
65
|
+
positionEnd: current(state).positionEnd,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
consume(state) // FILTER
|
|
69
|
+
expect(state, 'paren', '(')
|
|
70
|
+
expect(state, 'keyword', 'WHERE')
|
|
71
|
+
filter = parseExpression(state)
|
|
72
|
+
expect(state, 'paren', ')')
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
// Validate argument count at parse time
|
|
56
76
|
const funcNameUpper = funcName.toUpperCase()
|
|
57
77
|
const validation = validateFunctionArgCount(funcNameUpper, args.length, state.functions)
|
|
@@ -70,6 +90,7 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
70
90
|
name: funcName,
|
|
71
91
|
args,
|
|
72
92
|
distinct: distinct || undefined,
|
|
93
|
+
filter,
|
|
73
94
|
positionStart,
|
|
74
95
|
positionEnd: lastPosition(state),
|
|
75
96
|
}
|
package/src/parse/joins.js
CHANGED
|
@@ -24,21 +24,15 @@ export function parseJoins(state) {
|
|
|
24
24
|
joinType = 'INNER'
|
|
25
25
|
} else if (tok.value === 'LEFT') {
|
|
26
26
|
consume(state)
|
|
27
|
-
|
|
28
|
-
// LEFT OUTER JOIN
|
|
29
|
-
}
|
|
27
|
+
match(state, 'keyword', 'OUTER') // LEFT OUTER JOIN
|
|
30
28
|
joinType = 'LEFT'
|
|
31
29
|
} else if (tok.value === 'RIGHT') {
|
|
32
30
|
consume(state)
|
|
33
|
-
|
|
34
|
-
// RIGHT OUTER JOIN
|
|
35
|
-
}
|
|
31
|
+
match(state, 'keyword', 'OUTER') // RIGHT OUTER JOIN
|
|
36
32
|
joinType = 'RIGHT'
|
|
37
33
|
} else if (tok.value === 'FULL') {
|
|
38
34
|
consume(state)
|
|
39
|
-
|
|
40
|
-
// FULL OUTER JOIN
|
|
41
|
-
}
|
|
35
|
+
match(state, 'keyword', 'OUTER') // FULL OUTER JOIN
|
|
42
36
|
joinType = 'FULL'
|
|
43
37
|
} else if (tok.value === 'POSITIONAL') {
|
|
44
38
|
consume(state)
|
package/src/parse/tokenize.js
CHANGED
package/src/types.d.ts
CHANGED
|
@@ -141,6 +141,7 @@ export interface FunctionNode extends ExprNodeBase {
|
|
|
141
141
|
name: string
|
|
142
142
|
args: ExprNode[]
|
|
143
143
|
distinct?: boolean
|
|
144
|
+
filter?: ExprNode
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export interface CastNode extends ExprNodeBase {
|
|
@@ -211,7 +212,7 @@ export interface StarColumn {
|
|
|
211
212
|
alias?: string
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'JSON_ARRAYAGG'
|
|
215
|
+
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'JSON_ARRAYAGG' | 'STDDEV_SAMP' | 'STDDEV_POP'
|
|
215
216
|
|
|
216
217
|
export type MathFunc =
|
|
217
218
|
| 'FLOOR'
|
package/src/validation.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @returns {name is AggregateFunc}
|
|
6
6
|
*/
|
|
7
7
|
export function isAggregateFunc(name) {
|
|
8
|
-
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG'].includes(name)
|
|
8
|
+
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP'].includes(name)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -117,6 +117,7 @@ export const FUNCTION_ARG_COUNTS = {
|
|
|
117
117
|
|
|
118
118
|
// Conditional functions
|
|
119
119
|
COALESCE: { min: 1 },
|
|
120
|
+
NULLIF: { min: 2, max: 2 },
|
|
120
121
|
|
|
121
122
|
// Aggregate functions
|
|
122
123
|
COUNT: { min: 1, max: 1 },
|
|
@@ -124,6 +125,8 @@ export const FUNCTION_ARG_COUNTS = {
|
|
|
124
125
|
AVG: { min: 1, max: 1 },
|
|
125
126
|
MIN: { min: 1, max: 1 },
|
|
126
127
|
MAX: { min: 1, max: 1 },
|
|
128
|
+
STDDEV_SAMP: { min: 1, max: 1 },
|
|
129
|
+
STDDEV_POP: { min: 1, max: 1 },
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
/**
|
|
@@ -192,7 +195,7 @@ export function isKnownFunction(funcName, functions) {
|
|
|
192
195
|
if ([
|
|
193
196
|
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
|
194
197
|
'JSON_VALUE', 'JSON_QUERY', 'JSON_OBJECT',
|
|
195
|
-
'COALESCE', 'CAST',
|
|
198
|
+
'COALESCE', 'NULLIF', 'CAST',
|
|
196
199
|
].includes(funcName)) {
|
|
197
200
|
return true
|
|
198
201
|
}
|