piggly 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/README.markdown +84 -0
  2. data/Rakefile +19 -0
  3. data/bin/piggly +245 -0
  4. data/lib/piggly/compiler/cache.rb +151 -0
  5. data/lib/piggly/compiler/pretty.rb +67 -0
  6. data/lib/piggly/compiler/queue.rb +46 -0
  7. data/lib/piggly/compiler/tags.rb +244 -0
  8. data/lib/piggly/compiler/trace.rb +91 -0
  9. data/lib/piggly/compiler.rb +5 -0
  10. data/lib/piggly/config.rb +43 -0
  11. data/lib/piggly/filecache.rb +40 -0
  12. data/lib/piggly/installer.rb +95 -0
  13. data/lib/piggly/parser/grammar.tt +747 -0
  14. data/lib/piggly/parser/nodes.rb +319 -0
  15. data/lib/piggly/parser/parser.rb +11783 -0
  16. data/lib/piggly/parser/traversal.rb +48 -0
  17. data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
  18. data/lib/piggly/parser.rb +67 -0
  19. data/lib/piggly/profile.rb +87 -0
  20. data/lib/piggly/reporter/html.rb +207 -0
  21. data/lib/piggly/reporter/piggly.css +187 -0
  22. data/lib/piggly/reporter/sortable.js +493 -0
  23. data/lib/piggly/reporter.rb +21 -0
  24. data/lib/piggly/task.rb +64 -0
  25. data/lib/piggly/util.rb +28 -0
  26. data/lib/piggly/version.rb +15 -0
  27. data/lib/piggly.rb +18 -0
  28. data/spec/compiler/cache_spec.rb +9 -0
  29. data/spec/compiler/pretty_spec.rb +9 -0
  30. data/spec/compiler/queue_spec.rb +3 -0
  31. data/spec/compiler/rewrite_spec.rb +3 -0
  32. data/spec/compiler/tags_spec.rb +285 -0
  33. data/spec/compiler/trace_spec.rb +173 -0
  34. data/spec/config_spec.rb +58 -0
  35. data/spec/filecache_spec.rb +70 -0
  36. data/spec/fixtures/snippets.sql +158 -0
  37. data/spec/grammar/expression_spec.rb +302 -0
  38. data/spec/grammar/statements/assignment_spec.rb +70 -0
  39. data/spec/grammar/statements/exception_spec.rb +52 -0
  40. data/spec/grammar/statements/if_spec.rb +178 -0
  41. data/spec/grammar/statements/loop_spec.rb +41 -0
  42. data/spec/grammar/statements/sql_spec.rb +71 -0
  43. data/spec/grammar/tokens/comment_spec.rb +58 -0
  44. data/spec/grammar/tokens/datatype_spec.rb +52 -0
  45. data/spec/grammar/tokens/identifier_spec.rb +58 -0
  46. data/spec/grammar/tokens/keyword_spec.rb +44 -0
  47. data/spec/grammar/tokens/label_spec.rb +40 -0
  48. data/spec/grammar/tokens/literal_spec.rb +30 -0
  49. data/spec/grammar/tokens/lval_spec.rb +50 -0
  50. data/spec/grammar/tokens/number_spec.rb +34 -0
  51. data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
  52. data/spec/grammar/tokens/string_spec.rb +54 -0
  53. data/spec/grammar/tokens/whitespace_spec.rb +40 -0
  54. data/spec/parser_spec.rb +8 -0
  55. data/spec/profile_spec.rb +5 -0
  56. data/spec/reporter/html_spec.rb +0 -0
  57. data/spec/spec_helper.rb +61 -0
  58. data/spec/spec_suite.rb +5 -0
  59. metadata +121 -0
@@ -0,0 +1,158 @@
1
+ CREATE OR REPLACE FUNCTION piggly_coverage_prog(a integer, b boolean) RETURNS void AS $$
2
+ DECLARE
3
+ x varchar(20);
4
+ y integer[] := '{}';
5
+ BEGIN
6
+
7
+ -- IF-THEN
8
+ IF v_user_id <> 0
9
+ THEN
10
+ UPDATE users SET email = v_email WHERE user_id = v_user_id;
11
+ END IF;
12
+
13
+ -- IF-THEN-ELSE
14
+ IF parentid IS NULL OR parentid = ''
15
+ THEN
16
+ RETURN fullname;
17
+ ELSE
18
+ RETURN hp_true_filename(parentid) || '/' || fullname;
19
+ END IF;
20
+
21
+ -- IF-THEN-ELSE
22
+ IF v_count > 0 THEN
23
+ INSERT INTO users_count(count) VALUES (v_count);
24
+ RETURN 't';
25
+ ELSE
26
+ RETURN 'f';
27
+ END IF;
28
+
29
+ -- IF-THEN-ELSIF
30
+ IF number = 0 THEN
31
+ result := 'zero';
32
+ ELSIF number > 0 THEN
33
+ result := 'positive';
34
+ ELSIF number < 0 THEN
35
+ result := 'negative';
36
+ ELSE
37
+ -- hmm, the only other possibility is that the number is null
38
+ result := 'NULL';
39
+ END IF;
40
+
41
+ -- nest IF-THEN-ELSE statements
42
+ IF demo_row.sex = 'm' THEN
43
+ pretty_sex := 'man';
44
+ ELSE
45
+ IF demo_row.sex = 'f' THEN
46
+ pretty_sex := 'woman';
47
+ END IF;
48
+ END IF;
49
+
50
+ -- simple CASE with search-expression
51
+ CASE x
52
+ WHEN 1, 2 THEN
53
+ msg := 'one or two';
54
+ ELSE
55
+ msg := 'other value than one or two';
56
+ END CASE;
57
+
58
+ -- searched CASE with boolean-expression
59
+ CASE
60
+ WHEN x BETWEEN 0 and 10 THEN
61
+ msg := 'value is between zero and ten';
62
+ WHEN x BETWEEN 11 and 20 THEN
63
+ msg := 'value is between eleven and twenty';
64
+ END CASE;
65
+
66
+ << labelA >>
67
+ LOOP
68
+ a := a + 1;
69
+ EXIT labelA WHEN a > 10;
70
+ END LOOP labelA;
71
+
72
+ LOOP
73
+ b := b + 1
74
+ EXIT WHEN b > 10;
75
+ END LOOP;
76
+
77
+ LOOP
78
+ c := c + 1;
79
+ IF c > 10 THEN
80
+ EXIT;
81
+ END IF;
82
+ END LOOP;
83
+
84
+ << labelB >>
85
+ BEGIN
86
+ -- some computations
87
+ IF stocks > 100000 THEN
88
+ EXIT labelB; -- causes exit from BEGIN block
89
+ END IF;
90
+ -- computations here will be skipped when stocks > 100000
91
+ END;
92
+
93
+ LOOP
94
+ -- some computations
95
+ EXIT WHEN count > 100;
96
+ CONTINUE WHEN count < 50;
97
+ -- some computations for count IN [50 .. 100]
98
+ END LOOP;
99
+
100
+ WHILE amount_owed > 0 AND gift_certificate_balance > 0
101
+ LOOP
102
+ -- some computations here
103
+ a := 10;
104
+ END LOOP;
105
+
106
+ << labelC >>
107
+ WHILE NOT done LOOP
108
+ -- some computations here
109
+ a := 10;
110
+ END LOOP labelC;
111
+
112
+ << labelD >>
113
+ WHILE x NOT IN (1,2,3) LOOP
114
+ CONTINUE labelD WHEN x < 10;
115
+ END LOOP;
116
+
117
+ FOR i IN 1..10 LOOP
118
+ --
119
+ END LOOP;
120
+
121
+ << labelD >>
122
+ FOR i IN REVERSE 10..1 LOOP
123
+ --
124
+ EXIT labelD WHEN i = 5;
125
+ END LOOP labelD;
126
+
127
+ << labelE >>
128
+ FOR i IN REVERSE 10..1 BY 2 LOOP
129
+ --
130
+ CONTINUE labelE;
131
+ END LOOP;
132
+
133
+ FOR f IN SELECT *
134
+ FROM foo
135
+ WHERE id > 100
136
+ LOOP
137
+ -- can do some processing home
138
+ RETURN NEXT f; -- return current row of SELECT
139
+ END LOOP;
140
+
141
+ FOR t IN EXECUTE 'SELECT * FROM foo' LOOP
142
+ CONTINUE;
143
+ END LOOP;
144
+
145
+ BEGIN
146
+ UPDATE tab SET fname = 'J' WHERE lname = 'J';
147
+ x := x + 1;
148
+ y := x / 0;
149
+ EXCEPTION
150
+ WHEN SQLSTATE '22012' THEN
151
+ -- relax, don't do it
152
+ WHEN division_by_zero OR unique_violation THEN
153
+ RAISE NOTICE 'caught a fool';
154
+ RETURN x;
155
+ END;
156
+
157
+ END $$
158
+ LANGUAGE 'plpgsql' IMMUTABLE;
@@ -0,0 +1,302 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "expressions" do
5
+ include GrammarHelper
6
+
7
+ describe "expressionUntilSemiColon" do
8
+ it "does not consume semicolon" do
9
+ # parser stops in front of THEN and dies
10
+ lambda{ parse(:expressionUntilSemiColon, 'abc;') }.should raise_error
11
+
12
+ node, rest = parse_some(:expressionUntilSemiColon, 'abc; xyz')
13
+ rest.should == '; xyz'
14
+ end
15
+
16
+ it "can be a blank expression" do
17
+ node, rest = parse_some(:expressionUntilSemiColon, ';')
18
+ node.should be_a(Expression)
19
+ node.source_text.should == ''
20
+ rest.should == ';'
21
+ end
22
+
23
+ it "can be a comment" do
24
+ node, rest = parse_some(:expressionUntilSemiColon, "/* comment */;")
25
+ node.should be_a(Expression)
26
+ node.count{|e| e.is_a?(TComment) }.should == 1
27
+
28
+ node, rest = parse_some(:expressionUntilSemiColon, "-- comment\n;")
29
+ node.should be_a(Expression)
30
+ node.count{|e| e.is_a?(TComment) }.should == 1
31
+ end
32
+
33
+ it "can be a string" do
34
+ node, rest = parse_some(:expressionUntilSemiColon, "'string';")
35
+ node.should be_a(Expression)
36
+ node.count{|e| e.is_a?(TString) }.should == 1
37
+
38
+ node, rest = parse_some(:expressionUntilSemiColon, "$$ string $$;")
39
+ node.should be_a(Expression)
40
+ node.count{|e| e.is_a?(TString) }.should == 1
41
+ end
42
+
43
+ it "can be an arithmetic expression" do
44
+ node, rest = parse_some(:expressionUntilSemiColon, "10 * (3 + x);")
45
+ node.should be_a(Expression)
46
+ end
47
+
48
+ it "can be an SQL statement" do
49
+ node, rest = parse_some(:expressionUntilSemiColon, "SELECT id FROM dataset;")
50
+ node.should be_a(Expression)
51
+ end
52
+
53
+ it "can be an expression with comments embedded" do
54
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
55
+ SELECT id -- primary key ;
56
+ FROM "dataset" /* ; */ -- previous comments shouldn't terminate expression
57
+ WHERE value IS /*NOT*/ NULL;
58
+ SQL
59
+ node.should be_a(Expression)
60
+ node.count{|e| e.is_a?(TComment) }.should == 4
61
+ end
62
+
63
+ it "can be an expression with strings and comments embedded" do
64
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
65
+ SELECT id -- 1. upcoming single quote doesn't matter
66
+ FROM dataset /* 2. this one's no problem either */
67
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
68
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
69
+ /* 4. farewell comment in tail */;
70
+ SQL
71
+ node.should be_a(Expression)
72
+ node.count{|e| e.is_a?(TComment) }.should == 4
73
+ node.count{|e| e.is_a?(TString) }.should == 1
74
+ end
75
+
76
+ it "can be an expression with strings embedded" do
77
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
78
+ SELECT id, created_at
79
+ FROM "dataset"
80
+ WHERE value IS NOT NULL
81
+ AND value <> '; this should not terminate expression'
82
+ AND created_at = '2001-01-01';
83
+ SQL
84
+ node.should be_a(Expression)
85
+ node.count{|e| e.is_a?(TString) }.should == 2
86
+ end
87
+
88
+ it "should combine trailing whitespace into 'tail' node" do
89
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y \t;")
90
+ node.should be_a(Expression)
91
+ node.tail.source_text.should == " \t"
92
+ end
93
+
94
+ it "should combine trailing comments into 'tail' node" do
95
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y /* note -- comment */;")
96
+ node.should be_a(Expression)
97
+ node.tail.source_text.should == ' /* note -- comment */'
98
+
99
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
100
+ SELECT id -- 1. upcoming single quote doesn't matter
101
+ FROM dataset /* 2. this one's no problem either */
102
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
103
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
104
+ /* 4. farewell comment in tail */;
105
+ SQL
106
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
107
+ end
108
+ end
109
+
110
+ describe "expressionUntilThen" do
111
+ it "does not consume THEN token" do
112
+ # parser stops in front of THEN and dies
113
+ lambda{ parse(:expressionUntilThen, 'abc THEN') }.should raise_error
114
+
115
+ node, rest = parse_some(:expressionUntilThen, 'abc THEN xyz')
116
+ rest.should == 'THEN xyz'
117
+ end
118
+
119
+ it "cannot be a blank expression" do
120
+ lambda{ parse_some(:expressionUntilThen, ' THEN') }.should raise_error
121
+ end
122
+
123
+ it "cannot be a comment" do
124
+ lambda{ parse_some(:expressionUntilThen, "/* comment */ THEN") }.should raise_error
125
+ lambda{ parse_some(:expressionUntilThen, "-- comment\n THEN") }.should raise_error
126
+ end
127
+
128
+ it "can be a string" do
129
+ node, rest = parse_some(:expressionUntilThen, "'string' THEN")
130
+ node.should be_a(Expression)
131
+ node.count{|e| e.is_a?(TString) }.should == 1
132
+
133
+ node, rest = parse_some(:expressionUntilThen, "$$ string $$ THEN")
134
+ node.should be_a(Expression)
135
+ node.count{|e| e.is_a?(TString) }.should == 1
136
+ end
137
+
138
+ it "can be an arithmetic expression" do
139
+ node, rest = parse_some(:expressionUntilThen, "10 * (3 + x) THEN")
140
+ node.should be_a(Expression)
141
+ end
142
+
143
+ it "can be an SQL statement" do
144
+ node, rest = parse_some(:expressionUntilThen, "SELECT id FROM dataset THEN")
145
+ node.should be_a(Expression)
146
+ end
147
+
148
+ it "can be an expression with comments embedded" do
149
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
150
+ SELECT id -- primary key THEN
151
+ FROM "dataset" /* THEN */ -- previous comments shouldn't terminate expression
152
+ WHERE value IS /*NOT*/ NULL THEN
153
+ SQL
154
+ node.should be_a(Expression)
155
+ node.count{|e| e.is_a?(TComment) }.should == 4
156
+ end
157
+
158
+ it "can be an expression with strings and comments embedded" do
159
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
160
+ SELECT id -- 1. upcoming single quote doesn't matter
161
+ FROM dataset /* 2. this one's no problem either */
162
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
163
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
164
+ /* 4. farewell comment in tail */ THEN
165
+ SQL
166
+ node.should be_a(Expression)
167
+ node.count{|e| e.is_a?(TComment) }.should == 4
168
+ node.count{|e| e.is_a?(TString) }.should == 1
169
+ end
170
+
171
+ it "can be an expression with strings embedded" do
172
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
173
+ SELECT id, created_at
174
+ FROM "dataset"
175
+ WHERE value IS NOT NULL
176
+ AND value <> ' THEN this should not terminate expression'
177
+ AND created_at = '2001-01-01' THEN
178
+ SQL
179
+ node.should be_a(Expression)
180
+ node.count{|e| e.is_a?(TString) }.should == 2
181
+ end
182
+
183
+ it "should combine trailing whitespace into 'tail' node" do
184
+ node, rest = parse_some(:expressionUntilThen, "a := x + y \tTHEN")
185
+ node.should be_a(Expression)
186
+ node.tail.source_text.should == " \t"
187
+ end
188
+
189
+ it "should combine trailing comments into 'tail' node" do
190
+ node, rest = parse_some(:expressionUntilThen, "a := x + y /* note -- comment */THEN")
191
+ node.should be_a(Expression)
192
+ node.tail.source_text.should == ' /* note -- comment */'
193
+
194
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
195
+ SELECT id -- 1. upcoming single quote doesn't matter
196
+ FROM dataset /* 2. this one's no problem either */
197
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
198
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
199
+ /* 4. farewell comment in tail */THEN
200
+ SQL
201
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
202
+ end
203
+ end
204
+
205
+ describe "expressionUntilLoop" do
206
+ it "does not consume LOOP token" do
207
+ # parser stops in front of LOOP and dies
208
+ lambda{ parse(:expressionUntilLoop, 'abc LOOP') }.should raise_error
209
+
210
+ node, rest = parse_some(:expressionUntilLoop, 'abc LOOP xyz')
211
+ rest.should == 'LOOP xyz'
212
+ end
213
+
214
+ it "cannot be a blank expression" do
215
+ lambda{ parse_some(:expressionUntilLoop, ' LOOP') }.should raise_error
216
+ end
217
+
218
+ it "cannot be a comment" do
219
+ lambda{ parse_some(:expressionUntilLoop, "/* comment */ LOOP") }.should raise_error
220
+ lambda{ parse_some(:expressionUntilLoop, "-- comment\n LOOP") }.should raise_error
221
+ end
222
+
223
+ it "can be a string" do
224
+ node, rest = parse_some(:expressionUntilLoop, "'string' LOOP")
225
+ node.should be_a(Expression)
226
+ node.count{|e| e.is_a?(TString) }.should == 1
227
+
228
+ node, rest = parse_some(:expressionUntilLoop, "$$ string $$ LOOP")
229
+ node.should be_a(Expression)
230
+ node.count{|e| e.is_a?(TString) }.should == 1
231
+ end
232
+
233
+ it "can be an arithmetic expression" do
234
+ node, rest = parse_some(:expressionUntilLoop, "10 * (3 + x) LOOP")
235
+ node.should be_a(Expression)
236
+ end
237
+
238
+ it "can be an SQL statement" do
239
+ node, rest = parse_some(:expressionUntilLoop, "SELECT id FROM dataset LOOP")
240
+ node.should be_a(Expression)
241
+ end
242
+
243
+ it "can be an expression with comments embedded" do
244
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
245
+ SELECT id -- primary key LOOP
246
+ FROM "dataset" /* LOOP */ -- previous comments shouldn't terminate expression
247
+ WHERE value IS /*NOT*/ NULL LOOP
248
+ SQL
249
+ node.should be_a(Expression)
250
+ node.count{|e| e.is_a?(TComment) }.should == 4
251
+ end
252
+
253
+ it "can be an expression with strings and comments embedded" do
254
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
255
+ SELECT id -- 1. upcoming single quote doesn't matter
256
+ FROM dataset /* 2. this one's no problem either */
257
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
258
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
259
+ /* 4. farewell comment in tail */ LOOP
260
+ SQL
261
+ node.should be_a(Expression)
262
+ node.count{|e| e.is_a?(TComment) }.should == 4
263
+ node.count{|e| e.is_a?(TString) }.should == 1
264
+ end
265
+
266
+ it "can be an expression with strings embedded" do
267
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
268
+ SELECT id, created_at
269
+ FROM "dataset"
270
+ WHERE value IS NOT NULL
271
+ AND value <> ' LOOP this should not terminate expression'
272
+ AND created_at = '2001-01-01' LOOP
273
+ SQL
274
+ node.should be_a(Expression)
275
+ node.count{|e| e.is_a?(TString) }.should == 2
276
+ end
277
+
278
+ it "should combine trailing whitespace into 'tail' node" do
279
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y \tLOOP")
280
+ node.should be_a(Expression)
281
+ node.tail.source_text.should == " \t"
282
+ end
283
+
284
+ it "should combine trailing comments into 'tail' node" do
285
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y /* note -- comment */LOOP")
286
+ node.should be_a(Expression)
287
+ node.tail.source_text.should == ' /* note -- comment */'
288
+
289
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
290
+ SELECT id -- 1. upcoming single quote doesn't matter
291
+ FROM dataset /* 2. this one's no problem either */
292
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
293
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
294
+ /* 4. farewell comment in tail */LOOP
295
+ SQL
296
+ node.tail.count{|e| e.is_a?(TComment) }.should == 2
297
+ end
298
+ end
299
+
300
+ end
301
+
302
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe Parser, "statements" do
6
+ include GrammarHelper
7
+
8
+ describe "assignment statements" do
9
+ it "parses successfully" do
10
+ node = parse(:statement, "a := 10;")
11
+ node.should be_a(Statement)
12
+ node.count{|e| e.is_a? Assignment }.should == 1
13
+ node.count{|e| e.is_a? Assignable }.should == 1
14
+ node.find{|e| e.named? :lval }.should be_a(Assignable)
15
+ node.find{|e| e.named? :rval }.should be_a(Expression)
16
+ end
17
+
18
+ it "must end with a semicolon" do
19
+ lambda { parse_some(:statement, 'a := 10') }.should raise_error
20
+ lambda { parse(:statement, 'a := 10') }.should raise_error
21
+ end
22
+
23
+ it "can use := or =" do
24
+ a = parse(:statement, "a := 10;")
25
+ a.should be_a(Statement)
26
+ a.count{|e| e.is_a? Assignment }.should == 1
27
+
28
+ b = parse(:statement, "a = 10;")
29
+ b.should be_a(Statement)
30
+ b.count{|e| e.is_a? Assignment }.should == 1
31
+ end
32
+
33
+ it "can assign strings" do
34
+ node = parse(:statement, "a := 'string';")
35
+ rval = node.find{|e| e.named? :rval }
36
+ rval.count{|e| e.is_a? TString }.should == 1
37
+ end
38
+
39
+ it "can assign value expressions containing comments" do
40
+ ['a := /* comment */ 100;',
41
+ 'a := 100 /* comment */;',
42
+ 'a := 10 /* comment */ + 10;'].test_each do |s|
43
+ node = parse(:statement, s)
44
+ rval = node.find{|e| e.named? :rval }
45
+ rval.count{|e| e.is_a? TComment }.should == 1
46
+ end
47
+ end
48
+
49
+ it "can assign value expressions containing strings" do
50
+ node = parse(:statement, "a := 'Hello,' || space || 'world';")
51
+ rval = node.find{|e| e.named? :rval }
52
+ rval.count{|e| e.is_a? TString }.should == 2
53
+ end
54
+
55
+ it "can assign value expressions containing comments and strings" do
56
+ node = parse(:statement, <<-SQL.strip)
57
+ a := (SELECT fk, count(*), 'not a statement;'
58
+ FROM dataset
59
+ WHERE id > 100 /* filter out 'reserved' range */
60
+ AND id < 900 -- shouldn't be a string
61
+ OR value <> 'alpha /* no comment */;'
62
+ GROUP BY /* pk ; 'abc' */ fk);
63
+ SQL
64
+ rval = node.find{|e| e.named? :rval }
65
+ rval.count{|e| e.is_a? TString }.should == 2
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+ describe Parser, "control structures" do
5
+ include GrammarHelper
6
+
7
+ describe "exceptions" do
8
+ describe "raise" do
9
+ it "parses successfully" do
10
+ node, rest = parse_some(:statement, "RAISE EXCEPTION 'message';")
11
+ node.should be_a(Statement)
12
+ rest.should == ''
13
+ end
14
+
15
+ it "handles exception" do
16
+ node = parse(:statement, "RAISE EXCEPTION 'message';")
17
+ node.count{|e| e.is_a?(Throw) }.should == 1
18
+ node.count{|e| e.is_a?(Raise) }.should == 0
19
+ end
20
+
21
+ it "handles events" do
22
+ %w(WARNING LOG INFO NOTICE DEBUG).each do |event|
23
+ node = parse(:statement, "RAISE #{event} 'message';")
24
+ node.count{|e| e.is_a?(Throw) }.should == 0
25
+ node.count{|e| e.is_a?(Raise) }.should == 1
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "catch" do
31
+ before do
32
+ @text = 'BEGIN a := 10; EXCEPTION WHEN cond THEN b := 10; WHEN cond THEN b := 20; END;'
33
+ end
34
+
35
+ it "parses successfully" do
36
+ node, rest = parse_some(:statement, @text)
37
+ node.should be_a(Statement)
38
+ rest.should == ''
39
+ end
40
+
41
+ it "has Catch node" do
42
+ node = parse(:statement, @text)
43
+ catches = node.select{|e| e.is_a?(Catch) }
44
+ catches.size.should == 2
45
+
46
+ catches[0].count{|e| e.named?(:cond) and e.is_a?(Expression) }.should == 1
47
+ catches[1].count{|e| e.named?(:cond) and e.is_a?(Expression) }.should == 1
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end