PostgreSQL 9.3を使用して、空のクエリ結果や次のようなものをチェックするためにassert
ヘルパー関数を定義しようとしています。
_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$
;
_
テストの結果、期待したとおりに例外がスローされないことがわかりました。たとえば、CREATE TABLE emptytable (somecolumn text);
および
_CREATE 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$
;
_
SELECT testassert_buggy();
のような呼び出しが例外をスローすることを期待しますが、代わりに結果は
_ somevalue
-----------
(1 row)
_
(firstquery
は実際には0行を返すことに注意してください。1行は、これがout
パラメーターを持つ関数であるためです。)
最後から2行目が次のように少し変更されたため、例外ISがスローされました。
_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$
;
_
テーブルリストを切り替える最後のクエリを書き直した場合(つまり、_FROM firstquery, nonemptycheck
_を使用)、再び例外はありません。私は困惑しています。クエリは、例外などの副作用を無視する方法で最適化されていますか? IMMUTABLE
の定義からassert
を削除しようとしましたが、違いはありませんでした。
参照されていないCTEはまったく実行されません(data-modifyingCTEを除く!)
動作を説明するTom Laneとのpgsql-bugsの関連スレッド
最初の例では、次のようになります。
SELECT * FROM firstquery;
CTE nonemptycheck
への参照はありません。したがって、CTEは実行されません。
2番目の例では、次のようになります。
SELECT firstquery.* FROM nonemptycheck, firstquery;
nonemptycheck
が参照されるため、実行され、例外が発生します。
コメントに追加したテストケースは、同様の理由で失敗します。最初のCTEはno rowを返すため、外側のSELECTはnoを返します行。結果が表示されないため、2番目のCTEを実行する必要はありません。オプティマイザの仕事は、無駄な作業を避けることです。
2番目のCTEではなくクロス結合サブクエリ(CROSS JOIN
またはコンマの後に追加)としてnonemptycheck
を追加すると、は行われませんヘルプ、どちらか。 同様の最適化は実行を回避します:firstquery
は行を返さないため、nonemptycheck
を評価しても意味がありませんサブクエリ内:
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 executed
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 executed
副作用:NULL
が行を返さない場合、これはfirstquery
値で満たされた単一の行を返します。ただし、この特定のケースではありません。この場合、アサートによって例外が発生するためです。