支援的版本:目前 (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. SQLSTATE vs. SQLCODE #

欄位 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)

提交更正

如果您在文件中發現任何不正確、與您特定功能的經驗不符或需要進一步澄清的地方,請使用此表單來回報文件問題。