|
|
| version 1.14, 2001/11/14 10:46:36 | version 1.72, 2008/07/08 08:07:49 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser Oracle driver. | Parser Oracle driver. |
| Copyright(c) 2001 ArtLebedev Group(http://www.artlebedev.com) | Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com) |
| Author: Alexander Petrosyan <paf@design.ru>(http://design.ru/paf) | Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru) |
| 2001.07.30 using Oracle 8.1.6 [@test tested with Oracle 7.x.x] | 2001.07.30 using Oracle 8.1.6 [@test tested with Oracle 7.x.x] |
| */ | */ |
| static const char *RCSId="$Id$"; | static const char *RCSId="$Id$"; |
| #include "config_includes.h" | #include "config_includes.h" |
| Line 19 static const char *RCSId="$Id$"; | Line 20 static const char *RCSId="$Id$"; |
| #define MAX_IN_LOBS 5 | #define MAX_IN_LOBS 5 |
| #define MAX_LOB_NAME_LENGTH 100 | #define MAX_LOB_NAME_LENGTH 100 |
| #define MAX_OUT_STRING_LENGTH 4000 | #define MAX_OUT_STRING_LENGTH 4000 |
| #define MAX_BINDS 100 | |
| #define EMPTY_CLOB_FUNC_CALL "empty_clob()" | #define EMPTY_CLOB_FUNC_CALL "empty_clob()" |
| Line 38 inline int max(int a, int b) { return a> | Line 40 inline int max(int a, int b) { return a> |
| inline int min(int a, int b){ return a<b?a:b; } | inline int min(int a, int b){ return a<b?a:b; } |
| #endif | #endif |
| /// @test setenv version memory. maybe key/value needs ::malloc? | #if _MSC_VER |
| // interaction between '_setjmp' and C++ object destruction is non-portable | |
| // but we forced to do that under HPUX | |
| #pragma warning(disable:4611) | |
| #endif | |
| const sb2 MAGIC_INDICATOR_VALUE_MEANING_NOT_NULL_AND_UNCHANGED=99; | |
| /// @todo small memory leaks here | |
| static int pa_setenv(const char *name, const char *value, bool do_append) { | static int pa_setenv(const char *name, const char *value, bool do_append) { |
| const char *prev_value=0; | const char *prev_value=0; |
| if(do_append) | if(do_append) |
| Line 68 static int pa_setenv(const char *name, c | Line 78 static int pa_setenv(const char *name, c |
| #else | #else |
| //#ifdef HAVE_SETENV | //#ifdef HAVE_SETENV |
| if(value) { | if(value) { |
| char *buf; | |
| if(prev_value) { | if(prev_value) { |
| // MEM_LEAK_HERE | // MEM_LEAK_HERE |
| buf=(char *)::malloc(strlen(prev_value) | char *buf=(char *)::malloc(strlen(prev_value) |
| +strlen(value) | +strlen(value) |
| +1); | +1); |
| strcpy(buf, prev_value); | strcpy(buf, prev_value); |
| strcat(buf, value); | strcat(buf, value); |
| } else | value=buf; |
| buf=value; | } |
| return setenv(name, value, 1/*overwrite*/); | |
| return setenv(name, buf, 1/*overwrite*/); | |
| } else { | } else { |
| unsetenv(name); | unsetenv(name); |
| return 0; | return 0; |
| Line 87 static int pa_setenv(const char *name, c | Line 95 static int pa_setenv(const char *name, c |
| #endif | #endif |
| } | } |
| static char *lsplit(char *string, char delim) { | static char *lsplit(char *string, char delim){ |
| if(string) { | if(string){ |
| char *v=strchr(string, delim); | if(char* v=strchr(string, delim)){ |
| if(v) { | |
| *v=0; | *v=0; |
| return v+1; | return v+1; |
| } | } |
| } | } |
| return 0; | return 0; |
| } | } |
| static char *lsplit(char **string_ref, char delim) { | static char *lsplit(char **string_ref, char delim){ |
| char *result=*string_ref; | char *result=*string_ref; |
| char *next=lsplit(*string_ref, delim); | char *next=lsplit(*string_ref, delim); |
| *string_ref=next; | *string_ref=next; |
| return result; | return result; |
| } | } |
| static const char *options2env(char *options) { | static char* rsplit(char* string, char delim){ |
| while(options) { | if(string){ |
| if(char *key=lsplit(&options, '&')) { | if(char* v=strrchr(string, delim)){ |
| if(*key) { | *v=0; |
| if(char *value=lsplit(key, '=')) { | return v+1; |
| bool do_append=key[strlen(key)-1]=='+'; // PATH+= | |
| if(do_append) | |
| key[strlen(key)-1]=0; // remove trailing + | |
| if(strncmp(key, "ORACLE_", 7)==0 // ORACLE_HOME & co | |
| || strncmp(key, "ORA_", 4)==0 // ORA_ENCRYPT_LOGIN & co | |
| || strncmp(key, "NLS_", 4)==0 // NLS_LANG & co | |
| || do_append | |
| ) { | |
| if(pa_setenv(key, value, do_append)!=0) | |
| return "problem changing process environment" /*key*/; | |
| } else | |
| return "unknown option (option must start with ORACLE_, ORA_ or NLS_)" /*key*/; | |
| } else | |
| return "option without =value" /*key*/; | |
| } | |
| } | } |
| } | } |
| return 0; | return NULL; |
| } | |
| static void tolower_str(char *out, const char *in, size_t size) { | |
| while(size--) | |
| *out++=(char)tolower(*in++); | |
| } | |
| static void toupper_str(char *out, const char *in, size_t size) { | |
| while(size--) | |
| *out++=(char)toupper(*in++); | |
| } | } |
| struct modified_statement { | |
| const char* statement; | |
| bool limit; | |
| bool offset; | |
| bool skip_rownum_column; | |
| }; | |
| #ifndef DOXYGEN | #ifndef DOXYGEN |
| struct OracleSQL_connection_struct { | struct Connection { |
| SQL_Driver_services *services; | |
| jmp_buf mark; char error[MAX_STRING]; | jmp_buf mark; char error[MAX_STRING]; |
| SQL_Error sql_error; | |
| OCIEnv *envhp; | OCIEnv *envhp; |
| OCIServer *srvhp; | OCIServer *srvhp; |
| OCIError *errhp; | OCIError *errhp; |
| OCISvcCtx *svchp; | OCISvcCtx *svchp; |
| OCISession *usrhp; | OCISession *usrhp; |
| char* fetch_buffers[MAX_COLS]; | |
| char* bind_buffers[MAX_BINDS]; | |
| struct Options { | |
| bool bLowerCaseColumnNames; | |
| bool bDisableQueryModification; | |
| const char* client_charset; | |
| } options; | |
| }; | }; |
| struct OracleSQL_query_lobs { | struct Query_lobs { |
| struct return_rows { | struct return_rows { |
| struct return_row { | struct return_row { |
| OCILobLocator *locator; ub4 len; | OCILobLocator *locator; ub4 len; |
| Line 149 struct OracleSQL_query_lobs { | Line 169 struct OracleSQL_query_lobs { |
| } *row; | } *row; |
| int count; | int count; |
| }; | }; |
| struct cbf_context_struct { | |
| SQL_Driver_services *services; | |
| OracleSQL_connection_struct *cs; | |
| return_rows *rows; | |
| }; | |
| struct Item { | struct Item { |
| const char *name_ptr; size_t name_size; | const char *name_ptr; size_t name_size; |
| char *data_ptr; size_t data_size; | char *data_ptr; size_t data_size; |
| OCILobLocator *locator; | OCILobLocator *locator; |
| OCIBind *bind; | OCIBind *bind; |
| return_rows rows; | return_rows rows; |
| Connection *connection; | |
| } items[MAX_IN_LOBS]; | } items[MAX_IN_LOBS]; |
| int count; | int count; |
| }; | }; |
| #endif | #endif |
| // forwards | // forwards |
| void check( | static void fail(Connection& connection, const char *msg); |
| SQL_Driver_services& services, OracleSQL_connection_struct &cs, | static void check(Connection& connection, const char *step, sword status); |
| const char *step, sword status); | static void check(Connection& connection, bool error); |
| static sb4 cbf_no_data( | static sb4 cbf_no_data( |
| dvoid *ctxp, | dvoid *ctxp, |
| OCIBind *bindp, | OCIBind *bindp, |
| Line 185 static sb4 cbf_get_data(dvoid *ctxp, | Line 202 static sb4 cbf_get_data(dvoid *ctxp, |
| ub1 *piecep, | ub1 *piecep, |
| dvoid **indpp, | dvoid **indpp, |
| ub2 **rcodepp); | ub2 **rcodepp); |
| void tolower(char *out, const char *in, size_t size); | |
| static bool transcode_required(Connection& connection); | |
| static const char *options2env(char *s, Connection::Options* options) { | |
| while(s){ | |
| if(char *key=lsplit(&s, '&')){ | |
| if(*key){ | |
| if(char *value=lsplit(key, '=')){ | |
| if(strcmp(key, "ClientCharset")== 0){ | |
| if(options){ | |
| toupper_str(value, value, strlen(value)); | |
| options->client_charset=value; | |
| } | |
| continue; | |
| } | |
| if(strcmp(key, "LowerCaseColumnNames")==0){ | |
| if(options) | |
| options->bLowerCaseColumnNames=atoi(value)!=0; | |
| continue; | |
| } | |
| if(strcmp(key, "DisableQueryModification")==0){ | |
| if(options) | |
| options->bDisableQueryModification=atoi(value)!=0; | |
| continue; | |
| } | |
| bool do_append=key[strlen(key)-1]=='+'; // PATH+= | |
| if(do_append) | |
| key[strlen(key)-1]=0; // remove trailing + | |
| if(strncmp(key, "ORACLE_", 7)==0 // ORACLE_HOME & co | |
| || strncmp(key, "ORA_", 4)==0 // ORA_ENCRYPT_LOGIN & co | |
| || strncmp(key, "NLS_", 4)==0 // NLS_LANG & co | |
| || do_append | |
| ) { | |
| if(pa_setenv(key, value, do_append)!=0) | |
| return "problem changing process environment" /*key*/; | |
| } else | |
| return "unknown option" /*key*/; | |
| } else | |
| return "option without =value" /*key*/; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| /** | /** |
| OracleSQL server driver | OracleSQL server driver |
| @test NLS_LANG=AMERICAN_AMERICA.CL8MSWIN1251 | |
| */ | */ |
| class OracleSQL_Driver : public SQL_Driver { | class OracleSQL_Driver : public SQL_Driver { |
| public: | public: |
| Line 199 public: | Line 261 public: |
| /// get api version | /// get api version |
| int api_version() { return SQL_DRIVER_API_VERSION; } | int api_version() { return SQL_DRIVER_API_VERSION; } |
| /** initialize driver by loading sql dynamic link library | /** initialize driver by loading sql dynamic link library |
| @todo ?objects=1 which would turn on OCI_OBJECT init flag | @todo ?objects=1 which would turn on OCI_OBJECT init flag |
| */ | */ |
| Line 208 public: | Line 271 public: |
| const char *error=dlopen_file_spec? | const char *error=dlopen_file_spec? |
| dlink(dlopen_file_spec):"client library column is empty"; | dlink(dlopen_file_spec):"client library column is empty"; |
| if(!error) { | if(!error) { |
| error=options2env(options); | error=options2env(options, 0); |
| if(!error) | if(!error) |
| OCIInitialize((ub4)OCI_THREADED/*| OCI_OBJECT*/, (dvoid *)0, | OCIInitialize((ub4)OCI_THREADED/*| OCI_OBJECT*/, (dvoid *)0, |
| Line 222 public: | Line 285 public: |
| } | } |
| /** connect | /** connect |
| @param used_only_in_connect_url | @param url |
| format: @b user:pass@service? | format: @b user:pass@service? |
| ORACLE_HOME=/u01/app/oracle/product/8.1.5& | ORACLE_HOME=/u01/app/oracle/product/8.1.5& |
| ORA_NLS33=/u01/app/oracle/product/8.1.5/ocommon/nls/admin/data& | ORA_NLS33=/u01/app/oracle/product/8.1.5/ocommon/nls/admin/data& |
| NLS_LANG=RUSSIAN_AMERICA.CL8MSWIN1251& | NLS_LANG=RUSSIAN_AMERICA.CL8MSWIN1251& |
| ORA_ENCRYPT_LOGIN=TRUE | ORA_ENCRYPT_LOGIN=TRUE& |
| ClientCharset=charset& | |
| LowerCaseColumnNames=0 | |
| @todo environment manupulation doesnt look thread safe | @todo environment manupulation doesnt look thread safe |
| @todo allocate 'aused_only_in_connect_url' on gc heap, so it can be manipulated directly | |
| */ | */ |
| void connect( | void connect( |
| char *used_only_in_connect_url, | char *url, |
| SQL_Driver_services& services, | SQL_Driver_services& services, |
| void **connection ///< output: OracleSQL_connection_struct * | void **connection_ref ///< output: Connection * |
| ) { | ) |
| { | |
| // connections are cross-request, do not use services._alloc [linked with request] | // connections are cross-request, do not use services._alloc [linked with request] |
| OracleSQL_connection_struct &cs= | Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); |
| *(OracleSQL_connection_struct *)::calloc(sizeof(OracleSQL_connection_struct), 1); | connection.services=&services; |
| connection.options.bLowerCaseColumnNames=true; | |
| connection.options.bDisableQueryModification=false; | |
| *connection_ref=&connection; | |
| char *user=used_only_in_connect_url; | char *user=url; |
| char *service=lsplit(user, '@'); | char *service=rsplit(user, '@'); |
| char *pwd=lsplit(user, ':'); | char *pwd=lsplit(user, ':'); |
| char *options=lsplit(service, '?'); | char *options=lsplit(service, '?'); |
| if(!(user && pwd && service)) | if(!(user && pwd && service)) |
| services._throw("mailformed connect part, must be 'user:pass@service'"); | services._throw("mailformed connect part, must be 'user:pass@service'"); |
| if(const char *error=options2env(options)) | if(const char *error=options2env(options, &connection.options)) |
| services._throw(error); | services._throw(error); |
| if(setjmp(cs.mark)) | if(setjmp(connection.mark)) |
| services._throw(cs.error); | services._throw(connection.error); |
| // Allocate and initialize OCIError handle, attempt #1 | // Allocate and initialize OCIError handle, attempt #1 |
| /* | /* |
| Line 270 public: | Line 340 public: |
| think, this is some sort of backward compatibility wonder. | think, this is some sort of backward compatibility wonder. |
| leaving as it is, and without check() | leaving as it is, and without check() |
| */ | */ |
| OCIHandleAlloc((dvoid *)NULL, (dvoid **) &cs.envhp, (ub4)OCI_HTYPE_ENV, 0, 0); | OCIHandleAlloc((dvoid *)NULL, (dvoid **) &connection.envhp, (ub4)OCI_HTYPE_ENV, 0, 0); |
| // Initialize an environment handle, attempt #2 | // Initialize an environment handle, attempt #2 |
| check(services, cs, "EnvInit", OCIEnvInit( | check(connection, "EnvInit", OCIEnvInit( |
| &cs.envhp, (ub4)OCI_DEFAULT, 0, 0)); | &connection.envhp, (ub4)OCI_DEFAULT, 0, 0)); |
| // Allocate and initialize OCIError handle | // Allocate and initialize OCIError handle |
| check(services, cs, "HandleAlloc errhp", OCIHandleAlloc( | check(connection, "HandleAlloc errhp", OCIHandleAlloc( |
| (dvoid *)cs.envhp, (dvoid **) &cs.errhp, (ub4)OCI_HTYPE_ERROR, 0, 0)); | (dvoid *)connection.envhp, (dvoid **) &connection.errhp, (ub4)OCI_HTYPE_ERROR, 0, 0)); |
| // Allocate and initialize OCIServer handle | // Allocate and initialize OCIServer handle |
| check(services, cs, "HandleAlloc srvhp", OCIHandleAlloc( | check(connection, "HandleAlloc srvhp", OCIHandleAlloc( |
| (dvoid *)cs.envhp, (dvoid **) &cs.srvhp, (ub4)OCI_HTYPE_SERVER, 0, 0)); | (dvoid *)connection.envhp, (dvoid **) &connection.srvhp, (ub4)OCI_HTYPE_SERVER, 0, 0)); |
| // Attach to a 'service'; initialize server context handle | // Attach to a 'service'; initialize server context handle |
| check(services, cs, "ServerAttach", OCIServerAttach( | check(connection, "ServerAttach", OCIServerAttach( |
| cs.srvhp, cs.errhp, (text *)service, (sb4)strlen(service), (ub4)OCI_DEFAULT)); | connection.srvhp, connection.errhp, (text *)service, (sb4)strlen(service), (ub4)OCI_DEFAULT)); |
| // Allocate and initialize OCISvcCtx handle | // Allocate and initialize OCISvcCtx handle |
| check(services, cs, "HandleAlloc svchp", OCIHandleAlloc( | check(connection, "HandleAlloc svchp", OCIHandleAlloc( |
| (dvoid *)cs.envhp, (dvoid **) &cs.svchp, (ub4)OCI_HTYPE_SVCCTX, 0, 0)); | (dvoid *)connection.envhp, (dvoid **) &connection.svchp, (ub4)OCI_HTYPE_SVCCTX, 0, 0)); |
| // set attribute server context in the service context | // set attribute server context in the service context |
| check(services, cs, "AttrSet server-service", OCIAttrSet( | check(connection, "AttrSet server-service", OCIAttrSet( |
| (dvoid *)cs.svchp, (ub4)OCI_HTYPE_SVCCTX, | (dvoid *)connection.svchp, (ub4)OCI_HTYPE_SVCCTX, |
| (dvoid *)cs.srvhp, (ub4)0, | (dvoid *)connection.srvhp, (ub4)0, |
| (ub4)OCI_ATTR_SERVER, (OCIError *)cs.errhp)); | (ub4)OCI_ATTR_SERVER, (OCIError *)connection.errhp)); |
| // allocate a user context handle | // allocate a user context handle |
| check(services, cs, "HandleAlloc usrhp", OCIHandleAlloc( | check(connection, "HandleAlloc usrhp", OCIHandleAlloc( |
| (dvoid *)cs.envhp, (dvoid **)&cs.usrhp, (ub4)OCI_HTYPE_SESSION, 0, 0)); | (dvoid *)connection.envhp, (dvoid **)&connection.usrhp, (ub4)OCI_HTYPE_SESSION, 0, 0)); |
| // set 'user' name | // set 'user' name |
| check(services, cs, "AttrSet user-session", OCIAttrSet( | check(connection, "AttrSet user-session", OCIAttrSet( |
| (dvoid *)cs.usrhp, (ub4)OCI_HTYPE_SESSION, | (dvoid *)connection.usrhp, (ub4)OCI_HTYPE_SESSION, |
| (dvoid *)user, (ub4)strlen(user), | (dvoid *)user, (ub4)strlen(user), |
| OCI_ATTR_USERNAME, cs.errhp)); | OCI_ATTR_USERNAME, connection.errhp)); |
| // set 'pwd' password | // set 'pwd' password |
| check(services, cs, "AttrSet pwd-session", OCIAttrSet( | check(connection, "AttrSet pwd-session", OCIAttrSet( |
| (dvoid *)cs.usrhp, (ub4)OCI_HTYPE_SESSION, | (dvoid *)connection.usrhp, (ub4)OCI_HTYPE_SESSION, |
| (dvoid *)pwd, (ub4)strlen(pwd), | (dvoid *)pwd, (ub4)strlen(pwd), |
| OCI_ATTR_PASSWORD, cs.errhp)); | OCI_ATTR_PASSWORD, connection.errhp)); |
| // Authenticate a user | // Authenticate a user |
| check(services, cs, "SessionBegin", OCISessionBegin( | check(connection, "SessionBegin", OCISessionBegin( |
| cs.svchp, cs.errhp, cs.usrhp, | connection.svchp, connection.errhp, connection.usrhp, |
| OCI_CRED_RDBMS, OCI_DEFAULT)); | OCI_CRED_RDBMS, OCI_DEFAULT)); |
| // remember connection in session | // remember connection in session |
| check(services, cs, "AttrSet service-session", OCIAttrSet( | check(connection, "AttrSet service-session", OCIAttrSet( |
| (dvoid *)cs.svchp, (ub4)OCI_HTYPE_SVCCTX, | (dvoid *)connection.svchp, (ub4)OCI_HTYPE_SVCCTX, |
| (dvoid *)cs.usrhp, (ub4)0, | (dvoid *)connection.usrhp, (ub4)0, |
| OCI_ATTR_SESSION, cs.errhp)); | OCI_ATTR_SESSION, connection.errhp)); |
| // return created connection | |
| *(OracleSQL_connection_struct **)connection=&cs; | |
| } | } |
| void disconnect(void *connection) { | |
| OracleSQL_connection_struct &cs=*(OracleSQL_connection_struct *)connection; | void disconnect(void *aconnection) { |
| Connection& connection=*static_cast<Connection *>(aconnection); | |
| // free fetch buffers. leave that to GC [no such services func. yet?] | |
| /* | |
| for(int i=0; i<MAX_COLS; i++) { | |
| if(void* fetch_buffer=connection.fetch_buffers[i]) | |
| connection.services->free(fetch_buffer); | |
| else | |
| break; | |
| } | |
| */ | |
| // Terminate a user session | // Terminate a user session |
| OCISessionEnd( | OCISessionEnd( |
| cs.svchp, cs.errhp, cs.usrhp, (ub4)OCI_DEFAULT); | connection.svchp, connection.errhp, connection.usrhp, (ub4)OCI_DEFAULT); |
| // Detach from a server; uninitialize server context handle | // Detach from a server; uninitialize server context handle |
| OCIServerDetach( | OCIServerDetach( |
| cs.srvhp, cs.errhp, (ub4)OCI_DEFAULT); | connection.srvhp, connection.errhp, (ub4)OCI_DEFAULT); |
| // Free a previously allocated handles | // Free a previously allocated handles |
| /* | /* |
| oci will free them up as belonging to env | oci will free them up as belonging to env |
| OCIHandleFree( | OCIHandleFree( |
| (dvoid *)cs.srvhp, (ub4)OCI_HTYPE_SERVER); | (dvoid *)connection.srvhp, (ub4)OCI_HTYPE_SERVER); |
| OCIHandleFree( | OCIHandleFree( |
| (dvoid *)cs.svchp, (ub4)OCI_HTYPE_SVCCTX); | (dvoid *)connection.svchp, (ub4)OCI_HTYPE_SVCCTX); |
| OCIHandleFree( | OCIHandleFree( |
| (dvoid *)cs.errhp, (ub4)OCI_HTYPE_ERROR); | (dvoid *)connection.errhp, (ub4)OCI_HTYPE_ERROR); |
| */ | */ |
| OCIHandleFree( | OCIHandleFree( |
| (dvoid *)cs.envhp, (ub4)OCI_HTYPE_ENV); | (dvoid *)connection.envhp, (ub4)OCI_HTYPE_ENV); |
| // connections are cross-request, do not use services._alloc [linked with request] | // free connection. leave that to GC [no such services func. yet?] |
| ::free(&cs); | // connection.services->free(&connection); |
| } | } |
| void commit(SQL_Driver_services& services, void *connection) { | |
| OracleSQL_connection_struct &cs=*(OracleSQL_connection_struct *)connection; | |
| if(setjmp(cs.mark)) | |
| services._throw(cs.error); | |
| check(services, cs, "commit", OCITransCommit(cs.svchp, cs.errhp, 0)); | void commit(void *aconnection) { |
| Connection& connection=*static_cast<Connection *>(aconnection); | |
| if(setjmp(connection.mark)) | |
| connection.services->_throw(connection.error); | |
| check(connection, "commit", OCITransCommit(connection.svchp, connection.errhp, 0)); | |
| } | } |
| void rollback(SQL_Driver_services& services, void *connection) { | |
| OracleSQL_connection_struct &cs=*(OracleSQL_connection_struct *)connection; | |
| if(setjmp(cs.mark)) | |
| services._throw(cs.error); | |
| check(services, cs, "rollback", OCITransRollback(cs.svchp, cs.errhp, 0)); | void rollback(void *aconnection) { |
| Connection& connection=*static_cast<Connection *>(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(SQL_Driver_services&, void *connection) { | bool ping(void* /*connection*/) { |
| // maybe OCIServerVersion? | // maybe OCIServerVersion? |
| // select 0 from dual | // select 0 from dual |
| return true; | return true; |
| } | } |
| unsigned int quote( | const char* quote(void *aconnection, |
| SQL_Driver_services&, void *, | const char *from, unsigned int length) |
| char *to, const char *from, unsigned int length) { | { |
| /* | Connection& connection=*static_cast<Connection *>(aconnection); |
| it's already UNTAINT_TIMES_BIGGER | char *result=(char*)connection.services->malloc_atomic(length*2+1); |
| */ | char *to=result; |
| unsigned int result=length; | |
| while(length--) { | while(length--) { |
| switch(*from) { | switch(*from) { |
| case '\'': // "'" -> "''" | case '\'': // "'" -> "''" |
| *to++='\''; result++; | *to++='\''; |
| break; | break; |
| } | } |
| *to++=*from++; | *to++=*from++; |
| } | } |
| *to=0; | |
| return result; | return result; |
| } | } |
| void query( | |
| SQL_Driver_services& services, void *connection, | void query(void* aconnection, |
| const char *astatement, unsigned long offset, unsigned long limit, | const char* astatement, |
| SQL_Driver_query_event_handlers& handlers) { | size_t placeholders_count, Placeholder* placeholders, |
| unsigned long offset, unsigned long limit, | |
| OracleSQL_connection_struct &cs=*(OracleSQL_connection_struct *)connection; | SQL_Driver_query_event_handlers& handlers |
| OracleSQL_query_lobs lobs={{0}, 0}; | ){ |
| Connection& connection=*static_cast<Connection *>(aconnection); | |
| Query_lobs lobs={{0}, 0}; | |
| OCIStmt *stmthp=0; | OCIStmt *stmthp=0; |
| SQL_Driver_services& services=*connection.services; | |
| bool failed=false; | bool failed=false; |
| if(setjmp(cs.mark)) { | if(setjmp(connection.mark)) { |
| failed=true; | failed=true; |
| goto cleanup; | goto cleanup; |
| } else { | } else { |
| const char *statement=preprocess_statement(services, cs, | if(placeholders_count>MAX_BINDS) |
| astatement, lobs); | 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); | |
| check(services, cs, "HandleAlloc STMT", OCIHandleAlloc( | modified_statement mstatement=_preprocess_statement_limit(connection, statement, offset, limit); |
| (dvoid *)cs.envhp, (dvoid **) &stmthp, (ub4)OCI_HTYPE_STMT, 0, 0)); | statement=mstatement.statement; |
| check(services, cs, "syntax", | |
| OCIStmtPrepare(stmthp, cs.errhp, (unsigned char *)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)strlen((char *)statement), |
| (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT)); | (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<Bind_info*>(services.malloc_atomic(binds_size)); | |
| { | { |
| for(int i=0; i<lobs.count; i++) { | for(size_t i=0; i<placeholders_count; i++) { |
| check(services, cs, "alloc output var desc", OCIDescriptorAlloc( | Placeholder& ph=placeholders[i]; |
| (dvoid *)cs.envhp, (dvoid **)&lobs.items[i].locator, (ub4)OCI_DTYPE_LOB, 0, 0)); | Bind_info& bi=binds[i]; |
| bi.bind=0; | |
| // http://i/docs/oracle/server.804/a58234/basics.htm#422173 | |
| bi.indicator=ph.is_null? -1: MAGIC_INDICATOR_VALUE_MEANING_NOT_NULL_AND_UNCHANGED; | |
| size_t value_length; | |
| if(transcode_needed){ | |
| // transcode bind variables names and their values from $request:charset to ?ClientCharset | |
| size_t name_length; | |
| services.transcode(ph.name, strlen(ph.name), | |
| ph.name, name_length, | |
| request_charset, | |
| client_charset); | |
| if(ph.value) | |
| services.transcode(ph.value, strlen(ph.value), | |
| ph.value, value_length, | |
| request_charset, | |
| client_charset); | |
| } else { | |
| value_length=ph.value? strlen(ph.value): 0; | |
| } | |
| // clone value for possible output binds | |
| // note: even empty input can be replaced by huge output | |
| char*& value_buf=connection.bind_buffers[i]; // get cached buffer | |
| if(!value_buf) // allocate if needed, caching it | |
| value_buf=(char *)services.malloc_atomic(MAX_OUT_STRING_LENGTH+1/*terminator*/); | |
| if(value_length) | |
| memcpy(value_buf, ph.value, value_length+1); | |
| else | |
| value_buf[0]=0; | |
| char name_buf[MAX_STRING]; | |
| sb4 placeh_len=snprintf(name_buf, sizeof(name_buf), ":%s", ph.name); | |
| char check_step_buf[MAX_STRING]; | |
| snprintf(check_step_buf, sizeof(check_step_buf), "bind by name :%s", ph.name); | |
| check(connection, check_step_buf, OCIBindByName(stmthp, | |
| &bi.bind, connection.errhp, | |
| (text*)name_buf, placeh_len, | |
| (dvoid *)value_buf, (sword)(MAX_OUT_STRING_LENGTH+1), SQLT_STR, (dvoid *)&bi.indicator, | |
| (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT)); | |
| } | |
| check(services, cs, "bind output", OCIBindByPos(stmthp, | for(int i=0; i<lobs.count; i++) { |
| &lobs.items[i].bind, cs.errhp, | Query_lobs::Item &item=lobs.items[i]; |
| (ub4)1+i, | check(connection, "alloc output var desc", OCIDescriptorAlloc( |
| (dvoid *)&lobs.items[i].locator, | (dvoid *)connection.envhp, (dvoid **)&item.locator, (ub4)OCI_DTYPE_LOB, 0, 0)); |
| (sword)sizeof (lobs.items[i].locator), SQLT_CLOB, (dvoid *)0, | |
| char placeholder_buf[MAX_STRING]; | |
| sb4 placeh_len=snprintf(placeholder_buf, sizeof(placeholder_buf), | |
| ":%.*s", item.name_size, item.name_ptr); | |
| check(connection, "bind lob", OCIBindByName(stmthp, | |
| &item.bind, connection.errhp, | |
| (text*)placeholder_buf, placeh_len, | |
| (dvoid *)&item.locator, | |
| (sword)sizeof (item.locator), SQLT_CLOB, (dvoid *)0, | |
| (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DATA_AT_EXEC)); | (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DATA_AT_EXEC)); |
| lobs.items[i].rows.count=0; | item.rows.count=0; |
| OracleSQL_query_lobs::cbf_context_struct cbf_context={ | item.connection=&connection; |
| &services, &cs, &lobs.items[i].rows}; | check(connection, "bind dynamic", OCIBindDynamic( |
| check(services, cs, "bind dynamic", OCIBindDynamic( | item.bind, connection.errhp, |
| lobs.items[i].bind, cs.errhp, | (dvoid *) &item, cbf_no_data, |
| (dvoid *) &cbf_context, cbf_no_data, | (dvoid *) &item, cbf_get_data)); |
| (dvoid *) &cbf_context, cbf_get_data)); | |
| } | } |
| } | } |
| execute_prepared(services, cs, | execute_prepared(connection, |
| statement, stmthp, lobs, | statement, stmthp, lobs, |
| offset, limit, handlers); | offset, limit, handlers, mstatement.skip_rownum_column); |
| { | |
| for(size_t i=0; i<placeholders_count; i++) { | |
| Bind_info& bi=binds[i]; | |
| if(bi.indicator==MAGIC_INDICATOR_VALUE_MEANING_NOT_NULL_AND_UNCHANGED/*unchanged*/) | |
| continue; | |
| Placeholder& ph=placeholders[i]; | |
| if(bi.indicator==-1) | |
| ph.is_null=true; | |
| else | |
| if(bi.indicator==0) | |
| ph.is_null=false; | |
| else | |
| fail(connection, bi.indicator<0? | |
| "output bind buffer overflow, additionally size too big to be returned in 'indicator'" | |
| : "output bind buffer overflow"); | |
| ph.were_updated=true; | |
| const char* bind_buffer=connection.bind_buffers[i]; | |
| if( size_t value_length=strlen(bind_buffer) ) { | |
| char* returned_value=(char*)services.malloc_atomic(value_length+1/*terminator*/); | |
| memcpy(returned_value, bind_buffer, value_length+1 ); | |
| ph.value=returned_value; | |
| if(transcode_needed){ | |
| // transcode bind variable output from ?ClientCharset to $request:charset | |
| services.transcode(ph.value, value_length, | |
| ph.value, value_length/*<this new value is not used afterwards, actually*/, | |
| client_charset, | |
| request_charset); | |
| } | |
| } else { | |
| ph.value=0; | |
| } | |
| } | |
| } | |
| } | } |
| cleanup: // no check call after this point! | cleanup: // no check call after this point! |
| { | { |
| Line 436 cleanup: // no check call after this poi | Line 648 cleanup: // no check call after this poi |
| OCIDescriptorFree((dvoid *)locator, (ub4)OCI_DTYPE_LOB); | OCIDescriptorFree((dvoid *)locator, (ub4)OCI_DTYPE_LOB); |
| /* free rows descriptors */ | /* free rows descriptors */ |
| OracleSQL_query_lobs::return_rows &rows=lobs.items[i].rows; | Query_lobs::return_rows &rows=lobs.items[i].rows; |
| for(int r=0; r<rows.count; r++) | for(int r=0; r<rows.count; r++) |
| OCIDescriptorFree((dvoid *)rows.row[r].locator, (ub4)OCI_DTYPE_LOB); | OCIDescriptorFree((dvoid *)rows.row[r].locator, (ub4)OCI_DTYPE_LOB); |
| } | } |
| Line 444 cleanup: // no check call after this poi | Line 656 cleanup: // no check call after this poi |
| if(stmthp) | if(stmthp) |
| OCIHandleFree((dvoid *)stmthp, (ub4)OCI_HTYPE_STMT); | OCIHandleFree((dvoid *)stmthp, (ub4)OCI_HTYPE_STMT); |
| if(failed) | if(failed) { |
| services._throw(cs.error); | if(connection.sql_error.defined()) |
| services._throw(connection.sql_error); | |
| services._throw(connection.error); | |
| } | |
| } | } |
| private: // private funcs | private: // private funcs |
| const char *preprocess_statement(SQL_Driver_services& services, OracleSQL_connection_struct &cs, | const char* _preprocess_statement_lobs( |
| const char *astatement, OracleSQL_query_lobs &lobs) { | Connection& connection, |
| size_t statement_size=strlen(astatement); | const char* statement, |
| Query_lobs &lobs | |
| char *result=(char *)services.malloc(statement_size | ){ |
| size_t statement_size=strlen(statement); | |
| SQL_Driver_services& services=*connection.services; | |
| char *result=(char *)services.malloc_atomic(statement_size | |
| +MAX_STRING // in case of short 'strings' | +MAX_STRING // in case of short 'strings' |
| +11/* returning */+6/* into */+(MAX_LOB_NAME_LENGTH+2/*:, */)*2/*ret into*/*MAX_IN_LOBS | +11/* returning */+6/* into */+(MAX_LOB_NAME_LENGTH+2/*:, */)*2/*ret into*/*MAX_IN_LOBS |
| +1); | +1); |
| const char *o=astatement; | const char *o=statement; |
| // /**xxx**/'literal' -> EMPTY_CLOB_FUNC_CALL | // /**xxx**/'literal' -> EMPTY_CLOB_FUNC_CALL |
| char *n=result; | char *n=result; |
| Line 467 private: // private funcs | Line 686 private: // private funcs |
| o[0]=='/' && | o[0]=='/' && |
| o[1]=='*' && | o[1]=='*' && |
| o[2]=='*') { // name start | o[2]=='*') { // name start |
| const char* saved_o=o; | |
| o+=3; | o+=3; |
| const char *name_begin=o; | const char *name_begin=o; |
| while(*o) | while(*o) |
| Line 475 private: // private funcs | Line 695 private: // private funcs |
| o[1]=='*' && | o[1]=='*' && |
| o[2]=='/' && | o[2]=='/' && |
| o[3]=='\'') { // name end | o[3]=='\'') { // name end |
| saved_o=0; // found, marking that | |
| const char *name_end=o; | const char *name_end=o; |
| o+=4; | o+=4; |
| OracleSQL_query_lobs::Item &item=lobs.items[lobs.count++]; | Query_lobs::Item &item=lobs.items[lobs.count++]; |
| item.name_ptr=name_begin; item.name_size=name_end-name_begin; | item.name_ptr=name_begin; item.name_size=name_end-name_begin; |
| item.data_ptr=(char *)services.malloc(statement_size/*max*/); item.data_size=0; | item.data_ptr=(char *)services.malloc_atomic(statement_size/*max*/); item.data_size=0; |
| const char *start=o; | const char *start=o; |
| bool escaped=false; | bool escaped=false; |
| while(*o && !(o[0]=='\'' && o[1]!='\'' && !escaped)) { | while(*o && !(o[0]=='\'' && o[1]!='\'' && !escaped)) { |
| escaped=o[0]=='\'' && o[1]=='\''; | escaped=!escaped && (o[0]=='\'' && o[1]=='\''); |
| if(escaped) { | if(escaped) { |
| // write pending, skip "\" or "'" | // write pending, skip "\" or "'" |
| if(size_t size=o-start) { | if(size_t size=o-start) { |
| Line 506 private: // private funcs | Line 727 private: // private funcs |
| break; | break; |
| } else | } else |
| o++; // /**skip**/'xxx' | o++; // /**skip**/'xxx' |
| if(saved_o) { | |
| o=saved_o; | |
| *n++=*o++; | |
| } | |
| } else | } else |
| *n++=*o++; | *n++=*o++; |
| } | } |
| Line 518 private: // private funcs | Line 743 private: // private funcs |
| if(i) | if(i) |
| *n++=','; | *n++=','; |
| n+=sprintf(n, "%.*s", lobs.items[i].name_size, lobs.items[i].name_ptr); | n+=sprintf(n, "%.*s", lobs.items[i].name_size, lobs.items[i].name_ptr); |
| /*memcpy(n, lobs.items[i].name_ptr, lobs.items[i].name_size); | |
| n+=lobs.items[i].name_size;*/ | |
| } | } |
| n+=sprintf(n, " into "); | n+=sprintf(n, " into "); |
| for(i=0; i<lobs.count; i++) { | for(i=0; i<lobs.count; i++) { |
| if(i) | if(i) |
| *n++='x'; | *n++=','; |
| n+=sprintf(n, ":%.*s", lobs.items[i].name_size, lobs.items[i].name_ptr); | n+=sprintf(n, ":%.*s", lobs.items[i].name_size, lobs.items[i].name_ptr); |
| /**n++=':'; | |
| memcpy(n, lobs.items[i].name_ptr, lobs.items[i].name_size); | |
| n+=lobs.items[i].name_size;*/ | |
| } | } |
| } | } |
| Line 536 private: // private funcs | Line 756 private: // private funcs |
| } | } |
| void execute_prepared( | void execute_prepared( |
| SQL_Driver_services& services, OracleSQL_connection_struct &cs, | Connection& connection, |
| const char *statement, OCIStmt *stmthp, OracleSQL_query_lobs &lobs, | const char* astatement, OCIStmt *stmthp, Query_lobs &lobs, |
| unsigned long offset, unsigned long limit, | unsigned long offset, unsigned long limit, |
| SQL_Driver_query_event_handlers& handlers) { | SQL_Driver_query_event_handlers& handlers, bool skip_rownum_column) { |
| ub2 stmt_type=0; // UNKNOWN | ub2 stmt_type=0; // UNKNOWN |
| /* | /* |
| //gpfs on sun. paf 000818 | //gpfs on sun. paf 000818 |
| //Zanyway, this is needed before. | //Zanyway, this is needed before. |
| check(services, cs, "get stmt type", OCIAttrGet( | check(connection, "get stmt type", OCIAttrGet( |
| (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, (ub1 *)&stmt_type, | (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, (ub1 *)&stmt_type, |
| (ub4 *)0, OCI_ATTR_STMT_TYPE, cs.errhp)); | (ub4 *)0, OCI_ATTR_STMT_TYPE, connection.errhp)); |
| */ | */ |
| if(strncasecmp(statement, "select", 6)==0) | |
| if(strncasecmp(astatement, "select", 6)==0) | |
| stmt_type=OCI_STMT_SELECT; | stmt_type=OCI_STMT_SELECT; |
| else if(strncasecmp(statement, "insert", 6)==0) | else if(strncasecmp(astatement, "insert", 6)==0) |
| stmt_type=OCI_STMT_INSERT; | stmt_type=OCI_STMT_INSERT; |
| else if(strncasecmp(statement, "update", 6)==0) | else if(strncasecmp(astatement, "update", 6)==0) |
| stmt_type=OCI_STMT_UPDATE; | stmt_type=OCI_STMT_UPDATE; |
| sword status=OCIStmtExecute(cs.svchp, stmthp, cs.errhp, | sword status=OCIStmtExecute(connection.svchp, stmthp, connection.errhp, |
| (ub4)stmt_type==OCI_STMT_SELECT?0:1, (ub4)0, | (ub4)stmt_type==OCI_STMT_SELECT?0:1, (ub4)0, |
| (OCISnapshot *)NULL, | (OCISnapshot *)NULL, |
| (OCISnapshot *)NULL, (ub4)OCI_DEFAULT); | (OCISnapshot *)NULL, (ub4)OCI_DEFAULT); |
| if(status!=OCI_NO_DATA) | if(status!=OCI_NO_DATA) |
| check(services, cs, "execute", status); | check(connection, "execute", status); |
| { | { |
| for(int i=0; i<lobs.count; i++) | for(int i=0; i<lobs.count; i++) |
| if(ub4 bytes_to_write=lobs.items[i].data_size) { | if(ub4 bytes_to_write=lobs.items[i].data_size) { |
| OracleSQL_query_lobs::return_rows *rows=&lobs.items[i].rows; | Query_lobs::return_rows *rows=&lobs.items[i].rows; |
| for(int r=0; r<rows->count; r++) { | for(int r=0; r<rows->count; r++) { |
| OCILobLocator *locator=rows->row[r].locator; | OCILobLocator *locator=rows->row[r].locator; |
| check(services, cs, "lobwrite", OCILobWrite ( | check(connection, "lobwrite", OCILobWrite ( |
| cs.svchp, cs.errhp, | connection.svchp, connection.errhp, |
| locator, &bytes_to_write, 1, | locator, &bytes_to_write, 1, |
| (dvoid *)lobs.items[i].data_ptr, (ub4)bytes_to_write, OCI_ONE_PIECE, | (dvoid *)lobs.items[i].data_ptr, (ub4)bytes_to_write, OCI_ONE_PIECE, |
| (dvoid *)0, 0, (ub2)0, | (dvoid *)0, 0, (ub2)0, |
| Line 582 private: // private funcs | Line 803 private: // private funcs |
| switch(stmt_type) { | switch(stmt_type) { |
| case OCI_STMT_SELECT: | case OCI_STMT_SELECT: |
| fetch_table(services, cs, | fetch_table(connection, |
| stmthp, offset, limit, | stmthp, offset, limit, |
| handlers); | handlers, skip_rownum_column); |
| break; | break; |
| default: | default: |
| /* | /* |
| Line 595 private: // private funcs | Line 816 private: // private funcs |
| } | } |
| } | } |
| void fetch_table(SQL_Driver_services& services, OracleSQL_connection_struct &cs, | void fetch_table(Connection& connection, |
| OCIStmt *stmthp, unsigned long offset, unsigned long limit, | OCIStmt *stmthp, unsigned long offset, unsigned long limit, |
| SQL_Driver_query_event_handlers& handlers) { | SQL_Driver_query_event_handlers& handlers, bool skip_rownum_column) |
| { | |
| SQL_Driver_services& services=*connection.services; | |
| ub4 prefetch_rows=100; | ub4 prefetch_rows=100; |
| check(services, cs, "AttrSet prefetch-rows", OCIAttrSet( | check(connection, "AttrSet prefetch-rows", OCIAttrSet( |
| (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, | (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, |
| (dvoid *)&prefetch_rows, (ub4)0, | (dvoid *)&prefetch_rows, (ub4)0, |
| (ub4)OCI_ATTR_PREFETCH_ROWS, (OCIError *)cs.errhp)); | (ub4)OCI_ATTR_PREFETCH_ROWS, (OCIError *)connection.errhp)); |
| ub4 prefetch_mem_size=100*1024; | ub4 prefetch_mem_size=100*0x400; |
| check(services, cs, "AttrSet prefetch-memory", OCIAttrSet( | check(connection, "AttrSet prefetch-memory", OCIAttrSet( |
| (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, | (dvoid *)stmthp, (ub4)OCI_HTYPE_STMT, |
| (dvoid *)&prefetch_mem_size, (ub4)0, | (dvoid *)&prefetch_mem_size, (ub4)0, |
| (ub4)OCI_ATTR_PREFETCH_MEMORY, (OCIError *)cs.errhp)); | (ub4)OCI_ATTR_PREFETCH_MEMORY, (OCIError *)connection.errhp)); |
| OCIParam *mypard; | OCIParam *mypard; |
| ub2 dtype; | ub2 dtype; |
| text *col_name; | const char* col_name; |
| struct { | struct Col { |
| ub2 type; | ub2 type; |
| char *str; | char *str; |
| OCILobLocator *var; | OCILobLocator *var; |
| Line 625 private: // private funcs | Line 848 private: // private funcs |
| int column_count=0; | int column_count=0; |
| bool failed=false; | bool failed=false; |
| jmp_buf saved_mark; memcpy(saved_mark, cs.mark, sizeof(jmp_buf)); | jmp_buf saved_mark; memcpy(saved_mark, connection.mark, sizeof(jmp_buf)); |
| if(setjmp(cs.mark)) { | if(setjmp(connection.mark)) { |
| failed=true; | failed=true; |
| goto cleanup; | goto cleanup; |
| } else { | } 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 | // idea of preincrementing is that at error time all handles would free up |
| while(++column_count<=MAX_COLS) { | while(++column_count<=MAX_COLS) { |
| /* get next descriptor, if there is one */ | /* get next descriptor, if there is one */ |
| if(OCIParamGet(stmthp, OCI_HTYPE_STMT, cs.errhp, (void **)&mypard, | if(OCIParamGet(stmthp, OCI_HTYPE_STMT, connection.errhp, (void **)&mypard, |
| (ub4) column_count)!=OCI_SUCCESS) { | (ub4) column_count)!=OCI_SUCCESS) { |
| --column_count; | --column_count; |
| break; | break; |
| } | } |
| if(skip_rownum_column && column_count==1) | |
| continue; | |
| /* Retrieve the data type attribute */ | /* Retrieve the data type attribute */ |
| check(services, cs, "get type", OCIAttrGet( | check(connection, "get type", OCIAttrGet( |
| (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, | (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, |
| (dvoid*) &dtype, (ub4 *)0, (ub4)OCI_ATTR_DATA_TYPE, | (dvoid*) &dtype, (ub4 *)0, (ub4)OCI_ATTR_DATA_TYPE, |
| (OCIError *)cs.errhp)); | (OCIError *)connection.errhp)); |
| /* Retrieve the column name attribute */ | /* Retrieve the column name attribute */ |
| ub4 col_name_len; | ub4 col_name_len; |
| check(services, cs, "get name", OCIAttrGet( | check(connection, "get name", OCIAttrGet( |
| (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, | (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, |
| (dvoid**) &col_name, (ub4 *) &col_name_len, (ub4)OCI_ATTR_NAME, | (dvoid**) &col_name, (ub4 *) &col_name_len, (ub4)OCI_ATTR_NAME, |
| (OCIError *)cs.errhp)); | (OCIError *)connection.errhp)); |
| if(transcode_needed){ | |
| // transcode column name from ?ClientCharset to $request:charset | |
| services.transcode(col_name, col_name_len, | |
| col_name, col_name_len, | |
| client_charset, | |
| request_charset); | |
| } | |
| Col& col=cols[column_count-1]; | |
| { | { |
| size_t size=(size_t)col_name_len; | size_t length=(size_t)col_name_len; |
| char *ptr=(char *)services.malloc(size); | char *ptr=(char *)services.malloc_atomic(length+1); |
| tolower(ptr, (char *)col_name, size); | if( connection.options.bLowerCaseColumnNames ) |
| handlers.add_column(ptr, size); | 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; | ub2 coerce_type=dtype; |
| Line 664 private: // private funcs | Line 907 private: // private funcs |
| void *ptr; | void *ptr; |
| switch(dtype) { | switch(dtype) { |
| case SQLT_CLOB: | case SQLT_CLOB: |
| { | { |
| check(services, cs, "alloc output var desc", OCIDescriptorAlloc( | check(connection, "alloc output var desc", OCIDescriptorAlloc( |
| (dvoid *)cs.envhp, (dvoid **)(ptr=&cols[column_count-1].var), | (dvoid *)connection.envhp, (dvoid **)(ptr=&col.var), |
| (ub4)OCI_DTYPE_LOB, | (ub4)OCI_DTYPE_LOB, |
| 0, (dvoid **)0)); | 0, (dvoid **)0)); |
| size=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; | break; |
| } | |
| default: | |
| coerce_type=SQLT_STR; | |
| ptr=cols[column_count-1].str=(char *)services.malloc(MAX_OUT_STRING_LENGTH+1); | |
| size=MAX_OUT_STRING_LENGTH; | |
| break; | |
| } | } |
| cols[column_count-1].type=coerce_type; | col.type=coerce_type; |
| check(services, cs, "DefineByPos", OCIDefineByPos( | // http://i/docs/oracle/server.804/a58234/oci_func.htm#449680 |
| stmthp, &cols[column_count-1].def, cs.errhp, | // 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, | column_count, (ub1 *) ptr, size, |
| coerce_type, (dvoid *) &cols[column_count-1].indicator, | coerce_type, (dvoid *) &col.indicator, |
| (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); | (ub2 *)0, (ub2 *)0, OCI_DEFAULT)); |
| } | } |
| handlers.before_rows(); | check(connection, handlers.before_rows(connection.sql_error)); |
| for(unsigned long row=0; !limit||row<limit+offset; row++) { | for(unsigned long row=0; limit==SQL_NO_LIMIT || row<offset+limit; row++) { |
| sword status=OCIStmtFetch(stmthp, cs.errhp, (ub4)1, (ub4)OCI_FETCH_NEXT, | sword status=OCIStmtFetch(stmthp, connection.errhp, (ub4)1, (ub4)OCI_FETCH_NEXT, |
| (ub4)OCI_DEFAULT); | (ub4)OCI_DEFAULT); |
| if(status==OCI_NO_DATA) | if(status==OCI_NO_DATA) |
| break; | break; |
| check(services, cs, "fetch", status); | check(connection, "fetch", status); |
| if(row>=offset) { | if(row>=offset) { |
| handlers.add_row(); | check(connection, handlers.add_row(connection.sql_error)); |
| for(int i=0; i<column_count; i++) { | for(int i=0; i<column_count; i++) { |
| size_t size=0; | if(skip_rownum_column && i==0) |
| void *ptr=0; | continue; |
| if(!cols[i].indicator) // not NULL | |
| switch(cols[i].type) { | size_t length=0; |
| case SQLT_CLOB: | char* strm=0; |
| { | bool transcode_value=transcode_needed; |
| ub4 amtp=4096000000UL; | |
| ub4 offset=1; | sb2 indicator=cols[i].indicator; |
| ub4 loblen=0; | if(indicator!=-1) { // not NULL |
| OCILobLocator *var=(OCILobLocator *)cols[i].var; | if(indicator!=0) |
| OCILobGetLength(cs.svchp, cs.errhp, var, &loblen); | fail(connection, indicator<0? |
| if(loblen) { | "column return buffer overflow, additionally size too big to be returned in 'indicator'" |
| size=(size_t)loblen; | : "column return buffer overflow"); |
| ptr=services.malloc(size); | |
| check(services, cs, "lobread", OCILobRead(cs.svchp, cs.errhp, | switch(cols[i].type){ |
| var, &amtp, offset, (dvoid *) ptr, | case SQLT_CLOB: |
| loblen, (dvoid *)0, | { |
| 0, | ub4 offset=1; |
| (ub2)0, (ub1) SQLCS_IMPLICIT)); | OCILobLocator *var=(OCILobLocator *)cols[i].var; |
| size_t read_size=0; | |
| strm=(char*)services.malloc_atomic(1/*for terminator*/); // set type of memory block | |
| do { | |
| char buf[MAX_STRING*10]; | |
| ub4 amtp=0/*to be read in stream mode*/; | |
| // http://i/docs/oracle/server.804/a58234/oci_func.htm#427818 | |
| status=OCILobRead(connection.svchp, connection.errhp, | |
| var, &amtp, offset, (dvoid *)buf, | |
| sizeof(buf), | |
| (dvoid *)0, 0, | |
| (ub2)0, (ub1)SQLCS_IMPLICIT); | |
| if(status!=OCI_SUCCESS && status!=OCI_NEED_DATA) | |
| check(connection, "lobread", status); | |
| strm=(char*)services.realloc(strm, read_size+amtp+1/*for termintator*/); | |
| memcpy(strm+read_size, buf, amtp); | |
| read_size+=amtp; | |
| offset+=amtp; | |
| } while(status==OCI_NEED_DATA); | |
| length=(size_t)read_size; | |
| strm[length]=0; | |
| break; | |
| } | |
| case SQLT_NUM: | |
| case SQLT_INT: | |
| case SQLT_FLT: | |
| case SQLT_LNG: | |
| case SQLT_RID: | |
| case SQLT_UIN: | |
| case SQLT_DATE: | |
| case SQLT_TIME: | |
| case SQLT_TIMESTAMP: | |
| transcode_value=false; // transcode calls not needed for numbers and dates | |
| default: | |
| if(const char *value=cols[i].str) { | |
| length=strlen(value); | |
| strm=(char*)services.malloc_atomic(length+1); | |
| memcpy(strm, value, length+1); | |
| } else { | |
| length=0; | |
| strm=0; | |
| } | } |
| break; | break; |
| } | |
| default: | |
| if(const char *str=cols[i].str) { | |
| size=strlen(str); | |
| ptr=services.malloc(size); | |
| memcpy(ptr, str, size); | |
| } else { | |
| size=0; | |
| ptr=0; | |
| } | |
| break; | |
| } | } |
| handlers.add_row_cell(ptr, size); | } |
| const char* str=strm; | |
| if(transcode_value && str && length){ | |
| // transcode cell value from ?ClientCharset to $request:charset | |
| services.transcode(str, length, | |
| str, length, | |
| client_charset, | |
| request_charset); | |
| } | |
| check(connection, handlers.add_row_cell(connection.sql_error, str, length)); | |
| } | } |
| } | } |
| } | } |
| Line 757 cleanup: // no check call after this poi | Line 1051 cleanup: // no check call after this poi |
| longjmp(saved_mark, 1); | longjmp(saved_mark, 1); |
| } | } |
| modified_statement _preprocess_statement_limit( | |
| Connection& connection, | |
| const char* astatement, | |
| unsigned long offset, | |
| unsigned long limit | |
| ){ | |
| modified_statement result={astatement, false, false, false}; | |
| if(!connection.options.bDisableQueryModification && limit!=SQL_NO_LIMIT && strncasecmp(astatement, "select", 6)==0){ | |
| result.limit=true; | |
| size_t statement_size=strlen(astatement); | |
| char* statement_limited; | |
| if(offset && limit/* throwing offset away if limit==0 */){ | |
| result.skip_rownum_column=true; | |
| result.offset=true; | |
| // SELECT * FROM (SELECT ROWNUM r__, z__.* FROM (user_query) z__) WHERE r__<=limit+offset AND r__>offset | |
| 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__<=%u", limit+offset); | |
| statement_limited+=snprintf(statement_limited, 9+MAX_NUMBER, " AND r__>%u", 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<=%u", limit); | |
| } | |
| *statement_limited=0; | |
| //connection.services->_throw(result.statement); | |
| } | |
| return result; | |
| } | |
| private: // conn client library funcs | private: // conn client library funcs |
| friend void check( | friend void fail(Connection& connection, const char *msg); |
| SQL_Driver_services& services, OracleSQL_connection_struct &cs, | friend void check(Connection& connection, const char *step, sword status); |
| const char *step, sword status); | |
| friend sb4 cbf_get_data(dvoid *ctxp, | friend sb4 cbf_get_data(dvoid *ctxp, |
| OCIBind *bindp, | OCIBind *bindp, |
| ub4 iter, ub4 index, | ub4 iter, ub4 index, |
| Line 795 private: // conn client library funcs | Line 1154 private: // conn client library funcs |
| ub2 dty, dvoid *indp, ub2 *alenp, ub2 *rcodep, | ub2 dty, dvoid *indp, ub2 *alenp, ub2 *rcodep, |
| ub4 maxarr_len, ub4 *curelep, ub4 mode)); | 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, | OCI_DECL(BindDynamic, (OCIBind *bindp, OCIError *errhp, dvoid *ictxp, |
| OCICallbackInBind icbfp, dvoid *octxp, | OCICallbackInBind icbfp, dvoid *octxp, |
| OCICallbackOutBind ocbfp)); | OCICallbackOutBind ocbfp)); |
| Line 871 private: // conn client library funcs li | Line 1235 private: // conn client library funcs li |
| const char *dlink(const char *dlopen_file_spec) { | const char *dlink(const char *dlopen_file_spec) { |
| if(lt_dlinit()) | if(lt_dlinit()) |
| return lt_dlerror(); | return lt_dlerror(); |
| lt_dlhandle handle=lt_dlopen(dlopen_file_spec); | lt_dlhandle handle=lt_dlopen(dlopen_file_spec); |
| //return "hren31"; | |
| if(!handle) | if(!handle) |
| return lt_dlerror(); //"can not open the dynamic link module"; | return lt_dlerror(); //"can not open the dynamic link module"; |
| Line 893 private: // conn client library funcs li | Line 1250 private: // conn client library funcs li |
| OCI_LINK(Initialize); | OCI_LINK(Initialize); |
| OCI_LINK(EnvInit); | OCI_LINK(EnvInit); |
| OCI_LINK(AttrGet); OCI_LINK(AttrSet); | OCI_LINK(AttrGet); OCI_LINK(AttrSet); |
| OCI_LINK(BindByPos); OCI_LINK(BindDynamic); | OCI_LINK(BindByPos); OCI_LINK(BindByName); OCI_LINK(BindDynamic); |
| OCI_LINK(DefineByPos); | OCI_LINK(DefineByPos); |
| OCI_LINK(DescriptorAlloc); OCI_LINK(DescriptorFree); | OCI_LINK(DescriptorAlloc); OCI_LINK(DescriptorFree); |
| OCI_LINK(ErrorGet); | OCI_LINK(ErrorGet); |
| Line 911 private: // conn client library funcs li | Line 1268 private: // conn client library funcs li |
| } *OracleSQL_driver; | } *OracleSQL_driver; |
| void check( | void check(Connection& connection, const char *step, sword status) { |
| SQL_Driver_services& services, OracleSQL_connection_struct &cs, | |
| const char *step, sword status) { | |
| const char *msg; | const char *msg; |
| char reason[MAX_STRING/2]; | char reason[MAX_STRING/2]; |
| switch (status) { | switch (status) { |
| case OCI_SUCCESS: | case OCI_SUCCESS: // hurrah |
| return; // 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: | case OCI_ERROR: |
| { | { |
| sb4 errcode; | sb4 errcode; |
| if(OracleSQL_driver->OCIErrorGet((dvoid *)cs.errhp, (ub4)1, (text *)NULL, &errcode, | if(OracleSQL_driver->OCIErrorGet((dvoid *)connection.errhp, (ub4)1, (text *)NULL, &errcode, |
| (text *)reason, (ub4)sizeof(reason), OCI_HTYPE_ERROR)==OCI_SUCCESS) | (text *)reason, (ub4)sizeof(reason), OCI_HTYPE_ERROR)==OCI_SUCCESS) { |
| msg=reason; | msg=reason; |
| else | |
| msg="[can not get error description]"; | if(msg && transcode_required(connection)){ |
| break; | // 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_SUCCESS_WITH_INFO: | |
| msg="SUCCESS_WITH_INFO"; break; | |
| case OCI_NEED_DATA: | case OCI_NEED_DATA: |
| msg="NEED_DATA"; break; | msg="NEED_DATA"; break; |
| case OCI_NO_DATA: | case OCI_NO_DATA: |
| Line 947 void check( | Line 1312 void check( |
| msg="unknown"; break; | msg="unknown"; break; |
| } | } |
| snprintf(cs.error, sizeof(cs.error), "%s (%s, %d)", | snprintf(connection.error, sizeof(connection.error), "%s (%s, %d)", |
| msg, step, (int)status); | msg, step, (int)status); |
| longjmp(cs.mark, 1); | 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. */ | /* Intbind callback that does not do any data input. */ |
| /* ----------------------------------------------------------------- */ | /* ----------------------------------------------------------------- */ |
| static sb4 cbf_no_data( | sb4 cbf_no_data( |
| dvoid *ctxp, | dvoid* /*ctxp*/, |
| OCIBind *bindp, | OCIBind* /*bindp*/, |
| ub4 iter, ub4 index, | ub4 /*iter*/, ub4 /*index*/, |
| dvoid **bufpp, | dvoid **bufpp, |
| ub4 *alenpp, | ub4 *alenpp, |
| ub1 *piecep, | ub1 *piecep, |
| Line 978 static sb4 cbf_no_data( | Line 1356 static sb4 cbf_no_data( |
| /* ----------------------------------------------------------------- */ | /* ----------------------------------------------------------------- */ |
| static sb4 cbf_get_data(dvoid *ctxp, | static sb4 cbf_get_data(dvoid *ctxp, |
| OCIBind *bindp, | OCIBind *bindp, |
| ub4 iter, ub4 index, | ub4 /*iter*/, ub4 index, |
| dvoid **bufpp, | dvoid **bufpp, |
| ub4 **alenp, | ub4 **alenp, |
| ub1 *piecep, | ub1 *piecep, |
| dvoid **indpp, | dvoid **indpp, |
| ub2 **rcodepp) { | ub2 **rcodepp) { |
| OracleSQL_query_lobs::cbf_context_struct &context= | Query_lobs::Item& context=*static_cast<Query_lobs::Item*>(ctxp); |
| *(OracleSQL_query_lobs::cbf_context_struct *)ctxp; | |
| if(index==0) { | if(index==0) { |
| static ub4 rows; | static ub4 rows; |
| check(*context.services, *context.cs, "AttrGet cbf_get_data ROWS_RETURNED", | check(*context.connection, "AttrGet cbf_get_data ROWS_RETURNED", |
| OracleSQL_driver->OCIAttrGet( | OracleSQL_driver->OCIAttrGet( |
| (CONST dvoid *) bindp, OCI_HTYPE_BIND, (dvoid *)&rows, | (CONST dvoid *) bindp, OCI_HTYPE_BIND, (dvoid *)&rows, |
| (ub4 *)sizeof(ub2), OCI_ATTR_ROWS_RETURNED, context.cs->errhp)) ; | (ub4 *)sizeof(ub2), OCI_ATTR_ROWS_RETURNED, context.connection->errhp)) ; |
| context.rows->count=(ub2)rows; | context.rows.count=(ub2)rows; |
| context.rows->row=(OracleSQL_query_lobs::return_rows::return_row *) | context.rows.row=(Query_lobs::return_rows::return_row *) |
| context.services->malloc(sizeof(OracleSQL_query_lobs::return_rows::return_row)*rows); | context.connection->services->malloc_atomic(sizeof(Query_lobs::return_rows::return_row)*rows); |
| } | } |
| OracleSQL_query_lobs::return_rows::return_row &var=context.rows->row[index]; | Query_lobs::return_rows::return_row &var=context.rows.row[index]; |
| check(*context.services, *context.cs, "alloc output var desc dynamic", OracleSQL_driver->OCIDescriptorAlloc( | check(*context.connection, "alloc output var desc dynamic", OracleSQL_driver->OCIDescriptorAlloc( |
| (dvoid *) context.cs->envhp, (dvoid **)&var.locator, | (dvoid *) context.connection->envhp, (dvoid **)&var.locator, |
| (ub4)OCI_DTYPE_LOB, | (ub4)OCI_DTYPE_LOB, |
| 0, (dvoid **)0)); | 0, (dvoid **)0)); |
| Line 1014 static sb4 cbf_get_data(dvoid *ctxp, | Line 1391 static sb4 cbf_get_data(dvoid *ctxp, |
| return OCI_CONTINUE; | return OCI_CONTINUE; |
| } | } |
| void tolower(char *out, const char *in, size_t size) { | |
| while(size--) | |
| *out++=tolower(*in++); | |
| } | |
| extern "C" SQL_Driver *SQL_DRIVER_CREATE() { | extern "C" SQL_Driver *SQL_DRIVER_CREATE() { |
| //_asm int 3; | |
| return OracleSQL_driver=new OracleSQL_Driver(); | return OracleSQL_driver=new OracleSQL_Driver(); |
| } | } |