squirreling 0.12.4 → 0.12.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/README.md +1 -0
- package/package.json +4 -4
- package/src/ast.d.ts +10 -1
- package/src/execute/aggregates.js +1 -2
- package/src/execute/execute.js +94 -80
- package/src/execute/join.js +76 -0
- package/src/execute/sort.js +29 -7
- package/src/expression/evaluate.js +12 -0
- package/src/parse/joins.js +83 -1
- package/src/parse/parse.js +124 -5
- package/src/parse/primary.js +38 -2
- package/src/parse/tokenize.js +11 -0
- package/src/parse/types.d.ts +1 -0
- package/src/plan/columns.js +105 -10
- package/src/plan/plan.js +180 -6
- package/src/plan/types.d.ts +11 -1
- package/src/types.d.ts +10 -0
- package/src/validation/functions.js +13 -0
- package/src/validation/keywords.js +2 -2
- package/src/validation/tables.js +48 -0
package/src/parse/parse.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { derivedAlias } from '../expression/alias.js'
|
|
1
2
|
import { expectNoAggregate, findAggregate } from '../validation/aggregates.js'
|
|
3
|
+
import { isTableFunction, validateFunctionArgs } from '../validation/functions.js'
|
|
2
4
|
import { RESERVED_AFTER_COLUMN, RESERVED_AFTER_TABLE } from '../validation/keywords.js'
|
|
3
5
|
import { ParseError } from '../validation/parseErrors.js'
|
|
4
6
|
import { parseExpression } from './expression.js'
|
|
@@ -7,7 +9,7 @@ import { consume, current, expect, match, parseError, peekToken } from './state.
|
|
|
7
9
|
import { tokenizeSql } from './tokenize.js'
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
* @import { CTEDefinition, ExprNode, FromSubquery, FromTable, OrderByItem, ParseSqlOptions, ParserState, SelectColumn, SelectStatement, SetOperationStatement, SetOperator, Statement } from '../types.js'
|
|
12
|
+
* @import { CTEDefinition, ExprNode, FromFunction, FromSubquery, FromTable, OrderByItem, ParseSqlOptions, ParserState, SelectColumn, SelectStatement, SetOperationStatement, SetOperator, Statement } from '../types.js'
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -183,8 +185,8 @@ function parseSelect(state) {
|
|
|
183
185
|
expect(state, 'keyword', 'FROM')
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
// Check if it's a subquery or table name
|
|
187
|
-
/** @type {FromTable | FromSubquery} */
|
|
188
|
+
// Check if it's a subquery, table function, or table name
|
|
189
|
+
/** @type {FromTable | FromSubquery | FromFunction} */
|
|
188
190
|
let from
|
|
189
191
|
const fromTok = current(state)
|
|
190
192
|
if (fromTok.type === 'paren' && fromTok.value === '(') {
|
|
@@ -200,6 +202,9 @@ function parseSelect(state) {
|
|
|
200
202
|
positionStart: fromTok.positionStart,
|
|
201
203
|
positionEnd: state.lastPos,
|
|
202
204
|
}
|
|
205
|
+
} else if (isTableFunctionStart(state)) {
|
|
206
|
+
// Table function: SELECT * FROM UNNEST(expr) [AS alias[(col_alias)]]
|
|
207
|
+
from = parseFromFunction(state)
|
|
203
208
|
} else {
|
|
204
209
|
// Simple table name: SELECT * FROM users
|
|
205
210
|
expect(state, 'identifier')
|
|
@@ -237,7 +242,7 @@ function parseSelect(state) {
|
|
|
237
242
|
if (match(state, 'keyword', 'GROUP')) {
|
|
238
243
|
expect(state, 'keyword', 'BY')
|
|
239
244
|
while (true) {
|
|
240
|
-
const expr = parseExpression(state)
|
|
245
|
+
const expr = resolvePositionalRef(parseExpression(state), columns, 'GROUP BY')
|
|
241
246
|
expectNoAggregate(expr, 'GROUP BY')
|
|
242
247
|
groupBy.push(expr)
|
|
243
248
|
if (!match(state, 'comma')) break
|
|
@@ -255,7 +260,7 @@ function parseSelect(state) {
|
|
|
255
260
|
if (match(state, 'keyword', 'ORDER')) {
|
|
256
261
|
expect(state, 'keyword', 'BY')
|
|
257
262
|
while (true) {
|
|
258
|
-
const expr = parseExpression(state)
|
|
263
|
+
const expr = resolvePositionalRef(parseExpression(state), columns, 'ORDER BY', hasAggregate)
|
|
259
264
|
if (!hasAggregate) {
|
|
260
265
|
expectNoAggregate(expr, 'ORDER BY')
|
|
261
266
|
}
|
|
@@ -333,6 +338,53 @@ function parseSelect(state) {
|
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Resolves a positive integer literal in GROUP BY or ORDER BY as a positional
|
|
343
|
+
* reference to the Nth SELECT column. Non-integer and non-literal expressions
|
|
344
|
+
* pass through unchanged.
|
|
345
|
+
*
|
|
346
|
+
* In post-aggregation contexts (ORDER BY when the query aggregates), the
|
|
347
|
+
* reference resolves to an identifier on the output column name, so it
|
|
348
|
+
* matches columns produced by the aggregate projection. In pre-projection
|
|
349
|
+
* contexts (GROUP BY, non-aggregated ORDER BY), the target column's full
|
|
350
|
+
* expression is substituted, since the output name won't exist yet.
|
|
351
|
+
*
|
|
352
|
+
* @param {ExprNode} expr
|
|
353
|
+
* @param {SelectColumn[]} columns
|
|
354
|
+
* @param {string} clauseName
|
|
355
|
+
* @param {boolean} [postAgg]
|
|
356
|
+
* @returns {ExprNode}
|
|
357
|
+
*/
|
|
358
|
+
function resolvePositionalRef(expr, columns, clauseName, postAgg) {
|
|
359
|
+
if (expr.type !== 'literal') return expr
|
|
360
|
+
const n = expr.value
|
|
361
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n <= 0) return expr
|
|
362
|
+
if (n > columns.length) {
|
|
363
|
+
throw new ParseError({
|
|
364
|
+
message: `${clauseName} position ${n} is out of range (expected 1..${columns.length}) at position ${expr.positionStart}`,
|
|
365
|
+
positionStart: expr.positionStart,
|
|
366
|
+
positionEnd: expr.positionEnd,
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
const col = columns[n - 1]
|
|
370
|
+
if (col.type === 'star') {
|
|
371
|
+
throw new ParseError({
|
|
372
|
+
message: `${clauseName} position ${n} refers to * which is not supported at position ${expr.positionStart}`,
|
|
373
|
+
positionStart: expr.positionStart,
|
|
374
|
+
positionEnd: expr.positionEnd,
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
if (postAgg) {
|
|
378
|
+
return {
|
|
379
|
+
type: 'identifier',
|
|
380
|
+
name: col.alias ?? derivedAlias(col.expr),
|
|
381
|
+
positionStart: expr.positionStart,
|
|
382
|
+
positionEnd: expr.positionEnd,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return col.expr
|
|
386
|
+
}
|
|
387
|
+
|
|
336
388
|
/**
|
|
337
389
|
* @param {ParserState} state
|
|
338
390
|
* @returns {SelectColumn[]}
|
|
@@ -376,6 +428,73 @@ function parseSelectList(state) {
|
|
|
376
428
|
return cols
|
|
377
429
|
}
|
|
378
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Peeks whether the current token starts a table-valued function call
|
|
433
|
+
* like `UNNEST(...)`.
|
|
434
|
+
*
|
|
435
|
+
* @param {ParserState} state
|
|
436
|
+
* @returns {boolean}
|
|
437
|
+
*/
|
|
438
|
+
export function isTableFunctionStart(state) {
|
|
439
|
+
const tok = current(state)
|
|
440
|
+
if (tok.type !== 'identifier') return false
|
|
441
|
+
if (!isTableFunction(tok.value.toUpperCase())) return false
|
|
442
|
+
const next = peekToken(state, 1)
|
|
443
|
+
return next.type === 'paren' && next.value === '('
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Parses a table function source: UNNEST(args...) [AS alias[(col_alias)]]
|
|
448
|
+
*
|
|
449
|
+
* @param {ParserState} state
|
|
450
|
+
* @returns {FromFunction}
|
|
451
|
+
*/
|
|
452
|
+
export function parseFromFunction(state) {
|
|
453
|
+
const funcTok = consume(state)
|
|
454
|
+
const funcName = funcTok.value.toUpperCase()
|
|
455
|
+
const { positionStart } = funcTok
|
|
456
|
+
|
|
457
|
+
expect(state, 'paren', '(')
|
|
458
|
+
/** @type {ExprNode[]} */
|
|
459
|
+
const args = []
|
|
460
|
+
if (!match(state, 'paren', ')')) {
|
|
461
|
+
while (true) {
|
|
462
|
+
args.push(parseExpression(state))
|
|
463
|
+
if (!match(state, 'comma')) break
|
|
464
|
+
}
|
|
465
|
+
expect(state, 'paren', ')')
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
validateFunctionArgs(funcName, args.length, positionStart, state.lastPos, state.functions)
|
|
469
|
+
|
|
470
|
+
const alias = parseTableAlias(state)
|
|
471
|
+
/** @type {string | undefined} */
|
|
472
|
+
let columnAlias
|
|
473
|
+
if (alias && match(state, 'paren', '(')) {
|
|
474
|
+
const colStart = state.lastPos
|
|
475
|
+
const colTok = expect(state, 'identifier')
|
|
476
|
+
columnAlias = colTok.value
|
|
477
|
+
if (match(state, 'comma')) {
|
|
478
|
+
throw new ParseError({
|
|
479
|
+
message: `${funcName} produces a single column; only one column alias is allowed`,
|
|
480
|
+
positionStart: colStart,
|
|
481
|
+
positionEnd: state.lastPos,
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
expect(state, 'paren', ')')
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
type: 'function',
|
|
489
|
+
funcName,
|
|
490
|
+
args,
|
|
491
|
+
alias,
|
|
492
|
+
columnAlias,
|
|
493
|
+
positionStart,
|
|
494
|
+
positionEnd: state.lastPos,
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
379
498
|
/**
|
|
380
499
|
* Parses an optional table alias (e.g., "FROM users u" or "FROM users AS u")
|
|
381
500
|
* @param {ParserState} state
|
package/src/parse/primary.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isCastType, isExtractField, isIntervalUnit, isKnownFunction, niladicFuncs } from '../validation/functions.js'
|
|
1
|
+
import { isCastType, isExtractField, isIntervalUnit, isKnownFunction, isTableFunction, niladicFuncs } from '../validation/functions.js'
|
|
2
2
|
import { InvalidLiteralError, ParseError, SyntaxError, UnknownFunctionError } from '../validation/parseErrors.js'
|
|
3
3
|
import { RESERVED_KEYWORDS } from '../validation/keywords.js'
|
|
4
4
|
import { parseExpression } from './expression.js'
|
|
@@ -7,7 +7,7 @@ import { parseStatement } from './parse.js'
|
|
|
7
7
|
import { consume, current, expect, match, parseError, peekToken } from './state.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @import { ExprNode, IntervalNode, ParserState, WhenClause } from '../types.js'
|
|
10
|
+
* @import { ExprNode, IntervalNode, ParserState, SqlPrimitive, WhenClause } from '../types.js'
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -40,6 +40,34 @@ export function parsePrimary(state) {
|
|
|
40
40
|
return expr
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Array literal: [elem, elem, ...] — elements must be literals
|
|
44
|
+
if (match(state, 'bracket', '[')) {
|
|
45
|
+
/** @type {SqlPrimitive[]} */
|
|
46
|
+
const values = []
|
|
47
|
+
if (!match(state, 'bracket', ']')) {
|
|
48
|
+
while (true) {
|
|
49
|
+
const elemStart = current(state).positionStart
|
|
50
|
+
const elem = parseExpression(state)
|
|
51
|
+
if (elem.type !== 'literal') {
|
|
52
|
+
throw new ParseError({
|
|
53
|
+
message: 'Array literal elements must be constant literals',
|
|
54
|
+
positionStart: elemStart,
|
|
55
|
+
positionEnd: state.lastPos,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
values.push(elem.value)
|
|
59
|
+
if (!match(state, 'comma')) break
|
|
60
|
+
}
|
|
61
|
+
expect(state, 'bracket', ']')
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: 'literal',
|
|
65
|
+
value: values,
|
|
66
|
+
positionStart,
|
|
67
|
+
positionEnd: state.lastPos,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
if (tok.type === 'identifier') {
|
|
44
72
|
const next = peekToken(state, 1)
|
|
45
73
|
const funcNameUpper = tok.value.toUpperCase()
|
|
@@ -109,6 +137,14 @@ export function parsePrimary(state) {
|
|
|
109
137
|
})
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
if (isTableFunction(funcNameUpper)) {
|
|
141
|
+
throw new ParseError({
|
|
142
|
+
message: `${funcNameUpper} is a table function and can only be used in FROM clauses at position ${positionStart}`,
|
|
143
|
+
positionStart,
|
|
144
|
+
positionEnd: tok.positionEnd,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
return parseFunctionCall(state, positionStart)
|
|
113
149
|
}
|
|
114
150
|
|
package/src/parse/tokenize.js
CHANGED
|
@@ -222,6 +222,17 @@ export function tokenizeSql(query) {
|
|
|
222
222
|
continue
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
if (ch === '[' || ch === ']') {
|
|
226
|
+
i++
|
|
227
|
+
tokens.push({
|
|
228
|
+
type: 'bracket',
|
|
229
|
+
value: ch,
|
|
230
|
+
positionStart,
|
|
231
|
+
positionEnd: i,
|
|
232
|
+
})
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
if (ch === ';') {
|
|
226
237
|
i++
|
|
227
238
|
tokens.push({
|
package/src/parse/types.d.ts
CHANGED
package/src/plan/columns.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { derivedAlias } from '../expression/alias.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @import { AsyncDataSource, ExprNode, FromSubquery, FromTable, IdentifierNode, SelectStatement, Statement } from '../types.js'
|
|
4
|
+
* @import { AsyncDataSource, ExprNode, FromFunction, FromSubquery, FromTable, IdentifierNode, SelectStatement, Statement } from '../types.js'
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @param {FromTable | FromSubquery} from
|
|
8
|
+
* @param {FromTable | FromSubquery | FromFunction} from
|
|
9
9
|
* @returns {string}
|
|
10
10
|
*/
|
|
11
11
|
export function fromAlias(from) {
|
|
12
|
-
|
|
12
|
+
if (from.alias) return from.alias
|
|
13
|
+
if (from.type === 'table') return from.table
|
|
14
|
+
if (from.type === 'function') return from.funcName.toLowerCase()
|
|
15
|
+
// Unaliased subquery: no natural name. Callers that need a stable alias
|
|
16
|
+
// should require one at parse time.
|
|
17
|
+
return 'table'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the single output column name for a FROM table function.
|
|
22
|
+
*
|
|
23
|
+
* @param {FromFunction} from
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function tableFunctionColumnName(from) {
|
|
27
|
+
return from.columnAlias ?? from.funcName.toLowerCase()
|
|
13
28
|
}
|
|
14
29
|
|
|
15
30
|
/**
|
|
@@ -54,8 +69,24 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
54
69
|
// directly. For non-star queries, parent names may be aliases and are
|
|
55
70
|
// handled below by filtering derived columns and collecting from expressions.
|
|
56
71
|
const hasStar = select.columns.some(col => col.type === 'star' && !col.table)
|
|
72
|
+
// Exclude parent names that match a derived alias in this SELECT — those are
|
|
73
|
+
// produced by projection (e.g. `SELECT *, a+b AS c`), not by the source.
|
|
74
|
+
/** @type {Set<string>} */
|
|
75
|
+
const derivedAliases = new Set()
|
|
76
|
+
for (const col of select.columns) {
|
|
77
|
+
if (col.type === 'derived') {
|
|
78
|
+
derivedAliases.add(col.alias ?? derivedAlias(col.expr))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
57
81
|
/** @type {IdentifierNode[]} */
|
|
58
|
-
const identifiers = hasStar && parentColumns
|
|
82
|
+
const identifiers = hasStar && parentColumns
|
|
83
|
+
? parentColumns.filter(id => !derivedAliases.has(id.name))
|
|
84
|
+
: []
|
|
85
|
+
// Identifiers collected from lateral table function arguments, grouped with
|
|
86
|
+
// the aliases visible to that argument. Earlier lateral outputs are visible;
|
|
87
|
+
// the current function alias and later joins are not.
|
|
88
|
+
/** @type {{ identifiers: IdentifierNode[], visibleAliases: string[] }[]} */
|
|
89
|
+
const lateralArgGroups = []
|
|
59
90
|
|
|
60
91
|
// Collect ORDER BY identifiers, excluding SELECT aliases (their underlying
|
|
61
92
|
// columns are already collected from select.columns expressions above)
|
|
@@ -88,8 +119,19 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
88
119
|
collectColumnsFromExpr(expr, identifiers, selectAliases)
|
|
89
120
|
}
|
|
90
121
|
collectColumnsFromExpr(select.having, identifiers, selectAliases)
|
|
122
|
+
const visibleLateralAliases = [fromAlias(select.from)]
|
|
91
123
|
for (const join of select.joins) {
|
|
92
124
|
collectColumnsFromExpr(join.on, identifiers)
|
|
125
|
+
const joinAlias = join.alias ?? join.table
|
|
126
|
+
if (join.fromFunction) {
|
|
127
|
+
/** @type {IdentifierNode[]} */
|
|
128
|
+
const lateralArgIdentifiers = []
|
|
129
|
+
for (const arg of join.fromFunction.args) {
|
|
130
|
+
collectColumnsFromExpr(arg, lateralArgIdentifiers)
|
|
131
|
+
}
|
|
132
|
+
lateralArgGroups.push({ identifiers: lateralArgIdentifiers, visibleAliases: [...visibleLateralAliases] })
|
|
133
|
+
}
|
|
134
|
+
visibleLateralAliases.push(joinAlias)
|
|
93
135
|
}
|
|
94
136
|
|
|
95
137
|
// Partition identifiers by table prefix
|
|
@@ -111,6 +153,26 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
|
|
156
|
+
// Partition identifiers from lateral UNNEST args using only the left-side
|
|
157
|
+
// aliases that are in scope for that specific join.
|
|
158
|
+
for (const { identifiers, visibleAliases } of lateralArgGroups) {
|
|
159
|
+
for (const { prefix, name } of identifiers) {
|
|
160
|
+
if (prefix) {
|
|
161
|
+
const set = perTable.get(prefix)
|
|
162
|
+
if (set) set.add(name)
|
|
163
|
+
} else {
|
|
164
|
+
if (visibleAliases.length > 1) {
|
|
165
|
+
for (const alias of visibleAliases) {
|
|
166
|
+
perTable.set(alias, undefined)
|
|
167
|
+
}
|
|
168
|
+
} else if (visibleAliases.length === 1) {
|
|
169
|
+
const set = perTable.get(visibleAliases[0])
|
|
170
|
+
if (set) set.add(name)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
114
176
|
// Build result map: convert Sets to arrays, undefined for all-columns tables
|
|
115
177
|
for (const alias of aliases) {
|
|
116
178
|
const set = perTable.get(alias)
|
|
@@ -207,7 +269,14 @@ function collectColumnsFromStatement(stmt, columns) {
|
|
|
207
269
|
if (stmt.from?.type === 'subquery') {
|
|
208
270
|
collectColumnsFromStatement(stmt.from.query, columns)
|
|
209
271
|
}
|
|
210
|
-
for (const join of stmt.joins)
|
|
272
|
+
for (const join of stmt.joins) {
|
|
273
|
+
collectColumnsFromExpr(join.on, columns)
|
|
274
|
+
if (join.fromFunction) {
|
|
275
|
+
for (const arg of join.fromFunction.args) {
|
|
276
|
+
collectColumnsFromExpr(arg, columns)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
211
280
|
for (const expr of stmt.groupBy) collectColumnsFromExpr(expr, columns)
|
|
212
281
|
collectColumnsFromExpr(stmt.having, columns)
|
|
213
282
|
for (const item of stmt.orderBy) collectColumnsFromExpr(item.expr, columns)
|
|
@@ -255,11 +324,33 @@ export function inferStatementColumns({ stmt, cteColumns, tables }) {
|
|
|
255
324
|
* @param {Record<string, AsyncDataSource>} [options.tables]
|
|
256
325
|
* @returns {string[]}
|
|
257
326
|
*/
|
|
258
|
-
function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
327
|
+
export function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
259
328
|
if (select.from.type === 'subquery') {
|
|
260
329
|
return inferStatementColumns({ stmt: select.from.query, cteColumns, tables })
|
|
261
330
|
}
|
|
262
331
|
|
|
332
|
+
if (select.from.type === 'function') {
|
|
333
|
+
// Table functions currently produce a single column
|
|
334
|
+
if (!select.joins.length) {
|
|
335
|
+
return [tableFunctionColumnName(select.from)]
|
|
336
|
+
}
|
|
337
|
+
/** @type {string[]} */
|
|
338
|
+
const result = []
|
|
339
|
+
const alias = fromAlias(select.from)
|
|
340
|
+
result.push(`${alias}.${tableFunctionColumnName(select.from)}`)
|
|
341
|
+
for (const join of select.joins) {
|
|
342
|
+
const joinAlias = join.alias ?? join.table
|
|
343
|
+
if (join.fromFunction) {
|
|
344
|
+
result.push(`${joinAlias}.${tableFunctionColumnName(join.fromFunction)}`)
|
|
345
|
+
} else {
|
|
346
|
+
for (const col of lookupTableColumns(join.table, cteColumns, tables)) {
|
|
347
|
+
result.push(`${joinAlias}.${col}`)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return result
|
|
352
|
+
}
|
|
353
|
+
|
|
263
354
|
if (!select.joins.length) {
|
|
264
355
|
return lookupTableColumns(select.from.table, cteColumns, tables)
|
|
265
356
|
}
|
|
@@ -267,14 +358,18 @@ function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
|
267
358
|
// Collect all sources, then prefix each table's columns
|
|
268
359
|
/** @type {string[]} */
|
|
269
360
|
const result = []
|
|
270
|
-
const
|
|
361
|
+
const sourceAlias = select.from.alias ?? select.from.table
|
|
271
362
|
for (const col of lookupTableColumns(select.from.table, cteColumns, tables)) {
|
|
272
|
-
result.push(`${
|
|
363
|
+
result.push(`${sourceAlias}.${col}`)
|
|
273
364
|
}
|
|
274
365
|
for (const join of select.joins) {
|
|
275
366
|
const joinAlias = join.alias ?? join.table
|
|
276
|
-
|
|
277
|
-
result.push(`${joinAlias}.${
|
|
367
|
+
if (join.fromFunction) {
|
|
368
|
+
result.push(`${joinAlias}.${tableFunctionColumnName(join.fromFunction)}`)
|
|
369
|
+
} else {
|
|
370
|
+
for (const col of lookupTableColumns(join.table, cteColumns, tables)) {
|
|
371
|
+
result.push(`${joinAlias}.${col}`)
|
|
372
|
+
}
|
|
278
373
|
}
|
|
279
374
|
}
|
|
280
375
|
return result
|