Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions snowflake/deparse/deparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ func TestDeparse_Select_Fetch(t *testing.T) {
assertRoundTrip(t, `SELECT a FROM t FETCH FIRST 5 ROWS ONLY`)
}

func TestDeparse_Select_FetchBare(t *testing.T) {
// All FETCH noise words are optional on input; deparse canonicalizes
// to FETCH FIRST n ROWS ONLY, which parses back to the same AST.
assertRoundTrip(t, `SELECT a FROM t FETCH 123`)
}

func TestDeparse_Select_OffsetFetchBare(t *testing.T) {
assertRoundTrip(t, `SELECT a FROM t OFFSET 12 FETCH 123`)
}

func TestDeparse_Select_TopStar(t *testing.T) {
// The TOP count must not swallow the `*` select target.
assertRoundTrip(t, `SELECT TOP 125 * FROM t`)
}

// ---------------------------------------------------------------------------
// SET operations
// ---------------------------------------------------------------------------
Expand Down
57 changes: 35 additions & 22 deletions snowflake/parser/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (p *Parser) tryParseSetOp() (ast.SetOp, bool, bool, bool) {
// [QUALIFY expr]
// [ORDER BY ...]
// [LIMIT n [OFFSET n]]
// [FETCH FIRST|NEXT n ROWS ONLY]
// [FETCH [FIRST|NEXT] n [ROW|ROWS] [ONLY]]
func (p *Parser) parseSelectStmt() (*ast.SelectStmt, error) {
selectTok, err := p.expect(kwSELECT)
if err != nil {
Expand All @@ -182,7 +182,7 @@ func (p *Parser) parseSelectStmt() (*ast.SelectStmt, error) {
// TOP n
if p.cur.Type == kwTOP {
p.advance() // consume TOP
topExpr, err := p.parseExpr()
topExpr, err := p.parseTopCount()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -276,6 +276,23 @@ func (p *Parser) parseSelectStmt() (*ast.SelectStmt, error) {
return stmt, nil
}

// parseTopCount parses the count following TOP. Snowflake documents TOP n
// as taking a constant, so this deliberately parses a single primary
// expression (number literal, $variable, or parenthesized expression)
// rather than a full expression: in `SELECT TOP 125 * FROM t` the `*` is
// the star select target, not a multiplication operator applied to 125.
func (p *Parser) parseTopCount() (ast.Node, error) {
switch p.cur.Type {
case tokInt, tokFloat, tokReal, tokVariable, '(':
return p.parsePrimaryExpr()
default:
return nil, &ParseError{
Loc: p.cur.Loc,
Msg: "expected a number after TOP",
}
}
}

// ---------------------------------------------------------------------------
// WITH ... SELECT (CTEs)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1404,43 +1421,39 @@ func (p *Parser) parseLimitOffsetFetch(stmt *ast.SelectStmt) error {
stmt.Offset = offsetExpr
}

// FETCH FIRST|NEXT n ROWS ONLY
// FETCH [FIRST|NEXT] n [ROW|ROWS] [ONLY]
//
// FIRST/NEXT, ROW/ROWS, and ONLY are all optional noise words in
// Snowflake: `FETCH 123` is equivalent to `FETCH FIRST 123 ROWS ONLY`.
if p.cur.Type == kwFETCH {
fetchTok := p.advance() // consume FETCH

// Expect FIRST or NEXT
if p.cur.Type != kwFIRST && p.cur.Type != kwNEXT {
return &ParseError{
Loc: p.cur.Loc,
Msg: "expected FIRST or NEXT after FETCH",
}
// Optional FIRST or NEXT
if p.cur.Type == kwFIRST || p.cur.Type == kwNEXT {
p.advance() // consume FIRST or NEXT
}
p.advance() // consume FIRST or NEXT

// Parse count expression
countExpr, err := p.parseExpr()
if err != nil {
return err
}

// Expect ROWS or ROW
if p.cur.Type != kwROWS && p.cur.Type != kwROW {
return &ParseError{
Loc: p.cur.Loc,
Msg: "expected ROWS or ROW after FETCH count",
}
// Optional ROWS or ROW
if p.cur.Type == kwROWS || p.cur.Type == kwROW {
p.advance() // consume ROWS or ROW
}
p.advance() // consume ROWS or ROW

// Expect ONLY
onlyTok, err := p.expect(kwONLY)
if err != nil {
return err
// Optional ONLY
if p.cur.Type == kwONLY {
p.advance() // consume ONLY
}

stmt.Fetch = &ast.FetchClause{
Count: countExpr,
Loc: ast.Loc{Start: fetchTok.Loc.Start, End: onlyTok.Loc.End},
// p.prev is the last token consumed: ONLY, ROW/ROWS, or the
// end of the count expression.
Loc: ast.Loc{Start: fetchTok.Loc.Start, End: p.prev.Loc.End},
}
}

Expand Down
149 changes: 149 additions & 0 deletions snowflake/parser/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,62 @@ func TestSelect_Top(t *testing.T) {
}
}

// The TOP count is a constant, not a full expression: the `*` after the
// count is the star select target, not a multiplication operator.
func TestSelect_TopStar(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT TOP 125 * FROM t")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Top == nil {
t.Fatal("expected Top to be set")
}
lit, ok := sel.Top.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Top, got %T", sel.Top)
}
if lit.Ival != 125 {
t.Errorf("Top = %d, want 125", lit.Ival)
}
if len(sel.Targets) != 1 {
t.Fatalf("targets = %d, want 1", len(sel.Targets))
}
if !sel.Targets[0].Star {
t.Error("expected Targets[0].Star = true")
}
if len(sel.From) != 1 {
t.Fatalf("from = %d, want 1", len(sel.From))
}
}

func TestSelect_TopColumn(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT TOP 40 c1 FROM t")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Top == nil {
t.Fatal("expected Top to be set")
}
lit, ok := sel.Top.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Top, got %T", sel.Top)
}
if lit.Ival != 40 {
t.Errorf("Top = %d, want 40", lit.Ival)
}
if len(sel.Targets) != 1 {
t.Fatalf("targets = %d, want 1", len(sel.Targets))
}
}

func TestSelect_TopMissingCount(t *testing.T) {
// TOP with no count expression must error.
_, errs := testParseSelectStmt("SELECT TOP FROM t")
if len(errs) == 0 {
t.Fatal("expected error for TOP without count")
}
}

// ---------------------------------------------------------------------------
// 10. SELECT a FROM t1, t2 AS x — comma FROM with alias
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -541,6 +597,99 @@ func TestSelect_OffsetFetch(t *testing.T) {
}
}

// FIRST/NEXT, ROW/ROWS, and ONLY are all optional noise words: FETCH 123
// is equivalent to FETCH FIRST 123 ROWS ONLY.
func TestSelect_FetchBare(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT a FROM t FETCH 123")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Fetch == nil {
t.Fatal("expected Fetch to be set")
}
litCount, ok := sel.Fetch.Count.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Fetch.Count, got %T", sel.Fetch.Count)
}
if litCount.Ival != 123 {
t.Errorf("Fetch.Count = %d, want 123", litCount.Ival)
}
// Fetch.Loc must span FETCH..123.
input := "SELECT a FROM t FETCH 123"
if got := input[sel.Fetch.Loc.Start:sel.Fetch.Loc.End]; got != "FETCH 123" {
t.Errorf("Fetch.Loc spans %q, want %q", got, "FETCH 123")
}
}

func TestSelect_OffsetFetchBare(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT a FROM t OFFSET 12 FETCH 123")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Offset == nil {
t.Fatal("expected Offset to be set")
}
litOff, ok := sel.Offset.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Offset, got %T", sel.Offset)
}
if litOff.Ival != 12 {
t.Errorf("Offset = %d, want 12", litOff.Ival)
}
if sel.Fetch == nil {
t.Fatal("expected Fetch to be set")
}
litCount, ok := sel.Fetch.Count.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Fetch.Count, got %T", sel.Fetch.Count)
}
if litCount.Ival != 123 {
t.Errorf("Fetch.Count = %d, want 123", litCount.Ival)
}
}

func TestSelect_FetchFirstNoRowsOnly(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT a FROM t FETCH FIRST 5")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Fetch == nil {
t.Fatal("expected Fetch to be set")
}
litCount, ok := sel.Fetch.Count.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Fetch.Count, got %T", sel.Fetch.Count)
}
if litCount.Ival != 5 {
t.Errorf("Fetch.Count = %d, want 5", litCount.Ival)
}
}

func TestSelect_FetchNextRowNoOnly(t *testing.T) {
sel, errs := testParseSelectStmt("SELECT a FROM t FETCH NEXT 7 ROW")
if len(errs) > 0 {
t.Fatalf("unexpected errors: %v", errs)
}
if sel.Fetch == nil {
t.Fatal("expected Fetch to be set")
}
litCount, ok := sel.Fetch.Count.(*ast.Literal)
if !ok {
t.Fatalf("expected *ast.Literal for Fetch.Count, got %T", sel.Fetch.Count)
}
if litCount.Ival != 7 {
t.Errorf("Fetch.Count = %d, want 7", litCount.Ival)
}
}

func TestSelect_FetchMissingCount(t *testing.T) {
// FETCH with no count expression must error.
_, errs := testParseSelectStmt("SELECT a FROM t FETCH")
if len(errs) == 0 {
t.Fatal("expected error for FETCH without count")
}
}

// ---------------------------------------------------------------------------
// 19. WITH cte AS (SELECT 1 AS x) SELECT * FROM cte — basic CTE
// ---------------------------------------------------------------------------
Expand Down
Loading