@@ -206,3 +206,302 @@ BEGIN
206206 FROM users u
207207 WHERE u .id = p_id;
208208END$$;
209+
210+ -- =============================================================================
211+ -- Edge Case Tests: Nested Block Compositions (END; bug class)
212+ -- These test the exact bug class where END; of a nested block could be
213+ -- confused with statement keywords that follow it.
214+ -- =============================================================================
215+
216+ -- Test 17: Nested block followed by RETURN (the original END; bug pattern)
217+ CREATE FUNCTION test_nested_block_return () RETURNS integer
218+ LANGUAGE plpgsql AS $$
219+ DECLARE
220+ v_result integer ;
221+ BEGIN
222+ BEGIN
223+ v_result := 1 ;
224+ END;
225+ RETURN v_result;
226+ END$$;
227+
228+ -- Test 18: Nested block followed by IF
229+ CREATE FUNCTION test_nested_block_if () RETURNS boolean
230+ LANGUAGE plpgsql AS $$
231+ BEGIN
232+ BEGIN
233+ PERFORM setup_something();
234+ END;
235+ IF FOUND THEN
236+ RETURN TRUE;
237+ END IF;
238+ RETURN FALSE;
239+ END$$;
240+
241+ -- Test 19: Nested block followed by RAISE
242+ CREATE FUNCTION test_nested_block_raise () RETURNS void
243+ LANGUAGE plpgsql AS $$
244+ BEGIN
245+ BEGIN
246+ PERFORM risky_operation();
247+ END;
248+ RAISE NOTICE ' Operation completed' ;
249+ RETURN;
250+ END$$;
251+
252+ -- Test 20: Nested block followed by PERFORM
253+ CREATE FUNCTION test_nested_block_perform () RETURNS integer
254+ LANGUAGE plpgsql AS $$
255+ DECLARE
256+ v_count integer ;
257+ BEGIN
258+ BEGIN
259+ v_count := 42 ;
260+ END;
261+ PERFORM log_result(v_count);
262+ RETURN v_count;
263+ END$$;
264+
265+ -- Test 21: Nested block followed by assignment
266+ CREATE FUNCTION test_nested_block_assign () RETURNS text
267+ LANGUAGE plpgsql AS $$
268+ DECLARE
269+ v_status text ;
270+ BEGIN
271+ BEGIN
272+ PERFORM init();
273+ END;
274+ v_status := ' complete' ;
275+ RETURN v_status;
276+ END$$;
277+
278+ -- Test 22: Labeled nested block
279+ CREATE FUNCTION test_labeled_nested_block () RETURNS boolean
280+ LANGUAGE plpgsql AS $$
281+ BEGIN
282+ << inner>>
283+ BEGIN
284+ PERFORM do_work();
285+ END inner;
286+ RETURN TRUE;
287+ END$$;
288+
289+ -- =============================================================================
290+ -- Edge Case Tests: Blocks Inside Control Structures
291+ -- =============================================================================
292+
293+ -- Test 23: Block inside IF THEN branch with exception handler
294+ CREATE FUNCTION test_block_in_if () RETURNS integer
295+ LANGUAGE plpgsql AS $$
296+ BEGIN
297+ IF 1 > 0 THEN
298+ BEGIN
299+ PERFORM positive_handler();
300+ EXCEPTION
301+ WHEN others THEN
302+ RAISE NOTICE ' error in positive handler' ;
303+ END;
304+ ELSE
305+ RETURN 0 ;
306+ END IF;
307+ RETURN 1 ;
308+ END$$;
309+
310+ -- Test 24: Block inside LOOP body with exception handler
311+ CREATE FUNCTION test_block_in_loop () RETURNS void
312+ LANGUAGE plpgsql AS $$
313+ BEGIN
314+ LOOP
315+ BEGIN
316+ PERFORM process_next();
317+ EXCEPTION
318+ WHEN others THEN
319+ RAISE NOTICE ' skipping bad record' ;
320+ END;
321+ EXIT WHEN NOT FOUND;
322+ END LOOP;
323+ RETURN;
324+ END$$;
325+
326+ -- Test 25: Block inside CASE WHEN
327+ CREATE FUNCTION test_block_in_case (p_status text ) RETURNS void
328+ LANGUAGE plpgsql AS $$
329+ BEGIN
330+ CASE p_status
331+ WHEN ' retry' THEN
332+ BEGIN
333+ PERFORM retry_operation();
334+ EXCEPTION
335+ WHEN others THEN
336+ RAISE EXCEPTION ' retry failed' ;
337+ END;
338+ WHEN ' skip' THEN
339+ RAISE NOTICE ' skipping' ;
340+ END CASE;
341+ RETURN;
342+ END$$;
343+
344+ -- =============================================================================
345+ -- Edge Case Tests: Deep Nesting & Sequential Blocks
346+ -- =============================================================================
347+
348+ -- Test 26: Two sequential nested blocks
349+ CREATE FUNCTION test_sequential_blocks () RETURNS void
350+ LANGUAGE plpgsql AS $$
351+ BEGIN
352+ BEGIN
353+ PERFORM step_one();
354+ END;
355+ BEGIN
356+ PERFORM step_two();
357+ END;
358+ RETURN;
359+ END$$;
360+
361+ -- Test 27: Triple-nested blocks
362+ CREATE FUNCTION test_triple_nested () RETURNS void
363+ LANGUAGE plpgsql AS $$
364+ BEGIN
365+ BEGIN
366+ BEGIN
367+ PERFORM deep_call();
368+ END;
369+ RAISE NOTICE ' middle' ;
370+ END;
371+ RETURN;
372+ END$$;
373+
374+ -- Test 28: Block inside exception handler action
375+ CREATE FUNCTION test_block_in_exception () RETURNS void
376+ LANGUAGE plpgsql AS $$
377+ BEGIN
378+ PERFORM risky();
379+ EXCEPTION
380+ WHEN others THEN
381+ BEGIN
382+ PERFORM log_error();
383+ EXCEPTION
384+ WHEN others THEN
385+ RAISE NOTICE ' even logging failed' ;
386+ END;
387+ END$$;
388+
389+ -- =============================================================================
390+ -- Edge Case Tests: Untested Statement Types
391+ -- =============================================================================
392+
393+ -- Test 29: FOR integer loop
394+ CREATE FUNCTION test_for_integer_loop () RETURNS void
395+ LANGUAGE plpgsql AS $$
396+ BEGIN
397+ FOR i IN 1 ..10 LOOP
398+ PERFORM process(i);
399+ END LOOP;
400+ RETURN;
401+ END$$;
402+
403+ -- Test 30: FOR query loop
404+ CREATE FUNCTION test_for_query_loop () RETURNS void
405+ LANGUAGE plpgsql AS $$
406+ DECLARE
407+ rec record;
408+ BEGIN
409+ FOR rec IN SELECT id, name FROM my_table LOOP
410+ PERFORM handle(rec);
411+ END LOOP;
412+ RETURN;
413+ END$$;
414+
415+ -- Test 31: Labeled FOR loop with EXIT
416+ CREATE FUNCTION test_labeled_for_loop () RETURNS void
417+ LANGUAGE plpgsql AS $$
418+ DECLARE
419+ rec record;
420+ BEGIN
421+ << row_loop>>
422+ FOR rec IN SELECT id, name FROM items LOOP
423+ EXIT row_loop WHEN rec .id IS NULL ;
424+ PERFORM process(rec);
425+ END LOOP row_loop;
426+ RETURN;
427+ END$$;
428+
429+ -- Test 32: RETURN NEXT in set-returning function with OUT parameters
430+ CREATE FUNCTION test_return_next_out (OUT x integer , OUT y text ) RETURNS SETOF record
431+ LANGUAGE plpgsql AS $$
432+ BEGIN
433+ FOR i IN 1 ..5 LOOP
434+ x := i;
435+ y := ' item_' || i::text ;
436+ RETURN NEXT;
437+ END LOOP;
438+ RETURN;
439+ END$$;
440+
441+ -- Test 33: RETURN QUERY
442+ CREATE FUNCTION test_return_query_simple () RETURNS SETOF record
443+ LANGUAGE plpgsql AS $$
444+ BEGIN
445+ RETURN QUERY SELECT id, name FROM my_table WHERE active = TRUE;
446+ END$$;
447+
448+ -- Test 34: ASSERT statement
449+ CREATE FUNCTION test_assert (p_x integer ) RETURNS integer
450+ LANGUAGE plpgsql AS $$
451+ BEGIN
452+ ASSERT p_x > 0 , ' x must be positive' ;
453+ RETURN p_x;
454+ END$$;
455+
456+ -- Test 35: CALL statement
457+ CREATE FUNCTION test_call_statement () RETURNS void
458+ LANGUAGE plpgsql AS $$
459+ BEGIN
460+ CALL my_procedure(1 , ' hello' );
461+ RETURN;
462+ END$$;
463+
464+ -- =============================================================================
465+ -- Edge Case Tests: Real-World Patterns
466+ -- =============================================================================
467+
468+ -- Test 36: Permission bitnum trigger pattern (the function that exposed the END; bug)
469+ CREATE FUNCTION test_permission_bitnum_trigger () RETURNS trigger
470+ LANGUAGE plpgsql AS $$
471+ DECLARE
472+ bitlen int ;
473+ v_len int ;
474+ BEGIN
475+ v_len := 32 ;
476+ BEGIN
477+ bitlen := bit_length(NEW .bitstr );
478+ EXCEPTION
479+ WHEN others THEN
480+ bitlen := 0 ;
481+ END;
482+ IF bitlen = 0 THEN
483+ NEW .bitstr := lpad(' ' , v_len, ' 0' );
484+ END IF;
485+ RETURN NEW;
486+ END$$;
487+
488+ -- Test 37: Multi-step sign-in pattern (deeply nested IF chains)
489+ CREATE FUNCTION test_signin_pattern (v_email text ) RETURNS record
490+ LANGUAGE plpgsql AS $$
491+ DECLARE
492+ v_user record;
493+ v_secret record;
494+ BEGIN
495+ SELECT * INTO v_user FROM users WHERE email = v_email;
496+ IF NOT FOUND THEN
497+ RAISE EXCEPTION ' USER_NOT_FOUND' ;
498+ END IF;
499+ SELECT * INTO v_secret FROM secrets WHERE user_id = v_user .id ;
500+ IF NOT FOUND THEN
501+ RAISE EXCEPTION ' NO_CREDENTIALS' ;
502+ END IF;
503+ IF v_secret .locked_at IS NOT NULL THEN
504+ RAISE EXCEPTION ' ACCOUNT_LOCKED' ;
505+ END IF;
506+ RETURN v_user;
507+ END$$;
0 commit comments