1--
2-- Tests for PL/pgSQL control structures
3--
4
5-- integer FOR loop
6
7do $$
8begin
9  -- basic case
10  for i in 1..3 loop
11    raise notice '1..3: i = %', i;
12  end loop;
13  -- with BY, end matches exactly
14  for i in 1..10 by 3 loop
15    raise notice '1..10 by 3: i = %', i;
16  end loop;
17  -- with BY, end does not match
18  for i in 1..11 by 3 loop
19    raise notice '1..11 by 3: i = %', i;
20  end loop;
21  -- zero iterations
22  for i in 1..0 by 3 loop
23    raise notice '1..0 by 3: i = %', i;
24  end loop;
25  -- REVERSE
26  for i in reverse 10..0 by 3 loop
27    raise notice 'reverse 10..0 by 3: i = %', i;
28  end loop;
29  -- potential overflow
30  for i in 2147483620..2147483647 by 10 loop
31    raise notice '2147483620..2147483647 by 10: i = %', i;
32  end loop;
33  -- potential overflow, reverse direction
34  for i in reverse -2147483620..-2147483647 by 10 loop
35    raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;
36  end loop;
37end$$;
38
39-- BY can't be zero or negative
40do $$
41begin
42  for i in 1..3 by 0 loop
43    raise notice '1..3 by 0: i = %', i;
44  end loop;
45end$$;
46
47do $$
48begin
49  for i in 1..3 by -1 loop
50    raise notice '1..3 by -1: i = %', i;
51  end loop;
52end$$;
53
54do $$
55begin
56  for i in reverse 1..3 by -1 loop
57    raise notice 'reverse 1..3 by -1: i = %', i;
58  end loop;
59end$$;
60
61
62-- CONTINUE statement
63
64create table conttesttbl(idx serial, v integer);
65insert into conttesttbl(v) values(10);
66insert into conttesttbl(v) values(20);
67insert into conttesttbl(v) values(30);
68insert into conttesttbl(v) values(40);
69
70create function continue_test1() returns void as $$
71declare _i integer = 0; _r record;
72begin
73  raise notice '---1---';
74  loop
75    _i := _i + 1;
76    raise notice '%', _i;
77    continue when _i < 10;
78    exit;
79  end loop;
80
81  raise notice '---2---';
82  <<lbl>>
83  loop
84    _i := _i - 1;
85    loop
86      raise notice '%', _i;
87      continue lbl when _i > 0;
88      exit lbl;
89    end loop;
90  end loop;
91
92  raise notice '---3---';
93  <<the_loop>>
94  while _i < 10 loop
95    _i := _i + 1;
96    continue the_loop when _i % 2 = 0;
97    raise notice '%', _i;
98  end loop;
99
100  raise notice '---4---';
101  for _i in 1..10 loop
102    begin
103      -- applies to outer loop, not the nested begin block
104      continue when _i < 5;
105      raise notice '%', _i;
106    end;
107  end loop;
108
109  raise notice '---5---';
110  for _r in select * from conttesttbl loop
111    continue when _r.v <= 20;
112    raise notice '%', _r.v;
113  end loop;
114
115  raise notice '---6---';
116  for _r in execute 'select * from conttesttbl' loop
117    continue when _r.v <= 20;
118    raise notice '%', _r.v;
119  end loop;
120
121  raise notice '---7---';
122  <<looplabel>>
123  for _i in 1..3 loop
124    continue looplabel when _i = 2;
125    raise notice '%', _i;
126  end loop;
127
128  raise notice '---8---';
129  _i := 1;
130  while _i <= 3 loop
131    raise notice '%', _i;
132    _i := _i + 1;
133    continue when _i = 3;
134  end loop;
135
136  raise notice '---9---';
137  for _r in select * from conttesttbl order by v limit 1 loop
138    raise notice '%', _r.v;
139    continue;
140  end loop;
141
142  raise notice '---10---';
143  for _r in execute 'select * from conttesttbl order by v limit 1' loop
144    raise notice '%', _r.v;
145    continue;
146  end loop;
147
148  raise notice '---11---';
149  <<outerlooplabel>>
150  for _i in 1..2 loop
151    raise notice 'outer %', _i;
152    <<innerlooplabel>>
153    for _j in 1..3 loop
154      continue outerlooplabel when _j = 2;
155      raise notice 'inner %', _j;
156    end loop;
157  end loop;
158end; $$ language plpgsql;
159
160select continue_test1();
161
162-- should fail: CONTINUE is only legal inside a loop
163create function continue_error1() returns void as $$
164begin
165    begin
166        continue;
167    end;
168end;
169$$ language plpgsql;
170
171-- should fail: unlabeled EXIT is only legal inside a loop
172create function exit_error1() returns void as $$
173begin
174    begin
175        exit;
176    end;
177end;
178$$ language plpgsql;
179
180-- should fail: no such label
181create function continue_error2() returns void as $$
182begin
183    begin
184        loop
185            continue no_such_label;
186        end loop;
187    end;
188end;
189$$ language plpgsql;
190
191-- should fail: no such label
192create function exit_error2() returns void as $$
193begin
194    begin
195        loop
196            exit no_such_label;
197        end loop;
198    end;
199end;
200$$ language plpgsql;
201
202-- should fail: CONTINUE can't reference the label of a named block
203create function continue_error3() returns void as $$
204begin
205    <<begin_block1>>
206    begin
207        loop
208            continue begin_block1;
209        end loop;
210    end;
211end;
212$$ language plpgsql;
213
214-- On the other hand, EXIT *can* reference the label of a named block
215create function exit_block1() returns void as $$
216begin
217    <<begin_block1>>
218    begin
219        loop
220            exit begin_block1;
221            raise exception 'should not get here';
222        end loop;
223    end;
224end;
225$$ language plpgsql;
226
227select exit_block1();
228
229-- verbose end block and end loop
230create function end_label1() returns void as $$
231<<blbl>>
232begin
233  <<flbl1>>
234  for i in 1 .. 10 loop
235    raise notice 'i = %', i;
236    exit flbl1;
237  end loop flbl1;
238  <<flbl2>>
239  for j in 1 .. 10 loop
240    raise notice 'j = %', j;
241    exit flbl2;
242  end loop;
243end blbl;
244$$ language plpgsql;
245
246select end_label1();
247
248-- should fail: undefined end label
249create function end_label2() returns void as $$
250begin
251  for _i in 1 .. 10 loop
252    exit;
253  end loop flbl1;
254end;
255$$ language plpgsql;
256
257-- should fail: end label does not match start label
258create function end_label3() returns void as $$
259<<outer_label>>
260begin
261  <<inner_label>>
262  for _i in 1 .. 10 loop
263    exit;
264  end loop outer_label;
265end;
266$$ language plpgsql;
267
268-- should fail: end label on a block without a start label
269create function end_label4() returns void as $$
270<<outer_label>>
271begin
272  for _i in 1 .. 10 loop
273    exit;
274  end loop outer_label;
275end;
276$$ language plpgsql;
277
278-- unlabeled exit matches no blocks
279do $$
280begin
281for i in 1..10 loop
282  <<innerblock>>
283  begin
284    begin  -- unlabeled block
285      exit;
286      raise notice 'should not get here';
287    end;
288    raise notice 'should not get here, either';
289  end;
290  raise notice 'nor here';
291end loop;
292raise notice 'should get here';
293end$$;
294
295-- check exit out of an unlabeled block to a labeled one
296do $$
297<<outerblock>>
298begin
299  <<innerblock>>
300  begin
301    <<moreinnerblock>>
302    begin
303      begin  -- unlabeled block
304        exit innerblock;
305        raise notice 'should not get here';
306      end;
307      raise notice 'should not get here, either';
308    end;
309    raise notice 'nor here';
310  end;
311  raise notice 'should get here';
312end$$;
313
314-- check exit out of outermost block
315do $$
316<<outerblock>>
317begin
318  <<innerblock>>
319  begin
320    exit outerblock;
321    raise notice 'should not get here';
322  end;
323  raise notice 'should not get here, either';
324end$$;
325
326-- unlabeled exit does match a while loop
327do $$
328begin
329  <<outermostwhile>>
330  while 1 > 0 loop
331    <<outerwhile>>
332    while 1 > 0 loop
333      <<innerwhile>>
334      while 1 > 0 loop
335        exit;
336        raise notice 'should not get here';
337      end loop;
338      raise notice 'should get here';
339      exit outermostwhile;
340      raise notice 'should not get here, either';
341    end loop;
342    raise notice 'nor here';
343  end loop;
344  raise notice 'should get here, too';
345end$$;
346
347-- check exit out of an unlabeled while to a labeled one
348do $$
349begin
350  <<outerwhile>>
351  while 1 > 0 loop
352    while 1 > 0 loop
353      exit outerwhile;
354      raise notice 'should not get here';
355    end loop;
356    raise notice 'should not get here, either';
357  end loop;
358  raise notice 'should get here';
359end$$;
360
361-- continue to an outer while
362do $$
363declare i int := 0;
364begin
365  <<outermostwhile>>
366  while i < 2 loop
367    raise notice 'outermostwhile, i = %', i;
368    i := i + 1;
369    <<outerwhile>>
370    while 1 > 0 loop
371      <<innerwhile>>
372      while 1 > 0 loop
373        continue outermostwhile;
374        raise notice 'should not get here';
375      end loop;
376      raise notice 'should not get here, either';
377    end loop;
378    raise notice 'nor here';
379  end loop;
380  raise notice 'out of outermostwhile, i = %', i;
381end$$;
382
383-- return out of a while
384create function return_from_while() returns int language plpgsql as $$
385declare i int := 0;
386begin
387  while i < 10 loop
388    if i > 2 then
389      return i;
390    end if;
391    i := i + 1;
392  end loop;
393  return null;
394end$$;
395
396select return_from_while();
397
398-- using list of scalars in fori and fore stmts
399create function for_vect() returns void as $proc$
400<<lbl>>declare a integer; b varchar; c varchar; r record;
401begin
402  -- fori
403  for i in 1 .. 3 loop
404    raise notice '%', i;
405  end loop;
406  -- fore with record var
407  for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
408    raise notice '% % %', r.aa, r.bb, r.cc;
409  end loop;
410  -- fore with single scalar
411  for a in select gs from generate_series(1,4) gs loop
412    raise notice '%', a;
413  end loop;
414  -- fore with multiple scalars
415  for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
416    raise notice '% % %', a, b, c;
417  end loop;
418  -- using qualified names in fors, fore is enabled, disabled only for fori
419  for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
420    raise notice '% % %', a, b, c;
421  end loop;
422end;
423$proc$ language plpgsql;
424
425select for_vect();
426
427-- CASE statement
428
429create or replace function case_test(bigint) returns text as $$
430declare a int = 10;
431        b int = 1;
432begin
433  case $1
434    when 1 then
435      return 'one';
436    when 2 then
437      return 'two';
438    when 3,4,3+5 then
439      return 'three, four or eight';
440    when a then
441      return 'ten';
442    when a+b, a+b+1 then
443      return 'eleven, twelve';
444  end case;
445end;
446$$ language plpgsql immutable;
447
448select case_test(1);
449select case_test(2);
450select case_test(3);
451select case_test(4);
452select case_test(5); -- fails
453select case_test(8);
454select case_test(10);
455select case_test(11);
456select case_test(12);
457select case_test(13); -- fails
458
459create or replace function catch() returns void as $$
460begin
461  raise notice '%', case_test(6);
462exception
463  when case_not_found then
464    raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
465end
466$$ language plpgsql;
467
468select catch();
469
470-- test the searched variant too, as well as ELSE
471create or replace function case_test(bigint) returns text as $$
472declare a int = 10;
473begin
474  case
475    when $1 = 1 then
476      return 'one';
477    when $1 = a + 2 then
478      return 'twelve';
479    else
480      return 'other';
481  end case;
482end;
483$$ language plpgsql immutable;
484
485select case_test(1);
486select case_test(2);
487select case_test(12);
488select case_test(13);
489