本节,我将介绍一种新的句柄类型——SQLHSTMT
语句句柄。SQLHSTMT
的父级是连接句柄。您必须先释放所有子语句句柄,然后才能释放连接句柄。ODBC 结果处理模型比其他 PostgreSQL API 更复杂。在 libpq、libpq++ 和 libpgeasy API 中,您向服务器发送查询,然后调用函数来访问结果集中的每个字段(每行中)。
ODBC 应用程序通常使用不同的方案。将查询发送到服务器后,将结果集中的每个字段绑定到应用程序中的变量。绑定所有结果字段后,您可以获取结果集中的各个行,每次获取新行时,绑定变量都会由 ODBC 填充。
示例3 展示了如何执行查询并显示结果
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#include <stdio.h>
#include <stdlib.h>
typedef enum {FALSE, TRUE} bool;
static bool SQL_OK(SQLRETURN result){
if(result==SQL_SUCCESS || result==SQL_SUCCESS_WITH_INFO)
return(TRUE);
else
return(FALSE);
}
typedef struct{
char name[128+1];
SQLSMALLINT nameLength;
SQLSMALLINT dataType;
SQLULEN fieldLength;
SQLSMALLINT scale;
SQLSMALLINT nullable;
SQLLEN displaySize;
int headerLength;
SQLLEN resultLength;
char *value;
} resultField;
static void printResultSet(SQLHSTMT stmt);
这里唯一的新内容是resultField
结构体。我将使用resultField
数组来处理结果集。这里有一个关于术语的注释:PostgreSQL 文档对字段和列进行了细微的区分。列指数据库中的列,而字段可以指列或计算值。ODBC 不做这种区分。
static bool printErrors(SQLHENV envHandle,
SQLHDBC conHandle,
SQLHSTMT stmtHandle){
SQLRETURN result;
SQLCHAR sqlState[6];
SQLINTEGER nativeError;
SQLSMALLINT requiredLength;
SQLCHAR messageText[SQL_MAX_MESSAGE_LENGTH+1];
do{
result = SQLError(envHandle,
conHandle,
stmtHandle,
sqlState,
&nativeError,
messageText,
sizeof(messageText),
&requiredLength);
if(SQL_OK(result)){
printf("SQLState = %s\n", sqlState);
printf("Native error = %d\n", nativeError);
printf("Message text = %s\n", messageText);
}
} while(SQL_OK(result));
}
您已经在前面的示例中看到了SQL_OK()
和printErrors()
,因此我不会在这里费心解释它们。
static void executeStmt(SQLHDBC con, char *stmtText){
SQLHSTMT stmt;
SQLAllocHandle(SQL_HANDLE_STMT, con, &stmt);
if(SQL_OK(SQLExecDirect(stmt, stmtText, SQL_NTS)))
printResultSet(stmt);
else
printErrors(SQL_NULL_HENV, SQL_NULL_HDBC, stmt);
}
executeStmt ()
函数负责向服务器发送查询。首先分配一种新类型的句柄SQLHSTMT
。SQLHSTMT
是一个语句句柄。语句句柄的父级始终是连接句柄(或 SQLHDBC
)。
获得语句句柄后,使用SQLExecDirect()
将查询发送到服务器。SQLExecDirect()
非常简单,您提供一个语句句柄、要发送到服务器的查询文本以及查询字符串的长度(或SQL_NTS
来指示查询文本是一个以 null 结尾的字符串) 。
如果SQLExecDirect()
返回成功值,则调用printResultSet()
来处理结果集。
static void printResultSet(SQLHSTMT stmt){
SQLSMALLINT i;
SQLSMALLINT columnCount;
resultField *fields;
//首先,检查元数据,以便我们知道有多少结果集,我们拥有的字段和每个字段需要多少空间
SQLNumResultCols(stmt, &columnCount);
fields = (resultField *)calloc(columnCount+1, sizeof(resultField));
for(i=1;i<=columnCount;i++){
SQLDescribeCol(stmt,
i,
fields[i].name,
sizeof( fields[i].name),
&fields[i].nameLength,
&fields[i].dataType,
&fields[i].fieldLength,
&fields[i].scale,
&fields[i].nullable);
SQLColAttribute(stmt, i, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &fields[i].displaySize);
fields[i].value = (char *)malloc(fields[i].displaySize+1);
if(fields[i].nameLength > fields[i].displaySize)
fields[i].headerLength = fields[i].nameLength;
else
fields[i].headerLength = fields[i].displaySize;
}
//现在打印出列标题
for(i=1;i<=columnCount;i++){
printf( "%-*s ", fields[i].headerLength, fields[i].name );
}
printf( "\n" );
//现在获取并显示结果…
while(SQL_OK(SQLFetch(stmt))){
for(i=1;i<=columnCount;i++){
SQLRETURN result;
result = SQLGetData(stmt,
i,
SQL_C_CHAR,
fields[i].value,
fields[i].displaySize,
&fields[i].resultLength);
if(fields[i].resultLength == SQL_NULL_DATA)
printf("%-*s", fields[i].headerLength, "");
else
printf("%-*s", fields[i].headerLength, fields[i].value);
}
printf("\n");
}
for(i=1;i<=columnCount;i++)
free(fields[i].value);
free(fields);
}
printResultSet ()
函数有些复杂。它首先构建一个resultField
结构体数组来跟踪刚刚执行的查询的元数据。
首先调用SQLNumResultCols()
来确定结果集中将出现多少个字段(或列)。在知道要处理多少个字段后,您可以分配一个resultField
结构数组——每个字段一个结构(以及一个额外的结构以简化代码)。
接下来,您调用两个元数据函数,以便了解每个字段返回的信息类型。SQLDescribeCol()
函数返回给定字段的列名、数据类型、二进制字段长度、小数位数(用于数字数据类型)以及可为空性。请注意,字段索引从1开始,而不是0。
SQLColAttribute()
函数返回给定列 ( i )的特定元数据属性。您将以空终止字符串的形式检索每个字段,因此您需要知道每个字段的最大显示长度。SQL_DESC_DISPLAY_SIZE
属性正是您所需要的。
SQLDescribeCol()
和SQLColAttribute()
函数都返回与列相关的元数据。SQLDescribeCol()
是一个方便的函数,它返回最常用的元数据属性。调用SQLDescribeCol()
相当于
SQLColAttribute(stmt, column, SQL_DESC_NAME, ...);
SQLColAttribute(stmt, column, SQL_DESC_TYPE, ...);
SQLColAttribute(stmt, column, SQL_DESC_LENGTH, ...);
SQLColAttribute(stmt, column, SQL_DESC_SCALE, ...);
SQLColAttribute(stmt, column, SQL_DESC_NULLABLE, ...);
检索并存储列的元数据后,您可以分配一个足够大的缓冲区来包含以 null 结尾的字符串形式的列数据。您还可以计算标头长度。您希望在足够大的水平空间中打印每列,以容纳列名称或列内容(以较长者为准)。
打印出列标题(第 122 到 126 行)后,我们开始处理结果集的内容。SQLFetch()
函数将获取与给定SQLHSTMT
关联的结果集中的下一行。当您耗尽结果集时,SQLFetch()
将返回值SQL_NO_DATA
。
ODBC 元数据类型到目前为止,我们只查看了描述结果集的元数据。由于 ODBC 被设计为应用程序和后端数据库之间的可移植层,因此 ODBC 提供了一组丰富的元数据功能。首先,您可以使用 SQLDataSources()
函数检索系统上定义的数据源列表。SQLDrivers()
函数将检索已安装驱动程序的列表。连接到数据源后,可以通过调用SQLGetTypeInfo()
检索支持的数据类型的列表。该函数将列表作为结果集返回?您可以使用SQLFetch()
和SQLGetData()
来获取列表。您可以使用SQLFunctions()
来确定给定驱动程序支持哪些 ODBC API 函数。PostgreSQL ODBC 驱动程序(当前)是 ODBC 2.5 驱动程序,不直接支持 ODBC 3.0 函数。PostgreSQL 驱动程序不支持一些 ODBC 2.5 函数(例如SQLProcedures()
、SQLProcedureColumns()
和SQLBrowseConnect(
))。您还可以询问驱动程序是否支持各种SQL语法功能。例如,如果调用SQLGetInfo(..., SQL_CREATE_TABLE, ...)
,则可以确定数据库的CREATE TABLE
语句支持哪些CREATE TABLE
子句。SQLGetInfo()
函数还返回版本信息,如表6所示。
SQLGetInfo() InfoType Argument |
Return Information |
---|---|
SQL_DBMS_VER |
Database version (for example, PostgreSQL 7.1.3) |
SQL_DM_VER |
Driver manager version |
SQL_DRIVER_NAME |
Driver name |
SQL_DRIVER_ODBC_VER |
ODBC version that driver conforms to |
SQL_DRIVER_VER |
Driver version |
SQL_SERVER_NAME |
Name of server |
您可以使用SQLGetInfo(..., SQL_TXN_CAPABLE, ...)
来了解数据库的事务处理功能。据我统计,SQLGetInfo()
可以返回超过 150 条有关数据源的不同信息!
如果SQLFetch()
成功,则可以使用SQLGetData()
函数检索当前行中的每一列,该函数具有以下原型:
SQLRETURN SQLGetData(SQLHSTMT stmtHandle,
SQLUSMALLINT columnNumber,
SQLSMALLINT desiredDataType,
SQLPOINTER destination,
SQLINTEGER destinationLength,
SQLINTEGER *resultLength);
当您调用SQLGetData()
时,您希望 ODBC 将数据放入fields[i].value
缓冲区中,以便您传递该地址(和displaySize
)。传入SQL_C_CHAR
的所需数据类型告诉 ODBC 以空终止字符串的形式返回每列。SQLGetData()
返回fields[i].resultLength
中的实际字段长度?如果字段为NULL
,您将返回值SQL_NULL_DATA
。
第 143-146 行打印每个字段(在fields[i].headerLength
空间内左对齐)。
最后,通过释放值缓冲区和resultField
数组来进行清理:
int main(int argc, char * argv[]){
SQLRETURN res;
SQLHENV henv;
SQLHDBC conn;
SQLCHAR fullConnectStr[SQL_MAX_OPTION_STRING_LENGTH];
SQLSMALLINT requiredLength;
// 1.申请环境句柄
res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if(SQL_OK(res)){
// 2.设置环境属性
res = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC2, 0);
if(!SQL_OK(res)){
printErrors(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
exit(-1);
}
// 3.申请连接句柄
res = SQLAllocHandle(SQL_HANDLE_DBC, henv, &conn);
if(!SQL_OK(res)){
printErrors(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
exit(-2);
}
res = SQLConnect(conn, // connection handle
argv[1], SQL_NTS, // data source name
argv[2], SQL_NTS, // user name
argv[3], SQL_NTS); // password
if(!SQL_OK(res)){
printErrors(SQL_NULL_HENV, conn, SQL_NULL_HSTMT);
exit(-3);
}
printf("connection ok\n");
executeStmt(conn, argv[4]);
res = SQLDisconnect(conn);
if(!SQL_OK(res)){
printErrors( SQL_NULL_HENV, conn, SQL_NULL_HSTMT );
exit(-4);
}
res = SQLFreeHandle(SQL_HANDLE_DBC, conn);
if(!SQL_OK(res)){
printErrors(SQL_NULL_HENV, conn, SQL_NULL_HSTMT);
exit(-5);
}
res = SQLFreeHandle(SQL_HANDLE_ENV, henv);
if(!SQL_OK(res)){
printErrors(henv, SQL_NULL_HDBC, SQL_NULL_HSTMT);
exit(-6);
}
}
exit(0);
}
如果要运行此程序,执行如下:
gcc -o client3 client3.c -I /home/postgres/odbc/include -L /home/postgres/odbc/lib -lodbc
./client3 PSQLODBC postgres postgresql@123 'select * from a'
此示例向您展示了在 ODBC 应用程序中执行查询和处理结果的最简单方法。
标签:res,句柄,查询处理,ODBC,stmt,SQL,NULL,客户端 From: https://www.cnblogs.com/jl1771/p/17986060