原代码地址:https://blog.csdn.net/numbibi/article/details/8446704
根据自己的理解加上了注释
程序代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h> //提供了一个类型jmp_buf jb和两个函数setjmp(jp)、longjmp(jp,[1~n]); setjmp(jp)用来保存当前的环境,未使用longjmp时
//其返回值总为0。而当使用了longjmp后,程序会直接跳转到setjmp执行后,而且其返回值会改变,常用来模拟异常处理
#include <sqlcpr.h> // Pro*C产生的与平台相关的SQLLIB函数的ANSI原形定义
#include <sqlda.h> //使用动态SQL语句时,需准备DynamicStagingArea对象(全局对象SQLSA)和DynamicDescriptionArea对象(全局对象SQLDA)
#include <sqlca.h> //系统默认事务对象
//定义绑定变量和选择列表项的最大个数
#define MAX_ITEMS 40
//定义绑定变量和选择列表项名称的最大长度
#define MAX_VNAME_LEN 30
//定义指示变量名称的最大长度
#define MAX_INAME_LEN 30
/*
struct sqlda
{ //SLI=SELECT-List P=占位符 BV=绑定变量 IV=指示变量
long N; //SLI或P的最大个数
char * * V; //指向SLI或BV值的地址数组的指针
long * L; //指向SLI或BV地址的长度数组的指针
short * T; //指向SLI或BV值的数据类型数组的指针
short * * I; //指向IV值的地址数组指针
long F; //SLI或P的实际个数
char * * S; //指向SLI或P名字的地址数组的指针
short * M; //指向SLI或P名字的最大长度数组的指针
short * C; //指向SLI或P名字的当前长度数据组的指针
char * * X; //指向IV名字的地址数组的指针
short * Y; //指向IV名字最大长度数组的指针
short * Z; //指向IV名字当前长度数组的指针
};
P 占位符 SQL语句中不可缺少的类似 :oraName 这样的东西
BV 绑定变量 用来减少硬连接,提高SQL语句的解析效率
IV 指示变量
//这部分注释来源于 https://lj-zhu.iteye.com/blog/682465
N 执行DESCRIBE前必须执行SQLSQLDAAlloc()函数给N进行复制,设定描述数组的维数,其决定了描述字
结构成员数组的最大元素的最大元素个数。在DESCRIBE命令后,必须将存储在F中的变量真实个数赋值
给N。
V 是一个指向一个地址数组的指针,在该数组中存储SELECT-LIST列值或绑定变量的值。当使用SQLSQLDAAlloc
函数之前,系统给V[0]到V[n-]赋值为0.在使用select descriptors时,必须在FETCH语句前,给V指针
数组分配内存空间,应为下列语句要用到两种描述字的具体的值:
EXEC SQL FETCH ... USING 'select descriptors'
对于bind descriptors,必须在OPEN语句前分配空间
EXEC SQL OPEN ... USING 'bind descriptors'
L 是一个指向存放SLI或BV变量值长度数组的指针。对于select descriotprs,DESCRIBE SELECT LIST语
句设置数组中各元素的值,对不在SLI的对应长度设置成该类型的可定义的最大长度 。在FETCH语句前,
用NUMVER类型数据存储到C语言char[]类型,
就需要通过SQLNumberPrecV6()函数得到NUMBER长度,然后转换成字符串后的真实长度。
对于bind descriptors,用户必须在OPEN语句前,给数组成员赋对应V变量成员长度的值。
因为Oracle间接的通过使用存储在V[i]变量的地址访问数据区域,如果不指定L[i]变量,就不能得到
V[i]的存储数据的长度,如果用户需要更改V[i]指定数据区域的长度,只需要简单更改L[i]的值即可
T 指向一个数组的指针,在这个数组中存放着SLI或者BV的数据类型。这个成员决定了Oracle数据存储到
V数组中时如何进行转换。
对于select descriptors,DESCRIBE SELECT LIST语句设置数据类型数组值为Oracle内部数据了类型(
char,number或dare等)。在进行FETCH前,用户可能需要重新更改此数组某元素的值,因为Oracle内部
数据类型很难被C语言所采用。通常为了对SLI对用的select descriptors数据进行线束,需要将数据
转换成varchar2或STRING类型。T[i]的高位用来指示其对应的SLI列值的"NULL/NOT NULL"状态。在使
用OPEN或FETCH语句前,需要调用SQLColumnNummCheck()接受T[i]数据类型值,并清空高位NULL/NOT-
NULL状态。
对于bind descriptors,DECRIBE BIND VARIABLES设置数据类型数组的各元素为0.用户必须在OPEN命
令前设置各输入宿主变量的数据类型。变量类型值采用Oracle外部数据类型描述。通常,绑定变量值
存储在字符串数组中,其对应的T[i]就设置为1(Varchar2)或者5(STRING).
I 这是一个指向存储指示变量值数组的指针。
对于select descriptors,在FETCH语句前必须设置I数组各元素所指向的地址。
EXEC SQL FETCH ... USING 'select descriptor'
如果第i各SLI对应的列值为空,则I[i]值为-1 否则是个》=0的证书。
对于bind descriptors,必须在OPEN语句前设置I数组各变量的值。
EXEC SQL OPEN ... USING 'bind descriptor'
F 存放通过DESCRIBE语句得到的SLI或占位符的真实数组,如果经过DECRIBE语句后F小于零标识DECRIBE
语句发现的SLI数目或占位符数目比分配描述符时指定的最大数据数目N,例如,如果设置N=10但
DESCRIBE发现SLI的数目时11个,那么F将被设置成-11,这允许用户根据此值那么F将被设置成-11,这允许用户根据此值重新调用SQLSQLDAAlloc函数分配大的描述符存储区域。
*/
SQLDA *bindDp; //绑定描述区 用来设置类似 select * from table where ID = :userId 中 userId的值以及相关设置
SQLDA *selectDp; //选择描述区 用来设置使用select语句时,从数据库中FETCH到的值以及相关设置
int OracleConnect(); //数据库连接函数
int GetConfValue(char *, char *); //取得设置文件中的数据,ourVar实际上是为了保存取得的值
void Trim(char *); //去除字符串两边的空格
void AllocDescriptors(int, int, int); //分配空间给选择描述区和绑定描述区
void DeallocDescriptors(); //释放分配给选择描述区和绑定描述区的空间
void SetBindVariables(); //设置绑定变量
void PrintQueryResult(); //输出从数据库查询到的值
int main(int argc, char *argv[])
{
//定义用来存放动态sql语句的宿主变量、
EXEC SQL BEGIN DECLARE SECTION;
char SqlQuery[100];
EXEC SQL END DECLARE SECTION;
//连接数据库
OracleConnect();
//分配绑定描述区与选择描述区
AllocDescriptors(MAX_ITEMS, MAX_VNAME_LEN, MAX_INAME_LEN);
for (; ;)
{
printf("请输入SQL语句(exit退出): \n");
fgets(SqlQuery, 100, stdin);
if ((strncmp(SqlQuery, "exit", 4) == 0) || (strncmp(SqlQuery, "EXIT", 4) == 0))
{
break;
}
//准备动态sql语句
EXEC SQL PREPARE S FROM :SqlQuery;
if (sqlca.sqlcode != 0)
{
continue;
}
//定义游标
EXEC SQL DECLARE C CURSOR FOR S;
if (sqlca.sqlcode != 0)
{
continue;
}
//设置绑定变量
SetBindVariables();
//打开游标
EXEC SQL OPEN C USING DESCRIPTOR bindDp;
printf("open cursor sqlcode : %d\n", sqlca.sqlcode);
printf("list :\n|%s|\n", SqlQuery);
//如果是select语句,那么就要打印结果集
if ((strncmp(SqlQuery, "select", 6) ==0) || (strncmp(SqlQuery, "SELECT", 6) == 0))
{
printf("Printf Query Result:\n");
PrintQueryResult();
}
printf("return code %d\n", sqlca.sqlcode);
//关闭游标 提交结果
EXEC SQL CLOSE C;
EXEC SQL COMMIT WORK;
}
//释放绑定描述符与选择描述符
printf("DEAllocDescriptors :\n");
DeallocDescriptors();
//提交更改 释放数据库连接
printf("EXEC SQL COMMIT WORK RELEASW:\n");
EXEC SQL COMMIT WORK RELEASE;
printf("程序关闭\n");
return 0;
}
int OracleConnect() //数据库连接函数
{
char str[100] = {0};
char port[10] = {0};
char ip[50] = {0};
char threadMax[5] = {0};
EXEC SQL BEGIN DECLARE SECTION;
char oraSid[50] = {0};
char oraUser[50] = {0};
char oraPass[50] = {0};
EXEC SQL END DECLARE SECTION;
if ((GetConfValue("oraSid", oraSid) == 0) || //从配置文件中取得相关信息
(GetConfValue("oraUser", oraUser) == 0) ||
(GetConfValue("oraPass", oraPass) == 0) ||
(GetConfValue("ip", ip) == 0) ||
(GetConfValue("port", port) == 0))
{
printf("value is not found\n");
return 0;
}
//数据库连接
EXEC SQL CONNECT :oraUser IDENTIFIED BY :oraPass using :oraSid;
if (sqlca.sqlcode != 0)
{
perror("db connect :");
printf("sqlcode = %d ->%s ->%s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf("Connect success\n");
return 1;
}
int GetConfValue(char *type, char *outVar) //从配置文件中获取信息
{
char *confFile = "conf.txt";
char buf[1024] = {0};
char typeStr[1024] = {0};
int i = 0;
FILE *fp = NULL; //FILE实际上是一个结构体
if (!(fp = fopen(confFile, "r"))) //'r'以只读的方式打开文件,fopen返回一个指针,也可以进行真or假判断
{
perror("conf.txt open :");
return 0;
}
while (fgets(buf, 1024, fp)) //fgets 读取fp中的数据,每次读取一行,读取size-1个字符,并在后面补上一个'\0'。
{
for (i = 0; i < 1024; i++) //!BUG 1024写成了2024.。。
{
if (buf[i] == '=')
{
strncpy(typeStr, buf ,i); //复制 = 前面的i个字符
Trim(typeStr); //去除字符串两边的空格
}
if (strcmp(type, typeStr)==0) //截取到的字符串与输出的字符串相同,说明找到了想要获取的目标
{
strcpy(outVar, buf+i+1); //将 = 后面的字符串复制到outVar中
outVar[strlen(outVar) -1]='\0';
Trim(outVar);
return 1;
}
}
}
printf("%s not found \n", type);
return 0;
}
void Trim(char *inVar) //自定义函数,目的是为了去除字符串两边的空格
{
int i = 0;
int j = strlen(inVar) - 1; //strlen一个指针数组,返回的长度是不包含'/0'的.因为j要作为数组下标,所以这里要减去1
while (inVar[i] == ' ')
{
i++;
}
while (inVar[j] == ' ')
{
j--;
}
strncpy(inVar, inVar + i ,j - i + 1); //要把当前j指向的字符也放进去,所以要加上一个1
inVar[j - i + 1] = '\0'; //手动加上'/0',因为此时inVar的元素有j-i+1个,而最后一个元素的下标为j-i+1-1,所以j-i+1-1+1=j-i+1上添加一个'/0'
}
void AllocDescriptors(int size, int maxVnameLen, int maxInameLen) //分配描述区
{
int i;
//分配绑定描述区和选择描述区 size:最大STL或P数量 即N maxVnameLen:STI或P最大名字长度 即M maxInameLen:存储指示变量最大名字长度 即Y
bindDp = SQLSQLDAAlloc(0, size, maxVnameLen, maxInameLen);
selectDp = SQLSQLDAAlloc(0, size, maxVnameLen, maxInameLen);
//为指示变量,绑定变量分配内存
for (i = 0; i < MAX_ITEMS; i++)
{
bindDp->I[i] = (short *)malloc(sizeof(short)); //I 指示变量的地址
selectDp->I[i] = (short *)malloc(sizeof(short));
bindDp->V[i] = (char *)malloc(sizeof(char)); //V STI或P的地址
selectDp->V[i] = (char *)malloc(sizeof(char));
}
}
void DeallocDescriptors()
{
int i = 0;
//释放指示变量、绑定变量或选择列表项的内存
for (i = 0; i < MAX_ITEMS; i++)
{
if (bindDp->V[i] != (char *)0)
{
printf("FREE bindDp the %d:\n", i);
free(bindDp->V[i]);
bindDp->V[i] = NULL; //释放数组空间后,手动指向NULL是一个好习惯
printf("FREE bindDp OVER %d:\n", i);
}
free(bindDp->I[i]);
bindDp->I[i] = NULL;
if (selectDp->V[i] != (char *)0)
{
printf("FREE selectDp the %d:\n", i);
free(selectDp->V[i]);
selectDp->V[i] = NULL;
printf("FREE selectDp OVER %d:\n", i);
}
free(selectDp->I[i]);
selectDp->I[i] = NULL;
}
printf("SQLSQLDAFree:\n");
//释放绑定描述区和选择描述区
SQLSQLDAFree(0, bindDp);
SQLSQLDAFree(0, selectDp);
}
void SetBindVariables() //进项绑定变量的设置
{
int i = 0;
char bindVar[64] = {0};
//设置绑定变量的最大个数
//bindDp->N = MAX_ITEMS; //多次一举???前面已经将N初始化
//作用,将S语句中的绑定变量放入bindDp中?
EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bindDp;
printf("BIND VARIABLES sqlcode : %d\n", sqlca.sqlcode);
//DESCRIBE后,将sqlda->F中绑定变量真实的数量给sqlda->N
bindDp->N = bindDp->F;
//开始循环处理绑定变量
for (i = 0; i < bindDp->F; i++)
{
//显示绑定变量值 这里实际上显示的是占位符的名字
printf("请输入绑定变量 %.*s : ", (int)bindDp->C[i], bindDp->S[i]);
//获得绑定变量值 fgets会读取换行符,所以要想办法把换行符去掉,要不然就会读取到相邻的数据
fgets(bindVar, 100, stdin);
bindVar[strlen(bindVar) - 1] = '\0';
//也可以使用scanf scanf不会读取到换行符
//scanf("%s", bindVar);
//设置绑定变量的长度 Oracle通过V[i]的地址(即字符串的首地址)访问绑定变量,所以必须告知其长度L
bindDp->L[i] = strlen(bindVar);
printf("bindDp->L[%d] = %d\n", i, bindDp->L[i]);
//为绑定变量缓存区重新分配内存,多一位留给'\0' realloc重新分配地址 sqlda->L是指针的指针,sqlda->L[i]访问外层指针所指向的指针
//V[i]保存的实际上是一个字符串数组,因为char的长度为1,并且char需要一个'/0'作为终止符,所以写法如下。 int数组写法 sizeof(int)*length
bindDp->V[i] = (char *)realloc(bindDp->V[i], bindDp->L[i] + 1);
//将绑定变量数据放入
strcpy(bindDp->V[i], bindVar);
//设置指示变量,处理NULL 可能是因为如果输入NULL,也会当作字符串,所以就用一个变量指定
if ((strncmp(bindVar, "NULL", 4) == 0) || (strncmp(bindVar, "null", 4) == 0))
{
*bindDp->I[i] = -1; //sqlda->I[i]表示外层指针指向的指针,*sqlda->I[i]则标识指针指向的指针指向的值
}
else
{
*bindDp->I[i] = 0;
}
//设置数据缓冲区数据类型代码 char
bindDp->T[i] = 1;
printf("This BindVar is : %s\n", bindDp->V[i]);
}
}
void PrintQueryResult() //输出select查询语句获取到的数据库数据
{
int i = 0; //循环用
int nullOk;
int precision;
int scale;
char title[30]; //保存列名
//设置选择列表项的最大个数
//selectDp->N = MAX_ITEMS; //应该也是多此一举
//选择列表项,选择描述区
EXEC SQL DESCRIBE SELECT LIST FOR S INTO selectDp;
printf("DESCRIBE SELECT LIST sqlcode is : %d\n", sqlca.sqlcode);
//设置选择列表项的实际个数
selectDp->N = selectDp->F;
//循环处理选择列表项
for (i = 0; i < selectDp->F; i++)
{
//清除selectDp->T[i]的高位->null T保存对应选择列表项的数据类型 高位则指示其是否能为null
//并根据数据库中此项是否能够为null设置nullOk的值 如果允许为空 nullOk=1 否则为0
//0001 0001 1001 0001 左八位为高位、右八位为低位 当然,并不知T的结构是否如此
SQLColumnNullCheck(0, (unsigned short *)&selectDp->T[i], (unsigned short *)&selectDp->T[i], &nullOk);
//根据内部数据类型确定外部数据类型长度 很明显T存储类型是数值形式的
switch (selectDp->T[i])
{
case 2:
//number数据类型,取得精度与标度 -4.75 精度=3,标度=2
SQLNumberPrecV6(0, (unsigned int *)&selectDp->L[i], &precision, &scale);
//oracle使用number保存所有的数值类型,但现在要把它放出来,就要有一个对应的外部数值类型的长度保存
if (scale > 0) //scale大于零,说明有小数位,那么就要用float保存number
{
selectDp->L[i] = sizeof(float);
}
else //如果没有小数位,那么就可以愉快地使用int存了
{
selectDp->L[i] = sizeof(int);
}
break;
case 12:
//data数据类型
selectDp->L[i] = 9;
break;
}
//还有第三种数据类型 varchar类型 推测因为L[i]中初始保存的数据类型长度就是对应的char[]长度,所以不用进行处理??? -------------含有疑问
//根据变量长度,重新为选择列表项分配内存
if (selectDp->L[i] != 2) //其他类型(其实本质是字符串??) -------------------含有疑问
{
selectDp->V[i] = (char *)realloc(selectDp->V[i], selectDp->L[i]+1);
}
else //number类型
{
selectDp->V[i] = (char *)realloc(selectDp->V[i], selectDp->L[i]);
}
//初始化title
memset(title, ' ', 30); //写memset(title, '0', 30) 也行,反正这里只是用来输出,但是填' '输出比较好看
//选择列表项名称 S保存选择列表项名字地址的指针 C保存选择列表项名字地址长度的指针
strncpy(title, selectDp->S[i], selectDp->C[i]);
//显示列名
if (selectDp->T[i] == 2) //T保存数据类型
{
if (scale > 0)
{
printf("\t%.*s", selectDp->L[i] + 3, title); //指向STI的地址的长度的数组 在输出列名的时候就设置好格式
}
else
{
printf("\t%.*s", selectDp->L[i], title);
}
}
else
{
printf("\t%-.*s", selectDp->L[i], title);
}
//根据oracle内部类型确定外部类型
if (selectDp->T[i] == 2)
{
if (scale > 0) //float
{
selectDp->T[i] = 4;
}
else //int
{
selectDp->T[i] = 3;
}
}
else //char
{
selectDp->T[i] = 1;
}
}
printf("\n");
//当selectDp中的数据处理完毕后,跳出
EXEC SQL whenever not found do break;
//printf("WHEN NOT FOUND sqlcode is : %d\n", sqlca.sqlcode);
//EXEC SQL DECLARE C CURSOR FOR S; //--------------含有疑问
//printf("DECLARE C sqlcode is : %d\n", sqlca.sqlcode);
int n = 0;
//循环输出数据
for(; ;)
{
//用选择描述区提取数据 ----------含有疑问
//这个语句有问题
EXEC SQL FETCH C using DESCRIPTOR selectDp;
//开始显示数据
for (i = 0; i < selectDp->F; i++)
{
//处理null值
if (*selectDp->I[i] < 0)
{
printf("\tNull");
}
else
{
if (selectDp->T[i] == 3)
{
printf("\t%d", *(int *)selectDp->V[i]);
}
else if (selectDp->T[i] == 4)
{
printf("\t%8.2f", *(float *)selectDp->V[i]);
}
else //char
{
printf("\t%.*s", selectDp->L[i], selectDp->V[i]);
}
}
}
printf("\n");
//n++;
/*if (n%1000 == 0)
{
char a[10] = {0};
scanf("%s", &a);
if(!strncmp(a, "break", 5))
{
break;
}
}*/
}
}
在定义了一个sqlda结构体后,要使用SQLSQLDAAlloc()函数为其分配空间。
descriptor_name = SQLSQLDAAlloc (runtime_context, max_vars, max_name, max_ind_name);
//runtime_context //运行时上下文指针
//max_vars //最大SLI或者P数量 即N
//max_name //SLI或者P最大名字长度 即M
//max_ind_name /IV最大名字长度 即Y
宿主变量与指示变量还有绑定变量
EXEC SQL DECLARE BEGIN SECTION;
int oraId = 0; //宿主变量
EXEC SQL DECLARE END SECTION;
//指示器变量是与宿主变量相关联的一类SQL变量,它被用来监督和管理与其相关联的宿主变量,每一个宿主变量都可以定义一个指示器变量。
//绑定变量 对于提交的sql语句,有硬解和软解两重方式。第一次提交的sql语句会采用硬解会消耗大量的系统资源。而如果提交的sql语句
//在数据库中已有,则为软解。针对查询同一类数据的情况,就使用了绑定变量,提高效率。
网友评论