Skip to content

Commit 315bc2b

Browse files
authored
sql/parse: parse ORDER BY and LIMIT clauses. (#35)
1 parent 167cfe5 commit 315bc2b

File tree

2 files changed

+170
-50
lines changed

2 files changed

+170
-50
lines changed

sql/parse/parse.go

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"io"
7+
"strconv"
78
"strings"
89

910
"github.com/gitql/gitql/sql"
@@ -25,6 +26,8 @@ const (
2526
OrderState
2627
OrderByState
2728
OrderClauseState
29+
LimitState
30+
LimitNumberState
2831
DoneState
2932

3033
ExprState
@@ -43,6 +46,7 @@ type parser struct {
4346
relation string
4447
filterClauses []sql.Expression
4548
sortFields []plan.SortField
49+
limit *int
4650
}
4751

4852
func newParser(input io.Reader) *parser {
@@ -111,16 +115,16 @@ func (p *parser) parse() error {
111115
p.stateStack.pop()
112116
state := p.stateStack.peek()
113117
var (
114-
breakKeyword string
115-
nextState ParseState
118+
breakKeywords []string
119+
nextState ParseState
116120
)
117121

118122
switch state {
119123
case SelectState:
120-
breakKeyword = "from"
124+
breakKeywords = []string{"from"}
121125
nextState = FromState
122126
case WhereState:
123-
breakKeyword = "order"
127+
breakKeywords = []string{"order", "limit"}
124128
nextState = OrderState
125129
default:
126130
p.errorf(`unexpected token %q`, t.Value)
@@ -133,11 +137,13 @@ func (p *parser) parse() error {
133137
p.stateStack.put(ExprState)
134138
break OuterSwitch
135139
case KeywordToken:
136-
if kwMatches(t.Value, breakKeyword) {
137-
p.lexer.Backup()
138-
p.stateStack.pop()
139-
p.stateStack.put(nextState)
140-
break OuterSwitch
140+
for _, kw := range breakKeywords {
141+
if kwMatches(t.Value, kw) {
142+
p.lexer.Backup()
143+
p.stateStack.pop()
144+
p.stateStack.put(nextState)
145+
break OuterSwitch
146+
}
141147
}
142148
case EOFToken:
143149
p.stateStack.pop()
@@ -146,8 +152,8 @@ func (p *parser) parse() error {
146152
}
147153
}
148154

149-
if breakKeyword != "" {
150-
p.errorf(`expecting "," or %q`, breakKeyword)
155+
if len(breakKeywords) > 0 {
156+
p.errorf(`expecting "," or %q`, breakKeywords)
151157
} else {
152158
p.errorf(`expecting "," or end of sentence`)
153159
}
@@ -181,7 +187,9 @@ func (p *parser) parse() error {
181187
p.stateStack.pop()
182188
p.stateStack.put(DoneState)
183189
} else if t.Type != KeywordToken || !kwMatches(t.Value, "where") {
184-
p.errorf("expecting 'WHERE', %q received", t.Value)
190+
p.lexer.Backup()
191+
p.stateStack.pop()
192+
p.stateStack.put(OrderState)
185193
} else {
186194
p.stateStack.put(WhereClauseState)
187195
}
@@ -202,7 +210,9 @@ func (p *parser) parse() error {
202210
p.stateStack.pop()
203211
p.stateStack.put(DoneState)
204212
} else if t.Type != KeywordToken || !kwMatches(t.Value, "order") {
205-
p.errorf("expecting 'ORDER', %q received", t.Value)
213+
p.lexer.Backup()
214+
p.stateStack.pop()
215+
p.stateStack.put(LimitState)
206216
} else {
207217
p.stateStack.put(OrderByState)
208218
}
@@ -224,6 +234,35 @@ func (p *parser) parse() error {
224234
} else {
225235
p.sortFields = fields
226236
p.stateStack.pop()
237+
p.stateStack.put(LimitState)
238+
}
239+
240+
case LimitState:
241+
t = p.lexer.Next()
242+
if t == nil || t.Type == EOFToken {
243+
p.stateStack.pop()
244+
p.stateStack.put(DoneState)
245+
} else if t.Type != KeywordToken || !kwMatches(t.Value, "limit") {
246+
p.errorf("expecting 'LIMIT', %q received", t.Value)
247+
} else {
248+
p.stateStack.pop()
249+
p.stateStack.put(LimitNumberState)
250+
}
251+
252+
case LimitNumberState:
253+
t = p.lexer.Next()
254+
if t == nil || t.Type == EOFToken {
255+
p.errorf("expecting integer, nothing received")
256+
} else if t.Type != IntToken {
257+
p.errorf("expecting integer, %q received", t.Value)
258+
} else {
259+
i, err := strconv.Atoi(t.Value)
260+
if err != nil {
261+
p.errorf("error parsing integer: %q", err)
262+
}
263+
264+
p.limit = &i
265+
p.stateStack.pop()
227266
p.stateStack.put(DoneState)
228267
}
229268
}
@@ -248,6 +287,10 @@ func (p *parser) buildPlan() (sql.Node, error) {
248287
node = plan.NewSort(p.sortFields, node)
249288
}
250289

290+
if p.limit != nil {
291+
node = plan.NewLimit(int64(*p.limit), node)
292+
}
293+
251294
return node, nil
252295
}
253296

@@ -301,6 +344,14 @@ func parseOrderClause(q tokenQueue) ([]plan.SortField, error) {
301344
field.Order = plan.Descending
302345
} else if kwMatches(tk.Value, "asc") {
303346
field.Order = plan.Ascending
347+
} else if kwMatches(tk.Value, "limit") {
348+
if field == nil {
349+
return nil, errors.New(`unexpected LIMIT, expecting identifier`)
350+
}
351+
352+
q.Backup()
353+
fields = append(fields, *field)
354+
return fields, nil
304355
} else {
305356
return nil, fmt.Errorf(`unexpected keyword %q, expecting "ASC", "DESC" or ","`, tk.Value)
306357
}
@@ -312,7 +363,7 @@ func parseOrderClause(q tokenQueue) ([]plan.SortField, error) {
312363
fields = append(fields, *field)
313364
field = nil
314365
case EOFToken:
315-
if field == nil || len(fields) == 0 {
366+
if field == nil {
316367
return nil, errors.New(`unexpected end of input, expecting identifier`)
317368
}
318369

sql/parse/parse_test.go

Lines changed: 105 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,117 @@ import (
66

77
"github.com/gitql/gitql/sql"
88
"github.com/gitql/gitql/sql/expression"
9+
"github.com/gitql/gitql/sql/plan"
910

10-
"github.com/stretchr/testify/require"
11+
"github.com/stretchr/testify/assert"
1112
)
1213

13-
const testSelectFromWhere = `SELECT foo, bar FROM foo WHERE foo = bar;`
14-
const testSelectFrom = `SELECT foo, bar FROM foo;`
15-
16-
func TestParseSelectFromWhere(t *testing.T) {
17-
p := newParser(strings.NewReader(testSelectFromWhere))
18-
require.Nil(t, p.parse())
19-
20-
require.Equal(t, p.projection, []sql.Expression{
21-
expression.NewUnresolvedColumn("foo"),
22-
expression.NewUnresolvedColumn("bar"),
23-
})
24-
25-
require.Equal(t, p.relation, "foo")
26-
27-
require.Equal(t, p.filterClauses, []sql.Expression{
28-
expression.NewEquals(
14+
var fixtures = map[string]sql.Node{
15+
`SELECT foo, bar FROM foo;`: plan.NewProject(
16+
[]sql.Expression{
2917
expression.NewUnresolvedColumn("foo"),
3018
expression.NewUnresolvedColumn("bar"),
19+
},
20+
plan.NewUnresolvedRelation("foo"),
21+
),
22+
`SELECT foo, bar FROM foo WHERE foo = bar;`: plan.NewProject(
23+
[]sql.Expression{
24+
expression.NewUnresolvedColumn("foo"),
25+
expression.NewUnresolvedColumn("bar"),
26+
},
27+
plan.NewFilter(
28+
expression.NewEquals(
29+
expression.NewUnresolvedColumn("foo"),
30+
expression.NewUnresolvedColumn("bar"),
31+
),
32+
plan.NewUnresolvedRelation("foo"),
3133
),
32-
})
33-
34-
require.Nil(t, p.sortFields)
35-
require.Nil(t, p.err)
36-
require.Equal(t, DoneState, p.stateStack.pop())
34+
),
35+
`SELECT foo, bar FROM foo WHERE foo = 'bar';`: plan.NewProject(
36+
[]sql.Expression{
37+
expression.NewUnresolvedColumn("foo"),
38+
expression.NewUnresolvedColumn("bar"),
39+
},
40+
plan.NewFilter(
41+
expression.NewEquals(
42+
expression.NewUnresolvedColumn("foo"),
43+
expression.NewLiteral("bar", sql.String),
44+
),
45+
plan.NewUnresolvedRelation("foo"),
46+
),
47+
),
48+
`SELECT foo, bar FROM foo LIMIT 10;`: plan.NewLimit(int64(10),
49+
plan.NewProject(
50+
[]sql.Expression{
51+
expression.NewUnresolvedColumn("foo"),
52+
expression.NewUnresolvedColumn("bar"),
53+
},
54+
plan.NewUnresolvedRelation("foo"),
55+
),
56+
),
57+
`SELECT foo, bar FROM foo ORDER BY baz DESC;`: plan.NewSort(
58+
[]plan.SortField{{expression.NewUnresolvedColumn("baz"), plan.Descending}},
59+
plan.NewProject(
60+
[]sql.Expression{
61+
expression.NewUnresolvedColumn("foo"),
62+
expression.NewUnresolvedColumn("bar"),
63+
},
64+
plan.NewUnresolvedRelation("foo"),
65+
),
66+
),
67+
`SELECT foo, bar FROM foo WHERE foo = bar LIMIT 10;`: plan.NewLimit(int64(10),
68+
plan.NewProject(
69+
[]sql.Expression{
70+
expression.NewUnresolvedColumn("foo"),
71+
expression.NewUnresolvedColumn("bar"),
72+
},
73+
plan.NewFilter(
74+
expression.NewEquals(
75+
expression.NewUnresolvedColumn("foo"),
76+
expression.NewUnresolvedColumn("bar"),
77+
),
78+
plan.NewUnresolvedRelation("foo"),
79+
),
80+
),
81+
),
82+
`SELECT foo, bar FROM foo ORDER BY baz DESC LIMIT 1;`: plan.NewLimit(int64(1),
83+
plan.NewSort(
84+
[]plan.SortField{{expression.NewUnresolvedColumn("baz"), plan.Descending}},
85+
plan.NewProject(
86+
[]sql.Expression{
87+
expression.NewUnresolvedColumn("foo"),
88+
expression.NewUnresolvedColumn("bar"),
89+
},
90+
plan.NewUnresolvedRelation("foo"),
91+
),
92+
),
93+
),
94+
`SELECT foo, bar FROM foo WHERE qux = 1 ORDER BY baz DESC LIMIT 1;`: plan.NewLimit(int64(1),
95+
plan.NewSort(
96+
[]plan.SortField{{expression.NewUnresolvedColumn("baz"), plan.Descending}},
97+
plan.NewProject(
98+
[]sql.Expression{
99+
expression.NewUnresolvedColumn("foo"),
100+
expression.NewUnresolvedColumn("bar"),
101+
},
102+
plan.NewFilter(
103+
expression.NewEquals(
104+
expression.NewUnresolvedColumn("qux"),
105+
expression.NewLiteral(int64(1), sql.BigInteger),
106+
),
107+
plan.NewUnresolvedRelation("foo"),
108+
),
109+
),
110+
),
111+
),
37112
}
38113

39-
func TestParseSelectFrom(t *testing.T) {
40-
p := newParser(strings.NewReader(testSelectFrom))
41-
require.Nil(t, p.parse())
42-
43-
require.Equal(t, p.projection, []sql.Expression{
44-
expression.NewUnresolvedColumn("foo"),
45-
expression.NewUnresolvedColumn("bar"),
46-
})
47-
48-
require.Equal(t, p.relation, "foo")
49-
50-
require.Nil(t, p.sortFields)
51-
require.Nil(t, p.err)
52-
require.Equal(t, DoneState, p.stateStack.pop())
114+
func TestParse(t *testing.T) {
115+
assert := assert.New(t)
116+
for query, expectedPlan := range fixtures {
117+
p, err := Parse(strings.NewReader(query))
118+
assert.Nil(err)
119+
assert.Exactly(expectedPlan, p,
120+
"plans do not match for query '%s'", query)
121+
}
53122
}

0 commit comments

Comments
 (0)