squirreling 0.4.5 → 0.4.6
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/execute/expression.js +8 -38
- package/src/execute/having.js +6 -26
- package/src/execute/utils.js +36 -3
- package/src/validation.js +2 -2
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { executeSelect } from './execute.js'
|
|
2
|
+
import { applyBinaryOp } from './utils.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource } from '../types.js'
|
|
@@ -59,55 +60,24 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
59
60
|
if (node.op === '-') {
|
|
60
61
|
const val = await evaluateExpr({ node: node.argument, row, tables })
|
|
61
62
|
if (val == null) return null
|
|
62
|
-
return -
|
|
63
|
+
return -val
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// Binary operators
|
|
67
68
|
if (node.type === 'binary') {
|
|
69
|
+
const left = await evaluateExpr({ node: node.left, row, tables })
|
|
70
|
+
|
|
71
|
+
// Short-circuit evaluation for AND and OR
|
|
68
72
|
if (node.op === 'AND') {
|
|
69
|
-
|
|
70
|
-
if (!leftVal) return false
|
|
71
|
-
return Boolean(await evaluateExpr({ node: node.right, row, tables }))
|
|
73
|
+
if (!left) return false
|
|
72
74
|
}
|
|
73
|
-
|
|
74
75
|
if (node.op === 'OR') {
|
|
75
|
-
|
|
76
|
-
if (leftVal) return true
|
|
77
|
-
return Boolean(await evaluateExpr({ node: node.right, row, tables }))
|
|
76
|
+
if (left) return true
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
const left = await evaluateExpr({ node: node.left, row, tables })
|
|
81
79
|
const right = await evaluateExpr({ node: node.right, row, tables })
|
|
82
|
-
|
|
83
|
-
// In SQL, NULL comparisons with =, !=, <> always return false (unknown)
|
|
84
|
-
// You must use IS NULL or IS NOT NULL to check for NULL
|
|
85
|
-
if (left == null || right == null) {
|
|
86
|
-
if (node.op === '=' || node.op === '!=' || node.op === '<>') {
|
|
87
|
-
return false
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (node.op === '=') return left === right
|
|
92
|
-
if (node.op === '!=' || node.op === '<>') return left !== right
|
|
93
|
-
if (node.op === '<') return left < right
|
|
94
|
-
if (node.op === '>') return left > right
|
|
95
|
-
if (node.op === '<=') return left <= right
|
|
96
|
-
if (node.op === '>=') return left >= right
|
|
97
|
-
|
|
98
|
-
if (node.op === 'LIKE') {
|
|
99
|
-
const str = String(left)
|
|
100
|
-
const pattern = String(right)
|
|
101
|
-
// Convert SQL LIKE pattern to regex
|
|
102
|
-
// % matches zero or more characters
|
|
103
|
-
// _ matches exactly one character
|
|
104
|
-
const regexPattern = pattern
|
|
105
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
106
|
-
.replace(/%/g, '.*') // Replace % with .*
|
|
107
|
-
.replace(/_/g, '.') // Replace _ with .
|
|
108
|
-
const regex = new RegExp('^' + regexPattern + '$', 'i')
|
|
109
|
-
return regex.test(str)
|
|
110
|
-
}
|
|
80
|
+
return applyBinaryOp(node.op, left, right)
|
|
111
81
|
}
|
|
112
82
|
|
|
113
83
|
// Function calls
|
package/src/execute/having.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAggregateFunc } from '../validation.js'
|
|
2
2
|
import { evaluateExpr } from './expression.js'
|
|
3
|
+
import { applyBinaryOp } from './utils.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @import { AggregateFunc, AsyncDataSource, ExprNode, AsyncRow, SqlPrimitive } from '../types.js'
|
|
@@ -30,38 +31,17 @@ export async function evaluateHavingExpr(expr, row, group, tables) {
|
|
|
30
31
|
|
|
31
32
|
if (expr.type === 'binary') {
|
|
32
33
|
const left = await evaluateHavingValue(expr.left, context, group, tables)
|
|
33
|
-
const right = await evaluateHavingValue(expr.right, context, group, tables)
|
|
34
34
|
|
|
35
|
+
// Short-circuit evaluation for AND and OR
|
|
35
36
|
if (expr.op === 'AND') {
|
|
36
|
-
|
|
37
|
+
if (!left) return false
|
|
37
38
|
}
|
|
38
39
|
if (expr.op === 'OR') {
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Handle NULL comparisons
|
|
43
|
-
if (left == null || right == null) {
|
|
44
|
-
if (expr.op === '=' || expr.op === '!=' || expr.op === '<>') {
|
|
45
|
-
return false
|
|
46
|
-
}
|
|
40
|
+
if (left) return true
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (expr.op === '<') return left < right
|
|
52
|
-
if (expr.op === '>') return left > right
|
|
53
|
-
if (expr.op === '<=') return left <= right
|
|
54
|
-
if (expr.op === '>=') return left >= right
|
|
55
|
-
if (expr.op === 'LIKE') {
|
|
56
|
-
const str = String(left)
|
|
57
|
-
const pattern = String(right)
|
|
58
|
-
const regexPattern = pattern
|
|
59
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
60
|
-
.replace(/%/g, '.*')
|
|
61
|
-
.replace(/_/g, '.')
|
|
62
|
-
const regex = new RegExp('^' + regexPattern + '$', 'i')
|
|
63
|
-
return regex.test(str)
|
|
64
|
-
}
|
|
43
|
+
const right = await evaluateHavingValue(expr.right, context, group, tables)
|
|
44
|
+
return applyBinaryOp(expr.op, left, right)
|
|
65
45
|
}
|
|
66
46
|
|
|
67
47
|
if (expr.type === 'unary') {
|
package/src/execute/utils.js
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {AsyncRow, ExprNode, OrderByItem, SqlPrimitive} from '../types.js'
|
|
2
|
+
* @import {AsyncRow, BinaryOp, ExprNode, OrderByItem, SqlPrimitive} from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Compares two values with the given operator, handling nulls according to SQL semantics
|
|
7
|
+
*
|
|
8
|
+
* @param {BinaryOp} op
|
|
9
|
+
* @param {SqlPrimitive} a
|
|
10
|
+
* @param {SqlPrimitive} b
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function applyBinaryOp(op, a, b) {
|
|
14
|
+
if (a == null || b == null) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
if (op === 'AND') return Boolean(a) && Boolean(b)
|
|
18
|
+
if (op === 'OR') return Boolean(a) || Boolean(b)
|
|
19
|
+
if (op === '!=' || op === '<>') return a != b
|
|
20
|
+
if (op === '=') return a == b
|
|
21
|
+
if (op === '<') return a < b
|
|
22
|
+
if (op === '<=') return a <= b
|
|
23
|
+
if (op === '>') return a > b
|
|
24
|
+
if (op === '>=') return a >= b
|
|
25
|
+
|
|
26
|
+
if (op === 'LIKE') {
|
|
27
|
+
const str = String(a)
|
|
28
|
+
const pattern = String(b)
|
|
29
|
+
const regexPattern = pattern
|
|
30
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
31
|
+
.replace(/%/g, '.*')
|
|
32
|
+
.replace(/_/g, '.')
|
|
33
|
+
const regex = new RegExp(`^${regexPattern}$`, 'i')
|
|
34
|
+
return regex.test(str)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
5
38
|
/**
|
|
6
39
|
* Compares two values for a single ORDER BY term, handling nulls and direction
|
|
7
40
|
*
|
|
8
41
|
* @param {SqlPrimitive} a
|
|
9
42
|
* @param {SqlPrimitive} b
|
|
10
43
|
* @param {OrderByItem} term
|
|
11
|
-
* @returns {number}
|
|
44
|
+
* @returns {number}
|
|
12
45
|
*/
|
|
13
46
|
export function compareForTerm(a, b, term) {
|
|
14
47
|
const aIsNull = a == null
|
|
@@ -22,7 +55,7 @@ export function compareForTerm(a, b, term) {
|
|
|
22
55
|
}
|
|
23
56
|
|
|
24
57
|
// Compare non-null values
|
|
25
|
-
if (a
|
|
58
|
+
if (a == b) return 0
|
|
26
59
|
|
|
27
60
|
const primitives = ['number', 'bigint', 'boolean', 'string']
|
|
28
61
|
let cmp
|
package/src/validation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
* @import {AggregateFunc, BinaryOp, StringFunc} from './types.js'
|
|
3
|
+
* @import {AggregateFunc, BinaryOp, ComparisonOp, StringFunc} from './types.js'
|
|
4
4
|
* @param {string} name
|
|
5
5
|
* @returns {name is AggregateFunc}
|
|
6
6
|
*/
|
|
@@ -21,7 +21,7 @@ export function isStringFunc(name) {
|
|
|
21
21
|
* @returns {op is BinaryOp}
|
|
22
22
|
*/
|
|
23
23
|
export function isBinaryOp(op) {
|
|
24
|
-
return ['=', '!=', '<>', '<', '>', '<=', '>='].includes(op)
|
|
24
|
+
return ['AND', 'OR', 'LIKE', '=', '!=', '<>', '<', '>', '<=', '>='].includes(op)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Keywords that cannot be used as implicit aliases after a column
|