
# SQLDA
SQLDA（SQL Descriptor Area）是一个C语言结构体，用于动态处理SQL查询的结果集或输入参数。它允许程序在运行时处理未知数量和类型的列或参数。
```
EXEC SQL include sqlda.h; 
sqlda_t         *mysqlda;  
EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;
```
注意此处SQL关键词被省略了，[命名SQL描述符区域](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-0344.html)章节中关于INTO和USING关键词的用例在一定条件下也适用。在一个DESCRIBE语句中，如果使用了INTO关键词，则DESCRIPTOR关键词可以省略。
```
EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
```
#### SQLDA相关结构体
SQLDA 的核心数据结构包括 sqlda_t、sqlvar_t 和 struct sqlname。
- sqlda_t 结构 sqlda_t 结构体用于存储查询结果集的整体信息。其定义如下：
  ```
  struct sqlda_struct {
      char sqldaid[8];             // SQLDA标识符，通常为"SQLDA"
      long sqldabc;                // 已分配空间的大小（字节数）
      short sqln;                  // 输入参数的数量或输出结果集的列数
      short sqld;                  // 结果集中的列数
      struct sqlda_struct *desc_next; // 指向下一个SQLDA（链表结构）
      struct sqlvar_struct sqlvar[1]; // 结果集的列数组
  };
  typedef struct sqlda_struct sqlda_t;
  ```
  **字段解释：**
  - sqldaid：SQLDA的标识符，通常是固定字符串"SQLDA"。
  
  - sqldabc：已分配的空间大小，单位为字节。
  
  - sqln：输入参数的数量，或者在处理查询结果时，表示结果集中的列数。
  
  - sqld：结果集中的列数。
  
  - desc_next：用于链式存储多个结果集。
  
  - sqlvar：存储每个列的数据和元信息。
   
- sqlvar_t 结构 sqlvar_t 结构体保存单个列的值及其元数据（如类型和长度）。其定义如下：
  ```
  struct sqlvar_struct {
      short sqltype;               // 列的类型标识符
      short sqllen;                // 列的二进制长度
      char *sqldata;               // 指向列数据的指针
      short *sqlind;               // 指向空指示符的指针
      struct sqlname sqlname;      // 列名
  };
  typedef struct sqlvar_struct sqlvar_t;
  ```
  **字段解释：**
  - sqltype：列的数据类型（如整数、字符等）。
  
  - sqllen：列的数据长度。
  
  - sqldata：指向存储数据的内存区域。
  
  - sqlind：指示该列是否为空，0表示非空，-1表示为空。
  
  - sqlname：列的名称。
   
- struct sqlname 结构 sqlname 结构体保存列的名称。其定义如下：
  ```
  #define NAMEDATALEN 64
  struct sqlname {
      short length;               // 列名的长度
      char data[NAMEDATALEN];     // 列名数据
  };
  ```
  **字段解释：**
  - length：列名的长度。
  
  - data：列名字符串。
   
 
#### SQLDA检索结果集
通过SQLDA检索查询结果集的一般步骤：
1. 声明一个sqlda_t结构来接收结果集。
2. 执行FETCH/EXECUTE/DESCRIBE命令来处理一个已声明SQLDA的查询。
3. 通过查看sqlda_t结构的成员sqln来检查结果集中记录的数量。
4. 从sqlda_t结构体的成员sqlvar\[0\]、sqlvar\[1\]等中得到每一列的值。
5. 沿着sqlda_t结构的成员desc_next指针到达下一行（sqlda_t）。
6. 根据需要重复上述步骤。
示例如下：
```
/* 声明一个sqlda_t结构来接收结果集。*/
sqlda_t *sqlda1;
/* 指定一个命令中的SQLDA。如下是一个FETCH命令的例子。*/
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
/* 运行一个循环顺着链表来检索行。*/
sqlda_t *cur_sqlda;
for (cur_sqlda = sqlda1;
     cur_sqlda != NULL;
     cur_sqlda = cur_sqlda->desc_next)
{
    ...
}
/* 在循环内部，运行另一个循环来检索行中每一列的数据（sqlvar_t结构）。*/
for (i = 0; i < cur_sqlda->sqld; i++)
{
    sqlvar_t v = cur_sqlda->sqlvar[i];
    char *sqldata = v.sqldata;
    short sqllen  = v.sqllen;
    ...
}
/* 要得到一列的值，应检查sqlvar_t结构的成员sqltype的值。然后，根据列类型切换到一种合适的方法从sqlvar域中复制数据到一个主变量。*/
char var_buf[1024];
switch (v.sqltype)
{
    case ECPGt_char:
        memset(&var_buf, 0, sizeof(var_buf));
        memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen));
        break;
    case ECPGt_int: 
        memcpy(&intval, sqldata, sqllen);
        snprintf(var_buf, sizeof(var_buf), "%d", intval);
        break;
    ...
}
```
#### 使用SQLDA传递查询参数
使用SQLDA传递输入参数给一个预备查询的一般步骤：
1. 创建一个预备查询（预备语句）。
2. 声明一个sqlda_t结构体作为SQLDA。
3. 为SQLDA分配内存区域。
4. 在分配好的内存中设置（复制）输入值。
5. 打开一个在SQLDA上声明的游标。
6. 处理查询结果。
7. 释放内存。
示例如下：
```
/* 首先，创建一个预备语句。 */
    EXEC SQL BEGIN DECLARE SECTION;
    char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)";
    EXEC SQL END DECLARE SECTION;
    EXEC SQL PREPARE stmt1 FROM :query;
    /* 为一个SQLDA分配内存，并且在sqlda_t结构的sqln成员变量中设置输入参数的数量。
     * 当预备查询要求两个或多个输入参数时，应用必须分配额外的内存空间，空间的大小为 (参数数目 - 1) * sizeof(sqlvar_t)。
     * 该示例展示了为两个输入参数分配内存空间。
     */
    sqlda_t *sqlda2;
    sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
    memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
    sqlda2->sqln = 2; /* 输入变量的数目 */
    /* 内存分配之后，把参数值存储到sqlvar[]数组（当 SQLDA 在接收结果集时，这也是用来检索列值的数组）。
     在这个例子中，输入参数是"testdb"（字符串类型）和1（整数类型）。*/
    sqlda2->sqlvar[0].sqltype = ECPGt_char;
    sqlda2->sqlvar[0].sqldata = "testdb";
    sqlda2->sqlvar[0].sqllen  = 8;
    int intval = 1;
    sqlda2->sqlvar[1].sqltype = ECPGt_int;
    sqlda2->sqlvar[1].sqldata = (char *) &intval;
    sqlda2->sqlvar[1].sqllen  = sizeof(intval);
    /* 通过打开一个游标并且说明之前已经建立好的 SQLDA，输入参数被传递给预备语句。*/
    EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
    /* 最后，用完输入 SQLDA 后必须显式地释放已分配的内存空间，这与用于接收查询结果的 SQLDA 不同。*/
    free(sqlda2);
```
