
# SQLDA
SQLDA是一个C语言结构体，用来存放一个查询的结果集，一个结构体存储一个结果集的记录。
```
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-v8-gaussdb/gaussdb-12-0222.html#ZH-CN_TOPIC_0000002527825880)章节中关于INTO和USING关键词的用例在一定条件下也适用于这里。在一个DESCRIBE语句中，如果使用了INTO关键词，则DESCRIPTOR关键词可以省略。
```
EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
```
- 使用SQLDA的步骤：
  1. 准备一个查询，并且为它声明一个游标。
  
  2. 为结果行声明SQLDA。
  
  3. 为输入参数声明SQLDA，并且初始化参数和分配内存。
  
  4. 打开具有输入SQLDA的游标。
  
  5. 从游标中抓取行，并且将它们存储到输出SQLDA中。
  
  6. 从输出SQLDA中读取值到宿主变量中。
  
  7. 关闭游标。
  
  8. 释放为SQLDA分配的内存。
   
- SQLDA的数据结构类型有三种：sqlda_t、sqlvar_t以及struct sqlname。
  1. sqlda_t结构
     sqlda_t的定义如下：
     ```
     struct sqlda_struct
     {
         char            sqldaid[8];
         long            sqldabc;
         short           sqln;
         short           sqld;
         struct sqlda_struct *desc_next;
         struct sqlvar_struct sqlvar[1];
     };
     typedef struct sqlda_struct sqlda_t;
     ```
     结构体成员的含义如下：
     - sqldaid：包含一个字符串"SQLDA"。
     
     - sqldabc：包含已分配空间的尺寸（以字节计）。
     
     - sqln：当被传递给使用USING关键词的OPEN、DECLARE或者EXECUTE语句时，sqln包含一个参数化查询实例的输入参数的数目。当被用作SELECT、EXECUTE或FETCH语句的输出时，sqln的值和sqld一样。
     
     - sqld：包含一个结果集中域的数量。
     
     - desc_next：如果查询返回不止一个记录，那么会返回一个SQLDA结构体链表，desc_next指向下一个SQLDA结构体的指针。
     
     - sqlvar：结果集中的列组。
       
  
  2. 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：包含域的二进制长度，例如：ECPGt_int是4字节。
     
     - sqldata：指向数据。数据格式请参见[类型映射](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/gaussdb-12-0203.html#ZH-CN_TOPIC_0000002527985814)章节。
     
     - sqlind：指向空指示符。0表示非空，-1表示空。
     
     - sqlname：域的名称。
       
  
  3. struct sqlname结构
     一个struct sqlname结构保存一个列名。它被当作sqlvar_t结构的一个成员。该结构的定义如下：
     ```
     #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上声明的游标。
   
  示例如下：
  ```
  /* 首先，创建一个预备语句。 */
  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);
  ```
  
 
