|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** 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 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 "qsql_psql.h"
|
|
|
|
#include <private/qsqlextension_p.h>
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include <ntqpointarray.h>
|
|
|
|
#include <ntqsqlrecord.h>
|
|
|
|
#include <ntqregexp.h>
|
|
|
|
#include <ntqdatetime.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> *tqSqlDriverExtDict();
|
|
|
|
TQPtrDict<TQSqlOpenExtension> *tqSqlOpenExtDict();
|
|
|
|
|
|
|
|
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;
|
|
|
|
#ifdef ABSTIMEOID // PostgreSQL << 12.x
|
|
|
|
case ABSTIMEOID :
|
|
|
|
case RELTIMEOID :
|
|
|
|
#endif
|
|
|
|
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 ) ) {
|
|
|
|
tqWarning( "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") );
|
|
|
|
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) {
|
|
|
|
tqWarning( "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) {
|
|
|
|
tqWarning( "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
|
|
|
|
tqWarning("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 )
|
|
|
|
tqWarning( "%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
|
|
|
|
tqWarning( "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
|
|
|
|
tqWarning( "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()
|
|
|
|
{
|
|
|
|
tqSqlDriverExtDict()->insert( this, new TQPSQLDriverExtension(this) );
|
|
|
|
tqSqlOpenExtDict()->insert( this, new TQPSQLOpenExtension(this) );
|
|
|
|
|
|
|
|
d = new TQPSQLPrivate();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPSQLDriver::~TQPSQLDriver()
|
|
|
|
{
|
|
|
|
if ( d->connection )
|
|
|
|
PQfinish( d->connection );
|
|
|
|
delete d;
|
|
|
|
if ( !tqSqlDriverExtDict()->isEmpty() ) {
|
|
|
|
TQSqlDriverExtension *ext = tqSqlDriverExtDict()->take( this );
|
|
|
|
delete ext;
|
|
|
|
}
|
|
|
|
if ( !tqSqlOpenExtDict()->isEmpty() ) {
|
|
|
|
TQSqlOpenExtension *ext = tqSqlOpenExtDict()->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 )
|
|
|
|
{
|
|
|
|
tqWarning("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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TQPSQLDriver::ping()
|
|
|
|
{
|
|
|
|
if ( !isOpen() ) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PGresult *res = NULL;
|
|
|
|
|
|
|
|
// Send ping
|
|
|
|
res = PQexec( d->connection, "" );
|
|
|
|
PQclear(res);
|
|
|
|
|
|
|
|
// Check connection status
|
|
|
|
if ( PQstatus( d->connection ) != CONNECTION_OK ) {
|
|
|
|
PQreset( d->connection );
|
|
|
|
if ( PQstatus( d->connection ) != CONNECTION_OK ) {
|
|
|
|
setLastError( qMakeError("Unable to execute ping", TQSqlError::Statement, d ) );
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQSqlQuery TQPSQLDriver::createQuery() const
|
|
|
|
{
|
|
|
|
return TQSqlQuery( new TQPSQLResult( this, d ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TQPSQLDriver::beginTransaction()
|
|
|
|
{
|
|
|
|
if ( !isOpen() ) {
|
|
|
|
#ifdef QT_CHECK_RANGE
|
|
|
|
tqWarning( "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
|
|
|
|
tqWarning( "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
|
|
|
|
tqWarning( "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;
|
|
|
|
}
|