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.
@@ -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