SQL语句执行

最常见的是接收和处理SQL语句。在分配了必须的句柄,连接到数据库后,通过下图所示的方式来处理SQL语句:

../../../../_images/image218.png
  1. 准备:调用 ACIStmtPrepare / ACIStmtPrepare2 接口进行SQL准备,ACI接口要求任何SQL、且不管SQL中是否有参数绑定,都需要调用准备接口,传入SQL语句;
  2. 参数绑定:对于带有输入变量的DML语句和查询语句, 可以通过对下列一个或多个函数的调用将每个输入变量(或者PLOSCAR输出变量) 或数组的地址绑定到语句中的每个占位符中。 ACIBindByPos / ACIBindByPos2 / ACIBindByName / ACIBindByName2 接口进行参数绑定;
  3. 执行: ACIStmtExecute 用来最终执行,此时才会把sql和对应的参数发送给数据库服务端;
  4. 描述:对于有结果集的SQL,描述可以获得结果集各个列的类型、长度、列名等信息,方便后续数据获取的变量定义和绑定;
  5. 定义变量:当执行的SQL是有结果集返回的情况下,需要用 ACIDefineByPosACIDefineByPos2ACIDefineArrayOfStruct 等方法进行定义变量的绑定;
  6. 获取数据:对于有查询结果的SQL,需要用调用 ACIStmtFetch 获取结果。

注意:

可以在”执行”前进行”定义变量”,”描述”也不是必须的;”定义变量”和”获取数据”是必须结果集的情况下才执行。

准备SQL

通过语句准备函数或者一些必要的绑定函数的调用为应用程序的执行准备SQL或者PLOSCAR语句。在准备阶段,应用程序对用户传入的SQL语句进行解析,将语句中的占位符绑定到相关数据。客户端为准备执行的语句分配存储空间。

通过对 ACIStmtPrepare / ACIStmtPrepare2 的调用,并为其传入预先分配好的语句句柄为应用程序做准备工作。初始化工作是在本地完成,不需要反映到服务器。此阶段不需要与数据库服务器进行连接。

执行完 ACIStmtPrepare / ACIStmtPrepare2 后,可以使用 ACIAttrGet 获取SQL语句的类型(传入参数ACI_ATTR_STMT_TYPE),语句的类型如下所示:

属性值 语句类型
ACI_STMT_SELECT SELECT语句
ACI_STMT_UPDATE UPDATE语句
ACI_STMT_DELETE DELETE语句
ACI_STMT_INSERT INSERT语句
ACI_STMT_CREATE CREATE语句
ACI_STMT_DROP DROP语句
ACI_STMT_ALTER ALTER语句
ACI_STMT_BEGIN BEGIN(PL/SQL)
ACI_STMT_DECLARE DECLARE(PL/SQL)
ACI_STMT_MERGE MERGE语句

对于Insert、Update、Delete语句,如果带了returning into字句,可获取STMT属性ACI_ATTR_STMT_IS_RETURNING,返回执行的语句中是否包含了returning字句。

参数绑定

对于程序运行时产生的参数,需要通过绑定的方式传入SQL语句,ACI支持以冒号开头的名字占位符(如:ename)。在DELETE、INSERT、SELECT、UPDATE和PLOSCAR中,都能使用名字占位符。在输出的参数中,不能使用;且占位符不能代替表的名字,如INSERT INTO :emp VALUES(1, 2)是不允许的。

参数绑定的过程,就是将变量的地址传入后保存,以供执行的时候随SQL语句一起发送到数据库服务器上执行。参数的绑定是将参数的地址传入,因此修改参数值的之后,不需要重新进行绑定。

关于参数占位符的一些约束规则:

  1. 首个字符是分号(“:”),冒号后面一个字符不能是等号(:=)或者冒号( :: )。(:= 因为兼容Oracle的PL语法中,:=作为等号使用;而 :: 两个冒号作为强制转换符号使用)。
  2. 占位符可以由_,A-Z,a-z,0-9的这些字符组成,但冒号后的第一个字符不能是_。
  3. 占位符支持 : 冒号 、" 双引号、 _ 下划线 、 .点号 四个特殊符号
  4. 除去开始的冒号,占位符最多128个字符,最少1个字符,并且是不区分大小写的;
  5. 占位符可以由数字组成,如果只有数字,对应的数不能超过65536。如果开头的字符是数字,接下来的字符只能是数字;
  6. 如果在标识符前后加双引号,这占位符支持特殊字符:比如支持:insert into test values( :id ,:"*.[*name") ,*.[*name 是占位符的名称。

ACI支持参数按照名字绑定、按照位置绑定和批量绑定。 ACIBindByName / ACIBindByName2 函数实现了按照参数名字绑定的功能,绑定时给的名字是带:号的; ACIBindByPos / ACIBindByPos2 函数实现了按照参数的位置序号绑定的功能,参数的序号从1开始编号。

ACIBindArrayOfStruct 函数来实现了参数的批量绑定功能,用户程序需要提供一块以定长分割好的数据缓冲区,可以使用数组或者分配的连续的内存,用 ACIBindByPos 或者 ACIBindByName 将绑定参数的首地址提供给ACI接口,然后使用 ACIBindArrayOfStruct 函数来定义变量间隔大小和偏移量。

对于没有占位符做参数绑定的SQL语句,此步可省略。

变量定义

查询语句会向应用程序返回从数据库获取的数据。此时应该为查询语句列表项中的每一项定义输出变量或者一个输出变量数组以从中获取数据。此阶段决定数据存放的位置以及存放格式。

例如,应该为下列语句定义两个输出变量:一个变量存储从id列返回的数据,另一个存储从col1列返回的数据 :

SELECT id , col1 FROM mt_t WHERE id = :id;

在程序中定义变量后,然后通过 ACIDefineByPosACIDefineByPos2 相关接口进行定义到ACI接口中。

对于查询语句,变量定义时必须要做的,否则无法返回数据;对于非查询语句,变量定义可以省略。

执行SQL

在ACI执行查询时,会从数据库接收和查询语句中所定义的列表相匹配的数据。在神通数据库中,数据是按照内部格式存储。当返回结果时,ACI应用程序能够将要求的数据转换成特定的本地语言所需的格式并存储在输出变量或缓存中。

对于没有结果集返回的DML/DDL(Insert、Update等)语句执行, ACIStmtExecute 后会在数据库端执行,且iters是设置sql语句执行多少次,且iters常规为1,不能小于1;

对于查询语句(Select等),想要获得数据库,必须定义接收输出结果的变量,且定义变量的绑定。然后使用 ACIStmtExecute 函数来执行准备好的语句,查询语句iters常规设置为0;如果iters大于0,则代表结果输出的定义的变量中去,就相当于隐式的做了一次 ACIStmtFetch 操作。

ACIStmtExecute 的多种执行模式:
  1. ACI_DEFAULT

正常模式,这种模式会将SQL语句发送到数据库服务端去执行。

  1. ACI_EXACT_FETCH

与ACI_DEFAULT完全一致;

  1. ACI_COMMIT_ON_SUCCESS

执行完成后自动提交事务,对于Insert、Update等操作,执行后数据会被立即提交事务,其他用户可查询到变更后数据(默认情况下,ACI对数据库的操作必须要手动执行 ACITransCommit 提交的);

  1. ACI_DESCRIBE_ONLY:

描述模式,对于查询语句来说, ACIStmtExecute 执行时指定这种模式代表不会真正的去支持查询,而是只获得这个查询结果对应的列信息,包括列名、列类型、长度、精度等信息;可以用 ACIParamGet 获取指定列的参数句柄,然后使用ACIAttrGet获取所需的属性值。

  1. ACI_BATCH_ERRORS

批量错误模式,在执行完 ACIStmtExecute 函数后,可以通过调用 ACIAttrGet 函数并传入语句句柄以获取ACI_ATTR_NUM_DML_ERRORS属性的方式来取得在执行语句组时所发生的错误的数量:

ub4 num_errs;
ACIAttrGet(stmtp, ACI_HTYPE_STMT, &num_errs, 0, ACI_ATTR_NUM_DML_ERRORS,errhp);

应用程序会通过 ACIParamGet 函数从传入 ACIStmtExecute 的错误句柄中提取每个错误信息连同行信息。为了取出这些信息,必须为 ACIParamGet 另外分配句柄以获取带有批错误信息的新句柄。可以通过 ACIErrorGet 函数来获取每个错误的语法。通过 ACIAttrGet 函数来获取在语句组中错误发生的偏移量。

  1. ACI_STMT_SCROLLABLE_READONLY

执行完成后,结果集可滚动的,但无法更新结果集。

结果获取

当用户程序执行查询语句从数据库中查询并返回数据时,用户必须提供缓冲区去接受查询结果(也就是SELECT-LIST),并且需要同时指定数据类型和数据长度。

有时一条查询语句返回的记录不是一条,而是多条记录,如果用户想要一条一条的获取记录信息,可以通过 ACIStmtFetchACIStmtFetch2 函数来获取。

如果用户想要批量的获取结果记录,比如一次性的获取10条记录,可以使用 ACIDefineArrayOfStruct 函数来实现,程序通过用户设置每条记录的间隔来将多条记录进行填充。

ACIStmtFetchACIStmtFetch2 函数中,可以设置ACI_ATTR_PREFETCH_ROWS和ACI_ATTR_PREFETCH_MEMORY,ACI_ATTR_PREFETCH_ROWS如果不设置,则默认值为1。如果两个参数同时设置,则取最大的那个,尽可能缓存更多的数据。

执行示例

/*
本例中的SQL语句为SELECT语句,假设后台数据库中存在表名为mt_t表
建表语句
create table mt_t(id number(10),col1 number(10));
insert into mt_t values(10,100);
insert into mt_t values(11,1000);
*/

#include <aci.h>
/*句柄分配*/
ACIEnv                        *m_penv = NULL;                 /*环境句柄*/
ACIError              *m_perr = NULL;                 /*错误句柄*/
ACIServer             *m_psrv = NULL;                 /*服务器句柄*/
ACISvcCtx             *m_psvc = NULL;                 /*服务上下文句柄*/
ACISession            *m_pses = NULL;                 /*会话句柄*/
ACIStmt                       *m_pstmt = NULL;                /*语句句柄*/
ACIBind                       *m_bnd = NULL;                  /*绑定句柄*/
ACIDefine             *m_def = NULL;                  /*定义句柄*/

typedef struct
{
      int sID;
      int sCol1;
} Record;

int main()
{
      /*----------变量定义-------------*/
      sword r = ACI_SUCCESS;
      char *m_dblink = "127.0.0.1:2003/OSRDB";
      char *m_dbuser = "SYSDBA";
      char *m_dbpwd = "szoscar55";
      char ssql[] = "select * from mt_t where id=:id";
      int id = 10;
      Record rec = { 1,2 };

      /*----------分配并初始化各类应用句柄-------------*/
      r = ACIInitialize(ACI_DEFAULT, NULL, NULL, NULL, NULL);
      r = ACIEnvInit(&m_penv, ACI_DEFAULT, 0, 0);
      r = ACIHandleAlloc(m_penv, (void**)&m_perr, ACI_HTYPE_ERROR, 0, 0);
      r = ACIHandleAlloc(m_penv, (void**)&m_pstmt, ACI_HTYPE_STMT, 0, 0);

      /*----------连接数据库-------------*/
      r = ACILogon(m_penv, m_perr, &m_psvc, (const OraText *)m_dbuser, strlen(m_dbuser), (const OraText *)m_dbpwd, strlen(m_dbpwd), (const OraText *)m_dblink, strlen(m_dblink));

      /*----------准备SQL语句-------------*/
      r = ACIStmtPrepare(m_pstmt, m_perr, (OraText*)ssql, strlen(ssql), ACI_HTYPE_ERROR, ACI_DEFAULT);

      /*----------按位置进行绑定-------------*/
      r = ACIBindByPos(m_pstmt, &m_bnd, m_perr, 1, (void *)&id, sizeof(id), SQLT_INT, (void*)0, (ub2*)0, (ub2*)0, 0, 0, ACI_DEFAULT);

      /*----------按位置进行定义-------------*/
      r = ACIDefineByPos(m_pstmt, &m_def, m_perr, 1, (void*)&rec.sID, sizeof(rec.sID), SQLT_INT, (void*)0, 0, 0, ACI_DEFAULT);
      r = ACIDefineByPos(m_pstmt, &m_def, m_perr, 2, (void *)&rec.sCol1, sizeof(rec.sCol1), SQLT_INT, (void*)0, 0, 0, ACI_DEFAULT);

      /*----------执行SQL语句-------------*/
      r = ACIStmtExecute(m_psvc, m_pstmt, m_perr, 0, 0, 0, 0, ACI_DEFAULT);

      /*----------结果获取-------------*/
      while (((r = ACIStmtFetch(m_pstmt, m_perr, 1, ACI_FETCH_NEXT, ACI_DEFAULT)) != ACI_NO_DATA))
      {
              printf("%d, %d \n", rec.sID, rec.sCol1);
      }
}

取消执行

在大多数操作系统中,可以通过输入操作系统的中断命令字符(通常情况是Control + C)来取消长时间运行或者重复调用的ACI,此时神通数据库会返回相应的错误号。

对于特定的服务上下文指针或者服务器上下文指针,函数 ACIBreak 会直接异步停止当前正在执行的和该服务器相关联的ACI函数。该方法通常用于停止在服务器上长时间运行的ACI。