piggly 1.2.0
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.
- data/README.markdown +84 -0
- data/Rakefile +19 -0
- data/bin/piggly +245 -0
- data/lib/piggly/compiler/cache.rb +151 -0
- data/lib/piggly/compiler/pretty.rb +67 -0
- data/lib/piggly/compiler/queue.rb +46 -0
- data/lib/piggly/compiler/tags.rb +244 -0
- data/lib/piggly/compiler/trace.rb +91 -0
- data/lib/piggly/compiler.rb +5 -0
- data/lib/piggly/config.rb +43 -0
- data/lib/piggly/filecache.rb +40 -0
- data/lib/piggly/installer.rb +95 -0
- data/lib/piggly/parser/grammar.tt +747 -0
- data/lib/piggly/parser/nodes.rb +319 -0
- data/lib/piggly/parser/parser.rb +11783 -0
- data/lib/piggly/parser/traversal.rb +48 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
- data/lib/piggly/parser.rb +67 -0
- data/lib/piggly/profile.rb +87 -0
- data/lib/piggly/reporter/html.rb +207 -0
- data/lib/piggly/reporter/piggly.css +187 -0
- data/lib/piggly/reporter/sortable.js +493 -0
- data/lib/piggly/reporter.rb +21 -0
- data/lib/piggly/task.rb +64 -0
- data/lib/piggly/util.rb +28 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +18 -0
- data/spec/compiler/cache_spec.rb +9 -0
- data/spec/compiler/pretty_spec.rb +9 -0
- data/spec/compiler/queue_spec.rb +3 -0
- data/spec/compiler/rewrite_spec.rb +3 -0
- data/spec/compiler/tags_spec.rb +285 -0
- data/spec/compiler/trace_spec.rb +173 -0
- data/spec/config_spec.rb +58 -0
- data/spec/filecache_spec.rb +70 -0
- data/spec/fixtures/snippets.sql +158 -0
- data/spec/grammar/expression_spec.rb +302 -0
- data/spec/grammar/statements/assignment_spec.rb +70 -0
- data/spec/grammar/statements/exception_spec.rb +52 -0
- data/spec/grammar/statements/if_spec.rb +178 -0
- data/spec/grammar/statements/loop_spec.rb +41 -0
- data/spec/grammar/statements/sql_spec.rb +71 -0
- data/spec/grammar/tokens/comment_spec.rb +58 -0
- data/spec/grammar/tokens/datatype_spec.rb +52 -0
- data/spec/grammar/tokens/identifier_spec.rb +58 -0
- data/spec/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/grammar/tokens/label_spec.rb +40 -0
- data/spec/grammar/tokens/literal_spec.rb +30 -0
- data/spec/grammar/tokens/lval_spec.rb +50 -0
- data/spec/grammar/tokens/number_spec.rb +34 -0
- data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/grammar/tokens/string_spec.rb +54 -0
- data/spec/grammar/tokens/whitespace_spec.rb +40 -0
- data/spec/parser_spec.rb +8 -0
- data/spec/profile_spec.rb +5 -0
- data/spec/reporter/html_spec.rb +0 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/spec_suite.rb +5 -0
- 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
|