C语言访问INFORMIX数据库 — SQLDA使用

1 前言概述

  动态SQL语句在编译时可能不知道有多少列信息。在ESQL语句中,这些不确定的数据是通过SQLDA完成的。理解SQLDA的结构是理解动态SQL的关键。SQLDA的结构可参考《C语言访问INFORMIX数据库 — SQLDA结构》,此篇主要通过代码来分析SQLDA的使用。[注意:此文是基于前一博文的补充,接口定义和类型定义请参考《C语言访问INFORMIX数据库 — 接口实现》]


2 用法分析

2.1 DESCRIBE ... INTO

  在接口实现中的db_ifx_mquery()函数中有这样一段代码:

    ifx_sqlda_t *sqlda = NULL;

    ...

    EXEC SQL DESCRIBE QUERY_SQLSTMT_ID INTO sqlda;    

代码段1

代码分析:

  定义变量sqlda为一个空指针,但执行DESCRIBE ... INTO后,就能对sqlda进行其他操作处理了。我想这时每个人看到这里心中都有一个疑问:DESCRIBE ... INTO到底对空指针sqlda做了什么处理?

  1) 分配空间

    1. 定义变量sqlda为空指针,但DESCRIBE...INTO之后就能够进行操作,很显然,DESCRIBE...INTO为变量sqlda分配了内存空间

    2. 分配的空间包括:sqlda和sqlda->sqlvar。且sqlda->sqlvar指向大小为sqld*sizeof(struct sqlvar_struct)内存块。(sqlvar:指向struct sqlvar_struct结构体,即指向描述第一列信息的sqlvar结构体)

    3. 但未给sqlda->sqlvar中其他指针分配内存

  2) 获取语句信息,并存放在ifx_sqlda_t结构中

    获取的语句信息包括:

    sqld:使用的sqlvar结构的个数,即:输出列的个数

    sqlvar:指向struct sqlvar_struct结构体,即:指向描述第一列信息的sqlvar结构体

    desc_name:sqlda名称

    desc_occ:sqlda结构的大小

    sqltype:代表参数或列的数据类型。它是一个整数数据类型代码。

    sqllen:代表传送数据的长度

    sqlname:代表列名或变量名

    有了以上的语句信息,就可以为后续数据行的空间分配提供依据。

    举例分析:

      假设需执行查询2列的SQL语句,在执行DESCRIBE...INTO后,变量sqlda的内存结构图为:

C语言访问INFORMIX数据库 — SQLDA使用_第1张图片

图1 变量sqlda的内存结构图

2.2 SQLDA初始化

/* 依据sqlda的信息,初始化其他数据 */
int db_ifx_init_sqlda(db_ifx_cntx_t *context, ifx_sqlda_t *sqlda)
{
	int ret = 0,
	    idx = 0,
	    msg_len = 0,
	    row_size = 0,
	    alloc_num = 0;
	struct sqlvar_struct *sqlvar = NULL;
	
    context->result = sqlda;

	/* Step 1. 获取一行数据的长度 */
	sqlvar = sqlda->sqlvar;
	for(idx=0; idx<sqlda->sqld; idx++, sqlvar++)
	{
            /* 非C下一行数据的长度 */
            msg_len += sqlvar->sqllen;

	        /* 为col->sqllen 重新赋值,该值是在C下的大小。
			如:在数据库中的字符串,在C中应该多一个字节空间来存放NULL的结束符 */
	        sqlvar->sqllen = rtypmsize(sqlvar->sqltype, sqlvar->sqllen);

            /* C下一行数据的长度 */
            row_size += sqlvar->sqllen;
	}

        /* Step2. 设置FetArrSize的值 */
	if(FetArrSize < 0)
	{
		if(FetBufSize <= 0)
		{
			FetBufSize = 4096; /* 默认值为4096 */
		}
		FetArrSize = FetBufSize/msglen;
	}

     alloc_num = (FetArrSize <= 0)? 1:FetArrSize;

	/* Step3. 初始化列:列的取值分配空间等 */
	sqlvar = sqlda->sqlvar;
	for(idx=0; idx<sqlda->sqld; idx++, sqlvar++)
	{
		ret = db_ifx_set_sqltype(sqlvar);
		if(ret < 0)
		{
			return ret;
		}

		ret = db_ifx_init_sqldata(context, sqlvar, alloc_num);
		if(ret < 0)
		{
			return ret;
		}
	}

	return msg_len;
}

代码段2

代码说明:
  1. 全局变量FetBufSize、FetArrSize说明
    FetBufSize:是INFORMIX中的全局变量。此值决定了取数据库数据时的缓存大小。默认值为4096。
    FetArrSize:是INFORMIX中的全局变量。此值决定了一次FETCH可以从数据库取多少行数据。默认值为0。

  2. 变量alloc_num的值决定了后续初始化过程中空间申请大小的依据.[注:似乎INFORMIX数据库FETCH一次只能取一行数据]

/* 依据数据库中的数据类型,设置C下数据类型 */
int db_ifx_set_sqltype(struct sqlvar_struct *sqlvar)
{	
	switch(sqlvar->sqltype)
	{
		case SQLBOOL:
		{
			sqlvar->sqltype = CBOOLTYPE;
			break;
		}
		case SQLSMINT:
		{
			sqlvar->sqltype = CSHORTTYPE;
			break;
		}
		case SQLINT:
		{
			sqlvar->sqltype = CINTTYPE;
			break;
		}
		case SQLINT8:
		case SQLSERIAL:
		case SQLSERIAL8:
		{
			sqlvar->sqltype = CINT8TYPE;
			break;
		}
		case SQLBIGSERIAL:
		case SQLINFXBIGINT:
		{
			sqlvar->sqltype = CBIGINTTYPE;
			break;
		}
		case SQLDECIMAL:
		{
			sqlvar->sqltype = CDECIMALTYPE;
			break;
		}
		case SQLSMFLOAT:
		{
			sqlvar->sqltype = CFLOATTYPE;
			break;
		}
		case SQLFLOAT:
		{
			sqlvar->sqltype = CDOUBLETYPE;
			break;
		}
		case SQLCHAR:
		{
			sqlvar->sqltype = CCHARTYPE;
			break;
		}
		case SQLNCHAR:
		{
			sqlvar->sqltype = CFIXCHARTYPE;
			break;
		}
		case SQLVCHAR:
		case SQLNVCHAR:
		{
			sqlvar->sqltype = CVCHARTYPE;
			break;
		}
		case SQLLVARCHAR:
		{
			sqlvar->sqltype = CLVCHARTYPE;
			break;
		}
		case SQLMONEY:
		{
			sqlvar->sqltype = CMONEYTYPE;
			break;
		}
		case SQLINTERVAL:
		{
			sqlvar->sqltype = CINVTYPE;
			break;
		}
		case SQLDATE:
		{
			sqlvar->sqltype = CDATETYPE;
			break;
		}
		case SQLDTIME:
		{
			sqlvar->sqltype = CDTIMETYPE;
			break;
		}
		case SQLROW:
		{
			sqlvar->sqltype = CROWTYPE;
			break;
		}
		case SQLSET:
		case SQLLIST:
		case SQLMULTISET:
		case SQLCOLLECTION:
		{
			sqlvar->sqltype = CCOLLTYPE;
			break;
		}
		case SQLTEXT:
		case SQLBYTES:
		{
			sqlvar->sqltype = CLOCATORTYPE;
			break;
		}
		default: /* Other data type */
		{
			return -1;
		}
	}

	return 0;
}

代码段3

代码说明:
  1.  数据类型的转换对应关系,请参[http://blog.csdn.net/royalapex/article/details/8205654]

/* 初始化sqldata数据空间 */
int db_ifx_init_sqldata(db_ifx_cntx_t *context, struct sqlvar_struct *sqlvar, int alloc_num)
{
	char errmsg[DB_ERR_MSG_MAX_LEN] = {0};
	int ret = 0, alloc_size = 0;
	
	/* 1. 为指示符变量申请空间 */
	sqlvar->sqlind = (short *)calloc(alloc_num, sizeof(short));
	if(NULL == sqlvar->sqlind)
	{
		return -1;
	}

	/* 2. 为存放非TEXT 和BLOB的数据类型的sqldata申请空间.
		注意: 申请的地址是(char *),在输出数据时,要按照相应的数据类型做转换 */
	if(CLOCATORTYPE != sqlvar->sqltype)
	{
		alloc_size = alloc_num*sqlvar->sqllen;
		if(sqlvar->sqllen > context->convert_size)
		{
			context->convert_size = sqlvar->sqllen;
		}

		sqlvar->sqldata = (char*)calloc(1, alloc_size);
		if(NULL == sqlvar->sqldata)
		{
			return -1;
		}
		return 0;
	}

	/* 3. 为TEXT和BLOB的数据类型的sqldata申请空间 */
	return db_ifx_alloc_loc(context, sqlvar, alloc_num);
}

代码段4

/* 申请loc_t类型的数据空间 */
int db_ifx_alloc_loc(db_ifx_cntx_t *context, struct sqlvar_struct *sqlvar, int alloc_num)
{
	char errmsg[DB_ERR_MSG_MAX_LEN] = {0};
	int idx = 0, alloc_size = 0;
	loc_t *loc = NULL;

	alloc_size = alloc_num*sqlvar->sqllen;
	if(sqlvar->sqllen > context->convert_size)
	{
		context->convert_size = sqlvar->sqllen;
	}

	/* 1. 为存放TEXT或BYTE列数据申请空间 */
	loc = (loc_t*)calloc(1, alloc_size);
	if(NULL == loc)
	{ 
		return -1;				
	}
	sqlvar->sqldata = (char *)loc;

	/* 2. 初试化loc_t结构 */
	byfill(loc, alloc_size, 0);

	for(idx=0; idx<alloc_num; idx++)
	{
		loc->loc_loctype = LOCMEMORY;
		loc->loc_bufsize = DB_IFX_BLOB_SIZE;

		loc->loc_buffer = (char *)calloc(1, DB_IFX_BLOB_SIZE);
		if(NULL == loc->loc_buffer)
		{
			return -1;
		}
		
		loc->loc_oflags = 0;
		loc++;
	}
	
	return 0;
}

代码段5

代码分析:
  1. 由申请的空间大小的值,可以发现当取多行数据时,同一列的结果值在内存空间是并排存放的。其内存结构图:

C语言访问INFORMIX数据库 — SQLDA使用_第2张图片

图2 结果集存储结构图

图2说明:

  1. 图上每个格子代表一列数据

  2. 行列关系:(注:似乎INFORMIX数据库FETCH一次只能取一行数据,也就不存在行列关系的计算了)

    A:处在第一个sqlvar_struct中的结果集sqldata中第一列代表第一行第一列,第二列代表第二行第一列,第三列代表第三行第一列,....,以此类推;

    B:处在第二个sqlvar_struct中的结果集sqldata中第一列代表第一行第二列,第二列代表第二行第二列,第三列代表第三行第二列, ...,以此类推;

    C:依照AB的规律可推出其他的行列关系

2.3. 获取数据

  每次执行FETCH后,取到的结果集就会存放在sqlda之中,应用程序如何通过行号-列号 或 行号-列名获取对应列的取值呢?可通过如下方式:(行号:指结果集开始位置的相对行号,而不是数据库中的行号)(注:似乎INFORMIX数据库FETCH一次只能取一行数据,也就不存在行列关系的计算了)

/* 根据 行号+列号 从结果集中取数据 */
char *db_ifx_get_data_by_idx(int rowno, int col, void *cntx)
{
	ifx_sqlda_t *current = NULL;
	struct sqlvar_struct *sqlvar = NULL;
	db_ifx_cntx_t *context = (db_ifx_cntx_t *)cntx;

	if(NULL == context->result
		|| NULL == context->result->sqlvar
		|| col <= 0
		|| col > context->result->sqld
		|| row <= 0
		|| row > context->rows)
	{
		return NULL;
	}

	/* 1. 定位列 */
	sqlvar = context->result->sqlvar+col-1;

	/* 2. 返回结果 */
	return db_ifx_get_value(sqlvar, context, rowno);
}

代码段6

/* 通过 行号+列明 从结果集中取数据 */
char *db_ifx_get_data_by_name(int rowno, const char *name, db_ifx_cntx_t *context)
{
	int idx = 0;
	struct sqlvar_struct *sqlvar = NULL;
	if(NULL == context->result
		|| NULL == context->result->sqlvar
		|| rowno <= 0
		|| rowno > context->rows)
	{
		return NULL;
	}

	/* 1. 定位列 */
	sqlvar = context->result->sqlvar;
	for(idx=0; idx<context->result->sqld; idx++)
	{
		if(0 == strcasecmp(name, sqlvar->sqlname))
		{
			break;
		}
		sqlvar++;
	}
	
	if(idx == context->result->sqld)
	{		
		return NULL;
	}
	 
	/* 3. 返回结果 */
	return db_ifx_get_value(sqlvar, context, rowno);
}

代码段7

/* 转换并以字符串形式返回结果集数据 */
char *db_ifx_get_value(struct sqlvar_struct *sqlvar,  db_ifx_cntx_t *cntx, int rowno)
{
	int ret = 0;
	loc_t *loc = NULL;
	char *data = sqlvar->sqldata + (rowno-1)*sqlvar->sqllen,
	     *convert = cntx->convert,
	     *pconvert = NULL;

	memset(convert, 0, cntx->convert_size);
	
	switch (sqlvar->sqltype)
	{
		case CBOOLTYPE:
		{
			snprintf(convert, cntx->convert_size, "%d", *((unsigned char*)data));
			return convert;
		}
		case CSHORTTYPE:
		{
			snprintf(convert, cntx->convert_size, "%d", *((short*)data));
			return convert;
		}
		case CINTTYPE:
		{
			snprintf(convert, cntx->convert_size, "%d", *((int*)data));
			return convert;
		}
		case CLONGTYPE:
		{
			snprintf(convert, cntx->convert_size, "%l", *((long*)data));
			return convert;
		}
		case CINT8TYPE:
		{
			snprintf(convert, cntx->convert_size, "%ll", *((long long*)data));
			return convert;
		}
		case CBIGINTTYPE:
		{
			snprintf(convert, cntx->convert_size, "%ll", *((long long*)data));
			return convert;
		}
		case CDECIMALTYPE:
		{
			pconvert = convert;
			dectoasc((dec_t*)data, convert, cntx->convert_size, -1);
			/* Note: dectoasc() Left align and fill blank, so must delete blank */
			while('\0' != *pconvert)
			{
				if(isblank(*pconvert))
				{
					*pconvert = '\0';
					break;
				}
				pconvert++;
			}
			return convert;
		}
		case CFLOATTYPE:
		{
			snprintf(convert, cntx->convert_size, "%f", (double)(*(float*)data));
			return convert;
		}
		case CDOUBLETYPE:
		{
			snprintf(convert, cntx->convert_size, "%f", *((double*)data));
			return convert;
		}
		case CMONEYTYPE:
		{
			snprintf(convert, cntx->convert_size, "%d", *(int*)data);
			return convert;
		}
		case CINVTYPE:
		{
			intoasc((intrvl_t*)data, convert);
			return convert;
		}
		case CDATETYPE:
		{
			rfmtdate(*(int*)data, "YYYYMMDD", convert);
			return convert;
		}
		case CDTIMETYPE:
		{
			dttoasc((dtime_t*)data, convert);
			return convert;
		}
		case CROWTYPE:
		case CCOLLTYPE:
		{
			return data;
		}
		case CCHARTYPE:
		case CFIXCHARTYPE:
		case CVCHARTYPE:
		case CLVCHARTYPE:
		{
			return data;
		}
		case CLOCATORTYPE:
		{
			loc = (loc_t *)sqlvar->sqldata;

			return loc->loc_buffer;
		}
		default:
		{
			return NULL;
		}
	} 

	return NULL;
}

代码段8

你可能感兴趣的:(C语言访问INFORMIX数据库 — SQLDA使用)