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.4. 使用宿主變數 #

第 34.3 節 中,您看到了如何在嵌入式 SQL 程式中執行 SQL 語句。其中一些語句只使用了固定值,並且沒有提供將使用者提供的值插入到語句中或讓程式處理查詢返回的值的方法。這類語句在實際應用程式中用處不大。本節將詳細解釋如何使用一種稱為“宿主變數”的簡單機制在 C 程式和嵌入式 SQL 語句之間傳遞資料。在嵌入式 SQL 程式中,我們將 SQL 語句視為 C 程式程式碼中的“訪客”,而 C 程式程式碼是“宿主語言”。因此,C 程式的變數被稱為“宿主變數”。

在 PostgreSQL 後端和 ECPG 應用程式之間交換值的另一種方法是使用 SQL 描述符,詳見 第 34.7 節

34.4.1. 概述 #

在嵌入式 SQL 中,在 C 程式和 SQL 語句之間傳遞資料非常簡單。您無需讓程式將資料貼上到語句中(這會帶來各種複雜性,例如正確引用值),只需將 C 變數名加冒號字首寫在 SQL 語句中即可。例如:

EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);

該語句引用了名為 v1v2 的兩個 C 變數,並使用了一個普通的 SQL 字串字面量,以說明您不必僅限於使用一種資料型別或另一種資料型別。

這種將 C 變數插入 SQL 語句的風格適用於 SQL 語句中需要值表示式的任何地方。

34.4.2. 宣告段 #

要將資料從程式傳遞到資料庫(例如作為查詢引數),或將資料從資料庫傳遞迴程式,用於儲存這些資料的 C 變數需要在特殊標記的段中宣告,以便嵌入式 SQL 預處理器能夠識別它們。

該段以以下內容開始:

EXEC SQL BEGIN DECLARE SECTION;

並以以下內容結束:

EXEC SQL END DECLARE SECTION;

在這兩行之間,必須是正常的 C 變數宣告,例如:

int   x = 4;
char  foo[16], bar[16];

如您所見,您可以選擇性地為變數分配初始值。變數的範圍由其宣告段在程式中的位置決定。您也可以使用以下語法宣告變數,這將隱式建立一個宣告段:

EXEC SQL int i = 4;

您可以在一個程式中擁有任意數量的宣告段。

宣告也會回顯到輸出檔案中,作為普通的 C 變數,因此無需再次宣告它們。不打算在 SQL 命令中使用但要在程式中使用(例如,為了在 C 程式碼中進行計算)的變數可以在這些特殊段之外正常宣告。

結構體或聯合體的定義也必須包含在 DECLARE 段內。否則,預處理器將無法處理這些型別,因為它不知道其定義。

34.4.3. 檢索查詢結果 #

現在您應該能夠將程式生成的資料傳遞到 SQL 命令中。但是,如何檢索查詢結果呢?為此,嵌入式 SQL 提供了常用命令 SELECTFETCH 的特殊變體。這些命令有一個特殊的 INTO 子句,用於指定檢索到的值要儲存在哪些宿主變數中。SELECT 用於只返回單行的查詢,而 FETCH 用於使用遊標返回多行的查詢。

以下是一個例子

/*
 * assume this table:
 * CREATE TABLE test1 (a int, b varchar(50));
 */

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;

因此,INTO 子句出現在選擇列表和 FROM 子句之間。選擇列表中的元素數量以及 INTO 後面的列表(也稱為目標列表)中的元素數量必須相等。

以下是使用 FETCH 命令的示例:

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL DECLARE foo CURSOR FOR SELECT a, b FROM test;

 ...

do
{
    ...
    EXEC SQL FETCH NEXT FROM foo INTO :v1, :v2;
    ...
} while (...);

這裡,INTO 子句出現在所有常規子句之後。

34.4.4. 型別對映 #

當 ECPG 應用程式在 PostgreSQL 伺服器和 C 應用程式之間交換值時(例如,從伺服器檢索查詢結果或執行帶有輸入引數的 SQL 語句時),需要在 PostgreSQL 資料型別和宿主語言變數型別(具體為 C 語言資料型別)之間進行轉換。ECPG 的一個主要優點是它在大多數情況下會自動處理這些轉換。

在這方面,有兩種資料型別:一些簡單的 PostgreSQL 資料型別,如 integertext,可以由應用程式直接讀寫。其他 PostgreSQL 資料型別,如 timestampnumeric,只能透過特殊的庫函式訪問;參見 第 34.4.4.2 節

表 34.1 顯示了哪些 PostgreSQL 資料型別對應於哪些 C 資料型別。當您希望傳送或接收給定 PostgreSQL 資料型別的值時,應在宣告段中宣告一個對應 C 資料型別的 C 變數。

表 34.1. PostgreSQL 資料型別與 C 變數型別之間的對映

PostgreSQL 資料型別 宿主變數型別
smallint short
integer int
bigint long long int
decimal decimal[a]
numeric numeric[a]
real float
double precision double
smallserial short
serial int
bigserial long long int
oid unsigned int
character(n), varchar(n), text char[n+1], VARCHAR[n+1]
name char[NAMEDATALEN]
timestamp timestamp[a]
interval interval[a]
date date[a]
boolean bool[b]
bytea char *, bytea[n]

[a] 此型別只能透過特殊的庫函式訪問;參見 第 34.4.4.2 節

[b] 如果不是原生型別,則在 ecpglib.h 中宣告


34.4.4.1. 處理字元字串 #

要處理 SQL 字元字串資料型別,如 varchartext,有兩種方法可以宣告宿主變數。

一種方法是使用 char[],即 char 陣列,這是 C 中處理字元資料最常見的方式。

EXEC SQL BEGIN DECLARE SECTION;
    char str[50];
EXEC SQL END DECLARE SECTION;

請注意,您必須自己處理長度。如果您將此宿主變數用作查詢的目標變數,而該查詢返回的字串長度超過 49 個字元,則會發生緩衝區溢位。

另一種方法是使用 VARCHAR 型別,這是 ECPG 提供的一種特殊型別。對於每個變數,VARCHAR 陣列的宣告將被轉換為一個命名的 struct。宣告如:

VARCHAR var[180];

將被轉換為:

struct varchar_var { int len; char arr[180]; } var;

arr 成員包含字串,包括一個終止零位元組。因此,要將字串儲存在 VARCHAR 宿主變數中,宿主變數的宣告長度必須包含零位元組終止符。len 成員儲存 arr 中儲存的字串長度,不包括終止零位元組。當宿主變數用作查詢的輸入時,如果 strlen(arr)len 不同,則使用較小的值。

VARCHAR 可以大寫或小寫書寫,但不能混合大小寫。

charVARCHAR 宿主變數也可以儲存其他 SQL 型別的值,這些值將以其字串形式儲存。

34.4.4.2. 訪問特殊資料型別 #

ECPG 包含一些特殊型別,可以幫助您輕鬆地與 PostgreSQL 伺服器的一些特殊資料型別進行互動。特別是,它實現了對 numericdecimaldatetimestampinterval 型別的支援。這些資料型別不能有效地對映到原生宿主變數型別(如 intlong long intchar[]),因為它們具有複雜的內部結構。應用程式透過宣告特殊型別的宿主變數並使用 pgtypes 庫中的函式來處理這些型別。pgtypes 庫(詳見 第 34.6 節)包含處理這些型別的基礎函式,這樣您就不必僅僅為了將一個 interval 加到一個 timestamp 而傳送一個查詢到 SQL 伺服器。

接下來的小節描述了這些特殊資料型別。有關 pgtypes 庫函式的更多詳細資訊,請參閱 第 34.6 節

34.4.4.2.1. timestamp, date #

以下是在 ECPG 宿主應用程式中處理 timestamp 變數的模式:

首先,程式必須包含 timestamp 型別的標頭檔案:

#include <pgtypes_timestamp.h>

接下來,在宣告段中將宿主變數宣告為 timestamp 型別:

EXEC SQL BEGIN DECLARE SECTION;
timestamp ts;
EXEC SQL END DECLARE SECTION;

在將值讀入宿主變數後,使用 pgtypes 庫函式進行處理。在以下示例中,timestamp 值使用 PGTYPEStimestamp_to_asc() 函式轉換為文字(ASCII)格式:

EXEC SQL SELECT now()::timestamp INTO :ts;

printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));

此示例將顯示類似以下的輸出:

ts = 2010-06-27 18:03:56.949343

此外,DATE 型別也可以以相同的方式處理。程式必須包含 pgtypes_date.h,將宿主變數宣告為 date 型別,並使用 PGTYPESdate_to_asc() 函式將 DATE 值轉換為文字格式。有關 pgtypes 庫函式的更多詳細資訊,請參閱 第 34.6 節

34.4.4.2.2. interval #

處理 interval 型別也與 timestampdate 型別類似。但是,需要顯式地為 interval 型別值分配記憶體。換句話說,變數的記憶體空間必須在堆記憶體中分配,而不是在棧記憶體中分配。

以下是一個示例程式:

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_interval.h>

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    interval *in;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    in = PGTYPESinterval_new();
    EXEC SQL SELECT '1 min'::interval INTO :in;
    printf("interval = %s\n", PGTYPESinterval_to_asc(in));
    PGTYPESinterval_free(in);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}
34.4.4.2.3. numeric, decimal #

處理 numericdecimal 型別與 interval 型別類似:需要定義一個指標,在堆上分配一些記憶體空間,並使用 pgtypes 庫函式訪問變數。有關 pgtypes 庫函式的更多詳細資訊,請參閱 第 34.6 節

沒有專門為 decimal 型別提供函式。應用程式必須使用 pgtypes 庫函式將其轉換為 numeric 變數以進行進一步處理。

以下是一個處理 numericdecimal 型別變數的示例程式:

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_numeric.h>

EXEC SQL WHENEVER SQLERROR STOP;

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    numeric *num;
    numeric *num2;
    decimal *dec;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    num = PGTYPESnumeric_new();
    dec = PGTYPESdecimal_new();

    EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec;

    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2));

    /* Convert decimal to numeric to show a decimal value. */
    num2 = PGTYPESnumeric_new();
    PGTYPESnumeric_from_decimal(dec, num2);

    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2));

    PGTYPESnumeric_free(num2);
    PGTYPESdecimal_free(dec);
    PGTYPESnumeric_free(num);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}
34.4.4.2.4. bytea #

處理 bytea 型別與 VARCHAR 類似。對於每個變數,bytea 陣列的宣告將被轉換為一個命名的 struct。宣告如:

bytea var[180];

將被轉換為:

struct bytea_var { int len; char arr[180]; } var;

arr 成員包含二進位制格式資料。與 VARCHAR 不同,它還可以處理 '\0' 作為資料的一部分。資料以十六進位制格式在 ecpglib 中進行轉換和傳送/接收。

注意

bytea 變數僅在 bytea_output 設定為 hex 時才可使用。

34.4.4.3. 帶有非原生型別的宿主變數 #

您也可以使用陣列、typedef、結構體和指標作為宿主變數。

34.4.4.3.1. 陣列 #

陣列作為宿主變數有兩種用法。第一種是用 char[]VARCHAR[] 儲存文字字串,如 第 34.4.4.1 節中所述。第二種用法是在不使用遊標的情況下從查詢結果中檢索多行。沒有陣列時,要處理由多行組成的查詢結果,需要使用遊標和 FETCH 命令。但使用陣列宿主變數,可以一次性接收多行。陣列的長度必須定義得能夠容納所有行,否則很可能發生緩衝區溢位。

以下示例掃描 pg_database 系統表,並顯示所有可用資料庫的 OID 和名稱:

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    int dbid[8];
    char dbname[8][16];
    int i;
EXEC SQL END DECLARE SECTION;

    memset(dbname, 0, sizeof(char)* 16 * 8);
    memset(dbid, 0, sizeof(int) * 8);

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    /* Retrieve multiple rows into arrays at once. */
    EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;

    for (i = 0; i < 8; i++)
        printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}

此示例顯示以下結果。(具體值取決於本地情況。)

oid=1, dbname=template1
oid=11510, dbname=template0
oid=11511, dbname=postgres
oid=313780, dbname=testdb
oid=0, dbname=
oid=0, dbname=
oid=0, dbname=
34.4.4.3.2. 結構體 #

成員名稱與查詢結果的列名匹配的結構體可以用於一次檢索多個列。結構體允許在單個宿主變數中處理多個列值。

以下示例從 pg_database 系統表中檢索可用資料庫的 OID、名稱和大小,並使用 pg_database_size() 函式。在此示例中,使用名為 dbinfo_t 的結構體變數,其成員名稱與 SELECT 結果中的每個列匹配,以便在 FETCH 語句中不放置多個宿主變數即可檢索一行結果。

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
       long long int size;
    } dbinfo_t;

    dbinfo_t dbval;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size);
    }

    EXEC SQL CLOSE cur1;

此示例顯示以下結果。(具體值取決於本地情況。)

oid=1, datname=template1, size=4324580
oid=11510, datname=template0, size=4243460
oid=11511, datname=postgres, size=4324580
oid=313780, datname=testdb, size=8183012

結構體宿主變數“吸收”結構體中欄位數量的列。額外的列可以分配給其他宿主變數。例如,上面的程式也可以這樣重構,將 size 變數放在結構體之外:

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
    } dbinfo_t;

    dbinfo_t dbval;
    long long int size;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
    }

    EXEC SQL CLOSE cur1;
34.4.4.3.3. Typedefs #

使用 typedef 關鍵字將新型別對映到已存在的型別。

EXEC SQL BEGIN DECLARE SECTION;
    typedef char mychartype[40];
    typedef long serial_t;
EXEC SQL END DECLARE SECTION;

請注意,您也可以使用:

EXEC SQL TYPE serial_t IS long;

此宣告不需要是宣告段的一部分;也就是說,您也可以將 typedef 作為常規 C 語句編寫。

您宣告為 typedef 的任何單詞都不能在同一程式中稍後的 EXEC SQL 命令中用作 SQL 關鍵字。例如,以下程式碼將不起作用:

EXEC SQL BEGIN DECLARE SECTION;
    typedef int start;
EXEC SQL END DECLARE SECTION;
...
EXEC SQL START TRANSACTION;

ECPG 將報告 START TRANSACTION 的語法錯誤,因為它不再將 START 識別為 SQL 關鍵字,而只將其識別為 typedef。(如果您有此類衝突,並且重新命名 typedef 似乎不切實際,您可以嘗試使用 動態 SQL 來編寫 SQL 命令。)

注意

在 v16 之前的 PostgreSQL 版本中,將 SQL 關鍵字用作 typedef 名稱很可能導致與 typedef 本身使用相關的語法錯誤,而不是名稱被用作 SQL 關鍵字。新行為在新 PostgreSQL 版本中,當使用新關鍵字重新編譯現有 ECPG 應用程式時,不太可能導致問題。

34.4.4.3.4. 指標 #

您可以宣告大多數常見型別的指標。但請注意,在沒有自動分配的情況下,您不能使用指標作為查詢的目標變數。有關自動分配的更多資訊,請參閱 第 34.7 節

EXEC SQL BEGIN DECLARE SECTION;
    int   *intp;
    char **charp;
EXEC SQL END DECLARE SECTION;

34.4.5. 處理非原生 SQL 資料型別 #

本節包含關於如何在 ECPG 應用程式中處理非標量和使用者定義 SQL 級別資料型別的資訊。請注意,這與上一節中描述的非原生型別宿主變數的處理不同。

34.4.5.1. 陣列 #

ECPG 不直接支援多維 SQL 級別陣列。一維 SQL 級別陣列可以對映到 C 陣列宿主變數,反之亦然。但是,在建立語句時,ecpg 不知道列的型別,因此無法檢查 C 陣列是否被輸入到相應的 SQL 級別陣列中。在處理 SQL 語句的輸出時,ecpg 擁有必要的資訊,因此會檢查兩者是否都是陣列。

如果查詢單獨訪問陣列的元素,則可以避免在 ECPG 中使用陣列。然後,應使用可以對映到元素型別的宿主變數。例如,如果列型別是 integer 的陣列,則可以使用 int 型別的宿主變數。同樣,如果元素型別是 varchartext,則可以使用 char[]VARCHAR[] 型別的宿主變數。

以下是一個示例。假設有以下表:

CREATE TABLE t3 (
    ii integer[]
);

testdb=> SELECT * FROM t3;
     ii
-------------
 {1,2,3,4,5}
(1 row)

以下示例程式檢索陣列的第 4 個元素並將其儲存到 int 型別的宿主變數中:

EXEC SQL BEGIN DECLARE SECTION;
int ii;
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii ;
    printf("ii=%d\n", ii);
}

EXEC SQL CLOSE cur1;

此示例顯示以下結果:

ii=4

要將多個數組元素對映到陣列型別宿主變數中的多個元素,必須單獨管理陣列列的每個元素和宿主變數陣列的每個元素,例如:

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3];
    ...
}

再次注意:

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* WRONG */
    EXEC SQL FETCH FROM cur1 INTO :ii_a;
    ...
}

在此情況下將無法正確工作,因為您無法直接將陣列型別列對映到陣列宿主變數。

另一種解決方法是將陣列以其外部字串表示形式儲存在 char[]VARCHAR[] 型別的宿主變數中。有關此表示形式的更多詳細資訊,請參閱 第 8.15.2 節。請注意,這意味著在宿主程式中無法自然地(在不進行進一步解析文字表示的處理的情況下)將陣列作為陣列來訪問。

34.4.5.2. 複合型別 #

ECPG 不直接支援複合型別,但有一個簡單的解決方法。可用的解決方法與上面為陣列描述的解決方法類似:要麼單獨訪問每個屬性,要麼使用外部字串表示。

對於以下示例,假設有以下型別和表:

CREATE TYPE comp_t AS (intval integer, textval varchar(32));
CREATE TABLE t4 (compval comp_t);
INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );

最明顯的解決方案是單獨訪問每個屬性。以下程式透過分別選擇 comp_t 型別每個屬性的資料來檢索示例表中的資料:

EXEC SQL BEGIN DECLARE SECTION;
int intval;
varchar textval[33];
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Fetch each element of the composite type column into host variables. */
    EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

    printf("intval=%d, textval=%s\n", intval, textval.arr);
}

EXEC SQL CLOSE cur1;

為了增強此示例,用於在 FETCH 命令中儲存值的宿主變數可以收集到一個結構體中。有關結構體形式宿主變數的更多詳細資訊,請參閱 第 34.4.4.3.2 節。為了切換到結構體,可以修改示例如下:兩個宿主變數 intvaltextval 成為 comp_t 結構體的成員,並將結構體指定給 FETCH 命令。

EXEC SQL BEGIN DECLARE SECTION;
typedef struct
{
    int intval;
    varchar textval[33];
} comp_t;

comp_t compval;
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}

EXEC SQL CLOSE cur1;

儘管 FETCH 命令中使用了結構體,但在 SELECT 子句中仍然逐一指定了屬性名稱。可以透過使用 * 來要求獲取複合型別值的全部屬性來增強這一點。

...
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}
...

這樣,即使 ECPG 不理解複合型別本身,也可以將複合型別幾乎無縫地對映到結構體。

最後,還可以將複合型別值以其外部字串表示形式儲存在 char[]VARCHAR[] 型別的宿主變數中。但這樣,在宿主程式中就無法輕鬆地訪問值的欄位了。

34.4.5.3. 使用者定義基本型別 #

ECPG 不直接支援新的使用者定義基本型別。您可以使用外部字串表示和 char[]VARCHAR[] 型別的宿主變數,對於許多型別來說,這種解決方案確實是合適且充分的。

以下是一個使用 第 36.13 節中示例的 complex 資料型別的示例。該型別的外部字串表示為 (%f,%f),它在 第 36.13 節complex_in()complex_out() 函式中定義。以下示例將複數型別值 (1,1)(3,3) 插入到 ab 列中,然後從表中檢索它們。

EXEC SQL BEGIN DECLARE SECTION;
    varchar a[64];
    varchar b[64];
EXEC SQL END DECLARE SECTION;

    EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)');

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :a, :b;
        printf("a=%s, b=%s\n", a.arr, b.arr);
    }

    EXEC SQL CLOSE cur1;

此示例顯示以下結果:

a=(1,1), b=(3,3)

另一種解決方法是避免在 ECPG 中直接使用使用者定義型別,而是建立一個函式或型別轉換,在使用者定義型別和 ECPG 可以處理的原生型別之間進行轉換。但請注意,型別轉換(尤其是隱式型別轉換)應非常謹慎地引入到型別系統中。

例如,

CREATE FUNCTION create_complex(r double, i double) RETURNS complex
LANGUAGE SQL
IMMUTABLE
AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;

在此定義之後,以下

EXEC SQL BEGIN DECLARE SECTION;
double a, b, c, d;
EXEC SQL END DECLARE SECTION;

a = 1;
b = 2;
c = 3;
d = 4;

EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));

將與以下具有相同效果:

EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');

34.4.6. 指示器 #

前面的示例不處理 NULL 值。事實上,如果檢索示例從資料庫中獲取 NULL 值,將會引發錯誤。為了能夠將 NULL 值傳遞給資料庫或從資料庫中檢索 NULL 值,您需要為每個包含資料的宿主變數附加第二個宿主變數規格。第二個宿主變數稱為指示器,它包含一個標誌,用於指示資料是否為 NULL,在這種情況下,實際宿主變數的值將被忽略。以下是一個正確處理 NULL 值檢索的示例:

EXEC SQL BEGIN DECLARE SECTION;
VARCHAR val;
int val_ind;
EXEC SQL END DECLARE SECTION:

 ...

EXEC SQL SELECT b INTO :val :val_ind FROM test1;

如果值不是 NULL,則指示器變數 val_ind 將為零,如果值是 NULL,則指示器變數將為負數。(請參閱 第 34.16 節以啟用 Oracle 特定行為。)

指示器還有另一個功能:如果指示器值為正,則表示值不是 NULL,但在儲存到宿主變數時被截斷了。

如果將引數 -r no_indicator 傳遞給預處理器 ecpg,它將以“no-indicator”模式工作。在 no-indicator 模式下,如果未指定指示器變數,則 NULL 值將被訊號化(輸入和輸出),對於字元字串型別,為空字串;對於整數型別,則為型別的最小值(例如,對於 int,為 INT_MIN)。

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。