支援的版本:目前 (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 字串文字,以說明您不僅限於使用一種資料或其他資料。

這種在 SQL 陳述式中插入 C 變數的樣式適用於 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 命令中使用的變數可以在這些特殊區段之外正常宣告。

結構或聯合的定義也必須列在 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 節中詳細描述,包含處理這些類型的基本函數,這樣您就不需要僅僅為了將時間間隔加到時間戳記而傳送查詢到 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 型別的值明確地分配記憶體。換句話說,變數的記憶體空間必須在堆積記憶體 (heap memory) 中分配,而不是在堆疊記憶體 (stack memory) 中分配。

以下是一個範例程式

#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 的陣列定義會轉換為每個變數的具名結構 (named struct)。像這樣的宣告

bytea var[180];

會被轉換成

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

成員 arr 存放二進位格式資料。與 VARCHAR 不同,它還可以將 '\0' 作為資料的一部分來處理。資料會從/轉換為十六進位格式,並由 ecpglib 傳送/接收。

注意

只有當 bytea_output 設定為 hex 時,才能使用 bytea 變數。

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 命令中儲存值的 Host 變數。有關結構形式的主機變數的更多詳細資訊,請參閱第 34.4.4.3.2 節。若要切換到結構,可以如下修改範例。兩個 Host 變數 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[] 的 Host 變數中。但是,這樣就無法輕易地從 Host 程式存取值的欄位。

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

ECPG 不直接支援新的使用者定義基本型別。您可以使用外部字串表示法和類型為 char[]VARCHAR[] 的 Host 變數,並且此解決方案對於許多型別來說確實適用且足夠。

以下範例使用 第 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. 指示器 #

以上範例不處理空值。事實上,如果檢索範例從資料庫中提取空值,它們會引發錯誤。為了能夠將空值傳遞到資料庫或從資料庫檢索空值,您需要將第二個 Host 變數規範附加到每個包含資料的 Host 變數。第二個 Host 變數稱為指示器,並且包含一個標誌,該標誌指示資料是否為空值,在這種情況下,實際 Host 變數的值將被忽略。以下是一個正確處理空值檢索的範例

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;

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

指示器具有另一個功能:如果指示器值為正數,則表示該值不是空值,但是在將其儲存在 Host 變數中時被截斷了。

如果將引數 -r no_indicator 傳遞給前處理器 ecpg,則它將在no-indicator模式下工作。在 no-indicator 模式下,如果未指定指示器變數,則對於字串類型,空值將作為空字串發出訊號(在輸入和輸出時),對於整數類型,空值將作為該類型的最低可能值發出訊號(例如,INT_MIN 對於 int)。

提交更正

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