parseSelectStatement parses a SELECT statement. It delegates each clause to a focused helper method.
()
| 34 | // parseSelectStatement parses a SELECT statement. |
| 35 | // It delegates each clause to a focused helper method. |
| 36 | func (p *Parser) parseSelectStatement() (ast.Statement, error) { |
| 37 | // We've already consumed the SELECT token in matchType. |
| 38 | |
| 39 | // DISTINCT / ALL modifier |
| 40 | isDistinct, distinctOnColumns, err := p.parseDistinctModifier() |
| 41 | if err != nil { |
| 42 | return nil, err |
| 43 | } |
| 44 | |
| 45 | // Reject TOP in dialects that use LIMIT/OFFSET or ROWNUM/FETCH FIRST instead. |
| 46 | // String comparison is used here because the lexer has no TokenTypeTOP constant; |
| 47 | // TOP is emitted as an identifier literal (see parseTopClause comment). |
| 48 | nonTopDialects := map[string]bool{ |
| 49 | string(keywords.DialectMySQL): true, |
| 50 | string(keywords.DialectPostgreSQL): true, |
| 51 | string(keywords.DialectSQLite): true, |
| 52 | string(keywords.DialectOracle): true, |
| 53 | } |
| 54 | if nonTopDialects[p.dialect] && strings.ToUpper(p.currentToken.Token.Value) == "TOP" { |
| 55 | if p.dialect == string(keywords.DialectOracle) { |
| 56 | return nil, fmt.Errorf("TOP clause is not supported in Oracle; use ROWNUM or FETCH FIRST … ROWS ONLY instead") |
| 57 | } |
| 58 | return nil, fmt.Errorf("TOP clause is not supported in %s; use LIMIT/OFFSET instead", p.dialect) |
| 59 | } |
| 60 | |
| 61 | // SQL Server TOP clause |
| 62 | topClause, err := p.parseTopClause() |
| 63 | if err != nil { |
| 64 | return nil, err |
| 65 | } |
| 66 | |
| 67 | // Column list |
| 68 | columns, err := p.parseSelectColumnList() |
| 69 | if err != nil { |
| 70 | return nil, err |
| 71 | } |
| 72 | |
| 73 | // FROM … JOIN clauses |
| 74 | tableName, tables, joins, err := p.parseFromClause() |
| 75 | if err != nil { |
| 76 | return nil, err |
| 77 | } |
| 78 | |
| 79 | // Initialise the statement early so clause parsers can check dialect etc. |
| 80 | selectStmt := &ast.SelectStatement{ |
| 81 | Distinct: isDistinct, |
| 82 | DistinctOnColumns: distinctOnColumns, |
| 83 | Top: topClause, |
| 84 | Columns: columns, |
| 85 | From: tables, |
| 86 | Joins: joins, |
| 87 | TableName: tableName, |
| 88 | } |
| 89 | |
| 90 | // SAMPLE (ClickHouse-specific, specifies sampling rate/size; comes after FROM/FINAL) |
| 91 | if p.dialect == string(keywords.DialectClickHouse) && p.isTokenMatch("SAMPLE") { |
| 92 | if selectStmt.Sample, err = p.parseSampleClause(); err != nil { |
| 93 | return nil, err |
no test coverage detected