squirreling 0.12.7 → 0.12.8
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 +3 -3
- package/package.json +1 -1
- package/src/ast.d.ts +1 -1
- package/src/execute/execute.js +75 -7
- package/src/execute/join.js +1 -1
- package/src/expression/evaluate.js +35 -1
- package/src/parse/functions.js +24 -0
- package/src/parse/parse.js +33 -7
- package/src/plan/columns.js +21 -9
- package/src/plan/plan.js +2 -2
- package/src/plan/types.d.ts +1 -1
- package/src/types.d.ts +1 -1
- package/src/validation/functions.js +6 -2
- package/src/validation/keywords.js +1 -1
package/README.md
CHANGED
|
@@ -154,14 +154,14 @@ Squirreling mostly follows the SQL standard. The following features are supporte
|
|
|
154
154
|
|
|
155
155
|
### Functions
|
|
156
156
|
|
|
157
|
-
- Aggregate: `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `MEDIAN`, `PERCENTILE_CONT`, `APPROX_QUANTILE`, `STDDEV_POP`, `STDDEV_SAMP`, `JSON_ARRAYAGG`, `STRING_AGG`
|
|
157
|
+
- Aggregate: `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `MEDIAN`, `PERCENTILE_CONT`, `APPROX_QUANTILE`, `STDDEV_POP`, `STDDEV_SAMP`, `ARRAY_AGG`, `JSON_ARRAYAGG`, `STRING_AGG`
|
|
158
158
|
- String: `CONCAT`, `SUBSTRING`, `REPLACE`, `LENGTH`, `UPPER`, `LOWER`, `TRIM`, `LEFT`, `RIGHT`, `INSTR`, `POSITION`, `STRPOS`
|
|
159
159
|
- Math: `ABS`, `SIGN`, `CEIL`, `FLOOR`, `ROUND`, `MOD`, `RAND`, `RANDOM`, `LN`, `LOG10`, `EXP`, `POWER`, `SQRT`
|
|
160
160
|
- Trig: `SIN`, `COS`, `TAN`, `COT`, `ASIN`, `ACOS`, `ATAN`, `ATAN2`, `DEGREES`, `RADIANS`, `PI`
|
|
161
161
|
- Date: `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `DATE_PART`, `DATE_TRUNC`, `EXTRACT`, `INTERVAL`
|
|
162
|
-
- Json: `JSON_VALUE`, `JSON_QUERY`, `JSON_EXTRACT`, `JSON_OBJECT`, `JSON_ARRAY_LENGTH`
|
|
162
|
+
- Json: `JSON_VALUE`, `JSON_QUERY`, `JSON_EXTRACT`, `JSON_OBJECT`, `JSON_ARRAY_LENGTH`, `JSON_VALID`, `JSON_TYPE`
|
|
163
163
|
- Array: `ARRAY_LENGTH`, `ARRAY_POSITION`, `ARRAY_SORT`, `CARDINALITY`
|
|
164
|
-
- Table functions: `UNNEST`
|
|
164
|
+
- Table functions: `UNNEST`, `JSON_EACH`
|
|
165
165
|
- Regex: `REGEXP_SUBSTR`, `REGEXP_EXTRACT`, `REGEXP_REPLACE`, `REGEXP_MATCHES`
|
|
166
166
|
- Spatial: `ST_GeomFromText`, `ST_MakeEnvelope`, `ST_AsText`, `ST_Intersects`, `ST_Contains`, `ST_ContainsProperly`, `ST_Within`, `ST_Overlaps`, `ST_Touches`, `ST_Equals`, `ST_Crosses`, `ST_Covers`, `ST_CoveredBy`, `ST_DWithin`
|
|
167
167
|
- Conditional: `COALESCE`, `NULLIF`, `GREATEST`, `LEAST`
|
package/package.json
CHANGED
package/src/ast.d.ts
CHANGED
package/src/execute/execute.js
CHANGED
|
@@ -125,19 +125,31 @@ export function executePlan({ plan, context }) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Executes a table-valued function (e.g. UNNEST).
|
|
129
|
-
* Evaluates the argument once against
|
|
130
|
-
*
|
|
128
|
+
* Executes a table-valued function (e.g. UNNEST, JSON_EACH).
|
|
129
|
+
* Evaluates the argument once against the outer row (for lateral joins) or an
|
|
130
|
+
* empty row, then yields rows derived from the resulting value.
|
|
131
131
|
*
|
|
132
132
|
* @param {TableFunctionNode} plan
|
|
133
133
|
* @param {ExecuteContext} context
|
|
134
134
|
* @returns {QueryResults}
|
|
135
135
|
*/
|
|
136
136
|
function executeTableFunction(plan, context) {
|
|
137
|
-
if (plan.funcName
|
|
138
|
-
|
|
137
|
+
if (plan.funcName === 'UNNEST') {
|
|
138
|
+
return executeUnnest(plan, context)
|
|
139
|
+
} else if (plan.funcName === 'JSON_EACH') {
|
|
140
|
+
return executeJsonEach(plan, context)
|
|
139
141
|
}
|
|
140
|
-
|
|
142
|
+
throw new Error(`Unsupported table function: ${plan.funcName}`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {TableFunctionNode} plan
|
|
147
|
+
* @param {ExecuteContext} context
|
|
148
|
+
* @returns {QueryResults}
|
|
149
|
+
*/
|
|
150
|
+
function executeUnnest(plan, context) {
|
|
151
|
+
const columns = plan.columnNames
|
|
152
|
+
const [columnName] = columns
|
|
141
153
|
return {
|
|
142
154
|
columns,
|
|
143
155
|
async *rows() {
|
|
@@ -149,9 +161,65 @@ function executeTableFunction(plan, context) {
|
|
|
149
161
|
if (context.signal?.aborted) return
|
|
150
162
|
yield {
|
|
151
163
|
columns,
|
|
152
|
-
cells: { [
|
|
164
|
+
cells: { [columnName]: () => Promise.resolve(element) },
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {TableFunctionNode} plan
|
|
173
|
+
* @param {ExecuteContext} context
|
|
174
|
+
* @returns {QueryResults}
|
|
175
|
+
*/
|
|
176
|
+
function executeJsonEach(plan, context) {
|
|
177
|
+
const columns = plan.columnNames
|
|
178
|
+
const [keyCol, valueCol] = columns
|
|
179
|
+
return {
|
|
180
|
+
columns,
|
|
181
|
+
async *rows() {
|
|
182
|
+
/** @type {AsyncRow} */
|
|
183
|
+
const row = context.outerRow ?? { columns: [], cells: {} }
|
|
184
|
+
const value = await evaluateExpr({ node: plan.args[0], row, rowIndex: 1, context })
|
|
185
|
+
if (value == null) return
|
|
186
|
+
let parsed = value
|
|
187
|
+
if (typeof value === 'string') {
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(value)
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error('JSON_EACH(value): invalid JSON string. Argument must be valid JSON.')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(parsed)) {
|
|
195
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
196
|
+
if (context.signal?.aborted) return
|
|
197
|
+
const k = i
|
|
198
|
+
const v = parsed[i]
|
|
199
|
+
yield {
|
|
200
|
+
columns,
|
|
201
|
+
cells: {
|
|
202
|
+
[keyCol]: () => Promise.resolve(k),
|
|
203
|
+
[valueCol]: () => Promise.resolve(v),
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
210
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
211
|
+
if (context.signal?.aborted) return
|
|
212
|
+
yield {
|
|
213
|
+
columns,
|
|
214
|
+
cells: {
|
|
215
|
+
[keyCol]: () => Promise.resolve(k),
|
|
216
|
+
[valueCol]: () => Promise.resolve(v),
|
|
217
|
+
},
|
|
218
|
+
}
|
|
153
219
|
}
|
|
220
|
+
return
|
|
154
221
|
}
|
|
222
|
+
throw new Error('JSON_EACH(value): argument must be a JSON object or array')
|
|
155
223
|
},
|
|
156
224
|
}
|
|
157
225
|
}
|
package/src/execute/join.js
CHANGED
|
@@ -95,7 +95,7 @@ export function executeNestedLoopJoin(plan, context) {
|
|
|
95
95
|
function executeLateralJoin(plan, context) {
|
|
96
96
|
const left = executePlan({ plan: plan.left, context })
|
|
97
97
|
// Right columns are known statically for table functions (the common case).
|
|
98
|
-
const rightCols = plan.right.type === 'TableFunction' ?
|
|
98
|
+
const rightCols = plan.right.type === 'TableFunction' ? plan.right.columnNames : []
|
|
99
99
|
return {
|
|
100
100
|
columns: mergeColumnNames(left.columns, rightCols, plan.leftAlias, plan.rightAlias),
|
|
101
101
|
async *rows() {
|
|
@@ -272,7 +272,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
272
272
|
return values[lower] + (values[upper] - values[lower]) * (pos - lower)
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
if (funcName === 'JSON_ARRAYAGG') {
|
|
275
|
+
if (funcName === 'JSON_ARRAYAGG' || funcName === 'ARRAY_AGG') {
|
|
276
276
|
if (node.distinct) {
|
|
277
277
|
/** @type {SqlPrimitive[]} */
|
|
278
278
|
const values = []
|
|
@@ -417,6 +417,40 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
417
417
|
return result
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
if (funcName === 'JSON_VALID') {
|
|
421
|
+
const value = args[0]
|
|
422
|
+
if (value == null) return null
|
|
423
|
+
if (typeof value !== 'string') return false
|
|
424
|
+
try {
|
|
425
|
+
JSON.parse(value)
|
|
426
|
+
return true
|
|
427
|
+
} catch {
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (funcName === 'JSON_TYPE') {
|
|
433
|
+
let value = args[0]
|
|
434
|
+
if (value == null) return null
|
|
435
|
+
if (typeof value === 'string') {
|
|
436
|
+
try {
|
|
437
|
+
value = JSON.parse(value)
|
|
438
|
+
} catch {
|
|
439
|
+
throw new ArgValueError({
|
|
440
|
+
...node,
|
|
441
|
+
message: 'invalid JSON string',
|
|
442
|
+
hint: 'Argument must be valid JSON.',
|
|
443
|
+
rowIndex,
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (value === null) return 'null'
|
|
448
|
+
if (Array.isArray(value)) return 'array'
|
|
449
|
+
if (value instanceof Date) return 'string'
|
|
450
|
+
if (typeof value === 'bigint') return 'number'
|
|
451
|
+
return typeof value
|
|
452
|
+
}
|
|
453
|
+
|
|
420
454
|
if (funcName === 'JSON_ARRAY_LENGTH') {
|
|
421
455
|
let arr = args[0]
|
|
422
456
|
if (arr == null) return null
|
package/src/parse/functions.js
CHANGED
|
@@ -84,6 +84,30 @@ export function parseFunctionCall(state, positionStart) {
|
|
|
84
84
|
})
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Check for WITHIN GROUP (ORDER BY expr) clause — standard SQL ordered-set aggregate syntax.
|
|
88
|
+
// Supported for PERCENTILE_CONT: PERCENTILE_CONT(fraction) WITHIN GROUP (ORDER BY expr)
|
|
89
|
+
const withinTok = current(state)
|
|
90
|
+
if (match(state, 'keyword', 'WITHIN')) {
|
|
91
|
+
if (funcNameUpper !== 'PERCENTILE_CONT') {
|
|
92
|
+
throw new ParseError({
|
|
93
|
+
message: `WITHIN GROUP is only supported for PERCENTILE_CONT, not "${funcName}"`,
|
|
94
|
+
...withinTok,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
if (args.length !== 1) {
|
|
98
|
+
throw new ParseError({
|
|
99
|
+
message: `${funcName}: cannot combine WITHIN GROUP with a value argument`,
|
|
100
|
+
...withinTok,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
expect(state, 'keyword', 'GROUP')
|
|
104
|
+
expect(state, 'paren', '(')
|
|
105
|
+
expect(state, 'keyword', 'ORDER')
|
|
106
|
+
expect(state, 'keyword', 'BY')
|
|
107
|
+
args.push(parseExpression(state))
|
|
108
|
+
expect(state, 'paren', ')')
|
|
109
|
+
}
|
|
110
|
+
|
|
87
111
|
// Validate argument count at parse time
|
|
88
112
|
validateFunctionArgs(funcNameUpper, args.length, positionStart, state.lastPos, state.functions)
|
|
89
113
|
|
package/src/parse/parse.js
CHANGED
|
@@ -468,15 +468,22 @@ export function parseFromFunction(state) {
|
|
|
468
468
|
validateFunctionArgs(funcName, args.length, positionStart, state.lastPos, state.functions)
|
|
469
469
|
|
|
470
470
|
const alias = parseTableAlias(state)
|
|
471
|
-
/** @type {string
|
|
472
|
-
|
|
471
|
+
/** @type {string[]} */
|
|
472
|
+
const columnAliases = []
|
|
473
473
|
if (alias && match(state, 'paren', '(')) {
|
|
474
474
|
const colStart = state.lastPos
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
while (true) {
|
|
476
|
+
const colTok = expect(state, 'identifier')
|
|
477
|
+
columnAliases.push(colTok.value)
|
|
478
|
+
if (!match(state, 'comma')) break
|
|
479
|
+
}
|
|
480
|
+
const maxCols = tableFunctionColumnCount(funcName)
|
|
481
|
+
if (columnAliases.length > maxCols) {
|
|
482
|
+
const colLabels = tableFunctionDefaultColumns(funcName).join(', ')
|
|
478
483
|
throw new ParseError({
|
|
479
|
-
message:
|
|
484
|
+
message: maxCols === 1
|
|
485
|
+
? `${funcName} produces a single column; only one column alias is allowed`
|
|
486
|
+
: `${funcName} produces at most ${maxCols} columns (${colLabels}); too many column aliases`,
|
|
480
487
|
positionStart: colStart,
|
|
481
488
|
positionEnd: state.lastPos,
|
|
482
489
|
})
|
|
@@ -489,12 +496,31 @@ export function parseFromFunction(state) {
|
|
|
489
496
|
funcName,
|
|
490
497
|
args,
|
|
491
498
|
alias,
|
|
492
|
-
|
|
499
|
+
columnAliases,
|
|
493
500
|
positionStart,
|
|
494
501
|
positionEnd: state.lastPos,
|
|
495
502
|
}
|
|
496
503
|
}
|
|
497
504
|
|
|
505
|
+
/**
|
|
506
|
+
* Default column names produced by a table-valued function.
|
|
507
|
+
* @param {string} funcName
|
|
508
|
+
* @returns {string[]}
|
|
509
|
+
*/
|
|
510
|
+
export function tableFunctionDefaultColumns(funcName) {
|
|
511
|
+
if (funcName === 'JSON_EACH') return ['key', 'value']
|
|
512
|
+
return [funcName.toLowerCase()]
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Maximum number of output columns a table-valued function can produce.
|
|
517
|
+
* @param {string} funcName
|
|
518
|
+
* @returns {number}
|
|
519
|
+
*/
|
|
520
|
+
export function tableFunctionColumnCount(funcName) {
|
|
521
|
+
return tableFunctionDefaultColumns(funcName).length
|
|
522
|
+
}
|
|
523
|
+
|
|
498
524
|
/**
|
|
499
525
|
* Parses an optional table alias (e.g., "FROM users u" or "FROM users AS u")
|
|
500
526
|
* @param {ParserState} state
|
package/src/plan/columns.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { tableFunctionDefaultColumns } from '../parse/parse.js'
|
|
1
2
|
import { derivedAlias } from '../expression/alias.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,13 +19,19 @@ export function fromAlias(from) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Returns the
|
|
22
|
+
* Returns the output column names for a FROM table function, applying any
|
|
23
|
+
* column aliases over the function's default column names.
|
|
22
24
|
*
|
|
23
25
|
* @param {FromFunction} from
|
|
24
|
-
* @returns {string}
|
|
26
|
+
* @returns {string[]}
|
|
25
27
|
*/
|
|
26
|
-
export function
|
|
27
|
-
|
|
28
|
+
export function tableFunctionColumnNames(from) {
|
|
29
|
+
const defaults = tableFunctionDefaultColumns(from.funcName)
|
|
30
|
+
const result = []
|
|
31
|
+
for (let i = 0; i < defaults.length; i++) {
|
|
32
|
+
result.push(from.columnAliases[i] ?? defaults[i])
|
|
33
|
+
}
|
|
34
|
+
return result
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
@@ -330,18 +337,21 @@ export function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
|
330
337
|
}
|
|
331
338
|
|
|
332
339
|
if (select.from.type === 'function') {
|
|
333
|
-
// Table functions currently produce a single column
|
|
334
340
|
if (!select.joins.length) {
|
|
335
|
-
return
|
|
341
|
+
return tableFunctionColumnNames(select.from)
|
|
336
342
|
}
|
|
337
343
|
/** @type {string[]} */
|
|
338
344
|
const result = []
|
|
339
345
|
const alias = fromAlias(select.from)
|
|
340
|
-
|
|
346
|
+
for (const col of tableFunctionColumnNames(select.from)) {
|
|
347
|
+
result.push(`${alias}.${col}`)
|
|
348
|
+
}
|
|
341
349
|
for (const join of select.joins) {
|
|
342
350
|
const joinAlias = join.alias ?? join.table
|
|
343
351
|
if (join.fromFunction) {
|
|
344
|
-
|
|
352
|
+
for (const col of tableFunctionColumnNames(join.fromFunction)) {
|
|
353
|
+
result.push(`${joinAlias}.${col}`)
|
|
354
|
+
}
|
|
345
355
|
} else {
|
|
346
356
|
for (const col of lookupTableColumns(join.table, cteColumns, tables)) {
|
|
347
357
|
result.push(`${joinAlias}.${col}`)
|
|
@@ -365,7 +375,9 @@ export function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
|
365
375
|
for (const join of select.joins) {
|
|
366
376
|
const joinAlias = join.alias ?? join.table
|
|
367
377
|
if (join.fromFunction) {
|
|
368
|
-
|
|
378
|
+
for (const col of tableFunctionColumnNames(join.fromFunction)) {
|
|
379
|
+
result.push(`${joinAlias}.${col}`)
|
|
380
|
+
}
|
|
369
381
|
} else {
|
|
370
382
|
for (const col of lookupTableColumns(join.table, cteColumns, tables)) {
|
|
371
383
|
result.push(`${joinAlias}.${col}`)
|
package/src/plan/plan.js
CHANGED
|
@@ -3,7 +3,7 @@ import { parseSql } from '../parse/parse.js'
|
|
|
3
3
|
import { findAggregate } from '../validation/aggregates.js'
|
|
4
4
|
import { ColumnNotFoundError, TableNotFoundError } from '../validation/tables.js'
|
|
5
5
|
import { validateNoIdentifiers, validateScan, validateTableRefs } from '../validation/tables.js'
|
|
6
|
-
import { extractColumns, fromAlias, inferSelectSourceColumns, inferStatementColumns,
|
|
6
|
+
import { extractColumns, fromAlias, inferSelectSourceColumns, inferStatementColumns, tableFunctionColumnNames } from './columns.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @import { AsyncDataSource, ExprNode, DerivedColumn, IdentifierNode, JoinClause, PlanSqlOptions, ScanOptions, SelectColumn, SelectStatement, SetOperationStatement, Statement } from '../types.js'
|
|
@@ -321,7 +321,7 @@ function planTableFunction(from) {
|
|
|
321
321
|
type: 'TableFunction',
|
|
322
322
|
funcName: from.funcName,
|
|
323
323
|
args: from.args,
|
|
324
|
-
|
|
324
|
+
columnNames: tableFunctionColumnNames(from),
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
|
package/src/plan/types.d.ts
CHANGED
package/src/types.d.ts
CHANGED
|
@@ -129,7 +129,7 @@ export interface UserDefinedFunction {
|
|
|
129
129
|
arguments: FunctionSignature
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'JSON_ARRAYAGG' | 'STDDEV_SAMP' | 'STDDEV_POP' | 'MEDIAN' | 'PERCENTILE_CONT' | 'APPROX_QUANTILE' | 'STRING_AGG'
|
|
132
|
+
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'ARRAY_AGG' | 'JSON_ARRAYAGG' | 'STDDEV_SAMP' | 'STDDEV_POP' | 'MEDIAN' | 'PERCENTILE_CONT' | 'APPROX_QUANTILE' | 'STRING_AGG'
|
|
133
133
|
|
|
134
134
|
export type RegExpFunction = 'REGEXP_SUBSTR' | 'REGEXP_EXTRACT' | 'REGEXP_REPLACE' | 'REGEXP_MATCHES'
|
|
135
135
|
|
|
@@ -11,7 +11,7 @@ export const niladicFuncs = ['CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP'
|
|
|
11
11
|
* @returns {name is AggregateFunc}
|
|
12
12
|
*/
|
|
13
13
|
export function isAggregateFunc(name) {
|
|
14
|
-
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP', 'MEDIAN', 'PERCENTILE_CONT', 'APPROX_QUANTILE', 'STRING_AGG'].includes(name)
|
|
14
|
+
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'ARRAY_AGG', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP', 'MEDIAN', 'PERCENTILE_CONT', 'APPROX_QUANTILE', 'STRING_AGG'].includes(name)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -41,7 +41,7 @@ export function isRegexpFunc(name) {
|
|
|
41
41
|
* @returns {boolean}
|
|
42
42
|
*/
|
|
43
43
|
export function isTableFunction(name) {
|
|
44
|
-
return ['UNNEST'].includes(name)
|
|
44
|
+
return ['UNNEST', 'JSON_EACH'].includes(name)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -166,7 +166,10 @@ export const FUNCTION_SIGNATURES = {
|
|
|
166
166
|
JSON_EXTRACT: { min: 2, max: 2, signature: 'expression, path' },
|
|
167
167
|
JSON_OBJECT: { min: 0, signature: 'key1, value1[, ...]' },
|
|
168
168
|
JSON_ARRAY_LENGTH: { min: 1, max: 1, signature: 'array' },
|
|
169
|
+
JSON_VALID: { min: 1, max: 1, signature: 'value' },
|
|
170
|
+
JSON_TYPE: { min: 1, max: 1, signature: 'value' },
|
|
169
171
|
JSON_ARRAYAGG: { min: 1, max: 1, signature: 'expression' },
|
|
172
|
+
ARRAY_AGG: { min: 1, max: 1, signature: 'expression' },
|
|
170
173
|
|
|
171
174
|
// Array functions
|
|
172
175
|
ARRAY_LENGTH: { min: 1, max: 1, signature: 'array' },
|
|
@@ -176,6 +179,7 @@ export const FUNCTION_SIGNATURES = {
|
|
|
176
179
|
|
|
177
180
|
// Table functions (used in FROM clause)
|
|
178
181
|
UNNEST: { min: 1, max: 1, signature: 'array' },
|
|
182
|
+
JSON_EACH: { min: 1, max: 1, signature: 'value' },
|
|
179
183
|
|
|
180
184
|
// Conditional functions
|
|
181
185
|
COALESCE: { min: 1, signature: 'value1, value2[, ...]' },
|
|
@@ -4,7 +4,7 @@ export const KEYWORDS = new Set([
|
|
|
4
4
|
'DISTINCT', 'TRUE', 'FALSE', 'NULL', 'LIKE', 'IN', 'EXISTS', 'BETWEEN',
|
|
5
5
|
'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'JOIN', 'INNER', 'LEFT', 'RIGHT',
|
|
6
6
|
'FULL', 'OUTER', 'CROSS', 'POSITIONAL', 'LATERAL', 'ON', 'INTERVAL', 'DAY', 'MONTH', 'YEAR',
|
|
7
|
-
'HOUR', 'MINUTE', 'SECOND', 'FILTER',
|
|
7
|
+
'HOUR', 'MINUTE', 'SECOND', 'FILTER', 'WITHIN',
|
|
8
8
|
'UNION', 'INTERSECT', 'EXCEPT',
|
|
9
9
|
])
|
|
10
10
|
|