2025年9月25日: PostgreSQL 18 釋出!
支援的版本:當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

34.8. 錯誤處理 #

本節描述瞭如何在嵌入式 SQL 程式中處理異常情況和警告。為此提供了兩種非互斥的機制。

  • 可以使用 WHENEVER 命令配置回撥函式來處理警告和錯誤情況。
  • 可以從 sqlca 變數獲取有關錯誤或警告的詳細資訊。

34.8.1. 設定回撥函式 #

捕獲錯誤和警告的一種簡單方法是在發生特定條件時設定要執行的特定操作。通常

EXEC SQL WHENEVER condition action;

condition 可以是以下之一:

SQLERROR #

每當 SQL 語句執行過程中發生錯誤時,都會呼叫指定的操作。

SQLWARNING #

每當 SQL 語句執行過程中發生警告時,都會呼叫指定的操作。

NOT FOUND #

每當 SQL 語句檢索或影響的行數為零時,都會呼叫指定的操作。(此條件不是錯誤,但您可能希望特別處理它。)

action 可以是以下之一:

CONTINUE #

這實際上意味著該條件被忽略。這是預設設定。

GOTO label
GO TO label #

跳轉到指定的標籤(使用 C goto 語句)。

SQLPRINT #

列印一條訊息到標準錯誤。這對於簡單的程式或在原型設計期間很有用。訊息的詳細資訊無法配置。

STOP #

呼叫 exit(1),這將終止程式。

DO BREAK #

執行 C 語句 break。這隻能在迴圈或 switch 語句中使用。

DO CONTINUE #

執行 C 語句 continue。這隻能在迴圈語句中使用。如果執行,將導致控制流返回到迴圈的頂部。

CALL name (args)
DO name (args) #

使用指定的引數呼叫指定的 C 函式。(此用法不同於正常 PostgreSQL 語法中的 CALLDO 的含義。)

SQL 標準僅提供 CONTINUEGOTO(以及 GO TO)操作。

這是一個您可能想在簡單程式中使用的示例。它在發生警告時列印一條簡單的訊息,並在發生錯誤時中止程式。

EXEC SQL WHENEVER SQLWARNING SQLPRINT;
EXEC SQL WHENEVER SQLERROR STOP;

語句 EXEC SQL WHENEVER 是 SQL 預處理器的一個指令,而不是 C 語句。它設定的錯誤或警告操作適用於設定處理程式點以下的所有嵌入式 SQL 語句,除非在第一個 EXEC SQL WHENEVER 和導致條件的 SQL 語句之間為同一條件設定了不同的操作,而與 C 程式中的控制流無關。因此,以下兩個 C 程式片段都不會產生預期的效果。

/*
 * WRONG
 */
int main(int argc, char *argv[])
{
    ...
    if (verbose) {
        EXEC SQL WHENEVER SQLWARNING SQLPRINT;
    }
    ...
    EXEC SQL SELECT ...;
    ...
}
/*
 * WRONG
 */
int main(int argc, char *argv[])
{
    ...
    set_error_handler();
    ...
    EXEC SQL SELECT ...;
    ...
}

static void set_error_handler(void)
{
    EXEC SQL WHENEVER SQLERROR STOP;
}

34.8.2. sqlca #

為了更強大的錯誤處理,嵌入式 SQL 介面提供了一個名為 sqlca(SQL 通訊區)的全域性變數,其結構如下:

struct
{
    char sqlcaid[8];
    long sqlabc;
    long sqlcode;
    struct
    {
        int sqlerrml;
        char sqlerrmc[SQLERRMC_LEN];
    } sqlerrm;
    char sqlerrp[8];
    long sqlerrd[6];
    char sqlwarn[8];
    char sqlstate[5];
} sqlca;

(在多執行緒程式中,每個執行緒會自動獲得自己的 sqlca 副本。這類似於對標準 C 全域性變數 errno 的處理。)

sqlca 涵蓋警告和錯誤。如果在語句執行期間發生多個警告或錯誤,則 sqlca 只會包含最後一個的資訊。

如果在最後一個SQL語句中沒有發生錯誤,則 sqlca.sqlcode 將為 0,sqlca.sqlstate 將為 "00000"。如果發生警告或錯誤,則 sqlca.sqlcode 將為負數,sqlca.sqlstate 將與 "00000" 不同。正數的 sqlca.sqlcode 表示無害的條件,例如最後一個查詢返回了零行。 sqlcodesqlstate 是兩種不同的錯誤程式碼方案;詳細資訊如下。

如果最後一個 SQL 語句成功,則 sqlca.sqlerrd[1] 包含所處理行的 OID(如果適用),並且 sqlca.sqlerrd[2] 包含所處理或返回的行數(如果適用於命令)。

在發生錯誤或警告時,sqlca.sqlerrm.sqlerrmc 將包含描述錯誤的字串。sqlca.sqlerrm.sqlerrml 欄位包含儲存在 sqlca.sqlerrm.sqlerrmc 中的錯誤訊息的長度(strlen() 的結果,對 C 程式設計師來說並不真正重要)。請注意,有些訊息太長,無法放入固定大小的 sqlerrmc 陣列;它們將被截斷。

在發生警告時,sqlca.sqlwarn[2] 被設定為 W。(在所有其他情況下,它被設定為不同於 W 的值。)如果 sqlca.sqlwarn[1] 被設定為 W,則值在儲存到宿主變數時被截斷。sqlca.sqlwarn[0] 被設定為 W,如果任何其他元素被設定為表示警告。

欄位 sqlcaidsqlabcsqlerrp 以及 sqlerrdsqlwarn 的其餘元素目前不包含有用的資訊。

結構 sqlca 未在 SQL 標準中定義,但在其他幾個 SQL 資料庫系統中實現。核心定義相似,但如果您想編寫可移植的應用程式,則應仔細研究不同的實現。

這是一個結合使用 WHENEVERsqlca 的示例,在發生錯誤時列印 sqlca 的內容。在安裝更“使用者友好”的錯誤處理程式之前,這可能有助於除錯或原型設計應用程式。

EXEC SQL WHENEVER SQLERROR CALL print_sqlca();

void
print_sqlca()
{
    fprintf(stderr, "==== sqlca ====\n");
    fprintf(stderr, "sqlcode: %ld\n", sqlca.sqlcode);
    fprintf(stderr, "sqlerrm.sqlerrml: %d\n", sqlca.sqlerrm.sqlerrml);
    fprintf(stderr, "sqlerrm.sqlerrmc: %s\n", sqlca.sqlerrm.sqlerrmc);
    fprintf(stderr, "sqlerrd: %ld %ld %ld %ld %ld %ld\n", sqlca.sqlerrd[0],sqlca.sqlerrd[1],sqlca.sqlerrd[2],
                                                          sqlca.sqlerrd[3],sqlca.sqlerrd[4],sqlca.sqlerrd[5]);
    fprintf(stderr, "sqlwarn: %d %d %d %d %d %d %d %d\n", sqlca.sqlwarn[0], sqlca.sqlwarn[1], sqlca.sqlwarn[2],
                                                          sqlca.sqlwarn[3], sqlca.sqlwarn[4], sqlca.sqlwarn[5],
                                                          sqlca.sqlwarn[6], sqlca.sqlwarn[7]);
    fprintf(stderr, "sqlstate: %5s\n", sqlca.sqlstate);
    fprintf(stderr, "===============\n");
}

結果可能如下所示(此處是由於表名拼寫錯誤導致的錯誤):

==== sqlca ====
sqlcode: -400
sqlerrm.sqlerrml: 49
sqlerrm.sqlerrmc: relation "pg_databasep" does not exist on line 38
sqlerrd: 0 0 0 0 0 0
sqlwarn: 0 0 0 0 0 0 0 0
sqlstate: 42P01
===============

34.8.3. SQLSTATESQLCODE #

欄位 sqlca.sqlstatesqlca.sqlcode 是提供錯誤程式碼的兩種不同方案。兩者都源自 SQL 標準,但 SQLCODE 在 SQL-92 標準版中已被標記為不推薦使用,並在後續版本中被刪除。因此,強烈鼓勵新應用程式使用 SQLSTATE

SQLSTATE 是一個五字元陣列。這五個字元包含數字或大寫字母,代表各種錯誤和警告條件的編碼。 SQLSTATE 具有分層方案:前兩個字元表示條件的通用類別,後三個字元表示通用條件的子類別。成功狀態由程式碼 00000 表示。 SQLSTATE 程式碼在很大程度上由 SQL 標準定義。 PostgreSQL 伺服器原生支援 SQLSTATE 錯誤程式碼;因此,透過在所有應用程式中使用此錯誤程式碼方案,可以實現高度的一致性。有關更多資訊,請參見 附錄 A

SQLCODE 是已棄用的錯誤程式碼方案,它是一個簡單的整數。值為 0 表示成功,正值表示成功並帶有附加資訊,負值表示錯誤。SQL 標準僅定義了正值 +100,它表示最後一個命令返回或影響了零行,沒有特定的負值。因此,該方案只能實現較差的可移植性,並且沒有分層程式碼分配。歷史上,PostgreSQL 的嵌入式 SQL 處理器為其使用分配了一些特定的 SQLCODE 值,這些值與它們的數值和符號名稱一起列出如下。請記住,這些不能移植到其他 SQL 實現。為了簡化應用程式向 SQLSTATE 方案的移植,也列出了相應的 SQLSTATE。但是,這兩種方案之間沒有一對一或一對多的對映(事實上是多對多),因此您應該在每種情況下查閱 附錄 A 中的全域性 SQLSTATE 列表。

這些是分配的 SQLCODE 值:

0 (ECPG_NO_ERROR) #

表示沒有錯誤。(SQLSTATE 00000)

100 (ECPG_NOT_FOUND) #

這是一個無害的條件,表示最後一個命令檢索或處理了零行,或者您已到達遊標的末尾。(SQLSTATE 02000)

在迴圈中處理遊標時,您可以使用此程式碼來檢測何時中止迴圈,如下所示:

while (1)
{
    EXEC SQL FETCH ... ;
    if (sqlca.sqlcode == ECPG_NOT_FOUND)
        break;
}

但是 WHENEVER NOT FOUND DO BREAK 在內部有效地完成了此操作,因此通常沒有好處會顯式寫出此程式碼。

-12 (ECPG_OUT_OF_MEMORY) #

表示您的虛擬記憶體已耗盡。數值定義為 -ENOMEM。(SQLSTATE YE001)

-200 (ECPG_UNSUPPORTED) #

表示預處理器生成了庫不知道的內容。您可能正在執行不相容版本的預處理器和庫。(SQLSTATE YE002)

-201 (ECPG_TOO_MANY_ARGUMENTS) #

這意味著命令指定的宿主變數多於命令期望的。(SQLSTATE 07001 或 07002)

-202 (ECPG_TOO_FEW_ARGUMENTS) #

這意味著命令指定的宿主變數少於命令期望的。(SQLSTATE 07001 或 07002)

-203 (ECPG_TOO_MANY_MATCHES) #

這意味著查詢返回了多行,但語句僅準備好儲存一行結果(例如,因為指定的變數不是陣列)。(SQLSTATE 21000)

-204 (ECPG_INT_FORMAT) #

宿主變數的型別為 int,資料庫中的資料型別不同,並且包含的值無法解釋為 int。庫使用 strtol() 進行轉換。(SQLSTATE 42804)

-205 (ECPG_UINT_FORMAT) #

宿主變數的型別為 unsigned int,資料庫中的資料型別不同,並且包含的值無法解釋為 unsigned int。庫使用 strtoul() 進行轉換。(SQLSTATE 42804)

-206 (ECPG_FLOAT_FORMAT) #

宿主變數的型別為 float,資料庫中的資料型別不同,並且包含的值無法解釋為 float。庫使用 strtod() 進行轉換。(SQLSTATE 42804)

-207 (ECPG_NUMERIC_FORMAT) #

宿主變數的型別為 numeric,資料庫中的資料型別不同,並且包含的值無法解釋為 numeric 值。(SQLSTATE 42804)

-208 (ECPG_INTERVAL_FORMAT) #

宿主變數的型別為 interval,資料庫中的資料型別不同,並且包含的值無法解釋為 interval 值。(SQLSTATE 42804)

-209 (ECPG_DATE_FORMAT) #

宿主變數的型別為 date,資料庫中的資料型別不同,並且包含的值無法解釋為 date 值。(SQLSTATE 42804)

-210 (ECPG_TIMESTAMP_FORMAT) #

宿主變數的型別為 timestamp,資料庫中的資料型別不同,並且包含的值無法解釋為 timestamp 值。(SQLSTATE 42804)

-211 (ECPG_CONVERT_BOOL) #

這意味著宿主變數的型別為 bool,資料庫中的資料既不是 't' 也不是 'f'。(SQLSTATE 42804)

-212 (ECPG_EMPTY) #

傳送到 PostgreSQL 伺服器的語句是空的。(這通常不會在嵌入式 SQL 程式中發生,所以可能指向內部錯誤。)(SQLSTATE YE002)

-213 (ECPG_MISSING_INDICATOR) #

返回了一個 NULL 值,並且沒有提供 NULL 指示符變數。(SQLSTATE 22002)

-214 (ECPG_NO_ARRAY) #

在一個需要陣列的地方使用了普通變數。(SQLSTATE 42804)

-215 (ECPG_DATA_NOT_ARRAY) #

資料庫在需要陣列值的地方返回了一個普通變數。(SQLSTATE 42804)

-216 (ECPG_ARRAY_INSERT) #

值無法插入到陣列中。(SQLSTATE 42804)

-220 (ECPG_NO_CONN) #

程式試圖訪問一個不存在的連線。(SQLSTATE 08003)

-221 (ECPG_NOT_CONN) #

程式試圖訪問一個存在的但未開啟的連線。(這是一個內部錯誤。)(SQLSTATE YE002)

-230 (ECPG_INVALID_STMT) #

您試圖使用的語句尚未準備好。(SQLSTATE 26000)

-239 (ECPG_INFORMIX_DUPLICATE_KEY) #

重複鍵錯誤,違反唯一約束(Informix 相容模式)。(SQLSTATE 23505)

-240 (ECPG_UNKNOWN_DESCRIPTOR) #

找不到指定的描述符。您試圖使用的語句尚未準備好。(SQLSTATE 33000)

-241 (ECPG_INVALID_DESCRIPTOR_INDEX) #

指定的描述符索引超出範圍。(SQLSTATE 07009)

-242 (ECPG_UNKNOWN_DESCRIPTOR_ITEM) #

請求了無效的描述符項。(這是一個內部錯誤。)(SQLSTATE YE002)

-243 (ECPG_VAR_NOT_NUMERIC) #

在執行動態語句期間,資料庫返回了一個數值,而宿主變數不是數值。(SQLSTATE 07006)

-244 (ECPG_VAR_NOT_CHAR) #

在執行動態語句期間,資料庫返回了一個非數值,而宿主變數是數值。(SQLSTATE 07006)

-284 (ECPG_INFORMIX_SUBSELECT_NOT_ONE) #

子查詢的結果不是單行(Informix 相容模式)。(SQLSTATE 21000)

-400 (ECPG_PGSQL) #

PostgreSQL 伺服器引起的一些錯誤。訊息包含來自 PostgreSQL 伺服器的錯誤訊息。

-401 (ECPG_TRANS) #

PostgreSQL 伺服器發出訊號,表示我們無法開始、提交或回滾事務。(SQLSTATE 08007)

-402 (ECPG_CONNECT) #

連線到資料庫的嘗試未成功。(SQLSTATE 08001)

-403 (ECPG_DUPLICATE_KEY) #

重複鍵錯誤,違反唯一約束。(SQLSTATE 23505)

-404 (ECPG_SUBSELECT_NOT_ONE) #

子查詢的結果不是單行。(SQLSTATE 21000)

-602 (ECPG_WARNING_UNKNOWN_PORTAL) #

指定了無效的游標名稱。(SQLSTATE 34000)

-603 (ECPG_WARNING_IN_TRANSACTION) #

事務正在進行中。(SQLSTATE 25001)

-604 (ECPG_WARNING_NO_TRANSACTION) #

沒有活動的(正在進行的)事務。(SQLSTATE 25P01)

-605 (ECPG_WARNING_PORTAL_EXISTS) #

指定了已有的游標名稱。(SQLSTATE 42P03)

提交更正

如果您在文件中發現任何不正確之處,與您對特定功能的體驗不符,或者需要進一步說明,請使用 此表格 報告文件問題。