--- sql/oracle/parser3oracle.C 2004/10/07 09:27:02 1.65 +++ sql/oracle/parser3oracle.C 2013/05/27 20:10:14 1.78 @@ -1,21 +1,21 @@ /** @file Parser Oracle driver. - Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2001-2012 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] */ -static const char *RCSId="$Id: parser3oracle.C,v 1.65 2004/10/07 09:27:02 paf Exp $"; - #include "config_includes.h" #include "pa_sql_driver.h" #include +volatile const char * IDENT_PARSER3ORACLE_C="$Id: parser3oracle.C,v 1.78 2013/05/27 20:10:14 moko Exp $" IDENT_PA_SQL_DRIVER_H; + #define MAX_COLS 500 #define MAX_IN_LOBS 5 #define MAX_LOB_NAME_LENGTH 100 @@ -95,24 +95,49 @@ static int pa_setenv(const char *name, c #endif } -static char *lsplit(char *string, char delim) { - if(string) { - char *v=strchr(string, delim); - if(v) { +static char *lsplit(char *string, char delim){ + if(string){ + if(char* v=strchr(string, delim)){ *v=0; return v+1; } - } - return 0; + } + return 0; } -static char *lsplit(char **string_ref, char delim) { - char *result=*string_ref; +static char *lsplit(char **string_ref, char delim){ + char *result=*string_ref; char *next=lsplit(*string_ref, delim); - *string_ref=next; - return result; + *string_ref=next; + return result; +} + +static char* rsplit(char* string, char delim){ + if(string){ + if(char* v=strrchr(string, delim)){ + *v=0; + return v+1; + } + } + 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 struct Connection { SQL_Driver_services *services; @@ -130,7 +155,8 @@ struct Connection { struct Options { bool bLowerCaseColumnNames; - const char* cstrClientCharset; + bool bDisableQueryModification; + const char* client_charset; } options; }; @@ -157,7 +183,7 @@ struct Query_lobs { #endif // forwards -static void faile(Connection& connection, const char *msg); +static void fail(Connection& connection, const char *msg); static void check(Connection& connection, const char *step, sword status); static void check(Connection& connection, bool error); static sb4 cbf_no_data( @@ -176,25 +202,31 @@ static sb4 cbf_get_data(dvoid *ctxp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp); -static void tolower_str(char *out, const char *in, size_t size); -static void toupper_str(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) { + 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->cstrClientCharset = value; + options->client_charset=value; } continue; } - if( strcmp( key, "LowerCaseColumnNames" ) == 0 ) { + if(strcmp(key, "LowerCaseColumnNames")==0){ + if(options) + options->bLowerCaseColumnNames=atoi(value)!=0; + continue; + } + + if(strcmp(key, "DisableQueryModification")==0){ if(options) - options->bLowerCaseColumnNames = atoi(value)!=0; + options->bDisableQueryModification=atoi(value)!=0; continue; } @@ -229,21 +261,22 @@ public: /// get api version int api_version() { return SQL_DRIVER_API_VERSION; } + /** initialize driver by loading sql dynamic link library @todo ?objects=1 which would turn on OCI_OBJECT init flag */ const char *initialize(char *dlopen_file_spec) { char *options=lsplit(dlopen_file_spec, '?'); - const char *error=dlopen_file_spec? - dlink(dlopen_file_spec):"client library column is empty"; + const char *error=options2env(options, 0); + if(!error) { - error=options2env(options, 0); + error=dlopen_file_spec ? dlink(dlopen_file_spec) : "client library column is empty"; if(!error) OCIInitialize((ub4)OCI_THREADED/*| OCI_OBJECT*/, (dvoid *)0, - (dvoid * (*)(void *, unsigned int))0, - (dvoid * (*)(void*, void*, unsigned int))0, + (dvoid * (*)(void *, size_t))0, + (dvoid * (*)(void*, void*, size_t))0, (void (*)(void*, void*))0 ); } @@ -254,28 +287,31 @@ public: /** connect @param url format: @b user:pass@service? - ORACLE_HOME=/u01/app/oracle/product/8.1.5& - ORA_NLS33=/u01/app/oracle/product/8.1.5/ocommon/nls/admin/data& - NLS_LANG=RUSSIAN_AMERICA.CL8MSWIN1251& - ORA_ENCRYPT_LOGIN=TRUE + ORACLE_HOME=/u01/app/oracle/product/8.1.5& + ORA_NLS33=/u01/app/oracle/product/8.1.5/ocommon/nls/admin/data& + NLS_LANG=RUSSIAN_AMERICA.CL8MSWIN1251& + ORA_ENCRYPT_LOGIN=TRUE& + ClientCharset=charset& + LowerCaseColumnNames=0 @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( - char *url, - SQL_Driver_services& services, - void **connection_ref ///< output: Connection * + char *url, + SQL_Driver_services& services, + void **connection_ref ///< output: Connection * ) { // connections are cross-request, do not use services._alloc [linked with request] - Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); + Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); connection.services=&services; - connection.options.bLowerCaseColumnNames = true; + connection.options.bLowerCaseColumnNames=true; + connection.options.bDisableQueryModification=false; *connection_ref=&connection; char *user=url; - char *service=lsplit(user, '@'); + char *service=rsplit(user, '@'); char *pwd=lsplit(user, ':'); char *options=lsplit(service, '?'); @@ -348,6 +384,7 @@ public: (dvoid *)connection.usrhp, (ub4)0, OCI_ATTR_SESSION, connection.errhp)); } + void disconnect(void *aconnection) { Connection& connection=*static_cast(aconnection); @@ -383,6 +420,7 @@ public: // 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)) @@ -390,6 +428,7 @@ public: check(connection, "commit", OCITransCommit(connection.svchp, connection.errhp, 0)); } + void rollback(void *aconnection) { Connection& connection=*static_cast(aconnection); if(setjmp(connection.mark)) @@ -406,46 +445,50 @@ public: return true; } - const char* quote(void *aconnection, - const char *from, unsigned int length) + // 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) { - Connection& connection=*static_cast(aconnection); - char *result=(char*)connection.services->malloc_atomic(length*2+1); - char *to=result; - while(length--) { - switch(*from) { - case '\'': // "'" -> "''" - *to++='\''; - break; - } - *to++=*from++; + 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) - { + 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); - const char* cstrClientCharset=connection.options.cstrClientCharset; Query_lobs lobs={{0}, 0}; OCIStmt *stmthp=0; SQL_Driver_services& services=*connection.services; - // transcode from $request:charset to connect-string?client_charset - if(cstrClientCharset) { - size_t transcoded_xxx_size; - services.transcode(astatement, strlen(astatement), - astatement, transcoded_xxx_size, - services.request_charset(), - cstrClientCharset); - } - bool failed=false; if(setjmp(connection.mark)) { failed=true; @@ -453,8 +496,32 @@ public: } else { if(placeholders_count>MAX_BINDS) fail(connection, "too many bind variables"); + + while(isspace((unsigned char)*astatement)) + astatement++; - const char *statement=preprocess_statement(connection, astatement, lobs); + 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)); @@ -483,18 +550,19 @@ public: size_t value_length; - if(cstrClientCharset) { + 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, - services.request_charset(), - cstrClientCharset); + request_charset, + client_charset); if(ph.value) services.transcode(ph.value, strlen(ph.value), ph.value, value_length, - services.request_charset(), - cstrClientCharset); + request_charset, + client_charset); } else { value_length=ph.value? strlen(ph.value): 0; } @@ -506,6 +574,8 @@ public: 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); @@ -544,7 +614,7 @@ public: execute_prepared(connection, statement, stmthp, lobs, - offset, limit, handlers); + offset, limit, handlers, mstatement.skip_rownum_column); { for(size_t i=0; i EMPTY_CLOB_FUNC_CALL char *n=result; @@ -644,7 +718,7 @@ private: // private funcs const char *start=o; bool escaped=false; while(*o && !(o[0]=='\'' && o[1]!='\'' && !escaped)) { - escaped=o[0]=='\'' && o[1]=='\''; + escaped=!escaped && (o[0]=='\'' && o[1]=='\''); if(escaped) { // write pending, skip "\" or "'" if(size_t size=o-start) { @@ -696,9 +770,9 @@ private: // private funcs void execute_prepared( Connection& connection, - const char *statement, OCIStmt *stmthp, Query_lobs &lobs, - unsigned long offset, unsigned long limit, - SQL_Driver_query_event_handlers& handlers) { + const char* astatement, OCIStmt *stmthp, Query_lobs &lobs, + unsigned long offset, unsigned long limit, + SQL_Driver_query_event_handlers& handlers, bool skip_rownum_column) { ub2 stmt_type=0; // UNKNOWN /* @@ -709,13 +783,11 @@ private: // private funcs (ub4 *)0, OCI_ATTR_STMT_TYPE, connection.errhp)); */ - while(isspace((unsigned char)*statement)) - statement++; - if(strncasecmp(statement, "select", 6)==0) + if(strncasecmp(astatement, "select", 6)==0) stmt_type=OCI_STMT_SELECT; - else if(strncasecmp(statement, "insert", 6)==0) + else if(strncasecmp(astatement, "insert", 6)==0) stmt_type=OCI_STMT_INSERT; - else if(strncasecmp(statement, "update", 6)==0) + else if(strncasecmp(astatement, "update", 6)==0) stmt_type=OCI_STMT_UPDATE; sword status=OCIStmtExecute(connection.svchp, stmthp, connection.errhp, @@ -746,7 +818,7 @@ private: // private funcs case OCI_STMT_SELECT: fetch_table(connection, stmthp, offset, limit, - handlers); + handlers, skip_rownum_column); break; default: /* @@ -758,10 +830,9 @@ private: // private funcs } void fetch_table(Connection& connection, - OCIStmt *stmthp, unsigned long offset, unsigned long limit, - SQL_Driver_query_event_handlers& handlers) + OCIStmt *stmthp, unsigned long offset, unsigned long limit, + SQL_Driver_query_event_handlers& handlers, bool skip_rownum_column) { - const char* cstrClientCharset=connection.options.cstrClientCharset; SQL_Driver_services& services=*connection.services; ub4 prefetch_rows=100; @@ -776,9 +847,9 @@ private: // private funcs (dvoid *)&prefetch_mem_size, (ub4)0, (ub4)OCI_ATTR_PREFETCH_MEMORY, (OCIError *)connection.errhp)); - OCIParam *mypard; - ub2 dtype; - const char* col_name; + OCIParam *mypard; + ub2 dtype; + const char* col_name; struct Col { ub2 type; @@ -795,6 +866,10 @@ private: // private funcs 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 */ @@ -803,7 +878,10 @@ private: // private funcs --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, @@ -816,17 +894,18 @@ private: // private funcs (dvoid*) mypard, (ub4)OCI_DTYPE_PARAM, (dvoid**) &col_name, (ub4 *) &col_name_len, (ub4)OCI_ATTR_NAME, (OCIError *)connection.errhp)); - // transcode to $request:charset from connect-string?client_charset - if(cstrClientCharset) { + + 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, col_name_len, - cstrClientCharset, - services.request_charset()); + col_name, length, + client_charset, + request_charset); } Col& col=cols[column_count-1]; { - size_t length=(size_t)col_name_len; char *ptr=(char *)services.malloc_atomic(length+1); if( connection.options.bLowerCaseColumnNames ) tolower_str(ptr, col_name, length); @@ -841,25 +920,25 @@ private: // private funcs 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; + 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; - } - 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; @@ -878,7 +957,7 @@ private: // private funcs check(connection, handlers.before_rows(connection.sql_error)); - for(unsigned long row=0; !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); @@ -1090,11 +1247,19 @@ private: // conn client library funcs private: // conn client library funcs linking const char *dlink(const char *dlopen_file_spec) { - if(lt_dlinit()) - return lt_dlerror(); - lt_dlhandle handle=lt_dlopen(dlopen_file_spec); - if(!handle) - return lt_dlerror(); //"can not open the dynamic link module"; + 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); \ @@ -1141,15 +1306,13 @@ void check(Connection& connection, const (text *)reason, (ub4)sizeof(reason), OCI_HTYPE_ERROR)==OCI_SUCCESS) { msg=reason; - // transcode to $request:charset from connect-string?client_charset - if(const char* cstrClientCharset=connection.options.cstrClientCharset) { - if(msg) { - if(size_t msg_length=strlen(msg)) { - connection.services->transcode(msg, msg_length, - msg, msg_length, - cstrClientCharset, - connection.services->request_charset()); - } + 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 @@ -1175,6 +1338,10 @@ void check(Connection& connection, const 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); @@ -1245,16 +1412,6 @@ static sb4 cbf_get_data(dvoid *ctxp, return OCI_CONTINUE; } -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++); -} - - extern "C" SQL_Driver *SQL_DRIVER_CREATE() { return OracleSQL_driver=new OracleSQL_Driver(); }