squirreling 0.11.1 → 0.11.2
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/aggregates.js +6 -2
- package/src/execute/execute.js +11 -17
- package/src/execute/join.js +7 -18
- package/src/expression/evaluate.js +13 -5
- package/src/plan/columns.js +4 -3
- package/src/plan/plan.js +38 -54
- package/src/validation/planErrors.js +3 -3
- package/src/validation/tables.js +89 -0
package/package.json
CHANGED
|
@@ -26,9 +26,13 @@ function projectAggregateColumns(selectColumns, group, context) {
|
|
|
26
26
|
if (col.type === 'star') {
|
|
27
27
|
const firstRow = group[0]
|
|
28
28
|
if (firstRow) {
|
|
29
|
+
const prefix = col.table ? `${col.table}.` : undefined
|
|
29
30
|
for (const key of firstRow.columns) {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (prefix && !key.startsWith(prefix)) continue
|
|
32
|
+
const dotIndex = key.indexOf('.')
|
|
33
|
+
const outputKey = dotIndex >= 0 ? key.substring(dotIndex + 1) : key
|
|
34
|
+
columns.push(outputKey)
|
|
35
|
+
cells[outputKey] = firstRow.cells[key]
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
} else {
|
package/src/execute/execute.js
CHANGED
|
@@ -3,7 +3,7 @@ import { derivedAlias } from '../expression/alias.js'
|
|
|
3
3
|
import { evaluateExpr } from '../expression/evaluate.js'
|
|
4
4
|
import { parseSql } from '../parse/parse.js'
|
|
5
5
|
import { planSql } from '../plan/plan.js'
|
|
6
|
-
import {
|
|
6
|
+
import { validateScan, validateTable } from '../validation/tables.js'
|
|
7
7
|
import { executeHashAggregate, executeScalarAggregate } from './aggregates.js'
|
|
8
8
|
import { executeHashJoin, executeNestedLoopJoin, executePositionalJoin } from './join.js'
|
|
9
9
|
import { executeSort } from './sort.js'
|
|
@@ -95,16 +95,8 @@ export async function* executePlan({ plan, context }) {
|
|
|
95
95
|
*/
|
|
96
96
|
async function* executeScan(plan, context) {
|
|
97
97
|
const { tables, signal } = context
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!table) {
|
|
101
|
-
throw new TableNotFoundError({ table: plan.table, tables })
|
|
102
|
-
}
|
|
103
|
-
// check columns
|
|
104
|
-
const missingColumn = plan.hints.columns?.find(col => !table.columns.includes(col))
|
|
105
|
-
if (missingColumn) {
|
|
106
|
-
throw new Error(`Column "${missingColumn}" not found. Available columns: ${table.columns.join(', ') || '[]'}`)
|
|
107
|
-
}
|
|
98
|
+
const table = validateTable({ ...plan, tables })
|
|
99
|
+
validateScan({ ...plan, tables })
|
|
108
100
|
|
|
109
101
|
// do the scan
|
|
110
102
|
const { rows, appliedWhere, appliedLimitOffset } = table.scan({ ...plan.hints, signal })
|
|
@@ -138,10 +130,7 @@ async function* executeScan(plan, context) {
|
|
|
138
130
|
* @yields {AsyncRow}
|
|
139
131
|
*/
|
|
140
132
|
async function* executeCount(plan, { tables, signal }) {
|
|
141
|
-
const table = tables
|
|
142
|
-
if (!table) {
|
|
143
|
-
throw new TableNotFoundError({ table: plan.table, tables })
|
|
144
|
-
}
|
|
133
|
+
const table = validateTable({ ...plan, tables })
|
|
145
134
|
|
|
146
135
|
// Use source numRows if available
|
|
147
136
|
let count = table.numRows
|
|
@@ -273,9 +262,14 @@ async function* executeProject(plan, context) {
|
|
|
273
262
|
|
|
274
263
|
for (const col of plan.columns) {
|
|
275
264
|
if (col.type === 'star') {
|
|
265
|
+
const prefix = col.table ? `${col.table}.` : undefined
|
|
276
266
|
for (const key of row.columns) {
|
|
277
|
-
|
|
278
|
-
|
|
267
|
+
if (prefix && !key.startsWith(prefix)) continue
|
|
268
|
+
// Strip table prefix for output column names
|
|
269
|
+
const dotIndex = key.indexOf('.')
|
|
270
|
+
const outputKey = dotIndex >= 0 ? key.substring(dotIndex + 1) : key
|
|
271
|
+
columns.push(outputKey)
|
|
272
|
+
cells[outputKey] = row.cells[key]
|
|
279
273
|
}
|
|
280
274
|
} else {
|
|
281
275
|
const alias = col.alias ?? derivedAlias(col.expr)
|
package/src/execute/join.js
CHANGED
|
@@ -230,27 +230,16 @@ function mergeRows(leftRow, rightRow, leftTable, rightTable) {
|
|
|
230
230
|
|
|
231
231
|
// Add left table columns with prefix
|
|
232
232
|
for (const [key, cell] of Object.entries(leftRow.cells)) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
columns.push(alias)
|
|
237
|
-
cells[alias] = cell
|
|
238
|
-
}
|
|
239
|
-
// Also keep unqualified name for convenience
|
|
240
|
-
columns.push(key)
|
|
241
|
-
cells[key] = cell
|
|
233
|
+
const alias = key.includes('.') ? key : `${leftTable}.${key}`
|
|
234
|
+
columns.push(alias)
|
|
235
|
+
cells[alias] = cell
|
|
242
236
|
}
|
|
243
237
|
|
|
244
238
|
// Add right table columns with prefix
|
|
245
239
|
for (const [key, cell] of Object.entries(rightRow.cells)) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
cells[alias] = cell
|
|
250
|
-
}
|
|
251
|
-
// Unqualified name (overwrites if same name exists in left table)
|
|
252
|
-
columns.push(key)
|
|
253
|
-
cells[key] = cell
|
|
240
|
+
const alias = key.includes('.') ? key : `${rightTable}.${key}`
|
|
241
|
+
columns.push(alias)
|
|
242
|
+
cells[alias] = cell
|
|
254
243
|
}
|
|
255
244
|
|
|
256
245
|
return { columns, cells }
|
|
@@ -264,5 +253,5 @@ function mergeRows(leftRow, rightRow, leftTable, rightTable) {
|
|
|
264
253
|
* @returns {string[]}
|
|
265
254
|
*/
|
|
266
255
|
function prefixColumns(cols, table) {
|
|
267
|
-
return cols.
|
|
256
|
+
return cols.map(col => col.includes('.') ? col : `${table}.${col}`)
|
|
268
257
|
}
|
|
@@ -37,16 +37,24 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
|
37
37
|
if (node.name in row.cells) {
|
|
38
38
|
return row.cells[node.name]()
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const dotIndex = node.name.indexOf('.')
|
|
41
|
+
if (dotIndex >= 0) {
|
|
42
|
+
// For qualified names like 'users.id', try just the column part
|
|
43
|
+
const colName = node.name.substring(dotIndex + 1)
|
|
44
|
+
if (colName in row.cells) {
|
|
44
45
|
return row.cells[colName]()
|
|
45
46
|
}
|
|
47
|
+
} else {
|
|
48
|
+
// For unqualified names, search for a matching prefixed column (e.g. 'id' to 'a.id')
|
|
49
|
+
const suffix = '.' + node.name
|
|
50
|
+
const match = row.columns.find(col => col.endsWith(suffix))
|
|
51
|
+
if (match) {
|
|
52
|
+
return row.cells[match]()
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
// Unknown identifier
|
|
48
56
|
throw new ColumnNotFoundError({
|
|
49
|
-
|
|
57
|
+
missingColumn: node.name,
|
|
50
58
|
availableColumns: row.columns,
|
|
51
59
|
rowIndex,
|
|
52
60
|
...node,
|
package/src/plan/columns.js
CHANGED
|
@@ -71,7 +71,8 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
71
71
|
const outputName = col.alias ?? derivedAlias(col.expr)
|
|
72
72
|
if (!parentColumns.includes(outputName)) continue
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
// Exclude earlier SELECT aliases so they aren't treated as source columns
|
|
75
|
+
collectColumnsFromExpr(col.expr, identifiers, selectAliases)
|
|
75
76
|
if (col.alias) {
|
|
76
77
|
selectAliases.add(col.alias)
|
|
77
78
|
}
|
|
@@ -223,12 +224,12 @@ function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
|
223
224
|
const result = []
|
|
224
225
|
const fromAlias = select.from.alias ?? select.from.table
|
|
225
226
|
for (const col of lookupTableColumns(select.from.table, cteColumns, tables)) {
|
|
226
|
-
result.push(`${fromAlias}.${col}
|
|
227
|
+
result.push(`${fromAlias}.${col}`)
|
|
227
228
|
}
|
|
228
229
|
for (const join of select.joins) {
|
|
229
230
|
const alias = join.alias ?? join.table
|
|
230
231
|
for (const col of lookupTableColumns(join.table, cteColumns, tables)) {
|
|
231
|
-
result.push(`${alias}.${col}
|
|
232
|
+
result.push(`${alias}.${col}`)
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
return result
|
package/src/plan/plan.js
CHANGED
|
@@ -2,6 +2,7 @@ import { derivedAlias } from '../expression/alias.js'
|
|
|
2
2
|
import { parseSql } from '../parse/parse.js'
|
|
3
3
|
import { findAggregate } from '../validation/aggregates.js'
|
|
4
4
|
import { ColumnNotFoundError, TableNotFoundError } from '../validation/planErrors.js'
|
|
5
|
+
import { validateScan, validateTableRefs } from '../validation/tables.js'
|
|
5
6
|
import { extractColumns, fromAlias, inferStatementColumns } from './columns.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -112,6 +113,16 @@ function planSelect({ select, ctePlans, cteColumns, tables, parentColumns }) {
|
|
|
112
113
|
// Source alias for FROM clause
|
|
113
114
|
const sourceAlias = fromAlias(select.from)
|
|
114
115
|
|
|
116
|
+
// Validate qualified references
|
|
117
|
+
const scopeTables = Object.fromEntries([sourceAlias, ...select.joins.map(j => j.alias ?? j.table)].map(a => [a, true]))
|
|
118
|
+
for (const col of select.columns) {
|
|
119
|
+
if (col.type === 'derived') {
|
|
120
|
+
validateTableRefs(col.expr, scopeTables)
|
|
121
|
+
} else if (col.table && !(col.table in scopeTables)) {
|
|
122
|
+
throw new TableNotFoundError({ table: col.table, tables: scopeTables })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
// Determine scan hints for direct table scans (WHERE and LIMIT/OFFSET are
|
|
116
127
|
// included so they are only applied to fresh scans, not CTE/subquery plans)
|
|
117
128
|
/** @type {ScanOptions} */
|
|
@@ -191,13 +202,23 @@ function planSelect({ select, ctePlans, cteColumns, tables, parentColumns }) {
|
|
|
191
202
|
// However, for streaming distinct we need to project first
|
|
192
203
|
// So the order is: Sort -> Project -> Distinct -> Limit
|
|
193
204
|
|
|
194
|
-
// Fast path for SELECT *
|
|
195
|
-
const isPassthrough = select.columns.length === 1 && select.columns[0].type === 'star'
|
|
205
|
+
// Fast path for SELECT * without joins
|
|
206
|
+
const isPassthrough = select.columns.length === 1 && select.columns[0].type === 'star' && !select.joins.length
|
|
196
207
|
if (!isPassthrough) {
|
|
208
|
+
// Resolve earlier SELECT aliases in later column expressions
|
|
209
|
+
/** @type {Map<string, ExprNode>} */
|
|
210
|
+
const colAliases = new Map()
|
|
211
|
+
let projectColumns = select.columns.map(col => {
|
|
212
|
+
if (col.type !== 'derived') return col
|
|
213
|
+
const expr = resolveAliases(col.expr, colAliases)
|
|
214
|
+
if (col.alias) {
|
|
215
|
+
colAliases.set(col.alias, expr)
|
|
216
|
+
}
|
|
217
|
+
return { ...col, expr }
|
|
218
|
+
})
|
|
197
219
|
// When parent only needs specific columns, drop unneeded projections
|
|
198
|
-
let projectColumns = select.columns
|
|
199
220
|
if (parentColumns) {
|
|
200
|
-
projectColumns =
|
|
221
|
+
projectColumns = projectColumns.filter(col =>
|
|
201
222
|
col.type === 'star' || parentColumns.includes(col.alias ?? derivedAlias(col.expr))
|
|
202
223
|
)
|
|
203
224
|
}
|
|
@@ -240,11 +261,7 @@ function planFrom({ select, ctePlans, cteColumns, hints, tables }) {
|
|
|
240
261
|
if (hints.columns && availableColumns.length) {
|
|
241
262
|
const missingColumn = hints.columns.find(col => !availableColumns.includes(col))
|
|
242
263
|
if (missingColumn) {
|
|
243
|
-
throw new ColumnNotFoundError({
|
|
244
|
-
columnName: missingColumn,
|
|
245
|
-
availableColumns,
|
|
246
|
-
...select.from,
|
|
247
|
-
})
|
|
264
|
+
throw new ColumnNotFoundError({ missingColumn, availableColumns, ...select.from })
|
|
248
265
|
}
|
|
249
266
|
}
|
|
250
267
|
return subPlan
|
|
@@ -321,50 +338,44 @@ function planJoin({ left, joins, leftTable, ctePlans, cteColumns, perTableColumn
|
|
|
321
338
|
* Recursively replaces identifier nodes that match SELECT aliases
|
|
322
339
|
* with their aliased expressions.
|
|
323
340
|
*
|
|
324
|
-
* @param {ExprNode} node
|
|
341
|
+
* @param {ExprNode | undefined} node
|
|
325
342
|
* @param {Map<string, ExprNode>} aliases
|
|
326
343
|
* @returns {ExprNode}
|
|
327
344
|
*/
|
|
328
345
|
function resolveAliases(node, aliases) {
|
|
346
|
+
if (!node || !aliases.size) return node
|
|
329
347
|
if (node.type === 'identifier') {
|
|
330
|
-
|
|
331
|
-
if (resolved) return resolved
|
|
332
|
-
return node
|
|
348
|
+
return aliases.get(node.name) ?? node
|
|
333
349
|
}
|
|
334
350
|
if (node.type === 'unary') {
|
|
335
|
-
|
|
336
|
-
return argument === node.argument ? node : { ...node, argument }
|
|
351
|
+
return { ...node, argument: resolveAliases(node.argument, aliases) }
|
|
337
352
|
}
|
|
338
353
|
if (node.type === 'binary') {
|
|
339
354
|
const left = resolveAliases(node.left, aliases)
|
|
340
355
|
const right = resolveAliases(node.right, aliases)
|
|
341
|
-
return
|
|
356
|
+
return { ...node, left, right }
|
|
342
357
|
}
|
|
343
358
|
if (node.type === 'function') {
|
|
344
359
|
const args = node.args.map(arg => resolveAliases(arg, aliases))
|
|
345
|
-
|
|
346
|
-
return changed ? { ...node, args } : node
|
|
360
|
+
return { ...node, args }
|
|
347
361
|
}
|
|
348
362
|
if (node.type === 'cast') {
|
|
349
|
-
|
|
350
|
-
return expr === node.expr ? node : { ...node, expr }
|
|
363
|
+
return { ...node, expr: resolveAliases(node.expr, aliases) }
|
|
351
364
|
}
|
|
352
365
|
if (node.type === 'in valuelist') {
|
|
353
366
|
const expr = resolveAliases(node.expr, aliases)
|
|
354
367
|
const values = node.values.map(v => resolveAliases(v, aliases))
|
|
355
|
-
|
|
356
|
-
return changed ? { ...node, expr, values } : node
|
|
368
|
+
return { ...node, expr, values }
|
|
357
369
|
}
|
|
358
370
|
if (node.type === 'case') {
|
|
359
|
-
const caseExpr =
|
|
371
|
+
const caseExpr = resolveAliases(node.caseExpr, aliases)
|
|
360
372
|
const whenClauses = node.whenClauses.map(w => {
|
|
361
373
|
const condition = resolveAliases(w.condition, aliases)
|
|
362
374
|
const result = resolveAliases(w.result, aliases)
|
|
363
|
-
return
|
|
375
|
+
return { ...w, condition, result }
|
|
364
376
|
})
|
|
365
|
-
const elseResult =
|
|
366
|
-
|
|
367
|
-
return changed ? { ...node, caseExpr, whenClauses, elseResult } : node
|
|
377
|
+
const elseResult = resolveAliases(node.elseResult, aliases)
|
|
378
|
+
return { ...node, caseExpr, whenClauses, elseResult }
|
|
368
379
|
}
|
|
369
380
|
// literal, interval, subquery, in, exists: no identifiers to resolve
|
|
370
381
|
return node
|
|
@@ -396,33 +407,6 @@ function extractSimpleJoinKeys({ condition, leftTable, rightTable }) {
|
|
|
396
407
|
return { leftKey: left, rightKey: right }
|
|
397
408
|
}
|
|
398
409
|
|
|
399
|
-
/**
|
|
400
|
-
* Validates that a table exists and requested columns are available.
|
|
401
|
-
*
|
|
402
|
-
* @param {object} options
|
|
403
|
-
* @param {string} options.table
|
|
404
|
-
* @param {ScanOptions} options.hints
|
|
405
|
-
* @param {Record<string, AsyncDataSource>} [options.tables]
|
|
406
|
-
* @param {number} options.positionStart
|
|
407
|
-
* @param {number} options.positionEnd
|
|
408
|
-
*/
|
|
409
|
-
function validateScan({ table, hints, tables, positionStart, positionEnd }) {
|
|
410
|
-
if (!tables) return
|
|
411
|
-
const resolved = tables[table]
|
|
412
|
-
if (!resolved) {
|
|
413
|
-
throw new TableNotFoundError({ table, tables, positionStart, positionEnd })
|
|
414
|
-
}
|
|
415
|
-
const missingColumn = hints.columns?.find(col => !resolved.columns.includes(col))
|
|
416
|
-
if (missingColumn) {
|
|
417
|
-
throw new ColumnNotFoundError({
|
|
418
|
-
columnName: missingColumn,
|
|
419
|
-
availableColumns: resolved.columns,
|
|
420
|
-
positionStart,
|
|
421
|
-
positionEnd,
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
410
|
/**
|
|
427
411
|
* Checks if every SELECT column is a plain COUNT(*).
|
|
428
412
|
*
|
|
@@ -30,18 +30,18 @@ export class TableNotFoundError extends ExecutionError {
|
|
|
30
30
|
export class ColumnNotFoundError extends ExecutionError {
|
|
31
31
|
/**
|
|
32
32
|
* @param {Object} options
|
|
33
|
-
* @param {string} options.
|
|
33
|
+
* @param {string} options.missingColumn - The missing column name
|
|
34
34
|
* @param {string[]} options.availableColumns - List of available column names
|
|
35
35
|
* @param {number} options.positionStart
|
|
36
36
|
* @param {number} options.positionEnd
|
|
37
37
|
* @param {number} [options.rowIndex] - 1-based row number where error occurred
|
|
38
38
|
*/
|
|
39
|
-
constructor({
|
|
39
|
+
constructor({ missingColumn, availableColumns, positionStart, positionEnd, rowIndex }) {
|
|
40
40
|
const available = availableColumns.length > 0
|
|
41
41
|
? `. Available columns: ${availableColumns.join(', ')}`
|
|
42
42
|
: ''
|
|
43
43
|
super({
|
|
44
|
-
message: `Column "${
|
|
44
|
+
message: `Column "${missingColumn}" not found${available}`,
|
|
45
45
|
positionStart,
|
|
46
46
|
positionEnd,
|
|
47
47
|
rowIndex,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ColumnNotFoundError, TableNotFoundError } from './planErrors.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { AsyncDataSource, ExprNode, ScanOptions } from '../types.js'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {string} options.table - The name of the table to validate
|
|
10
|
+
* @param {Record<string, AsyncDataSource>} options.tables - Object mapping table names to data sources
|
|
11
|
+
* @param {number} [options.positionStart] - Optional start position for error reporting
|
|
12
|
+
* @param {number} [options.positionEnd] - Optional end position for error reporting
|
|
13
|
+
* @returns {AsyncDataSource}
|
|
14
|
+
*/
|
|
15
|
+
export function validateTable({ table, tables, positionStart, positionEnd } ) {
|
|
16
|
+
const resolved = tables[table]
|
|
17
|
+
if (!resolved) {
|
|
18
|
+
throw new TableNotFoundError({ table, tables, positionStart, positionEnd })
|
|
19
|
+
}
|
|
20
|
+
return resolved
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates that a table exists and requested columns are available.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} options
|
|
27
|
+
* @param {string} [options.table]
|
|
28
|
+
* @param {ScanOptions} options.hints
|
|
29
|
+
* @param {Record<string, AsyncDataSource>} [options.tables]
|
|
30
|
+
* @param {number} [options.positionStart]
|
|
31
|
+
* @param {number} [options.positionEnd]
|
|
32
|
+
*/
|
|
33
|
+
export function validateScan({ table, hints, tables, positionStart, positionEnd }) {
|
|
34
|
+
if (!tables) return
|
|
35
|
+
const resolved = validateTable({ table, tables, positionStart, positionEnd })
|
|
36
|
+
const missingColumn = hints.columns?.find(col => !resolved.columns.includes(col))
|
|
37
|
+
if (missingColumn) {
|
|
38
|
+
throw new ColumnNotFoundError({
|
|
39
|
+
missingColumn,
|
|
40
|
+
availableColumns: resolved.columns,
|
|
41
|
+
positionStart,
|
|
42
|
+
positionEnd,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates that qualified identifiers reference known table aliases.
|
|
49
|
+
*
|
|
50
|
+
* @param {ExprNode} expr
|
|
51
|
+
* @param {Record<string, any>} tables
|
|
52
|
+
*/
|
|
53
|
+
export function validateTableRefs(expr, tables) {
|
|
54
|
+
if (!expr) return
|
|
55
|
+
if (expr.type === 'identifier') {
|
|
56
|
+
const dotIndex = expr.name.indexOf('.')
|
|
57
|
+
if (dotIndex >= 0) {
|
|
58
|
+
const table = expr.name.substring(0, dotIndex)
|
|
59
|
+
if (!(table in tables)) {
|
|
60
|
+
throw new TableNotFoundError({ table, tables, positionStart: expr.positionStart, positionEnd: expr.positionStart + dotIndex })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (expr.type === 'binary') {
|
|
66
|
+
validateTableRefs(expr.left, tables)
|
|
67
|
+
validateTableRefs(expr.right, tables)
|
|
68
|
+
} else if (expr.type === 'unary') {
|
|
69
|
+
validateTableRefs(expr.argument, tables)
|
|
70
|
+
} else if (expr.type === 'function') {
|
|
71
|
+
for (const arg of expr.args) {
|
|
72
|
+
validateTableRefs(arg, tables)
|
|
73
|
+
}
|
|
74
|
+
} else if (expr.type === 'cast') {
|
|
75
|
+
validateTableRefs(expr.expr, tables)
|
|
76
|
+
} else if (expr.type === 'in valuelist') {
|
|
77
|
+
validateTableRefs(expr.expr, tables)
|
|
78
|
+
for (const val of expr.values) {
|
|
79
|
+
validateTableRefs(val, tables)
|
|
80
|
+
}
|
|
81
|
+
} else if (expr.type === 'case') {
|
|
82
|
+
validateTableRefs(expr.caseExpr, tables)
|
|
83
|
+
for (const w of expr.whenClauses) {
|
|
84
|
+
validateTableRefs(w.condition, tables)
|
|
85
|
+
validateTableRefs(w.result, tables)
|
|
86
|
+
}
|
|
87
|
+
validateTableRefs(expr.elseResult, tables)
|
|
88
|
+
}
|
|
89
|
+
}
|