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.
@@ -1,10 +1,23 @@
1
+ # The <tt>SQLTree::Parser</tt> class is used to construct a syntax tree
2
+ # using <tt>SQLTree::Node</tt> instances from a tree of tokens.
3
+ #
4
+ # This class does only kickstart the parsing process and manages the
5
+ # token stream that is being parsed. The actual parsing of the nodes
6
+ # occurs in the <tt>parse</tt> class method of the different node classes.
1
7
  class SQLTree::Parser
2
8
 
9
+ # The <tt>SQLTree::Parser::UnexpectedToken</tt> exception is thrown
10
+ # when the parser meets a token that it not expect.
11
+ #
12
+ # This exceptions usually means that an SQL syntax error has been found,
13
+ # however it can also mean that the SQL construct that is being used is
14
+ # not (yet) supported by this library. Please create an issue on Github
15
+ # if the latter is the case.
3
16
  class UnexpectedToken < StandardError
4
17
 
5
18
  attr_reader :expected_token, :actual_token
6
19
 
7
- def initialize(actual_token, expected_token = nil)
20
+ def initialize(actual_token, expected_token = nil) # :nodoc:
8
21
  @expected_token, @actual_token = expected_token, actual_token
9
22
  message = "Unexpected token: found #{actual_token.inspect}"
10
23
  message << ", but expected #{expected_token.inspect}" if expected_token
@@ -12,48 +25,91 @@ class SQLTree::Parser
12
25
  super(message)
13
26
  end
14
27
  end
15
-
28
+
29
+ # Kickstarts the parser by creating a new instance with the provided
30
+ # string, and calling the <tt>parse!</tt> method on this instance.
31
+ #
32
+ # Do not use this method directly, but use the <tt>SQLTree.[]</tt>
33
+ # method instead to parse SQL strings.
34
+ #
35
+ # <tt>sql_string</tt>:: The string to parse
36
+ # <tt>options</tt>:: Options to pass to the parser
16
37
  def self.parse(sql_string, options = {})
17
38
  self.new(sql_string, options).parse!
18
39
  end
19
40
 
41
+ # Hash for parser options.
20
42
  attr_reader :options
21
43
 
22
- def initialize(tokens, options)
23
- if tokens.kind_of?(String)
24
- @tokens = SQLTree::Tokenizer.new.tokenize(tokens)
25
- else
26
- @tokens = tokens
27
- end
44
+ # Initializes the parser instance.
45
+ # <tt>tokens</tt>:: The stream of tokens to turn into a syntax tree. If a
46
+ # string is given, it is tokenized automatically.
47
+ # <tt>options</tt>:: An optional hash of parser options.
48
+ def initialize(tokens, options = {})
49
+ @tokens = tokens.kind_of?(String) ? SQLTree::Tokenizer.tokenize(tokens) : tokens
28
50
  @options = options
29
51
  end
30
52
 
53
+ # Returns the current token that is being parsed.
31
54
  def current
32
55
  @current_token
33
56
  end
34
57
 
58
+ # Returns the next token on the token queue, and moves the token queue
59
+ # one position forward. This will update the result of the
60
+ # <tt>SQLTree::Parser#current</tt> method.
35
61
  def next
36
62
  @current_token = @tokens.shift
37
63
  end
38
64
 
39
- def consume(*checks)
65
+ # Consumes the current token(s), which will make the parser continue to the
66
+ # next token (see <tt>SQLTree::Parser#next</tt>).
67
+ #
68
+ # This method will also check if the consumed token is of the expected type.
69
+ # It will raise a <tt>SQLTree::Parser::UnexpectedToken</tt> exception if the
70
+ # consumed token is not of the expected type
71
+ #
72
+ # <tt>*checks</tt>:: a list of token types to consume.
73
+ def consume(*checks) # :raises: SQLTree::Parser::UnexpectedToken
40
74
  checks.each do |check|
41
- raise UnexpectedToken.new(self.current, check) unless check == self.next
75
+ raise UnexpectedToken.new(self.current, check) unless check === self.next
42
76
  end
43
77
  end
44
78
 
45
- def peek(distance = 1)
46
- @tokens[distance - 1]
79
+ # Looks at the next token on the token queue without consuming it.
80
+ #
81
+ # The token queue will not be adjusted, will is the case when using
82
+ # <tt>SQLTree::Parser#next</tt>.
83
+ #
84
+ # <tt>lookahead</tt>:: the number of positions to look ahead. Defaults to 1.
85
+ def peek(lookahead = 1)
86
+ @tokens[lookahead - 1]
47
87
  end
48
88
 
49
- def peek_tokens(amount)
50
- @tokens[0, amount]
89
+ # Peeks multiple tokens at the same time.
90
+ #
91
+ # This method will return an array of the requested number of tokens,
92
+ # except for when the token stream is nearing its end. In this case, the
93
+ # number of tokens returned can be less than requested.
94
+ # <tt>lookahead_amount</tt>:: the amount of tokens to return from the
95
+ # front of the token queue.
96
+ def peek_multiple(lookahead_amount)
97
+ @tokens[0, lookahead_amount]
51
98
  end
52
99
 
100
+ # Prints the current list of tokens to $stdout for inspection.
53
101
  def debug
54
102
  puts @tokens.inspect
55
103
  end
56
104
 
105
+ # Parser a complete SQL query into a tree of <tt>SQLTree::Node</tt> instances.
106
+ #
107
+ # Currently, SELECT, INSERT, UPDATE and DELETE queries are supported for
108
+ # the most part. This emthod should not be called directly, but is called
109
+ # by the <tt>SQLTree.[]</tt> method, e.g.:
110
+ #
111
+ # tree = SQLTree['SELECT * FROM table WHERE 1=1']
112
+ #
57
113
  def parse!
58
114
  case self.peek
59
115
  when SQLTree::Token::SELECT then SQLTree::Node::SelectQuery.parse(self)
@@ -30,24 +30,50 @@ class SQLTree::Token
30
30
  literal
31
31
  end
32
32
 
33
+ def not?
34
+ SQLTree::Token::NOT === self
35
+ end
36
+
37
+ def optional_not_prefix?
38
+ [SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::IN].include?(self.class)
39
+ end
40
+
41
+ def optional_not_suffix?
42
+ [SQLTree::Token::IS].include?(self.class)
43
+ end
44
+
45
+ # Returns true if this is a JOIN token?
33
46
  def join?
34
47
  [SQLTree::Token::JOIN, SQLTree::Token::LEFT, SQLTree::Token::RIGHT,
35
48
  SQLTree::Token::INNER, SQLTree::Token::OUTER, SQLTree::Token::NATURAL,
36
- SQLTree::Token::FULL].include?(self)
49
+ SQLTree::Token::FULL].include?(self.class)
50
+ end
51
+
52
+ def prefix_operator?
53
+ SQLTree::Node::Expression::PrefixOperator::TOKENS.include?(self.class)
54
+ end
55
+
56
+ def variable?
57
+ self.kind_of?(SQLTree::Token::Identifier)
58
+ end
59
+
60
+ def value?
61
+ self.kind_of?(SQLTree::Token::LiteralValue)
37
62
  end
38
63
 
64
+ # Returns true if this is an order direction token.
39
65
  def direction?
40
- [SQLTree::Token::ASC, SQLTree::Token::DESC].include?(self)
66
+ [SQLTree::Token::ASC, SQLTree::Token::DESC].include?(self.class)
41
67
  end
42
68
 
43
69
  ###################################################################
44
70
  # DYNAMIC TOKEN TYPES
45
71
  ###################################################################
46
72
 
47
- # The <tt>SQLTree::Token::Value</tt> class is the base class for
73
+ # The <tt>SQLTree::Token::DynamicToken</tt> class is the base class for
48
74
  # every dynamic token. A dynamic token is a token for which the
49
75
  # literal value used remains impoirtant during parsing.
50
- class Value < SQLTree::Token
76
+ class DynamicToken < SQLTree::Token
51
77
 
52
78
  def inspect # :nodoc:
53
79
  "#<#{self.class.name.split('::').last}:#{literal.inspect}>"
@@ -59,22 +85,27 @@ class SQLTree::Token
59
85
  other.class == self.class && @literal == other.literal
60
86
  end
61
87
  end
62
-
63
- # The <tt>SQLTree::Token::Variable</tt> class represents SQL
88
+
89
+ # The <tt>SQLTree::Token::Identifier</tt> class represents SQL
64
90
  # variables. The variable name is stored in the literal as string,
65
91
  # without quotes if they were present.
66
- class Variable < SQLTree::Token::Value
92
+ class Identifier < DynamicToken
93
+ end
94
+
95
+ # The <tt>SQLTree::Token::LiteralVakue</tt> class represents literal values
96
+ # like strings and numbers that occur in SQL.
97
+ class LiteralValue < DynamicToken
67
98
  end
68
99
 
69
100
  # The <tt>SQLTree::Token::String</tt> class represents strings.
70
101
  # The actual string is stored in the literal as string without quotes.
71
- class String < SQLTree::Token::Value
102
+ class String < LiteralValue
72
103
  end
73
104
 
74
105
  # The <tt>SQLTree::Token::Keyword</tt> class represents numbers.
75
106
  # The actual number is stored as an integer or float in the token's
76
107
  # literal.
77
- class Number < SQLTree::Token::Value
108
+ class Number < LiteralValue
78
109
  end
79
110
 
80
111
  ###################################################################
@@ -115,21 +146,16 @@ class SQLTree::Token
115
146
  AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN ASC DESC INSERT INTO VALUES DELETE UPDATE SET}
116
147
 
117
148
  # Create a token for all the reserved keywords in SQL
118
- KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword).new(kw)) }
149
+ KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword)) }
119
150
 
120
- # A list of keywords that aways occur in fixed combinations. Register these as separate keywords.
121
- KEYWORD_COMBINATIONS = [%w{IS NOT}, %w{NOT LIKE}, %w{NOT BETWEEN}, %w{NOT ILIKE}]
122
- KEYWORD_COMBINATIONS.each { |kw| const_set(kw.join('_'), Class.new(SQLTree::Token::Keyword).new(kw.join(' '))) }
151
+ OPERATORS_HASH = { '+' => :plus, '-' => :minus, '*' => :multiply,
152
+ '/' => :divide, '%' => :modulo, '||' => :concat, '|' => :binary_or,
153
+ '&' => :binary_and, '<<' => :lshift, '>>' => :rshift, '=' => :eq,
154
+ '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte,
155
+ '<=' => :lte, '==' => :eq }
123
156
 
124
- ARITHMETHIC_OPERATORS_HASH = { '+' => :plus, '-' => :minus, '*' => :multiply, '/' => :divide, '%' => :modulo }
125
- COMPARISON_OPERATORS_HASH = { '=' => :eq, '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte, '<=' => :lte }
126
-
127
- # Register a token class and single instance for every token.
128
- OPERATORS_HASH = ARITHMETHIC_OPERATORS_HASH.merge(COMPARISON_OPERATORS_HASH)
157
+ # Register a token class and single instance for every operator.
129
158
  OPERATORS_HASH.each_pair do |literal, symbol|
130
- self.const_set(symbol.to_s.upcase, Class.new(SQLTree::Token::Operator).new(literal)) unless self.const_defined?(symbol.to_s.upcase)
159
+ self.const_set(symbol.to_s.upcase, Class.new(SQLTree::Token::Operator)) unless self.const_defined?(symbol.to_s.upcase)
131
160
  end
132
-
133
- COMPARISON_OPERATORS = COMPARISON_OPERATORS_HASH.map { |(literal, symbol)| const_get(symbol.to_s.upcase) } +
134
- [SQLTree::Token::IN, SQLTree::Token::IS, SQLTree::Token::BETWEEN, SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::NOT]
135
161
  end
@@ -14,19 +14,24 @@ class SQLTree::Tokenizer
14
14
 
15
15
  include Enumerable
16
16
 
17
+ # Returns an array of tokens for the given string.
18
+ # <tt>string</tt>:: the string to tokenize
19
+ def self.tokenize(string)
20
+ self.new(string).tokens
21
+ end
22
+
17
23
  # The keyword queue, on which kywords are placed before they are yielded
18
24
  # to the parser, to enable keyword combining (e.g. NOT LIKE)
19
25
  attr_reader :keyword_queue
20
26
 
21
- def initialize # :nodoc:
27
+ def initialize(string) # :nodoc:
28
+ @string = string
29
+ @current_char_pos = -1
22
30
  @keyword_queue = []
23
31
  end
24
32
 
25
- # Returns an array of tokens for the given string.
26
- # <tt>string</tt>:: the string to tokenize
27
- def tokenize(string)
28
- @string = string
29
- @current_char_pos = -1
33
+ # Tokeinzes the string and returns all tokens as an array
34
+ def tokens
30
35
  self.entries
31
36
  end
32
37
 
@@ -79,6 +84,7 @@ class SQLTree::Tokenizer
79
84
  # This method is aliased to <tt>:each</tt> to make the Enumerable
80
85
  # methods work on this method.
81
86
  def each_token(&block) # :yields: SQLTree::Token
87
+
82
88
  while next_char
83
89
  case current_char
84
90
  when /^\s?$/; # whitespace, go to next character
@@ -88,9 +94,9 @@ class SQLTree::Tokenizer
88
94
  when ','; handle_token(SQLTree::Token::COMMA, &block)
89
95
  when /\d/; tokenize_number(&block)
90
96
  when "'"; tokenize_quoted_string(&block)
91
- when OPERATOR_CHARS; tokenize_operator(&block)
92
97
  when /\w/; tokenize_keyword(&block)
93
- when '"'; tokenize_quoted_variable(&block) # TODO: allow MySQL quoting mode
98
+ when OPERATOR_CHARS; tokenize_operator(&block)
99
+ when SQLTree.identifier_quote_char; tokenize_quoted_identifier(&block)
94
100
  end
95
101
  end
96
102
 
@@ -109,9 +115,9 @@ class SQLTree::Tokenizer
109
115
  literal << next_char while /[\w]/ =~ peek_char
110
116
 
111
117
  if SQLTree::Token::KEYWORDS.include?(literal.upcase)
112
- handle_token(SQLTree::Token.const_get(literal.upcase), &block)
118
+ handle_token(SQLTree::Token.const_get(literal.upcase).new(literal), &block)
113
119
  else
114
- handle_token(SQLTree::Token::Variable.new(literal), &block)
120
+ handle_token(SQLTree::Token::Identifier.new(literal), &block)
115
121
  end
116
122
  end
117
123
 
@@ -145,20 +151,20 @@ class SQLTree::Tokenizer
145
151
  end
146
152
 
147
153
  # Tokenize a quoted variable from the SQL stream. This method will
148
- # yield an SQLTree::Token::Variable when to closing quote is found.
154
+ # yield an SQLTree::Token::Identifier when to closing quote is found.
149
155
  #
150
156
  # The actual quote character that is used depends on the DBMS. For now,
151
157
  # only the more standard double quote is accepted.
152
- def tokenize_quoted_variable(&block) # :yields: SQLTree::Token::Variable
158
+ def tokenize_quoted_identifier(&block) # :yields: SQLTree::Token::Identifier
153
159
  variable = ''
154
- until next_char.nil? || current_char == '"' # TODO: allow MySQL quoting mode
160
+ until next_char.nil? || current_char == SQLTree.identifier_quote_char # TODO: allow MySQL quoting mode
155
161
  variable << (current_char == "\\" ? next_char : current_char)
156
162
  end
157
- handle_token(SQLTree::Token::Variable.new(variable), &block)
163
+ handle_token(SQLTree::Token::Identifier.new(variable), &block)
158
164
  end
159
165
 
160
166
  # A regular expression that matches all operator characters.
161
- OPERATOR_CHARS = /\=|<|>|!|\-|\+|\/|\*|\%/
167
+ OPERATOR_CHARS = /\=|<|>|!|\-|\+|\/|\*|\%|\||\&/
162
168
 
163
169
  # Tokenizes an operator in the SQL stream. This method will yield the
164
170
  # operator token when the last character of the token is encountered.
@@ -168,7 +174,8 @@ class SQLTree::Tokenizer
168
174
  tokenize_number(&block)
169
175
  else
170
176
  operator << next_char if SQLTree::Token::OPERATORS_HASH.has_key?(operator + peek_char)
171
- handle_token(SQLTree::Token.const_get(SQLTree::Token::OPERATORS_HASH[operator].to_s.upcase), &block)
177
+ operator_class = SQLTree::Token.const_get(SQLTree::Token::OPERATORS_HASH[operator].to_s.upcase)
178
+ handle_token(operator_class.new(operator), &block)
172
179
  end
173
180
  end
174
181
  end
@@ -2,9 +2,17 @@ require "#{File.dirname(__FILE__)}/../spec_helper"
2
2
 
3
3
  describe SQLTree, 'parsing and generating SQL' do
4
4
 
5
+ before(:each) { SQLTree.identifier_quote_char = '"' }
6
+
5
7
  it "should parse an generate q query without FROM" do
6
8
  SQLTree['SELECT 1'].to_sql.should == 'SELECT 1'
7
9
  end
10
+
11
+ it "should parse and generate MySQL type identifier quotes" do
12
+ SQLTree.identifier_quote_char = "`"
13
+ SQLTree['SELECT `field` FROM `table`'].to_sql.should ==
14
+ 'SELECT `field` FROM `table`'
15
+ end
8
16
 
9
17
  it "should parse and generate SQL fo a simple list query" do
10
18
  SQLTree["SELECT * FROM table"].to_sql.should == 'SELECT * FROM "table"'
@@ -14,7 +14,8 @@ class TokenizeTo
14
14
 
15
15
  def matches?(found_tokens)
16
16
  @found_tokens = found_tokens
17
- return @found_tokens == @expected_tokens
17
+ return @found_tokens.length == @expected_tokens.length &&
18
+ @found_tokens.zip(@expected_tokens).all? { |(f, e)| e === f }
18
19
  end
19
20
 
20
21
  def description
@@ -36,7 +37,7 @@ def tokenize_to(*expected_tokens)
36
37
  end
37
38
 
38
39
  def sql_var(name)
39
- SQLTree::Token::Variable.new(name.to_s)
40
+ SQLTree::Token::Identifier.new(name.to_s)
40
41
  end
41
42
 
42
43
  def dot
@@ -4,13 +4,13 @@ describe SQLTree::Node::DeleteQuery do
4
4
 
5
5
  it "should parse a delete query without WHERE clause correctly" do
6
6
  delete = SQLTree::Node::DeleteQuery["DELETE FROM table"]
7
- delete.table.should == 'table'
7
+ delete.table.should == SQLTree::Node::TableReference.new("table")
8
8
  delete.where.should be_nil
9
9
  end
10
10
 
11
11
  it "should parse a delete query without WHERE clause correctly" do
12
12
  delete = SQLTree::Node::DeleteQuery["DELETE FROM table WHERE 1 = 1"]
13
- delete.table.should == 'table'
13
+ delete.table.should == SQLTree::Node::TableReference.new("table")
14
14
  delete.where.should be_kind_of(SQLTree::Node::Expression)
15
15
  end
16
16
  end
@@ -4,7 +4,7 @@ describe SQLTree::Node::Expression do
4
4
 
5
5
  describe '.parse' do
6
6
  it "shoud parse a value correctly" do
7
- SQLTree::Node::Expression['123'].should == SQLTree::Node::Value.new(123)
7
+ SQLTree::Node::Expression['123'].should == SQLTree::Node::Expression::Value.new(123)
8
8
  end
9
9
 
10
10
  it "shoud parse a function call without arguments correctly" do
@@ -16,19 +16,21 @@ describe SQLTree::Node::Expression do
16
16
  it "shoud parse a function call with arguments correctly" do
17
17
  function = SQLTree::Node::Expression["MD5('string')"]
18
18
  function.function.should == 'MD5'
19
- function.arguments.should == [SQLTree::Node::Value.new('string')]
19
+ function.arguments.should == [SQLTree::Node::Expression::Value.new('string')]
20
20
  end
21
21
 
22
22
  it "should parse a logical OR expression correctly" do
23
23
  logical = SQLTree::Node::Expression["'this' OR 'that"]
24
- logical.operator.should == :or
25
- logical.expressions.should == [SQLTree::Node::Value.new('this'), SQLTree::Node::Value.new('that')]
24
+ logical.operator.should == 'OR'
25
+ logical.lhs.should == SQLTree::Node::Expression::Value.new('this')
26
+ logical.rhs.should == SQLTree::Node::Expression::Value.new('that')
26
27
  end
27
28
 
28
29
  it "should parse a logical AND expression correctly" do
29
30
  logical = SQLTree::Node::Expression['1 AND 2']
30
- logical.operator.should == :and
31
- logical.expressions == [SQLTree::Node::Value.new(1), SQLTree::Node::Value.new(2)]
31
+ logical.operator.should == 'AND'
32
+ logical.lhs.should == SQLTree::Node::Expression::Value.new(1)
33
+ logical.rhs.should == SQLTree::Node::Expression::Value.new(2)
32
34
  end
33
35
 
34
36
  it "should nest a logical AND expression correctly" do
@@ -42,61 +44,60 @@ describe SQLTree::Node::Expression do
42
44
  end
43
45
 
44
46
  it "should parse a NOT expression without parenteheses correctly" do
45
- SQLTree::Node::Expression['NOT 1'].should == SQLTree::Node::LogicalNotExpression.new(SQLTree::Node::Value.new(1))
47
+ SQLTree::Node::Expression['NOT 1'].should == SQLTree::Node::Expression::PrefixOperator.new(:operator => 'NOT', :rhs => SQLTree::Node::Expression::Value.new(1))
46
48
  end
47
49
 
48
50
  it "should parse a NOT expression without parenteheses correctly" do
49
- SQLTree::Node::Expression['NOT(1)'].should == SQLTree::Node::LogicalNotExpression.new(SQLTree::Node::Value.new(1))
51
+ SQLTree::Node::Expression['NOT(1)'].should == SQLTree::Node::Expression::PrefixOperator.new(:operator => 'NOT', :rhs => SQLTree::Node::Expression::Value.new(1))
50
52
  end
51
53
 
52
54
  it "should parse a comparison expression correctly" do
53
55
  comparison = SQLTree::Node::Expression['1 < 2']
54
56
  comparison.operator.should == '<'
55
- comparison.lhs.should == SQLTree::Node::Value.new(1)
56
- comparison.rhs.should == SQLTree::Node::Value.new(2)
57
+ comparison.lhs.should == SQLTree::Node::Expression::Value.new(1)
58
+ comparison.rhs.should == SQLTree::Node::Expression::Value.new(2)
57
59
  end
58
60
 
59
61
  it "should parse an IS NULL expression corectly" do
60
62
  comparison = SQLTree::Node::Expression['field IS NULL']
61
63
  comparison.operator.should == 'IS'
62
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
63
- comparison.rhs.should == SQLTree::Node::Value.new(nil)
64
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
65
+ comparison.rhs.should == SQLTree::Node::Expression::Value.new(nil)
64
66
  end
65
67
 
66
68
  it "should parse an IS NOT NULL expression corectly" do
67
69
  comparison = SQLTree::Node::Expression['field IS NOT NULL']
68
70
  comparison.operator.should == 'IS NOT'
69
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
70
- comparison.rhs.should == SQLTree::Node::Value.new(nil)
71
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
72
+ comparison.rhs.should == SQLTree::Node::Expression::Value.new(nil)
71
73
  end
72
74
 
73
75
  it "should parse a LIKE expression corectly" do
74
76
  comparison = SQLTree::Node::Expression["field LIKE '%search%"]
75
77
  comparison.operator.should == 'LIKE'
76
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
77
- comparison.rhs.should == SQLTree::Node::Value.new('%search%')
78
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
79
+ comparison.rhs.should == SQLTree::Node::Expression::Value.new('%search%')
78
80
  end
79
81
 
80
82
  it "should parse a NOT ILIKE expression corectly" do
81
83
  comparison = SQLTree::Node::Expression["field NOT ILIKE '%search%"]
82
84
  comparison.operator.should == 'NOT ILIKE'
83
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
84
- comparison.rhs.should == SQLTree::Node::Value.new('%search%')
85
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
86
+ comparison.rhs.should == SQLTree::Node::Expression::Value.new('%search%')
85
87
  end
86
88
 
87
89
  it "should parse an IN expression correctly" do
88
90
  comparison = SQLTree::Node::Expression["field IN (1,2,3,4)"]
89
91
  comparison.operator.should == 'IN'
90
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
91
- comparison.rhs.should be_kind_of(SQLTree::Node::SetExpression)
92
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
93
+ comparison.rhs.should be_kind_of(SQLTree::Node::Expression::List)
92
94
  end
93
95
 
94
96
  it "should parse a NOT IN expression correctly" do
95
97
  comparison = SQLTree::Node::Expression["field NOT IN (1>2, 3+6, 99)"]
96
98
  comparison.operator.should == 'NOT IN'
97
- comparison.lhs.should == SQLTree::Node::Variable.new('field')
98
- comparison.rhs.should be_kind_of(SQLTree::Node::SetExpression)
99
+ comparison.lhs.should == SQLTree::Node::Expression::Variable.new('field')
100
+ comparison.rhs.should be_kind_of(SQLTree::Node::Expression::List)
99
101
  end
100
-
101
102
  end
102
103
  end