sql_tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ /pkg
2
+ /tmp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Willem van Bergen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ = SQLTree
2
+
3
+ SQLTree is a pure Ruby library to represent SQL queries with a syntax tree
4
+ for inspection and modification.
5
+
6
+ The library can parse an SQL query (a string) to represent the query using
7
+ a syntax tree, and it can generate an SQL query from a syntax tree. The syntax
8
+ tree ca be used to inspect to query, or to modify it.
9
+
10
+ This library is currently under heavy development. This means that not all
11
+ SQL constructs are supported yet. For now, only SQL <tt>SELECT</tt> queries
12
+ are supported.
13
+
14
+ == Installation
15
+
16
+ The SQLTree library is distributed as a gem on Gemcutter.org. To install:
17
+
18
+ gem install sql_tree --source http://gemcutter.org
19
+
20
+ == Additional information
21
+
22
+ * See the project wiki at http://wiki.github.com/wvanbergen/sql_tree for more
23
+ information about using this library.
24
+ * This plugin is written by Willem van Bergen and is MIT licensed (see the
25
+ LICENSE file).
@@ -0,0 +1,5 @@
1
+ Dir['tasks/*.rake'].each { |file| load(file) }
2
+
3
+ GithubGem::RakeTasks.new(:gem)
4
+
5
+ task :default => [:spec]
@@ -0,0 +1,42 @@
1
+ # The SQLTree module is the basic namespace for the sql_tree gem.
2
+ #
3
+ # It contains the shorthand parse method (i.e. <tt>SQLTree[sql_query]</tt>)
4
+ # and some helper methods that are used by the gem. It also requires the
5
+ # necessary files for the gem to function properly.
6
+ module SQLTree
7
+
8
+ # Loads constants in the SQLTree namespace using self.load_default_class_file(base, const)
9
+ # <tt>const</tt>:: The constant that is not yet loaded in the SQLTree namespace. This should be passed as a string or symbol.
10
+ def self.const_missing(const)
11
+ load_default_class_file(SQLTree, const)
12
+ end
13
+
14
+ # Loads constants that reside in the SQLTree tree using the constant name
15
+ # and its base constant to determine the filename.
16
+ # <tt>base</tt>:: The base constant to load the constant from. This should be Foo when the constant Foo::Bar is being loaded.
17
+ # <tt>const</tt>:: The constant to load from the base constant as a string or symbol. This should be 'Bar' or :Bar when the constant Foo::Bar is being loaded.
18
+ def self.load_default_class_file(base, const)
19
+ require "#{to_underscore("#{base.name}::#{const}")}"
20
+ base.const_get(const) if base.const_defined?(const)
21
+ end
22
+
23
+ # The <tt>[]</tt> method is a shorthand for the <tt>SQLTree::Parser.parse</tt>
24
+ # method to parse an SQL query and return a SQL syntax tree.
25
+ def self.[](query, options = {})
26
+ SQLTree::Parser.parse(query)
27
+ end
28
+
29
+ # Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
30
+ # This function can be used to load the file (using require) in which the given constant is defined.
31
+ # <tt>str</tt>:: The string to convert in the following format: <tt>ModuleName::ClassName</tt>
32
+ def self.to_underscore(str)
33
+ str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
34
+ end
35
+
36
+ # Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
37
+ # (<tt>RequestLogAnalyzer::Controller</tt>). This can be used to find the class that is defined in a given filename.
38
+ # <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
39
+ def self.to_camelcase(str)
40
+ str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ module SQLTree::Node
2
+
3
+ def self.const_missing(const)
4
+ SQLTree.load_default_class_file(SQLTree::Node, const)
5
+ end
6
+
7
+ class Base
8
+
9
+ # Pretty prints this instance for inspection
10
+ def inspect
11
+ "#{self.class.name}[#{self.to_sql}]"
12
+ end
13
+
14
+ # Quotes a variable name so that it can be safely used within
15
+ # SQL queries.
16
+ def quote_var(name)
17
+ "\"#{name}\""
18
+ end
19
+
20
+ # Quotes a string so that it can be used within an SQL query.
21
+ def quote_str(str)
22
+ "'#{str.gsub(/\'/, "''")}'"
23
+ end
24
+
25
+ # This method should be implemented by a subclass.
26
+ def self.parse(tokens)
27
+ raise 'Only implemented in subclasses!'
28
+ end
29
+
30
+ # Parses a string, expecting it to be parsable to an instance of
31
+ # the current class.
32
+ def self.[](sql, options = {})
33
+ parser = SQLTree::Parser.new(sql, options)
34
+ self.parse(parser)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,240 @@
1
+ module SQLTree::Node
2
+
3
+ # Base class for all SQL expressions.
4
+ #
5
+ # This is an asbtract class and should not be used directly. Use
6
+ # one of the subclasses instead.
7
+ class Expression < Base
8
+
9
+ def self.parse(tokens)
10
+ SQLTree::Node::LogicalExpression.parse(tokens)
11
+ end
12
+
13
+ # Parses a single, atomic SQL expression. This can be either:
14
+ # * a full expression (or set of expressions) within parentheses.
15
+ # * a logical NOT expression
16
+ # * an SQL variable
17
+ # * an SQL function
18
+ # * a literal SQL value (numeric or string)
19
+ def self.parse_atomic(tokens)
20
+ case tokens.peek
21
+ when SQLTree::Token::LPAREN
22
+ tokens.consume(SQLTree::Token::LPAREN)
23
+ expr = SQLTree::Node::Expression.parse(tokens)
24
+ tokens.consume(SQLTree::Token::RPAREN)
25
+ expr
26
+ when SQLTree::Token::NOT
27
+ SQLTree::Node::LogicalNotExpression.parse(tokens)
28
+ when SQLTree::Token::Variable
29
+ if tokens.peek(2) == SQLTree::Token::LPAREN
30
+ SQLTree::Node::FunctionExpression.parse(tokens)
31
+ else
32
+ SQLTree::Node::Variable.parse(tokens)
33
+ end
34
+ else
35
+ SQLTree::Node::Value.parse(tokens)
36
+ end
37
+ end
38
+ end
39
+
40
+ class LogicalNotExpression < Expression
41
+
42
+ attr_accessor :expression
43
+
44
+ def initialize(expression)
45
+ @expression = expression
46
+ end
47
+
48
+ def to_sql
49
+ "NOT(#{@expression.to_sql})"
50
+ end
51
+
52
+ def to_tree
53
+ [:not, expression.to_tree]
54
+ end
55
+
56
+ def ==(other)
57
+ other.kind_of?(self.class) && other.expression == self.expression
58
+ end
59
+
60
+ def self.parse(tokens)
61
+ tokens.consume(SQLTree::Token::NOT)
62
+ self.new(SQLTree::Node::Expression.parse(tokens))
63
+ end
64
+ end
65
+
66
+ class LogicalExpression < Expression
67
+ attr_accessor :operator, :expressions
68
+
69
+ def initialize(operator, expressions)
70
+ @expressions = expressions
71
+ @operator = operator.to_s.downcase.to_sym
72
+ end
73
+
74
+ def to_sql
75
+ "(" + @expressions.map { |e| e.to_sql }.join(" #{@operator.to_s.upcase} ") + ")"
76
+ end
77
+
78
+ def to_tree
79
+ [@operator] + @expressions.map { |e| e.to_tree }
80
+ end
81
+
82
+ def ==(other)
83
+ self.operator == other.operator && self.expressions == other.expressions
84
+ end
85
+
86
+ def self.parse(tokens)
87
+ expr = ComparisonExpression.parse(tokens)
88
+ while [SQLTree::Token::AND, SQLTree::Token::OR].include?(tokens.peek)
89
+ expr = SQLTree::Node::LogicalExpression.new(tokens.next.literal, [expr, ComparisonExpression.parse(tokens)])
90
+ end
91
+ return expr
92
+ end
93
+ end
94
+
95
+ class ComparisonExpression < Expression
96
+ attr_accessor :lhs, :rhs, :operator
97
+
98
+ def initialize(operator, lhs, rhs)
99
+ @lhs = lhs
100
+ @rhs = rhs
101
+ @operator = operator
102
+ end
103
+
104
+ def to_sql
105
+ "(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
106
+ end
107
+
108
+ def to_tree
109
+ [SQLTree::Token::OPERATORS_HASH[@operator], @lhs.to_tree, @rhs.to_tree]
110
+ end
111
+
112
+ def self.parse_comparison_operator(tokens)
113
+ operator_token = tokens.next
114
+ if SQLTree::Token::IS === operator_token
115
+ if SQLTree::Token::NOT === tokens.peek
116
+ tokens.consume(SQLTree::Token::NOT)
117
+ 'IS NOT'
118
+ else
119
+ 'IS'
120
+ end
121
+ elsif SQLTree::Token::NOT === operator_token
122
+ case tokens.peek
123
+ when SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::BETWEEN, SQLTree::Token::IN
124
+ "NOT #{tokens.next.literal.upcase}"
125
+ else
126
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.peek)
127
+ end
128
+ else
129
+ operator_token.literal
130
+ end
131
+ end
132
+
133
+ def self.parse(tokens)
134
+ lhs = SQLTree::Node::ArithmeticExpression.parse(tokens)
135
+ while SQLTree::Token::COMPARISON_OPERATORS.include?(tokens.peek)
136
+ comparison_operator = parse_comparison_operator(tokens)
137
+ rhs = ['IN', 'NOT IN'].include?(comparison_operator) ?
138
+ SQLTree::Node::SetExpression.parse(tokens) :
139
+ SQLTree::Node::ArithmeticExpression.parse(tokens)
140
+
141
+ lhs = self.new(comparison_operator, lhs, rhs)
142
+ end
143
+ return lhs
144
+ end
145
+ end
146
+
147
+ class SetExpression < Expression
148
+ attr_accessor :items
149
+
150
+ def initialize(items = [])
151
+ @items = items
152
+ end
153
+
154
+ def to_tree
155
+ items.map { |i| i.to_tree }
156
+ end
157
+
158
+ def to_sql
159
+ "(#{items.map {|i| i.to_sql}.join(', ')})"
160
+ end
161
+
162
+ def self.parse(tokens)
163
+ tokens.consume(SQLTree::Token::LPAREN)
164
+ items = [SQLTree::Node::Expression.parse(tokens)]
165
+ while tokens.peek == SQLTree::Token::COMMA
166
+ tokens.consume(SQLTree::Token::COMMA)
167
+ items << SQLTree::Node::Expression.parse(tokens)
168
+ end
169
+ tokens.consume(SQLTree::Token::RPAREN)
170
+
171
+ self.new(items)
172
+ end
173
+ end
174
+
175
+ class FunctionExpression < Expression
176
+ attr_accessor :function, :arguments
177
+
178
+ def initialize(function, arguments = [])
179
+ @function = function
180
+ @arguments = arguments
181
+ end
182
+
183
+ def to_sql
184
+ "#{@function}(" + @arguments.map { |e| e.to_sql }.join(', ') + ")"
185
+ end
186
+
187
+ def to_tree
188
+ [@function.to_sym] + @arguments.map { |e| e.to_tree }
189
+ end
190
+
191
+ def self.parse(tokens)
192
+ expr = self.new(tokens.next.literal)
193
+ tokens.consume(SQLTree::Token::LPAREN)
194
+ until tokens.peek == SQLTree::Token::RPAREN
195
+ expr.arguments << SQLTree::Node::Expression.parse(tokens)
196
+ tokens.consume(SQLTree::Token::COMMA) if tokens.peek == SQLTree::Token::COMMA
197
+ end
198
+ tokens.consume(SQLTree::Token::RPAREN)
199
+ return expr
200
+ end
201
+ end
202
+
203
+ class ArithmeticExpression < Expression
204
+ attr_accessor :lhs, :rhs, :operator
205
+
206
+ def initialize(operator, lhs, rhs)
207
+ @lhs = lhs
208
+ @rhs = rhs
209
+ @operator = operator
210
+ end
211
+
212
+ def to_sql
213
+ "(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
214
+ end
215
+
216
+ def to_tree
217
+ [SQLTree::Token::OPERATORS_HASH[@operator], @lhs.to_tree, @rhs.to_tree]
218
+ end
219
+
220
+ def self.parse(tokens)
221
+ self.parse_primary(tokens)
222
+ end
223
+
224
+ def self.parse_primary(tokens)
225
+ expr = self.parse_secondary(tokens)
226
+ while [SQLTree::Token::PLUS, SQLTree::Token::MINUS].include?(tokens.peek)
227
+ expr = self.new(tokens.next.literal, expr, self.parse_secondary(tokens))
228
+ end
229
+ return expr
230
+ end
231
+
232
+ def self.parse_secondary(tokens)
233
+ expr = Expression.parse_atomic(tokens)
234
+ while [SQLTree::Token::PLUS, SQLTree::Token::MINUS].include?(tokens.peek)
235
+ expr = self.new(tokens.next.literal, expr, SQLTree::Node::Expression.parse_atomic(tokens))
236
+ end
237
+ return expr
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,50 @@
1
+ module SQLTree::Node
2
+
3
+ class Field < Base
4
+
5
+ attr_accessor :name, :table
6
+
7
+ def initialize(name, table = nil)
8
+ @name = name
9
+ @table = table
10
+ end
11
+
12
+ def quote_var(name)
13
+ return '*' if name == :all
14
+ super(name)
15
+ end
16
+
17
+ def to_sql
18
+ @table.nil? ? quote_var(@name) : quote_var(@table) + '.' + quote_var(@name)
19
+ end
20
+
21
+ def to_tree
22
+ to_sql.to_sym
23
+ end
24
+
25
+ def ==(other)
26
+ other.name == self.name && other.table == self.table
27
+ end
28
+
29
+ def self.parse(tokens)
30
+ field_or_table = case tokens.next
31
+ when SQLTree::Token::MULTIPLY then :all
32
+ when SQLTree::Token::Variable then tokens.current.literal
33
+ else raise SQLTree::Parser::UnexpectedToken.new(tokens.current)
34
+ end
35
+
36
+ if tokens.peek == SQLTree::Token::DOT
37
+ table = field_or_table
38
+ tokens.consume(SQLTree::Token::DOT)
39
+ field = case tokens.next
40
+ when SQLTree::Token::MULTIPLY then :all
41
+ when SQLTree::Token::Variable then tokens.current.literal
42
+ else raise SQLTree::Parser::UnexpectedToken.new(tokens.current)
43
+ end
44
+ self.new(field, table)
45
+ else
46
+ self.new(field_or_table)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ module SQLTree::Node
2
+
3
+ class Join < Base
4
+
5
+ attr_accessor :join_type, :table_reference, :join_expression
6
+
7
+ def initialize(values = {})
8
+ values.each { |key, value| self.send(:"#{key}=", value) }
9
+ end
10
+
11
+ def to_sql
12
+ join_sql = join_type ? "#{join_type.to_s.upcase} " : ""
13
+ join_sql << "JOIN #{table_reference.to_sql} "
14
+ join_sql << "ON #{join_expression.to_sql}"
15
+ join_sql
16
+ end
17
+
18
+ def table
19
+ table_reference.table
20
+ end
21
+
22
+ def table_alias
23
+ table_reference.table_alias
24
+ end
25
+
26
+ def self.parse(tokens)
27
+ join = self.new
28
+
29
+ if tokens.peek == SQLTree::Token::FULL
30
+ join.join_type = :outer
31
+ tokens.consume(SQLTree::Token::FULL, SQLTree::Token::OUTER)
32
+ elsif [SQLTree::Token::OUTER, SQLTree::Token::INNER, SQLTree::Token::LEFT, SQLTree::Token::RIGHT].include?(tokens.peek)
33
+ join.join_type = tokens.next.literal.downcase.to_sym
34
+ end
35
+
36
+
37
+ tokens.consume(SQLTree::Token::JOIN)
38
+ join.table_reference = SQLTree::Node::TableReference.parse(tokens)
39
+ tokens.consume(SQLTree::Token::ON)
40
+ join.join_expression = SQLTree::Node::Expression.parse(tokens)
41
+
42
+ return join
43
+ end
44
+
45
+ def ==(other)
46
+ other.table = self.table && other.table_alias == self.table_alias &&
47
+ other.join_type == self.join_type && other.join_expression == self.join_expression
48
+ end
49
+ end
50
+ end