sql_tree 0.1.0 → 0.1.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.
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