rgviz 0.14 → 0.15

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.
@@ -0,0 +1,171 @@
1
+ require 'rgviz'
2
+
3
+ include Rgviz
4
+
5
+ describe Lexer do
6
+ def self.it_lexes_keyword(str, token_value)
7
+ it "lexes #{str}" do
8
+ lex = Lexer.new str
9
+ tok = lex.next_token
10
+ tok.value.should == token_value
11
+ end
12
+
13
+ it "lexes #{str} upcase" do
14
+ lex = Lexer.new str.upcase
15
+ tok = lex.next_token
16
+ tok.value.should == token_value
17
+ end
18
+ end
19
+
20
+ def self.it_lexes_id(str, id = str)
21
+ it "lexes identifier #{str}" do
22
+ lex = Lexer.new str
23
+ tok = lex.next_token
24
+ tok.value.should == Token::ID
25
+ tok.string.should == id
26
+ end
27
+ end
28
+
29
+ def self.it_lexes_string(str, id = str)
30
+ it "lexes string #{str}" do
31
+ lex = Lexer.new str
32
+ tok = lex.next_token
33
+ tok.value.should == Token::STRING
34
+ tok.string.should == id
35
+ end
36
+ end
37
+
38
+ def self.it_lexes_token(str, token_value)
39
+ it "lexes #{str}" do
40
+ lex = Lexer.new str
41
+ tok = lex.next_token
42
+ tok.value.should == token_value
43
+ end
44
+ end
45
+
46
+ def self.it_lexes_integer(str, number)
47
+ it "lexes #{str}" do
48
+ lex = Lexer.new str
49
+ tok = lex.next_token
50
+ tok.value.should == Token::INTEGER
51
+ tok.number.should == number
52
+ end
53
+ end
54
+
55
+ def self.it_lexes_decimal(str, number)
56
+ it "lexes #{str}" do
57
+ lex = Lexer.new str
58
+ tok = lex.next_token
59
+ tok.value.should == Token::DECIMAL
60
+ tok.number.should == number
61
+ end
62
+ end
63
+
64
+ def self.it_lexes_eof(str)
65
+ it "lexes eof #{str}" do
66
+ lex = Lexer.new str
67
+ tok = lex.next_token
68
+ tok.value.should == Token::EOF
69
+ end
70
+ end
71
+
72
+ def self.it_lexes_error(str)
73
+ it "lexes error #{str}" do
74
+ lex = Lexer.new "x#{str}"
75
+ tok = lex.next_token
76
+ tok.value.should == Token::ID
77
+ tok.string.should == 'x'
78
+ lambda { lex.next_token }.should raise_error(ParseException)
79
+ end
80
+ end
81
+
82
+ it_lexes_keyword 'and', Token::And
83
+ it_lexes_keyword 'asc', Token::Asc
84
+ it_lexes_keyword 'avg', Token::Avg
85
+ it_lexes_keyword 'by', Token::By
86
+ it_lexes_keyword 'contains', Token::Contains
87
+ it_lexes_keyword 'count', Token::Count
88
+ it_lexes_keyword 'date', Token::Date
89
+ it_lexes_keyword 'datediff', Token::DateDiff
90
+ it_lexes_keyword 'datetime', Token::DateTime
91
+ it_lexes_keyword 'day', Token::Day
92
+ it_lexes_keyword 'dayofweek', Token::DayOfWeek
93
+ it_lexes_keyword 'desc', Token::Desc
94
+ it_lexes_keyword 'ends', Token::Ends
95
+ it_lexes_keyword 'false', Token::False
96
+ it_lexes_keyword 'format', Token::Format
97
+ it_lexes_keyword 'group', Token::Group
98
+ it_lexes_keyword 'hour', Token::Hour
99
+ it_lexes_keyword 'is', Token::Is
100
+ it_lexes_keyword 'label', Token::Label
101
+ it_lexes_keyword 'like', Token::Like
102
+ it_lexes_keyword 'limit', Token::Limit
103
+ it_lexes_keyword 'lower', Token::Lower
104
+ it_lexes_keyword 'matches', Token::Matches
105
+ it_lexes_keyword 'millisecond', Token::Millisecond
106
+ it_lexes_keyword 'min', Token::Min
107
+ it_lexes_keyword 'minute', Token::Minute
108
+ it_lexes_keyword 'max', Token::Max
109
+ it_lexes_keyword 'month', Token::Month
110
+ it_lexes_keyword 'not', Token::Not
111
+ it_lexes_keyword 'now', Token::Now
112
+ it_lexes_keyword 'no_format', Token::NoFormat
113
+ it_lexes_keyword 'no_values', Token::NoValues
114
+ it_lexes_keyword 'null', Token::Null
115
+ it_lexes_keyword 'offset', Token::Offset
116
+ it_lexes_keyword 'options', Token::Options
117
+ it_lexes_keyword 'or', Token::Or
118
+ it_lexes_keyword 'order', Token::Order
119
+ it_lexes_keyword 'pivot', Token::Pivot
120
+ it_lexes_keyword 'quarter', Token::Quarter
121
+ it_lexes_keyword 'second', Token::Second
122
+ it_lexes_keyword 'select', Token::Select
123
+ it_lexes_keyword 'starts', Token::Starts
124
+ it_lexes_keyword 'sum', Token::Sum
125
+ it_lexes_keyword 'timeofday', Token::TimeOfDay
126
+ it_lexes_keyword 'timestamp', Token::Timestamp
127
+ it_lexes_keyword 'todate', Token::ToDate
128
+ it_lexes_keyword 'true', Token::True
129
+ it_lexes_keyword 'upper', Token::Upper
130
+ it_lexes_keyword 'where', Token::Where
131
+ it_lexes_keyword 'with', Token::With
132
+ it_lexes_keyword 'year', Token::Year
133
+
134
+ it_lexes_id 'selected'
135
+ it_lexes_id ' selected ', 'selected'
136
+ it_lexes_id '`selected`', 'selected'
137
+ it_lexes_id '`some string`', 'some string'
138
+ it_lexes_id 'hello123bye'
139
+ it_lexes_id 'hello_123_bye'
140
+ it_lexes_string "'hello world'", "hello world"
141
+ it_lexes_string '"hello world"', "hello world"
142
+
143
+ it_lexes_token '+', Token::PLUS
144
+ it_lexes_token '-', Token::MINUS
145
+ it_lexes_token '*', Token::STAR
146
+ it_lexes_token '/', Token::SLASH
147
+
148
+ it_lexes_token ',', Token::COMMA
149
+ it_lexes_token '(', Token::LPAREN
150
+ it_lexes_token ')', Token::RPAREN
151
+ it_lexes_token '=', Token::EQ
152
+ it_lexes_token '<', Token::LT
153
+ it_lexes_token '<=', Token::LTE
154
+ it_lexes_token '>', Token::GT
155
+ it_lexes_token '>=', Token::GTE
156
+ it_lexes_token '!=', Token::NEQ
157
+ it_lexes_token '<>', Token::NEQ
158
+
159
+ it_lexes_integer '1', 1
160
+ it_lexes_integer '0123', 123
161
+ it_lexes_integer '45678791230', 45678791230
162
+ it_lexes_decimal '123.456', 123.456
163
+ it_lexes_decimal '.456', 0.456
164
+
165
+ it_lexes_eof ''
166
+ it_lexes_eof " \t\n\r"
167
+
168
+ it_lexes_error '!'
169
+ it_lexes_error '?'
170
+ it_lexes_error ':'
171
+ end
@@ -0,0 +1,360 @@
1
+ require 'rgviz'
2
+
3
+ include Rgviz
4
+
5
+ describe Parser do
6
+ def parse(string)
7
+ Parser.new(string).parse
8
+ end
9
+
10
+ def parse_select_single_column(string)
11
+ query = parse "select #{string}"
12
+ select = query.select
13
+ select.columns.length.should == 1
14
+
15
+ select.columns[0]
16
+ end
17
+
18
+ def parse_where(string)
19
+ query = parse "where #{string}"
20
+ query.where.expression
21
+ end
22
+
23
+ def self.it_parses_as_identifier(str)
24
+ it "parses #{str} as identifier" do
25
+ col = parse_select_single_column "#{str}"
26
+ col.should be_a_kind_of IdColumn
27
+ col.name.should == "#{str}"
28
+ end
29
+ end
30
+
31
+ def self.it_parses_columns_container(str, method)
32
+ it "parses #{str}" do
33
+ query = parse "#{str} foo"
34
+ cols = query.send method
35
+ cols.columns.length == 1
36
+ cols.columns[0].should be_a_kind_of IdColumn
37
+ cols.columns[0].name.should == 'foo'
38
+ end
39
+ end
40
+
41
+ def self.it_raises_on(str)
42
+ it "raises on #{str}" do
43
+ lambda { parse "#{str}" }.should raise_error(ParseException)
44
+ end
45
+ end
46
+
47
+ it "parses empty" do
48
+ query = parse ''
49
+ query.select.should be_nil
50
+ end
51
+
52
+ it "parses select all" do
53
+ query = parse 'select *'
54
+ select = query.select
55
+ select.columns.should be_empty
56
+ end
57
+
58
+ it "parses select simple columns" do
59
+ query = parse 'select one, two, three'
60
+ select = query.select
61
+
62
+ select.columns.length.should == 3
63
+
64
+ ['one', 'two', 'three'].each_with_index do |name, i|
65
+ select.columns[i].should be_a_kind_of IdColumn
66
+ select.columns[i].name.should == name
67
+ end
68
+ end
69
+
70
+ it "parses select number" do
71
+ col = parse_select_single_column '1'
72
+ col.should be_a_kind_of NumberColumn
73
+ col.value.should == 1
74
+ end
75
+
76
+ it "parses select number with minus" do
77
+ col = parse_select_single_column '-1'
78
+ col.should be_a_kind_of NumberColumn
79
+ col.value.should == -1
80
+ end
81
+
82
+ it "parses select number with plus" do
83
+ col = parse_select_single_column '+1'
84
+ col.should be_a_kind_of NumberColumn
85
+ col.value.should == 1
86
+ end
87
+
88
+ it "parses select parenthesized number" do
89
+ col = parse_select_single_column '(1)'
90
+ col.should be_a_kind_of NumberColumn
91
+ col.value.should == 1
92
+ end
93
+
94
+ it "parses select string" do
95
+ col = parse_select_single_column '"hello"'
96
+ col.should be_a_kind_of StringColumn
97
+ col.value.should == 'hello'
98
+ end
99
+
100
+ it "parses select false" do
101
+ col = parse_select_single_column 'false'
102
+ col.should be_a_kind_of BooleanColumn
103
+ col.value.should == false
104
+ end
105
+
106
+ it "parses select true" do
107
+ col = parse_select_single_column 'true'
108
+ col.should be_a_kind_of BooleanColumn
109
+ col.value.should == true
110
+ end
111
+
112
+ it "parses select date" do
113
+ col = parse_select_single_column 'date "2010-01-02"'
114
+ col.should be_a_kind_of DateColumn
115
+ col.value.should == Date.parse('2010-01-02')
116
+ end
117
+
118
+ it "parses select timeofday" do
119
+ col = parse_select_single_column 'timeofday "12:01:02"'
120
+ col.should be_a_kind_of TimeOfDayColumn
121
+ col.value.hour.should == 12
122
+ col.value.min.should == 1
123
+ col.value.sec.should == 2
124
+ end
125
+
126
+ it "parses select datetime" do
127
+ col = parse_select_single_column 'datetime "2010-01-02 12:02:03"'
128
+ col.should be_a_kind_of DateTimeColumn
129
+ col.value.should == Time.parse('2010-01-02 12:02:03')
130
+ end
131
+
132
+ [
133
+ ['*', ScalarFunctionColumn::Product],
134
+ ['/', ScalarFunctionColumn::Quotient],
135
+ ['+', ScalarFunctionColumn::Sum],
136
+ ['-', ScalarFunctionColumn::Difference]
137
+ ].each do |str, func|
138
+ it "parses select #{func}" do
139
+ col = parse_select_single_column "1 #{str} one"
140
+ col.should be_a_kind_of ScalarFunctionColumn
141
+ col.function.should == func
142
+
143
+ args = col.arguments
144
+ args.length.should == 2
145
+
146
+ args[0].should be_a_kind_of NumberColumn
147
+ args[0].value.should == 1
148
+
149
+ args[1].should be_a_kind_of IdColumn
150
+ args[1].name.should == "one"
151
+ end
152
+ end
153
+
154
+ it "parses compound arithmetic" do
155
+ col = parse_select_single_column "1 * 2 + 3 * 4"
156
+ col.should be_kind_of ScalarFunctionColumn
157
+ col.function.should == ScalarFunctionColumn::Sum
158
+
159
+ col.arguments.length.should == 2
160
+
161
+ [0, 1].each do |i|
162
+ sub = col.arguments[i]
163
+ sub.should be_kind_of ScalarFunctionColumn
164
+ sub.function.should == ScalarFunctionColumn::Product
165
+ sub.arguments.length.should == 2
166
+ end
167
+ end
168
+
169
+ [
170
+ ['min', AggregateColumn::Min],
171
+ ['max', AggregateColumn::Max],
172
+ ['count', AggregateColumn::Count],
173
+ ['avg', AggregateColumn::Avg],
174
+ ['sum', AggregateColumn::Sum]
175
+ ].each do |str, function|
176
+ it "parses #{str} aggregation" do
177
+ col = parse_select_single_column "#{str}(col)"
178
+ col.should be_a_kind_of AggregateColumn
179
+ col.function.should == function
180
+
181
+ col.argument.should be_a_kind_of IdColumn
182
+ col.argument.name.should == 'col'
183
+ end
184
+
185
+ it_parses_as_identifier str
186
+ end
187
+
188
+ [
189
+ ['year', ScalarFunctionColumn::Year],
190
+ ['month', ScalarFunctionColumn::Month],
191
+ ['day', ScalarFunctionColumn::Day],
192
+ ['hour', ScalarFunctionColumn::Hour],
193
+ ['minute', ScalarFunctionColumn::Minute],
194
+ ['second', ScalarFunctionColumn::Second],
195
+ ['millisecond', ScalarFunctionColumn::Millisecond],
196
+ ['now', ScalarFunctionColumn::Now],
197
+ ['datediff', ScalarFunctionColumn::DateDiff],
198
+ ['lower', ScalarFunctionColumn::Lower],
199
+ ['upper', ScalarFunctionColumn::Upper],
200
+ ['quarter', ScalarFunctionColumn::Quarter],
201
+ ['dayofweek', ScalarFunctionColumn::DayOfWeek],
202
+ ['todate', ScalarFunctionColumn::ToDate]
203
+ ].each do |str, function|
204
+ it "parses #{str} function" do
205
+ col = parse_select_single_column "#{str}(col)"
206
+ col.should be_a_kind_of ScalarFunctionColumn
207
+ col.function.should == function
208
+
209
+ col.arguments.length.should == 1
210
+ col.arguments[0].should be_a_kind_of IdColumn
211
+ col.arguments[0].name.should == 'col'
212
+ end
213
+
214
+ it_parses_as_identifier str
215
+ end
216
+
217
+ it "parses datediff function with many arguments" do
218
+ col = parse_select_single_column "datediff(1, 2)"
219
+ col.should be_a_kind_of ScalarFunctionColumn
220
+ col.function.should == ScalarFunctionColumn::DateDiff
221
+
222
+ col.arguments.length.should == 2
223
+ col.arguments[0].value == 1
224
+ col.arguments[1].value == 2
225
+ end
226
+
227
+ [
228
+ ['=', BinaryExpression::Eq],
229
+ ['!=', BinaryExpression::Neq],
230
+ ['<>', BinaryExpression::Neq],
231
+ ['<', BinaryExpression::Lt],
232
+ ['<=', BinaryExpression::Lte],
233
+ ['>', BinaryExpression::Gt],
234
+ ['>=', BinaryExpression::Gte],
235
+ ['contains', BinaryExpression::Contains],
236
+ ['starts with', BinaryExpression::StartsWith],
237
+ ['ends with', BinaryExpression::EndsWith],
238
+ ['matches', BinaryExpression::Matches],
239
+ ['like', BinaryExpression::Like]
240
+ ].each do |str, operator|
241
+ it "parses where #{str} 1" do
242
+ exp = parse_where "col #{str} 1"
243
+ exp.should be_a_kind_of BinaryExpression
244
+ exp.operator.should == operator
245
+ exp.left.should be_a_kind_of IdColumn
246
+ exp.left.name.should == 'col'
247
+ exp.right.should be_a_kind_of NumberColumn
248
+ exp.right.value.should == 1
249
+ end
250
+ end
251
+
252
+ it_parses_as_identifier 'contains'
253
+ it_parses_as_identifier 'starts'
254
+ it_parses_as_identifier 'ends'
255
+ it_parses_as_identifier 'with'
256
+ it_parses_as_identifier 'matches'
257
+ it_parses_as_identifier 'like'
258
+ it_parses_as_identifier 'no_values'
259
+ it_parses_as_identifier 'no_format'
260
+ it_parses_as_identifier 'is'
261
+ it_parses_as_identifier 'null'
262
+
263
+ it_parses_columns_container 'group by', :group_by
264
+ it_parses_columns_container 'pivot', :pivot
265
+
266
+ [
267
+ ['', Sort::Asc],
268
+ [' asc', Sort::Asc],
269
+ [' desc', Sort::Desc]
270
+ ].each do |str, order|
271
+ it "parses order by" do
272
+ query = parse "order by foo #{order}, bar #{order}"
273
+ order_by = query.order_by
274
+
275
+ sorts = order_by.sorts
276
+
277
+ sorts.length.should == 2
278
+
279
+ sorts[0].column.should be_a_kind_of IdColumn
280
+ sorts[0].column.name.should == 'foo'
281
+ sorts[0].order.should == order
282
+
283
+ sorts[1].column.should be_a_kind_of IdColumn
284
+ sorts[1].column.name.should == 'bar'
285
+ sorts[1].order.should == order
286
+ end
287
+ end
288
+
289
+ it "parses limit" do
290
+ query = parse "limit 10"
291
+ query.limit.should == 10
292
+ end
293
+
294
+ it "parses offset" do
295
+ query = parse "offset 10"
296
+ query.offset.should == 10
297
+ end
298
+
299
+ it "parses label" do
300
+ query = parse "label one 'unu', two 'du'"
301
+ labels = query.labels
302
+ labels.length.should == 2
303
+
304
+ labels[0].column.should be_a_kind_of IdColumn
305
+ labels[0].column.name.should == 'one'
306
+ labels[0].label.should == 'unu'
307
+
308
+ labels[1].column.should be_a_kind_of IdColumn
309
+ labels[1].column.name.should == 'two'
310
+ labels[1].label.should == 'du'
311
+ end
312
+
313
+ it "parses format" do
314
+ query = parse "format one 'unu', two 'du'"
315
+ formats = query.formats
316
+ formats.length.should == 2
317
+
318
+ formats[0].column.should be_a_kind_of IdColumn
319
+ formats[0].column.name.should == 'one'
320
+ formats[0].pattern.should == 'unu'
321
+
322
+ formats[1].column.should be_a_kind_of IdColumn
323
+ formats[1].column.name.should == 'two'
324
+ formats[1].pattern.should == 'du'
325
+ end
326
+
327
+ it "parses options" do
328
+ query = parse "options no_values no_format"
329
+ options = query.options
330
+ options.length.should == 2
331
+
332
+ options[0].should be_a_kind_of Option
333
+ options[0].option.should == Option::NoValues
334
+
335
+ options[1].should be_a_kind_of Option
336
+ options[1].option.should == Option::NoFormat
337
+ end
338
+
339
+ it_raises_on 'select'
340
+ it_raises_on 'where'
341
+ it_raises_on 'where 1'
342
+ it_raises_on 'where 1 <'
343
+ it_raises_on 'group by'
344
+ it_raises_on 'group by foo bar'
345
+ it_raises_on 'pivot'
346
+ it_raises_on 'order by'
347
+ it_raises_on 'order by foo bar'
348
+ it_raises_on 'limit'
349
+ it_raises_on 'limit foo'
350
+ it_raises_on 'offset'
351
+ it_raises_on 'offset foo'
352
+ it_raises_on 'label'
353
+ it_raises_on 'label foo'
354
+ it_raises_on 'label `foo`'
355
+ it_raises_on 'format'
356
+ it_raises_on 'format foo'
357
+ it_raises_on 'format `foo`'
358
+ it_raises_on 'options'
359
+ it_raises_on 'options foobar'
360
+ end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgviz
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 14
9
- version: "0.14"
8
+ - 15
9
+ version: "0.15"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ary Borenszweig
@@ -34,6 +34,8 @@ files:
34
34
  - lib/rgviz/token.rb
35
35
  - lib/rgviz/visitor.rb
36
36
  - lib/rgviz/table.rb
37
+ - spec/rgviz/lexer_spec.rb
38
+ - spec/rgviz/parser_spec.rb
37
39
  has_rdoc: true
38
40
  homepage: http://code.google.com/p/rgviz
39
41
  licenses: []