关键字:
客户端编程接口、LIBKCI、批量操作、例程、人大金仓、KingbaseES
一、环境准备
安装KES数据库,libkci驱动测试可以正常使用, libkci驱动无法正常使用的请参照作者的文章《产品使用-金仓数据库KingbaseES LIBKCI驱动使用-赵微》。
批量操作说明:在libkci的手册只提供了KES数据库批量插入两条数据的支持,无法根据需求插入多条数据,并且没有批量修改,没有相关的内存管理。
因此针对批量操作的历史用例进行重写,新增可控批量插入数据条数,批量修改数据和内存动态分配和释放,并规范化程序结构。
二、批量操作用例
2.1 数据库连接
1、连接模块首先创建连接参数信息,这里有两种方式,一是通过命令行输入参数作为数据库连接信息,二是直接修改conninfo中的字符串信息。
图2-1 连接参数
2、连接数据库,建立连接并检查是否连接成功,如果没成功调用函数终止程序。此处的exit_nicely可以参照作者的写法。
static void exit_nicely(KCIConnection *conn)
{
KCIConnectionDestory(conn);
exit(1);
}
图2-2 建立连接
3、设置安全的搜索路径,这样恶意用户就无法取得控制。可根据程序和个人新增schema和设置search_path。
create schema schemaname;
set search_path schemaname;
图2-3 设定安全的搜索路径
4、创建所需的表,建议提前创建,避免在函数中出错。
图2-4 创建表
5、创建程序入口,在主函数中调用批量插入和修改的程序,函数返回值为bool类型,打印函数执行的结果为success或failed,并且调用完程序关闭数据库连接。
图2-5 程序入口
2.2 批量多行插入
1、定义全局变量,以及字符串信息,方便一键插入多行数据。
图2-6 全局变量定义
2、创建函数,定义变量,并为SQL指令的字符串数组分配动态内存,动态分配不成功及时终止程序,分配完内存后进行初始化。
图2-7 数据库连接例程编辑
3、创建SQL预备语句,设置了三个参数,并检测预备语句是否创建成功,做失败的相关措施。
图2-8 创建带参数的预备语句
4、绑定参数,按行一次循环绑定参数,多行就绑定多组参数。
图2-9 绑定参数
5、执行预备语句,发送SQL指令给后端,批量插入多行数据。
图2-10 执行预备语句完成批量插入
6、最后释放malloc分配的内存,返回程序执行成功的结果。
图2-11 释放内存返回程序执行结果
2.3 批量多行修改
1、定义参数,创建预备语句,这里使用了两个参数。
图2-12 创建预备语句
2、绑定参数,在test1表中将i为1和2的行批量修改。
图2-13 绑定参数
3、执行预备语句,返回程序结果。
图2-14 执行预备语句
2.4 批量查询
1、定义批量查询函数testExtendQuery(),定义两种查询方式,第一种以文本为查询对象,但以二进制接收结果。
图2-15 以文本为查询对象
2、第二种以整数的二进制形式为查询对象,以二进制形式接收结果。
图2-13 以整数的二进制为查询对象
2.5 程序执行
1、定义两行数据,main中调用批量操作函数。
#define Row 2
#define Column 3
bool b = testBatchInsert();
testExtendQuery();
printf("testBatchInsert %s\n", (b?"success.":"failed!"));
b = testBatchUpdate();
testExtendQuery();
printf("testBatchUpdate %s\n", (b?"success.":"failed!"));
2、定义Makefile文件,在Makefile中加上需要编译的文件,比如作者这里是test2.c,加入数据库的lib和include路径。
图2-14 编辑Makefile文件
3、编译程序,使用make进行程序编译,编译完成生成可执行文件test2。
图2-15 编译文件
4、执行程序,在启动数据库的前提下直接执行./test2,执行完可以看到查询文本“kingbase”有两条数据,查询整数2的二进制字符串有一条数据,批量插入和批量修改均执行成功。
图2-16 执行程序
5、定义三行程序,只修改#define中的定义,其他程序不用修改,再次编译执行程序。
#define Row 3
#define Column 3
图2-17 再次编译
再次使用./test2运行程序,从执行结果可以看到批量插入三条数据,使用文本“kingbase”有三条数据,查询整数2的二进制字符串有一条数据,批量插入和批量修改均执行成功,其中批量修改时只修改了i为1和2的数据,因此i为3的数据跟插入的数据一致。
图2-18 程序执行结果
三、总结
1、libkci的驱动文件在安装完数据库后已经生成,不需要再去单独编译libkci,使用libkci例程时,需要在Makefile中定义数据库的lib和include路径。
2、执行例程时需要首选执行openssl的环境变量配置,将openssl版本从1.1.1k转到1.1.1q,并且还需要执行数据库lib环境变量配置。
export PATH=openssl地址/bin:$PATH
export LD_LIBRARY_PATH=openssl地址/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=数据库地址/lib:LD_LIBRARY_PATH
3、本libkci例程完成了libkci的批量操作用例(插入、修改、读取),作者接下来将完善libkci的copy用例、、负载均衡用例、大对象的二进制写入与读取等,用例的编写和使用可参考作者后续的文章。
附录一:批量操作例程源码支持
/*
* testlibkci3.c
* 批量插入和二进制 I/O。
*
* 在运行之前,使用下列命令填充一个数据库
*
* CREATE SCHEMA testlibkci3;
* SET search_path = testlibkci3;
* SET standard_conforming_strings = ON;
* CREATE TABLE test1 (i int4, t text, b bytea);
*
* 批量插入期待的输出是:
* 查找 t 为 kingbase 输出:
* tuple 0: got
* i = (4 bytes) 1
* t = (8 bytes) 'kingbase'
* b = (5 bytes) \000\001\002\003\004
*
* tuple 1: got
* i = (4 bytes) 2
* t = (13 bytes) 'kingbase'
* b = (5 bytes) \000\001\002\003\004
*
* 查找 i 为 2 输出:
* tuple 0: got
* i = (4 bytes) 2
* t = (8 bytes) 'kingbase'
* b = (5 bytes) \000\001\002\003\004
*
* 批量修改过程是:
* 将 i 为 1 的行的 b 列修改为 \\000\\001\\002\\003\004\\005
* 将 i 为 2 的行的 b 列修改为 \\005\\004\\003\\002\001\\000
*
*/
#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "libkci_fe.h"
/* for ntohl/htonl */
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* 定义表 test1 的行 Row 和列 Column
* 需要插入多行修改 Row 值即可,不用修改程序
*/
#define Row 3
#define Column 3
/*
* 定义的批量插入的数据
* 表test1中有三列,分别为i、t、b
* 其中i中插入数据的行号,t中插入t_data的数据
* b为插入b_data的数据
*/
const char* t_data = "kingbase";
const char* b_data = "\\000\\001\\002\\003\\004";
KCIConnection *conn;
KCIResult *res = NULL;
static void exit_nicely(KCIConnection *conn)
{
KCIConnectionDestory(conn);
exit(1);
}
/*
* 这个函数打印一个查询结果,该结果以二进制格式从上面的注释中定义的表中取得。
* 我们把它分离出来是因为 main() 函数需要使用它两次。
*/
static void show_binary_results(KCIResult *res)
{
int i,
j;
int i_fnum,
t_fnum,
b_fnum;
/* 使用 KCIResultGetColumnNo 来避免假定结果中字段的顺序 */
i_fnum = KCIResultGetColumnNo(res, "i");
t_fnum = KCIResultGetColumnNo(res, "t");
b_fnum = KCIResultGetColumnNo(res, "b");
for (i = 0; i < KCIResultGetRowCount(res); i++)
{
char *iptr;
char *tptr;
char *bptr;
int blen;
int ival;
/* 得到字段值(我们忽略它们为空值的可能性!) */
iptr = KCIResultGetColumnValue(res, i, i_fnum);
tptr = KCIResultGetColumnValue(res, i, t_fnum);
bptr = KCIResultGetColumnValue(res, i, b_fnum);
/*
* INT4 的二进制表示是按照网络字节序的,我们最好强制为本地字节序。
*/
ival = ntohl(*((uint32_t *)iptr));
/*
* TEXT 的二进制表示是文本,并且因为 libkci 会为它追加一个零字节,它将工作得和 C 字符串一样好。
*
* BYTEA 的二进制表示是一堆字节,其中可能包含嵌入的空值,因此我们必须注意字段长度。
*/
blen = KCIResultGetColumnValueLength(res, i, b_fnum);
printf("tuple %d: got\n", i);
printf(" i = (%d bytes) %d\n",
KCIResultGetColumnValueLength(res, i, i_fnum), ival);
printf(" t = (%d bytes) '%s'\n",
KCIResultGetColumnValueLength(res, i, t_fnum), tptr);
printf(" b = (%d bytes) ", blen);
for (j = 0; j < blen; j++)
printf("\\%03o", bptr[j]);
printf("\n\n");
}
}
/*
* 这个函数使用绑定参数用法批量插入数据
* 插入的数据行数由 Row 决定,插入成功返回 true
*/
bool testBatchInsert()
{
int i;
char *paramValues[Row * Column];
int paramLengths[Row * Column];
int paramFormats[Row * Column];
/* 分配内存并初始化 */
for( i =0 ; i < Row * Column ; i++)
{
paramValues[i] = (char *)malloc(sizeof(char));
if(NULL == paramValues[i])
{
exit(1);
}
memset(paramValues[i],0,sizeof(char));
}
memset(paramLengths,0,sizeof(paramLengths));
memset(paramFormats,0,sizeof(paramFormats));
/* 创建预备语句 */
res = KCIStatementPrepare(conn, "prestmt1", "insert into test1 values ($1,$2,$3)", 3, NULL);
if (KCIResultGetStatusCode(res) != EXECUTE_COMMAND_OK)
{
fprintf(stderr, "KCIStatementPrepare error:[%s]",
KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
return false;
}
KCIResultDealloc(res);
/* 绑定参数 */
for( i = 0 ; i < Row ; i++ )
{
sprintf(paramValues[i * Column] , "%d",i + 1);
sprintf(paramValues[i * Column + 1] ,"%s",t_data);
sprintf(paramValues[i * Column + 2] , "%s",b_data);
}
for (i = 0; i < Row * Column; i++)
{
paramLengths[i] = sizeof(paramValues[i]);
paramFormats[i] = 0;
}
/* 执行预备语句,多行插入 */
res = KCIStatementExecutePrepared_ext(conn, "prestmt1", 3, (const char * const*)paramValues,
paramLengths, paramFormats, 0, Row);
if (KCIResultGetStatusCode(res) != EXECUTE_COMMAND_OK)
{
fprintf(stderr, "batch insert error:[%s]",
KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
return false;
}
KCIResultDealloc(res);
/* 释放由 malloc 分配的内存 */
for( i =0 ; i < Row * Column ;i++)
{
free(paramValues[i]);
}
return true;
}
/*
* 这个函数使用绑定参数用法批量修改数据
* 修改时要指定修改的数据内容和数据列,修改成功返回 true
*/
bool testBatchUpdate()
{
const char *paramValues[6];
int paramLengths[6];
int paramFormats[6];
/* 创建预备语句 */
res = KCIStatementPrepare(conn, "prestmt2", "update test1 set b = $1 where i = $2 ", 2, NULL);
if (KCIResultGetStatusCode(res) != EXECUTE_COMMAND_OK)
{
fprintf(stderr, "KCIStatementPrepare error:[%s]",
KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
return false;
}
KCIResultDealloc(res);
/* 绑定参数 */
paramValues[0] = "\\000\\001\\002\\003\\004\\005";
paramValues[1] = "1";
paramValues[2] = "\\005\\004\\003\\002\001\\000";
paramValues[3] = "2";
paramLengths[0] = 30;
paramLengths[1] = 1;
paramLengths[2] = 30;
paramLengths[3] = 1;
paramFormats[0] = paramFormats[1] = paramFormats[2] = paramFormats[3] = 0;
/* 执行预备语句,多行修改 */
res = KCIStatementExecutePrepared_ext(conn, "prestmt2", 2, paramValues,
paramLengths, paramFormats, 0, 2);
if (KCIResultGetStatusCode(res) != EXECUTE_COMMAND_OK)
{
fprintf(stderr, "batch insert error:[%s]",
KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
return false;
}
KCIResultDealloc(res);
return true;
}
void testExtendQuery()
{
const char *paramValues[6];
int paramLengths[6];
int paramFormats[6];
uint32_t binaryIntVal;
/*
* 这个程序的要点在于用外部参数展示 KCIStatementExecuteParams() 的使用,以及数据的二进制传输。
*
* 第一个示例将参数作为文本传输,但是以二进制格式接收结果。
* 通过使用线外参数,我们能够避免使用繁杂的引用和转义,即便数据是文本。
* 注意我们怎么才能对参数值中的引号不做任何事情。
*/
/* 这里是我们的线外参数值 */
paramValues[0] = "kingbase";
res = KCIStatementExecuteParams(conn, "SELECT * FROM test1 WHERE t = $1",
1, /* 一个参数 */
NULL, /* 让后端推导参数类型 */
paramValues,
NULL, /* 因为文本不需要参数长度 */
NULL, /* 对所有文本参数的默认值 */
1); /* 要求二进制结果 */
if (KCIResultGetStatusCode(res) != EXECUTE_TUPLES_OK)
{
fprintf(stderr, "SELECT failed: %s", KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
}
show_binary_results(res);
KCIResultDealloc(res);
/*
* 在第二个示例中,我们以二进制形式传输一个整数参数,并且再次以二进制形式接收结果。
*
* 尽管我们告诉 KCIStatementExecuteParams 我们让后端推导参数类型,我们实际上通过
* 在查询文本中强制转换参数符号来强制该决定。
* 在发送二进制参数时,这是一种好的安全测度。
*/
/* 将整数值 "2" 转换为网络字节序 */
binaryIntVal = htonl((uint32_t)2);
/* 为 KCIStatementExecuteParams 设置参数数组 */
paramValues[0] = (char *)&binaryIntVal;
paramLengths[0] = sizeof(binaryIntVal);
paramFormats[0] = 1; /* binary */
res = KCIStatementExecuteParams(conn, "SELECT * FROM test1 WHERE i = $1::int4",
1, /* 一个参数 */
NULL, /* 让后端推导参数类型 */
paramValues,
paramLengths,
paramFormats,
1); /* 要求二进制结果 */
if (KCIResultGetStatusCode(res) != EXECUTE_TUPLES_OK)
{
fprintf(stderr, "SELECT failed: %s", KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
}
show_binary_results(res);
KCIResultDealloc(res);
}
int main(int argc, char **argv)
{
const char *conninfo;
/*
* 如果用户在命令行上提供了一个参数,将它用作连接信息串。如:
* ./testlibkci3 "hostaddr=127.0.0.1 port=54321 user=system dbname=test password=123456"
* 否则默认用设置 dbname=kingbase 并且为所有其他链接参数使用环境变量或默认值。
*/
if (argc > 1)
conninfo = argv[1];
else
conninfo = "host=10.12.1.30 port=52222 user=system password=123456 dbname=test sslmode=disable";
//conninfo = "dbname = kingbase";
/* 建立一个到数据库的连接 */
conn = KCIConnectionCreate(conninfo);
/* 检查看后端连接是否成功被建立 */
if (KCIConnectionGetStatus(conn) != CONNECTION_OK)
{
fprintf(stderr, "Connection to database failed: %s",
KCIConnectionGetLastError(conn));
exit_nicely(conn);
}
/* 设置保证安全的搜索路径,这样恶意用户就无法取得控制。*/
res = KCIStatementExecute(conn, "SET search_path = testlibkci3");
if (KCIResultGetStatusCode(res) != EXECUTE_COMMAND_OK)
{
fprintf(stderr, "SET failed: %s", KCIConnectionGetLastError(conn));
KCIResultDealloc(res);
exit_nicely(conn);
}
KCIResultDealloc(res);
/* 创建所需的表 */
res = KCIStatementExecute(conn, "DROP TABLE if exists test1");
KCIResultDealloc(res);
res = KCIStatementExecute(conn, "CREATE TABLE test1 (i int4, t text, b bytea)");
KCIResultDealloc(res);
/*
* 批量多行插入
* 定义 bool 类型变量 b 来接收 testBatchInsert 的结果,成功则返回 true
*/
bool b = testBatchInsert();
testExtendQuery();
printf("testBatchInsert %s\n", (b?"success.":"failed!"));
/*
* 批量多行修改
* 定义 bool 类型变量 b 来接收 testBatchUpdate 的结果,成功则返回 true
*/
b = testBatchUpdate();
testExtendQuery();
printf("testBatchUpdate %s\n", (b?"success.":"failed!"));
/* 关闭数据库的连接并清理 */
KCIConnectionDestory(conn);
return 0;
}




