/****************************************************************************
**
** Implementation of PostgreSQL driver classes
**
** Created : 001103
**
** Copyright (C) 2010 Timothy Pearson and (C) 1992-2008 Trolltech ASA.
**
** 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 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.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 "tqsql_psql.h"
#include <private/tqsqlextension_p.h>

#include <math.h>

#include <tqpointarray.h>
#include <tqsqlrecord.h>
#include <tqregexp.h>
#include <tqdatetime.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 TQPSTQLPrivate
{
public:
  TQPSTQLPrivate():connection(0), result(0), isUtf8(FALSE) {}
    PGconn	*connection;
    PGresult	*result;
    bool        isUtf8;
};

class TQPSTQLDriverExtension : public TQSqlDriverExtension
{
public:
    TQPSTQLDriverExtension( TQPSTQLDriver *dri )
	: TQSqlDriverExtension(), driver(dri) { }
    ~TQPSTQLDriverExtension() {}

    bool isOpen() const;
private:
    TQPSTQLDriver *driver;
};

bool TQPSTQLDriverExtension::isOpen() const
{
    return PQstatus( driver->connection() ) == CONNECTION_OK;
}

class TQPSTQLOpenExtension : public TQSqlOpenExtension
{
public:
    TQPSTQLOpenExtension( TQPSTQLDriver *dri )
	: TQSqlOpenExtension(), driver(dri) { }
    ~TQPSTQLOpenExtension() {}

    bool open( const TQString& db,
	       const TQString& user,
	       const TQString& password,
	       const TQString& host,
	       int port,
	       const TQString& connOpts );
private:
    TQPSTQLDriver *driver;
};

bool TQPSTQLOpenExtension::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 TQPSTQLPrivate* 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 qDecodePSTQLType( 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;
}

TQPSTQLResult::TQPSTQLResult( const TQPSTQLDriver* db, const TQPSTQLPrivate* p )
: TQSqlResult( db ),
  currentSize( 0 )
{
    d =   new TQPSTQLPrivate();
    (*d) = (*p);
}

TQPSTQLResult::~TQPSTQLResult()
{
    cleanup();
    delete d;
}

PGresult* TQPSTQLResult::result()
{
    return d->result;
}

void TQPSTQLResult::cleanup()
{
    if ( d->result )
	PQclear( d->result );
    d->result = 0;
    setAt( -1 );
    currentSize = 0;
    setActive( FALSE );
}

bool TQPSTQLResult::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 TQPSTQLResult::fetchFirst()
{
    return fetch( 0 );
}

bool TQPSTQLResult::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 TQPSTQLResult::data( int i )
{
    if ( i >= PQnfields( d->result ) ) {
	qWarning( "TQPSTQLResult::data: column %d out of range", i );
	return TQVariant();
    }
    int ptype = PQftype( d->result, i );
    TQVariant::Type type = qDecodePSTQLType( 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++ ).tqunicode();
		}
	    }
	    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 TQT_CHECK_RANGE
	if ( fd < 0) {
	    qWarning( "TQPSTQLResult::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 TQ_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( "TQPSTQLResult::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 TQT_CHECK_RANGE
	qWarning("TQPSTQLResult::data: unknown data type");
#endif
	;
    }
    return TQVariant();
}

bool TQPSTQLResult::isNull( int field )
{
    PQgetvalue( d->result, at(), field );
    return PQgetisnull( d->result, at(), field );
}

bool TQPSTQLResult::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 =  PQresultqStatus( 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 TQPSTQLResult::size()
{
    return currentSize;
}

int TQPSTQLResult::numRowsAffected()
{
    return TQString( PQcmdTuples( d->result ) ).toInt();
}

///////////////////////////////////////////////////////////////////

static bool setEncodingUtf8( PGconn* connection )
{
    PGresult* result = PQexec( connection, "SET CLIENT_ENCODING TO 'UNICODE'" );
    int status = PQresultqStatus( result );
    PQclear( result );
    return status == PGRES_COMMAND_OK;
}

static void setDatestyle( PGconn* connection )
{
    PGresult* result = PQexec( connection, "SET DATESTYLE TO 'ISO'" );
#ifdef TQT_CHECK_RANGE
    int status =  PQresultqStatus( result );
    if ( status != PGRES_COMMAND_OK )
        qWarning( "%s", PQerrorMessage( connection ) );
#endif
    PQclear( result );
}

static TQPSTQLDriver::Protocol getPSTQLVersion( PGconn* connection )
{
    PGresult* result = PQexec( connection, "select version()" );
    int status =  PQresultqStatus( 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 TQT_CHECK_RANGE
		qWarning( "This version of PostgreSQL is not supported and may not work." );
#endif
		return TQPSTQLDriver::Version6;
	    }
	    if ( vMaj == 6 ) {
		return TQPSTQLDriver::Version6;
	    } else if ( vMaj == 7 ) {
		if ( vMin < 1 )
		    return TQPSTQLDriver::Version7;
		else if ( vMin < 3 )
		    return TQPSTQLDriver::Version71;
	    }
	    return TQPSTQLDriver::Version73;
	}
    } else {
#ifdef TQT_CHECK_RANGE
	qWarning( "This version of PostgreSQL is not supported and may not work." );
#endif
    }

    return TQPSTQLDriver::Version6;
}

TQPSTQLDriver::TQPSTQLDriver( TQObject * parent, const char * name )
    : TQSqlDriver(parent,name ? name : "TQPSQL"), pro( TQPSTQLDriver::Version6 )
{
    init();
}

TQPSTQLDriver::TQPSTQLDriver( PGconn * conn, TQObject * parent, const char * name )
    : TQSqlDriver(parent,name ? name : "TQPSQL"), pro( TQPSTQLDriver::Version6 )
{
    init();
    d->connection = conn;
    if ( conn ) {
	pro = getPSTQLVersion( d->connection );
	setOpen( TRUE );
	setOpenError( FALSE );
    }
}

void TQPSTQLDriver::init()
{
    qSqlDriverExtDict()->insert( this, new TQPSTQLDriverExtension(this) );
    qSqlOpenExtDict()->insert( this, new TQPSTQLOpenExtension(this) );

    d = new TQPSTQLPrivate();
}

TQPSTQLDriver::~TQPSTQLDriver()
{
    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* TQPSTQLDriver::connection()
{
    return d->connection;
}


bool TQPSTQLDriver::hasFeature( DriverFeature f ) const
{
    switch ( f ) {
    case Transactions:
	return TRUE;
    case QuerySize:
	return TRUE;
    case BLOB:
	return pro >= TQPSTQLDriver::Version71;
    case Unicode:
	return d->isUtf8;
    default:
	return FALSE;
    }
}

bool TQPSTQLDriver::open( const TQString&,
			const TQString&,
			const TQString&,
			const TQString&,
			int )
{
    qWarning("TQPSTQLDriver::open(): This version of open() is no longer supported." );
    return FALSE;
}

bool TQPSTQLDriver::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 = getPSTQLVersion( d->connection );
    d->isUtf8 = setEncodingUtf8( d->connection );
    setDatestyle( d->connection );

    setOpen( TRUE );
    setOpenError( FALSE );
    return TRUE;
}

void TQPSTQLDriver::close()
{
    if ( isOpen() ) {
        if (d->connection)
	    PQfinish( d->connection );
	d->connection = 0;
	setOpen( FALSE );
	setOpenError( FALSE );
    }
}

TQSqlQuery TQPSTQLDriver::createQuery() const
{
    return TQSqlQuery( new TQPSTQLResult( this, d ) );
}

bool TQPSTQLDriver::beginTransaction()
{
    if ( !isOpen() ) {
#ifdef TQT_CHECK_RANGE
	qWarning( "TQPSTQLDriver::beginTransaction: Database not open" );
#endif
	return FALSE;
    }
    PGresult* res = PQexec( d->connection, "BEGIN" );
    if ( !res || PQresultqStatus( res ) != PGRES_COMMAND_OK ) {
	PQclear( res );
	setLastError( qMakeError( "Could not begin transaction", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    PQclear( res );
    return TRUE;
}

bool TQPSTQLDriver::commitTransaction()
{
    if ( !isOpen() ) {
#ifdef TQT_CHECK_RANGE
	qWarning( "TQPSTQLDriver::commitTransaction: Database not open" );
#endif
	return FALSE;
    }
    PGresult* res = PQexec( d->connection, "COMMIT" );
    if ( !res || PQresultqStatus( res ) != PGRES_COMMAND_OK ) {
	PQclear( res );
	setLastError( qMakeError( "Could not commit transaction", TQSqlError::Transaction, d ) );
	return FALSE;
    }
    PQclear( res );
    return TRUE;
}

bool TQPSTQLDriver::rollbackTransaction()
{
    if ( !isOpen() ) {
#ifdef TQT_CHECK_RANGE
	qWarning( "TQPSTQLDriver::rollbackTransaction: Database not open" );
#endif
	return FALSE;
    }
    PGresult* res = PQexec( d->connection, "ROLLBACK" );
    if ( !res || PQresultqStatus( res ) != PGRES_COMMAND_OK ) {
	setLastError( qMakeError( "Could not rollback transaction", TQSqlError::Transaction, d ) );
	PQclear( res );
	return FALSE;
    }
    PQclear( res );
    return TRUE;
}

TQStringList TQPSTQLDriver::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 >= TQPSTQLDriver::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 >= TQPSTQLDriver::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 >= TQPSTQLDriver::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 TQPSTQLDriver::primaryIndex( const TQString& tablename ) const
{
    TQSqlIndex idx( tablename );
    if ( !isOpen() )
	return idx;
    TQSqlQuery i = createQuery();
    TQString stmt;

    switch( pro ) {
    case TQPSTQLDriver::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 TQPSTQLDriver::Version7:
    case TQPSTQLDriver::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 TQPSTQLDriver::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(), qDecodePSTQLType( i.value(1).toInt() ) );
	idx.append( f );
	idx.setName( i.value(2).toString() );
    }
    return idx;
}

TQSqlRecord TQPSTQLDriver::record( const TQString& tablename ) const
{
    TQSqlRecord fil;
    if ( !isOpen() )
	return fil;
    TQString stmt;
    switch( pro ) {
    case TQPSTQLDriver::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 TQPSTQLDriver::Version7:
    case TQPSTQLDriver::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 TQPSTQLDriver::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(), qDecodePSTQLType( fi.value(1).toInt() ) );
	fil.append( f );
    }
    return fil;
}

TQSqlRecord TQPSTQLDriver::record( const TQSqlQuery& query ) const
{
    TQSqlRecord fil;
    if ( !isOpen() )
	return fil;
    if ( query.isActive() && query.driver() == this ) {
	TQPSTQLResult* result = (TQPSTQLResult*)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 = qDecodePSTQLType( PQftype( result->d->result, i ) );
	    TQSqlField rf( name, type );
	    fil.append( rf );
	}
    }
    return fil;
}

TQSqlRecordInfo TQPSTQLDriver::recordInfo( const TQString& tablename ) const
{
    TQSqlRecordInfo info;
    if ( !isOpen() )
	return info;

    TQString stmt;
    switch( pro ) {
    case TQPSTQLDriver::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 TQPSTQLDriver::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 TQPSTQLDriver::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 TQPSTQLDriver::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 >= TQPSTQLDriver::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(),
					qDecodePSTQLType( 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(),
					qDecodePSTQLType( query.value( 1 ).toInt() ),
					query.value( 2 ).toBool(),
					len,
					precision,
					defVal,
					query.value( 1 ).toInt() ) );
	}
    }

    return info;
}

TQSqlRecordInfo TQPSTQLDriver::recordInfo( const TQSqlQuery& query ) const
{
    TQSqlRecordInfo info;
    if ( !isOpen() )
	return info;
    if ( query.isActive() && query.driver() == this ) {
	TQPSTQLResult* result = (TQPSTQLResult*)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,
					qDecodePSTQLType( PQftype( result->d->result, i ) ),
					-1,
					len,
					precision,
					TQVariant(),
					PQftype( result->d->result, i ) ) );
	}
    }
    return info;
}

TQString TQPSTQLDriver::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;
}
