sql_tree 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/sql_tree/node/expression.rb +0 -24
- data/lib/sql_tree/node/field.rb +3 -4
- data/lib/sql_tree/node/ordering.rb +25 -0
- data/lib/sql_tree/node/select_query.rb +14 -6
- data/lib/sql_tree/node/source.rb +1 -1
- data/lib/sql_tree/node/value.rb +0 -4
- data/lib/sql_tree/node/variable.rb +0 -4
- data/lib/sql_tree/token.rb +6 -2
- data/spec/integration/full_queries_spec.rb +18 -3
- data/spec/lib/matchers.rb +0 -28
- data/spec/unit/select_query_spec.rb +29 -0
- data/sql_tree.gemspec +2 -2
- metadata +2 -1
@@ -49,10 +49,6 @@ module SQLTree::Node
|
|
49
49
|
"NOT(#{@expression.to_sql})"
|
50
50
|
end
|
51
51
|
|
52
|
-
def to_tree
|
53
|
-
[:not, expression.to_tree]
|
54
|
-
end
|
55
|
-
|
56
52
|
def ==(other)
|
57
53
|
other.kind_of?(self.class) && other.expression == self.expression
|
58
54
|
end
|
@@ -75,10 +71,6 @@ module SQLTree::Node
|
|
75
71
|
"(" + @expressions.map { |e| e.to_sql }.join(" #{@operator.to_s.upcase} ") + ")"
|
76
72
|
end
|
77
73
|
|
78
|
-
def to_tree
|
79
|
-
[@operator] + @expressions.map { |e| e.to_tree }
|
80
|
-
end
|
81
|
-
|
82
74
|
def ==(other)
|
83
75
|
self.operator == other.operator && self.expressions == other.expressions
|
84
76
|
end
|
@@ -105,10 +97,6 @@ module SQLTree::Node
|
|
105
97
|
"(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
|
106
98
|
end
|
107
99
|
|
108
|
-
def to_tree
|
109
|
-
[SQLTree::Token::OPERATORS_HASH[@operator], @lhs.to_tree, @rhs.to_tree]
|
110
|
-
end
|
111
|
-
|
112
100
|
def self.parse_comparison_operator(tokens)
|
113
101
|
operator_token = tokens.next
|
114
102
|
if SQLTree::Token::IS === operator_token
|
@@ -151,10 +139,6 @@ module SQLTree::Node
|
|
151
139
|
@items = items
|
152
140
|
end
|
153
141
|
|
154
|
-
def to_tree
|
155
|
-
items.map { |i| i.to_tree }
|
156
|
-
end
|
157
|
-
|
158
142
|
def to_sql
|
159
143
|
"(#{items.map {|i| i.to_sql}.join(', ')})"
|
160
144
|
end
|
@@ -184,10 +168,6 @@ module SQLTree::Node
|
|
184
168
|
"#{@function}(" + @arguments.map { |e| e.to_sql }.join(', ') + ")"
|
185
169
|
end
|
186
170
|
|
187
|
-
def to_tree
|
188
|
-
[@function.to_sym] + @arguments.map { |e| e.to_tree }
|
189
|
-
end
|
190
|
-
|
191
171
|
def self.parse(tokens)
|
192
172
|
expr = self.new(tokens.next.literal)
|
193
173
|
tokens.consume(SQLTree::Token::LPAREN)
|
@@ -213,10 +193,6 @@ module SQLTree::Node
|
|
213
193
|
"(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
|
214
194
|
end
|
215
195
|
|
216
|
-
def to_tree
|
217
|
-
[SQLTree::Token::OPERATORS_HASH[@operator], @lhs.to_tree, @rhs.to_tree]
|
218
|
-
end
|
219
|
-
|
220
196
|
def self.parse(tokens)
|
221
197
|
self.parse_primary(tokens)
|
222
198
|
end
|
data/lib/sql_tree/node/field.rb
CHANGED
@@ -4,6 +4,9 @@ module SQLTree::Node
|
|
4
4
|
|
5
5
|
attr_accessor :name, :table
|
6
6
|
|
7
|
+
alias :field :name
|
8
|
+
alias :field= :name=
|
9
|
+
|
7
10
|
def initialize(name, table = nil)
|
8
11
|
@name = name
|
9
12
|
@table = table
|
@@ -17,10 +20,6 @@ module SQLTree::Node
|
|
17
20
|
def to_sql
|
18
21
|
@table.nil? ? quote_var(@name) : quote_var(@table) + '.' + quote_var(@name)
|
19
22
|
end
|
20
|
-
|
21
|
-
def to_tree
|
22
|
-
to_sql.to_sym
|
23
|
-
end
|
24
23
|
|
25
24
|
def ==(other)
|
26
25
|
other.name == self.name && other.table == self.table
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SQLTree::Node
|
2
|
+
|
3
|
+
class Ordering < Base
|
4
|
+
|
5
|
+
attr_accessor :expression, :direction
|
6
|
+
|
7
|
+
def initialize(expression, direction = nil)
|
8
|
+
@expression, @direction = expression, direction
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_sql
|
12
|
+
sql = expression.to_sql
|
13
|
+
sql << " #{direction.to_s.upcase}" if direction
|
14
|
+
sql
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse(tokens)
|
18
|
+
ordering = self.new(SQLTree::Node::Expression.parse(tokens))
|
19
|
+
if tokens.peek && tokens.peek.direction?
|
20
|
+
ordering.direction = tokens.next.literal.downcase.to_sym
|
21
|
+
end
|
22
|
+
return ordering
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -14,7 +14,8 @@ module SQLTree::Node
|
|
14
14
|
sql = (self.distinct) ? "SELECT DISTINCT " : "SELECT "
|
15
15
|
sql << select.map { |s| s.to_sql }.join(', ')
|
16
16
|
sql << " FROM " << from.map { |f| f.to_sql }.join(', ')
|
17
|
-
sql << " WHERE " << where.to_sql
|
17
|
+
sql << " WHERE " << where.to_sql if where
|
18
|
+
sql << " ORDER BY " << order_by.map { |o| o.to_sql }.join(', ') if order_by
|
18
19
|
return sql
|
19
20
|
end
|
20
21
|
|
@@ -28,10 +29,10 @@ module SQLTree::Node
|
|
28
29
|
select_node.distinct = true
|
29
30
|
end
|
30
31
|
|
31
|
-
select_node.select
|
32
|
-
select_node.from
|
33
|
-
select_node.where
|
34
|
-
|
32
|
+
select_node.select = self.parse_select_clause(tokens)
|
33
|
+
select_node.from = self.parse_from_clause(tokens) if tokens.peek == SQLTree::Token::FROM
|
34
|
+
select_node.where = self.parse_where_clause(tokens) if tokens.peek == SQLTree::Token::WHERE
|
35
|
+
select_node.order_by = self.parse_order_clause(tokens) if tokens.peek == SQLTree::Token::ORDER
|
35
36
|
return select_node
|
36
37
|
end
|
37
38
|
|
@@ -68,7 +69,14 @@ module SQLTree::Node
|
|
68
69
|
end
|
69
70
|
|
70
71
|
def self.parse_order_clause(tokens)
|
71
|
-
|
72
|
+
tokens.consume(SQLTree::Token::ORDER)
|
73
|
+
tokens.consume(SQLTree::Token::BY)
|
74
|
+
exprs = [SQLTree::Node::Ordering.parse(tokens)]
|
75
|
+
while tokens.peek == SQLTree::Token::COMMA
|
76
|
+
tokens.consume(SQLTree::Token::COMMA)
|
77
|
+
exprs << SQLTree::Node::Ordering.parse(tokens)
|
78
|
+
end
|
79
|
+
return exprs
|
72
80
|
end
|
73
81
|
|
74
82
|
def self.parse_limit_clause(tokens)
|
data/lib/sql_tree/node/source.rb
CHANGED
@@ -29,7 +29,7 @@ module SQLTree::Node
|
|
29
29
|
def self.parse(tokens)
|
30
30
|
source = self.new(SQLTree::Node::TableReference.parse(tokens))
|
31
31
|
while tokens.peek && tokens.peek.join?
|
32
|
-
source.joins << Join.parse(tokens)
|
32
|
+
source.joins << SQLTree::Node::Join.parse(tokens)
|
33
33
|
end
|
34
34
|
return source
|
35
35
|
end
|
data/lib/sql_tree/node/value.rb
CHANGED
data/lib/sql_tree/token.rb
CHANGED
@@ -36,6 +36,10 @@ class SQLTree::Token
|
|
36
36
|
SQLTree::Token::FULL].include?(self)
|
37
37
|
end
|
38
38
|
|
39
|
+
def direction?
|
40
|
+
[SQLTree::Token::ASC, SQLTree::Token::DESC].include?(self)
|
41
|
+
end
|
42
|
+
|
39
43
|
###################################################################
|
40
44
|
# DYNAMIC TOKEN TYPES
|
41
45
|
###################################################################
|
@@ -107,8 +111,8 @@ class SQLTree::Token
|
|
107
111
|
COMMA = Class.new(SQLTree::Token).new(',')
|
108
112
|
|
109
113
|
# A list of all the SQL reserverd keywords.
|
110
|
-
KEYWORDS = %w{SELECT FROM WHERE
|
111
|
-
AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN}
|
114
|
+
KEYWORDS = %w{SELECT FROM WHERE GROUP HAVING ORDER DISTINCT LEFT RIGHT INNER FULL OUTER NATURAL JOIN USING
|
115
|
+
AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN ASC DESC}
|
112
116
|
|
113
117
|
# Create a token for all the reserved keywords in SQL
|
114
118
|
KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword).new(kw)) }
|
@@ -14,8 +14,23 @@ describe SQLTree, 'parsing and generating SQL' do
|
|
14
14
|
SQLTree["SELECT a.* FROM table AS a"].to_sql.should == 'SELECT "a".* FROM "table" AS "a"'
|
15
15
|
end
|
16
16
|
|
17
|
-
it "parse and generate
|
18
|
-
SQLTree[
|
19
|
-
|
17
|
+
it "should parse and generate an ORDER BY clause" do
|
18
|
+
SQLTree["SELECT * FROM table ORDER BY field1, field2"].to_sql.should ==
|
19
|
+
'SELECT * FROM "table" ORDER BY "field1", "field2"'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should parse and generate an expression in the SELECT clause" do
|
23
|
+
SQLTree['SELECT MD5( a) AS a, b > 0 AS test FROM table'].to_sql.should ==
|
24
|
+
'SELECT MD5("a") AS "a", ("b" > 0) AS "test" FROM "table"'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should parse and generate a complex FROM clause" do
|
28
|
+
SQLTree['SELECT * FROM a LEFT JOIN b ON ( a.id = b.a_id), c AS d'].to_sql.should ==
|
29
|
+
'SELECT * FROM "a" LEFT JOIN "b" ON ("a"."id" = "b"."a_id"), "c" AS "d"'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse and generate a WHERE clause" do
|
33
|
+
SQLTree['SELECT * FROM t WHERE ( field > 4 OR NOW() > timestamp) AND other_field IS NOT NULL'].to_sql.should ==
|
34
|
+
'SELECT * FROM "t" WHERE ((("field" > 4) OR (NOW() > "timestamp")) AND ("other_field" IS NOT NULL))'
|
20
35
|
end
|
21
36
|
end
|
data/spec/lib/matchers.rb
CHANGED
@@ -1,31 +1,3 @@
|
|
1
|
-
class ParseAs
|
2
|
-
|
3
|
-
def initialize(expected_tree)
|
4
|
-
@expected_tree = expected_tree
|
5
|
-
end
|
6
|
-
|
7
|
-
def matches?(found_tree)
|
8
|
-
@found_tree = found_tree.to_tree
|
9
|
-
return @found_tree == @expected_tree
|
10
|
-
end
|
11
|
-
|
12
|
-
def description
|
13
|
-
"expected to parse to #{@expected_tree.inspect}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def failure_message
|
17
|
-
" #{@expected_tree.inspect} expected, but found #{@found_tree.inspect}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def negative_failure_message
|
21
|
-
" expected not to be tokenized to #{@expected_tree.inspect}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def parse_as(tree)
|
26
|
-
ParseAs.new(tree)
|
27
|
-
end
|
28
|
-
|
29
1
|
class TokenizeTo
|
30
2
|
|
31
3
|
def initialize(expected_tokens)
|
@@ -50,3 +50,32 @@ describe SQLTree::Node::Join do
|
|
50
50
|
SQLTree::Node::Join['LEFT JOIN table t ON other.field = table.field'].table_alias.should == 't'
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
describe SQLTree::Node::Ordering do
|
55
|
+
it "should parse an ordering with direction" do
|
56
|
+
ordering = SQLTree::Node::Ordering["table.field ASC"]
|
57
|
+
ordering.expression.table.should == 'table'
|
58
|
+
ordering.expression.name.should == 'field'
|
59
|
+
ordering.direction.should == :asc
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should parse an ordering without direction" do
|
63
|
+
ordering = SQLTree::Node::Ordering["table.field"]
|
64
|
+
ordering.expression.table.should == 'table'
|
65
|
+
ordering.expression.name.should == 'field'
|
66
|
+
ordering.direction.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should parse an ordering without direction" do
|
70
|
+
ordering = SQLTree::Node::Ordering["MD5(3 + 6) DESC"]
|
71
|
+
ordering.expression.should be_kind_of(SQLTree::Node::FunctionExpression)
|
72
|
+
ordering.direction.should == :desc
|
73
|
+
end
|
74
|
+
|
75
|
+
it "shoulde parse multiple orderings" do
|
76
|
+
tree = SQLTree['SELECT * FROM table ORDER BY field1 ASC, field2 DESC']
|
77
|
+
tree.order_by.should have(2).items
|
78
|
+
tree.order_by[0].direction.should == :asc
|
79
|
+
tree.order_by[1].direction.should == :desc
|
80
|
+
end
|
81
|
+
end
|
data/sql_tree.gemspec
CHANGED
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
|
|
3
3
|
|
4
4
|
# Do not modify the version and date values by hand, because this will
|
5
5
|
# automatically by them gem release script.
|
6
|
-
s.version =
|
6
|
+
s.version = "0.0.2"
|
7
7
|
s.date = "2009-10-09"
|
8
8
|
|
9
9
|
s.summary = "A pure Ruby library to represent SQL queries with a syntax tree for inspection and modification."
|
@@ -22,6 +22,6 @@ Gem::Specification.new do |s|
|
|
22
22
|
|
23
23
|
# Do not modify the files and test_files values by hand, because this will
|
24
24
|
# automatically by them gem release script.
|
25
|
-
s.files = %w(spec/unit/select_query_spec.rb spec/spec_helper.rb lib/sql_tree/tokenizer.rb lib/sql_tree/node/variable.rb lib/sql_tree/node/join.rb .gitignore LICENSE spec/lib/matchers.rb spec/integration/full_queries_spec.rb lib/sql_tree/parser.rb sql_tree.gemspec spec/unit/tokenizer_spec.rb spec/unit/expression_node_spec.rb lib/sql_tree/node/select_expression.rb spec/unit/leaf_node_spec.rb lib/sql_tree/token.rb lib/sql_tree/node/table_reference.rb lib/sql_tree/node/source.rb lib/sql_tree/node/field.rb Rakefile tasks/github-gem.rake lib/sql_tree/node/select_query.rb lib/sql_tree/node.rb README.rdoc spec/integration/api_spec.rb lib/sql_tree/node/value.rb lib/sql_tree/node/expression.rb lib/sql_tree.rb)
|
25
|
+
s.files = %w(spec/unit/select_query_spec.rb spec/spec_helper.rb lib/sql_tree/tokenizer.rb lib/sql_tree/node/variable.rb lib/sql_tree/node/join.rb .gitignore lib/sql_tree/node/ordering.rb LICENSE spec/lib/matchers.rb spec/integration/full_queries_spec.rb lib/sql_tree/parser.rb sql_tree.gemspec spec/unit/tokenizer_spec.rb spec/unit/expression_node_spec.rb lib/sql_tree/node/select_expression.rb spec/unit/leaf_node_spec.rb lib/sql_tree/token.rb lib/sql_tree/node/table_reference.rb lib/sql_tree/node/source.rb lib/sql_tree/node/field.rb Rakefile tasks/github-gem.rake lib/sql_tree/node/select_query.rb lib/sql_tree/node.rb README.rdoc spec/integration/api_spec.rb lib/sql_tree/node/value.rb lib/sql_tree/node/expression.rb lib/sql_tree.rb)
|
26
26
|
s.test_files = %w(spec/unit/select_query_spec.rb spec/integration/full_queries_spec.rb spec/unit/tokenizer_spec.rb spec/unit/expression_node_spec.rb spec/unit/leaf_node_spec.rb spec/integration/api_spec.rb)
|
27
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sql_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- lib/sql_tree/node/variable.rb
|
29
29
|
- lib/sql_tree/node/join.rb
|
30
30
|
- .gitignore
|
31
|
+
- lib/sql_tree/node/ordering.rb
|
31
32
|
- LICENSE
|
32
33
|
- spec/lib/matchers.rb
|
33
34
|
- spec/integration/full_queries_spec.rb
|