本节,我将介绍一种新的句柄类型——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 ()函数负责向服务器发送查询。首先分配一种新类型的句柄SQLHSTMTSQLHSTMT是一个语句句柄。语句句柄的父级始终是连接句柄(或 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 应用程序中执行查询和处理结果的最简单方法。

posted on 2024-01-24 23:00  jl1771  阅读(8)  评论(0编辑  收藏  举报