odbc读写二进制数据
这里使用mariadb,其他数据库的操作应该是类似的。
MySQL字符串和二进制
数据类型mariadb官方文档
odbc api官方文档
@
定长二进制
使用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;
}