odbc读写二进制数据

阅读量: searchstar 2020-04-30 01:34:26
Categories: Tags:

这里使用mariadb,其他数据库的操作应该是类似的。

MySQL字符串和二进制
数据类型mariadb官方文档
odbc api官方文档

@[TOC]
# 定长二进制
使用BINARY保存定长二进制。
这里以保存哈希后的密码为例。
## 定义表

CREATE TABLE pw (
id BIGINT UNSIGNED PRIMARY KEY,
pw BINARY(4)
)

写入

官方文档中使用了SQLPutData
但是比较麻烦。这里直接使用SQLBindParameter把数据绑定到参数上,然后用SQLExecDirect执行insert即可。
关键代码:

SQLLEN length = sizeof(binary);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
sizeof(binary), 0, binary, sizeof(binary), &length);
const char *stmt = "INSERT INTO pw VALUES(3, ?);";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);

注意SQLBindParameter的最后一个参数如果为NULL,则相当于告诉odbc遇到NULL时停止,也就是null-terminated。如果要写入含NULL的数据,则必须指定要写入的数据长度。

读取

直接用SQLExecDirect执行select,然后用SQLBindCol绑定参数,然后用SQLFetch把数据读出来即可。
关键代码:

retcode = SQLExecDirect(hstmt, (SQLCHAR*)"SELECT * FROM pw;", SQL_NTS);
SQLBindCol(hstmt, 1, SQL_C_UBIGINT, &id, sizeof(id), &length);
SQLBindCol(hstmt, 2, SQL_C_BINARY, binary, sizeof(binary), &length);
while (SQL_NO_DATA != SQLFetch(hstmt)) {
cout << id << '\t' << hex << *(uint32_t*)binary << endl;
}

## 完整代码
#include <iostream>
#include <cstring>

#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#endif
#include <sqlext.h>

using namespace std;

SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHSTMT hstmt = SQL_NULL_HSTMT;

void Cleanup() {
//释放语句句柄
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

if (hdbc != SQL_NULL_HDBC) {
//断开数据库连接
SQLDisconnect(hdbc);
//释放连接句柄
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}

//释放环境句柄句柄
if (henv != SQL_NULL_HENV)
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

int main() {
SQLRETURN retcode;
SQLLEN length;

// Allocate the ODBC environment and save handle.
retcode = SQLAllocHandle (SQL_HANDLE_ENV, NULL, &henv);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLAllocHandle(Env) Failed\n\n");
Cleanup();
return(9);
}

// Notify ODBC that this is an ODBC 3.0 app.
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLSetEnvAttr(ODBC version) Failed\n\n");
Cleanup();
return(9);
}

// Allocate ODBC connection handle and connect.
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLAllocHandle(hdbc1) Failed\n\n");
Cleanup();
return(9);
}

//数据库连接
//第二个参数是之前配置的数据源,后面是数据库用户名和密码,如果数据源中已经指定了就直接写NULL即可。
retcode = SQLConnect(hdbc,(SQLCHAR*)"company",SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLConnect() Failed\n\n");
Cleanup();
return(9);
}

//分配执行语句句柄
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLAllocHandle(hstmt1) Failed\n\n");
Cleanup();
return(9);
}

retcode = SQLExecDirect(hstmt,(SQLCHAR*)"use test;", SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: use test;\n\n");
Cleanup();
return(9);
}

SQLUBIGINT id;
uint8_t binary[4] = {0x01, 0x02, 0x00, 0x04};
length = sizeof(binary);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
sizeof(binary), 0, binary, sizeof(binary), &length);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLBindParameter Failed\n\n");
Cleanup();
return(9);
}

const char *stmt = "INSERT INTO pw VALUES(6, ?);";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: %s\n\n", stmt);
Cleanup();
return(9);
}

id = 2333;
memset(binary, 0, sizeof(binary));
retcode = SQLExecDirect(hstmt, (SQLCHAR*)"SELECT * FROM pw;", SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: SELECT * FROM pw;\n\n");
Cleanup();
return(9);
}
cout << "id\tpw\n";
SQLBindCol(hstmt, 1, SQL_C_UBIGINT, &id, sizeof(id), &length);
SQLBindCol(hstmt, 2, SQL_C_BINARY, binary, sizeof(binary), &length);
while (SQL_NO_DATA != SQLFetch(hstmt)) {
cout << id << '\t' << hex << *(uint32_t*)binary << endl;
}

Cleanup();

return 0;
}

结果:
6	4000201

可见已经把数据写入了,只是因为我的机器是小端字节序的,所以字节序反了。
# 非定长二进制
blob系列和varbinary都可以保存非定长二进制。
它们的区别是blob系列的数据是存储在表外的,而varbinary的数据是存储在表内的,所以blob在select的时候相当于定长域,效率比较高,但是需要额外从表外取数据。而binary在select的时候相当于变长域,会影响效率,但是可以直接从表中获取数据。

blob系列官方文档:
TINYBLOB: 最长255字节
BLOB: 最长65535字节
LONGBLOB: 4,294,967,295字节

varbinary文档:https://mariadb.com/kb/en/varbinary/

blob

建表

CREATE TABLE msgcontent(
msgid BIGINT UNSIGNED PRIMARY KEY,
content BLOB(2000)
);

写入

关键代码:

uint8_t content[11] = {1, 2, 3, 4, 5, 0, 7, 8, 9, 10};
SQLLEN contentLen = sizeof(content);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
MAX_CONTENT_LEN, 0, content, sizeof(content), &contentLen);
const char *stmt = "INSERT INTO msgcontent VALUES(3, ?);";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);

注意SQLBindParameter的最后一个参数如果为NULL,则相当于告诉odbc遇到NULL时停止,也就是null-terminated。如果要写入含NULL的数据,则必须指定要写入的数据长度。

读取

关键代码:

   SQLUBIGINT id = 3332;
memset(content, 0, sizeof(content));
stmt = "SELECT * FROM msgcontent;";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);
SQLLEN idLen;
cout << "id\tcontent\n";
SQLBindCol(hstmt, 1, SQL_C_UBIGINT, &id, sizeof(id), &idLen);
SQLBindCol(hstmt, 2, SQL_C_BINARY, content, sizeof(content), &contentLen);
while (SQL_NO_DATA != SQLFetch(hstmt)) {
cout << id << '\t';
if (SQL_NULL_DATA == contentLen) {
cout << "<NULL>";
} else {
cout << '(' << contentLen << ')';
for (size_t i = 0; i < contentLen; ++i) {
printf("%02x", content[i]);
}
}
cout << endl;
}

### 完整代码
#include <iostream>
#include <cstring>

#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#endif
#include <sqlext.h>

using namespace std;

#define MAX_CONTENT_LEN 2000

SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHSTMT hstmt = SQL_NULL_HSTMT;

void Cleanup() {
//释放语句句柄
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

if (hdbc != SQL_NULL_HDBC) {
//断开数据库连接
SQLDisconnect(hdbc);
//释放连接句柄
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}

//释放环境句柄句柄
if (henv != SQL_NULL_HENV)
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

int main() {
SQLRETURN retcode;

// Allocate the ODBC environment and save handle.
retcode = SQLAllocHandle (SQL_HANDLE_ENV, NULL, &henv);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLAllocHandle(Env) Failed\n\n");
Cleanup();
return(9);
}

// Notify ODBC that this is an ODBC 3.0 app.
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLSetEnvAttr(ODBC version) Failed\n\n");
Cleanup();
return(9);
}

// Allocate ODBC connection handle and connect.
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if ((retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_SUCCESS)) {
printf("SQLAllocHandle(hdbc1) Failed\n\n");
Cleanup();
return(9);
}

//数据库连接
//第二个参数是之前配置的数据源,后面是数据库用户名和密码,如果数据源中已经指定了就直接写NULL即可。
retcode = SQLConnect(hdbc,(SQLCHAR*)"company",SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLConnect() Failed\n\n");
Cleanup();
return(9);
}

//分配执行语句句柄
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLAllocHandle(hstmt1) Failed\n\n");
Cleanup();
return(9);
}

retcode = SQLExecDirect(hstmt,(SQLCHAR*)"use test;", SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: use test;\n\n");
Cleanup();
return(9);
}

uint8_t content[11] = {1, 2, 3, 4, 5, 0, 7, 8, 9, 10};
SQLLEN contentLen = sizeof(content);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY,
MAX_CONTENT_LEN, 0, content, sizeof(content), &contentLen);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLBindParameter Failed\n\n");
Cleanup();
return(9);
}

const char *stmt = "INSERT INTO msgcontent VALUES(3, ?);";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: %s\n\n", stmt);
Cleanup();
return(9);
}

SQLUBIGINT id = 3332;
memset(content, 0, sizeof(content));
stmt = "SELECT * FROM msgcontent;";
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) {
printf("SQLExecDirect Failed: %s\n\n", stmt);
Cleanup();
return(9);
}
SQLLEN idLen;
cout << "id\tcontent\n";
SQLBindCol(hstmt, 1, SQL_C_UBIGINT, &id, sizeof(id), &idLen);
SQLBindCol(hstmt, 2, SQL_C_BINARY, content, sizeof(content), &contentLen);
while (SQL_NO_DATA != SQLFetch(hstmt)) {
cout << id << '\t';
if (SQL_NULL_DATA == contentLen) {
cout << "<NULL>";
} else {
cout << '(' << contentLen << ')';
for (size_t i = 0; i < contentLen; ++i) {
printf("%02x", content[i]);
}
}
cout << endl;
}

Cleanup();

return 0;
}