sql_tree 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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