sql_tree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +5 -0
- data/lib/sql_tree.rb +42 -0
- data/lib/sql_tree/node.rb +37 -0
- data/lib/sql_tree/node/expression.rb +240 -0
- data/lib/sql_tree/node/field.rb +50 -0
- data/lib/sql_tree/node/join.rb +50 -0
- data/lib/sql_tree/node/select_expression.rb +45 -0
- data/lib/sql_tree/node/select_query.rb +78 -0
- data/lib/sql_tree/node/source.rb +37 -0
- data/lib/sql_tree/node/table_reference.rb +34 -0
- data/lib/sql_tree/node/value.rb +37 -0
- data/lib/sql_tree/node/variable.rb +35 -0
- data/lib/sql_tree/parser.rb +63 -0
- data/lib/sql_tree/token.rb +131 -0
- data/lib/sql_tree/tokenizer.rb +174 -0
- data/spec/integration/api_spec.rb +5 -0
- data/spec/integration/full_queries_spec.rb +21 -0
- data/spec/lib/matchers.rb +84 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/unit/expression_node_spec.rb +102 -0
- data/spec/unit/leaf_node_spec.rb +84 -0
- data/spec/unit/select_query_spec.rb +52 -0
- data/spec/unit/tokenizer_spec.rb +86 -0
- data/sql_tree.gemspec +27 -0
- data/tasks/github-gem.rake +323 -0
- metadata +92 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
# The <tt>SQLTree::Tokenizer</tt> class transforms a string or stream of
|
2
|
+
# characters into a enumeration of tokens, that are more appropriate for
|
3
|
+
# the SQL parser to work with.
|
4
|
+
#
|
5
|
+
# An example:
|
6
|
+
#
|
7
|
+
# >> SQLTree::Tokenizer.new.tokenize('SELECT * FROM table')
|
8
|
+
# => [:select, :all, :from, Variable('table')]
|
9
|
+
#
|
10
|
+
# The <tt>tokenize</tt> method will return an array of tokens, while
|
11
|
+
# the <tt>each_token</tt> (aliased to <tt>each</tt>) will yield every
|
12
|
+
# token one by one.
|
13
|
+
class SQLTree::Tokenizer
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
# The keyword queue, on which kywords are placed before they are yielded
|
18
|
+
# to the parser, to enable keyword combining (e.g. NOT LIKE)
|
19
|
+
attr_reader :keyword_queue
|
20
|
+
|
21
|
+
def initialize # :nodoc:
|
22
|
+
@keyword_queue = []
|
23
|
+
end
|
24
|
+
|
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
|
30
|
+
self.entries
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the current character that is being tokenized
|
34
|
+
def current_char
|
35
|
+
@current_char
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the next character to tokenize, but does not move
|
39
|
+
# the pointer of the current character forward.
|
40
|
+
# <tt>lookahead</tt>:: how many positions forward to peek.
|
41
|
+
def peek_char(lookahead = 1)
|
42
|
+
@string[@current_char_pos + lookahead, 1]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the next character to tokenize, and moves the pointer
|
46
|
+
# of the current character one position forward.
|
47
|
+
def next_char
|
48
|
+
@current_char_pos += 1
|
49
|
+
@current_char = @string[@current_char_pos, 1]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Combines several tokens to a single token if possible, and
|
53
|
+
# yields teh result, or yields every single token if they cannot
|
54
|
+
# be combined.
|
55
|
+
# <tt>token</tt>:: the token to yield or combine
|
56
|
+
# <tt>block</tt>:: the block to yield tokens and combined tokens to.
|
57
|
+
def handle_token(token, &block) # :yields: SQLTree::Token
|
58
|
+
if token.kind_of?(SQLTree::Token::Keyword)
|
59
|
+
keyword_queue.push(token)
|
60
|
+
else
|
61
|
+
empty_keyword_queue!(&block)
|
62
|
+
block.call(token)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# This method ensures that every keyword currently in the queue is
|
67
|
+
# yielded. This method get called by <tt>handle_token</tt> when it
|
68
|
+
# knows for sure that the keywords on the queue cannot be combined
|
69
|
+
# into a single keyword.
|
70
|
+
# <tt>block</tt>:: the block to yield the tokens on the queue to.
|
71
|
+
def empty_keyword_queue!(&block) # :yields: SQLTree::Token
|
72
|
+
block.call(@keyword_queue.shift) until @keyword_queue.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Iterator method that yields each token that is encountered in the
|
76
|
+
# SQL stream. These tokens are passed to the SQL parser to construct
|
77
|
+
# a syntax tree for the SQL query.
|
78
|
+
#
|
79
|
+
# This method is aliased to <tt>:each</tt> to make the Enumerable
|
80
|
+
# methods work on this method.
|
81
|
+
def each_token(&block) # :yields: SQLTree::Token
|
82
|
+
while next_char
|
83
|
+
case current_char
|
84
|
+
when /^\s?$/; # whitespace, go to next character
|
85
|
+
when '('; handle_token(SQLTree::Token::LPAREN, &block)
|
86
|
+
when ')'; handle_token(SQLTree::Token::RPAREN, &block)
|
87
|
+
when '.'; handle_token(SQLTree::Token::DOT, &block)
|
88
|
+
when ','; handle_token(SQLTree::Token::COMMA, &block)
|
89
|
+
when /\d/; tokenize_number(&block)
|
90
|
+
when "'"; tokenize_quoted_string(&block)
|
91
|
+
when OPERATOR_CHARS; tokenize_operator(&block)
|
92
|
+
when /\w/; tokenize_keyword(&block)
|
93
|
+
when '"'; tokenize_quoted_variable(&block) # TODO: allow MySQL quoting mode
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Make sure to yield any tokens that are still stashed on the queue.
|
98
|
+
empty_keyword_queue!(&block)
|
99
|
+
end
|
100
|
+
|
101
|
+
alias :each :each_token
|
102
|
+
|
103
|
+
# Tokenizes a eyword in the code. This can either be a reserved SQL keyword
|
104
|
+
# or a variable. This method will yield variables directly. Keywords will be
|
105
|
+
# yielded with a delay, because they may need to be combined with other
|
106
|
+
# keywords in the <tt>handle_token</tt> method.
|
107
|
+
def tokenize_keyword(&block) # :yields: SQLTree::Token
|
108
|
+
literal = current_char
|
109
|
+
literal << next_char while /[\w]/ =~ peek_char
|
110
|
+
|
111
|
+
if SQLTree::Token::KEYWORDS.include?(literal.upcase)
|
112
|
+
handle_token(SQLTree::Token.const_get(literal.upcase), &block)
|
113
|
+
else
|
114
|
+
handle_token(SQLTree::Token::Variable.new(literal), &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Tokenizes a number (either an integer or float) in the SQL stream.
|
119
|
+
# This method will yield the token after the last digit of the number
|
120
|
+
# has been encountered.
|
121
|
+
def tokenize_number(&block) # :yields: SQLTree::Token::Number
|
122
|
+
number = current_char
|
123
|
+
dot_encountered = false
|
124
|
+
while /\d/ =~ peek_char || (peek_char == '.' && !dot_encountered)
|
125
|
+
dot_encountered = true if peek_char == '.'
|
126
|
+
number << next_char
|
127
|
+
end
|
128
|
+
|
129
|
+
if dot_encountered
|
130
|
+
handle_token(SQLTree::Token::Number.new(number.to_f), &block)
|
131
|
+
else
|
132
|
+
handle_token(SQLTree::Token::Number.new(number.to_i), &block)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Reads a quoted string token from the SQL stream. This method will
|
137
|
+
# yield an SQLTree::Token::String when the closing quote character is
|
138
|
+
# encountered.
|
139
|
+
def tokenize_quoted_string(&block) # :yields: SQLTree::Token::String
|
140
|
+
string = ''
|
141
|
+
until next_char.nil? || current_char == "'"
|
142
|
+
string << (current_char == "\\" ? next_char : current_char)
|
143
|
+
end
|
144
|
+
handle_token(SQLTree::Token::String.new(string), &block)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Tokenize a quoted variable from the SQL stream. This method will
|
148
|
+
# yield an SQLTree::Token::Variable when to closing quote is found.
|
149
|
+
#
|
150
|
+
# The actual quote character that is used depends on the DBMS. For now,
|
151
|
+
# only the more standard double quote is accepted.
|
152
|
+
def tokenize_quoted_variable(&block) # :yields: SQLTree::Token::Variable
|
153
|
+
variable = ''
|
154
|
+
until next_char.nil? || current_char == '"' # TODO: allow MySQL quoting mode
|
155
|
+
variable << (current_char == "\\" ? next_char : current_char)
|
156
|
+
end
|
157
|
+
handle_token(SQLTree::Token::Variable.new(variable), &block)
|
158
|
+
end
|
159
|
+
|
160
|
+
# A regular expression that matches all operator characters.
|
161
|
+
OPERATOR_CHARS = /\=|<|>|!|\-|\+|\/|\*|\%/
|
162
|
+
|
163
|
+
# Tokenizes an operator in the SQL stream. This method will yield the
|
164
|
+
# operator token when the last character of the token is encountered.
|
165
|
+
def tokenize_operator(&block) # :yields: SQLTree::Token
|
166
|
+
operator = current_char
|
167
|
+
if operator == '-' && /[\d\.]/ =~ peek_char
|
168
|
+
tokenize_number(&block)
|
169
|
+
else
|
170
|
+
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)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
describe SQLTree, 'parsing and generating SQL' do
|
4
|
+
|
5
|
+
it "should parse and generate SQL fo a simple list query" do
|
6
|
+
SQLTree["SELECT * FROM table"].to_sql.should == 'SELECT * FROM "table"'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should parse and generate the DISTINCT keyword" do
|
10
|
+
SQLTree["SELECT DISTINCT * FROM table"].to_sql.should == 'SELECT DISTINCT * FROM "table"'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should parse and generate table aliases' do
|
14
|
+
SQLTree["SELECT a.* FROM table AS a"].to_sql.should == 'SELECT "a".* FROM "table" AS "a"'
|
15
|
+
end
|
16
|
+
|
17
|
+
it "parse and generate a complex SQL query" do
|
18
|
+
SQLTree['SELECT a.*, MD5( a.name ) AS checksum FROM table AS a , other WHERE other.timestamp > a.timestamp'].to_sql.should ==
|
19
|
+
'SELECT "a".*, MD5("a"."name") AS "checksum" FROM "table" AS "a", "other" WHERE ("other"."timestamp" > "a"."timestamp")'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class ParseAs
|
2
|
+
|
3
|
+
def initialize(expected_tree)
|
4
|
+
@expected_tree = expected_tree
|
5
|
+
end
|
6
|
+
|
7
|
+
def matches?(found_tree)
|
8
|
+
@found_tree = found_tree.to_tree
|
9
|
+
return @found_tree == @expected_tree
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
"expected to parse to #{@expected_tree.inspect}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
" #{@expected_tree.inspect} expected, but found #{@found_tree.inspect}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def negative_failure_message
|
21
|
+
" expected not to be tokenized to #{@expected_tree.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_as(tree)
|
26
|
+
ParseAs.new(tree)
|
27
|
+
end
|
28
|
+
|
29
|
+
class TokenizeTo
|
30
|
+
|
31
|
+
def initialize(expected_tokens)
|
32
|
+
@expected_tokens = expected_tokens.map do |t|
|
33
|
+
case t
|
34
|
+
when SQLTree::Token then t
|
35
|
+
when String then SQLTree::Token::String.new(t)
|
36
|
+
when Numeric then SQLTree::Token::Number.new(t)
|
37
|
+
when Symbol then SQLTree::Token.const_get(t.to_s.upcase)
|
38
|
+
else "Cannot check for this token: #{t.inspect}!"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def matches?(found_tokens)
|
44
|
+
@found_tokens = found_tokens
|
45
|
+
return @found_tokens == @expected_tokens
|
46
|
+
end
|
47
|
+
|
48
|
+
def description
|
49
|
+
"expected to tokenized to #{@expected_tokens.inspect}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def failure_message
|
53
|
+
" #{@expected_tokens.inspect} expected, but found #{@found_tokens.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def negative_failure_message
|
57
|
+
" expected not to be tokenized to #{@expected_tokens.inspect}"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def tokenize_to(*expected_tokens)
|
63
|
+
TokenizeTo.new(expected_tokens)
|
64
|
+
end
|
65
|
+
|
66
|
+
def sql_var(name)
|
67
|
+
SQLTree::Token::Variable.new(name.to_s)
|
68
|
+
end
|
69
|
+
|
70
|
+
def dot
|
71
|
+
SQLTree::Token::DOT
|
72
|
+
end
|
73
|
+
|
74
|
+
def comma
|
75
|
+
SQLTree::Token::COMMA
|
76
|
+
end
|
77
|
+
|
78
|
+
def lparen
|
79
|
+
SQLTree::Token::LPAREN
|
80
|
+
end
|
81
|
+
|
82
|
+
def rparen
|
83
|
+
SQLTree::Token::RPAREN
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.reject! { |e| e.include? 'TextMate' }
|
2
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'spec'
|
6
|
+
require 'sql_tree'
|
7
|
+
|
8
|
+
module SQLTree::Spec
|
9
|
+
module NodeLoader
|
10
|
+
def self.const_missing(const)
|
11
|
+
SQLTree::Node.const_get(const)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module TokenLoader
|
16
|
+
def self.const_missing(const)
|
17
|
+
SQLTree::Token.const_get(const)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Spec::Runner.configure do |config|
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
require "#{File.dirname(__FILE__)}/lib/matchers"
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
describe SQLTree::Node::Expression do
|
4
|
+
|
5
|
+
describe '.parse' do
|
6
|
+
it "shoud parse a value correctly" do
|
7
|
+
SQLTree::Node::Expression['123'].should == SQLTree::Node::Value.new(123)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "shoud parse a function call without arguments correctly" do
|
11
|
+
function = SQLTree::Node::Expression['NOW()']
|
12
|
+
function.function.should == 'NOW'
|
13
|
+
function.arguments.should be_empty
|
14
|
+
end
|
15
|
+
|
16
|
+
it "shoud parse a function call with arguments correctly" do
|
17
|
+
function = SQLTree::Node::Expression["MD5('string')"]
|
18
|
+
function.function.should == 'MD5'
|
19
|
+
function.arguments.should == [SQLTree::Node::Value.new('string')]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should parse a logical OR expression correctly" do
|
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')]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should parse a logical AND expression correctly" do
|
29
|
+
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)]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should nest a logical AND expression correctly" do
|
35
|
+
logical = SQLTree::Node::Expression['1 AND 2 AND 3']
|
36
|
+
logical.should == SQLTree::Node::Expression['(1 AND 2) AND 3']
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should nest expressions correctly when parentheses are used" do
|
40
|
+
logical = SQLTree::Node::Expression['1 AND (2 AND 3)']
|
41
|
+
logical.should_not == SQLTree::Node::Expression['(1 AND 2) AND 3']
|
42
|
+
end
|
43
|
+
|
44
|
+
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))
|
46
|
+
end
|
47
|
+
|
48
|
+
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))
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should parse a comparison expression correctly" do
|
53
|
+
comparison = SQLTree::Node::Expression['1 < 2']
|
54
|
+
comparison.operator.should == '<'
|
55
|
+
comparison.lhs.should == SQLTree::Node::Value.new(1)
|
56
|
+
comparison.rhs.should == SQLTree::Node::Value.new(2)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should parse an IS NULL expression corectly" do
|
60
|
+
comparison = SQLTree::Node::Expression['field IS NULL']
|
61
|
+
comparison.operator.should == 'IS'
|
62
|
+
comparison.lhs.should == SQLTree::Node::Variable.new('field')
|
63
|
+
comparison.rhs.should == SQLTree::Node::Value.new(nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse an IS NOT NULL expression corectly" do
|
67
|
+
comparison = SQLTree::Node::Expression['field IS NOT NULL']
|
68
|
+
comparison.operator.should == 'IS NOT'
|
69
|
+
comparison.lhs.should == SQLTree::Node::Variable.new('field')
|
70
|
+
comparison.rhs.should == SQLTree::Node::Value.new(nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should parse a LIKE expression corectly" do
|
74
|
+
comparison = SQLTree::Node::Expression["field LIKE '%search%"]
|
75
|
+
comparison.operator.should == 'LIKE'
|
76
|
+
comparison.lhs.should == SQLTree::Node::Variable.new('field')
|
77
|
+
comparison.rhs.should == SQLTree::Node::Value.new('%search%')
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should parse a NOT ILIKE expression corectly" do
|
81
|
+
comparison = SQLTree::Node::Expression["field NOT ILIKE '%search%"]
|
82
|
+
comparison.operator.should == 'NOT ILIKE'
|
83
|
+
comparison.lhs.should == SQLTree::Node::Variable.new('field')
|
84
|
+
comparison.rhs.should == SQLTree::Node::Value.new('%search%')
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should parse an IN expression correctly" do
|
88
|
+
comparison = SQLTree::Node::Expression["field IN (1,2,3,4)"]
|
89
|
+
comparison.operator.should == 'IN'
|
90
|
+
comparison.lhs.should == SQLTree::Node::Variable.new('field')
|
91
|
+
comparison.rhs.should be_kind_of(SQLTree::Node::SetExpression)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should parse a NOT IN expression correctly" do
|
95
|
+
comparison = SQLTree::Node::Expression["field NOT IN (1>2, 3+6, 99)"]
|
96
|
+
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
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
describe SQLTree::Node::Value do
|
4
|
+
|
5
|
+
describe '.parse' do
|
6
|
+
it "should not parse a field name" do
|
7
|
+
lambda { SQLTree::Node::Value['field_name'] }.should raise_error(SQLTree::Parser::UnexpectedToken)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse an integer value correctly" do
|
11
|
+
SQLTree::Node::Value['123'].value.should == 123
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should parse a string correctly" do
|
15
|
+
SQLTree::Node::Value["'123'"].value.should == '123'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should parse a NULL value correctly" do
|
19
|
+
SQLTree::Node::Value['NULL'].value.should == nil
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe SQLTree::Node::Variable do
|
26
|
+
|
27
|
+
describe '.parse' do
|
28
|
+
it "should parse a variable name correctly" do
|
29
|
+
SQLTree::Node::Field['variable'].name.should == 'variable'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse a quoted variable name correctly" do
|
33
|
+
SQLTree::Node::Field['"variable"'].name.should == 'variable'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise an error when parsing a reserved keyword as variable" do
|
37
|
+
lambda { SQLTree::Node::Field['select'] }.should raise_error(SQLTree::Parser::UnexpectedToken)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should parse a quoted reserved keyword as variable name correctly" do
|
41
|
+
SQLTree::Node::Field['"select"'].name.should == 'select'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe SQLTree::Node::Field do
|
47
|
+
describe '.parse' do
|
48
|
+
it "should parse a field name with table name correclty" do
|
49
|
+
field = SQLTree::Node::Field['table.field']
|
50
|
+
field.table.should == 'table'
|
51
|
+
field.name.should == 'field'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should parse a field name without table name correclty" do
|
55
|
+
field = SQLTree::Node::Field['field']
|
56
|
+
field.table.should be_nil
|
57
|
+
field.name.should == 'field'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should parse a quoted field name without table name correclty" do
|
61
|
+
field = SQLTree::Node::Field['"field"']
|
62
|
+
field.table.should be_nil
|
63
|
+
field.name.should == 'field'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse a quoted field name with quoted table name correclty" do
|
67
|
+
field = SQLTree::Node::Field['"table"."field"']
|
68
|
+
field.table.should == 'table'
|
69
|
+
field.name.should == 'field'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should parse a quoted field name with non-quoted table name correclty" do
|
73
|
+
field = SQLTree::Node::Field['table."field"']
|
74
|
+
field.table.should == 'table'
|
75
|
+
field.name.should == 'field'
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should parse a non-quoted field name with quoted table name correclty" do
|
79
|
+
field = SQLTree::Node::Field['"table".field']
|
80
|
+
field.table.should == 'table'
|
81
|
+
field.name.should == 'field'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|