squirreling 0.2.4 → 0.2.5
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/execute.js +49 -49
- package/src/execute/expression.js +4 -4
- package/src/parse/expression.js +10 -6
- package/src/parse/parse.js +41 -75
- package/src/parse/tokenize.js +1 -0
- package/src/types.d.ts +27 -37
- package/src/validation.js +1 -1
package/package.json
CHANGED
package/src/execute/execute.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { DataSource, ExecuteSqlOptions, FunctionColumn, FunctionNode, OrderByItem, RowSource, SelectStatement, SqlPrimitive } from '../types.js'
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import { defaultAggregateAlias, evaluateAggregate } from './aggregates.js'
|
|
6
2
|
import { evaluateExpr } from './expression.js'
|
|
7
3
|
import { evaluateHavingExpr } from './having.js'
|
|
8
4
|
import { parseSql } from '../parse/parse.js'
|
|
9
5
|
import { createMemorySource, createRowAccessor } from '../backend/memory.js'
|
|
10
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @import { DataSource, ExecuteSqlOptions, ExprNode, OrderByItem, RowSource, SelectStatement, SqlPrimitive } from '../types.js'
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
11
|
/**
|
|
12
12
|
* Executes a SQL SELECT query against a data source
|
|
13
13
|
*
|
|
@@ -21,21 +21,31 @@ export function executeSql({ source, query }) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Generates a default alias
|
|
24
|
+
* Generates a default alias for a derived column expression
|
|
25
25
|
*
|
|
26
|
-
* @param {
|
|
27
|
-
* @returns {string} the generated alias
|
|
26
|
+
* @param {ExprNode} expr - the expression node
|
|
27
|
+
* @returns {string} the generated alias
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const columnNames = col.args
|
|
33
|
-
.filter(arg => arg.type === 'identifier')
|
|
34
|
-
.map(arg => arg.name)
|
|
35
|
-
if (columnNames.length > 0) {
|
|
36
|
-
return base + '_' + columnNames.join('_')
|
|
29
|
+
function defaultDerivedAlias(expr) {
|
|
30
|
+
if (expr.type === 'identifier') {
|
|
31
|
+
return expr.name
|
|
37
32
|
}
|
|
38
|
-
|
|
33
|
+
if (expr.type === 'function') {
|
|
34
|
+
const base = expr.name.toLowerCase()
|
|
35
|
+
// Try to extract column names from identifier arguments
|
|
36
|
+
const columnNames = expr.args
|
|
37
|
+
.filter(arg => arg.type === 'identifier')
|
|
38
|
+
.map(arg => arg.name)
|
|
39
|
+
if (columnNames.length > 0) {
|
|
40
|
+
return base + '_' + columnNames.join('_')
|
|
41
|
+
}
|
|
42
|
+
return base
|
|
43
|
+
}
|
|
44
|
+
if (expr.type === 'cast') return 'cast_expr'
|
|
45
|
+
if (expr.type === 'unary' && expr.argument.type === 'identifier') {
|
|
46
|
+
return expr.op === '-' ? 'neg_' + expr.argument.name : 'expr'
|
|
47
|
+
}
|
|
48
|
+
return 'expr'
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
/**
|
|
@@ -118,6 +128,24 @@ function applyOrderBy(rows, orderBy) {
|
|
|
118
128
|
const dir = term.direction
|
|
119
129
|
const av = evaluateExpr(term.expr, createRowAccessor(a))
|
|
120
130
|
const bv = evaluateExpr(term.expr, createRowAccessor(b))
|
|
131
|
+
|
|
132
|
+
// Handle NULLS FIRST / NULLS LAST
|
|
133
|
+
const aIsNull = av == null
|
|
134
|
+
const bIsNull = bv == null
|
|
135
|
+
|
|
136
|
+
if (aIsNull || bIsNull) {
|
|
137
|
+
if (aIsNull && bIsNull) continue // both null, try next sort term
|
|
138
|
+
|
|
139
|
+
// Determine null ordering
|
|
140
|
+
const nullsFirst = term.nulls === 'LAST' ? false : true // default is NULLS FIRST
|
|
141
|
+
|
|
142
|
+
if (aIsNull) {
|
|
143
|
+
return nullsFirst ? -1 : 1
|
|
144
|
+
} else {
|
|
145
|
+
return nullsFirst ? 1 : -1
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
const cmp = compareValues(av, bv)
|
|
122
150
|
if (cmp !== 0) {
|
|
123
151
|
return dir === 'DESC' ? -cmp : cmp
|
|
@@ -214,20 +242,9 @@ function evaluateSelectAst(select, dataSource) {
|
|
|
214
242
|
continue
|
|
215
243
|
}
|
|
216
244
|
|
|
217
|
-
if (col.kind === '
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
// Evaluate on first row of group (all rows have same value for GROUP BY columns)
|
|
221
|
-
resultRow[alias] = group[0]?.getCell(name)
|
|
222
|
-
continue
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (col.kind === 'function') {
|
|
226
|
-
// Evaluate function on the first row of the group
|
|
227
|
-
/** @type {FunctionNode} */
|
|
228
|
-
const funcNode = { type: 'function', name: col.func, args: col.args }
|
|
229
|
-
const alias = col.alias ?? defaultFunctionAlias(col)
|
|
230
|
-
const value = group.length > 0 ? evaluateExpr(funcNode, group[0]) : undefined
|
|
245
|
+
if (col.kind === 'derived') {
|
|
246
|
+
const alias = col.alias ?? defaultDerivedAlias(col.expr)
|
|
247
|
+
const value = group.length > 0 ? evaluateExpr(col.expr, group[0]) : undefined
|
|
231
248
|
resultRow[alias] = value
|
|
232
249
|
continue
|
|
233
250
|
}
|
|
@@ -238,13 +255,6 @@ function evaluateSelectAst(select, dataSource) {
|
|
|
238
255
|
resultRow[alias] = value
|
|
239
256
|
continue
|
|
240
257
|
}
|
|
241
|
-
|
|
242
|
-
if (col.kind === 'operation') {
|
|
243
|
-
const alias = col.alias ?? 'expr'
|
|
244
|
-
const value = group.length > 0 ? evaluateExpr(col.expr, group[0]) : undefined
|
|
245
|
-
resultRow[alias] = value
|
|
246
|
-
continue
|
|
247
|
-
}
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
// Apply HAVING filter before adding to projected results
|
|
@@ -269,18 +279,8 @@ function evaluateSelectAst(select, dataSource) {
|
|
|
269
279
|
for (const key of keys) {
|
|
270
280
|
outRow[key] = row.getCell(key)
|
|
271
281
|
}
|
|
272
|
-
} else if (col.kind === '
|
|
273
|
-
const
|
|
274
|
-
const alias = col.alias ?? name
|
|
275
|
-
outRow[alias] = row.getCell(name)
|
|
276
|
-
} else if (col.kind === 'function') {
|
|
277
|
-
/** @type {FunctionNode} */
|
|
278
|
-
const funcNode = { type: 'function', name: col.func, args: col.args }
|
|
279
|
-
const value = evaluateExpr(funcNode, row)
|
|
280
|
-
const alias = col.alias ?? defaultFunctionAlias(col)
|
|
281
|
-
outRow[alias] = value
|
|
282
|
-
} else if (col.kind === 'operation') {
|
|
283
|
-
const alias = col.alias ?? 'expr'
|
|
282
|
+
} else if (col.kind === 'derived') {
|
|
283
|
+
const alias = col.alias ?? defaultDerivedAlias(col.expr)
|
|
284
284
|
const value = evaluateExpr(col.expr, row)
|
|
285
285
|
outRow[alias] = value
|
|
286
286
|
} else if (col.kind === 'aggregate') {
|
|
@@ -131,23 +131,23 @@ export function evaluateExpr(node, row) {
|
|
|
131
131
|
return String(val).length
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
if (funcName === 'SUBSTRING') {
|
|
134
|
+
if (funcName === 'SUBSTRING' || funcName === 'SUBSTR') {
|
|
135
135
|
if (args.length < 2 || args.length > 3) {
|
|
136
|
-
throw new Error(
|
|
136
|
+
throw new Error(`${funcName} requires 2 or 3 arguments`)
|
|
137
137
|
}
|
|
138
138
|
const str = args[0]
|
|
139
139
|
if (str == null) return null
|
|
140
140
|
const strVal = String(str)
|
|
141
141
|
const start = Number(args[1])
|
|
142
142
|
if (!Number.isInteger(start) || start < 1) {
|
|
143
|
-
throw new Error(
|
|
143
|
+
throw new Error(`${funcName} start position must be a positive integer`)
|
|
144
144
|
}
|
|
145
145
|
// SQL uses 1-based indexing
|
|
146
146
|
const startIdx = start - 1
|
|
147
147
|
if (args.length === 3) {
|
|
148
148
|
const len = Number(args[2])
|
|
149
149
|
if (!Number.isInteger(len) || len < 0) {
|
|
150
|
-
throw new Error(
|
|
150
|
+
throw new Error(`${funcName} length must be a non-negative integer`)
|
|
151
151
|
}
|
|
152
152
|
return strVal.substring(startIdx, startIdx + len)
|
|
153
153
|
}
|
package/src/parse/expression.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isAggregateFunc, isStringFunc } from '../validation.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* @import { ExprCursor, ExprNode, BinaryOp
|
|
4
|
+
* @import { ExprCursor, ExprNode, BinaryOp } from '../types.js'
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -11,13 +13,10 @@ export function parseExpression(c) {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* Exposed so SELECT list parsing can reuse the same notion of "primary"
|
|
15
|
-
* for function arguments, etc.
|
|
16
|
-
*
|
|
17
16
|
* @param {ExprCursor} c
|
|
18
17
|
* @returns {ExprNode}
|
|
19
18
|
*/
|
|
20
|
-
|
|
19
|
+
function parsePrimary(c) {
|
|
21
20
|
const tok = c.current()
|
|
22
21
|
|
|
23
22
|
if (tok.type === 'paren' && tok.value === '(') {
|
|
@@ -48,7 +47,12 @@ export function parsePrimary(c) {
|
|
|
48
47
|
// function call
|
|
49
48
|
if (next.type === 'paren' && next.value === '(') {
|
|
50
49
|
const funcName = tok.value
|
|
51
|
-
|
|
50
|
+
|
|
51
|
+
// validate function names
|
|
52
|
+
if (!isStringFunc(funcName) && !isAggregateFunc(funcName)) {
|
|
53
|
+
throw new Error(`Unknown function "${funcName}" at position ${tok.position}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
c.consume() // function name
|
|
53
57
|
c.consume() // '('
|
|
54
58
|
|
package/src/parse/parse.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { tokenize } from './tokenize.js'
|
|
2
|
+
import { parseExpression } from './expression.js'
|
|
3
|
+
import { isAggregateFunc } from '../validation.js'
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
* @import { AggregateColumn, AggregateArg, AggregateFunc, ExprCursor, ExprNode, FromSubquery, JoinClause, JoinType, OrderByItem, ParserState, SelectStatement, SelectColumn,
|
|
6
|
+
* @import { AggregateColumn, AggregateArg, AggregateFunc, ExprCursor, ExprNode, FromSubquery, JoinClause, JoinType, OrderByItem, ParserState, SelectStatement, SelectColumn, Token, TokenType } from '../types.js'
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
|
-
import { tokenize } from './tokenize.js'
|
|
6
|
-
import { parseExpression, parsePrimary } from './expression.js'
|
|
7
|
-
import { isAggregateFunc, isStringFunc } from '../validation.js'
|
|
8
|
-
|
|
9
9
|
// Keywords that cannot be used as implicit aliases after a column
|
|
10
10
|
const RESERVED_AFTER_COLUMN = new Set([
|
|
11
11
|
'FROM',
|
|
@@ -112,6 +112,7 @@ function expectIdentifier(state) {
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* Creates an ExprCursor adapter for the ParserState.
|
|
115
|
+
*
|
|
115
116
|
* @param {ParserState} state
|
|
116
117
|
* @returns {ExprCursor}
|
|
117
118
|
*/
|
|
@@ -141,6 +142,20 @@ function parseSelectList(state) {
|
|
|
141
142
|
const cols = []
|
|
142
143
|
const tok = current(state)
|
|
143
144
|
|
|
145
|
+
// Check for qualified asterisk (table.*)
|
|
146
|
+
if (tok.type === 'identifier') {
|
|
147
|
+
const next = peekToken(state, 1)
|
|
148
|
+
const nextNext = peekToken(state, 2)
|
|
149
|
+
if (next.type === 'dot' && nextNext.type === 'operator' && nextNext.value === '*') {
|
|
150
|
+
const tableTok = consume(state) // consume table name
|
|
151
|
+
consume(state) // consume dot
|
|
152
|
+
consume(state) // consume asterisk
|
|
153
|
+
cols.push({ kind: 'star', table: tableTok.value })
|
|
154
|
+
return cols
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check for unqualified asterisk (*)
|
|
144
159
|
if (tok.type === 'operator' && tok.value === '*') {
|
|
145
160
|
consume(state)
|
|
146
161
|
cols.push({ kind: 'star' })
|
|
@@ -166,56 +181,20 @@ function parseSelectItem(state) {
|
|
|
166
181
|
throw parseError(state, 'column name or expression')
|
|
167
182
|
}
|
|
168
183
|
|
|
169
|
-
if (tok.type === 'identifier' && tok.value === 'CAST') {
|
|
170
|
-
expectIdentifier(state) // consume CAST
|
|
171
|
-
expect(state, 'paren', '(')
|
|
172
|
-
const cursor = createExprCursor(state)
|
|
173
|
-
const expr = parseExpression(cursor)
|
|
174
|
-
expect(state, 'keyword', 'AS')
|
|
175
|
-
const typeTok = expectIdentifier(state)
|
|
176
|
-
expect(state, 'paren', ')')
|
|
177
|
-
const alias = parseAs(state)
|
|
178
|
-
return {
|
|
179
|
-
kind: 'operation',
|
|
180
|
-
expr: { type: 'cast', expr, toType: typeTok.value },
|
|
181
|
-
alias,
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (tok.type === 'operator') {
|
|
186
|
-
// Handle SELECT expression AS alias
|
|
187
|
-
const cursor = createExprCursor(state)
|
|
188
|
-
const expr = parseExpression(cursor)
|
|
189
|
-
const alias = parseAs(state)
|
|
190
|
-
return { kind: 'operation', expr, alias }
|
|
191
|
-
}
|
|
192
|
-
|
|
193
184
|
const next = peekToken(state, 1)
|
|
194
|
-
const upper = tok.value.toUpperCase()
|
|
195
|
-
|
|
196
185
|
if (next.type === 'paren' && next.value === '(') {
|
|
197
|
-
|
|
186
|
+
const upper = tok.value.toUpperCase()
|
|
198
187
|
if (isAggregateFunc(upper)) {
|
|
188
|
+
expectIdentifier(state) // consume function name
|
|
199
189
|
return parseAggregateItem(state, upper)
|
|
200
190
|
}
|
|
201
|
-
if (isStringFunc(upper)) {
|
|
202
|
-
return parseStringFunctionItem(state, upper)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
consume(state)
|
|
207
|
-
let column = tok.value
|
|
208
|
-
|
|
209
|
-
// Handle dot notation (table.column)
|
|
210
|
-
if (current(state).type === 'dot') {
|
|
211
|
-
consume(state) // consume the dot
|
|
212
|
-
const columnTok = expectIdentifier(state)
|
|
213
|
-
column += '.' + columnTok.value
|
|
214
191
|
}
|
|
215
192
|
|
|
193
|
+
// Delegate to expression parser
|
|
194
|
+
const cursor = createExprCursor(state)
|
|
195
|
+
const expr = parseExpression(cursor)
|
|
216
196
|
const alias = parseAs(state)
|
|
217
|
-
|
|
218
|
-
return { kind: 'column', column, alias }
|
|
197
|
+
return { kind: 'derived', expr, alias }
|
|
219
198
|
}
|
|
220
199
|
|
|
221
200
|
/**
|
|
@@ -261,34 +240,6 @@ function parseAggregateItem(state, func) {
|
|
|
261
240
|
return { kind: 'aggregate', func, arg, alias }
|
|
262
241
|
}
|
|
263
242
|
|
|
264
|
-
/**
|
|
265
|
-
* @param {ParserState} state
|
|
266
|
-
* @param {StringFunc} func
|
|
267
|
-
* @returns {SelectColumn}
|
|
268
|
-
*/
|
|
269
|
-
function parseStringFunctionItem(state, func) {
|
|
270
|
-
expect(state, 'paren', '(')
|
|
271
|
-
|
|
272
|
-
/** @type {ExprNode[]} */
|
|
273
|
-
const args = []
|
|
274
|
-
|
|
275
|
-
// Parse comma-separated arguments
|
|
276
|
-
if (current(state).type !== 'paren' || current(state).value !== ')') {
|
|
277
|
-
const cursor = createExprCursor(state)
|
|
278
|
-
while (true) {
|
|
279
|
-
const arg = parsePrimary(cursor)
|
|
280
|
-
args.push(arg)
|
|
281
|
-
if (!match(state, 'comma')) break
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
expect(state, 'paren', ')')
|
|
286
|
-
|
|
287
|
-
const alias = parseAs(state)
|
|
288
|
-
|
|
289
|
-
return { kind: 'function', func, args, alias }
|
|
290
|
-
}
|
|
291
|
-
|
|
292
243
|
/**
|
|
293
244
|
* @param {ParserState} state
|
|
294
245
|
* @returns {string | undefined}
|
|
@@ -481,9 +432,24 @@ function parseSelectInternal(state) {
|
|
|
481
432
|
} else if (match(state, 'keyword', 'DESC')) {
|
|
482
433
|
direction = 'DESC'
|
|
483
434
|
}
|
|
435
|
+
/** @type {'FIRST' | 'LAST' | undefined} */
|
|
436
|
+
let nulls
|
|
437
|
+
if (match(state, 'keyword', 'NULLS')) {
|
|
438
|
+
const tok = current(state)
|
|
439
|
+
if (tok.type === 'identifier' && tok.value.toUpperCase() === 'FIRST') {
|
|
440
|
+
consume(state)
|
|
441
|
+
nulls = 'FIRST'
|
|
442
|
+
} else if (tok.type === 'identifier' && tok.value.toUpperCase() === 'LAST') {
|
|
443
|
+
consume(state)
|
|
444
|
+
nulls = 'LAST'
|
|
445
|
+
} else {
|
|
446
|
+
throw parseError(state, 'FIRST or LAST after NULLS')
|
|
447
|
+
}
|
|
448
|
+
}
|
|
484
449
|
orderBy.push({
|
|
485
450
|
expr,
|
|
486
451
|
direction,
|
|
452
|
+
nulls,
|
|
487
453
|
})
|
|
488
454
|
if (!match(state, 'comma')) break
|
|
489
455
|
}
|
package/src/parse/tokenize.js
CHANGED
package/src/types.d.ts
CHANGED
|
@@ -34,26 +34,6 @@ export interface SelectStatement {
|
|
|
34
34
|
offset?: number
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export type TokenType =
|
|
38
|
-
| 'keyword'
|
|
39
|
-
| 'identifier'
|
|
40
|
-
| 'number'
|
|
41
|
-
| 'string'
|
|
42
|
-
| 'operator'
|
|
43
|
-
| 'comma'
|
|
44
|
-
| 'dot'
|
|
45
|
-
| 'paren'
|
|
46
|
-
| 'semicolon'
|
|
47
|
-
| 'eof'
|
|
48
|
-
|
|
49
|
-
export interface Token {
|
|
50
|
-
type: TokenType
|
|
51
|
-
value: string
|
|
52
|
-
position: number
|
|
53
|
-
numericValue?: number
|
|
54
|
-
originalValue?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
37
|
export type BinaryOp =
|
|
58
38
|
| 'AND'
|
|
59
39
|
| 'OR'
|
|
@@ -139,18 +119,13 @@ export type ExprNode =
|
|
|
139
119
|
|
|
140
120
|
export interface StarColumn {
|
|
141
121
|
kind: 'star'
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export interface SimpleColumn {
|
|
146
|
-
kind: 'column'
|
|
147
|
-
column: string
|
|
122
|
+
table?: string
|
|
148
123
|
alias?: string
|
|
149
124
|
}
|
|
150
125
|
|
|
151
126
|
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX'
|
|
152
127
|
|
|
153
|
-
export type StringFunc = 'UPPER' | 'LOWER' | 'CONCAT' | 'LENGTH' | 'SUBSTRING' | 'TRIM'
|
|
128
|
+
export type StringFunc = 'UPPER' | 'LOWER' | 'CONCAT' | 'LENGTH' | 'SUBSTRING' | 'SUBSTR' | 'TRIM'
|
|
154
129
|
|
|
155
130
|
export interface AggregateArgStar {
|
|
156
131
|
kind: 'star'
|
|
@@ -170,24 +145,18 @@ export interface AggregateColumn {
|
|
|
170
145
|
alias?: string
|
|
171
146
|
}
|
|
172
147
|
|
|
173
|
-
export interface
|
|
174
|
-
kind: '
|
|
175
|
-
func: StringFunc
|
|
176
|
-
args: ExprNode[]
|
|
177
|
-
alias?: string
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface OperationColumn {
|
|
181
|
-
kind: 'operation'
|
|
148
|
+
export interface DerivedColumn {
|
|
149
|
+
kind: 'derived'
|
|
182
150
|
expr: ExprNode
|
|
183
151
|
alias?: string
|
|
184
152
|
}
|
|
185
153
|
|
|
186
|
-
export type SelectColumn = StarColumn |
|
|
154
|
+
export type SelectColumn = StarColumn | AggregateColumn | DerivedColumn
|
|
187
155
|
|
|
188
156
|
export interface OrderByItem {
|
|
189
157
|
expr: ExprNode
|
|
190
158
|
direction: 'ASC' | 'DESC'
|
|
159
|
+
nulls?: 'FIRST' | 'LAST'
|
|
191
160
|
}
|
|
192
161
|
|
|
193
162
|
export type JoinType = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL' | 'CROSS'
|
|
@@ -212,3 +181,24 @@ export interface ExprCursor {
|
|
|
212
181
|
expectIdentifier(): Token
|
|
213
182
|
parseSubquery?: () => SelectStatement
|
|
214
183
|
}
|
|
184
|
+
|
|
185
|
+
// Tokenizer types
|
|
186
|
+
export type TokenType =
|
|
187
|
+
| 'keyword'
|
|
188
|
+
| 'identifier'
|
|
189
|
+
| 'number'
|
|
190
|
+
| 'string'
|
|
191
|
+
| 'operator'
|
|
192
|
+
| 'comma'
|
|
193
|
+
| 'dot'
|
|
194
|
+
| 'paren'
|
|
195
|
+
| 'semicolon'
|
|
196
|
+
| 'eof'
|
|
197
|
+
|
|
198
|
+
export interface Token {
|
|
199
|
+
type: TokenType
|
|
200
|
+
value: string
|
|
201
|
+
position: number
|
|
202
|
+
numericValue?: number
|
|
203
|
+
originalValue?: string
|
|
204
|
+
}
|
package/src/validation.js
CHANGED
|
@@ -13,5 +13,5 @@ export function isAggregateFunc(name) {
|
|
|
13
13
|
* @returns {name is StringFunc}
|
|
14
14
|
*/
|
|
15
15
|
export function isStringFunc(name) {
|
|
16
|
-
return ['UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'SUBSTRING', 'TRIM'].includes(name)
|
|
16
|
+
return ['UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'SUBSTRING', 'SUBSTR', 'TRIM'].includes(name)
|
|
17
17
|
}
|