PostgreSQL 使用主變量

2021-09-02 14:18 更新
35.4.1. 概述
35.4.2. 聲明小節(jié)
35.4.3. 檢索查詢結(jié)果
35.4.4. 類型映射
35.4.5. 處理非簡單 SQL 數(shù)據(jù)類型
35.4.6. 指示符

第 35.3 節(jié)中,你了解了如何從一個嵌入式 SQL 程序執(zhí)行 SQL 語句。某些那種語句只使用固定值并且沒有提供方法來插入用戶提供的值到語句中或者讓程序處理查詢返回的值。那種語句在實際應(yīng)用中其實沒有什么用處。這一節(jié)詳細解釋了如何使用一種簡單的機制(主變量)在 C 程序和嵌入式 SQL 語句之間傳遞數(shù)據(jù)。在一個嵌入式 SQL 程序中,我們認為 SQL 語句是 C 程序代碼中的客人,而 C 代碼是主語言。因此 C 程序的變量被稱為主變量

另一種在 PostgreSQL 后端和 ECPG 應(yīng)用之間交換值的方式是使用 SQL 描述符,它在第 35.7 節(jié)中介紹。

35.4.1. 概述

在嵌入式 SQL 中進行 C 程序和 SQL 語句見的數(shù)據(jù)傳遞特別簡單。我們不需要讓程序把數(shù)據(jù)粘貼到語句(這會導(dǎo)致很多復(fù)雜性,例如正確地引用值),我們可以簡單地在 SQL 語句中寫 C 變量的名稱,只要在它前面放上一個冒號。例如:

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

這個語句引用了兩個 C 變量(名為v1v2)并且還使用了一個常規(guī)的 SQL 字符串來說明你沒有被限制于使用某一種數(shù)據(jù)。

這種在 SQL 語句中插入 C 變量的風(fēng)格可以用在 SQL 語句中每一個應(yīng)該出現(xiàn)值表達式的地方。

35.4.2. 聲明小節(jié)

要從程序傳遞數(shù)據(jù)給數(shù)據(jù)庫(例如作為一個查詢的參數(shù))或者從數(shù)據(jù)庫傳數(shù)據(jù)回程序,用于包含這些數(shù)據(jù)的 C 變量必須在特別標(biāo)記的節(jié)中被聲明,這樣嵌入式 SQL 預(yù)處理器才會注意它們。

這個節(jié)開始于:

EXEC SQL BEGIN DECLARE SECTION;

并且結(jié)束于:

EXEC SQL END DECLARE SECTION;

在這兩行之間,必須是正常的 C 變量聲明,例如:

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

如你所見,你可以選擇為變量賦一個初始值。變量的可見范圍由定義它的節(jié)在程序中的位置決定。你也可以使用下面的語法聲明變量,這種語法將會隱式地創(chuàng)建一個聲明節(jié):

EXEC SQL int i = 4;

你可以按照你的意愿在一個程序中放上多個聲明節(jié)。

這些聲明也會作為 C 變量被重復(fù)在輸出文件中,因此無需再次聲明它們。不準(zhǔn)備在 SQL 命令中使用的變量可以正常地在這些特殊節(jié)之外聲明。

一個結(jié)構(gòu)或聯(lián)合的定義也必須被列在一個DECLARE節(jié)中。否則預(yù)處理器無法處理這些類型,因為它不知道它們的定義。

35.4.3. 檢索查詢結(jié)果

現(xiàn)在你應(yīng)該能夠把程序產(chǎn)生的數(shù)據(jù)傳遞到一個 SQL 命令中了。但是怎么檢索一個查詢的結(jié)果呢?為此,嵌入式 SQL 提供了常規(guī)命令SELECTFETCH的特殊變體。這些命令有一個特殊的INTO子句,它指定被檢索到的值要被存儲在哪些主變量中。SELECT被用于只返回單一行的查詢,而 FETCH被用于使用一個游標(biāo)返回多行的查詢。

這里是一個例子:

/*
 * 假定有這個表:
 * 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子句出現(xiàn)在選擇列表和FROM子句之間。選擇列表中的元素數(shù)量必須和INTO后面列表(也被稱為目標(biāo)列表)的元素數(shù)量相等。

這里有一個使用命令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子句出現(xiàn)在所有正常子句的后面。

35.4.4. 類型映射

當(dāng) ECPG 應(yīng)用在 PostgreSQL 服務(wù)器和 C 應(yīng)用之間交換值時(例如從服務(wù)器檢索查詢結(jié)果時或者用輸入?yún)?shù)執(zhí)行 SQL 語句時),值需要在 PostgreSQL 數(shù)據(jù)類型和主語言變量類型(具體來說是 C 語言數(shù)據(jù)類型)之間轉(zhuǎn)換。ECPG 的要點之一就是它會在大多數(shù)情況下自動搞定這種轉(zhuǎn)換。

在這方面有兩類數(shù)據(jù)類型:一些簡單 PostgreSQL 數(shù)據(jù)類型(例如integertext)可以被應(yīng)用直接讀取和寫入。其他 PostgreSQL 數(shù)據(jù)類型(例如timestampnumeric)只能通過特殊庫函數(shù)訪問,見第 35.4.4.2 節(jié)

表 35.1展示了哪種 PostgreSQL 數(shù)據(jù)類型對應(yīng)于哪一種 C 數(shù)據(jù)類型。當(dāng)你希望發(fā)送或接收一種給定 PostgreSQL 數(shù)據(jù)類型的值時,你應(yīng)該在聲明節(jié)中聲明一個具有相應(yīng) C 數(shù)據(jù)類型的 C 變量。

表 35.1. 在 PostgreSQL 數(shù)據(jù)類型和 C 變量類型之間映射

PostgreSQL 數(shù)據(jù)類型 主變量類型
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] 這種類型只能通過特殊的庫函數(shù)訪問,見第 35.4.4.2 節(jié)。

[b] 如果不是本地化類型,則聲明在ecpglib.h


35.4.4.1. 處理字符串

要處理 SQL 字符串?dāng)?shù)據(jù)類型(例如varchar以及text),有兩種可能的方式來聲明主變量。

一種方式是使用char[](一個char字符串),這是在 C 中處理字符數(shù)據(jù)最常見的方式。

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

注意你必須自己照看長度。如果你把這個主變量用作一個查詢的目標(biāo)變量并且該查詢返回超過 49 個字符的字符串,那么將會發(fā)生緩沖區(qū)溢出。

另一種方式是使用VARCHAR類型,它是 ECPG 提供的一種特殊類型。在一個VARCHAR類型數(shù)組上的定義會被轉(zhuǎn)變成一個命名的struct。這樣一個聲明:

VARCHAR var[180];

會被轉(zhuǎn)變成:

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

成員arr容納包含一個終止零字節(jié)的字符串。因此,要在一個VARCHAR主變量中存儲一個字符串,該主變量必須被聲明為具有包括零字節(jié)終止符的長度。成員len保存存儲在arr中的字符串的長度,不包括終止零字節(jié)。當(dāng)一個主變量被用做一個查詢的輸入時,如果 strlen(arr)len不同,將使用短的那一個。

VARCHAR可以被寫成大寫或小寫形式,但是不能大小寫混合。

charVARCHAR主變量也可以保存其他 SQL 類型的值,它們將被存儲為字符串形式。

35.4.4.2. 訪問特殊數(shù)據(jù)類型

ECPG 包含一些特殊類型幫助你容易地與來自 PostgreSQL 服務(wù)器的一些特殊數(shù)據(jù)類型交互。特別地,它已經(jīng)實現(xiàn)了對于numericdecimal、datetimestamp以及interval類型的支持。這些數(shù)據(jù)類型無法有效地被映射到原始的主變量類型(例如 intlong long int或者char[]),因為它們有一種復(fù)雜的內(nèi)部結(jié)構(gòu)。應(yīng)用通過聲明特殊類型的主變量以及使用 pgtypes 庫中的函數(shù)來處理這些類型。pgtypes 庫(在第 35.6 節(jié)中詳細描述)包含了處理這些類型的基本函數(shù),這樣你不需要僅僅為了給一個時間戳增加一個時段而發(fā)送一個查詢給 SQL 服務(wù)器。

下面的小節(jié)描述了這些特殊數(shù)據(jù)類型。關(guān)于 pgtypes 庫函數(shù)的更多細節(jié),請參考第 35.6 節(jié)

35.4.4.2.1. timestamp, date

這里有一種在 ECPG 主應(yīng)用中處理timestamp變量的模式。

首先,程序必須包括用于timestamp類型的頭文件:

#include <pgtypes_timestamp.h>

接著,在聲明節(jié)中聲明一個主變量為類型timestamp

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

并且在讀入一個值到該主變量中之后,使用 pgtypes 庫函數(shù)處理它。在下面的例子中,timestamp值被PGTYPEStimestamp_to_asc()函數(shù)轉(zhuǎn)變成文本(ASCII)形式:

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

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

這個例子將展示像下面形式的一些結(jié)果:

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

另外,DATE 類型可以用相同的方式處理。程序必須包括pgtypes_date.h,聲明一個主變量為日期類型并且將一個 DATE 值使用PGTYPESdate_to_asc()函數(shù)轉(zhuǎn)變成一種文本形式。關(guān)于 pgtypes 庫函數(shù)的更多細節(jié),請參考第 35.6 節(jié)。

35.4.4.2.2. interval

interval類型的處理也類似于timestampdate類型。不過,必須顯式為一個interval類型分配內(nèi)存。換句話說,該變量的內(nèi)存空間必須在堆內(nèi)存中分配,而不是在棧內(nèi)存中分配。

這里是一個例子程序:

#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;
}

35.4.4.2.3. numeric, decimal

numericdecimal類型的處理類似于interval類型:需要定義一個指針、在堆上分配一些內(nèi)存空間并且使用 pgtypes 庫函數(shù)訪問該變量。關(guān)于 pgtypes 庫函數(shù)的更多細節(jié),請參考第 35.6 節(jié)。

pgtypes 庫沒有特別為decimal類型提供函數(shù)。一個應(yīng)用必須使用一個 pgtypes 庫函數(shù)把它轉(zhuǎn)變成一個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));

    /* 將一個decimal轉(zhuǎn)變成numeric以顯示一個decimal值。 */
    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;
}

35.4.4.2.4. bytea

bytea類型的處理與VARCHAR相似。 類型bytea的數(shù)組上的定義被轉(zhuǎn)換為每個變量的命名結(jié)構(gòu)。聲明類似于:

bytea var[180];

is converted into:

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

成員 arr 承載二進制格式數(shù)據(jù)。 不像VARCHAR,它還可以作為數(shù)據(jù)的一部分處理 '\0' 。 數(shù)據(jù)往/來轉(zhuǎn)換為十六進制格式,并通過 ecpglib 發(fā)送/接收。

注意

bytea 變量只有在 bytea_output 被設(shè)置為 hex時才能夠使用.

35.4.4.3. 非簡單類型的主變量

你也可以把數(shù)組、typedefs、結(jié)構(gòu)和指針用作主變量。

35.4.4.3.1. 數(shù)組

將數(shù)組用作主變量有兩種情況。第一種如第 35.4.4.1 節(jié)所述,是一種將一些文本字符串存儲在char[]VARCHAR[]中的方法。第二種是不用一個游標(biāo)從一個查詢結(jié)果中檢索多行。如果沒有一個數(shù)組,要處理由多個行組成的查詢結(jié)果,我們需要使用一個游標(biāo)以及 FETCH命令。但是使用數(shù)組主變量,多個行可以被一次收取。該數(shù)組的長度必須被定義成足以容納所有的行,否則很可能會發(fā)生一次緩沖區(qū)溢出。

下面的例子掃描pg_database系統(tǒng)表并且顯示所有可用數(shù)據(jù)庫的 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;

    /* 一次檢索多行到數(shù)組中。 */
    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;
}

這個例子顯示下面的結(jié)果(確切的值取決于本地環(huán)境)。

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=

35.4.4.3.2. 結(jié)構(gòu)

一個成員名稱匹配查詢結(jié)果列名的結(jié)構(gòu)可以被用來一次檢索多列。該結(jié)構(gòu)使得我們能夠在一個單一主變量中處理多列值。

下面的例子從pg_database系統(tǒng)表以及使用pg_database_size()函數(shù)檢索可用數(shù)據(jù)庫的 OID、名稱和尺寸。在這個例子中,一個成員名匹配SELECT結(jié)果的每一列的結(jié)構(gòu)變量dbinfo_t被用來檢索結(jié)果行,而不需要把多個主變量放在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; /* 在達到結(jié)果集末尾時,跳出 while 循環(huán) */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* 將多列取到一個結(jié)構(gòu)中。 */ EXEC SQL FETCH FROM cur1 INTO :dbval; /* 打印該結(jié)構(gòu)的成員。 */ printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname,
                dbval.size); } EXEC SQL CLOSE cur1;

這個例子會顯示下列結(jié)果(確切的值取決于本地環(huán)境)。

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

結(jié)構(gòu)主變量將列盡數(shù)吸收成結(jié)構(gòu)的域。額外的列可以被分配給其他主變量。例如,上面的程序也可以使用結(jié)構(gòu)外部的size變量重新構(gòu)造:

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;

    /* 在達到結(jié)果集末尾時,跳出 while 循環(huán) */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* 將多列取到一個結(jié)構(gòu)中。 */
        EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

        /* 打印該結(jié)構(gòu)的成員。 */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
    }

    EXEC SQL CLOSE cur1;

35.4.4.3.3. Typedefs

使用typedef關(guān)鍵詞可以把新類型映射到已經(jīng)存在的類型。

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;

這種聲明不需要位于一個聲明節(jié)之中。

35.4.4.3.4. 指針

你可以聲明最常見類型的指針。不過注意,你不能使用指針作為不帶自動分配內(nèi)存的查詢的目標(biāo)變量。關(guān)于自動分配內(nèi)存的詳情請參考第 35.7 節(jié)。

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

35.4.5. 處理非簡單 SQL 數(shù)據(jù)類型

這一節(jié)包含關(guān)于如何處理 ECPG 應(yīng)用中非標(biāo)量以及用戶定義的 SQL 級別數(shù)據(jù)類型。注意這和上一節(jié)中描述的簡單類型主變量的處理有所不同。

35.4.5.1. 數(shù)組

ECPG 中不直接支持 SQL 級別的多維數(shù)組。一維 SQL 數(shù)組可以被映射到 C 數(shù)組主機變量,反之 亦然。不過,在創(chuàng)建一個語句時,ecpg并不知道列的類型,因此它無法檢查一個 C 數(shù)組否是一個 SQL 數(shù)組的輸入。在處理一個 SQL 語句的輸出時,ecpg 有必需的信息并且進而檢查是否兩者都是 數(shù)組。

如果一個查詢個別地訪問一個數(shù)組的元素,那么這可以避免使用 ECPG 中的數(shù)組。然后,應(yīng)該使用一個能被映射到該元素類型的類型的主變量。例如,如果一個列類型是integer數(shù)組,可以使用一個類型int的主變量。還有如果元素類型是varchartext,可以使用一個類型 char[]VARCHAR[]的主變量。

這里是一個例子。假定有下面的表:

CREATE TABLE t3 (
    ii integer[]
);

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

下面的例子程序檢索數(shù)組的第四個元素并且把它存儲到一個類型為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;

這個例子會顯示下面的結(jié)果:

ii=4

要把多個數(shù)組元素映射到一個數(shù)組類型主變量中的多個元素,數(shù)組列的每一個元素以及主變量數(shù)組的每一個元素都必須被單獨管理,例如:

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)
{
    /* 錯誤 */
    EXEC SQL FETCH FROM cur1 INTO :ii_a;
    ...
}

在這種情況中不會正確工作,因為你無法把一個數(shù)組類型列直接映射到一個數(shù)組主變量。

另一種變通方案是在類型char[]VARCHAR[]的主變量中存儲數(shù)組的外部字符串表達。關(guān)于這種表達的詳情請見第 8.15.2 節(jié)。注意這意味著該數(shù)組無法作為一個主程序中的數(shù)組被自然地訪問(沒有解析文本表達的進一步處理)。

35.4.5.2. 組合類型

ECPG 中并不直接支持組合類型,但是有一種可能的簡單變通方案??捎玫淖兺ǚ桨负蜕鲜鲇糜跀?shù)組的方案相似:要么單獨訪問每一個屬性或者使用外部字符串表達。

對于下列例子,假定有下面的類型和表:

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

最顯而易見的解決方案是單獨訪問每一個屬性。下面的程序通過單獨選擇類型comp_t的每一個屬性從例子表中檢索數(shù)據(jù):

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

/* 將組合類型列的每一個元素放在 SELECT 列表中。 */
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)
{
    /* 將組合類型列的每一個元素取到主變量中。 */
    EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

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

EXEC SQL CLOSE cur1;

為了加強這個例子,在FETCH命令中存儲值的主變量可以被集中在一個結(jié)構(gòu)中。結(jié)構(gòu)形式的主變量的詳情可見第 35.4.4.3.2 節(jié)。要切換到結(jié)構(gòu)形式,該例子可以被改成下面的樣子。兩個主變量intvaltextval變成comp_t結(jié)構(gòu)的成員,并且該結(jié)構(gòu)在FETCH命令中指定。

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

comp_t compval;
EXEC SQL END DECLARE SECTION;

/* 將組合類型列的每一個元素放在 SELECT 列表中。 */
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)
{
    /* 將 SELECT 列表中的所有值放入一個結(jié)構(gòu)。 */
    EXEC SQL FETCH FROM cur1 INTO :compval;

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

EXEC SQL CLOSE cur1;

盡管在FETCH命令中使用了一個結(jié)構(gòu),SELECT子句中的屬性名還是要一個一個指定??梢酝ㄟ^使用一個*來要求該組合類型值的所有屬性來改進。

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

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* 將 SELECT 列表中的所有值放入一個結(jié)構(gòu)。 */
    EXEC SQL FETCH FROM cur1 INTO :compval;

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

通過這種方法,即便 ECPG 不理解組合類型本身,組合類型也能夠幾乎無縫地被映射到結(jié)構(gòu)。

最后,也可以在類型char[]VARCHAR[]的主變量中把組合類型值存儲成它們的外部字符串表達。但是如果使用那種方法,就不太可能從主程序中訪問該值的各個域了。

35.4.5.3. 用戶定義的基礎(chǔ)類型

ECPG 并不直接支持新的用戶定義的基本類型。你可以使用外部字符串表達以及類型char[]VARCHAR[]的主變量,并且這種方案事實上對很多類型都是合適和足夠的。

這里有一個使用來自第 37.13 節(jié)中例子里的數(shù)據(jù)類型complex的例子。該類型的外部字符串表達是(%f,%f),它被定義在函數(shù)complex_in()以及第 37.13 節(jié)中的complex_out()函數(shù)內(nèi)。下面的例子把復(fù)雜類型值(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;

這個例子會顯示下列結(jié)果:

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

另一種變通方案是避免在 ECPG 中直接使用用戶定義的類型,而是創(chuàng)建一個在用戶定義的類型和 ECPG 能處理的簡單類型之間轉(zhuǎn)換的函數(shù)或者造型。不過要注意,在類型系統(tǒng)中引入類型造型(特別是隱式造型)要非常小心。

例如,

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)');

相同的效果。

35.4.6. 指示符

上述例子并沒有處理空值。事實上,如果檢索的例子從數(shù)據(jù)庫取到了一個空值,它們將會產(chǎn)生一個錯誤。要能夠向數(shù)據(jù)庫傳遞空值或者從數(shù)據(jù)庫檢索空值,你需要對每一個包含數(shù)據(jù)的主變量追加一個次要主變量說明。這個次要主變量被稱為指示符并且包含一個說明數(shù)據(jù)是否為空的標(biāo)志,如果為空真正的主變量中的值就應(yīng)該被忽略。這里有一個能正確處理檢索空值的例子:

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將為零;否則它將為負值。

指示符有另一種功能:如果指示符值為正,它表示值不為空,但是當(dāng)它被存儲在主變量中時已被截斷。

如果參數(shù)-r no_indicator被傳遞給預(yù)處理器ecpg,它會工作在無指示符模式。在無指示符模式中,如果沒有指定指示符變量,對于字符串類型空值被標(biāo)志(在輸入和輸出上)為空串,對于整數(shù)類型空值被標(biāo)志為類型的最低可能值(例如,int的是 INT_MIN)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號