/**************************************************************************** ** ** 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 Qt 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 Qt Foundation. ** ** Please review the following information to ensure GNU General ** Public Licensing requirements 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 sales@trolltech.com. ** ** This file may be used under the terms of the Q Public License as ** defined by Trolltech ASA and appearing in the file LICENSE.QPL ** included in the packaging of this file. Licensees holding valid Qt ** Commercial licenses may use this file in accordance with the Qt ** 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 #include #include #include #include #include // PostgreSQL header included by redefines DEBUG. #if defined(DEBUG) # undef DEBUG #endif #include #include // PostgreSQL header redefines errno erroneously. #if defined(errno) # undef errno #endif #define errno qt_psql_errno #include #undef errno #ifdef open # undef open #endif QPtrDict *qSqlDriverExtDict(); QPtrDict *qSqlOpenExtDict(); class QPSQLPrivate { public: QPSQLPrivate():connection(0), result(0), isUtf8(FALSE) {} PGconn *connection; PGresult *result; bool isUtf8; }; class QPSQLDriverExtension : public QSqlDriverExtension { public: QPSQLDriverExtension( QPSQLDriver *dri ) : QSqlDriverExtension(), driver(dri) { } ~QPSQLDriverExtension() {} bool isOpen() const; private: QPSQLDriver *driver; }; bool QPSQLDriverExtension::isOpen() const { return PQstatus( driver->connection() ) == CONNECTION_OK; } class QPSQLOpenExtension : public QSqlOpenExtension { public: QPSQLOpenExtension( QPSQLDriver *dri ) : QSqlOpenExtension(), driver(dri) { } ~QPSQLOpenExtension() {} bool open( const QString& db, const QString& user, const QString& password, const QString& host, int port, const QString& connOpts ); private: QPSQLDriver *driver; }; bool QPSQLOpenExtension::open( const QString& db, const QString& user, const QString& password, const QString& host, int port, const QString& connOpts ) { return driver->open( db, user, password, host, port, connOpts ); } static QSqlError qMakeError( const QString& err, int type, const QPSQLPrivate* p ) { const char *s = PQerrorMessage(p->connection); QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s); return QSqlError("QPSQL: " + err, msg, type); } static QVariant::Type qDecodePSQLType( int t ) { QVariant::Type type = QVariant::Invalid; switch ( t ) { case BOOLOID : type = QVariant::Bool; break; case INT8OID : type = QVariant::LongLong; break; case INT2OID : // case INT2VECTOROID : // 7.x case INT4OID : type = QVariant::Int; break; case NUMERICOID : case FLOAT4OID : case FLOAT8OID : type = QVariant::Double; break; case ABSTIMEOID : case RELTIMEOID : case DATEOID : type = QVariant::Date; break; case TIMEOID : #ifdef TIMETZOID // 7.x case TIMETZOID : #endif type = QVariant::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 = QVariant::DateTime; break; // case ZPBITOID : // 7.x // case VARBITOID : // 7.x case OIDOID : case BYTEAOID : type = QVariant::ByteArray; break; case REGPROCOID : case TIDOID : case XIDOID : case CIDOID : // case OIDVECTOROID : // 7.x case UNKNOWNOID : // case TINTERVALOID : // 7.x type = QVariant::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 = QVariant::String; break; } return type; } QPSQLResult::QPSQLResult( const QPSQLDriver* db, const QPSQLPrivate* p ) : QSqlResult( db ), currentSize( 0 ) { d = new QPSQLPrivate(); (*d) = (*p); } QPSQLResult::~QPSQLResult() { cleanup(); delete d; } PGresult* QPSQLResult::result() { return d->result; } void QPSQLResult::cleanup() { if ( d->result ) PQclear( d->result ); d->result = 0; setAt( -1 ); currentSize = 0; setActive( FALSE ); } bool QPSQLResult::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 QPSQLResult::fetchFirst() { return fetch( 0 ); } bool QPSQLResult::fetchLast() { return fetch( PQntuples( d->result ) - 1 ); } // some Postgres conversions static QPoint pointFromString( const QString& 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 QPoint( x, y ) ; } else return QPoint(); } QVariant QPSQLResult::data( int i ) { if ( i >= PQnfields( d->result ) ) { qWarning( "QPSQLResult::data: column %d out of range", i ); return QVariant(); } int ptype = PQftype( d->result, i ); QVariant::Type type = qDecodePSQLType( ptype ); const QString val = ( d->isUtf8 && ptype != BYTEAOID ) ? QString::fromUtf8( PQgetvalue( d->result, at(), i ) ) : QString::fromLocal8Bit( PQgetvalue( d->result, at(), i ) ); if ( PQgetisnull( d->result, at(), i ) ) { QVariant v; v.cast( type ); return v; } switch ( type ) { case QVariant::Bool: { QVariant b ( (bool)(val == "t"), 0 ); return ( b ); } case QVariant::String: return QVariant( val ); case QVariant::LongLong: if ( val[0] == '-' ) return QVariant( val.toLongLong() ); else return QVariant( val.toULongLong() ); case QVariant::Int: return QVariant( val.toInt() ); case QVariant::Double: if ( ptype == NUMERICOID ) return QVariant( val ); return QVariant( val.toDouble() ); case QVariant::Date: if ( val.isEmpty() ) { return QVariant( QDate() ); } else { return QVariant( QDate::fromString( val, Qt::ISODate ) ); } case QVariant::Time: if ( val.isEmpty() ) return QVariant( QTime() ); if ( val.at( val.length() - 3 ) == '+' ) // strip the timezone return QVariant( QTime::fromString( val.left( val.length() - 3 ), Qt::ISODate ) ); return QVariant( QTime::fromString( val, Qt::ISODate ) ); case QVariant::DateTime: { if ( val.length() < 10 ) return QVariant( QDateTime() ); // remove the timezone QString 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 QVariant( QDateTime() ); else return QVariant( QDateTime::fromString( dtval, Qt::ISODate ) ); } case QVariant::Point: return QVariant( pointFromString( val ) ); case QVariant::Rect: // format '(x,y),(x',y')' { int pivot = val.find( "),(" ); if ( pivot != -1 ) return QVariant( QRect( pointFromString( val.mid(pivot+2,val.length()) ), pointFromString( val.mid(0,pivot+1) ) ) ); return QVariant( QRect() ); } case QVariant::PointArray: // format '((x,y),(x1,y1),...,(xn,yn))' { QRegExp pointPattern("\\([0-9-]*,[0-9-]*\\)"); int points = val.contains( pointPattern ); QPointArray 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, QPoint() ); } else { parray.setPoint( i, QPoint() ); break; } idx = end+2; } return QVariant( parray ); } case QVariant::ByteArray: { if ( ptype == BYTEAOID ) { uint i = 0; int index = 0; uint len = val.length(); static const QChar backslash( '\\' ); QByteArray 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 QVariant( ba ); } QByteArray ba; ((QSqlDriver*)driver())->beginTransaction(); Oid oid = val.toInt(); int fd = lo_open( d->connection, oid, INV_READ ); #ifdef QT_CHECK_RANGE if ( fd < 0) { qWarning( "QPSQLResult::data: unable to open large object for read" ); ((QSqlDriver*)driver())->commitTransaction(); return QVariant( 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 ); ((QSqlDriver*)driver())->commitTransaction(); return QVariant( 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( "QPSQLResult::data: unable to read large object" ); } else { ba.duplicate( buf, size ); } delete [] buf; lo_close( d->connection, fd ); ((QSqlDriver*)driver())->commitTransaction(); return QVariant( ba ); } default: case QVariant::Invalid: #ifdef QT_CHECK_RANGE qWarning("QPSQLResult::data: unknown data type"); #endif ; } return QVariant(); } bool QPSQLResult::isNull( int field ) { PQgetvalue( d->result, at(), field ); return PQgetisnull( d->result, at(), field ); } bool QPSQLResult::reset ( const QString& query ) { cleanup(); if ( !driver() ) return FALSE; if ( !driver()->isOpen() || driver()->isOpenError() ) return FALSE; setActive( FALSE ); setAt( QSql::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", QSqlError::Statement, d ) ); return FALSE; } int QPSQLResult::size() { return currentSize; } int QPSQLResult::numRowsAffected() { return QString( 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 QPSQLDriver::Protocol getPSQLVersion( PGconn* connection ) { PGresult* result = PQexec( connection, "select version()" ); int status = PQresultStatus( result ); if ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) { QString val( PQgetvalue( result, 0, 0 ) ); PQclear( result ); QRegExp 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 QPSQLDriver::Version6; } if ( vMaj == 6 ) { return QPSQLDriver::Version6; } else if ( vMaj == 7 ) { if ( vMin < 1 ) return QPSQLDriver::Version7; else if ( vMin < 3 ) return QPSQLDriver::Version71; } return QPSQLDriver::Version73; } } else { #ifdef QT_CHECK_RANGE qWarning( "This version of PostgreSQL is not supported and may not work." ); #endif } return QPSQLDriver::Version6; } QPSQLDriver::QPSQLDriver( QObject * parent, const char * name ) : QSqlDriver(parent,name ? name : "QPSQL"), pro( QPSQLDriver::Version6 ) { init(); } QPSQLDriver::QPSQLDriver( PGconn * conn, QObject * parent, const char * name ) : QSqlDriver(parent,name ? name : "QPSQL"), pro( QPSQLDriver::Version6 ) { init(); d->connection = conn; if ( conn ) { pro = getPSQLVersion( d->connection ); setOpen( TRUE ); setOpenError( FALSE ); } } void QPSQLDriver::init() { qSqlDriverExtDict()->insert( this, new QPSQLDriverExtension(this) ); qSqlOpenExtDict()->insert( this, new QPSQLOpenExtension(this) ); d = new QPSQLPrivate(); } QPSQLDriver::~QPSQLDriver() { if ( d->connection ) PQfinish( d->connection ); delete d; if ( !qSqlDriverExtDict()->isEmpty() ) { QSqlDriverExtension *ext = qSqlDriverExtDict()->take( this ); delete ext; } if ( !qSqlOpenExtDict()->isEmpty() ) { QSqlOpenExtension *ext = qSqlOpenExtDict()->take( this ); delete ext; } } PGconn* QPSQLDriver::connection() { return d->connection; } bool QPSQLDriver::hasFeature( DriverFeature f ) const { switch ( f ) { case Transactions: return TRUE; case QuerySize: return TRUE; case BLOB: return pro >= QPSQLDriver::Version71; case Unicode: return d->isUtf8; default: return FALSE; } } bool QPSQLDriver::open( const QString&, const QString&, const QString&, const QString&, int ) { qWarning("QPSQLDriver::open(): This version of open() is no longer supported." ); return FALSE; } bool QPSQLDriver::open( const QString & db, const QString & user, const QString & password, const QString & host, int port, const QString& connOpts ) { if ( isOpen() ) close(); QString 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( QString::number( port ) ); // add any connect options - the server will handle error detection if ( !connOpts.isEmpty() ) connectString += " " + QStringList::split( ';', connOpts ).join( " " ); d->connection = PQconnectdb( connectString.local8Bit().data() ); if ( PQstatus( d->connection ) == CONNECTION_BAD ) { setLastError( qMakeError("Unable to connect", QSqlError::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 QPSQLDriver::close() { if ( isOpen() ) { if (d->connection) PQfinish( d->connection ); d->connection = 0; setOpen( FALSE ); setOpenError( FALSE ); } } QSqlQuery QPSQLDriver::createQuery() const { return QSqlQuery( new QPSQLResult( this, d ) ); } bool QPSQLDriver::beginTransaction() { if ( !isOpen() ) { #ifdef QT_CHECK_RANGE qWarning( "QPSQLDriver::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", QSqlError::Transaction, d ) ); return FALSE; } PQclear( res ); return TRUE; } bool QPSQLDriver::commitTransaction() { if ( !isOpen() ) { #ifdef QT_CHECK_RANGE qWarning( "QPSQLDriver::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", QSqlError::Transaction, d ) ); return FALSE; } PQclear( res ); return TRUE; } bool QPSQLDriver::rollbackTransaction() { if ( !isOpen() ) { #ifdef QT_CHECK_RANGE qWarning( "QPSQLDriver::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", QSqlError::Transaction, d ) ); PQclear( res ); return FALSE; } PQclear( res ); return TRUE; } QStringList QPSQLDriver::tables( const QString& typeName ) const { QStringList tl; if ( !isOpen() ) return tl; int type = typeName.toInt(); QSqlQuery t = createQuery(); t.setForwardOnly( TRUE ); if ( typeName.isEmpty() || ((type & (int)QSql::Tables) == (int)QSql::Tables) ) { QString query("select relname from pg_class where (relkind = 'r') " "and (relname !~ '^Inv') " "and (relname !~ '^pg_') "); if (pro >= QPSQLDriver::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)QSql::Views) == (int)QSql::Views ) { QString query("select relname from pg_class where ( relkind = 'v' ) " "and ( relname !~ '^Inv' ) " "and ( relname !~ '^pg_' ) "); if (pro >= QPSQLDriver::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)QSql::SystemTables) == (int)QSql::SystemTables ) { QString query( "select relname from pg_class where ( relkind = 'r' ) " "and ( relname like 'pg_%' ) " ); if (pro >= QPSQLDriver::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; } QSqlIndex QPSQLDriver::primaryIndex( const QString& tablename ) const { QSqlIndex idx( tablename ); if ( !isOpen() ) return idx; QSqlQuery i = createQuery(); QString stmt; switch( pro ) { case QPSQLDriver::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 QPSQLDriver::Version7: case QPSQLDriver::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 QPSQLDriver::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() ) { QSqlField f( i.value(0).toString(), qDecodePSQLType( i.value(1).toInt() ) ); idx.append( f ); idx.setName( i.value(2).toString() ); } return idx; } QSqlRecord QPSQLDriver::record( const QString& tablename ) const { QSqlRecord fil; if ( !isOpen() ) return fil; QString stmt; switch( pro ) { case QPSQLDriver::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 QPSQLDriver::Version7: case QPSQLDriver::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 QPSQLDriver::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; } QSqlQuery fi = createQuery(); fi.exec( stmt.arg( tablename.lower() ) ); while ( fi.next() ) { QSqlField f( fi.value(0).toString(), qDecodePSQLType( fi.value(1).toInt() ) ); fil.append( f ); } return fil; } QSqlRecord QPSQLDriver::record( const QSqlQuery& query ) const { QSqlRecord fil; if ( !isOpen() ) return fil; if ( query.isActive() && query.driver() == this ) { QPSQLResult* result = (QPSQLResult*)query.result(); int count = PQnfields( result->d->result ); for ( int i = 0; i < count; ++i ) { QString name = PQfname( result->d->result, i ); QVariant::Type type = qDecodePSQLType( PQftype( result->d->result, i ) ); QSqlField rf( name, type ); fil.append( rf ); } } return fil; } QSqlRecordInfo QPSQLDriver::recordInfo( const QString& tablename ) const { QSqlRecordInfo info; if ( !isOpen() ) return info; QString stmt; switch( pro ) { case QPSQLDriver::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 QPSQLDriver::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 QPSQLDriver::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 QPSQLDriver::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; } QSqlQuery query = createQuery(); query.exec( stmt.arg( tablename.lower() ) ); if ( pro >= QPSQLDriver::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; } QString defVal = query.value( 5 ).toString(); if ( !defVal.isEmpty() && defVal.startsWith( "'" ) ) defVal = defVal.mid( 1, defVal.length() - 2 ); info.append( QSqlFieldInfo( 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() ) { QString defVal; QString stmt2 = "select pg_attrdef.adsrc from pg_attrdef where " "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 "; QSqlQuery 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( QSqlFieldInfo( query.value( 0 ).toString(), qDecodePSQLType( query.value( 1 ).toInt() ), query.value( 2 ).toBool(), len, precision, defVal, query.value( 1 ).toInt() ) ); } } return info; } QSqlRecordInfo QPSQLDriver::recordInfo( const QSqlQuery& query ) const { QSqlRecordInfo info; if ( !isOpen() ) return info; if ( query.isActive() && query.driver() == this ) { QPSQLResult* result = (QPSQLResult*)query.result(); int count = PQnfields( result->d->result ); for ( int i = 0; i < count; ++i ) { QString 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( QSqlFieldInfo( name, qDecodePSQLType( PQftype( result->d->result, i ) ), -1, len, precision, QVariant(), PQftype( result->d->result, i ) ) ); } } return info; } QString QPSQLDriver::formatValue( const QSqlField* field, bool ) const { QString r; if ( field->isNull() ) { r = nullText(); } else { switch ( field->type() ) { case QVariant::DateTime: if ( field->value().toDateTime().isValid() ) { QDate dt = field->value().toDateTime().date(); QTime tm = field->value().toDateTime().time(); // msecs need to be right aligned otherwise psql // interpretes them wrong r = "'" + QString::number( dt.year() ) + "-" + QString::number( dt.month() ) + "-" + QString::number( dt.day() ) + " " + tm.toString() + "." + QString::number( tm.msec() ).rightJustify( 3, '0' ) + "'"; } else { r = nullText(); } break; case QVariant::Time: if ( field->value().toTime().isValid() ) { r = field->value().toTime().toString( Qt::ISODate ); } else { r = nullText(); } case QVariant::String: case QVariant::CString: { switch ( field->value().type() ) { case QVariant::Rect: { QRect rec = field->value().toRect(); // upper right corner then lower left according to psql docs r = "'(" + QString::number( rec.right() ) + "," + QString::number( rec.bottom() ) + "),(" + QString::number( rec.left() ) + "," + QString::number( rec.top() ) + ")'"; break; } case QVariant::Point: { QPoint p = field->value().toPoint(); r = "'(" + QString::number( p.x() ) + "," + QString::number( p.y() ) + ")'"; break; } case QVariant::PointArray: { QPointArray pa = field->value().toPointArray(); r = "' "; for ( int i = 0; i < (int)pa.size(); ++i ) { r += "(" + QString::number( pa[i].x() ) + "," + QString::number( pa[i].y() ) + "),"; } r.truncate( r.length() - 1 ); r += "'"; break; } default: // Escape '\' characters r = QSqlDriver::formatValue( field ); r.replace( "\\", "\\\\" ); break; } break; } case QVariant::Bool: if ( field->value().toBool() ) r = "TRUE"; else r = "FALSE"; break; case QVariant::ByteArray: { QByteArray ba = field->value().asByteArray(); QString 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 += QString::number( (unsigned char) ba[ i ], 8 ).rightJustify( 3, '0', TRUE ); } } r += "'"; break; } default: r = QSqlDriver::formatValue( field ); break; } } return r; }