squirreling 0.15.1 → 0.15.3
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 +4 -4
- package/package.json +1 -1
- package/src/ast.d.ts +2 -0
- package/src/expression/binary.js +17 -4
- package/src/expression/evaluate.js +67 -12
- package/src/expression/regexp.js +1 -1
- package/src/parse/primary.js +3 -2
- package/src/types.d.ts +2 -2
- package/src/validation/functions.js +9 -2
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ Squirreling mostly follows the SQL standard. The following features are supporte
|
|
|
146
146
|
- `JOIN` operations: `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `FULL JOIN`, `CROSS JOIN`, `POSITIONAL JOIN`, `LATERAL VIEW [OUTER] EXPLODE(...)`, with `ON` or `USING (col, ...)` conditions
|
|
147
147
|
- `GROUP BY` and `HAVING` clauses
|
|
148
148
|
- Set operations: `UNION`, `UNION ALL`, `INTERSECT`, `INTERSECT ALL`, `EXCEPT`, `EXCEPT ALL`
|
|
149
|
-
- Expressions: `CASE`, `CAST`, `BETWEEN`, `IN`, `LIKE`, `IS NULL`, `IS NOT NULL`, string concatenation `||`
|
|
149
|
+
- Expressions: `CASE`, `CAST`, `TRY_CAST`, `BETWEEN`, `IN`, `LIKE`, `IS NULL`, `IS NOT NULL`, string concatenation `||`
|
|
150
150
|
- Subscript access: zero-based array indexing `col[0]`, struct field access `col['field']`, and chains like `col[0].field`
|
|
151
151
|
|
|
152
152
|
### Quoting
|
|
@@ -157,16 +157,16 @@ Squirreling mostly follows the SQL standard. The following features are supporte
|
|
|
157
157
|
|
|
158
158
|
### Functions
|
|
159
159
|
|
|
160
|
-
- Aggregate: `COUNT`, `COUNTIF`, `SUM`, `AVG`, `MIN`, `MAX`, `MEDIAN`, `PERCENTILE_CONT`, `APPROX_QUANTILE`, `STDDEV_POP`, `STDDEV_SAMP`, `ARRAY_AGG`, `JSON_ARRAYAGG`, `STRING_AGG`
|
|
160
|
+
- Aggregate: `COUNT`, `COUNTIF`, `SUM`, `AVG`, `MIN`, `MAX`, `MIN_BY`, `MAX_BY`, `ANY_VALUE`, `MEDIAN`, `PERCENTILE_CONT`, `APPROX_QUANTILE`, `STDDEV_POP`, `STDDEV_SAMP`, `ARRAY_AGG`, `JSON_ARRAYAGG`, `STRING_AGG`
|
|
161
161
|
- Window: `ROW_NUMBER`, `LAG`, `LEAD`
|
|
162
162
|
- String: `CONCAT`, `SUBSTRING`, `REPLACE`, `LENGTH`, `OCTET_LENGTH`, `UPPER`, `LOWER`, `TRIM`, `LEFT`, `RIGHT`, `INSTR`, `POSITION`, `STRPOS`, `SPLIT_PART`, `STRING_SPLIT`
|
|
163
163
|
- Math: `ABS`, `SIGN`, `CEIL`, `FLOOR`, `ROUND`, `MOD`, `RAND`, `RANDOM`, `LN`, `LOG10`, `EXP`, `POWER`, `SQRT`
|
|
164
164
|
- Trig: `SIN`, `COS`, `TAN`, `COT`, `ASIN`, `ACOS`, `ATAN`, `ATAN2`, `DEGREES`, `RADIANS`, `PI`
|
|
165
165
|
- Date: `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `DATE_DIFF`, `DATEDIFF`, `DATE_PART`, `DATE_TRUNC`, `EPOCH`, `EXTRACT`, `INTERVAL`
|
|
166
|
-
- Json: `JSON_VALUE`, `JSON_QUERY`, `JSON_EXTRACT`, `JSON_OBJECT`, `JSON_ARRAY_LENGTH`, `JSON_VALID`, `JSON_TYPE`, `JSON_KEYS`
|
|
166
|
+
- Json: `JSON_VALUE`, `JSON_QUERY`, `JSON_EXTRACT`, `JSON_EXTRACT_STRING`, `JSON_OBJECT`, `JSON_ARRAY_LENGTH`, `JSON_VALID`, `JSON_TYPE`, `JSON_KEYS`
|
|
167
167
|
- Array: `ARRAY_LENGTH`, `ARRAY_POSITION`, `ARRAY_CONTAINS`, `ARRAY_SORT`, `ARRAY_APPEND`, `ARRAY_CONCAT`, `LEN`, `CARDINALITY`, `SIZE`
|
|
168
168
|
- Table functions: `UNNEST`, `EXPLODE`, `JSON_EACH`
|
|
169
|
-
- Regex: `REGEXP_SUBSTR`, `REGEXP_EXTRACT`, `REGEXP_REPLACE`, `REGEXP_MATCHES`
|
|
169
|
+
- Regex: `REGEXP_SUBSTR`, `REGEXP_EXTRACT`, `REGEXP_REPLACE`, `REGEXP_MATCHES`, `REGEXP_LIKE`
|
|
170
170
|
- 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`
|
|
171
171
|
- Conditional: `COALESCE`, `NULLIF`, `GREATEST`, `LEAST`
|
|
172
172
|
- User-defined functions (UDFs)
|
package/package.json
CHANGED
package/src/ast.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ export interface CastNode extends AstBase {
|
|
|
120
120
|
type: 'cast'
|
|
121
121
|
expr: ExprNode
|
|
122
122
|
toType: CastType
|
|
123
|
+
/** TRY_CAST returns null instead of throwing when the value cannot be cast */
|
|
124
|
+
tryCast?: boolean
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
export interface InSubqueryNode extends AstBase {
|
package/src/expression/binary.js
CHANGED
|
@@ -29,12 +29,25 @@ export function applyBinaryOp(op, a, b) {
|
|
|
29
29
|
return String(a) + String(b)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// Logical operators use Kleene three-valued logic: null is UNKNOWN, which
|
|
33
|
+
// only resolves when the other operand decides the answer on its own
|
|
34
|
+
if (op === 'AND') {
|
|
35
|
+
if (a != null && !a || b != null && !b) return false
|
|
36
|
+
if (a == null || b == null) return null
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
if (op === 'OR') {
|
|
40
|
+
if (a != null && Boolean(a) || b != null && Boolean(b)) return true
|
|
41
|
+
if (a == null || b == null) return null
|
|
34
42
|
return false
|
|
35
43
|
}
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
|
|
45
|
+
// A comparison with a null operand is UNKNOWN, not false. The difference is
|
|
46
|
+
// invisible to a WHERE (both exclude the row) but not to a NOT above it,
|
|
47
|
+
// which must keep UNKNOWN as UNKNOWN rather than flip false to true
|
|
48
|
+
if (a == null || b == null) {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
38
51
|
// Compare Date values by their time so distinct instances for the same
|
|
39
52
|
// instant are equal, matching SQL TIMESTAMP semantics rather than JS identity.
|
|
40
53
|
if (a instanceof Date && b instanceof Date) {
|
|
@@ -167,7 +167,9 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
167
167
|
if (val == null) return null
|
|
168
168
|
return -val
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
// NOT UNKNOWN is UNKNOWN: JS ! would flip null to true and return rows
|
|
171
|
+
// SQL excludes, e.g. WHERE NOT (nullable < 300)
|
|
172
|
+
if (node.op === 'NOT') return val == null ? null : !val
|
|
171
173
|
if (node.op === 'IS NULL') return val == null
|
|
172
174
|
if (node.op === 'IS NOT NULL') return val != null
|
|
173
175
|
}
|
|
@@ -186,9 +188,11 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
186
188
|
|
|
187
189
|
const left = await evaluateExpr({ node: node.left, row, rowIndex, rows, context })
|
|
188
190
|
|
|
189
|
-
// Short-circuit evaluation for AND and OR
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
// Short-circuit evaluation for AND and OR. A null left cannot
|
|
192
|
+
// short-circuit AND: NULL AND FALSE is false but NULL AND TRUE is null,
|
|
193
|
+
// so the right side decides
|
|
194
|
+
if (node.op === 'AND' && left != null && !left) return false
|
|
195
|
+
if (node.op === 'OR' && left != null && Boolean(left)) return true
|
|
192
196
|
|
|
193
197
|
const right = await evaluateExpr({ node: node.right, row, rowIndex, rows, context })
|
|
194
198
|
return applyBinaryOp(node.op, left, right)
|
|
@@ -289,6 +293,37 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
289
293
|
if (funcName === 'MAX') return max
|
|
290
294
|
}
|
|
291
295
|
|
|
296
|
+
if (funcName === 'MIN_BY' || funcName === 'ARG_MIN' || funcName === 'MAX_BY' || funcName === 'ARG_MAX') {
|
|
297
|
+
// Returns the value at the row with the smallest (or largest) key.
|
|
298
|
+
// Rows with a null value or key are ignored, and ties keep the first row.
|
|
299
|
+
const isMin = funcName === 'MIN_BY' || funcName === 'ARG_MIN'
|
|
300
|
+
const values = await evaluateAll(argNode, filteredRows, context)
|
|
301
|
+
const keys = await evaluateAll(node.args[1], filteredRows, context)
|
|
302
|
+
/** @type {SqlPrimitive} */
|
|
303
|
+
let best = null
|
|
304
|
+
/** @type {SqlPrimitive} */
|
|
305
|
+
let bestKey = null
|
|
306
|
+
for (let i = 0; i < values.length; i++) {
|
|
307
|
+
const value = values[i]
|
|
308
|
+
const key = keys[i]
|
|
309
|
+
if (value == null || key == null) continue
|
|
310
|
+
if (bestKey === null || (isMin ? key < bestKey : key > bestKey)) {
|
|
311
|
+
best = value
|
|
312
|
+
bestKey = key
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return best
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (funcName === 'ANY_VALUE') {
|
|
319
|
+
// Returns an arbitrary non-null value from the group; we pick the first
|
|
320
|
+
const values = await evaluateAll(argNode, filteredRows, context)
|
|
321
|
+
for (const v of values) {
|
|
322
|
+
if (v != null) return v
|
|
323
|
+
}
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
292
327
|
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
293
328
|
const rawValues = await evaluateAll(argNode, filteredRows, context)
|
|
294
329
|
let sum = 0
|
|
@@ -633,7 +668,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
633
668
|
})
|
|
634
669
|
}
|
|
635
670
|
|
|
636
|
-
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY' || funcName === 'JSON_EXTRACT') {
|
|
671
|
+
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY' || funcName === 'JSON_EXTRACT' || funcName === 'JSON_EXTRACT_STRING') {
|
|
637
672
|
let jsonArg = args[0]
|
|
638
673
|
const pathArg = args[1]
|
|
639
674
|
if (jsonArg == null || pathArg == null) return null
|
|
@@ -682,6 +717,11 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
682
717
|
}
|
|
683
718
|
|
|
684
719
|
if (current == null) return null
|
|
720
|
+
// JSON_EXTRACT_STRING returns text: unquoted scalars, JSON text for objects and arrays
|
|
721
|
+
if (funcName === 'JSON_EXTRACT_STRING') {
|
|
722
|
+
if (typeof current === 'object') return JSON.stringify(current)
|
|
723
|
+
return String(current)
|
|
724
|
+
}
|
|
685
725
|
return current
|
|
686
726
|
}
|
|
687
727
|
|
|
@@ -707,6 +747,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
707
747
|
}
|
|
708
748
|
// Can only cast primitives (and Dates) to other primitive types
|
|
709
749
|
if (typeof val === 'object' && !(val instanceof Date)) {
|
|
750
|
+
if (node.tryCast) return null
|
|
710
751
|
throw new ExecutionError({ message: `Cannot CAST object to ${toType}`, rowIndex, ...node })
|
|
711
752
|
}
|
|
712
753
|
if (toType === 'INTEGER' || toType === 'INT') {
|
|
@@ -717,7 +758,8 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
717
758
|
if (toType === 'BIGINT') {
|
|
718
759
|
if (typeof val === 'bigint') return val
|
|
719
760
|
const num = Number(val)
|
|
720
|
-
|
|
761
|
+
// NaN and Infinity have no bigint representation
|
|
762
|
+
if (!isFinite(num)) return null
|
|
721
763
|
return BigInt(Math.trunc(num))
|
|
722
764
|
}
|
|
723
765
|
if (toType === 'FLOAT' || toType === 'REAL' || toType === 'DOUBLE') {
|
|
@@ -738,19 +780,28 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
738
780
|
}
|
|
739
781
|
}
|
|
740
782
|
|
|
741
|
-
// IN and NOT IN with value lists
|
|
783
|
+
// IN and NOT IN with value lists. IN is a chain of OR'd equalities, so it
|
|
784
|
+
// inherits their three-valued logic: a null on either side of any equality
|
|
785
|
+
// makes a non-match UNKNOWN rather than false, and NOT above it must not
|
|
786
|
+
// turn that into true
|
|
742
787
|
if (node.type === 'in valuelist') {
|
|
743
788
|
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
789
|
+
let sawNull = exprVal == null
|
|
744
790
|
for (const valueNode of node.values) {
|
|
745
791
|
const val = await evaluateExpr({ node: valueNode, row, rowIndex, rows, context })
|
|
746
|
-
if (
|
|
792
|
+
if (val == null) {
|
|
793
|
+
sawNull = true
|
|
794
|
+
} else if (exprVal != null && sqlEquals(exprVal, val)) {
|
|
795
|
+
return true
|
|
796
|
+
}
|
|
747
797
|
}
|
|
748
|
-
return false
|
|
798
|
+
return sawNull ? null : false
|
|
749
799
|
}
|
|
750
|
-
// IN with subqueries
|
|
800
|
+
// IN with subqueries, same three-valued logic as the value-list form
|
|
751
801
|
if (node.type === 'in') {
|
|
752
802
|
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
753
803
|
const subResult = executeStatement({ query: node.subquery, context })
|
|
804
|
+
let sawNull = false
|
|
754
805
|
let innerCount = 0
|
|
755
806
|
for await (const resRow of subResult.rows()) {
|
|
756
807
|
if (++innerCount % YIELD_INTERVAL === 0) {
|
|
@@ -758,9 +809,13 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
758
809
|
context.signal?.throwIfAborted()
|
|
759
810
|
}
|
|
760
811
|
const value = await resRow.cells[resRow.columns[0]]()
|
|
761
|
-
if (
|
|
812
|
+
if (exprVal == null || value == null) {
|
|
813
|
+
sawNull = true
|
|
814
|
+
} else if (exprVal != null && sqlEquals(exprVal, value)) {
|
|
815
|
+
return true
|
|
816
|
+
}
|
|
762
817
|
}
|
|
763
|
-
return false
|
|
818
|
+
return sawNull ? null : false
|
|
764
819
|
}
|
|
765
820
|
|
|
766
821
|
// EXISTS and NOT EXISTS with subqueries
|
package/src/expression/regexp.js
CHANGED
|
@@ -78,7 +78,7 @@ export function evaluateRegexpFunc({ funcName, node, args, rowIndex }) {
|
|
|
78
78
|
return null
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
if (funcName === 'REGEXP_MATCHES') {
|
|
81
|
+
if (funcName === 'REGEXP_MATCHES' || funcName === 'REGEXP_LIKE') {
|
|
82
82
|
const str = args[0]
|
|
83
83
|
const pattern = args[1]
|
|
84
84
|
if (str == null || pattern == null) return null
|
package/src/parse/primary.js
CHANGED
|
@@ -125,8 +125,8 @@ function parsePrimaryBase(state) {
|
|
|
125
125
|
const next = peekToken(state, 1)
|
|
126
126
|
const funcNameUpper = tok.value.toUpperCase()
|
|
127
127
|
|
|
128
|
-
// CAST(expr AS type)
|
|
129
|
-
if (funcNameUpper === 'CAST' && next.type === 'paren' && next.value === '(') {
|
|
128
|
+
// CAST(expr AS type) and TRY_CAST(expr AS type)
|
|
129
|
+
if ((funcNameUpper === 'CAST' || funcNameUpper === 'TRY_CAST') && next.type === 'paren' && next.value === '(') {
|
|
130
130
|
consume(state) // CAST
|
|
131
131
|
consume(state) // '('
|
|
132
132
|
const expr = parseExpression(state)
|
|
@@ -145,6 +145,7 @@ function parsePrimaryBase(state) {
|
|
|
145
145
|
type: 'cast',
|
|
146
146
|
expr,
|
|
147
147
|
toType,
|
|
148
|
+
tryCast: funcNameUpper === 'TRY_CAST' ? true : undefined,
|
|
148
149
|
positionStart,
|
|
149
150
|
positionEnd: state.lastPos,
|
|
150
151
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -137,9 +137,9 @@ export interface UserDefinedFunction {
|
|
|
137
137
|
arguments: FunctionSignature
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
export type AggregateFunc = 'COUNT' | 'COUNTIF' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'ARRAY_AGG' | 'LIST' | 'JSON_ARRAYAGG' | 'STDDEV_SAMP' | 'STDDEV_POP' | 'MEDIAN' | 'PERCENTILE_CONT' | 'APPROX_QUANTILE' | 'STRING_AGG'
|
|
140
|
+
export type AggregateFunc = 'COUNT' | 'COUNTIF' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'MIN_BY' | 'ARG_MIN' | 'MAX_BY' | 'ARG_MAX' | 'ANY_VALUE' | 'ARRAY_AGG' | 'LIST' | 'JSON_ARRAYAGG' | 'STDDEV_SAMP' | 'STDDEV_POP' | 'MEDIAN' | 'PERCENTILE_CONT' | 'APPROX_QUANTILE' | 'STRING_AGG'
|
|
141
141
|
|
|
142
|
-
export type RegExpFunction = 'REGEXP_SUBSTR' | 'REGEXP_EXTRACT' | 'REGEXP_REPLACE' | 'REGEXP_MATCHES'
|
|
142
|
+
export type RegExpFunction = 'REGEXP_SUBSTR' | 'REGEXP_EXTRACT' | 'REGEXP_REPLACE' | 'REGEXP_MATCHES' | 'REGEXP_LIKE'
|
|
143
143
|
|
|
144
144
|
export type MathFunc =
|
|
145
145
|
| 'FLOOR'
|
|
@@ -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', 'COUNTIF', 'SUM', 'AVG', 'MIN', 'MAX', 'ARRAY_AGG', 'LIST', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP', 'MEDIAN', 'PERCENTILE_CONT', 'APPROX_QUANTILE', 'STRING_AGG'].includes(name)
|
|
14
|
+
return ['COUNT', 'COUNTIF', 'SUM', 'AVG', 'MIN', 'MAX', 'MIN_BY', 'ARG_MIN', 'MAX_BY', 'ARG_MAX', 'ANY_VALUE', 'ARRAY_AGG', 'LIST', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP', 'MEDIAN', 'PERCENTILE_CONT', 'APPROX_QUANTILE', 'STRING_AGG'].includes(name)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -39,7 +39,7 @@ export function isWindowFunc(name) {
|
|
|
39
39
|
* @returns {name is RegExpFunction}
|
|
40
40
|
*/
|
|
41
41
|
export function isRegexpFunc(name) {
|
|
42
|
-
return ['REGEXP_SUBSTR', 'REGEXP_EXTRACT', 'REGEXP_REPLACE', 'REGEXP_MATCHES'].includes(name)
|
|
42
|
+
return ['REGEXP_SUBSTR', 'REGEXP_EXTRACT', 'REGEXP_REPLACE', 'REGEXP_MATCHES', 'REGEXP_LIKE'].includes(name)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -134,6 +134,7 @@ export const FUNCTION_SIGNATURES = {
|
|
|
134
134
|
REGEXP_EXTRACT: { min: 2, max: 4, signature: 'string, pattern[, position[, occurrence]]' },
|
|
135
135
|
REGEXP_REPLACE: { min: 3, max: 5, signature: 'string, pattern, replacement[, position[, occurrence]]' },
|
|
136
136
|
REGEXP_MATCHES: { min: 2, max: 2, signature: 'string, pattern' },
|
|
137
|
+
REGEXP_LIKE: { min: 2, max: 2, signature: 'string, pattern' },
|
|
137
138
|
|
|
138
139
|
// Date/time functions
|
|
139
140
|
RANDOM: { min: 0, max: 0, signature: '' },
|
|
@@ -178,6 +179,7 @@ export const FUNCTION_SIGNATURES = {
|
|
|
178
179
|
JSON_VALUE: { min: 2, max: 2, signature: 'expression, path' },
|
|
179
180
|
JSON_QUERY: { min: 2, max: 2, signature: 'expression, path' },
|
|
180
181
|
JSON_EXTRACT: { min: 2, max: 2, signature: 'expression, path' },
|
|
182
|
+
JSON_EXTRACT_STRING: { min: 2, max: 2, signature: 'expression, path' },
|
|
181
183
|
JSON_OBJECT: { min: 0, signature: 'key1, value1[, ...]' },
|
|
182
184
|
JSON_ARRAY_LENGTH: { min: 1, max: 1, signature: 'array' },
|
|
183
185
|
JSON_VALID: { min: 1, max: 1, signature: 'value' },
|
|
@@ -221,6 +223,11 @@ export const FUNCTION_SIGNATURES = {
|
|
|
221
223
|
AVG: { min: 1, max: 1, signature: 'expression' },
|
|
222
224
|
MIN: { min: 1, max: 1, signature: 'expression' },
|
|
223
225
|
MAX: { min: 1, max: 1, signature: 'expression' },
|
|
226
|
+
MIN_BY: { min: 2, max: 2, signature: 'value, key' },
|
|
227
|
+
ARG_MIN: { min: 2, max: 2, signature: 'value, key' },
|
|
228
|
+
MAX_BY: { min: 2, max: 2, signature: 'value, key' },
|
|
229
|
+
ARG_MAX: { min: 2, max: 2, signature: 'value, key' },
|
|
230
|
+
ANY_VALUE: { min: 1, max: 1, signature: 'expression' },
|
|
224
231
|
STDDEV_SAMP: { min: 1, max: 1, signature: 'expression' },
|
|
225
232
|
STDDEV_POP: { min: 1, max: 1, signature: 'expression' },
|
|
226
233
|
MEDIAN: { min: 1, max: 1, signature: 'expression' },
|