วิธีรับบริบทข้อยกเว้นสำหรับข้อยกเว้นที่ยกขึ้นด้วยตนเองใน PL / pgSQL


11

ใน Postgres เราได้รับ "การติดตามสแต็ก" ของข้อยกเว้นโดยใช้รหัสนี้:

EXCEPTION WHEN others THEN
    GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;

มันใช้งานได้ดีสำหรับข้อยกเว้น "ธรรมชาติ" แต่ถ้าเราใช้ข้อยกเว้น

RAISE EXCEPTION 'This is an error!';

... จากนั้นไม่มีการติดตามสแต็ก ตามรายการส่งเมล์นี่อาจเป็นการจงใจแม้ว่าฉันจะทำไม่ได้สำหรับชีวิตของฉันก็หาสาเหตุ มันทำให้ผมอยากจะคิดหาวิธีอื่นที่จะโยนยกเว้นอื่น ๆ RAISEกว่าการใช้ ฉันเพิ่งจะเห็นบางสิ่งบางอย่างที่ชัดเจนหรือไม่? ไม่มีใครมีเคล็ดลับสำหรับเรื่องนี้? มีข้อยกเว้นที่ฉันสามารถให้ Postgres ขว้างได้ซึ่งจะมีสตริงที่ฉันเลือกไว้ดังนั้นฉันจะได้รับไม่เพียง แต่สตริงของฉันในข้อความแสดงข้อผิดพลาด แต่การติดตามสแต็กเต็มเช่นกัน?

นี่คือตัวอย่างเต็มรูปแบบ:

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;

มันอาจเป็นความคิดที่ดีที่จะแสดงตัวอย่างง่ายๆที่นี่
Craig Ringer

จุดดี @ CraigRinger ทำ!
Taytay

มันไม่ได้อยู่ในตัวเอง อะไรนะerror_info? ดูเหมือนว่าเป็นประเภทที่กำหนดเอง
Craig Ringer

ขออภัย - คิดว่าคุณแค่ต้องการบริบททั่วไป ฉันได้ลบสิ่งที่ไม่เกี่ยวข้องออกไป
Taytay

คำตอบ:


9

พฤติกรรมนี้ดูเหมือนจะเกิดจากการออกแบบ

ในsrc/pl/plpgsql/src/pl_exec.cการโทรกลับบริบทข้อผิดพลาดตรวจสอบอย่างชัดเจนเพื่อดูว่ามันถูกเรียกว่าในบริบทของRAISEคำสั่งPL / PgSQL และถ้าเป็นเช่นนั้นข้ามการเปล่งบริบทข้อผิดพลาด:

/*
 * 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;

ฉันไม่พบข้อมูลอ้างอิงที่เฉพาะเจาะจงเกี่ยวกับสาเหตุที่เป็นเช่นนั้น

ภายในเซิร์ฟเวอร์บริบทสแต็กถูกสร้างขึ้นโดยการประมวลผลerror_context_stackซึ่งเป็นการเรียกกลับแบบสายโซ่ที่ผนวกข้อมูลไปยังรายการเมื่อเรียก

เมื่อ PL / PgSQL เข้าสู่ฟังก์ชั่นมันจะเพิ่มรายการไปยังสแต็กการเรียกกลับบริบทข้อผิดพลาด เมื่อออกจากฟังก์ชั่นมันจะลบไอเท็มออกจากสแต็กนั้น

หากฟังก์ชั่นการรายงานข้อผิดพลาดของเซิร์ฟเวอร์ PostgreSQL เช่นereportหรือelogถูกเรียกมันจะเรียกการโทรกลับบริบทข้อผิดพลาด แต่ใน PL / PgSQL ถ้ามันสังเกตเห็นว่ามันถูกเรียกจากการRAISEเรียกกลับของมันโดยไม่ได้ตั้งใจทำอะไร

ระบุว่าฉันไม่เห็นวิธีที่จะบรรลุสิ่งที่คุณต้องการโดยไม่ต้องแก้ไข PostgreSQL ฉันขอแนะนำให้โพสต์จดหมายไปที่ pgsql-General ถามว่าทำไมRAISEไม่ให้บริบทข้อผิดพลาดตอนนี้ที่ PL / PgSQL GET STACKED DIAGNOSTICSต้องใช้มัน

(BTW บริบทข้อยกเว้นไม่ใช่การติดตามสแต็กเช่นนี้ดูเหมือนเล็กน้อยเนื่องจาก PL / PgSQL เพิ่มการเรียกใช้ฟังก์ชันแต่ละครั้งในสแต็ก แต่ใช้สำหรับรายละเอียดอื่น ๆ ในเซิร์ฟเวอร์ด้วย)


ขอบคุณ Craig มากสำหรับคำตอบที่รวดเร็วและละเอียดถี่ถ้วน มันดูแปลกสำหรับฉันและตอบโต้ความคาดหวังของฉันอย่างแน่นอน ประโยชน์ของการRAISEลดลงโดยการตรวจสอบที่ ฉันจะเขียนถึงพวกเขา
Taytay

@Taytay โปรดใส่ลิงก์ไปยังคำถามของคุณที่นี่ แต่ต้องแน่ใจว่าอีเมลของคุณเสร็จสมบูรณ์และสามารถเข้าใจได้โดยไม่ต้องไปที่ลิงก์ หลายคนไม่สนใจลิงค์อย่างเดียวหรือลิงค์ส่วนใหญ่โพสต์ หากคุณได้รับโอกาสที่จะปรากฏลิงค์ไปยังโพสต์ของคุณในความคิดเห็นที่นี่ผ่านarchives.postgresql.orgที่ยอดเยี่ยมจริงๆที่จะช่วยเหลือคนอื่นในภายหลัง
Craig Ringer

ขอบคุณเครก คำปรึกษาที่ดี. ฉันสร้างกระทู้ที่นี่: postgresql.org/message-id/… ณ ตอนนี้พวกเขากำลังมองหาวิธีแก้ไขปัญหาที่ดี
Taytay

6

คุณสามารถหลีกเลี่ยงข้อ จำกัด นี้และทำให้บริบทของข้อผิดพลาด plpgsql ปล่อยตามที่ต้องการโดยการเรียกฟังก์ชั่นอื่นที่เพิ่มขึ้น (คำเตือนการแจ้งเตือน ... ) ข้อผิดพลาดสำหรับคุณ

ฉันโพสต์วิธีแก้ปัญหาเมื่อสองสามปีก่อน - ในหนึ่งในโพสต์แรกของฉันที่ 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;

รายละเอียด:

ฉันขยายกรณีทดสอบที่โพสต์ของคุณเพื่อแสดงว่าทำงานได้ใน Postgres 9.3:

ซอ Fiddle


ขอบคุณมากเออร์วิน! สนุกพอฉันได้ทดลองวิธีแก้ปัญหาของคุณก่อนโพสต์ แต่ฉันต้องทำอะไรผิดและฉันไม่เข้าใจบริบทที่ฉันคาดไว้ ตอนนี้ฉันได้เห็นซอ (ขอบคุณที่แสดงให้ฉันเห็นด้วย) ฉันจะให้มันอีกครั้ง!
Taytay

ทำได้ดีมาก ไม่ควรจำเป็น แต่ดูเหมือนว่ามันจะทำเคล็ดลับ
Craig Ringer

@CraigRinger: เนื่องจากข้อยกเว้นควรเป็นอย่างดีข้อยกเว้นผลกระทบต่อประสิทธิภาพที่น้อยที่สุดก็ไม่สำคัญเช่นกัน เรามีตัวเลือกทั้งหมดด้วยวิธีนี้
Erwin Brandstetter

เห็นด้วยทั้งหมดฉันแค่ต้องการเห็นความจำเป็นในการหลีกเลี่ยงปัญหาในบางจุด
Craig Ringer

@ CraigRinger: จริง หากนั่นจะไม่เกิดขึ้นอีกในเร็ว ๆ นี้เราอาจแนะนำวิธีแก้ปัญหานี้ในคู่มือ ...
Erwin Brandstetter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.