暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

人大金仓数据库KingbaseES LIBKCI批量操作用例支持

原创 数据猿 2023-12-26
1411


关键字:

客户端编程接口、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;

}

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论