LINUX C ORACLE动态SQL第四种

原代码地址:https://blog.csdn.net/numbibi/article/details/8446704
根据自己的理解加上了注释

程序代码

#include 
#include 
#include 
#include      //提供了一个类型jmp_buf jb和两个函数setjmp(jp)、longjmp(jp,[1~n]); setjmp(jp)用来保存当前的环境,未使用longjmp时
                        //其返回值总为0。而当使用了longjmp后,程序会直接跳转到setjmp执行后,而且其返回值会改变,常用来模拟异常处理
#include      // Pro*C产生的与平台相关的SQLLIB函数的ANSI原形定义
#include       //使用动态SQL语句时,需准备DynamicStagingArea对象(全局对象SQLSA)和DynamicDescriptionArea对象(全局对象SQLDA)
#include       //系统默认事务对象

//定义绑定变量和选择列表项的最大个数
#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语句
//在数据库中已有,则为软解。针对查询同一类数据的情况,就使用了绑定变量,提高效率。

你可能感兴趣的:(LINUX C ORACLE动态SQL第四种)