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 +2 -1
- data/README.rdoc +6 -5
- data/lib/sql_tree.rb +11 -3
- data/lib/sql_tree/node.rb +75 -4
- data/lib/sql_tree/node/delete_query.rb +19 -6
- data/lib/sql_tree/node/expression.rb +431 -163
- data/lib/sql_tree/node/insert_query.rb +13 -17
- data/lib/sql_tree/node/join.rb +8 -12
- data/lib/sql_tree/node/ordering.rb +4 -3
- data/lib/sql_tree/node/select_declaration.rb +62 -0
- data/lib/sql_tree/node/select_query.rb +27 -46
- data/lib/sql_tree/node/source.rb +5 -8
- data/lib/sql_tree/node/table_reference.rb +7 -10
- data/lib/sql_tree/node/update_query.rb +73 -12
- data/lib/sql_tree/parser.rb +70 -14
- data/lib/sql_tree/token.rb +48 -22
- data/lib/sql_tree/tokenizer.rb +23 -16
- data/spec/integration/parse_and_generate_spec.rb +8 -0
- data/spec/lib/matchers.rb +3 -2
- data/spec/unit/delete_query_spec.rb +2 -2
- data/spec/unit/expression_node_spec.rb +24 -23
- data/spec/unit/insert_query_spec.rb +8 -8
- data/spec/unit/leaf_node_spec.rb +17 -17
- data/spec/unit/select_query_spec.rb +4 -4
- data/spec/unit/tokenizer_spec.rb +17 -21
- data/spec/unit/update_query_spec.rb +6 -6
- data/sql_tree.gemspec +3 -3
- metadata +4 -8
- data/lib/sql_tree/node/assignment.rb +0 -22
- data/lib/sql_tree/node/field.rb +0 -49
- data/lib/sql_tree/node/select_expression.rb +0 -45
- data/lib/sql_tree/node/value.rb +0 -33
- data/lib/sql_tree/node/variable.rb +0 -31
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -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
|
11
|
-
SQL constructs are
|
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).
|
data/lib/sql_tree.rb
CHANGED
@@ -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 (
|
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>
|
37
|
-
# (<tt>
|
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 }
|
data/lib/sql_tree/node.rb
CHANGED
@@ -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
|
-
"
|
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
|
-
#
|
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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
sql
|
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::
|
21
|
-
if
|
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
|
-
#
|
3
|
+
# Abstract base class for all SQL expressions.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
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::
|
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
|
-
|
21
|
-
when SQLTree::Token::LPAREN
|
37
|
+
if SQLTree::Token::LPAREN === tokens.peek
|
22
38
|
tokens.consume(SQLTree::Token::LPAREN)
|
23
|
-
expr =
|
39
|
+
expr = self.parse(tokens)
|
24
40
|
tokens.consume(SQLTree::Token::RPAREN)
|
25
41
|
expr
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
if tokens.peek(2)
|
30
|
-
|
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
|
-
|
50
|
+
Variable.parse(tokens)
|
33
51
|
end
|
34
52
|
else
|
35
|
-
|
53
|
+
Value.parse(tokens)
|
36
54
|
end
|
37
55
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
208
|
+
return parse(tokens, precedence + 1)
|
108
209
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
168
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
197
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|