parseFromTableReference parses a single table reference in a FROM clause, including derived tables (subqueries), LATERAL, and optional aliases.
()
| 30 | // parseFromTableReference parses a single table reference in a FROM clause, |
| 31 | // including derived tables (subqueries), LATERAL, and optional aliases. |
| 32 | func (p *Parser) parseFromTableReference() (ast.TableReference, error) { |
| 33 | var tableRef ast.TableReference |
| 34 | |
| 35 | // Check for LATERAL keyword (PostgreSQL) |
| 36 | isLateral := false |
| 37 | if p.isType(models.TokenTypeLateral) { |
| 38 | isLateral = true |
| 39 | p.advance() // Consume LATERAL |
| 40 | } |
| 41 | |
| 42 | // Check for derived table (subquery in parentheses) |
| 43 | if p.isType(models.TokenTypeLParen) { |
| 44 | p.advance() // Consume ( |
| 45 | |
| 46 | // Check if this is a subquery (starts with SELECT or WITH) |
| 47 | if !p.isType(models.TokenTypeSelect) && !p.isType(models.TokenTypeWith) { |
| 48 | return tableRef, p.expectedError("SELECT in derived table") |
| 49 | } |
| 50 | |
| 51 | // Consume SELECT token before calling parseSelectStatement |
| 52 | p.advance() // Consume SELECT |
| 53 | |
| 54 | // Parse the subquery |
| 55 | subquery, err := p.parseSelectStatement() |
| 56 | if err != nil { |
| 57 | return tableRef, err |
| 58 | } |
| 59 | selectStmt, ok := subquery.(*ast.SelectStatement) |
| 60 | if !ok { |
| 61 | return tableRef, p.expectedError("SELECT statement in derived table") |
| 62 | } |
| 63 | |
| 64 | // Expect closing parenthesis |
| 65 | if !p.isType(models.TokenTypeRParen) { |
| 66 | return tableRef, p.expectedError(")") |
| 67 | } |
| 68 | p.advance() // Consume ) |
| 69 | |
| 70 | tableRef = ast.TableReference{ |
| 71 | Subquery: selectStmt, |
| 72 | Lateral: isLateral, |
| 73 | } |
| 74 | } else if p.dialect == string(keywords.DialectSnowflake) && |
| 75 | p.isType(models.TokenTypePlaceholder) && strings.HasPrefix(p.currentToken.Token.Value, "@") { |
| 76 | // Snowflake stage reference: @stage_name or @db.schema.stage/path. |
| 77 | // Tokenized as PLACEHOLDER; consume as a table name. |
| 78 | // Gated to Snowflake to avoid misinterpreting @variable in other dialects. |
| 79 | stageName := p.currentToken.Token.Value |
| 80 | p.advance() |
| 81 | // Optional /path suffix — consume tokens joined by / until a space boundary. |
| 82 | // Slash tokenizes as TokenTypeDiv. |
| 83 | for p.isType(models.TokenTypeDiv) { |
| 84 | stageName += "/" |
| 85 | p.advance() |
| 86 | if p.isIdentifier() || p.isType(models.TokenTypeKeyword) { |
| 87 | stageName += p.currentToken.Token.Value |
| 88 | p.advance() |
| 89 | } |
no test coverage detected