debugsqlMinor
PostgreSQL seems to ignore RAISE EXCEPTION in a CTE
Viewed 0 times
postgresqlignoreexceptioncteseemsraise
Problem
Using PostgreSQL 9.3 I have been trying to define an
Upon testing I found that the exception is not thrown as I would expect. For example, with
I would expect a call like
(Note that
With the following small change in the second-to-last line, the exception IS thrown.
If I rewrite the last query switching the table list (i.e. with
assert helper function to check for empty query results and similar things as follows:CREATE FUNCTION public.assert (
in_assertion boolean,
in_errormessage text
)
RETURNS boolean
IMMUTABLE
LANGUAGE plpgsql
SECURITY INVOKER
AS $function$
BEGIN
IF NOT in_assertion THEN
RAISE EXCEPTION 'assertion failed: %', in_errormessage;
END IF;
RETURN in_assertion;
END;
$function$
;Upon testing I found that the exception is not thrown as I would expect. For example, with
CREATE TABLE emptytable (somecolumn text); andCREATE FUNCTION public.testassert_buggy (
out somevalue text
)
LANGUAGE sql
SECURITY DEFINER
AS $function$
WITH firstquery AS (
SELECT * FROM emptytable
), nonemptycheck AS (
SELECT assert(count(*) = 42, 'nonemptycheck failed') FROM firstquery
) SELECT * FROM firstquery;
$function$
;I would expect a call like
SELECT testassert_buggy(); to throw the exception, but instead the result issomevalue
-----------
(1 row)(Note that
firstquery actually returns 0 rows; the 1 row is due to this being a function with out parameters.)With the following small change in the second-to-last line, the exception IS thrown.
CREATE FUNCTION public.testassert (
out somevalue text
)
LANGUAGE sql
SECURITY DEFINER
AS $function$
WITH firstquery AS (
SELECT * FROM emptytable
), nonemptycheck AS (
SELECT assert(count(*) = 42, 'nonemptycheck failed') FROM firstquery
) SELECT firstquery.* FROM nonemptycheck, firstquery;
$function$
;If I rewrite the last query switching the table list (i.e. with
FROM firstquery, nonemptycheck) there is again no exception. I'm puzzled. Is the query optimized in some way that ignores side-effects like exceptions? I tried to remove IMMUTABLE from the definition of assert, but that didn't make a difference.Solution
Unreferenced CTEs are not executed at all - except data-modifying CTEs.
Related thread on pgsql-bugs with Tom Lane explaining the behavior.
In your first example you have:
No reference to the CTE
In the second example you have:
No row
Your added test case in the comment fails for a similar reason. Since the first CTE returns no row, the outer SELECT returns no row. There is no need to execute the second CTE, since the result will not be displayed. The optimizer's job is to avoid fruitless work ..
Appending
Solution
You can force evaluation with a
Side effect: This would return a single row filled with
Related thread on pgsql-bugs with Tom Lane explaining the behavior.
In your first example you have:
SELECT * FROM firstquery;No reference to the CTE
nonemptycheck. So the CTE is never executed.In the second example you have:
SELECT firstquery.* FROM nonemptycheck, firstquery;nonemptycheck is referenced, so it is executed, resulting in the exception.No row
Your added test case in the comment fails for a similar reason. Since the first CTE returns no row, the outer SELECT returns no row. There is no need to execute the second CTE, since the result will not be displayed. The optimizer's job is to avoid fruitless work ..
Appending
nonemptycheck as cross-joined subquery (CROSS JOIN or appended after comma) instead of a second CTE does not help, either. A similar optimization avoids execution: Since firstquery returns no row, there is no point in evaluating the nonemptycheck, even in a subquery:WITH firstquery AS (
SELECT *
FROM emptytable
WHERE FALSE
)
SELECT f.* -- even if you append ", n.*" to SELECT list
FROM firstquery f
, (
SELECT assert(count(*) = 42, 'check failed')
FROM firstquery
) n; -- not executedSolution
You can force evaluation with a
FULL OUTER JOIN:WITH firstquery AS (
SELECT *
FROM emptytable
WHERE false
)
SELECT f.*
FROM firstquery f
FULL JOIN (
SELECT assert(count(*) = 42, 'check failed')
FROM firstquery
) nonemptycheck ON true; -- always executedSide effect: This would return a single row filled with
NULL values, when firstquery returns no row. Not in this particular case, though, since your assert raises an exception in this case.Code Snippets
SELECT * FROM firstquery;SELECT firstquery.* FROM nonemptycheck, firstquery;WITH firstquery AS (
SELECT *
FROM emptytable
WHERE FALSE
)
SELECT f.* -- even if you append ", n.*" to SELECT list
FROM firstquery f
, (
SELECT assert(count(*) = 42, 'check failed')
FROM firstquery
) n; -- not executedWITH firstquery AS (
SELECT *
FROM emptytable
WHERE false
)
SELECT f.*
FROM firstquery f
FULL JOIN (
SELECT assert(count(*) = 42, 'check failed')
FROM firstquery
) nonemptycheck ON true; -- always executedContext
StackExchange Database Administrators Q#69648, answer score: 9
Revisions (0)
No revisions yet.