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 +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
|