DCI中的错误码处理流程
关键字:
错误码、ODBC、DCI、人大金仓、KingbaseES
DCI层错误码
错误码是一组数字(或者数字与字母的组合),会与各种错误消息建立关联,用于识别应用程序运行过程产生的各种问题,便于程序员快速定位和解决问题。从程序员的角度来说,错误码是程序中出现问题的指示器;从用户角度出发,错误码是软件和操作系统中的常见问题;从组织的角度看,错误码也是重要的质量控制指标。因此,一套完善的错误码处理流程是应用程序必须具备的功能。
为保持与Oracle的高度兼容,DCI层需要设置Oracle对应的错误码,这些错误码主要记录DCI接口函数在执行过程中的错误码和ODBC层传输到DCI层的错误码。DCI层的错误码主要定义在error.h头文件中,使用最多的是Oracle对应的错误码,各个错误码的定义、错误码值、错误消息与设置方法如表1所示。
表1 DCI层错误码(部分)
定义 | 错误码 | 错误消息 | 设置方法 |
ORA_00001 | 1 | kdci.c:duplicate key violates primary key constraint |
|
ORA_00384 | 384 | kdci_lo.c:Insufficient memory to grow cache | InternalSetError |
ORA_00942 | 942 | kdci.c:relation "T" does not exist |
|
ORA_01014 | 1014 | kdci_stmt.c: |
|
ORA_01019 | 1019 | kdci_stmt.c:unable to allocate memory in the userside | InternalSetError |
ORA_01034 | 1034 | kdci_stmt.c: | InternalSetError |
ORA_01036 | 1036 | kdci_stmt.c:illegal variable name/number | InternalSetError |
ORA_01092 | 1092 | kdci_stmt.c: |
|
ORA_01653 | 1653 | kdci.c:could not extend relation |
|
ORA_01841 | 1841 | kdci_describe.c:(full) year must be between -4713 and +9999, and not be 0 | InternalSetError |
ORA_01843 | 1843 | kdci_describe.c:An invalid month was specified | InternalSetError |
ORA_01846 | 1846 | kdci_describe.c:not a valid day of the week | InternalSetError |
ORA_01847 | 1847 | kdci_describe.c:day of month must be between 1 and last day of month | InternalSetError |
ORA_01850 | 1850 | kdci_describe.c:hour must be between 0 and 23 | InternalSetError |
ORA_01851 | 1851 | kdci_describe.c:minutes must be between 0 and 59 | InternalSetError |
ORA_01852 | 1852 | kdci_describe.c:seconds must be between 0 and 59 | InternalSetError |
DCI层错误码主要存储在DCIError句柄中。该句柄的定义如下所示:
struct DCIError
{
DCIHandleHead head; // head type=DCI_HTYPE_ERROR,结构体的第一个
int allocated_count;//可以分配的错误数量,MAX_ERROR_SIZE(16)
int err_count;//当前的错误数量
KSQLSMALLINT *handletype;//错误类型,包含KSQL_HANDLE_DBC、KSQL_HANDLE_STMT、DCI_HTYPE_DIRPATH_CTX,0等
void **handle;//发生错误的句柄
int *__error_number;//错误码
char **__error_message;//错误消息
char **sqlstate;//ODBC层错误代码
DCIEnv *pEnv;//关联的环境句柄,只在申请时设置
};
当handletype为KSQL_HANDLE_DBC、KSQL_HANDLE_STMT时,该错误码为ODBC层的错误码。一个环境句柄只能有一个错误句柄,最多可存储16条错误信息,每一条错误信息包含:错误类型、发生错误的句柄(可为NULL)、错误码、错误消息、sqlstate。
图1 DCI层错误码日志记录
ODBC层错误码
由于DCI底层复用ODBC的代码,因此DCI的错误码类型较多,不仅包含了DCI层与Oracle兼容的错误码,还包括了ODBC层DescriptorClass、StatementClass、ConnectionClass、EnvironmentClass四类句柄的错误码。其对应的定义、错误码值和错误消息如表2所示。
表2 ODBC错误码(部分)
定义 | 错误码 | ODBC错误码 | 错误消息 | 设置方法 | |
LOWEST_STMT_ERROR | -6 | ||||
STMT_ERROR_IN_ROW | -6 | 01S01 | 01S01 | convert.c:conversion error to wide chars occurred | SC_set_error |
STMT_ERROR_OPTION_VALUE_CHANGED | -5 | 01S02 | 01S02 | execute.c:cursor updatability changed | SC_set_error |
STMT_ERROR_ROW_VERSION_CHANGED | -4 | 01001 | 01001 | results.c:the row was already deleted ? | SC_set_error |
STMT_ERROR_POS_BEFORE_RECORDSET | -3 | 01S06 | 01S06 | results.c:fetch prior from eof and before the beginning | SC_set_error |
STMT_ERROR_TRUNCATED | -2 | 01004 | 01004 | results.c:The buffer was too small for the colName. | SC_set_error |
在ODBC层,其错误码主要存储在各个句柄定义的错误消息相关成员中:
//仅列出与错误相关的参数,其余参数不做展示
struct EnvironmentClass_
{
int errornumber;
char* errormsg;
};
struct ConnectionClass_
{
char* __error_message;
int __error_number;
char sqlstate[8];
};
struct StatementClass_
{
char *__error_message;//语句执行过程中的错误信息
int __error_number;//语句执行过程中的错误码
KS_ErrorInfo *kserror;//记录__error_number对应的错误信息,并进行相应处理,以便返回给上层应用
};
typedef struct DescriptorHeader_
{
UInt4 error_row; /* 1-based row */
UInt4 error_index; /* 1-based index */
Int4 __error_number;
char* __error_message;
KS_ErrorInfo* kserror;// 记录__error_number对应的错误信息,并进行相应处理,以便返回给上层应用
} DescriptorHeader;
typedef struct
{
UInt4 status;//错误码,连接错误码或者语句错误码
Int2 errorsize;//错误信息的长度sizeof(__error_message)
Int2 recsize;//-1
Int2 errorpos;
char sqlstate[6];
KSQLLEN diag_row_count;
char __error_message[40];//错误消息
} KS_ErrorInfo;
DCI与ODBC层错误码设置
(1)DCI层错误码设置
DCI层错误码设置主要通过InternalSetError( )函数实现,操作对象为DCIError,该函数的定义如下:
void InternalSetError(DCIError *errhp,
int handle_type,
void *handle,
int error_code,
char *msg,
char *sqlstate)
函数主要包含三步操作,错误码判断,错误句柄空间申请和错误信息设置:
- 错误码判断主要是对ODBC层传输的错误码进行转换,具体方式在下一节详述;
- 错误句柄空间申请。在错误句柄DCIError初始化时,仅申请MAX_ERROR_SIZE大小的空间,若是在保存新的错误信息时,空间不足,需要扩充错误句柄空间,并更新相关参数,如图2所示。
图2 错误句柄空间申请实现
- 错误消息设置主要是将对应的句柄、错误码、sqlstate和错误类型写入错误句柄,并更新相关参数。
图3 错误消息设置实现
(2)ODBC错误码设置
在ODBC层,各个句柄的错误码是分开设置的:
- 对于EnvironmentClass中发生的错误,只记录一条错误信息,一旦发生错误,整个流程结束,因此直接在句柄操作过程中设置,并没有专用的错误设置函数。
- 对于ConnectionClass中发生的错误,调用CC_set_error( )与CC_set_errormsg( )进行设置,一个句柄也仅能存储一条错误信息。
- 对于StatementClass中发生的错误,调用SC_set_error( )与SC_set_errormsg( )处理,一个句柄对应一条错误信息,但该错误在设置时通过ref_CC_error参数与ConnectionClass句柄联系。
- 对于DescriptorClass中发生的错误,调用DC_set_error( )与DC_set_errormsg( )进行设置。一个句柄仅能存储一条错误码。
ODBC层错误码获取与转换
为了将底层错误码传到DCI层进行转换和处理,DCI设计了ODBC层的错误码获取与转换机制,大致思路如图4所示。
图4 ODBC层错误码处理与转换
针对不同的错误码类型,设计了不同的错误码获取方法和思路:
(1)KSQL_HANDLE_ENV
环境句柄的错误消息通过KSAPI_EnvError( )进行获取,实现思路为:
- RecNumber判断,为1或者-1才可进行下一步,设置为1。若不满足条件,返回KSQL_NO_DATA_FOUND;
- cbErrorMsgMax判断,大于0才可进行下一步。若不满足条件,返回KSQL_ERROR;
- 调用ENV_get_error判断当前环境创建是否存在错误,有返回1,没有为0。若没有错误或者错误消息为NULL;
①调用strncpy_null将szSqlState设置为‘00000’,设置传入的参数,返回KSQL_NO_DATA_FOUND。
- 判断并设置pcbErrorMsg、错误消息szErrorMsg && (cbErrorMsgMax > 0)、错误码pfNativeError参数的状态;
- 如果szSqlState(存放5位ODBC错误码)不为空,根据当前环境对象的错误码status调用ks_sqlstate_set与ODBC错误码的映射:ENV_ALLOC_ERROR==>HY001 or S1001,default==>HY000 or S1000;
- 返回KSQL_SUCCESS。
涉及到的相关方法如图5所示:
图5 环境句柄错误信息传输路径
(2)KSQL_HANDLE_DBC
连接句柄的错误消息通过KSAPI_ConnectError( )进行获取,实现思路为:
- RecNumber判断,为1或者-1才可进行下一步,设置为1。若不满足条件,返回KSQL_NO_DATA_FOUND;
- cbErrorMsgMax判断,大于0才可进行下一步。若不满足条件,返回KSQL_ERROR;
- 判断当前连接状态,conn->status=CONN_EXECUTING;CC_get_error(conn):有错误为1,没有错误为0;错误消息为空,满足其中之一,则连接正常;
① 调用strncpy_null将szSqlState设置为‘00000’,设置传入的参数,返回KSQL_NO_DATA_FOUND。
- 判断并设置pcbErrorMsg、错误消息szErrorMsg && (cbErrorMsgMax > 0)、错误码pfNativeError参数的状态;
- 如果szSqlState(存放5位ODBC错误码)不为空,进行ConnectionClass错误码与ODBC错误码的映射:
① 如果当前连接conn->sqlstate[0]不为空,直接赋值给szSqlState;
② 否则根据当前连接的错误码status调用ks_sqlstate_set进行映射,映射规则如图6所示。在确定错误码于ODBC错误码之间的对应关系时,需要根据DCI的版本选择相应的sqlstate。
图6 连接句柄错误码映射关系
- 如果once_again为真:调用CC_set_errornumber,返回KSQL_SUCCESS_WITH_INFO;否则返回KSQL_SUCCESS。
涉及到的相关方法如图7所示:
图7 连接句柄错误信息传输路径
(3)KSQL_HANDLE_STMT
语句句柄的错误消息通过KSAPI_StmtError ( )进行获取,实现思路为:
- 调用SC_get_errornumber获取当前StatementClass对象的错误码errnum;
- 调用 SC_create_errorinfo(stmt, &error)获取当前StatementClass对象的错误成员kserror,若是kserror为空,返回KSQL_NO_DATA_FOUND;
① SC_create_errorinfo内部通过使用结构体数组Statement_sqlstate进行STMT错误码与ODBC错误码的映射。
- 若是kserror != &error,stmt->kserror = kserror;
- 如果错误码errnum为STMT_ERROR_NO_MEMORY且kserror->__error_message[0]为NULL,设置kserror->__error_message为”Memory Allocation Error??”;
- 如果应用程序创建的错误信息内存过大,底层驱动程序会自动拆分,因此调用return ER_ReturnError(kserror, RecNumber, szSqlState, pfNativeError, szErrorMsg, cbErrorMsgMax, pcbErrorMsg, flag);来分割错误信息——environ.c;
① 如果kserror为空,返回KSQL_NO_DATA_FOUND;
② 设置kserror->recsize的值;
③ 根据容纳长度设置*pcbErrorMsg;
④ 设置错误码:*pfNativeError = kserror->status;
⑤ 设置SqlState:strncpy_null((char *) szSqlState, kserror->sqlstate, 6)。
- 如果写入长度wrtlen小于pcblen,返回KSQL_SUCCESS_WITH_INFO,否则返回KSQL_SUCCESS
涉及到的相关方法如图8所示:
图8 语句句柄错误信息传输路径
(4)KSQL_HANDLE_DESC
描述句柄的错误消息通过KSAPI_DescError ( )进行获取,实现思路为:
- 获取DescriptorHeader对象deschd,里面含有错误信息成员kserror;
- DescriptorClass对象desc调用DC_create_errorinfo,获取错误信息deschd->kserror;
① DC_create_errorinfo内部根据deschd->__error_number通过使用结构体数组Descriptor_sqlstate进行DESC错误码与ODBC错误码的映射。
- 如果应用程序创建的错误信息内存过大,底层驱动程序会自动拆分,因此调用return ER_ReturnError(kserror, RecNumber, szSqlState, pfNativeError, szErrorMsg, cbErrorMsgMax, pcbErrorMsg, flag);来分割错误信息——environ.c;
① 如果kserror为空,返回KSQL_NO_DATA_FOUND
- 设置kserror->recsize的值
- 根据容纳长度设置*pcbErrorMsg
- 设置错误码:*pfNativeError = kserror->status;
- 设置SqlState:strncpy_null((char *) szSqlState, kserror->sqlstate, 6);
- 如果写入长度wrtlen小于pcblen,返回KSQL_SUCCESS_WITH_INFO,否则返回KSQL_SUCCESS。
涉及到的相关方法如图9所示:
图9 描述句柄错误信息传输路径
错误码优化与改进思路
针对DCI接口错误码设置方式多,参数众多,底层错误码和上层错误码联系不够紧密的问题,初始拟定以下改进思路:
- 流程优化:将底层错误码设置、读取和错误日志设置方式进行整合,尽可能用一个方法调用实现所有ODBC层句柄错误码的设置和读取;
- 参数优化:摒弃ODBC层DescriptorClass、StatementClass、ConnectionClass的错误参数,将其整合到EnvironmentClass中;
- 错误码标准化:对照Oracle错误码手册,将ODBC层错误码的值对应的Oracle数据库错误码的标准输出,进一步提升对Oracle的兼容性。