/*********************************version*******************************************
**
** Implementation of PostgreSQL driver classes
**
** Created : 001103
**
** Copyright (C) 1992-2000 Trolltech AS.  All rights reserved.
**
** This file is part of the sql module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition 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
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/
#include "qpsql_driver.h"
#include <qsqlrecord.h>
#include <qregexp.h>
#include <qdatetime.h>
#include <qpointarray.h>
//#include <postgres_fe.h>
#include <server/postgres.h>
#include <libpq/libpq-fs.h>
#if defined(Q_CC_MSVC)
#pragma warning(disable: 4273) // '_errno' : inconsistent dll linkage.  dllexport assumed
#endif
#include <catalog/pg_type.h>
#if defined(Q_CC_MSVC)
#pragma warning(default: 4273)
#endif
#ifndef Q_WS_MAC
//#include <utils/geo_decls.h>
#endif
#include <math.h>


/*================================Non-class functions=========================================*/
QSqlError qMakeError( const QString& err, int type, const QPGSQLPrivate* p )
{
    //return QSqlError("QPGSQL: " + err, QString(PQerrorMessage( p->connection )), type);
	//return QSqlError("QPGSQL: " + err, QString::fromUtf8(PQerrorMessage( p->connection )), type);
	return QSqlError("QPGSQL: " + err, QString::fromLocal8Bit(PQerrorMessage( p->connection )), type);
}

QVariant::Type qDecodePGSQLType( int t )
{
    QVariant::Type type = QVariant::Invalid;
    switch ( t ) {
    case BOOLOID	:
		type = QVariant::Bool;
		break;
    case INT8OID	:
    case INT2OID	:
	case INT2VECTOROID  :
    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	:
	case TIMETZOID      :
		type = QVariant::Time;
		break;
    case TIMESTAMPOID   :
		type = QVariant::DateTime;
		break;
    case POINTOID       :
		type = QVariant::Point;
		break;
    case BOXOID         :
		type = QVariant::Rect;
		break;
    case POLYGONOID     :
		type = QVariant::PointArray;
		break;
	case VARBITOID	:
    case OIDOID         :
		type = QVariant::Int;
		break;
    case REGPROCOID     :
    case TIDOID         :
    case XIDOID         :
		type = QVariant::Int;
		break;
    case CIDOID         :
		type = QVariant::Int;
		break;
	case BYTEAOID	:
	case OIDVECTOROID   :
		type = QVariant::ByteArray;
		break;
    case UNKNOWNOID     :
		//type = QVariant::Invalid;
		type = QVariant::String;
		break;
    case INETOID        :
	case CIDROID        :
		type = QVariant::String;
		break;
	case TINTERVALOID   :
    case CIRCLEOID      :
		type = QVariant::PointArray;
		break;
    case PATHOID        :
    case LSEGOID        :
    case LINEOID        :
		type = QVariant::Invalid;
		break;
    default:
    case CHAROID	:
    case BPCHAROID	:
    case VARCHAROID	:
    case TEXTOID	:
    case NAMEOID	:
    case CASHOID        :
		type = QVariant::String;
	break;
    }
	
	if (type == QVariant::Invalid){
		//#ifdef DEBUG
			qWarning("qDecodePGSQLType() - Data type unknown: %i", t);
		//#endif
	}
    return type;
}

QVariant::Type qFieldType( QPGSQLPrivate* p, int i )
{
    QVariant::Type type = qDecodePGSQLType( PQftype( p->result, i ) );
    return type;
}

QDate qDateFromUInt( uint dt )
{
    int y,m,d;
    QDate::julianToGregorian( dt, y, m, d );
    return QDate( y, m, d );
}

// some Postgres conversions
QPoint pointFromString( const QString& s)
{
    // format '(x,y)'
    int pivot = s.find( QRegExp(",") );
    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();
}

/* Notice callback function*/
void noticeCallBack(void* conn , const char* msg){

	if (conn == NULL){
		return;	
	}
	QPGSQLDriver* Fconn = (QPGSQLDriver*)conn;	
	if (Fconn->connection() == NULL){
		return;	
	}
	QString noticeFrom(PQhost(Fconn->connection()));

	Fconn->sendNotice("SQL Driver Notice",QString(msg));
}


/*================================QPGSQLResult================================================*/
QPGSQLResult::QPGSQLResult( const QPGSQLDriver* db, const QPGSQLPrivate* p )
: QSqlResult( db ),
  currentSize( 0 )
{
    d =   new QPGSQLPrivate();
    (*d) = (*p);
}

QPGSQLDriver* QPGSQLResult::pgDriver(){
	//QSqlResult* fRes = (QSqlResult*)this;
	QPGSQLDriver* dr = (QPGSQLDriver*)this->driver();
    return dr;
}


QPGSQLResult::~QPGSQLResult()
{
    cleanup();
    delete d;
}

bool QPGSQLResult::beginTransaction(){
	return pgDriver()->beginTransaction();
}

bool QPGSQLResult::commitTransaction(){
	return pgDriver()->commitTransaction();
}

bool QPGSQLResult::rollbackTransaction(){
	return pgDriver()->rollbackTransaction();
}

int QPGSQLResult::fieldColumn(const QString fieldName){
	for (int nCurCol = 0;  nCurCol<fieldCount(); nCurCol++){
    	if (fieldName == PQfname(d->result,nCurCol))
			return nCurCol;
	}
	return -1;
}

QString QPGSQLResult::escapeString(const QString& strData){
	size_t srcLen = strlen(strData.local8Bit().data());
	char* qResSql= (char*)malloc((srcLen*2)+1);
	if (d->isUtf8)
		PQescapeString (qResSql, strData.utf8().data() , srcLen);
	else
		PQescapeString (qResSql, strData.local8Bit().data() , srcLen);

	free(qResSql);
	QString retStr(qResSql);
	return retStr;
}

void QPGSQLResult::cleanup()
{
    if ( d->result )
		PQclear( d->result );
    d->result = 0;
    setAt( -1 );
    currentSize = 0;
    setActive( FALSE );
}

bool QPGSQLResult::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 QPGSQLResult::fetchFirst()
{
    return fetch( 0 );
}

bool QPGSQLResult::fetchLast()
{
    return fetch( PQntuples( d->result ) - 1 );
}

QVariant QPGSQLResult::data( const QString fieldName ){
	int nCol = fieldColumn(fieldName);
    if ( nCol >=0 )
		return data(nCol);
	else
		return QVariant();
}


QVariant QPGSQLResult::data( int i )
{
    QVariant::Type type = qFieldType( d, i );
    QString val;
    if (type == QVariant::ByteArray){
		//ByTeArray
		char* val = PQgetvalue( d->result, at(), i );
		size_t newLen= strlen(val);
    	QByteArray  ba;
		if (newLen>0){
    		//unsigned char* unesc = PQunescapeBytea((unsigned char*)val, &newLen);
			ba.setRawData( (const char*)(PQunescapeBytea((unsigned char*)val, &newLen)) , newLen );
			//ba.duplicate( (const char*)unesc, newLen );
			
			//delete val;
			//delete unesc;
		}
		return QVariant(ba);
	}else{
		if ( d->isUtf8 ) {
			val = QString::fromUtf8( PQgetvalue( d->result, at(), i ) );
	    } else {
			val = QString::fromLocal8Bit( PQgetvalue( d->result, at(), i ) );
	    }
    }
	switch ( type ) {
    case QVariant::Bool:
	{
	    QVariant b ( (bool)(val == "t"), 0 );
	    return ( b );
	}
    case QVariant::String:
		return QVariant( val );
    case QVariant::Int:
		return QVariant( val.toInt() );
    case QVariant::Double:
		return QVariant( val.toDouble() );
    case QVariant::Date:
		if (val=="")
			return QVariant( QDate() );			
		else	
			return QVariant( QDate::fromString( val, Qt::ISODate ) );
    case QVariant::Time:
		if (val=="")
			return QVariant( QTime() );
		else			
			return QVariant( QTime::fromString( val, Qt::ISODate ) );
    case QVariant::DateTime:
		if ( val.length() < 10 )
	    	return QVariant( QDateTime() );
		// remove the timezone
		if ( val.find( "+", val.length() - 3 ) >= 0 )
	    	val = val.left( val.length() - 3 );
		// for some reasons the milliseconds are sometimes only 2 digits
		// (12 instead of 120)
		if ( val.find( ".", val.length() - 3 ) >= 0 )
		    val += "0";
		return QVariant( QDateTime::fromString( val, Qt::ISODate ) );
    case QVariant::Point:
		return QVariant( pointFromString( val ) );
    case QVariant::Rect: // format '(x,y),(x',y')'
	{
	    int pivot = val.find( QRegExp( "\\),\\(" ) );
	    if ( pivot != -1 )
		return QVariant( QRect( pointFromString( val.mid(0,pivot+1) ), pointFromString( val.mid(pivot+2,val.length()) ) ) );
	    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( QRegExp("\\)"), 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 );
	}
    default:
    case QVariant::Invalid:
#ifdef QT_CHECK_RANGE
	qWarning("QPGSQLResult::data: unknown data type in column %i", i);
#endif
	;
    }
    return QVariant();
}

bool QPGSQLResult::isNull( int field )
{
    PQgetvalue( d->result, at(), field );
    return PQgetisnull( d->result, at(), field );
}

bool QPGSQLResult::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 ) {
		setSelect( (status == PGRES_TUPLES_OK) );
		currentSize = PQntuples( d->result );
		setActive( TRUE );

		pgDriver()->checkSqlNotify();

		return TRUE;
    }
    setLastError( qMakeError( "Unable to create query", QSqlError::Statement, d ) );
    return FALSE;
}

int QPGSQLResult::size()
{
    return currentSize;
}

int QPGSQLResult::numRowsAffected()
{
    return QString( PQcmdTuples( d->result ) ).toInt();
}

QByteArray QPGSQLResult::readBlob(int oid){
	return this->pgDriver()->readBlob(oid);
}

int QPGSQLResult::createBlob(){
	return this->pgDriver()->createBlob();
}

bool QPGSQLResult::writeBlob(int oid, QByteArray blobData){
	return this->pgDriver()->writeBlob(oid,blobData);
}


/*================================QPGSQLDriver===============================================*/
QPGSQLDriver::QPGSQLDriver( QObject * parent, const char * name )
    : QSqlDriver(parent,name ? name : "QPGSQL73"), pro( QPGSQLDriver::Version7 )
{
    init();
}

void QPGSQLDriver::init()
{
	FSqlNotify = new QSignal;
    d = new QPGSQLPrivate();
}

QPGSQLDriver::~QPGSQLDriver()
{
    if ( d->connection )
	PQfinish( d->connection );
    delete d;
    delete FSqlNotify;
}

bool QPGSQLDriver::hasFeature( DriverFeature f ) const
{
    switch ( f ) {
    case Transactions:
		return TRUE;
    case QuerySize:
		return TRUE;
    case BLOB:
		return FALSE;
    default:
		return FALSE;
    }
}

static QPGSQLDriver::Protocol getPGSQLVersion( 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();
		#ifdef DEBUG
		    qDebug("Postgres version change %i.%i",vMaj,vMin);
		#endif
	    if ( vMaj < 7 ) {
			#ifdef QT_CHECK_RANGE
				qWarning( "This version of PostgreSQL is not supported and may not work." );
			#endif
			return QPGSQLDriver::Version7;
	    }
	    if ( vMaj == 7 ) {
			if ( vMin < 1 ) {
		    	return QPGSQLDriver::Version7;
			} else if (vMin == 1){
		    	return QPGSQLDriver::Version71;
			} else if (vMin == 2){
		    	return QPGSQLDriver::Version72;
			} else {
		    	return QPGSQLDriver::Version73;
			}
	    }
	}
    } else {
	PQclear( result );
		#ifdef QT_CHECK_RANGE
			qWarning( "This version of PostgreSQL is not supported and may not work." );
		#endif
    }

    return QPGSQLDriver::Version7;
}

PGconn*	QPGSQLDriver::connection(){
	return d->connection;
}

QString QPGSQLDriver::escapeString(const QString& strData){
	size_t srcLen = strlen(strData.local8Bit().data());
	char* qResSql= (char*)malloc((srcLen*2)+1);
	if (d->isUtf8)
		PQescapeString (qResSql, strData.utf8().data() , srcLen);
	else
		PQescapeString (qResSql, strData.local8Bit().data() , srcLen);

    return QString(qResSql);
}

bool QPGSQLDriver::open( const QString & db,
			const QString & user,
			const QString & password,
			const QString & host,
			int port )
{
    int status;
    if ( isOpen() )
		close();
    QString connectString;
    if ( host.length() )
		connectString += QString("host=%1 ").arg( host );
    if ( db.length() )
		connectString += QString("dbname=%1 ").arg( db );
    if ( user.length() )
		connectString += QString("user=%1 ").arg( user );
    if ( password.length() )
		connectString += QString("password=%1 ").arg( password );
    if ( port > -1 )
		connectString += QString("port=%1 ").arg( port );
    d->connection = PQconnectdb( connectString.local8Bit().data() );
    if ( PQstatus( d->connection ) == CONNECTION_BAD ) {
		setLastError( qMakeError("Unable to connect", QSqlError::Connection, d ) );
		setOpenError( TRUE );
		return FALSE;
    }

// Unicode support is only working if the client library has been compiled with
// multibyte support.
#ifdef MULTIBYTE
    status = PQsetClientEncoding( d->connection, "UNICODE" );
    if ( status == 0 ) {
		d->isUtf8 = TRUE;
    }
#endif

    pro = getPGSQLVersion( d->connection );

    PGresult* dateResult = 0;
	dateResult = PQexec( d->connection, "SET DATESTYLE=ISO" );
#ifdef QT_CHECK_RANGE
    status =  PQresultStatus( dateResult );
    if ( status != PGRES_COMMAND_OK )
		qWarning( PQerrorMessage( d->connection ) );
#endif
    setOpen( TRUE );
	PQclear(dateResult);

	//Set notice processor
	PQsetNoticeProcessor(d->connection, noticeCallBack, this);

	//Send the first notice
	this->sendNotice("SQL Driver Notice","CONNECT");


    return TRUE;
}

void QPGSQLDriver::close()
{
    if ( isOpen() ) {
	PQfinish( d->connection );
	d->connection = 0;
	setOpen( FALSE );
	setOpenError( FALSE );
    }
	this->sendNotice("SQL Driver Notice","CLOSE");
}

QSqlQuery QPGSQLDriver::createQuery() const
{
    return QSqlQuery( new QPGSQLResult( this, d ) );
}

bool QPGSQLDriver::beginTransaction()
{
    if ( !isOpen() ) {
		#ifdef QT_CHECK_RANGE
			qWarning( "QPGSQLDriver::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;
    }
    this->sendNotice("SQL Driver Notice","BEGIN TRANSACTION.");
    PQclear( res );
    return TRUE;
}

bool QPGSQLDriver::commitTransaction()
{
    if ( !isOpen() ) {
		#ifdef QT_CHECK_RANGE
			qWarning( "QPGSQLDriver::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;
    }
    this->sendNotice("SQL Driver Notice","COMMIT");
    PQclear( res );
    return TRUE;
}

bool QPGSQLDriver::rollbackTransaction()
{
    if ( !isOpen() ) {
		#ifdef QT_CHECK_RANGE
			qWarning( "QPGSQLDriver::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;
    }
    this->sendNotice("SQL Driver Notice","ROLLBACK.");    
    PQclear( res );
    return TRUE;
}

QStringList QPGSQLDriver::tables( const QString& /* user */ ) const
{
    QStringList tl;
    if ( !isOpen() )
		return tl;
    QSqlQuery t = createQuery();
    QString stmt;
    stmt = "select relname from pg_class where ( relkind = 'r' or relkind = 'v' ) "
		"and ( relname !~ '^Inv' ) "
		"and ( relname !~ '^pg_' ) ";
    t.exec( stmt );
    while ( t.isActive() && t.next() )
		tl.append( t.value(0).toString() );
    return tl;
}

QSqlIndex QPGSQLDriver::primaryIndex( const QString& tablename ) const
{
    QSqlIndex idx( tablename );
    if ( !isOpen() )
		return idx;
    QSqlQuery i = createQuery();
    QString stmt;

    switch( pro ) {
    	case QPGSQLDriver::Version7:
    	case QPGSQLDriver::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 pg_cl.relname = '%1_pkey' 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";
			break;
	    case QPGSQLDriver::Version72:
    	case QPGSQLDriver::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 pg_cl.relname = '%1_pkey' 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";
			break;
    }
    i.exec( stmt.arg( tablename ) );
    while ( i.isActive() && i.next() ) {
		QSqlField f( i.value(0).toString(), qDecodePGSQLType( i.value(1).toInt() ) );
		idx.append( f );
		idx.setName( i.value(2).toString() );
    }
    return idx;
}

QSqlRecord QPGSQLDriver::record( const QString& tablename ) const
{
    QSqlRecord fil;
    if ( !isOpen() )
		return fil;
    QString stmt;
    switch( pro ) {
    	case QPGSQLDriver::Version7:
    	case QPGSQLDriver::Version71:
			stmt = "select pg_attribute.attname, pg_attribute.atttypid::int "
				"from pg_class, pg_attribute "
				"where pg_class.relname = '%1' "
				"and pg_attribute.attnum > 0 "
				"and pg_attribute.attrelid = pg_class.oid ";
			break;
    	case QPGSQLDriver::Version72:
    	case QPGSQLDriver::Version73:
			stmt = "select pg_attribute.attname, pg_attribute.atttypid::int "
				"from pg_class, pg_attribute "
				"where pg_class.relname = '%1' "
				"and pg_attribute.attnum > 0 "
				"and pg_attribute.attrelid = pg_class.oid ";
		break;
    }

    QSqlQuery fi = createQuery();
    fi.exec( stmt.arg( tablename ) );
    while ( fi.next() ) {
		QSqlField f( fi.value(0).toString(), qDecodePGSQLType( fi.value(1).toInt() ) );
		fil.append( f );
    }
    return fil;
}

QSqlRecord QPGSQLDriver::record( const QSqlQuery& query ) const
{
    QSqlRecord fil;
    if ( !isOpen() )
		return fil;
    if ( query.isActive() && query.driver() == this ) {
		QPGSQLResult* result = (QPGSQLResult*)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 = qDecodePGSQLType( PQftype( result->d->result, i ) );
	    	QSqlField rf( name, type );
	    	fil.append( rf );
		}
    }
    return fil;
}

QSqlRecordInfo QPGSQLDriver::recordInfo( const QString& tablename ) const
{
    QSqlRecordInfo info;
    if ( !isOpen() )
		return info;

    QString stmt;
    switch( pro ) {
    	case QPGSQLDriver::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 pg_class.relname = '%1' "
				"and pg_attribute.attnum > 0 "
				"and pg_attribute.attrelid = pg_class.oid ";
			break;
	    case QPGSQLDriver::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 pg_class.relname = '%1' "
				"and pg_attribute.attnum > 0 "
				"and pg_attribute.attrelid = pg_class.oid ";
			break;
	    case QPGSQLDriver::Version72:
    	case QPGSQLDriver::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 pg_class.relname = '%1' "
				"and pg_attribute.attnum > 0 "
				"and pg_attribute.attrelid = pg_class.oid ";
			break;
    }

    QSqlQuery query = createQuery();
    query.exec( stmt.arg( tablename ) );
    if ( pro >= QPGSQLDriver::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(),
					qDecodePGSQLType( 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(),
					qDecodePGSQLType( query.value( 1 ).toInt() ),
					query.value( 2 ).toBool(),
					len,
					precision,
					defVal,
					query.value( 1 ).toInt() ) );
		}
    }

    return info;
}

QSqlRecordInfo QPGSQLDriver::recordInfo( const QSqlQuery& query ) const
{
	QSqlRecordInfo info;
	if ( !isOpen() )
		return info;
	if ( query.isActive() && query.driver() == this ) {
		QPGSQLResult* result = (QPGSQLResult*)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 ) {
			 if ( len == -1) {
                int nMod	= PQfmod( result->d->result, i );
                int nTypOid = PQftype( result->d->result, i );
				QString sPrec;
				sPrec.sprintf("SELECT format_type(%i,%i);",nTypOid,nMod);
				PGresult* TmpResult = PQexec( d->connection,  sPrec );
				QString sValFormat(PQgetvalue(TmpResult,0,0));
				PQclear(TmpResult);
				QRegExp rx("\\((\\S+)\\)");
				rx.search( sValFormat , 0);
				if (rx.cap(1)==QString::null){
					//Neuspješno formatiranje
	    			len = -1;
		    		precision = -1;
				}else{
					QString cPart(rx.cap(1));
					rx. setPattern("(\\d+)\\,");
					rx.search( cPart , 0);
					if (rx.cap(1)==QString::null){
						//Nema zareza - samo veličina
	    				len = cPart.toInt();
		    			precision = -1;
					}else{
						//Zarez - sa veličinom
						len = rx.cap(1).toInt();
						precision= cPart.mid(rx.cap(0).length()).toInt();
					}
                }
		    }
		    info.append( QSqlFieldInfo( name,
					qDecodePGSQLType( PQftype( result->d->result, i ) ),
					-1,
					len,
					precision,
					QVariant(),
					PQftype( result->d->result, i ) ) );
		}
    }
    return info;
}


QString QPGSQLDriver::formatValue( const QSqlField* field,  bool )
{
    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();
					r = "'" + QString::number(dt.year()) + "-" +
					  QString::number(dt.month()) + "-" +
					  QString::number(dt.day()) + " " +
					  tm.toString() + "." +
				  	QString::number(tm.msec()) + "'";
		    	} else {
					r = nullText();
	    		}
	    		break;
			case QVariant::String:
			case QVariant::CString: {
	    		// Escape '\' characters
			    //r = QSqlDriver::formatValue( field );
	    		//r.replace( QRegExp( "\\\\" ), "\\\\" );
				//size_t PQescapeString (char *to, const char *from, size_t length);

					//const char* strData = (const char*)field->value().asString();
					r = this->escapeString(field->value().toString());		
			    break;
				}
			case QVariant::Bool:
	    		if ( field->value().toBool() )
					r = "TRUE";
			    else
					r = "FALSE";
	    		break;
			case QVariant::ByteArray:{
				QByteArray val = field->value().toByteArray();
				size_t newLen= 0;
				size_t oldLen=val.size();
				unsigned char* valData = PQescapeBytea((unsigned char*)val.data(),
                                         oldLen,
                                         &newLen);
                r = QString((const char*)valData);
			    break;
				}
			default:
	    		r = QSqlDriver::formatValue( field );
			    break;
		}
    }
    return r;
}

int	QPGSQLDriver::backendPID(){
	return PQbackendPID(d->connection);
}


QString QPGSQLDriver::version(){
    switch( pro ) {
    case QPGSQLDriver::Version7:
	return "7.0";
    case QPGSQLDriver::Version71:
	return "7.1";
    case QPGSQLDriver::Version72:
	return "7.1";
    case QPGSQLDriver::Version73:
	return "7.3";
    }
    return "Unknown";
}

void QPGSQLDriver::addNoticeMonitor(QObject* mon, const char* member){
	FSqlNotify->connect( mon ,  member);
}

void QPGSQLDriver::removeNoticeMonitor(QObject* mon){
	FSqlNotify->disconnect(mon);
}


void QPGSQLDriver::sendNotice(const QString notType, const QString msg){
	FSqlNotify->setValue("#"+notType+"# " +msg);
	FSqlNotify->activate();
	
}

//Check if any NOTIFY messages came from server.
//If any message is pending send it through sendNotice mechanism
void QPGSQLDriver::checkSqlNotify(){
	PQconsumeInput(connection());
	pgNotify* noti = PQnotifies( connection() );
	while (noti !=NULL){
		sendNotice("SQL Driver Notify",QString(noti->relname));
		noti = PQnotifies( connection() );
	}
}


QByteArray QPGSQLDriver::readBlob(int oid){
	#ifdef DEBUG
		qDebug("QPGSQLDriver::readBlob(%i)", oid);
	#endif
	QByteArray ba;
	this->beginTransaction();
	int fd = lo_open( d->connection, oid, INV_READ );
#ifdef QT_CHECK_RANGE
	if ( fd < 0) {
		qWarning( "QPGSQLResult::readBlob: unable to open large object for read" );
		this->commitTransaction();
		return 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 );
		this->commitTransaction();
		return 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( "QPGSQLDriver::readBlob: unable to read large object" );
	} else {
		ba.duplicate( buf, size );
	}
	delete [] buf;
	lo_close( d->connection, fd );
	this->commitTransaction();
	return ba;
}

int QPGSQLDriver::createBlob(){
	return lo_creat(d->connection, INV_READ|INV_WRITE);
}

bool QPGSQLDriver::writeBlob(int oid, QByteArray blobData){
	this->beginTransaction();
	int fd = lo_open( d->connection, oid, INV_READ|INV_WRITE);
	if ( fd < 0) {
		qWarning( "QPGSQLDriver::writeBlob: unable to open large object for read" );
		this->rollbackTransaction();
		return FALSE;
	}

	size_t len = blobData.size();
	int nBytesWriten = lo_write(d->connection, fd, blobData.data(), len);
	if ( nBytesWriten < 0) {
		qWarning( "QPGSQLDriver::writeBlob: unable to write large object" );
		this->rollbackTransaction();
		return FALSE;
	}
	this->commitTransaction();
	return TRUE;
}

