/** @file Parser Oracle driver. Copyright (c) 2001-2015 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) 2001.07.30 using Oracle 8.1.6 [@test tested with Oracle 7.x.x] */ #include "config_includes.h" #include "pa_sql_driver.h" #include volatile const char * IDENT_PARSER3ORACLE_C="$Id: parser3oracle.C,v 1.79 2015/10/26 16:00:50 moko Exp $" IDENT_PA_SQL_DRIVER_H; #define MAX_COLS 500 #define MAX_IN_LOBS 5 #define MAX_LOB_NAME_LENGTH 100 #define MAX_OUT_STRING_LENGTH 4000 #define MAX_BINDS 100 #define EMPTY_CLOB_FUNC_CALL "empty_clob()" #include "ltdl.h" #define MAX_STRING 0x400 #define MAX_NUMBER 20 #if _MSC_VER # define snprintf _snprintf # define strcasecmp _stricmp # define strncasecmp _strnicmp #endif #ifndef max inline int max(int a, int b) { return a>b?a:b; } inline int min(int a, int b){ return a(aconnection); // free fetch buffers. leave that to GC [no such services func. yet?] /* for(int i=0; ifree(fetch_buffer); else break; } */ // Terminate a user session OCISessionEnd( connection.svchp, connection.errhp, connection.usrhp, (ub4)OCI_DEFAULT); // Detach from a server; uninitialize server context handle OCIServerDetach( connection.srvhp, connection.errhp, (ub4)OCI_DEFAULT); // Free a previously allocated handles /* oci will free them up as belonging to env OCIHandleFree( (dvoid *)connection.srvhp, (ub4)OCI_HTYPE_SERVER); OCIHandleFree( (dvoid *)connection.svchp, (ub4)OCI_HTYPE_SVCCTX); OCIHandleFree( (dvoid *)connection.errhp, (ub4)OCI_HTYPE_ERROR); */ OCIHandleFree( (dvoid *)connection.envhp, (ub4)OCI_HTYPE_ENV); // free connection. leave that to GC [no such services func. yet?] // connection.services->free(&connection); } void commit(void *aconnection) { Connection& connection=*static_cast(aconnection); if(setjmp(connection.mark)) connection.services->_throw(connection.error); check(connection, "commit", OCITransCommit(connection.svchp, connection.errhp, 0)); } void rollback(void *aconnection) { Connection& connection=*static_cast(aconnection); if(setjmp(connection.mark)) connection.services->_throw(connection.error); // sometimes rollback is done in context when this yields error which masks previous error // consider consequent errors not very important to report, reporting first one /*check(connection, "rollback", */OCITransRollback(connection.svchp, connection.errhp, 0)/*)*/; } bool ping(void* /*connection*/) { // maybe OCIServerVersion? // select 0 from dual return true; } // charset here is services.request_charset(), not connection.client_charset // thus we can't use the sql server quoting support const char* quote(void *aconnection, const char *str, unsigned int length) { const char* from; const char* from_end=str+length; size_t quoted=0; for(from=str; from(aconnection); char *result=(char*)connection.services->malloc_atomic(length + quoted + 1); char *to = result; for(from=str; from '' *to++=*from; } *to=0; return result; } void query(void* aconnection, const char* astatement, size_t placeholders_count, Placeholder* placeholders, unsigned long offset, unsigned long limit, SQL_Driver_query_event_handlers& handlers ){ Connection& connection=*static_cast(aconnection); Query_lobs lobs={{0}, 0}; OCIStmt *stmthp=0; SQL_Driver_services& services=*connection.services; bool failed=false; if(setjmp(connection.mark)) { failed=true; goto cleanup; } else { if(placeholders_count>MAX_BINDS) fail(connection, "too many bind variables"); while(isspace((unsigned char)*astatement)) astatement++; const char* client_charset=connection.options.client_charset; const char* request_charset=services.request_charset(); bool transcode_needed=transcode_required(connection); if(transcode_needed){ // transcode query from $request:charset to ?ClientCharset size_t transcoded_xxx_size; services.transcode(astatement, strlen(astatement), astatement, transcoded_xxx_size, request_charset, client_charset); } const char *statement=_preprocess_statement_lobs(connection, astatement, lobs); modified_statement mstatement=_preprocess_statement_limit(connection, statement, offset, limit); statement=mstatement.statement; if(mstatement.limit) // limit was added in statement limit=SQL_NO_LIMIT; if(mstatement.offset) // limit was added in statement offset=0; check(connection, "HandleAlloc STMT", OCIHandleAlloc( (dvoid *)connection.envhp, (dvoid **) &stmthp, (ub4)OCI_HTYPE_STMT, 0, 0)); check(connection, "syntax", OCIStmtPrepare(stmthp, connection.errhp, (unsigned char *)statement, (ub4)strlen((char *)statement), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT)); struct Bind_info { OCIBind *bind; sb2 indicator; }; int binds_size=sizeof(Bind_info) * placeholders_count; // we DO store OCIBind* into ATOMIC gc memory, // but we do not allocate/free it, that's done automatically from oracle [using environment handles] // so we don't have to bother with that Bind_info* binds=static_cast(services.malloc_atomic(binds_size)); { for(size_t i=0; i EMPTY_CLOB_FUNC_CALL char *n=result; while(*o) { if( o[0]=='/' && o[1]=='*' && o[2]=='*') { // name start const char* saved_o=o; o+=3; const char *name_begin=o; while(*o) if( o[0]=='*' && o[1]=='*' && o[2]=='/' && o[3]=='\'') { // name end saved_o=0; // found, marking that const char *name_end=o; o+=4; Query_lobs::Item &item=lobs.items[lobs.count++]; item.name_ptr=name_begin; item.name_size=name_end-name_begin; item.data_ptr=(char *)services.malloc_atomic(statement_size/*max*/); item.data_size=0; const char *start=o; bool escaped=false; while(*o && !(o[0]=='\'' && o[1]!='\'' && !escaped)) { escaped=!escaped && (o[0]=='\'' && o[1]=='\''); if(escaped) { // write pending, skip "\" or "'" if(size_t size=o-start) { memcpy(item.data_ptr+item.data_size, start, size); item.data_size+=size; } start=++o; } else o++; } if(size_t size=o-start) { memcpy(item.data_ptr+item.data_size, start, size); item.data_size+=size; } if(*o) o++; // skip "'" n+=sprintf(n, EMPTY_CLOB_FUNC_CALL); break; } else o++; // /**skip**/'xxx' if(saved_o) { o=saved_o; *n++=*o++; } } else *n++=*o++; } *n=0; if(lobs.count) { int i; n+=sprintf(n, " returning "); for(i=0; icount; r++) { OCILobLocator *locator=rows->row[r].locator; check(connection, "lobwrite", OCILobWrite ( connection.svchp, connection.errhp, locator, &bytes_to_write, 1, (dvoid *)lobs.items[i].data_ptr, (ub4)bytes_to_write, OCI_ONE_PIECE, (dvoid *)0, 0, (ub2)0, (ub1) SQLCS_IMPLICIT)); } } } switch(stmt_type) { case OCI_STMT_SELECT: fetch_table(connection, stmthp, offset, limit, handlers, skip_rownum_column); break; default: /* case OCI_STMT_INSERT: case OCI_STMT_UPDATE: */ break; } } void fetch_table(Connection& connection, OCIStmt *stmthp, unsigned long offset, unsigned long limit, SQL_Driver_query_event_handlers& handlers, bool skip_rownum_column) { SQL_Driver_services& services=*connection.services; ub4 prefetch_rows=100; check(connection, "AttrSet prefetch-rows", OCIAttrSet( (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&prefetch_rows, (ub4)0, (ub4)OCI_ATTR_PREFETCH_ROWS, (OCIError *)connection.errhp)); ub4 prefetch_mem_size=100*0x400; check(connection, "AttrSet prefetch-memory", OCIAttrSet( (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&prefetch_mem_size, (ub4)0, (ub4)OCI_ATTR_PREFETCH_MEMORY, (OCIError *)connection.errhp)); OCIParam *mypard; ub2 dtype; const char* col_name; struct Col { ub2 type; char *str; OCILobLocator *var; OCIDefine *def; sb2 indicator; } cols[MAX_COLS]={0}; int column_count=0; bool failed=false; jmp_buf saved_mark; memcpy(saved_mark, connection.mark, sizeof(jmp_buf)); if(setjmp(connection.mark)) { failed=true; goto cleanup; } else { bool transcode_needed=transcode_required(connection); const char* client_charset=connection.options.client_charset; const char* request_charset=services.request_charset(); // idea of preincrementing is that at error time all handles would free up while(++column_count<=MAX_COLS) { /* get next descriptor, if there is one */ if(OCIParamGet(stmthp, OCI_HTYPE_STMT, connection.errhp, (void **)&mypard, (ub4) column_count)!=OCI_SUCCESS) { --column_count; break; } if(skip_rownum_column && column_count==1) continue; /* Retrieve the data type attribute */ check(connection, "get type", OCIAttrGet( (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, (dvoid*) &dtype, (ub4 *)0, (ub4)OCI_ATTR_DATA_TYPE, (OCIError *)connection.errhp)); /* Retrieve the column name attribute */ ub4 col_name_len; check(connection, "get name", OCIAttrGet( (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, (dvoid**) &col_name, (ub4 *) &col_name_len, (ub4)OCI_ATTR_NAME, (OCIError *)connection.errhp)); size_t length=(size_t)col_name_len; if(transcode_needed){ // transcode column name from ?ClientCharset to $request:charset services.transcode(col_name, col_name_len, col_name, length, client_charset, request_charset); } Col& col=cols[column_count-1]; { char *ptr=(char *)services.malloc_atomic(length+1); if( connection.options.bLowerCaseColumnNames ) tolower_str(ptr, col_name, length); else memcpy(ptr, col_name, length); ptr[length]=0; check(connection, handlers.add_column(connection.sql_error, ptr, length)); } ub2 coerce_type=dtype; sb4 size=0; void *ptr; switch(dtype) { case SQLT_CLOB: { check(connection, "alloc output var desc", OCIDescriptorAlloc( (dvoid *)connection.envhp, (dvoid **)(ptr=&col.var), (ub4)OCI_DTYPE_LOB, 0, (dvoid **)0)); size=0; break; } default: coerce_type=SQLT_STR; char*& buf=connection.fetch_buffers[column_count-1]; ptr=buf; // get cached buffer if(!ptr) // allocate if needed, caching it ptr=buf=(char *)services.malloc_atomic(MAX_OUT_STRING_LENGTH+1/*terminator*/); col.str=(char*)ptr; size=MAX_OUT_STRING_LENGTH; break; } col.type=coerce_type; // http://i/docs/oracle/server.804/a58234/oci_func.htm#449680 // this call implicitly allocates the define handle // http://sunsite.eunnet.net/documentation/oracle.8.0.4/server.804/a58234/basics.htm // when a statement handle is freed, any bind and define handles associated with it // are also freed col.def=0; check(connection, "DefineByPos", OCIDefineByPos( stmthp, &col.def, connection.errhp, column_count, (ub1 *) ptr, size, coerce_type, (dvoid *) &col.indicator, (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); } check(connection, handlers.before_rows(connection.sql_error)); for(unsigned long row=0; limit==SQL_NO_LIMIT || row=offset) { check(connection, handlers.add_row(connection.sql_error)); for(int i=0; ioffset statement_limited=(char *)connection.services->malloc_atomic( statement_size +64/*SELECT * FROM (SELECT ROWNUM r__, z__.* FROM () z__) WHERE r__<=*/ +MAX_NUMBER +9/* AND r__>*/ +MAX_NUMBER +1/*terminator*/ ); result.statement=statement_limited; strcpy(statement_limited, "SELECT * FROM (SELECT ROWNUM r__, z__.* FROM ("); strcat(statement_limited, astatement); statement_limited+=46+statement_size; statement_limited+=snprintf(statement_limited, 18+MAX_NUMBER, ") z__) WHERE r__<=%lu", limit+offset); statement_limited+=snprintf(statement_limited, 9+MAX_NUMBER, " AND r__>%lu", offset); } else { // SELECT * FROM (user_query) WHERE ROWNUM<=limit // this statement can be easy for the sql server but we can't use it with offset statement_limited=(char *)connection.services->malloc_atomic( statement_size +31/*SELECT * FROM () WHERE ROWNUM<=*/ +MAX_NUMBER +1/*terminator*/ ); result.statement=statement_limited; strcpy(statement_limited, "SELECT * FROM ("); strcat(statement_limited, astatement); statement_limited+=15+statement_size; statement_limited+=snprintf(statement_limited, 16+MAX_NUMBER, ") WHERE ROWNUM<=%lu", limit); } *statement_limited=0; //connection.services->_throw(result.statement); } return result; } private: // conn client library funcs friend void fail(Connection& connection, const char *msg); friend void check(Connection& connection, const char *step, sword status); friend sb4 cbf_get_data(dvoid *ctxp, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 **alenp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp); #define OCI_DECL(name, params) \ typedef sword (*t_OCI##name)params; t_OCI##name OCI##name OCI_DECL(Initialize, (ub4 mode, dvoid *ctxp, dvoid * (*malocfp)(dvoid *ctxp, size_t size), dvoid * (*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t newsize), void (*mfreefp)(dvoid *ctxp, dvoid *memptr) )); OCI_DECL(EnvInit, (OCIEnv **envp, ub4 mode, size_t xtramem_sz, dvoid **usrmempp)); OCI_DECL(AttrGet, (CONST dvoid *trgthndlp, ub4 trghndltyp, dvoid *attributep, ub4 *sizep, ub4 attrtype, OCIError *errhp)); OCI_DECL(AttrSet, (dvoid *trgthndlp, ub4 trghndltyp, dvoid *attributep, ub4 size, ub4 attrtype, OCIError *errhp)); OCI_DECL(BindByPos, (OCIStmt *stmtp, OCIBind **bindp, OCIError *errhp, ub4 position, dvoid *valuep, sb4 value_sz, ub2 dty, dvoid *indp, ub2 *alenp, ub2 *rcodep, ub4 maxarr_len, ub4 *curelep, ub4 mode)); OCI_DECL(BindByName, (OCIStmt *stmtp, OCIBind **bindp, OCIError *errhp, text* placeholder, sb4 placeh_len, dvoid *valuep, sb4 value_sz, ub2 dty, dvoid *indp, ub2 *alenp, ub2 *rcodep, ub4 maxarr_len, ub4 *curelep, ub4 mode)); OCI_DECL(BindDynamic, (OCIBind *bindp, OCIError *errhp, dvoid *ictxp, OCICallbackInBind icbfp, dvoid *octxp, OCICallbackOutBind ocbfp)); OCI_DECL(DefineByPos, (OCIStmt *stmtp, OCIDefine **defnp, OCIError *errhp, ub4 position, dvoid *valuep, sb4 value_sz, ub2 dty, dvoid *indp, ub2 *rlenp, ub2 *rcodep, ub4 mode)); OCI_DECL(DescriptorAlloc, (CONST dvoid *parenth, dvoid **descpp, CONST ub4 type, CONST size_t xtramem_sz, dvoid **usrmempp)); OCI_DECL(DescriptorFree, (dvoid *descp, CONST ub4 type)); OCI_DECL(ErrorGet, (dvoid *hndlp, ub4 recordno, OraText *sqlstate, sb4 *errcodep, OraText *bufp, ub4 bufsiz, ub4 type)); OCI_DECL(HandleAlloc, (CONST dvoid *parenth, dvoid **hndlpp, CONST ub4 type, CONST size_t xtramem_sz, dvoid **usrmempp)); OCI_DECL(HandleFree, (dvoid *hndlp, CONST ub4 type)); OCI_DECL(LobGetLength, (OCISvcCtx *svchp, OCIError *errhp, OCILobLocator *locp, ub4 *lenp)); OCI_DECL(LobRead, (OCISvcCtx *svchp, OCIError *errhp, OCILobLocator *locp, ub4 *amtp, ub4 offset, dvoid *bufp, ub4 bufl, dvoid *ctxp, sb4 (*cbfp)(dvoid *ctxp, CONST dvoid *bufp, ub4 len, ub1 piece), ub2 csid, ub1 csfrm)); OCI_DECL(LobWrite, (OCISvcCtx *svchp, OCIError *errhp, OCILobLocator *locp, ub4 *amtp, ub4 offset, dvoid *bufp, ub4 buflen, ub1 piece, dvoid *ctxp, sb4 (*cbfp)(dvoid *ctxp, dvoid *bufp, ub4 *len, ub1 *piece), ub2 csid, ub1 csfrm)); OCI_DECL(ParamGet, (CONST dvoid *hndlp, ub4 htype, OCIError *errhp, dvoid **parmdpp, ub4 pos)); OCI_DECL(ServerAttach, (OCIServer *srvhp, OCIError *errhp, CONST OraText *dblink, sb4 dblink_len, ub4 mode)); OCI_DECL(ServerDetach, (OCIServer *srvhp, OCIError *errhp, ub4 mode)); OCI_DECL(SessionBegin, (OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 credt, ub4 mode)); OCI_DECL(SessionEnd, (OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 mode)); OCI_DECL(StmtExecute, (OCISvcCtx *svchp, OCIStmt *stmtp, OCIError *errhp, ub4 iters, ub4 rowoff, CONST OCISnapshot *snap_in, OCISnapshot *snap_out, ub4 mode)); OCI_DECL(StmtFetch, (OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, ub4 mode)); OCI_DECL(StmtPrepare, (OCIStmt *stmtp, OCIError *errhp, CONST OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode)); OCI_DECL(TransCommit, (OCISvcCtx *svchp, OCIError *errhp, ub4 flags)); OCI_DECL(TransRollback, (OCISvcCtx *svchp, OCIError *errhp, ub4 flags)); private: // conn client library funcs linking const char *dlink(const char *dlopen_file_spec) { if(lt_dlinit()){ if(const char* result=lt_dlerror()) return result; return "can not prepare to dynamic loading"; } lt_dlhandle handle=lt_dlopen(dlopen_file_spec); if(!handle){ if(const char* result=lt_dlerror()) return result; return "can not open the dynamic link module"; } #define DSLINK(name, action) \ name=(t_##name)lt_dlsym(handle, #name); \ if(!name) \ action; #define OCI_LINK(name) DSLINK(OCI##name, return "function OCI" #name " was not found") OCI_LINK(Initialize); OCI_LINK(EnvInit); OCI_LINK(AttrGet); OCI_LINK(AttrSet); OCI_LINK(BindByPos); OCI_LINK(BindByName); OCI_LINK(BindDynamic); OCI_LINK(DefineByPos); OCI_LINK(DescriptorAlloc); OCI_LINK(DescriptorFree); OCI_LINK(ErrorGet); OCI_LINK(HandleAlloc); OCI_LINK(HandleFree); OCI_LINK(LobGetLength); OCI_LINK(LobRead); OCI_LINK(LobWrite); OCI_LINK(ParamGet); OCI_LINK(ServerAttach); OCI_LINK(ServerDetach); OCI_LINK(SessionBegin); OCI_LINK(SessionEnd); OCI_LINK(StmtExecute); OCI_LINK(StmtFetch); OCI_LINK(StmtPrepare); OCI_LINK(TransCommit); OCI_LINK(TransRollback); return 0; } } *OracleSQL_driver; void check(Connection& connection, const char *step, sword status) { const char *msg; char reason[MAX_STRING/2]; switch (status) { case OCI_SUCCESS: // hurrah case OCI_SUCCESS_WITH_INFO: // ignoring. example: count(column) when column contains NULLs, // count() not counting them and gives that status return; case OCI_ERROR: { sb4 errcode; if(OracleSQL_driver->OCIErrorGet((dvoid *)connection.errhp, (ub4)1, (text *)NULL, &errcode, (text *)reason, (ub4)sizeof(reason), OCI_HTYPE_ERROR)==OCI_SUCCESS) { msg=reason; if(msg && transcode_required(connection)){ // transcode server error message from ?ClientCharset to $request:charset if(size_t msg_length=strlen(msg)){ connection.services->transcode(msg, msg_length, msg, msg_length, connection.options.client_charset, connection.services->request_charset()); } } } else msg="[can not get error description]"; break; } case OCI_NEED_DATA: msg="NEED_DATA"; break; case OCI_NO_DATA: msg="NODATA"; break; case OCI_INVALID_HANDLE: msg="INVALID_HANDLE"; break; case OCI_STILL_EXECUTING: msg="STILL_EXECUTE"; break; case OCI_CONTINUE: msg="CONTINUE"; break; default: msg="unknown"; break; } snprintf(connection.error, sizeof(connection.error), "%s (%s, %d)", msg, step, (int)status); longjmp(connection.mark, 1); } bool transcode_required(Connection& connection){ return (connection.options.client_charset && strcmp(connection.options.client_charset, connection.services->request_charset())!=0); } void fail(Connection& connection, const char* msg) { snprintf(connection.error, sizeof(connection.error), "%s", msg); longjmp(connection.mark, 1); } void check(Connection& connection, bool error) { if(error) longjmp(connection.mark, 1); } /* ----------------------------------------------------------------- */ /* Intbind callback that does not do any data input. */ /* ----------------------------------------------------------------- */ sb4 cbf_no_data( dvoid* /*ctxp*/, OCIBind* /*bindp*/, ub4 /*iter*/, ub4 /*index*/, dvoid **bufpp, ub4 *alenpp, ub1 *piecep, dvoid **indpp) { *bufpp=(dvoid *)0; *alenpp=0; static sb2 null_ind=-1; *indpp=(dvoid *) &null_ind; *piecep=OCI_ONE_PIECE; return OCI_CONTINUE; } /* ----------------------------------------------------------------- */ /* Outbind callback for returning data. */ /* ----------------------------------------------------------------- */ static sb4 cbf_get_data(dvoid *ctxp, OCIBind *bindp, ub4 /*iter*/, ub4 index, dvoid **bufpp, ub4 **alenp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { Query_lobs::Item& context=*static_cast(ctxp); if(index==0) { static ub4 rows; check(*context.connection, "AttrGet cbf_get_data ROWS_RETURNED", OracleSQL_driver->OCIAttrGet( (CONST dvoid *) bindp, OCI_HTYPE_BIND, (dvoid *)&rows, (ub4 *)sizeof(ub2), OCI_ATTR_ROWS_RETURNED, context.connection->errhp)) ; context.rows.count=(ub2)rows; context.rows.row=(Query_lobs::return_rows::return_row *) context.connection->services->malloc_atomic(sizeof(Query_lobs::return_rows::return_row)*rows); } Query_lobs::return_rows::return_row &var=context.rows.row[index]; check(*context.connection, "alloc output var desc dynamic", OracleSQL_driver->OCIDescriptorAlloc( (dvoid *) context.connection->envhp, (dvoid **)&var.locator, (ub4)OCI_DTYPE_LOB, 0, (dvoid **)0)); *bufpp=var.locator; *alenp=&var.len; *indpp=(dvoid *) &var.ind; *piecep=OCI_ONE_PIECE; *rcodepp=&var.rcode; return OCI_CONTINUE; } extern "C" SQL_Driver *SQL_DRIVER_CREATE() { return OracleSQL_driver=new OracleSQL_Driver(); }