--- sql/oracle/parser3oracle.C 2008/06/30 15:22:28 1.69 +++ 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.69 2008/06/30 15:22:28 misha 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 @@ -131,6 +131,13 @@ static void toupper_str(char *out, const *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; @@ -148,6 +155,7 @@ struct Connection { struct Options { bool bLowerCaseColumnNames; + bool bDisableQueryModification; const char* client_charset; } options; }; @@ -175,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( @@ -195,6 +203,8 @@ static sb4 cbf_get_data(dvoid *ctxp, dvoid **indpp, ub2 **rcodepp); +static bool transcode_required(Connection& connection); + static const char *options2env(char *s, Connection::Options* options) { while(s){ if(char *key=lsplit(&s, '&')){ @@ -214,6 +224,12 @@ static const char *options2env(char *s, 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 + @@ -252,15 +268,15 @@ public: 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 ); } @@ -291,6 +307,7 @@ public: Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); connection.services=&services; connection.options.bLowerCaseColumnNames=true; + connection.options.bDisableQueryModification=false; *connection_ref=&connection; char *user=url; @@ -428,20 +445,33 @@ 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; } @@ -459,17 +489,6 @@ public: SQL_Driver_services& services=*connection.services; - 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, - services.request_charset(), - connection.options.client_charset); - } - bool failed=false; if(setjmp(connection.mark)) { failed=true; @@ -477,8 +496,32 @@ public: } 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); - const char *statement=preprocess_statement(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)); @@ -512,14 +555,14 @@ public: size_t name_length; services.transcode(ph.name, strlen(ph.name), ph.name, name_length, - services.request_charset(), - connection.options.client_charset); + request_charset, + client_charset); if(ph.value) services.transcode(ph.value, strlen(ph.value), ph.value, value_length, - services.request_charset(), - connection.options.client_charset); + request_charset, + client_charset); } else { value_length=ph.value? strlen(ph.value): 0; } @@ -571,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; @@ -724,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 /* @@ -737,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, @@ -774,7 +818,7 @@ private: // private funcs case OCI_STMT_SELECT: fetch_table(connection, stmthp, offset, limit, - handlers); + handlers, skip_rownum_column); break; default: /* @@ -786,11 +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) { - bool transcode_needed=_transcode_required(connection); - SQL_Driver_services& services=*connection.services; ub4 prefetch_rows=100; @@ -824,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 */ @@ -832,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, @@ -846,17 +895,17 @@ private: // private funcs (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, col_name_len, - connection.options.client_charset, - 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); @@ -918,6 +967,9 @@ private: // private funcs if(row>=offset) { check(connection, handlers.add_row(connection.sql_error)); for(int i=0; irequest_charset())!=0); + 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__<=%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 @@ -1137,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); \ @@ -1188,8 +1306,8 @@ void check(Connection& connection, const (text *)reason, (ub4)sizeof(reason), OCI_HTYPE_ERROR)==OCI_SUCCESS) { msg=reason; - // transcode server error message from ?ClientCharset to $request:charset - if(msg && connection.options.client_charset && strcmp(connection.options.client_charset, connection.services->request_charset())!=0){ + 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, @@ -1220,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);