Bagaimana cara mendapatkan konteks pengecualian untuk pengecualian yang dimunculkan secara manual di PL / pgSQL?

11

Di Postgres, kita mendapatkan "stack trace" pengecualian menggunakan kode ini:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

Ini berfungsi dengan baik untuk pengecualian "alami", tetapi jika kita menggunakan pengecualian

RAISE EXCEPTION 'This is an error!';

... maka tidak ada jejak tumpukan. Menurut entri milis , ini mungkin disengaja, meskipun saya tidak bisa seumur hidup mencari tahu mengapa. Itu membuat saya ingin mencari cara lain untuk melemparkan pengecualian selain menggunakan RAISE. Apakah saya hanya melewatkan sesuatu yang jelas? Adakah yang punya trik untuk ini? Apakah ada pengecualian yang bisa membuat Postgres untuk melempar yang berisi string yang saya pilih, sehingga saya tidak hanya akan mendapatkan string saya dalam pesan kesalahan, tetapi jejak stack penuh juga?

Ini contoh lengkapnya:

CREATE OR REPLACE FUNCTION error_test() RETURNS json AS $$
DECLARE
    v_error_stack text;
BEGIN

    -- Comment this out to see how a "normal" exception will give you the stack trace
    RAISE EXCEPTION 'This exception will not get a stack trace';

    -- This will give a divide by zero error, complete with stack trace
    SELECT 1/0;

-- In case of any exception, wrap it in error object and send it back as json
EXCEPTION WHEN others THEN

    -- If the exception we're catching is one that Postgres threw,
    -- like a divide by zero error, then this will get the full
    -- stack trace of the place where the exception was thrown.
    -- However, since we are catching an exception we raised manually
    -- using RAISE EXCEPTION, there is no context/stack trace!
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

    RAISE WARNING 'The stack trace of the error is: "%"', v_error_stack;

    return to_json(v_error_stack);
END;
$$ LANGUAGE plpgsql;
Taytay
sumber
Mungkin ide yang baik untuk menunjukkan contoh sederhana di sini.
Craig Ringer
Poin bagus @CraigRinger. Selesai!
Taytay
Itu tidak mandiri. Apa error_info? Sepertinya jenis khusus.
Craig Ringer
Maaf - pikir Anda hanya ingin konteks umum. Saya telah menghapus hal-hal asing.
Taytay

Jawaban:

9

Perilaku ini tampaknya dirancang.

Dalam src/pl/plpgsql/src/pl_exec.ckonteks kesalahan, callback secara eksplisit memeriksa untuk melihat apakah itu dipanggil dalam konteks pernyataan PL / PgSQL RAISEdan, jika demikian, lewati memancarkan konteks kesalahan:

/*
 * error context callback to let us supply a call-stack traceback
 */
static void
plpgsql_exec_error_callback(void *arg)
{
        PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;

        /* if we are doing RAISE, don't report its location */
        if (estate->err_text == raise_skip_msg)
                return;

Saya tidak dapat menemukan referensi spesifik mengapa itu terjadi.

Secara internal di server, tumpukan konteks dihasilkan dengan memproses error_context_stack, yang merupakan panggilan balik berantai yang menambahkan informasi ke daftar ketika dipanggil.

Ketika PL / PgSQL memasuki fungsi itu menambahkan item ke tumpukan panggilan balik konteks kesalahan. Ketika meninggalkan fungsi, ia menghapus item dari tumpukan itu.

Jika fungsi pelaporan kesalahan server PostgreSQL, suka ereportatau elogdipanggil, itu panggilan balik konteks kesalahan. Tetapi dalam PL / PgSQL jika pemberitahuan bahwa itu dipanggil dari RAISEpanggilan baliknya sengaja tidak melakukan apa-apa.

Mengingat itu, saya tidak melihat cara untuk mencapai apa yang Anda inginkan tanpa menambal PostgreSQL. Saya menyarankan memposting email ke pgsql-general menanyakan mengapa RAISEtidak memberikan konteks kesalahan sekarang karena PL / PgSQL harus GET STACKED DIAGNOSTICSmemanfaatkannya.

(BTW, konteks pengecualian bukan jejak stack seperti itu. Ini terlihat seperti satu karena PL / PgSQL menambahkan setiap panggilan fungsi ke stack, tetapi juga digunakan untuk detail lain di server.)

Craig Ringer
sumber
Terima kasih banyak Craig atas jawaban yang cepat dan menyeluruh. Tampaknya aneh bagi saya, dan tentu saja bertentangan dengan harapan saya. Kegunaan RAISEberkurang oleh cek itu. Saya akan menulis kepada mereka.
Taytay
@Taytay Harap sertakan tautan ke pertanyaan Anda di sini, tetapi pastikan surat Anda lengkap dan dapat dipahami tanpa mengikuti tautan; banyak orang mengabaikan tautan saja atau sebagian besar tautan. Jika Anda mendapat kesempatan untuk melayangkan tautan ke pos Anda di komentar di sini, melalui archives.postgresql.org, itu akan sangat luar biasa untuk membantu orang lain nanti.
Craig Ringer
Terima kasih Craig. Saran yang bagus. Saya membuat utas di sini: postgresql.org/message-id/… Sampai sekarang, mereka sedang mencari solusi yang baik untuk masalah ini.
Taytay
6

Anda dapat mengatasi batasan ini dan membuat konteks galat memancarkan plpgsql seperti yang diinginkan dengan memanggil fungsi lain yang menimbulkan (peringatan, pemberitahuan, ...) kesalahan untuk Anda.

Saya memposting solusi untuk itu beberapa tahun yang lalu - di salah satu posting pertama saya di sini di dba.SE :

-- helper function to raise an exception with CONTEXT
CREATE OR REPLACE FUNCTION f_raise(_lvl text = 'EXCEPTION'
                                  ,_msg text = 'Default error msg.')
  RETURNS void AS
$func$
BEGIN
   CASE upper(_lvl)
      WHEN 'EXCEPTION' THEN RAISE EXCEPTION '%', _msg;
      WHEN 'WARNING'   THEN RAISE WARNING   '%', _msg;
      WHEN 'NOTICE'    THEN RAISE NOTICE    '%', _msg;
      WHEN 'DEBUG'     THEN RAISE DEBUG     '%', _msg;
      WHEN 'LOG'       THEN RAISE LOG       '%', _msg;
      WHEN 'INFO'      THEN RAISE INFO      '%', _msg;
      ELSE RAISE EXCEPTION 'f_raise(): unexpected raise-level: "%"', _lvl;
   END CASE;
END
$func$  LANGUAGE plpgsql STRICT;

Detail:

Saya memperluas test case yang diposting untuk menunjukkan bahwa ia berfungsi di Postgres 9.3:

SQL Fiddle.

Erwin Brandstetter
sumber
Terima kasih banyak, Erwin! Cukup lucu, saya benar-benar bereksperimen dengan solusi Anda sebelum memposting, tetapi saya pasti telah melakukan sesuatu yang salah dan saya tidak mendapatkan konteks yang saya harapkan. Sekarang saya telah melihat biola (terima kasih telah menunjukkan itu juga), saya akan mencobanya lagi!
Taytay
Bagus sekali; seharusnya tidak perlu, tetapi sepertinya itu akan melakukan trik.
Craig Ringer
@CraigRinger: Karena pengecualian seharusnya, yah, pengecualian , dampak kinerja minimal juga tidak masalah. Kami memiliki semua opsi dengan cara ini.
Erwin Brandstetter
Sepenuhnya setuju, saya hanya ingin melihat kebutuhan untuk penyelesaian pergi pada beberapa titik.
Craig Ringer
@CraigRinger: Benar. Jika itu tidak akan terjadi dalam waktu dekat, kami mungkin menyarankan solusi ini dalam manual ...
Erwin Brandstetter