动态SQL语句在编译时可能不知道有多少列信息。在ESQL语句中,这些不确定的数据是通过SQLDA完成的。理解SQLDA的结构是理解动态SQL的关键。SQLDA的结构可参考《C语言访问INFORMIX数据库 — SQLDA结构》,此篇主要通过代码来分析SQLDA的使用。[注意:此文是基于前一博文的补充,接口定义和类型定义请参考《C语言访问INFORMIX数据库 — 接口实现》]
在接口实现中的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的内存结构图为:
图1 变量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. 由申请的空间大小的值,可以发现当取多行数据时,同一列的结果值在内存空间是并排存放的。其内存结构图:
图2 结果集存储结构图
图2说明:
1. 图上每个格子代表一列数据
2. 行列关系:(注:似乎INFORMIX数据库FETCH一次只能取一行数据,也就不存在行列关系的计算了)
A:处在第一个sqlvar_struct中的结果集sqldata中第一列代表第一行第一列,第二列代表第二行第一列,第三列代表第三行第一列,....,以此类推;
B:处在第二个sqlvar_struct中的结果集sqldata中第一列代表第一行第二列,第二列代表第二行第二列,第三列代表第三行第二列, ...,以此类推;
C:依照AB的规律可推出其他的行列关系
每次执行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