sql_tree 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  /pkg
2
- /tmp
2
+ /tmp
3
+ /doc
@@ -7,9 +7,8 @@ The library can parse an SQL query (a string) to represent the query using
7
7
  a syntax tree, and it can generate an SQL query from a syntax tree. The syntax
8
8
  tree ca be used to inspect to query, or to modify it.
9
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.
10
+ This library is currently in the early stages. This means that the API is not
11
+ yet stable and not all SQL constructs are implemented yet.
13
12
 
14
13
  == Installation
15
14
 
@@ -30,7 +29,9 @@ Consider the following example:
30
29
 
31
30
  == Additional information
32
31
 
32
+ This library is written by Willem van Bergen and is MIT licensed (see the
33
+ LICENSE file).
34
+
35
+ * Full RDoc API documentation can be found at http://rdoc.info/projects/wvanbergen/sql_tree
33
36
  * See the project wiki at http://wiki.github.com/wvanbergen/sql_tree for more
34
37
  information about using this library.
35
- * This plugin is written by Willem van Bergen and is MIT licensed (see the
36
- LICENSE file).
@@ -5,6 +5,14 @@
5
5
  # necessary files for the gem to function properly.
6
6
  module SQLTree
7
7
 
8
+ class << self
9
+ # The character to quote variable names with.
10
+ attr_accessor :identifier_quote_char
11
+ end
12
+
13
+ # Set default quote characters
14
+ self.identifier_quote_char = '"'
15
+
8
16
  # Loads constants in the SQLTree namespace using self.load_default_class_file(base, const)
9
17
  # <tt>const</tt>:: The constant that is not yet loaded in the SQLTree namespace. This should be passed as a string or symbol.
10
18
  def self.const_missing(const)
@@ -26,15 +34,15 @@ module SQLTree
26
34
  SQLTree::Parser.parse(query)
27
35
  end
28
36
 
29
- # Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
37
+ # Convert a string/symbol in camelcase (SQLTree::Parser) to underscores (sql_tree/parser)
30
38
  # This function can be used to load the file (using require) in which the given constant is defined.
31
39
  # <tt>str</tt>:: The string to convert in the following format: <tt>ModuleName::ClassName</tt>
32
40
  def self.to_underscore(str)
33
41
  str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
34
42
  end
35
43
 
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.
44
+ # Convert a string/symbol in underscores (<tt>sql_tree/parser</tt>) to camelcase
45
+ # (<tt>SqlTree::Parser</tt>). This can be used to find the class that is defined in a given filename.
38
46
  # <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
39
47
  def self.to_camelcase(str)
40
48
  str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
@@ -1,10 +1,22 @@
1
1
  module SQLTree::Node
2
2
 
3
+ # Auto-loades files for Node subclasses that reside in the
4
+ # node directory, based on the classname.
3
5
  def self.const_missing(const)
4
6
  SQLTree.load_default_class_file(SQLTree::Node, const)
5
7
  end
6
8
 
9
+ # The SQLTree::Node::Base class is the superclass for all node
10
+ # types that are used to represent SQL queries.
11
+ #
12
+ # This class implements some helper methods, and enables the
13
+ # SQLTree::Node::NodeType['SQL fragment'] construct to parse SQL
14
+ # queries.
7
15
  class Base
16
+
17
+ def initialize(attributes = {}) # :nodoc:
18
+ attributes.each { |key, value| send(:"#{key}=", value) }
19
+ end
8
20
 
9
21
  # Pretty prints this instance for inspection
10
22
  def inspect
@@ -13,22 +25,81 @@ module SQLTree::Node
13
25
 
14
26
  # Quotes a variable name so that it can be safely used within
15
27
  # SQL queries.
28
+ # <tt>name</tt>:: The name of the variable to quote.
16
29
  def quote_var(name)
17
- "\"#{name}\""
30
+ "#{SQLTree.identifier_quote_char}#{name}#{SQLTree.identifier_quote_char}" # TODO: MySQL style variable quoting
18
31
  end
19
32
 
20
- # Quotes a string so that it can be used within an SQL query.
33
+ # Quotes a string so that it can be used safey within an SQL query.
34
+ # <tt>str</tt>:: The string to quote.
21
35
  def quote_str(str)
22
- "'#{str.gsub(/\'/, "''")}'"
36
+ "'#{str.gsub("'", "''")}'"
23
37
  end
24
38
 
25
- # This method should be implemented by a subclass.
39
+ # Registers the children and leafs attributes in the subclass
40
+ def self.inherited(subclass)
41
+ class << subclass; attr_accessor :children, :leafs; end
42
+ subclass.children = []
43
+ subclass.leafs = []
44
+ end
45
+
46
+ # Adds another leaf to this node class.
47
+ def self.leaf(name)
48
+ self.leafs << name
49
+ self.send(:attr_accessor, name)
50
+ end
51
+
52
+ # Adds another child to this node class.
53
+ def self.child(name)
54
+ self.children << name
55
+ self.send(:attr_accessor, name)
56
+ end
57
+
58
+ # Compares this node with another node, returns true if the nodes are equal.
59
+ def ==(other)
60
+ other.kind_of?(self.class) && equal_children?(other) && equal_leafs?(other)
61
+ end
62
+
63
+ # Returns true if all children of the current object and the other object are equal.
64
+ def equal_children?(other)
65
+ self.class.children.all? { |child| send(child) == other.send(child) }
66
+ end
67
+
68
+ # Returns true if all leaf values of the current object and the other object are equal.
69
+ def equal_leafs?(other)
70
+ self.class.leafs.all? { |leaf| send(leaf) == other.send(leaf) }
71
+ end
72
+
73
+ # Parses an SQL fragment tree from a stream of tokens.
74
+ #
75
+ # This method should be implemented by each subclass.
76
+ # This method should not be called directly, but the
77
+ # <tt>SQLTree::Node::Subclass#[]</tt> should be called to
78
+ # parse an SQL fragment provided as a string.
79
+ #
80
+ # <tt>tokens</tt>:: the token stream to use for parsing.
26
81
  def self.parse(tokens)
27
82
  raise 'Only implemented in subclasses!'
28
83
  end
84
+
85
+ def self.parse_list(tokens, item_class = SQLTree::Node::Expression)
86
+ items = [item_class.parse(tokens)]
87
+ while SQLTree::Token::COMMA === tokens.peek
88
+ tokens.consume(SQLTree::Token::COMMA)
89
+ items << item_class.parse(tokens)
90
+ end
91
+ return items
92
+ end
29
93
 
30
94
  # Parses a string, expecting it to be parsable to an instance of
31
95
  # the current class.
96
+ #
97
+ # This method will construct a new parser that will tokenize the
98
+ # string, and will then present the stream of tokens to the
99
+ # <tt>self.parse</tt> method of the current class.
100
+ #
101
+ # <tt>sql</tt>:: The SQL string to parse
102
+ # <tt>options</tt>:: A Hash of options to send to the parser.
32
103
  def self.[](sql, options = {})
33
104
  parser = SQLTree::Parser.new(sql, options)
34
105
  self.parse(parser)
@@ -1,24 +1,37 @@
1
1
  module SQLTree::Node
2
2
 
3
+ # The <tt>DeleteQuery</tt> node represents an SQL DELETE query.
4
+ #
5
+ # This node has two children: <tt>table</tt> and <tt>where</tt>.
3
6
  class DeleteQuery < Base
4
7
 
5
- attr_accessor :table, :where
8
+ # The table (<tt>SQLTree::Node::TableReference</tt>) from which to delete records.
9
+ child :table
10
+
11
+ # The <tt>SQLTree::Node::Expression</tt> instance that defines what
12
+ # nodes to delete.
13
+ child :where
6
14
 
15
+ # Initializes a new DeleteQuery instance.
7
16
  def initialize(table, where = nil)
8
17
  @table, @where = table, where
9
18
  end
10
19
 
11
- def to_sql
12
- sql = "DELETE FROM #{self.quote_var(table)}"
13
- sql << " WHERE #{where.to_sql}" if self.where
20
+ # Generates an SQL DELETE query from this node.
21
+ def to_sql(options = {})
22
+ sql = "DELETE FROM #{table.to_sql(options)}"
23
+ sql << " WHERE #{where.to_sql(options)}" if self.where
14
24
  sql
15
25
  end
16
26
 
27
+ # Parses a DELETE query from a stream of tokens.
28
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
29
+ # of <tt> SQLTree::Parser</tt>.
17
30
  def self.parse(tokens)
18
31
  tokens.consume(SQLTree::Token::DELETE)
19
32
  tokens.consume(SQLTree::Token::FROM)
20
- delete_query = self.new(SQLTree::Node::Variable.parse(tokens).name)
21
- if tokens.peek == SQLTree::Token::WHERE
33
+ delete_query = self.new(SQLTree::Node::TableReference.parse(tokens))
34
+ if SQLTree::Token::WHERE === tokens.peek
22
35
  tokens.consume(SQLTree::Token::WHERE)
23
36
  delete_query.where = SQLTree::Node::Expression.parse(tokens)
24
37
  end
@@ -1,13 +1,27 @@
1
1
  module SQLTree::Node
2
2
 
3
- # Base class for all SQL expressions.
3
+ # Abstract base class for all SQL expressions.
4
4
  #
5
- # This is an asbtract class and should not be used directly. Use
6
- # one of the subclasses instead.
5
+ # To parse a string as an SQL expression, use:
6
+ #
7
+ # SQLTree::Node::Expression["(3 + 2 = 10 / 2) AND MD5('$ecret') = password"]
8
+ #
9
+ # This is an abtract class: its parse method will never return an
10
+ # <tt>SQLTree::Node::Expression</tt> instance, but always an instance
11
+ # of one of its subclasses. The concrete expression classes are defined in the
12
+ # SQLTree::Node::Expression namespace.
7
13
  class Expression < Base
8
14
 
15
+ # Parses an SQL expression from a stream of tokens.
16
+ #
17
+ # This method will start trying to parse the token stream as a
18
+ # <tt>SQLTree::Node::Expression::BinaryOperator</tt>, which will in turn try
19
+ # to parse it as other kinds of expressions if a binary expression is not appropriate.
20
+ #
21
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
22
+ # of <tt> SQLTree::Parser</tt>.
9
23
  def self.parse(tokens)
10
- SQLTree::Node::LogicalExpression.parse(tokens)
24
+ SQLTree::Node::Expression::BinaryOperator.parse(tokens)
11
25
  end
12
26
 
13
27
  # Parses a single, atomic SQL expression. This can be either:
@@ -16,201 +30,455 @@ module SQLTree::Node
16
30
  # * an SQL variable
17
31
  # * an SQL function
18
32
  # * a literal SQL value (numeric or string)
33
+ #
34
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
35
+ # of <tt> SQLTree::Parser</tt>.
19
36
  def self.parse_atomic(tokens)
20
- case tokens.peek
21
- when SQLTree::Token::LPAREN
37
+ if SQLTree::Token::LPAREN === tokens.peek
22
38
  tokens.consume(SQLTree::Token::LPAREN)
23
- expr = SQLTree::Node::Expression.parse(tokens)
39
+ expr = self.parse(tokens)
24
40
  tokens.consume(SQLTree::Token::RPAREN)
25
41
  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)
42
+ elsif tokens.peek.prefix_operator?
43
+ PrefixOperator.parse(tokens)
44
+ elsif tokens.peek.variable?
45
+ if SQLTree::Token::LPAREN === tokens.peek(2)
46
+ FunctionCall.parse(tokens)
47
+ elsif SQLTree::Token::DOT === tokens.peek(2)
48
+ Field.parse(tokens)
31
49
  else
32
- SQLTree::Node::Variable.parse(tokens)
50
+ Variable.parse(tokens)
33
51
  end
34
52
  else
35
- SQLTree::Node::Value.parse(tokens)
53
+ Value.parse(tokens)
36
54
  end
37
55
  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 ==(other)
53
- other.kind_of?(self.class) && other.expression == self.expression
54
- end
55
-
56
- def self.parse(tokens)
57
- tokens.consume(SQLTree::Token::NOT)
58
- self.new(SQLTree::Node::Expression.parse(tokens))
59
- end
60
- end
61
-
62
- class LogicalExpression < Expression
63
- attr_accessor :operator, :expressions
64
-
65
- def initialize(operator, expressions)
66
- @expressions = expressions
67
- @operator = operator.to_s.downcase.to_sym
68
- end
69
-
70
- def to_sql
71
- "(" + @expressions.map { |e| e.to_sql }.join(" #{@operator.to_s.upcase} ") + ")"
72
- end
73
-
74
- def ==(other)
75
- self.operator == other.operator && self.expressions == other.expressions
76
- end
77
-
78
- def self.parse(tokens)
79
- expr = ComparisonExpression.parse(tokens)
80
- while [SQLTree::Token::AND, SQLTree::Token::OR].include?(tokens.peek)
81
- expr = SQLTree::Node::LogicalExpression.new(tokens.next.literal, [expr, ComparisonExpression.parse(tokens)])
56
+
57
+ # A prefix operator expression parses a construct that consists of an
58
+ # operator and an expression. Currently, the only prefix operator that
59
+ # is supported is the NOT keyword.
60
+ #
61
+ # This node has two child nodes: <tt>operator</tt> and <tt>rhs</tt>.
62
+ class PrefixOperator < SQLTree::Node::Expression
63
+
64
+ # The list of operator tokens that can be used as prefix operator.
65
+ TOKENS = [SQLTree::Token::NOT]
66
+
67
+ # The SQL operator as <tt>String</tt> that was used for this expression.
68
+ leaf :operator
69
+
70
+ # The right hand side of the prefix expression, i.e. the <tt>SQLTree::Node::Expression</tt>
71
+ # instance that appeared after the operator.
72
+ child :rhs
73
+
74
+ # Generates an SQL fragment for this prefix operator expression.
75
+ def to_sql(options = {})
76
+ "#{operator} #{rhs.to_sql(options)}"
77
+ end
78
+
79
+ # Parses the operator from the token stream.
80
+ # <tt>tokens</tt>:: the token stream to parse from.
81
+ def self.parse_operator(tokens)
82
+ tokens.next.literal.upcase
83
+ end
84
+
85
+ # Parses a prefix operator expression, by first parsing the operator
86
+ # and then parsing the right hand side expression.
87
+ # <tt>tokens</tt>:: the token stream to parse from, which is an instance
88
+ # of <tt> SQLTree::Parser</tt>.
89
+ def self.parse(tokens)
90
+ if tokens.peek.prefix_operator?
91
+ node = self.new
92
+ node.operator = parse_operator(tokens)
93
+ node.rhs = SQLTree::Node::Expression.parse(tokens)
94
+ return node
95
+ else
96
+ raise UnexpectedTokenException.new(tokens.peek)
97
+ end
82
98
  end
83
- return expr
84
99
  end
85
- end
86
-
87
- class ComparisonExpression < Expression
88
- attr_accessor :lhs, :rhs, :operator
89
-
90
- def initialize(operator, lhs, rhs)
91
- @lhs = lhs
92
- @rhs = rhs
93
- @operator = operator
100
+
101
+ # A postfix operator expression is a construct in which the operator appears
102
+ # after a (left-hand side) expression.
103
+ #
104
+ # This operator has two child nodes: <tt>operator</tt> and <tt>lhs</tt>.
105
+ #
106
+ # Currently, SQLTreedoes not support any postfix operator.
107
+ class PostfixOperator < SQLTree::Node::Expression
108
+
109
+ # The left-hand side <tt>SQLTree::Node::Expression</tt> instance that was parsed
110
+ # before the postfix operator.
111
+ child :lhs
112
+
113
+ # The postfoix operator for this expression as <tt>String</tt>.
114
+ leaf :operator
115
+
116
+ # Generates an SQL fragment for this postfix operator expression.
117
+ def to_sql(options = {})
118
+ "#{lhs.to_sql(options)} #{operator}"
119
+ end
120
+
121
+ # Parses a postfix operator expression. This method is not yet implemented.
122
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
123
+ # of <tt> SQLTree::Parser</tt>.
124
+ def self.parse(tokens)
125
+ raise "Not yet implemented"
126
+ end
94
127
  end
128
+
129
+ # A binary operator expression consists of a left-hand side expression (lhs), the
130
+ # binary operator itself and a right-hand side expression (rhs). It therefore has
131
+ # three children: <tt>operator</tt>, <tt>lhs</tt> and <tt>rhs</tt>.
132
+ #
133
+ # When multiple binary operators appear in an expression, they can be grouped
134
+ # using parenthesis (e.g. "(1 + 3) / 2", or "1 + (3 / 2)" ). If the parentheses
135
+ # are absent, the grouping is determined using the precedence of the operator.
136
+ class BinaryOperator < SQLTree::Node::Expression
137
+
138
+ # The token precedence list. Tokens that occur first in this list have
139
+ # the lowest precedence, the last tokens have the highest. This impacts
140
+ # parsing when no parentheses are used to indicate how operators should
141
+ # be grouped.
142
+ #
143
+ # The token precedence list is taken from the SQLite3 documentation:
144
+ # http://www.sqlite.org/lang_expr.html
145
+ TOKEN_PRECEDENCE = [
146
+ [SQLTree::Token::OR],
147
+ [SQLTree::Token::AND],
148
+ [SQLTree::Token::EQ, SQLTree::Token::NE, SQLTree::Token::IN, SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::IS],
149
+ [SQLTree::Token::LT, SQLTree::Token::LTE, SQLTree::Token::GT, SQLTree::Token::GTE],
150
+ [SQLTree::Token::LSHIFT, SQLTree::Token::RSHIFT, SQLTree::Token::BINARY_AND, SQLTree::Token::BINARY_OR],
151
+ [SQLTree::Token::PLUS, SQLTree::Token::MINUS],
152
+ [SQLTree::Token::MULTIPLY, SQLTree::Token::DIVIDE, SQLTree::Token::MODULO],
153
+ [SQLTree::Token::CONCAT],
154
+ ]
155
+
156
+ # A list of binary operator tokens, taken from the operator precedence list.
157
+ TOKENS = TOKEN_PRECEDENCE.flatten
158
+
159
+ # The operator to use for this binary operator expression.
160
+ leaf :operator
161
+
162
+ # The left hand side <tt>SQLTree::Node::Expression</tt> instance for this operator.
163
+ child :lhs
164
+
165
+ # The rights hand side <tt>SQLTree::Node::Expression</tt> instance for this operator.
166
+ child :rhs
167
+
168
+ # Generates an SQL fragment for this exression.
169
+ def to_sql(options = {})
170
+ "(#{lhs.to_sql(options)} #{operator} #{rhs.to_sql(options)})"
171
+ end
95
172
 
96
- def to_sql
97
- "(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
98
- end
173
+ # Parses the operator for this expression.
174
+ #
175
+ # Some operators can be negated using the NOT operator (e.g. <tt>IS NOT</tt>,
176
+ # <tt>NOT LIKE</tt>). This is handled in this function as well.
177
+ #
178
+ # <tt>tokens</tt>:: The token stream to parse from.
179
+ def self.parse_operator(tokens)
180
+ if tokens.peek.optional_not_suffix? && tokens.peek(2).not?
181
+ return "#{tokens.next.literal.upcase} #{tokens.next.literal.upcase}"
182
+ elsif tokens.peek.not? && tokens.peek(2).optional_not_prefix?
183
+ return "#{tokens.next.literal.upcase} #{tokens.next.literal.upcase}"
184
+ else
185
+ return tokens.next.literal.upcase
186
+ end
187
+ end
99
188
 
100
- def self.parse_comparison_operator(tokens)
101
- operator_token = tokens.next
102
- if SQLTree::Token::IS === operator_token
103
- if SQLTree::Token::NOT === tokens.peek
104
- tokens.consume(SQLTree::Token::NOT)
105
- 'IS NOT'
189
+ # Parses the right hand side expression of the operator.
190
+ #
191
+ # Usually, this will parse another BinaryOperator expression with a higher
192
+ # precedence, but for some operators (+IN+ and +IS+), the default behavior
193
+ # is overriden to implement exceptions.
194
+ #
195
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
196
+ # of <tt> SQLTree::Parser</tt>.
197
+ # <tt>precedence</tt>:: The current precedence level. By default, this method
198
+ # will try to parse a BinaryOperator expression with a
199
+ # one higher precedence level than the current level.
200
+ # <tt>operator</tt>:: The operator that was parsed.
201
+ def self.parse_rhs(tokens, precedence, operator = nil)
202
+ if ['IN', 'NOT IN'].include?(operator)
203
+ return List.parse(tokens)
204
+ elsif ['IS', 'IS NOT'].include?(operator)
205
+ tokens.consume(SQLTree::Token::NULL)
206
+ return SQLTree::Node::Expression::Value.new(nil)
106
207
  else
107
- 'IS'
208
+ return parse(tokens, precedence + 1)
108
209
  end
109
- elsif SQLTree::Token::NOT === operator_token
110
- case tokens.peek
111
- when SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::BETWEEN, SQLTree::Token::IN
112
- "NOT #{tokens.next.literal.upcase}"
210
+ end
211
+
212
+ # Parses the binary operator by first parsing the left hand side, then the operator
213
+ # itself, and finally the right hand side.
214
+ #
215
+ # BinaryOperator -> Expression <operator> Expression
216
+ #
217
+ # This method will try to parse the lowest precedence operator first, and gradually
218
+ # try to parse operators with a higher precedence level. The left and right hand side
219
+ # will both be parsed with a higher precedence level. This ensures that the resulting
220
+ # expression is grouped correctly.
221
+ #
222
+ # If no binary operator is found of any precedence level, this method will back on
223
+ # pasring an atomic expression, see {SQLTree::Node::Expression.parse_atomic}.
224
+ #
225
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
226
+ # @param [Integer] precedence The current precedence level. Starts with the lowest
227
+ # precedence level (0) by default.
228
+ # @return [SQLTree::Node::Expression] The parsed expression. This may not be
229
+ # a binary operator expression, as this method falls back on parsing other
230
+ # expresison types if no binary operator is found.
231
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
232
+ # encountered during parsing.
233
+ def self.parse(tokens, precedence = 0)
234
+ if precedence >= TOKEN_PRECEDENCE.length
235
+ return SQLTree::Node::Expression.parse_atomic(tokens)
113
236
  else
114
- raise SQLTree::Parser::UnexpectedToken.new(tokens.peek)
237
+ expr = parse(tokens, precedence + 1)
238
+ while TOKEN_PRECEDENCE[precedence].include?(tokens.peek.class) || (tokens.peek && tokens.peek.not?)
239
+ operator = parse_operator(tokens)
240
+ rhs = parse_rhs(tokens, precedence, operator)
241
+ expr = self.new(:operator => operator, :lhs => expr, :rhs => rhs)
242
+ end
243
+ return expr
115
244
  end
116
- else
117
- operator_token.literal
118
245
  end
119
246
  end
120
-
121
- def self.parse(tokens)
122
- lhs = SQLTree::Node::ArithmeticExpression.parse(tokens)
123
- while SQLTree::Token::COMPARISON_OPERATORS.include?(tokens.peek)
124
- comparison_operator = parse_comparison_operator(tokens)
125
- rhs = ['IN', 'NOT IN'].include?(comparison_operator) ?
126
- SQLTree::Node::SetExpression.parse(tokens) :
127
- SQLTree::Node::ArithmeticExpression.parse(tokens)
128
-
129
- lhs = self.new(comparison_operator, lhs, rhs)
247
+
248
+ # Parses a comma-separated list of expressions, which is used after the IN operator.
249
+ # The attribute <tt>items</tt> contains the array of child nodes, all instances of
250
+ # {SQLTree::Node::Expression}.
251
+ class List < SQLTree::Node::Expression
252
+
253
+ # Include the enumerable module to simplify handling the items in this list.
254
+ include Enumerable
255
+
256
+ # The items that appear in the list, i.e. an array of {SQLTree::Node::Expression}
257
+ # instances.
258
+ child :items
259
+
260
+ def initialize(*items)
261
+ if items.length == 1 && items.first.kind_of?(Array)
262
+ @items = items.first
263
+ elsif items.length == 1 && items.first.kind_of?(Hash)
264
+ super(items.first)
265
+ else
266
+ @items
267
+ end
130
268
  end
131
- return lhs
132
- end
133
- end
134
-
135
- class SetExpression < Expression
136
- attr_accessor :items
137
-
138
- def initialize(items = [])
139
- @items = items
140
- end
141
-
142
- def to_sql
143
- "(#{items.map {|i| i.to_sql}.join(', ')})"
144
- end
145
269
 
146
- def self.parse(tokens)
147
- tokens.consume(SQLTree::Token::LPAREN)
148
- items = [SQLTree::Node::Expression.parse(tokens)]
149
- while tokens.peek == SQLTree::Token::COMMA
150
- tokens.consume(SQLTree::Token::COMMA)
151
- items << SQLTree::Node::Expression.parse(tokens)
270
+ # Generates an SQL fragment for this list.
271
+ def to_sql(options = {})
272
+ "(#{items.map {|i| i.to_sql(options)}.join(', ')})"
152
273
  end
153
- tokens.consume(SQLTree::Token::RPAREN)
154
-
155
- self.new(items)
156
- end
157
- end
158
-
159
- class FunctionExpression < Expression
160
- attr_accessor :function, :arguments
161
274
 
162
- def initialize(function, arguments = [])
163
- @function = function
164
- @arguments = arguments
165
- end
275
+ # Returns true if this list has no items.
276
+ def empty?
277
+ items.empty?
278
+ end
279
+
280
+ # Makes sure the enumerable module works over the items in the list.
281
+ def each(&block) # :nodoc:
282
+ items.each(&block)
283
+ end
166
284
 
167
- def to_sql
168
- "#{@function}(" + @arguments.map { |e| e.to_sql }.join(', ') + ")"
285
+ # Parses a list of expresison by parsing expressions as long as it sees
286
+ # a comma that indicates the presence of a next expression.
287
+ #
288
+ # List -> LPAREN (Expression (COMMA Expression)*)? RPAREN
289
+ #
290
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
291
+ # @return [SQLTree::Node::Expression::List] The parsed list instance.
292
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
293
+ # encountered during parsing.
294
+ def self.parse(tokens)
295
+ tokens.consume(SQLTree::Token::LPAREN)
296
+ items = []
297
+ unless SQLTree::Token::RPAREN === tokens.peek
298
+ items = self.parse_list(tokens, SQLTree::Node::Expression)
299
+ end
300
+ tokens.consume(SQLTree::Token::RPAREN)
301
+ self.new(items)
302
+ end
169
303
  end
170
-
171
- def self.parse(tokens)
172
- expr = self.new(tokens.next.literal)
173
- tokens.consume(SQLTree::Token::LPAREN)
174
- until tokens.peek == SQLTree::Token::RPAREN
175
- expr.arguments << SQLTree::Node::Expression.parse(tokens)
176
- tokens.consume(SQLTree::Token::COMMA) if tokens.peek == SQLTree::Token::COMMA
177
- end
178
- tokens.consume(SQLTree::Token::RPAREN)
179
- return expr
304
+
305
+ # Represents a SQL function call expression. This node has two child nodes:
306
+ # <tt>function</tt> and <tt>argument_list</tt>.
307
+ class FunctionCall < SQLTree::Node::Expression
308
+
309
+ # The name of the function that is called as <tt>String</tt>.
310
+ leaf :function
311
+
312
+ # The argument list as {SQLTree::Node::Expression::List} instance.
313
+ child :arguments
314
+
315
+ # Generates an SQL fragment for this function call.
316
+ def to_sql(options = {})
317
+ "#{function}(" + arguments.map { |e| e.to_sql(options) }.join(', ') + ")"
318
+ end
319
+
320
+ # Parses an SQL function call.
321
+ #
322
+ # FunctionCall -> <identifier> List
323
+ #
324
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
325
+ # @return [SQLTree::Node::Expression::FunctionCall] The parsed function call instance.
326
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
327
+ # encountered during parsing.
328
+ def self.parse(tokens)
329
+ function_call = self.new(:function => tokens.next.literal, :arguments => [])
330
+ tokens.consume(SQLTree::Token::LPAREN)
331
+ function_call.arguments = self.parse_list(tokens) unless SQLTree::Token::RPAREN === tokens.peek
332
+ tokens.consume(SQLTree::Token::RPAREN)
333
+ return function_call
334
+ end
180
335
  end
181
- end
182
-
183
- class ArithmeticExpression < Expression
184
- attr_accessor :lhs, :rhs, :operator
336
+
337
+ # Represents alitreal value in an SQL expression. This node is a leaf node
338
+ # and thus has no child nodes.
339
+ #
340
+ # A value can either be:
341
+ # * the SQL <tt>NULL</tt> keyword, which is represented by <tt>nil</tt>.
342
+ # * an SQL string, which is represented by a <tt>String</tt> instance.
343
+ # * an SQL date or time value, which can be represented as a <tt>Date</tt>,
344
+ # <tt>Time</tt> or <tt>DateTime</tt> instance.
345
+ # * an integer or decimal value, which is represented by an appropriate
346
+ # <tt>Numeric</tt> instance.
347
+ class Value < SQLTree::Node::Expression
348
+
349
+ # The actual value this node represents.
350
+ leaf :value
351
+
352
+ def initialize(value) # :nodoc:
353
+ @value = value
354
+ end
355
+
356
+ # Generates an SQL representation for this value.
357
+ #
358
+ # This method supports nil, string, numeric, date and time values.
359
+ #
360
+ # @return [String] A correctly quoted value that can be used safely
361
+ # within an SQL query
362
+ def to_sql(options = {})
363
+ case value
364
+ when nil then 'NULL'
365
+ when String then quote_str(@value)
366
+ when Numeric then @value.to_s
367
+ when Date then @value.strftime("'%Y-%m-%d'")
368
+ when DateTime, Time then @value.strftime("'%Y-%m-%d %H:%M:%S'")
369
+ else raise "Don't know how te represent this value in SQL!"
370
+ end
371
+ end
185
372
 
186
- def initialize(operator, lhs, rhs)
187
- @lhs = lhs
188
- @rhs = rhs
189
- @operator = operator
373
+ # Parses a literal value.
374
+ #
375
+ # Value -> (NULL | <string> | <number>)
376
+ #
377
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
378
+ # @return [SQLTree::Node::Expression::Value] The parsed value instance.
379
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
380
+ # encountered during parsing.
381
+ def self.parse(tokens)
382
+ case tokens.next
383
+ when SQLTree::Token::String, SQLTree::Token::Number
384
+ SQLTree::Node::Expression::Value.new(tokens.current.literal)
385
+ when SQLTree::Token::NULL
386
+ SQLTree::Node::Expression::Value.new(nil)
387
+ else
388
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.current, :literal)
389
+ end
390
+ end
190
391
  end
392
+
393
+ # Represents a variable within an SQL expression. This is a leaf node, so it
394
+ # does not have any child nodes. A variale can point to a field of a table or
395
+ # to another expression that was declared elsewhere.
396
+ class Variable < SQLTree::Node::Expression
397
+
398
+ # The name of the variable as <tt>String</tt>.
399
+ leaf :name
400
+
401
+ def initialize(name) # :nodoc:
402
+ @name = name
403
+ end
191
404
 
192
- def to_sql
193
- "(#{@lhs.to_sql} #{@operator} #{@rhs.to_sql})"
194
- end
405
+ # Generates a quoted reference to the variable.
406
+ #
407
+ # @return [String] A correctly quoted variable that can be safely
408
+ # used in SQL queries
409
+ def to_sql(options = {})
410
+ quote_var(@name)
411
+ end
195
412
 
196
- def self.parse(tokens)
197
- self.parse_primary(tokens)
413
+ # Parses an SQL variable.
414
+ #
415
+ # Variable -> <identifier>
416
+ #
417
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
418
+ # @return [SQLTree::Node::Expression::Variable] The parsed variable instance.
419
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
420
+ # encountered during parsing.
421
+ def self.parse(tokens)
422
+ if SQLTree::Token::Identifier === tokens.peek
423
+ self.new(tokens.next.literal)
424
+ else
425
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.peek, :variable)
426
+ end
427
+ end
198
428
  end
429
+
430
+ # Represents a reference to a field of a table in an SQL expression.
431
+ # This is a leaf node, which means that it does not have any child nodes.
432
+ class Field < Variable
433
+
434
+ # The table in which the field resides. This can be +nil+, in which case
435
+ # the table the field belongs to is inferred from the rest of the query.
436
+ leaf :table
437
+
438
+ # The name of the field.
439
+ leaf :name
440
+
441
+ alias :field :name
442
+ alias :field= :name=
443
+
444
+ # Initializes a new Field
445
+ def initialize(name, table = nil)
446
+ @name = name
447
+ @table = table
448
+ end
199
449
 
200
- def self.parse_primary(tokens)
201
- expr = self.parse_secondary(tokens)
202
- while [SQLTree::Token::PLUS, SQLTree::Token::MINUS].include?(tokens.peek)
203
- expr = self.new(tokens.next.literal, expr, self.parse_secondary(tokens))
450
+ # Generates a correctly quoted reference to the field, which can
451
+ # be incorporated safely into an SQL query.
452
+ def to_sql(options = {})
453
+ @table.nil? ? quote_var(@name) : quote_var(@table) + '.' + quote_var(@name)
204
454
  end
205
- return expr
206
- end
207
455
 
208
- def self.parse_secondary(tokens)
209
- expr = Expression.parse_atomic(tokens)
210
- while [SQLTree::Token::PLUS, SQLTree::Token::MINUS].include?(tokens.peek)
211
- expr = self.new(tokens.next.literal, expr, SQLTree::Node::Expression.parse_atomic(tokens))
456
+ # Parses a field, either with or without the table reference.
457
+ #
458
+ # Field -> (<identifier> DOT)? <identifier>
459
+ #
460
+ # @param [SQLTree::Parser] tokens The token stream to parse from.
461
+ # @return [SQLTree::Node::Expression::Field] The parsed field instance.
462
+ # @raise [SQLTree::Parser::UnexpectedToken] if an unexpected token is
463
+ # encountered during parsing.
464
+ def self.parse(tokens)
465
+ if SQLTree::Token::Identifier === tokens.peek
466
+ field_or_table = tokens.next.literal
467
+ else
468
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.next)
469
+ end
470
+
471
+ if SQLTree::Token::DOT === tokens.peek
472
+ tokens.consume(SQLTree::Token::DOT)
473
+ if SQLTree::Token::Identifier === tokens.peek
474
+ self.new(tokens.next.literal, field_or_table)
475
+ else
476
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.next)
477
+ end
478
+ else
479
+ self.new(field_or_table)
480
+ end
212
481
  end
213
- return expr
214
482
  end
215
483
  end
216
484
  end