summaryrefslogtreecommitdiffstats
path: root/src/sql/drivers/psql/qsql_psql.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sql/drivers/psql/qsql_psql.cpp')
-rw-r--r--src/sql/drivers/psql/qsql_psql.cpp1117
1 files changed, 1117 insertions, 0 deletions
diff --git a/src/sql/drivers/psql/qsql_psql.cpp b/src/sql/drivers/psql/qsql_psql.cpp
new file mode 100644
index 000000000..99df0fdfb
--- /dev/null
+++ b/src/sql/drivers/psql/qsql_psql.cpp
@@ -0,0 +1,1117 @@
+/****************************************************************************
+**
+** Implementation of PostgreSQL driver classes
+**
+** Created : 001103
+**
+** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the sql module of the TQt GUI Toolkit.
+**
+** This file may be used under the terms of the GNU General
+** Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the files LICENSE.GPL2
+** and LICENSE.GPL3 included in the packaging of this file.
+** Alternatively you may (at your option) use any later version
+** of the GNU General Public License if such license has been
+** publicly approved by Trolltech ASA (or its successors, if any)
+** and the KDE Free TQt Foundation.
+**
+** Please review the following information to ensure GNU General
+** Public Licensing retquirements will be met:
+** http://trolltech.com/products/qt/licenses/licensing/opensource/.
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
+** or contact the sales department at [email protected].
+**
+** This file may be used under the terms of the Q Public License as
+** defined by Trolltech ASA and appearing in the file LICENSE.TQPL
+** included in the packaging of this file. Licensees holding valid TQt
+** Commercial licenses may use this file in accordance with the TQt
+** Commercial License Agreement provided with the Software.
+**
+** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
+** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
+** herein.
+**
+**********************************************************************/
+
+#include "qsql_psql.h"
+#include <private/qsqlextension_p.h>
+
+#include <math.h>
+
+#include <qpointarray.h>
+#include <qsqlrecord.h>
+#include <qregexp.h>
+#include <qdatetime.h>
+// PostgreSQL header <utils/elog.h> included by <postgres.h> redefines DEBUG.
+#if defined(DEBUG)
+# undef DEBUG
+#endif
+#include <postgres.h>
+#include <libpq/libpq-fs.h>
+// PostgreSQL header <catalog/pg_type.h> redefines errno erroneously.
+#if defined(errno)
+# undef errno
+#endif
+#define errno qt_psql_errno
+#include <catalog/pg_type.h>
+#undef errno
+#ifdef open
+# undef open
+#endif
+
+TQPtrDict<TQSqlDriverExtension> *qSqlDriverExtDict();
+TQPtrDict<TQSqlOpenExtension> *qSqlOpenExtDict();
+
+class TQPSQLPrivate
+{
+public:
+ TQPSQLPrivate():connection(0), result(0), isUtf8(FALSE) {}
+ PGconn *connection;
+ PGresult *result;
+ bool isUtf8;
+};
+
+class TQPSQLDriverExtension : public TQSqlDriverExtension
+{
+public:
+ TQPSQLDriverExtension( TQPSQLDriver *dri )
+ : TQSqlDriverExtension(), driver(dri) { }
+ ~TQPSQLDriverExtension() {}
+
+ bool isOpen() const;
+private:
+ TQPSQLDriver *driver;
+};
+
+bool TQPSQLDriverExtension::isOpen() const
+{
+ return PQstatus( driver->connection() ) == CONNECTION_OK;
+}
+
+class TQPSQLOpenExtension : public TQSqlOpenExtension
+{
+public:
+ TQPSQLOpenExtension( TQPSQLDriver *dri )
+ : TQSqlOpenExtension(), driver(dri) { }
+ ~TQPSQLOpenExtension() {}
+
+ bool open( const TQString& db,
+ const TQString& user,
+ const TQString& password,
+ const TQString& host,
+ int port,
+ const TQString& connOpts );
+private:
+ TQPSQLDriver *driver;
+};
+
+bool TQPSQLOpenExtension::open( const TQString& db,
+ const TQString& user,
+ const TQString& password,
+ const TQString& host,
+ int port,
+ const TQString& connOpts )
+{
+ return driver->open( db, user, password, host, port, connOpts );
+}
+
+static TQSqlError qMakeError( const TQString& err, int type, const TQPSQLPrivate* p )
+{
+ const char *s = PQerrorMessage(p->connection);
+ TQString msg = p->isUtf8 ? TQString::fromUtf8(s) : TQString::fromLocal8Bit(s);
+ return TQSqlError("TQPSQL: " + err, msg, type);
+}
+
+static TQVariant::Type qDecodePSQLType( int t )
+{
+ TQVariant::Type type = TQVariant::Invalid;
+ switch ( t ) {
+ case BOOLOID :
+ type = TQVariant::Bool;
+ break;
+ case INT8OID :
+ type = TQVariant::LongLong;
+ break;
+ case INT2OID :
+ // case INT2VECTOROID : // 7.x
+ case INT4OID :
+ type = TQVariant::Int;
+ break;
+ case NUMERICOID :
+ case FLOAT4OID :
+ case FLOAT8OID :
+ type = TQVariant::Double;
+ break;
+ case ABSTIMEOID :
+ case RELTIMEOID :
+ case DATEOID :
+ type = TQVariant::Date;
+ break;
+ case TIMEOID :
+#ifdef TIMETZOID // 7.x
+ case TIMETZOID :
+#endif
+ type = TQVariant::Time;
+ break;
+ case TIMESTAMPOID :
+#ifdef DATETIMEOID
+ // Postgres 6.x datetime workaround.
+ // DATETIMEOID == TIMESTAMPOID (only the names have changed)
+ case DATETIMEOID :
+#endif
+#ifdef TIMESTAMPTZOID
+ // Postgres 7.2 workaround
+ // TIMESTAMPTZOID == TIMESTAMPOID == DATETIMEOID
+ case TIMESTAMPTZOID :
+#endif
+ type = TQVariant::DateTime;
+ break;
+ // case ZPBITOID : // 7.x
+ // case VARBITOID : // 7.x
+ case OIDOID :
+ case BYTEAOID :
+ type = TQVariant::ByteArray;
+ break;
+ case REGPROCOID :
+ case TIDOID :
+ case XIDOID :
+ case CIDOID :
+ // case OIDVECTOROID : // 7.x
+ case UNKNOWNOID :
+ // case TINTERVALOID : // 7.x
+ type = TQVariant::Invalid;
+ break;
+ default:
+ case CHAROID :
+ case BPCHAROID :
+ // case LZTEXTOID : // 7.x
+ case VARCHAROID :
+ case TEXTOID :
+ case NAMEOID :
+ case CASHOID :
+ case INETOID :
+ case CIDROID :
+ case CIRCLEOID :
+ type = TQVariant::String;
+ break;
+ }
+ return type;
+}
+
+TQPSQLResult::TQPSQLResult( const TQPSQLDriver* db, const TQPSQLPrivate* p )
+: TQSqlResult( db ),
+ currentSize( 0 )
+{
+ d = new TQPSQLPrivate();
+ (*d) = (*p);
+}
+
+TQPSQLResult::~TQPSQLResult()
+{
+ cleanup();
+ delete d;
+}
+
+PGresult* TQPSQLResult::result()
+{
+ return d->result;
+}
+
+void TQPSQLResult::cleanup()
+{
+ if ( d->result )
+ PQclear( d->result );
+ d->result = 0;
+ setAt( -1 );
+ currentSize = 0;
+ setActive( FALSE );
+}
+
+bool TQPSQLResult::fetch( int i )
+{
+ if ( !isActive() )
+ return FALSE;
+ if ( i < 0 )
+ return FALSE;
+ if ( i >= currentSize )
+ return FALSE;
+ if ( at() == i )
+ return TRUE;
+ setAt( i );
+ return TRUE;
+}
+
+bool TQPSQLResult::fetchFirst()
+{
+ return fetch( 0 );
+}
+
+bool TQPSQLResult::fetchLast()
+{
+ return fetch( PQntuples( d->result ) - 1 );
+}
+
+// some Postgres conversions
+static TQPoint pointFromString( const TQString& s)
+{
+ // format '(x,y)'
+ int pivot = s.find( ',' );
+ if ( pivot != -1 ) {
+ int x = s.mid( 1, pivot-1 ).toInt();
+ int y = s.mid( pivot+1, s.length()-pivot-2 ).toInt();
+ return TQPoint( x, y ) ;
+ } else
+ return TQPoint();
+}
+
+TQVariant TQPSQLResult::data( int i )
+{
+ if ( i >= PQnfields( d->result ) ) {
+ qWarning( "TQPSQLResult::data: column %d out of range", i );
+ return TQVariant();
+ }
+ int ptype = PQftype( d->result, i );
+ TQVariant::Type type = qDecodePSQLType( ptype );
+ const TQString val = ( d->isUtf8 && ptype != BYTEAOID ) ?
+ TQString::fromUtf8( PQgetvalue( d->result, at(), i ) ) :
+ TQString::fromLocal8Bit( PQgetvalue( d->result, at(), i ) );
+ if ( PQgetisnull( d->result, at(), i ) ) {
+ TQVariant v;
+ v.cast( type );
+ return v;
+ }
+ switch ( type ) {
+ case TQVariant::Bool:
+ {
+ TQVariant b ( (bool)(val == "t"), 0 );
+ return ( b );
+ }
+ case TQVariant::String:
+ return TQVariant( val );
+ case TQVariant::LongLong:
+ if ( val[0] == '-' )
+ return TQVariant( val.toLongLong() );
+ else
+ return TQVariant( val.toULongLong() );
+ case TQVariant::Int:
+ return TQVariant( val.toInt() );
+ case TQVariant::Double:
+ if ( ptype == NUMERICOID )
+ return TQVariant( val );
+ return TQVariant( val.toDouble() );
+ case TQVariant::Date:
+ if ( val.isEmpty() ) {
+ return TQVariant( TQDate() );
+ } else {
+ return TQVariant( TQDate::fromString( val, TQt::ISODate ) );
+ }
+ case TQVariant::Time:
+ if ( val.isEmpty() )
+ return TQVariant( TQTime() );
+ if ( val.at( val.length() - 3 ) == '+' )
+ // strip the timezone
+ return TQVariant( TQTime::fromString( val.left( val.length() - 3 ), TQt::ISODate ) );
+ return TQVariant( TQTime::fromString( val, TQt::ISODate ) );
+ case TQVariant::DateTime: {
+ if ( val.length() < 10 )
+ return TQVariant( TQDateTime() );
+ // remove the timezone
+ TQString dtval = val;
+ if ( dtval.at( dtval.length() - 3 ) == '+' )
+ dtval.truncate( dtval.length() - 3 );
+ // milliseconds are sometimes returned with 2 digits only
+ if ( dtval.at( dtval.length() - 3 ).isPunct() )
+ dtval += '0';
+ if ( dtval.isEmpty() )
+ return TQVariant( TQDateTime() );
+ else
+ return TQVariant( TQDateTime::fromString( dtval, TQt::ISODate ) );
+ }
+ case TQVariant::Point:
+ return TQVariant( pointFromString( val ) );
+ case TQVariant::Rect: // format '(x,y),(x',y')'
+ {
+ int pivot = val.find( "),(" );
+ if ( pivot != -1 )
+ return TQVariant( TQRect( pointFromString( val.mid(pivot+2,val.length()) ), pointFromString( val.mid(0,pivot+1) ) ) );
+ return TQVariant( TQRect() );
+ }
+ case TQVariant::PointArray: // format '((x,y),(x1,y1),...,(xn,yn))'
+ {
+ TQRegExp pointPattern("\\([0-9-]*,[0-9-]*\\)");
+ int points = val.contains( pointPattern );
+ TQPointArray parray( points );
+ int idx = 1;
+ for ( int i = 0; i < points; i++ ){
+ int start = val.find( pointPattern, idx );
+ int end = -1;
+ if ( start != -1 ) {
+ end = val.find( ')', start+1 );
+ if ( end != -1 ) {
+ parray.setPoint( i, pointFromString( val.mid(idx, end-idx+1) ) );
+ }
+ else
+ parray.setPoint( i, TQPoint() );
+ } else {
+ parray.setPoint( i, TQPoint() );
+ break;
+ }
+ idx = end+2;
+ }
+ return TQVariant( parray );
+ }
+ case TQVariant::ByteArray: {
+ if ( ptype == BYTEAOID ) {
+ uint i = 0;
+ int index = 0;
+ uint len = val.length();
+ static const TQChar backslash( '\\' );
+ TQByteArray ba( (int)len );
+ while ( i < len ) {
+ if ( val.at( i ) == backslash ) {
+ if ( val.at( i + 1 ).isDigit() ) {
+ ba[ index++ ] = (char)(val.mid( i + 1, 3 ).toInt( 0, 8 ));
+ i += 4;
+ } else {
+ ba[ index++ ] = val.at( i + 1 );
+ i += 2;
+ }
+ } else {
+ ba[ index++ ] = val.at( i++ ).unicode();
+ }
+ }
+ ba.resize( index );
+ return TQVariant( ba );
+ }
+
+ TQByteArray ba;
+ ((TQSqlDriver*)driver())->beginTransaction();
+ Oid oid = val.toInt();
+ int fd = lo_open( d->connection, oid, INV_READ );
+#ifdef QT_CHECK_RANGE
+ if ( fd < 0) {
+ qWarning( "TQPSQLResult::data: unable to open large object for read" );
+ ((TQSqlDriver*)driver())->commitTransaction();
+ return TQVariant( ba );
+ }
+#endif
+ int size = 0;
+ int retval = lo_lseek( d->connection, fd, 0L, SEEK_END );
+ if ( retval >= 0 ) {
+ size = lo_tell( d->connection, fd );
+ lo_lseek( d->connection, fd, 0L, SEEK_SET );
+ }
+ if ( size == 0 ) {
+ lo_close( d->connection, fd );
+ ((TQSqlDriver*)driver())->commitTransaction();
+ return TQVariant( ba );
+ }
+ char * buf = new char[ size ];
+
+#ifdef Q_OS_WIN32
+ // ### For some reason lo_read() fails if we try to read more than
+ // ### 32760 bytes
+ char * p = buf;
+ int nread = 0;
+
+ while( size < nread ){
+ retval = lo_read( d->connection, fd, p, 32760 );
+ nread += retval;
+ p += retval;
+ }
+#else
+ retval = lo_read( d->connection, fd, buf, size );
+#endif
+
+ if (retval < 0) {
+ qWarning( "TQPSQLResult::data: unable to read large object" );
+ } else {
+ ba.duplicate( buf, size );
+ }
+ delete [] buf;
+ lo_close( d->connection, fd );
+ ((TQSqlDriver*)driver())->commitTransaction();
+ return TQVariant( ba );
+ }
+ default:
+ case TQVariant::Invalid:
+#ifdef QT_CHECK_RANGE
+ qWarning("TQPSQLResult::data: unknown data type");
+#endif
+ ;
+ }
+ return TQVariant();
+}
+
+bool TQPSQLResult::isNull( int field )
+{
+ PQgetvalue( d->result, at(), field );
+ return PQgetisnull( d->result, at(), field );
+}
+
+bool TQPSQLResult::reset ( const TQString& query )
+{
+ cleanup();
+ if ( !driver() )
+ return FALSE;
+ if ( !driver()->isOpen() || driver()->isOpenError() )
+ return FALSE;
+ setActive( FALSE );
+ setAt( TQSql::BeforeFirst );
+ if ( d->result )
+ PQclear( d->result );
+ if ( d->isUtf8 ) {
+ d->result = PQexec( d->connection, query.utf8().data() );
+ } else {
+ d->result = PQexec( d->connection, query.local8Bit().data() );
+ }
+ int status = PQresultStatus( d->result );
+ if ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) {
+ if ( status == PGRES_TUPLES_OK ) {
+ setSelect( TRUE );
+ currentSize = PQntuples( d->result );
+ } else {
+ setSelect( FALSE );
+ currentSize = -1;
+ }
+ setActive( TRUE );
+ return TRUE;
+ }
+ setLastError( qMakeError( "Unable to create query", TQSqlError::Statement, d ) );
+ return FALSE;
+}
+
+int TQPSQLResult::size()
+{
+ return currentSize;
+}
+
+int TQPSQLResult::numRowsAffected()
+{
+ return TQString( PQcmdTuples( d->result ) ).toInt();
+}
+
+///////////////////////////////////////////////////////////////////
+
+static bool setEncodingUtf8( PGconn* connection )
+{
+ PGresult* result = PQexec( connection, "SET CLIENT_ENCODING TO 'UNICODE'" );
+ int status = PQresultStatus( result );
+ PQclear( result );
+ return status == PGRES_COMMAND_OK;
+}
+
+static void setDatestyle( PGconn* connection )
+{
+ PGresult* result = PQexec( connection, "SET DATESTYLE TO 'ISO'" );
+#ifdef QT_CHECK_RANGE
+ int status = PQresultStatus( result );
+ if ( status != PGRES_COMMAND_OK )
+ qWarning( "%s", PQerrorMessage( connection ) );
+#endif
+ PQclear( result );
+}
+
+static TQPSQLDriver::Protocol getPSQLVersion( PGconn* connection )
+{
+ PGresult* result = PQexec( connection, "select version()" );
+ int status = PQresultStatus( result );
+ if ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) {
+ TQString val( PQgetvalue( result, 0, 0 ) );
+ PQclear( result );
+ TQRegExp rx( "(\\d+)\\.(\\d+)" );
+ rx.setMinimal ( TRUE ); // enforce non-greedy RegExp
+ if ( rx.search( val ) != -1 ) {
+ int vMaj = rx.cap( 1 ).toInt();
+ int vMin = rx.cap( 2 ).toInt();
+ if ( vMaj < 6 ) {
+#ifdef QT_CHECK_RANGE
+ qWarning( "This version of PostgreSQL is not supported and may not work." );
+#endif
+ return TQPSQLDriver::Version6;
+ }
+ if ( vMaj == 6 ) {
+ return TQPSQLDriver::Version6;
+ } else if ( vMaj == 7 ) {
+ if ( vMin < 1 )
+ return TQPSQLDriver::Version7;
+ else if ( vMin < 3 )
+ return TQPSQLDriver::Version71;
+ }
+ return TQPSQLDriver::Version73;
+ }
+ } else {
+#ifdef QT_CHECK_RANGE
+ qWarning( "This version of PostgreSQL is not supported and may not work." );
+#endif
+ }
+
+ return TQPSQLDriver::Version6;
+}
+
+TQPSQLDriver::TQPSQLDriver( TQObject * parent, const char * name )
+ : TQSqlDriver(parent,name ? name : "TQPSQL"), pro( TQPSQLDriver::Version6 )
+{
+ init();
+}
+
+TQPSQLDriver::TQPSQLDriver( PGconn * conn, TQObject * parent, const char * name )
+ : TQSqlDriver(parent,name ? name : "TQPSQL"), pro( TQPSQLDriver::Version6 )
+{
+ init();
+ d->connection = conn;
+ if ( conn ) {
+ pro = getPSQLVersion( d->connection );
+ setOpen( TRUE );
+ setOpenError( FALSE );
+ }
+}
+
+void TQPSQLDriver::init()
+{
+ qSqlDriverExtDict()->insert( this, new TQPSQLDriverExtension(this) );
+ qSqlOpenExtDict()->insert( this, new TQPSQLOpenExtension(this) );
+
+ d = new TQPSQLPrivate();
+}
+
+TQPSQLDriver::~TQPSQLDriver()
+{
+ if ( d->connection )
+ PQfinish( d->connection );
+ delete d;
+ if ( !qSqlDriverExtDict()->isEmpty() ) {
+ TQSqlDriverExtension *ext = qSqlDriverExtDict()->take( this );
+ delete ext;
+ }
+ if ( !qSqlOpenExtDict()->isEmpty() ) {
+ TQSqlOpenExtension *ext = qSqlOpenExtDict()->take( this );
+ delete ext;
+ }
+}
+
+PGconn* TQPSQLDriver::connection()
+{
+ return d->connection;
+}
+
+
+bool TQPSQLDriver::hasFeature( DriverFeature f ) const
+{
+ switch ( f ) {
+ case Transactions:
+ return TRUE;
+ case QuerySize:
+ return TRUE;
+ case BLOB:
+ return pro >= TQPSQLDriver::Version71;
+ case Unicode:
+ return d->isUtf8;
+ default:
+ return FALSE;
+ }
+}
+
+bool TQPSQLDriver::open( const TQString&,
+ const TQString&,
+ const TQString&,
+ const TQString&,
+ int )
+{
+ qWarning("TQPSQLDriver::open(): This version of open() is no longer supported." );
+ return FALSE;
+}
+
+bool TQPSQLDriver::open( const TQString & db,
+ const TQString & user,
+ const TQString & password,
+ const TQString & host,
+ int port,
+ const TQString& connOpts )
+{
+ if ( isOpen() )
+ close();
+ TQString connectString;
+ if ( host.length() )
+ connectString.append( "host=" ).append( host );
+ if ( db.length() )
+ connectString.append( " dbname=" ).append( db );
+ if ( user.length() )
+ connectString.append( " user=" ).append( user );
+ if ( password.length() )
+ connectString.append( " password=" ).append( password );
+ if ( port > -1 )
+ connectString.append( " port=" ).append( TQString::number( port ) );
+
+ // add any connect options - the server will handle error detection
+ if ( !connOpts.isEmpty() )
+ connectString += " " + TQStringList::split( ';', connOpts ).join( " " );
+
+ d->connection = PQconnectdb( connectString.local8Bit().data() );
+ if ( PQstatus( d->connection ) == CONNECTION_BAD ) {
+ setLastError( qMakeError("Unable to connect", TQSqlError::Connection, d ) );
+ setOpenError( TRUE );
+ return FALSE;
+ }
+
+ pro = getPSQLVersion( d->connection );
+ d->isUtf8 = setEncodingUtf8( d->connection );
+ setDatestyle( d->connection );
+
+ setOpen( TRUE );
+ setOpenError( FALSE );
+ return TRUE;
+}
+
+void TQPSQLDriver::close()
+{
+ if ( isOpen() ) {
+ if (d->connection)
+ PQfinish( d->connection );
+ d->connection = 0;
+ setOpen( FALSE );
+ setOpenError( FALSE );
+ }
+}
+
+TQSqlQuery TQPSQLDriver::createQuery() const
+{
+ return TQSqlQuery( new TQPSQLResult( this, d ) );
+}
+
+bool TQPSQLDriver::beginTransaction()
+{
+ if ( !isOpen() ) {
+#ifdef QT_CHECK_RANGE
+ qWarning( "TQPSQLDriver::beginTransaction: Database not open" );
+#endif
+ return FALSE;
+ }
+ PGresult* res = PQexec( d->connection, "BEGIN" );
+ if ( !res || PQresultStatus( res ) != PGRES_COMMAND_OK ) {
+ PQclear( res );
+ setLastError( qMakeError( "Could not begin transaction", TQSqlError::Transaction, d ) );
+ return FALSE;
+ }
+ PQclear( res );
+ return TRUE;
+}
+
+bool TQPSQLDriver::commitTransaction()
+{
+ if ( !isOpen() ) {
+#ifdef QT_CHECK_RANGE
+ qWarning( "TQPSQLDriver::commitTransaction: Database not open" );
+#endif
+ return FALSE;
+ }
+ PGresult* res = PQexec( d->connection, "COMMIT" );
+ if ( !res || PQresultStatus( res ) != PGRES_COMMAND_OK ) {
+ PQclear( res );
+ setLastError( qMakeError( "Could not commit transaction", TQSqlError::Transaction, d ) );
+ return FALSE;
+ }
+ PQclear( res );
+ return TRUE;
+}
+
+bool TQPSQLDriver::rollbackTransaction()
+{
+ if ( !isOpen() ) {
+#ifdef QT_CHECK_RANGE
+ qWarning( "TQPSQLDriver::rollbackTransaction: Database not open" );
+#endif
+ return FALSE;
+ }
+ PGresult* res = PQexec( d->connection, "ROLLBACK" );
+ if ( !res || PQresultStatus( res ) != PGRES_COMMAND_OK ) {
+ setLastError( qMakeError( "Could not rollback transaction", TQSqlError::Transaction, d ) );
+ PQclear( res );
+ return FALSE;
+ }
+ PQclear( res );
+ return TRUE;
+}
+
+TQStringList TQPSQLDriver::tables( const TQString& typeName ) const
+{
+ TQStringList tl;
+ if ( !isOpen() )
+ return tl;
+ int type = typeName.toInt();
+ TQSqlQuery t = createQuery();
+ t.setForwardOnly( TRUE );
+
+ if ( typeName.isEmpty() || ((type & (int)TQSql::Tables) == (int)TQSql::Tables) ) {
+
+ TQString query("select relname from pg_class where (relkind = 'r') "
+ "and (relname !~ '^Inv') "
+ "and (relname !~ '^pg_') ");
+ if (pro >= TQPSQLDriver::Version73)
+ query.append("and (relnamespace not in "
+ "(select oid from pg_namespace where nspname = 'information_schema')) "
+ "and pg_table_is_visible(pg_class.oid) ");
+ t.exec(query);
+ while ( t.next() )
+ tl.append( t.value(0).toString() );
+ }
+ if ( (type & (int)TQSql::Views) == (int)TQSql::Views ) {
+ TQString query("select relname from pg_class where ( relkind = 'v' ) "
+ "and ( relname !~ '^Inv' ) "
+ "and ( relname !~ '^pg_' ) ");
+ if (pro >= TQPSQLDriver::Version73)
+ query.append("and (relnamespace not in "
+ "(select oid from pg_namespace where nspname = 'information_schema')) "
+ "and pg_table_is_visible(pg_class.oid) ");
+ t.exec(query);
+ while ( t.next() )
+ tl.append( t.value(0).toString() );
+ }
+ if ( (type & (int)TQSql::SystemTables) == (int)TQSql::SystemTables ) {
+ TQString query( "select relname from pg_class where ( relkind = 'r' ) "
+ "and ( relname like 'pg_%' ) " );
+ if (pro >= TQPSQLDriver::Version73)
+ query.append( "and pg_table_is_visible(pg_class.oid) " );
+ t.exec(query);
+ while ( t.next() )
+ tl.append( t.value(0).toString() );
+ }
+
+ return tl;
+}
+
+TQSqlIndex TQPSQLDriver::primaryIndex( const TQString& tablename ) const
+{
+ TQSqlIndex idx( tablename );
+ if ( !isOpen() )
+ return idx;
+ TQSqlQuery i = createQuery();
+ TQString stmt;
+
+ switch( pro ) {
+ case TQPSQLDriver::Version6:
+ stmt = "select pg_att1.attname, int(pg_att1.atttypid), pg_att2.attnum, pg_cl.relname "
+ "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
+ "where lower(pg_cl.relname) = '%1_pkey' ";
+ break;
+ case TQPSQLDriver::Version7:
+ case TQPSQLDriver::Version71:
+ stmt = "select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname "
+ "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
+ "where lower(pg_cl.relname) = '%1_pkey' ";
+ break;
+ case TQPSQLDriver::Version73:
+ stmt = "select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname "
+ "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
+ "where lower(pg_cl.relname) = '%1_pkey' "
+ "and pg_table_is_visible(pg_cl.oid) "
+ "and pg_att1.attisdropped = false ";
+ break;
+ }
+ stmt += "and pg_cl.oid = pg_ind.indexrelid "
+ "and pg_att2.attrelid = pg_ind.indexrelid "
+ "and pg_att1.attrelid = pg_ind.indrelid "
+ "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
+ "order by pg_att2.attnum";
+
+ i.exec( stmt.arg( tablename.lower() ) );
+ while ( i.isActive() && i.next() ) {
+ TQSqlField f( i.value(0).toString(), qDecodePSQLType( i.value(1).toInt() ) );
+ idx.append( f );
+ idx.setName( i.value(2).toString() );
+ }
+ return idx;
+}
+
+TQSqlRecord TQPSQLDriver::record( const TQString& tablename ) const
+{
+ TQSqlRecord fil;
+ if ( !isOpen() )
+ return fil;
+ TQString stmt;
+ switch( pro ) {
+ case TQPSQLDriver::Version6:
+ stmt = "select pg_attribute.attname, int(pg_attribute.atttypid) "
+ "from pg_class, pg_attribute "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ";
+ break;
+ case TQPSQLDriver::Version7:
+ case TQPSQLDriver::Version71:
+ stmt = "select pg_attribute.attname, pg_attribute.atttypid::int "
+ "from pg_class, pg_attribute "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ";
+ break;
+ case TQPSQLDriver::Version73:
+ stmt = "select pg_attribute.attname, pg_attribute.atttypid::int "
+ "from pg_class, pg_attribute "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_table_is_visible(pg_class.oid) "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attisdropped = false "
+ "and pg_attribute.attrelid = pg_class.oid ";
+ break;
+ }
+
+ TQSqlQuery fi = createQuery();
+ fi.exec( stmt.arg( tablename.lower() ) );
+ while ( fi.next() ) {
+ TQSqlField f( fi.value(0).toString(), qDecodePSQLType( fi.value(1).toInt() ) );
+ fil.append( f );
+ }
+ return fil;
+}
+
+TQSqlRecord TQPSQLDriver::record( const TQSqlQuery& query ) const
+{
+ TQSqlRecord fil;
+ if ( !isOpen() )
+ return fil;
+ if ( query.isActive() && query.driver() == this ) {
+ TQPSQLResult* result = (TQPSQLResult*)query.result();
+ int count = PQnfields( result->d->result );
+ for ( int i = 0; i < count; ++i ) {
+ TQString name = PQfname( result->d->result, i );
+ TQVariant::Type type = qDecodePSQLType( PQftype( result->d->result, i ) );
+ TQSqlField rf( name, type );
+ fil.append( rf );
+ }
+ }
+ return fil;
+}
+
+TQSqlRecordInfo TQPSQLDriver::recordInfo( const TQString& tablename ) const
+{
+ TQSqlRecordInfo info;
+ if ( !isOpen() )
+ return info;
+
+ TQString stmt;
+ switch( pro ) {
+ case TQPSQLDriver::Version6:
+ stmt = "select pg_attribute.attname, int(pg_attribute.atttypid), pg_attribute.attnotnull, "
+ "pg_attribute.attlen, pg_attribute.atttypmod, int(pg_attribute.attrelid), pg_attribute.attnum "
+ "from pg_class, pg_attribute "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ";
+ break;
+ case TQPSQLDriver::Version7:
+ stmt = "select pg_attribute.attname, pg_attribute.atttypid::int, pg_attribute.attnotnull, "
+ "pg_attribute.attlen, pg_attribute.atttypmod, pg_attribute.attrelid::int, pg_attribute.attnum "
+ "from pg_class, pg_attribute "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ";
+ break;
+ case TQPSQLDriver::Version71:
+ stmt = "select pg_attribute.attname, pg_attribute.atttypid::int, pg_attribute.attnotnull, "
+ "pg_attribute.attlen, pg_attribute.atttypmod, pg_attrdef.adsrc "
+ "from pg_class, pg_attribute "
+ "left join pg_attrdef on (pg_attrdef.adrelid = pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid "
+ "order by pg_attribute.attnum ";
+ break;
+ case TQPSQLDriver::Version73:
+ stmt = "select pg_attribute.attname, pg_attribute.atttypid::int, pg_attribute.attnotnull, "
+ "pg_attribute.attlen, pg_attribute.atttypmod, pg_attrdef.adsrc "
+ "from pg_class, pg_attribute "
+ "left join pg_attrdef on (pg_attrdef.adrelid = pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
+ "where lower(pg_class.relname) = '%1' "
+ "and pg_table_is_visible(pg_class.oid) "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid "
+ "and pg_attribute.attisdropped = false "
+ "order by pg_attribute.attnum ";
+ break;
+ }
+
+ TQSqlQuery query = createQuery();
+ query.exec( stmt.arg( tablename.lower() ) );
+ if ( pro >= TQPSQLDriver::Version71 ) {
+ while ( query.next() ) {
+ int len = query.value( 3 ).toInt();
+ int precision = query.value( 4 ).toInt();
+ // swap length and precision if length == -1
+ if ( len == -1 && precision > -1 ) {
+ len = precision - 4;
+ precision = -1;
+ }
+ TQString defVal = query.value( 5 ).toString();
+ if ( !defVal.isEmpty() && defVal.startsWith( "'" ) )
+ defVal = defVal.mid( 1, defVal.length() - 2 );
+ info.append( TQSqlFieldInfo( query.value( 0 ).toString(),
+ qDecodePSQLType( query.value( 1 ).toInt() ),
+ query.value( 2 ).toBool(),
+ len,
+ precision,
+ defVal,
+ query.value( 1 ).toInt() ) );
+ }
+ } else {
+ // Postgres < 7.1 cannot handle outer joins
+ while ( query.next() ) {
+ TQString defVal;
+ TQString stmt2 = "select pg_attrdef.adsrc from pg_attrdef where "
+ "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 ";
+ TQSqlQuery query2 = createQuery();
+ query2.exec( stmt2.arg( query.value( 5 ).toInt() ).arg( query.value( 6 ).toInt() ) );
+ if ( query2.isActive() && query2.next() )
+ defVal = query2.value( 0 ).toString();
+ if ( !defVal.isEmpty() && defVal.startsWith( "'" ) )
+ defVal = defVal.mid( 1, defVal.length() - 2 );
+ int len = query.value( 3 ).toInt();
+ int precision = query.value( 4 ).toInt();
+ // swap length and precision if length == -1
+ if ( len == -1 && precision > -1 ) {
+ len = precision - 4;
+ precision = -1;
+ }
+ info.append( TQSqlFieldInfo( query.value( 0 ).toString(),
+ qDecodePSQLType( query.value( 1 ).toInt() ),
+ query.value( 2 ).toBool(),
+ len,
+ precision,
+ defVal,
+ query.value( 1 ).toInt() ) );
+ }
+ }
+
+ return info;
+}
+
+TQSqlRecordInfo TQPSQLDriver::recordInfo( const TQSqlQuery& query ) const
+{
+ TQSqlRecordInfo info;
+ if ( !isOpen() )
+ return info;
+ if ( query.isActive() && query.driver() == this ) {
+ TQPSQLResult* result = (TQPSQLResult*)query.result();
+ int count = PQnfields( result->d->result );
+ for ( int i = 0; i < count; ++i ) {
+ TQString name = PQfname( result->d->result, i );
+ int len = PQfsize( result->d->result, i );
+ int precision = PQfmod( result->d->result, i );
+ // swap length and precision if length == -1
+ if ( len == -1 && precision > -1 ) {
+ len = precision - 4;
+ precision = -1;
+ }
+ info.append( TQSqlFieldInfo( name,
+ qDecodePSQLType( PQftype( result->d->result, i ) ),
+ -1,
+ len,
+ precision,
+ TQVariant(),
+ PQftype( result->d->result, i ) ) );
+ }
+ }
+ return info;
+}
+
+TQString TQPSQLDriver::formatValue( const TQSqlField* field,
+ bool ) const
+{
+ TQString r;
+ if ( field->isNull() ) {
+ r = nullText();
+ } else {
+ switch ( field->type() ) {
+ case TQVariant::DateTime:
+ if ( field->value().toDateTime().isValid() ) {
+ TQDate dt = field->value().toDateTime().date();
+ TQTime tm = field->value().toDateTime().time();
+ // msecs need to be right aligned otherwise psql
+ // interpretes them wrong
+ r = "'" + TQString::number( dt.year() ) + "-" +
+ TQString::number( dt.month() ) + "-" +
+ TQString::number( dt.day() ) + " " +
+ tm.toString() + "." +
+ TQString::number( tm.msec() ).rightJustify( 3, '0' ) + "'";
+ } else {
+ r = nullText();
+ }
+ break;
+ case TQVariant::Time:
+ if ( field->value().toTime().isValid() ) {
+ r = field->value().toTime().toString( TQt::ISODate );
+ } else {
+ r = nullText();
+ }
+ case TQVariant::String:
+ case TQVariant::CString: {
+ switch ( field->value().type() ) {
+ case TQVariant::Rect: {
+ TQRect rec = field->value().toRect();
+ // upper right corner then lower left according to psql docs
+ r = "'(" + TQString::number( rec.right() ) +
+ "," + TQString::number( rec.bottom() ) +
+ "),(" + TQString::number( rec.left() ) +
+ "," + TQString::number( rec.top() ) + ")'";
+ break;
+ }
+ case TQVariant::Point: {
+ TQPoint p = field->value().toPoint();
+ r = "'(" + TQString::number( p.x() ) +
+ "," + TQString::number( p.y() ) + ")'";
+ break;
+ }
+ case TQVariant::PointArray: {
+ TQPointArray pa = field->value().toPointArray();
+ r = "' ";
+ for ( int i = 0; i < (int)pa.size(); ++i ) {
+ r += "(" + TQString::number( pa[i].x() ) +
+ "," + TQString::number( pa[i].y() ) + "),";
+ }
+ r.truncate( r.length() - 1 );
+ r += "'";
+ break;
+ }
+ default:
+ // Escape '\' characters
+ r = TQSqlDriver::formatValue( field );
+ r.replace( "\\", "\\\\" );
+ break;
+ }
+ break;
+ }
+ case TQVariant::Bool:
+ if ( field->value().toBool() )
+ r = "TRUE";
+ else
+ r = "FALSE";
+ break;
+ case TQVariant::ByteArray: {
+ TQByteArray ba = field->value().asByteArray();
+ TQString res;
+ r = "'";
+ unsigned char uc;
+ for ( int i = 0; i < (int)ba.size(); ++i ) {
+ uc = (unsigned char) ba[ i ];
+ if ( uc > 40 && uc < 92 ) {
+ r += uc;
+ } else {
+ r += "\\\\";
+ r += TQString::number( (unsigned char) ba[ i ], 8 ).rightJustify( 3, '0', TRUE );
+ }
+ }
+ r += "'";
+ break;
+ }
+ default:
+ r = TQSqlDriver::formatValue( field );
+ break;
+ }
+ }
+ return r;
+}