--- sql/pgsql/parser3pgsql.C 2002/02/08 07:28:25 1.5 +++ sql/pgsql/parser3pgsql.C 2004/03/05 10:16:41 1.18 @@ -1,13 +1,13 @@ /** @file Parser PgSQL driver. - Copyright(c) 2001, 2002 ArtLebedev Group(http://www.artlebedev.com) + Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com) - Author: Alexander Petrosyan (http://design.ru/paf) + Author: Alexandr Petrosian (http://paf.design.ru) 2001.07.30 using PgSQL 7.1.2 */ -static const char *RCSId="$Id: parser3pgsql.C,v 1.5 2002/02/08 07:28:25 paf Exp $"; +static const char *RCSId="$Id: parser3pgsql.C,v 1.18 2004/03/05 10:16:41 paf Exp $"; #include "config_includes.h" @@ -51,6 +51,25 @@ static char *lsplit(char *string, char d return 0; } +static char *lsplit(char **string_ref, char delim) { + char *result=*string_ref; + char *next=lsplit(*string_ref, delim); + *string_ref=next; + return result; +} + +static void toupper(char *out, const char *in, size_t size) { + while(size--) + *out++=(char)toupper(*in++); +} + +struct Connection { + SQL_Driver_services* services; + + PGconn *conn; + const char* cstrClientCharset; +}; + /** PgSQL server driver */ @@ -68,7 +87,12 @@ public: dlink(dlopen_file_spec):"client library column is empty"; } - #define throwPQerror services._throw(PQerrorMessage(conn)) + #define throwPQerror connection.services->_throw(PQerrorMessage(connection.conn)) + #define PQclear_throw(msg) { \ + PQclear(res); \ + connection.services->_throw(msg); \ + } + #define PQclear_throwPQerror PQclear_throw(PQerrorMessage(connection.conn)) /** connect @param used_only_in_connect_url @@ -77,7 +101,7 @@ public: void connect( char *used_only_in_connect_url, SQL_Driver_services& services, - void **connection ///< output: PGconn * + void **connection_ref ///< output: Connection* ) { char *user=used_only_in_connect_url; char *host=lsplit(user, '@'); @@ -85,75 +109,142 @@ public: char *pwd=lsplit(user, ':'); char *port=lsplit(host, ':'); - PGconn *conn=PQsetdbLogin( - strcasecmp(host, "local")==0?NULL/* local Unix domain socket */:host, port, + char *options=lsplit(db, '?'); + + char *cstrBackwardCompAskServerToTranscode=0; + + Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); + *connection_ref=&connection; + connection.services=&services; + connection.cstrClientCharset=0; + connection.conn=PQsetdbLogin( + (host&&strcasecmp(host, "local")==0)?NULL/* local Unix domain socket */:host, port, NULL, NULL, db, user, pwd); - if(!conn) + if(!connection.conn) services._throw("PQsetdbLogin failed"); - if(PQstatus(conn)!=CONNECTION_OK) + if(PQstatus(connection.conn)!=CONNECTION_OK) throwPQerror; - *(PGconn **)connection=conn; - begin_transaction(services, conn); + char *charset=0; + char *datestyle=0; + + while(options) { + if(char *key=lsplit(&options, '&')) { + if(*key) { + if(char *value=lsplit(key, '=')) { + if(strcmp(key, "ClientCharset" ) == 0) { + toupper(value, value, strlen(value)); + connection.cstrClientCharset=value; + } else if(strcasecmp(key, "charset")==0) { // left for backward compatibility, consider using ClientCharset + cstrBackwardCompAskServerToTranscode=value; + } else if(strcasecmp(key, "datestyle")==0) { + datestyle=value; + } else + services._throw("unknown connect option" /*key*/); + } else + services._throw("connect option without =value" /*key*/); + } + } + } + + if(connection.cstrClientCharset && cstrBackwardCompAskServerToTranscode) + services._throw("use 'ClientCharset' option only, " + "'charset' option is obsolete and should not be used with new 'ClientCharset' option"); + + if(cstrBackwardCompAskServerToTranscode) { + // set CLIENT_ENCODING + char statement[MAX_STRING]="set CLIENT_ENCODING="; // win + strncat(statement, cstrBackwardCompAskServerToTranscode, MAX_STRING); + + PGresult *res=PQexec(connection.conn, statement); + if(!res) + throwPQerror; + PQclear(res); // throw out the result [don't need but must call] + } + + if(datestyle) { + // set DATESTYLE + char statement[MAX_STRING]="set DATESTYLE="; // ISO,SQL,Postgres,European,NonEuropean=US,German,DEFAULT=ISO + strncat(statement, charset, MAX_STRING); + + PGresult *res=PQexec(connection.conn, statement); + if(!res) + throwPQerror; + PQclear(res); // throw out the result [don't need but must call] + } + + begin_transaction(connection); } - void disconnect(void *connection) { - PQfinish((PGconn *)connection); + void disconnect(void *aconnection) { + Connection& connection=*static_cast(aconnection); + + PQfinish(connection.conn); + connection.conn=0; } - void commit(SQL_Driver_services& services, void *connection) { - PGconn *conn=(PGconn *)connection; - if(PGresult *res=PQexec(conn, "COMMIT")) + void commit(void *aconnection) { + Connection& connection=*static_cast(aconnection); + + if(PGresult *res=PQexec(connection.conn, "COMMIT")) PQclear(res); else throwPQerror; - begin_transaction(services, conn); + begin_transaction(connection); } - void rollback(SQL_Driver_services& services, void *connection) { - PGconn *conn=(PGconn *)connection; - if(PGresult *res=PQexec(conn, "ROLLBACK")) + void rollback(void *aconnection) { + Connection& connection=*static_cast(aconnection); + + if(PGresult *res=PQexec(connection.conn, "ROLLBACK")) PQclear(res); else throwPQerror; - begin_transaction(services, conn); + begin_transaction(connection); } - bool ping(SQL_Driver_services&, void *connection) { - return PQstatus((PGconn *)connection)==CONNECTION_OK; + bool ping(void *aconnection) { + Connection& connection=*static_cast(aconnection); + + return PQstatus(connection.conn)==CONNECTION_OK; } - unsigned int quote( - SQL_Driver_services&, void *connection, - char *to, const char *from, unsigned int length) { - if(to) { // store mode - unsigned int result=length; - while(length--) { - switch(*from) { - case '\'': // "'" -> "''" - *to++='\''; result++; - break; - case '\\': // "\" -> "\\" - *to++='\''; result++; - break; - } - *to++=*from++; + const char* quote( + void *aconnection, + const char *from, 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; + case '\\': // "\" -> "\\" + *to++='\\'; + break; } - return result; - } else // estimate mode - return length*2; - } - void query( - SQL_Driver_services& services, void *connection, + *to++=*from++; + } + *to=0; + return result; + } + void query(void *aconnection, const char *astatement, unsigned long offset, unsigned long limit, SQL_Driver_query_event_handlers& handlers) { // _asm int 3; + Connection& connection=*static_cast(aconnection); + SQL_Driver_services& services=*connection.services; + PGconn *conn=connection.conn; + + // transcode from $request:charset to connect-string?client_charset + if(const char* cstrClientCharset=connection.cstrClientCharset) { + size_t transcoded_statement_size; + services.transcode(astatement, strlen(astatement), + astatement, transcoded_statement_size, + services.request_charset(), + cstrClientCharset); + } - PGconn *conn=(PGconn *)connection; - #define PQclear_throw(msg) { \ - PQclear(res); \ - services._throw(msg); \ - } - #define PQclear_throwPQerror PQclear_throw(PQerrorMessage(conn)) - - const char *statement=preprocess_statement(services, conn, + const char *statement=preprocess_statement(connection, astatement, offset, limit); PGresult *res=PQexec(conn, statement); @@ -179,23 +270,40 @@ public: if(!column_count) PQclear_throw("result contains no columns"); + bool failed=false; + SQL_Error sql_error; +#define CHECK(afailed) \ + if(afailed) { \ + failed=true; \ + goto cleanup; \ + } + for(int i=0; imalloc(statement_size +MAX_NUMBER*2+15 // limit # offset # +MAX_STRING // in case of short 'strings' +1); @@ -278,6 +401,7 @@ private: // private funcs o[0]=='/' && o[1]=='*' && o[2]=='*') { // name start + const char* saved_o=o; o+=3; while(*o) if( @@ -285,6 +409,7 @@ private: // private funcs o[1]=='*' && o[2]=='/' && o[3]=='\'') { // name end + saved_o=0; // found, marking that o+=4; Oid oid=lo_creat(conn, INV_READ|INV_WRITE); if(oid==InvalidOid) @@ -298,13 +423,13 @@ private: // private funcs if(escaped) { // write pending, skip "\" or "'" if(!lo_write_ex(conn, fd, start, o-start)) - services._throw("lo_write could not write all bytes of object (1)"); + connection.services->_throw("lo_write could not write all bytes of object (1)"); start=++o; } else o++; } if(!lo_write_ex(conn, fd, start, o-start)) - services._throw("lo_write can not write all bytes of object (2)"); + connection.services->_throw("lo_write can not write all bytes of object (2)"); if(lo_close(conn, fd)<0) throwPQerror; } else @@ -316,6 +441,10 @@ private: // private funcs break; } else o++; // /**skip**/'xxx' + if(saved_o) { + o=saved_o; + *n++=*o++; + } } else *n++=*o++; } @@ -388,6 +517,8 @@ 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 "can not open the dynamic link module";