Skip to content

Commit 7eaa23b

Browse files
committed
JOIN support
1 parent 4f5b7e5 commit 7eaa23b

6 files changed

Lines changed: 79 additions & 27 deletions

File tree

‎lib/grammar.coffee‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ grammar =
3838
SelectClause: [
3939
o 'SELECT Fields FROM Table', -> new Select($2, $4, false)
4040
o 'SELECT DISTINCT Fields FROM Table', -> new Select($3, $5, true)
41+
o 'SELECT Fields FROM Table Joins', -> new Select($2, $4, false, $5)
42+
o 'SELECT DISTINCT Fields FROM Table Joins', -> new Select($3, $5, true, $6)
4143
]
4244

4345
Table: [
@@ -48,6 +50,21 @@ grammar =
4850
-> new Table($1, $2, $3, $5)
4951
]
5052

53+
Joins: [
54+
o 'Join', -> [$1]
55+
o 'Joins Join', -> $1.concat($2)
56+
]
57+
58+
Join: [
59+
o 'JOIN Table ON Expression', -> new Join($2, $4)
60+
o 'LEFT JOIN Table ON Expression', -> new Join($3, $5, 'LEFT')
61+
o 'RIGHT JOIN Table ON Expression', -> new Join($3, $5, 'RIGHT')
62+
o 'LEFT INNER JOIN Table ON Expression', -> new Join($4, $6, 'LEFT', 'INNER')
63+
o 'RIGHT INNER JOIN Table ON Expression', -> new Join($4, $6, 'RIGHT', 'INNER')
64+
o 'LEFT OUTER JOIN Table ON Expression', -> new Join($4, $6, 'LEFT', 'OUTER')
65+
o 'RIGHT OUTER JOIN Table ON Expression', -> new Join($4, $6, 'RIGHT', 'OUTER')
66+
]
67+
5168
WhereClause: [
5269
o 'WHERE Expression', -> new Where($2)
5370
]

‎lib/lexer.coffee‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class Lexer
6363
@tokenizeFromWord('BY') or
6464
@tokenizeFromWord('HAVING') or
6565
@tokenizeFromWord('LIMIT') or
66+
@tokenizeFromWord('JOIN') or
67+
@tokenizeFromWord('LEFT') or
68+
@tokenizeFromWord('RIGHT') or
69+
@tokenizeFromWord('INNER') or
70+
@tokenizeFromWord('OUTER') or
71+
@tokenizeFromWord('ON') or
6672
@tokenizeFromWord('AS')
6773

6874
dotToken: -> @tokenizeFromWord('DOT', '.')

‎lib/nodes.coffee‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ indent = (str) ->
22
(" #{line}" for line in str.split("\n")).join("\n")
33

44
exports.Select = class Select
5-
constructor: (@fields, @source, @distinct=false) ->
5+
constructor: (@fields, @source, @distinct=false, @joins=[]) ->
66
@order = null
77
@group = null
88
@where = null
99
@limit = null
1010
toString: ->
1111
ret = ["SELECT #{@fields.join(', ')}"]
1212
ret.push indent("FROM #{@source}")
13+
ret.push indent(join.toString()) for join in @joins
1314
ret.push indent(@where.toString()) if @where
1415
ret.push indent(@group.toString()) if @group
1516
ret.push indent(@order.toString()) if @order
@@ -25,6 +26,14 @@ exports.SubSelect = class SubSelect
2526
ret.push if @name then ") #{@name.toString()}" else ")"
2627
ret.join("\n")
2728

29+
exports.Join = class Join
30+
constructor: (@right, @condtions=null, @side=null, @mode=null) -> null
31+
toString: ->
32+
ret = ''
33+
ret += "#{@side} " if @side?
34+
ret += "#{@mode} " if @mode?
35+
ret + "JOIN #{@right}\n" + indent("ON #{@condtions}")
36+
2837
exports.LiteralValue = class LiteralValue
2938
constructor: (@value, @value2=null) ->
3039
if @value2

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "sql-parser",
33
"description": "Lexer and Parser for SQL Syntax",
4-
"version": "0.2.0",
4+
"version": "0.2.1",
55
"author": {
66
"name": "Andy Kent",
77
"email": "andy@forward.co.uk"

‎test/grammar.spec.coffee‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,29 @@ describe "SQL Grammer", ->
160160
FROM `my_table`
161161
) `t`
162162
"""
163+
164+
it "parses single joins", ->
165+
parse("select * from a join b on a.id = b.id").toString().should.eql """
166+
SELECT *
167+
FROM `a`
168+
JOIN `b`
169+
ON (`a.id` = `b.id`)
170+
"""
171+
172+
it "parses right outer joins", ->
173+
parse("select * from a right outer join b on a.id = b.id").toString().should.eql """
174+
SELECT *
175+
FROM `a`
176+
RIGHT OUTER JOIN `b`
177+
ON (`a.id` = `b.id`)
178+
"""
179+
180+
it "parses multiple joins", ->
181+
parse("select * from a join b on a.id = b.id join c on a.id = c.id").toString().should.eql """
182+
SELECT *
183+
FROM `a`
184+
JOIN `b`
185+
ON (`a.id` = `b.id`)
186+
JOIN `c`
187+
ON (`a.id` = `c.id`)
188+
"""

‎test/lexer.spec.coffee‎

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,22 @@ describe "SQL Lexer", ->
2727
["EOF", "", 1]
2828
]
2929

30-
# tokens = lexer.tokenize("SELECT * FROM my_stream WHERE name = 'andy' AND age = 28")
31-
#
32-
# console.log(tokens)
33-
#
34-
# tokens = lexer.tokenize("SELECT name, age FROM my_stream WHERE name = 'andy' AND age = 28")
35-
#
36-
# console.log(tokens)
37-
#
38-
# tokens = lexer.tokenize("SELECT LENGTH(foo) FROM data")
39-
#
40-
# console.log(tokens)
41-
#
42-
# tokens = lexer.tokenize("SELECT f+1 AS f1 FROM data")
43-
#
44-
# console.log(tokens)
45-
#
46-
# tokens = lexer.tokenize """
47-
# SELECT x AS `first_name`, min(age)
48-
# FROM my_stream.win:length(123)
49-
# WHERE age > 10.2 AND (age < 30 OR first_name = 'andy')
50-
# GROUP BY age, name
51-
# ORDER BY age DESC
52-
# HAVING COUNT(*) > 5"""
53-
#
54-
# console.log(tokens)
30+
it "eats joins", ->
31+
tokens = lexer.tokenize("select * from a join b on a.id = b.id")
32+
tokens.should.eql [
33+
["SELECT", "select", 1]
34+
["STAR", "*", 1]
35+
["FROM", "from", 1]
36+
[ 'LITERAL', 'a', 1 ]
37+
[ 'JOIN', 'join', 1 ]
38+
[ 'LITERAL', 'b', 1 ]
39+
[ 'ON', 'on', 1 ]
40+
[ 'LITERAL', 'a', 1 ]
41+
[ 'DOT', '.', 1 ]
42+
[ 'LITERAL', 'id', 1 ]
43+
[ 'OPERATOR', '=', 1 ]
44+
[ 'LITERAL', 'b', 1 ]
45+
[ 'DOT', '.', 1 ]
46+
[ 'LITERAL', 'id', 1 ]
47+
["EOF", "", 1]
48+
]

0 commit comments

Comments
 (0)