33.16. Oracle 兼容模式

33.16.1. oracle-兼容的字符串
33.16.2. Oracle-兼容的 SQLDA 描述符区域
33.16.3. 附加函数

ecpg(Oracle Pro*c兼容) 可以在所谓的 Oracle compatibility mode 下运行。 如果该模式处于活动状态,则它会尝试表现得像 Oracle 的 Pro*C 一样。

33.16.1. oracle-兼容的字符串

具体来说,此模式通过三种方式更改 ecpg(Oracle Pro*c兼容)

  • 填充字符数组,用于接收具有指定长度的尾随空格的字符串类型

  • 零字节终止这些字符数组,并在发生截断时设置指示符变量

  • 当字符数组接收到空字符串类型时,将null指示符设置为-1

在Oracle兼容模式下,支持从数据库读取数据到一个char类型变量中。

33.16.2. Oracle-兼容的 SQLDA 描述符区域

Oracle-兼容模式支持一种与Section 33.7.2中所述不同的结构。如下:

  struct SQLDA
  {
    int     N; /* Number of entries             */
    char  **V; /* Variables                 */
    int    *L; /* Variable lengths          */
    short  *T; /* Variable types            */
    short **I; /* Indicators                */
    int     F; /* Count of variables discovered by DESCRIBE */
    char  **S; /* Variable names            */
    short  *M; /* Variable name maximum lengths     */
    short  *C; /* Variable name actual lengths  */
    char  **X; /* Indicator names           */
    short  *Y; /* Indicator name maximum lengths */
    short  *Z; /* Indicator name actual lengths     */
  };
  

以下是每个描述符变量的说明:

N 变量

  • 用途: 指定可以描述的选择列表项或占位符的最大数量。

  • 使用方法:

    • 在 DESCRIBE 之前: 使用 sqlald() 设置 N 为描述符数组的维度。

    • 在 DESCRIBE 之后: 将 N 重置为实际描述的变量数量,该数量存储在 F 中。

V 变量

  • 用途: 指向存储选择列表或绑定变量值的数据缓冲区地址数组。

  • 使用方法:

    • 对于选择描述符,在执行 FETCH 命令之前分配数据缓冲区并设置此数组。

    • 对于绑定描述符,在执行 OPEN 命令之前设置此数组。

L 变量

  • 用途: 指向存储在数据缓冲区中的选择列表或绑定变量值长度的数组。

  • 使用方法:

    • 对于选择描述符,DESCRIBE SELECT LIST 会将长度数组设置为每个选择列表项的最大预期长度。

    • 在 FETCH 之前,可能需要重置一些长度以满足实际需要。

    • 对于绑定描述符,在执行 OPEN 命令之前必须设置长度数组。

T 变量

  • 用途: 指向选择列表或绑定变量值的数据类型代码数组。这些代码决定了 Oracle 数据在存储到由 V 数组元素指向的数据缓冲区时如何转换。

  • 使用方法:

    • 对于选择描述符,DESCRIBE SELECT LIST 会将数据类型代码数组设置为选择列表项的内部数据类型。

    • 在 FETCH 之前,可能需要重置一些数据类型以便更易于处理。

    • 对于绑定描述符,DESCRIBE BIND VARIABLES 会将数据类型代码数组设置为零。在执行 OPEN 命令之前,必须设置每个元素的数据类型代码。

I 变量

  • 用途: 指向存储指示变量值的数据缓冲区地址的数组。

  • 使用方法:

    • 对于选择描述符,在执行 FETCH 命令之前必须设置地址数组。如果返回的选择列表值为 NULL,则指示变量值设置为 -1。

    • 对于绑定描述符,在执行 OPEN 命令之前必须设置地址数组。如果指示变量值为 -1,则绑定变量值为 NULL。

F 变量

  • 用途: 实际找到的选择列表项或占位符的数量。

  • 使用方法:

    • 由 DESCRIBE 设置。如果 F 小于零,表示描述的选择列表项或占位符数量超出了描述符的分配大小。

S 变量

  • 用途: 指向存储选择列表或占位符名称的数据缓冲区地址的数组。

  • 使用方法:

    • 使用 sqlald() 分配数据缓冲区,并将其地址存储在 S 数组中。

M 变量

  • 用途: 存储选择列表或占位符名称的数据缓冲区的最大长度数组。

  • 使用方法:

    • 使用 sqlald() 设置 M 数组中的元素。

C 变量

  • 用途: 存储选择列表或占位符名称的当前长度数组。

  • 使用方法:

    • 由 DESCRIBE 设置。在 DESCRIBE 之后,数组包含每个选择列表或占位符名称的字符数。

X 变量

  • 用途: 指向存储指示变量名称的数据缓冲区地址的数组。仅适用于绑定描述符。

  • 使用方法:

    • 使用 sqlald() 分配数据缓冲区,并将其地址存储在 X 数组中。

Y 变量

  • 用途: 存储指示变量名称的数据缓冲区的最大长度数组。仅适用于绑定描述符。

  • 使用方法:

    • 使用 sqlald() 设置 Y 数组中的元素。

Z 变量

  • 用途: 存储指示变量名称的当前长度数组。仅适用于绑定描述符。

  • 使用方法:

    • 由 DESCRIBE BIND VARIABLES 设置。在 DESCRIBE 之后,数组包含每个指示变量名称的字符数。

33.16.2.1. 描述绑定变量

DESCRIBE BIND VARIABLES 将占位符的描述放入绑定描述符。在我们的例子中,DESCRIBE 准备好 bind_des,如下所示:

EXEC SQL DESCRIBE BIND VARIABLES FOR sql_stmt INTO bind_des;
    

DESCRIBE BIND VARIABLES 语句必须在 PREPARE 语句之后,但在 OPEN 语句之前。

33.16.2.2. 描述选择列表

如果动态 SQL 语句是查询,则 DESCRIBE SELECT LIST 语句必须在 OPEN 语句之后,但在 FETCH 语句之前。DESCRIBE SELECT LIST 将选择列表项的描述放入选择描述符。在我们的例子中,DESCRIBE 准备好 select_des,如下所示:

EXEC SQL DESCRIBE SELECT LIST FOR sql_stmt INTO select_des;
    

通过访问数据字典,DESCRIBE 设置每个选择列表值的长度和数据类型。

33.16.2.3. 从结果集合中提取行

FETCH 从活动集合中返回一行,将选择列表值存储在数据缓冲区中,并将游标推进到活动集合中的下一行。如果没有更多的行,FETCH 将 sqlca.sqlcode 设置为“未找到数据”的错误代码。在我们的例子中,FETCH 将列的值返回到 select_des,如下所示:

EXEC SQL FETCH emp_cursor USING DESCRIPTOR select_des;
    

要一次提取多行,请使用指定行数的 FETCH 语句。以下示例演示如何将指定数量的行(FETCH_ROWS_COUNT)提取到选择描述符区域(outp_sqlda)中:

EXEC SQL FOR :fetch_rows_count FETCH mycur1 USING DESCRIPTOR outp_sqlda;
    

33.16.2.4. 示例

#include <stdlib.h>
#include <string.h>
#include <limits.h>

// exec sql include ../regression;

exec sql include sqlda.h;
exec sql include pgtypes_numeric.h;

exec sql whenever sqlerror stop;

/* These shouldn't be under DECLARE SECTION */
SQLDA   *inp_sqlda, *outp_sqlda;

static void
dump_sqlda(SQLDA *sqlda, int row)
{
    int i;

    if (sqlda == NULL)
    {
        printf("dump_sqlda called with NULL sqlda\n");
        return;
    }

    for (i = 0; i < sqlda->F; i++)
    {
        short* ptr = (short*)sqlda->I[i];

        if (ptr[row] == -1)
            printf("name sqlda descriptor: '%s' value NULL'\n", sqlda->S[i]);
        else
        {
            switch (sqlda->T[i])
            {
                case 96:
                    printf("name sqlda descriptor: '%s' value '%s'\n", sqlda->S[i], sqlda->V[i] + row * sqlda->L[i]);
                    break;
                case 3:
                    printf("name sqlda descriptor: '%s' value %d\n", sqlda->S[i], *(int *)(sqlda->V[i] + row * sqlda->L[i]));
                    break;
                case 22:
                    printf("name sqlda descriptor: '%s' value %f\n", sqlda->S[i], *(double *)(sqlda->V[i] + row * sqlda->L[i]));
                    break;
                case 2:
                    {
                        char *val;
                        val = PGTYPESnumeric_to_asc((numeric*)(sqlda->V[i] + row * sqlda->L[i]), -1);
                        printf("name sqlda descriptor: '%s' value NUMERIC '%s'\n", sqlda->S[i], val);
                        PGTYPESchar_free(val);

                        break;
                    }
                default:
                    printf("type = %d \n", sqlda->T[i]);
            }
        }
    }
}

int
main (void)
{
exec sql begin declare section;
    char    *stmt1 = "SELECT * FROM t1";
    char    *stmt2 = "SELECT * FROM t1 WHERE id = :bindID";
    int rec;
    int id;
    int fetch_rows_count;
exec sql end declare section;

    char msg[128];

    ECPGdebug(1, stderr);

    
    // exec sql connect to REGRESSDB1 as regress1;
    exec sql connect to postgres as regress1;
    exec sql drop table if exists t1;

    
    exec sql set datestyle to iso;

    
    exec sql create table t1(
        id integer,
        t text,
        d1 numeric,
        d2 float8,
        c char(10));

    
    exec sql insert into t1 values
        (1, 'a', 1.0, 1, 'a'),
        (2, null, null, null, null),
        (4, 'd', 4.0, 4, 'd');

    
    exec sql commit;

    /* SQLDA test for getting all records from a table */
    
    outp_sqlda = NULL;
    outp_sqlda = sqlald(10, 20, 20);

    
    outp_sqlda->N = 10;

    
    exec sql prepare st_id1 from :stmt1;

    
    exec sql declare mycur1 cursor for st_id1;

    
    exec sql open mycur1;

    exec sql whenever not found do break;

    
    // exec sql describe st_id1 into outp_sqlda;
    exec sql describe select list for st_id1 into outp_sqlda;

    
    outp_sqlda->N = outp_sqlda->F;

    
    for (int i = 0; i < outp_sqlda->F; i++)
    {
        outp_sqlda->V[i] = calloc(1, 100);
        outp_sqlda->I[i] = calloc(1, 100);
    }

    rec = 0;
    while (1)
    {
        
        exec sql fetch mycur1 using descriptor outp_sqlda;

        printf("FETCH RECORD %d\n", ++rec);
        dump_sqlda(outp_sqlda, 0);
    }

    exec sql whenever not found continue;

    
    exec sql close mycur1;

    
    exec sql deallocate prepare st_id1;

    for (int i = 0; i < outp_sqlda->F; i++)
    {
        free(outp_sqlda->V[i]);
        free(outp_sqlda->I[i]);
    }
    
    sqlclu(outp_sqlda);


    /* SQLDA test for getting FETCH_ROWS_COUNT records from a table */
    printf("FETCH_ROWS_COUNT RECORD \n");

    
    outp_sqlda = NULL;
    outp_sqlda = sqlald(10, 20, 20);

    
    outp_sqlda->N = 10;

    
    exec sql prepare st_id1 from :stmt1;

    
    exec sql open mycur1;

    
    // exec sql describe st_id1 into outp_sqlda;
    exec sql describe select list for st_id1 into outp_sqlda;

    
    outp_sqlda->N = outp_sqlda->F;

    
    /* 注意,获取多行记录时,一定要分配足够的空间 */
    for (int i = 0; i < outp_sqlda->F; i++)
    {
        outp_sqlda->V[i] = calloc(1, 200);
        outp_sqlda->I[i] = calloc(1, 20);
    }

    fetch_rows_count = 3;
    
    /* 每次提取 FETCH_ROWS_COUNT 条数据到选择描述区 */
    /* sqlca.sqlerrd[2] 保存当前处理成功的总行数 */
    EXEC SQL FOR :fetch_rows_count FETCH mycur1 USING DESCRIPTOR outp_sqlda;

    for (int iRow = 0; iRow < sqlca.sqlerrd[2]; iRow++)  /*循环行*/
    {
        dump_sqlda(outp_sqlda, iRow);
    }

    
    exec sql close mycur1;

    
    exec sql deallocate prepare st_id1;

    for (int i = 0; i < outp_sqlda->F; i++)
    {
        free(outp_sqlda->V[i]);
        free(outp_sqlda->I[i]);
    }
    sqlclu(outp_sqlda);


    /* SQLDA test for getting one record using an input descriptor */

    /* Input sqlda has to be built manually */
    inp_sqlda = sqlald(10, 20, 20);
    outp_sqlda = sqlald(10, 20, 20);

    printf("EXECUTE RECORD 4\n");

    id = 4;

    
    exec sql prepare st_id1 from :stmt2;

    
    // exec sql declare mycur1 cursor for st_id1;

    exec sql DESCRIBE BIND VARIABLES FOR st_id1 into inp_sqlda;

    printf("inp_sqlda->F = %d \n", inp_sqlda->F);
    printf("inp_sqlda->S[0] = %s \n", inp_sqlda->S[0]);
    printf("inp_sqlda->C[0] = %d \n", inp_sqlda->C[0]);

    inp_sqlda->N = inp_sqlda->F;
    printf("inp_sqlda->N = %d \n", inp_sqlda->N);

    inp_sqlda->V[0] = (char *)&id;
    inp_sqlda->T[0] = 3;    /* ECPGt_int */
    inp_sqlda->L[0] = sizeof(id);

    
    exec sql open mycur1 USING DESCRIPTOR inp_sqlda;;

    exec sql whenever not found do break;

    
    // exec sql describe st_id1 into outp_sqlda;
    exec sql describe select list for st_id1 into outp_sqlda;

    
    outp_sqlda->N = outp_sqlda->F;

    
    for (int i = 0; i < outp_sqlda->F; i++)
    {
        outp_sqlda->V[i] = calloc(1, 200);
        outp_sqlda->I[i] = calloc(1, 20);
    }

    rec = 0;
    while (1)
    {
        
        exec sql fetch mycur1 using descriptor outp_sqlda;

        printf("FETCH RECORD %d\n", ++rec);
        dump_sqlda(outp_sqlda, 0);
    }

    exec sql whenever not found continue;

    
    exec sql close mycur1;

    
    exec sql deallocate prepare st_id1;

    for (int i = 0; i < outp_sqlda->F; i++)
    {
        free(outp_sqlda->V[i]);
        free(outp_sqlda->I[i]);
    }
    sqlclu(inp_sqlda);
    sqlclu(outp_sqlda);

    /* End test */

    
    exec sql drop table t1;

    
    exec sql commit;

    
    exec sql disconnect;

    return 0;
}
  

33.16.3. 附加函数

  • sqlald()

sqlald() 函数为描述符分配存储空间。

  SQLDA *
  sqlald(unsigned int max_vars, 
         unsigned int max_name, 
         unsigned int max_ind_name)
  

    max_vars:表示描述符中能够存储的最大变量数。

    max_name:如果非零,则为存储变量名的数组分配空间。

    max_ind_name:如果非零,则为存储指示器变量名的数组分配空间。

函数返回的 SQLDA * 是一个指向描述符结构体的指针。如果分配成功,则返回该指针;否则返回零。

  • sqlclu()

sqlclu() 释放 sqlda 结构本身。

  • Sqlnul()

T[i] 的高位存储着第 i 个查询列表项的 NULL \ NOT NULL 状态信息。在执行 OPEN 或 FETCH 命令前必须清除该位,这可以通过调用函数 sqlnul() 来完成。函数 sqlnul() 的语法如下:

sqlnul(unsigned short *value_type,unsigned short *type_code,int*null_status);

参数说明如下:

    values_type:指向无符号短整数变量的指针,该变量存储着查询列表项的数据代码类型,而数据类型存储在 T[i]中。

    type_code:指向无符号短整数变量的指针,该变量返回了查询列表项的数据类型代码。

    null_status:指向整数变量的指针,该变量返回了查询列表项的 NULLNULL 状态。其中,1 表示允许该列为空,而 0 表示不允许该列为空。