- Suppress deprecated QSqlQuery copy-constructor warning locally in NSqlQuery::operator= - Replace qChecksum overload with QByteArrayView - Use QTimeZone::utc(), QLibraryInfo::path(), check QTranslator::load return values - Replace deprecated operator+ for key combinations and other small Qt6 fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1335 lines
32 KiB
C++
Executable File
1335 lines
32 KiB
C++
Executable File
|
|
|
|
|
|
#include "qsql_informix.h"
|
|
|
|
|
|
#include <it.h>
|
|
#include <itcppop.h>
|
|
#include <sqltypes.h>
|
|
#include <sqlca.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
#include <QAtomicInt>
|
|
#include <QDateTime>
|
|
#include <QMutex>
|
|
#include <QSqlError>
|
|
#include <QSqlField>
|
|
#include <QSqlIndex>
|
|
#include <QSqlRecord>
|
|
#include <QSqlQuery>
|
|
#include <QStringList>
|
|
#include <QTextCodec>
|
|
#include <QTimer>
|
|
#include <QVariant>
|
|
#include <QMetaType>
|
|
|
|
|
|
|
|
|
|
#define DEBUG_FUNCTION
|
|
|
|
#ifdef Q_CC_MSVC
|
|
float truncf(float in)
|
|
{
|
|
return in < 0 ? in + 0.5 : in - 0.5;
|
|
}
|
|
#endif
|
|
|
|
Q_DECLARE_METATYPE(ITConnection *)
|
|
Q_DECLARE_METATYPE(ITSet *)
|
|
|
|
|
|
|
|
|
|
class QInformixDriverPrivate
|
|
{
|
|
public:
|
|
QInformixDriverPrivate(QInformixDriver *parent)
|
|
: connection(0)
|
|
, driver(parent)
|
|
, dbLocale(0)
|
|
, openCount(0)
|
|
, connTimeout(-1)
|
|
, connectOnFirstUse(false)
|
|
{
|
|
connectionTimeoutTimer.setSingleShot(true);
|
|
QObject::connect(&connectionTimeoutTimer, SIGNAL(timeout()),
|
|
driver, SLOT(timeout()));
|
|
}
|
|
|
|
ITConnection *connection;
|
|
QInformixDriver *driver;
|
|
QTextCodec *dbLocale;
|
|
ITDBInfo conn_params;
|
|
QMutex connCountMutex;
|
|
QAtomicInt openCount;
|
|
int connTimeout;
|
|
bool connectOnFirstUse;
|
|
QTimer connectionTimeoutTimer;
|
|
|
|
inline ITString toInformixEncoding(const QString &str) const
|
|
{
|
|
return ITString(dbLocale->fromUnicode(str).constData(), true);
|
|
}
|
|
|
|
inline QByteArray toInformixEncodingBA(const QString &str) const
|
|
{
|
|
return dbLocale->fromUnicode(str).constData();
|
|
}
|
|
|
|
inline QString fromInformixEncoding(const ITString &str) const
|
|
{
|
|
return dbLocale->toUnicode(str.Data()).trimmed();
|
|
}
|
|
|
|
inline QString fromInformixEncoding(const QByteArray &str) const
|
|
{
|
|
return dbLocale->toUnicode(str);
|
|
}
|
|
|
|
void addConnection()
|
|
{
|
|
QMutexLocker l(&connCountMutex);
|
|
checkOpen();
|
|
openCount.ref();
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p %s opencount: %d\n", this, __PRETTY_FUNCTION__, (int)openCount);
|
|
#endif
|
|
}
|
|
|
|
void removeConnection()
|
|
{
|
|
QMutexLocker l(&connCountMutex);
|
|
if (openCount.deref() == false && connTimeout >= 0)
|
|
{
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p singleShot timeout: %d, driver: %p\n", this, connTimeout, driver);
|
|
#endif
|
|
connectionTimeoutTimer.start(connTimeout);
|
|
}
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p %s opencount: %d\n", this, __PRETTY_FUNCTION__, (int)openCount);
|
|
#endif
|
|
}
|
|
|
|
void closeConnection();
|
|
|
|
void checkOpen()
|
|
{
|
|
if (!connection)
|
|
{
|
|
if (connectOnFirstUse)
|
|
{
|
|
connectOnFirstUse = false;
|
|
if (!driver->openDatabaseInternal())
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
if (!connection->IsOpen())
|
|
connection->Open(conn_params);
|
|
}
|
|
};
|
|
|
|
void QInformixDriverPrivate::closeConnection()
|
|
{
|
|
QMutexLocker l(&connCountMutex);
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p %s\n", this, __PRETTY_FUNCTION__);
|
|
#endif
|
|
if (!driver->isOpen())
|
|
return;
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p opencount: %d\n", this, (int)openCount);
|
|
#endif
|
|
if (openCount > 0)
|
|
return;
|
|
connection->Close();
|
|
}
|
|
|
|
static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type,
|
|
const QInformixDriverPrivate *p)
|
|
{
|
|
const char *e = p->connection->ErrorText();
|
|
QString msg = QStringLiteral("IFX Error: ") + QLatin1String(e ? e : "<UNKNOWN>");
|
|
#ifdef DEBUG_QSQL_INFORMIX
|
|
fprintf(stderr, "qMakeError: %s\n", qPrintable(msg));
|
|
#endif
|
|
const struct sqlca_s *my_sqlca = &sqlca;
|
|
return QSqlError(QStringLiteral("QInformix: %1").arg(err), msg, type, my_sqlca->sqlcode);
|
|
}
|
|
|
|
static QVariant::Type qSqlType2Variant(int t)
|
|
{
|
|
t &= SQLTYPE;
|
|
switch (t)
|
|
{
|
|
case SQLSMINT:
|
|
case SQLINT:
|
|
case SQLINTERVAL:
|
|
case SQLSERIAL:
|
|
return QVariant::Int;
|
|
case SQLINT8:
|
|
case SQLSERIAL8:
|
|
return QVariant::LongLong;
|
|
case SQLFLOAT:
|
|
case SQLSMFLOAT:
|
|
case SQLDECIMAL:
|
|
case SQLMONEY:
|
|
return QVariant::Double;
|
|
case SQLDATE:
|
|
return QVariant::Date;
|
|
case SQLDTIME:
|
|
return QVariant::DateTime;
|
|
case SQLBYTES:
|
|
return QVariant::ByteArray;
|
|
case SQLCHAR:
|
|
case SQLVCHAR:
|
|
case SQLNCHAR:
|
|
case SQLNVCHAR:
|
|
case SQLLVARCHAR:
|
|
case SQLTEXT:
|
|
return QVariant::String;
|
|
default:
|
|
break;
|
|
}
|
|
qWarning("Unknown sql data type %d", t);
|
|
return QVariant::Invalid;
|
|
}
|
|
|
|
static int qIfxType2SqlType(const char *typeName)
|
|
{
|
|
switch (typeName[0])
|
|
{
|
|
case 'b':
|
|
if (strcmp(typeName, "byte") == 0)
|
|
return SQLBYTES;
|
|
break;
|
|
case 'c':
|
|
if (strcmp(typeName, "clob") == 0)
|
|
return SQLBYTES;
|
|
if (strcmp(typeName, "char") == 0)
|
|
return SQLCHAR;
|
|
break;
|
|
case 'd':
|
|
if (strcmp(typeName, "date") == 0)
|
|
return SQLDATE;
|
|
if (strcmp(typeName, "datetime") == 0)
|
|
return SQLDTIME;
|
|
if (strcmp(typeName, "decimal") == 0)
|
|
return SQLDECIMAL;
|
|
break;
|
|
case 'f':
|
|
if (strcmp(typeName, "float") == 0)
|
|
return SQLFLOAT;
|
|
break;
|
|
case 'i':
|
|
if (strcmp(typeName, "integer") == 0)
|
|
return SQLINT;
|
|
if (strcmp(typeName, "int8") == 0)
|
|
return SQLINT8;
|
|
break;
|
|
case 'l':
|
|
if (strcmp(typeName, "lvarchar") == 0)
|
|
return SQLLVARCHAR;
|
|
break;
|
|
case 'n':
|
|
if (strcmp(typeName, "nchar") == 0)
|
|
return SQLNCHAR;
|
|
if (strcmp(typeName, "nvarchar") == 0)
|
|
return SQLNVCHAR;
|
|
break;
|
|
case 's':
|
|
if (strcmp(typeName, "serial") == 0)
|
|
return SQLSERIAL;
|
|
if (strcmp(typeName, "serial8") == 0)
|
|
return SQLSERIAL8;
|
|
if (strcmp(typeName, "smallint") == 0)
|
|
return SQLSMINT;
|
|
if (strcmp(typeName, "smallfloat") == 0)
|
|
return SQLFLOAT;
|
|
break;
|
|
case 't':
|
|
if (strcmp(typeName, "text") == 0)
|
|
return SQLTEXT;
|
|
break;
|
|
case 'v':
|
|
if (strcmp(typeName, "varchar") == 0)
|
|
return SQLVCHAR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
qWarning("Unknown informix data type '%s'", typeName);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static inline qint64 getLastSerial(ITConnection *itconn)
|
|
{
|
|
MI_CONNECTION *conn = itconn->CheckOutConn();
|
|
|
|
mi_integer lastSerial;
|
|
mi_last_serial(conn, &lastSerial);
|
|
if (lastSerial != 0)
|
|
{
|
|
itconn->CheckInConn();
|
|
return lastSerial;
|
|
}
|
|
mi_int8 lastSerial8;
|
|
mi_last_serial8(conn, &lastSerial8);
|
|
itconn->CheckInConn();
|
|
const quint64 low = static_cast<quint64>(lastSerial8.data[0]);
|
|
const quint64 high = static_cast<quint64>(lastSerial8.data[1]);
|
|
qint64 ret = (high << 32) + low;
|
|
return lastSerial8.sign == -1 ? -ret : ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
class QInformixResultPrivate
|
|
{
|
|
public:
|
|
QInformixResultPrivate(QInformixResult *qq)
|
|
: q(qq)
|
|
, driverPrivate(NULL)
|
|
, query(NULL)
|
|
, result(NULL)
|
|
, row(NULL)
|
|
, preparedStmt(NULL)
|
|
, preparedCursor(NULL)
|
|
, currentSize(-1)
|
|
, numAffectedRows(-1)
|
|
, lastInsertId(-1)
|
|
{}
|
|
|
|
QInformixResult *q;
|
|
QInformixDriverPrivate *driverPrivate;
|
|
|
|
ITQuery *query;
|
|
|
|
ITSet *result;
|
|
|
|
ITRow *row;
|
|
|
|
|
|
ITStatement *preparedStmt;
|
|
|
|
ITCursor *preparedCursor;
|
|
|
|
int currentSize;
|
|
int numAffectedRows;
|
|
qint64 lastInsertId;
|
|
|
|
inline ITConnection *getConnection() const
|
|
{
|
|
return driverPrivate->connection;
|
|
}
|
|
void cleanupInternal()
|
|
{
|
|
q->cleanup();
|
|
if (preparedStmt)
|
|
{
|
|
preparedStmt->Drop();
|
|
delete preparedStmt;
|
|
preparedStmt = NULL;
|
|
}
|
|
if (preparedCursor)
|
|
{
|
|
preparedCursor->Drop();
|
|
preparedCursor->Close();
|
|
delete preparedCursor;
|
|
preparedCursor = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
QInformixResult::QInformixResult(const QInformixDriver *db, QInformixDriverPrivate *p)
|
|
: QSqlResult(db)
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
p->addConnection();
|
|
d = new QInformixResultPrivate(this);
|
|
d->driverPrivate = p;
|
|
}
|
|
|
|
QInformixResult::~QInformixResult()
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
d->cleanupInternal();
|
|
d->driverPrivate->removeConnection();
|
|
delete d;
|
|
}
|
|
|
|
QVariant QInformixResult::handle() const
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
return qVariantFromValue(d->result);
|
|
}
|
|
|
|
void QInformixResult::cleanup()
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
if (d->query)
|
|
{
|
|
d->query->Finish();
|
|
delete d->query;
|
|
d->query = NULL;
|
|
}
|
|
if (d->result)
|
|
{
|
|
d->result->Close();
|
|
d->result->Release();
|
|
d->result = NULL;
|
|
}
|
|
if (d->row)
|
|
{
|
|
d->row->Release();
|
|
d->row = NULL;
|
|
}
|
|
|
|
setActive(false);
|
|
}
|
|
|
|
bool QInformixResult::fetch(int i)
|
|
{
|
|
|
|
|
|
if (!isActive())
|
|
return false;
|
|
if (i < 0)
|
|
return false;
|
|
if (at() == i)
|
|
return true;
|
|
|
|
if (d->row)
|
|
{
|
|
d->row->Release();
|
|
d->row = NULL;
|
|
}
|
|
|
|
if (d->result)
|
|
{
|
|
if (i >= d->currentSize)
|
|
return false;
|
|
|
|
ITValue *value = d->result->Fetch(NULL, ITPositionAbsolute, i + 1);
|
|
|
|
if (!value)
|
|
{
|
|
qDebug("QInformixResult::fetch(%d): Fetch failed\n", i);
|
|
return false;
|
|
}
|
|
|
|
int ret = value->QueryInterface(ITRowIID, (void **) & d->row);
|
|
if (ret == IT_QUERYINTERFACE_FAILED)
|
|
{
|
|
value->Release();
|
|
return false;
|
|
}
|
|
value->Release();
|
|
}
|
|
else
|
|
{
|
|
if (!d->preparedCursor)
|
|
return false;
|
|
|
|
if (isForwardOnly())
|
|
d->row = d->preparedCursor->NextRow();
|
|
else
|
|
d->row = d->preparedCursor->NextRow(NULL, ITPositionAbsolute, i + 1);
|
|
if (!d->row)
|
|
return false;
|
|
}
|
|
|
|
setAt(i);
|
|
return true;
|
|
}
|
|
|
|
bool QInformixResult::fetchFirst()
|
|
{
|
|
return fetch(0);
|
|
}
|
|
|
|
bool QInformixResult::fetchLast()
|
|
{
|
|
return fetch(d->currentSize - 1);
|
|
}
|
|
|
|
QVariant QInformixResult::data(int i)
|
|
{
|
|
|
|
|
|
if (!d->row)
|
|
return QVariant();
|
|
|
|
if (i >= d->row->NumColumns())
|
|
{
|
|
qWarning("QInformixResult::data: column %d out of range", i);
|
|
return QVariant();
|
|
}
|
|
|
|
ITValue *col = d->row->Column(i);
|
|
if (!col)
|
|
{
|
|
qWarning("QInformixResult::data: column %d out of range", i);
|
|
return QVariant();
|
|
}
|
|
|
|
const int sqlType = qIfxType2SqlType(col->TypeOf().Name());
|
|
if (col->IsNull())
|
|
{
|
|
col->Release();
|
|
return QVariant(qSqlType2Variant(sqlType));
|
|
}
|
|
|
|
|
|
|
|
QVariant variant;
|
|
switch (sqlType)
|
|
{
|
|
case SQLBYTES:
|
|
{
|
|
ITDatum *lobj = NULL;
|
|
col->QueryInterface(ITDatumIID, (void **) &lobj);
|
|
variant = QByteArray((const char *) lobj->Data(), lobj->DataLength());
|
|
lobj->Release();
|
|
break;
|
|
}
|
|
case SQLTEXT:
|
|
{
|
|
ITDatum *lobj = NULL;
|
|
col->QueryInterface(ITDatumIID, (void **) &lobj);
|
|
const QByteArray ba = QByteArray((const char *) lobj->Data());
|
|
variant = d->driverPrivate->fromInformixEncoding(ba);
|
|
lobj->Release();
|
|
break;
|
|
}
|
|
case SQLCHAR:
|
|
case SQLNCHAR:
|
|
case SQLNVCHAR:
|
|
case SQLVCHAR:
|
|
case SQLLVARCHAR:
|
|
variant = QVariant(d->driverPrivate->fromInformixEncoding(col->Printable()));
|
|
break;
|
|
case SQLDATE:
|
|
{
|
|
ITDateTime *dt = NULL;
|
|
col->QueryInterface(ITDateTimeIID, (void **) &dt);
|
|
variant = QVariant(QDate(dt->Year(), dt->Month(), dt->Day()));
|
|
dt->Release();
|
|
break;
|
|
}
|
|
case SQLDTIME:
|
|
{
|
|
ITDateTime *dt = NULL;
|
|
col->QueryInterface(ITDateTimeIID, (void **) &dt);
|
|
const QDate date(QDate(dt->Year(), dt->Month(), dt->Day()));
|
|
const int seconds = (int) truncf(dt->Second());
|
|
const int msecs = (int) truncf((dt->Second() - seconds) * 1000);
|
|
const QTime time(QTime(dt->Hour(), dt->Minute(), seconds, msecs));
|
|
variant = QVariant(QDateTime(date, time));
|
|
dt->Release();
|
|
break;
|
|
}
|
|
case SQLDECIMAL:
|
|
case SQLFLOAT:
|
|
{
|
|
ITConversions *conv = NULL;
|
|
col->QueryInterface(ITConversionsIID, (void **) &conv);
|
|
double dbl;
|
|
conv->ConvertTo(dbl);
|
|
variant = QVariant(dbl);
|
|
conv->Release();
|
|
break;
|
|
}
|
|
case SQLSERIAL:
|
|
case SQLINT:
|
|
{
|
|
ITConversions *conv = NULL;
|
|
col->QueryInterface(ITConversionsIID, (void **) &conv);
|
|
int int32;
|
|
conv->ConvertTo(int32);
|
|
variant = QVariant(int32);
|
|
conv->Release();
|
|
break;
|
|
}
|
|
case SQLSERIAL8:
|
|
case SQLINT8:
|
|
{
|
|
ITConversions *conv = NULL;
|
|
col->QueryInterface(ITConversionsIID, (void **) &conv);
|
|
long int int64;
|
|
conv->ConvertTo(int64);
|
|
variant = QVariant((qint64) int64);
|
|
conv->Release();
|
|
break;
|
|
}
|
|
case SQLSMINT:
|
|
{
|
|
ITConversions *conv = NULL;
|
|
col->QueryInterface(ITConversionsIID, (void **) &conv);
|
|
short int int16;
|
|
conv->ConvertTo(int16);
|
|
variant = QVariant(int16);
|
|
conv->Release();
|
|
break;
|
|
}
|
|
default:
|
|
variant = QVariant(qSqlType2Variant(sqlType));
|
|
qWarning("QInformixResult::data: Unknown sql data type %d at row %d\n", sqlType, i);
|
|
break;
|
|
}
|
|
col->Release();
|
|
|
|
return variant;
|
|
}
|
|
|
|
bool QInformixResult::isNull(int i)
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
if (!d->row)
|
|
return true;
|
|
|
|
if (i >= d->row->NumColumns())
|
|
{
|
|
qWarning("QInformixResult::isNull: column %d out of range", i);
|
|
return true;
|
|
}
|
|
|
|
ITValue *col = d->row->Column(i);
|
|
if (!col)
|
|
{
|
|
qWarning("QInformixResult::isNull: column %d out of range", i);
|
|
return false;
|
|
}
|
|
return col->IsNull();
|
|
}
|
|
|
|
bool QInformixResult::reset(const QString &query)
|
|
{
|
|
#ifdef DEBUG_QSQL_INFORMIX
|
|
fprintf(stderr, " QInformixResult::reset(%s)\n", qPrintable(query));
|
|
#endif
|
|
|
|
d->cleanupInternal();
|
|
|
|
if (!driver())
|
|
return false;
|
|
if (!driver()->isOpen() || driver()->isOpenError())
|
|
return false;
|
|
|
|
d->currentSize = -1;
|
|
d->numAffectedRows = -1;
|
|
d->lastInsertId = -1;
|
|
setSelect(false);
|
|
|
|
d->query = new ITQuery(*d->getConnection());
|
|
bool bIsSelect = query.trimmed().startsWith(QLatin1String("SELECT"), Qt::CaseInsensitive);
|
|
|
|
try
|
|
{
|
|
if (bIsSelect)
|
|
{
|
|
d->result = d->query->ExecToSet(d->driverPrivate->toInformixEncoding(query));
|
|
|
|
if (!d->result || !d->result->Open())
|
|
throw false;
|
|
|
|
}
|
|
else
|
|
{
|
|
bool bOk = d->query->ExecForStatus(d->driverPrivate->toInformixEncoding(query));
|
|
if (!bOk)
|
|
throw false;
|
|
}
|
|
|
|
if (d->query->Error())
|
|
throw false;
|
|
|
|
}
|
|
catch (bool bOk)
|
|
{
|
|
if (!bOk)
|
|
{
|
|
setLastError(qMakeError(QObject::tr("Unable to create query"),
|
|
QSqlError::StatementError, d->driverPrivate));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setSelect(bIsSelect);
|
|
setActive(true);
|
|
if (bIsSelect)
|
|
d->currentSize = d->query->RowCount();
|
|
else
|
|
d->numAffectedRows = d->query->RowCount();
|
|
d->lastInsertId = getLastSerial(d->getConnection());
|
|
|
|
return true;
|
|
}
|
|
|
|
int QInformixResult::size()
|
|
{
|
|
return d->currentSize;
|
|
}
|
|
|
|
int QInformixResult::numRowsAffected()
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
return d->numAffectedRows;
|
|
}
|
|
|
|
QVariant QInformixResult::lastInsertId() const
|
|
{
|
|
return d->lastInsertId ? QVariant(d->lastInsertId) : QVariant();
|
|
}
|
|
|
|
QSqlRecord QInformixResult::record() const
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
QSqlRecord info;
|
|
if (!isActive() || !isSelect())
|
|
return info;
|
|
|
|
if (!d->query && !d->preparedCursor)
|
|
return info;
|
|
|
|
const ITTypeInfo *rowInfo = d->query ? d->query->RowType() : d->preparedCursor->RowType();
|
|
const int count = rowInfo->ColumnCount();
|
|
QSqlField f;
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
const ITTypeInfo *colInfo = rowInfo->ColumnType(i);
|
|
const char *typeName = colInfo->Name();
|
|
const int len = colInfo->Size();
|
|
const int prec = colInfo->Precision();
|
|
const int sqlType = qIfxType2SqlType(typeName);
|
|
|
|
f.setName(d->driverPrivate->fromInformixEncoding(rowInfo->ColumnName(i)));
|
|
f.setLength(len == -1 ? prec : len);
|
|
f.setPrecision(len == -1 ? len : prec);
|
|
f.setType(qSqlType2Variant(sqlType));
|
|
f.setSqlType(sqlType);
|
|
|
|
if (qSqlType2Variant(sqlType) == QVariant::Invalid)
|
|
qWarning("QInformixResult::record(): Unknown datatype %s", typeName);
|
|
|
|
info.append(f);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
|
|
|
|
inline QString replaceAt(const QString &str)
|
|
{
|
|
QString ret(str);
|
|
QChar *data = ret.data();
|
|
const int len = str.length();
|
|
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
if (data[i] == QLatin1Char('@'))
|
|
data[i] = QLatin1Char(':');
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool QInformixResult::prepare(const QString &_query)
|
|
{
|
|
QString query = replaceAt(_query.trimmed());
|
|
#ifdef DEBUG_QSQL_INFORMIX
|
|
fprintf(stderr, "QInformixResult::prepare(%s)\n", qPrintable(query));
|
|
#endif
|
|
|
|
d->cleanupInternal();
|
|
|
|
bool bIsSelect = query.startsWith(QLatin1String("SELECT"), Qt::CaseInsensitive);
|
|
setSelect(bIsSelect);
|
|
if (bIsSelect)
|
|
{
|
|
d->preparedCursor = new ITCursor(*d->getConnection());
|
|
|
|
if (!d->preparedCursor->Prepare(d->driverPrivate->toInformixEncoding(query)))
|
|
{
|
|
setLastError(qMakeError(QObject::tr("Unable to prepare statement"),
|
|
QSqlError::StatementError, d->driverPrivate));
|
|
delete d->preparedCursor;
|
|
d->preparedCursor = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
d->preparedStmt = new ITStatement(*d->getConnection());
|
|
|
|
if (!d->preparedStmt->Prepare(d->driverPrivate->toInformixEncoding(query)))
|
|
{
|
|
setLastError(qMakeError(QObject::tr("Unable to prepare statement"),
|
|
QSqlError::StatementError, d->driverPrivate));
|
|
delete d->preparedStmt;
|
|
d->preparedStmt = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
setActive(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class T>
|
|
bool bindValues(T *prep, const QVector<QVariant> &boundValues, const QInformixResultPrivate *p, const QSqlDriver *driver)
|
|
{
|
|
const int cnt = boundValues.count();
|
|
|
|
if (cnt != prep->NumParams())
|
|
return false;
|
|
|
|
QSqlField f;
|
|
for (int i = 0; i < cnt; ++i)
|
|
{
|
|
const QVariant &val = boundValues.at(i);
|
|
ITValue *param = prep->Param(i);
|
|
|
|
if (val.metaType().id() == QMetaType::QByteArray)
|
|
{
|
|
ITDatum *lobj;
|
|
|
|
ITTypeInfo ti(*p->getConnection(), "byte");
|
|
|
|
ITMVDesc desc;
|
|
desc.vf_origtypeinfo = &ti;
|
|
desc.vf_connection = p->getConnection();
|
|
desc.vf_typedesc = NULL;
|
|
desc.vf_preservedata = NULL;
|
|
desc.vf_outerunknown = NULL;
|
|
desc.vf_datalength = ti.Size();
|
|
desc.vf_libmivaluetype = MI_NULL_VALUE;
|
|
desc.vf_data = NULL;
|
|
ITValue *v = ITFactoryList::DatumToValue(desc);
|
|
v->QueryInterface(ITDatumIID, (void **) & lobj);
|
|
|
|
QByteArray ba = val.toByteArray();
|
|
if (ba.size())
|
|
lobj->SetData(ba.data(), ba.size());
|
|
else
|
|
lobj->SetNull();
|
|
prep->SetParam(i, lobj);
|
|
v->Release();
|
|
lobj->Release();
|
|
|
|
}
|
|
else
|
|
{
|
|
const char *t = param->TypeOf().Name();
|
|
if (strcmp(t, "text") == 0)
|
|
{
|
|
ITDatum *lobj;
|
|
|
|
|
|
|
|
ITTypeInfo ti(*p->getConnection(), "byte");
|
|
|
|
ITMVDesc desc;
|
|
desc.vf_origtypeinfo = &ti;
|
|
desc.vf_connection = p->getConnection();
|
|
desc.vf_typedesc = NULL;
|
|
desc.vf_preservedata = NULL;
|
|
desc.vf_outerunknown = NULL;
|
|
desc.vf_datalength = ti.Size();
|
|
desc.vf_libmivaluetype = MI_NULL_VALUE;
|
|
desc.vf_data = NULL;
|
|
ITValue *v = ITFactoryList::DatumToValue(desc);
|
|
v->QueryInterface(ITDatumIID, (void **) & lobj);
|
|
|
|
QByteArray ba = p->driverPrivate->toInformixEncodingBA(val.toString());
|
|
if (ba.size())
|
|
lobj->SetData(ba.data(), ba.size());
|
|
else
|
|
lobj->SetNull();
|
|
lobj->FromPrintable(ba.data());
|
|
prep->SetParam(i, lobj);
|
|
v->Release();
|
|
lobj->Release();
|
|
|
|
}
|
|
else
|
|
{
|
|
// setValue() already sets the type appropriately
|
|
f.setValue(val);
|
|
if (!param->FromPrintable(p->driverPrivate->toInformixEncoding(driver->formatValue(f))))
|
|
return false;
|
|
}
|
|
}
|
|
param->Release();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QInformixResult::exec()
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
if (isSelect())
|
|
{
|
|
if (d->query)
|
|
return false;
|
|
if (!d->preparedCursor)
|
|
return false;
|
|
|
|
cleanup();
|
|
|
|
setActive(true);
|
|
|
|
const int openMode = isForwardOnly() ? ITCursor::ReadOnly : ITCursor::ReadOnly + ITCursor::Scrollable;
|
|
if (!bindValues(d->preparedCursor, boundValues(), d, driver()) ||
|
|
!d->preparedCursor->Open(openMode))
|
|
{
|
|
setLastError(qMakeError(QObject::tr("Unable to execute statement"),
|
|
QSqlError::StatementError, d->driverPrivate));
|
|
d->preparedCursor->Drop();
|
|
d->preparedCursor->Close();
|
|
delete d->preparedCursor;
|
|
d->preparedCursor = NULL;
|
|
return false;
|
|
}
|
|
d->lastInsertId = -1;
|
|
d->numAffectedRows = -1;
|
|
d->currentSize = -1;
|
|
}
|
|
else
|
|
{
|
|
if (!d->preparedStmt)
|
|
return false;
|
|
|
|
cleanup();
|
|
|
|
setActive(true);
|
|
|
|
if (!bindValues(d->preparedStmt, boundValues(), d, driver()) ||
|
|
!d->preparedStmt->Exec())
|
|
{
|
|
setLastError(qMakeError(QObject::tr("Unable to execute statement"),
|
|
QSqlError::StatementError, d->driverPrivate));
|
|
d->preparedStmt->Drop();
|
|
delete d->preparedStmt;
|
|
d->preparedStmt = NULL;
|
|
return false;
|
|
}
|
|
d->currentSize = -1;
|
|
d->lastInsertId = getLastSerial(d->getConnection());
|
|
d->numAffectedRows = d->preparedStmt->RowCount();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
QInformixDriver::QInformixDriver(QObject *parent)
|
|
: QSqlDriver(parent)
|
|
{
|
|
init();
|
|
}
|
|
|
|
void QInformixDriver::init()
|
|
{
|
|
d = new QInformixDriverPrivate(this);
|
|
}
|
|
|
|
QInformixDriver::~QInformixDriver()
|
|
{
|
|
if (d->connection)
|
|
close();
|
|
delete d;
|
|
}
|
|
|
|
QVariant QInformixDriver::handle() const
|
|
{
|
|
return qVariantFromValue(d->connection);
|
|
}
|
|
|
|
bool QInformixDriver::hasFeature(DriverFeature f) const
|
|
{
|
|
switch (f)
|
|
{
|
|
case LastInsertId:
|
|
case Transactions:
|
|
case QuerySize:
|
|
case PreparedQueries:
|
|
return true;
|
|
case LowPrecisionNumbers:
|
|
case EventNotifications:
|
|
case SimpleLocking:
|
|
case FinishQuery:
|
|
case MultipleResultSets:
|
|
case PositionalPlaceholders:
|
|
case BatchOperations:
|
|
case NamedPlaceholders:
|
|
case CancelQuery:
|
|
return false;
|
|
case BLOB:
|
|
case Unicode:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QInformixDriver::open(const QString &db,
|
|
const QString &user,
|
|
const QString &password,
|
|
const QString &host,
|
|
int port,
|
|
const QString &connOpts)
|
|
{
|
|
Q_UNUSED(port)
|
|
const QVector<QStringRef> options = connOpts.splitRef(QLatin1Char(';'));
|
|
QString dbLocale = QStringLiteral("UTF-8");
|
|
|
|
d->connTimeout = -1;
|
|
d->connectOnFirstUse = false;
|
|
for (const QStringRef &opt : options)
|
|
{
|
|
if (opt.startsWith(QLatin1String("DB_LOCALE=")))
|
|
dbLocale = opt.mid(strlen("DB_LOCALE=")).toString();
|
|
if (opt.startsWith(QLatin1String("CONN_TIMEOUT=")))
|
|
{
|
|
bool bOk;
|
|
d->connTimeout = opt.mid(strlen("CONN_TIMEOUT=")).toInt(&bOk);
|
|
if (bOk && d->connTimeout > 0)
|
|
{
|
|
d->connTimeout *= 1000;
|
|
QTimer::singleShot(d->connTimeout, this, SLOT(timeout()));
|
|
}
|
|
else
|
|
d->connTimeout = -1;
|
|
}
|
|
if (opt.startsWith(QLatin1String("CONNECT_ON_FIRST_USE=")))
|
|
{
|
|
bool bOk;
|
|
int val = opt.mid(strlen("CONNECT_ON_FIRST_USE=")).toInt(&bOk);
|
|
if (bOk)
|
|
d->connectOnFirstUse = (val != 0);
|
|
else
|
|
d->connectOnFirstUse = false;
|
|
}
|
|
}
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p %s connTimeout: %d\n", this, __PRETTY_FUNCTION__, d->connTimeout);
|
|
#endif
|
|
d->dbLocale = QTextCodec::codecForName(dbLocale.toUtf8());
|
|
if (!d->dbLocale)
|
|
{
|
|
setLastError(qMakeError(tr("Can't create Codec for Name %1").arg(dbLocale), QSqlError::ConnectionError, d));
|
|
setOpenError(true);
|
|
return false;
|
|
}
|
|
|
|
if (isOpen())
|
|
close();
|
|
|
|
if (!host.isEmpty())
|
|
d->conn_params.SetSystem(d->toInformixEncoding(host));
|
|
if (!db.isEmpty())
|
|
d->conn_params.SetDatabase(d->toInformixEncoding(db));
|
|
if (!user.isEmpty())
|
|
d->conn_params.SetUser(d->toInformixEncoding(user));
|
|
if (!password.isEmpty())
|
|
d->conn_params.SetPassword(d->toInformixEncoding(password));
|
|
d->conn_params.Freeze();
|
|
|
|
d->connection = new ITConnection();
|
|
|
|
if (d->connectOnFirstUse)
|
|
return true;
|
|
|
|
return openDatabaseInternal();
|
|
}
|
|
|
|
bool QInformixDriver::openDatabaseInternal()
|
|
{
|
|
int ret = d->connection->Open(d->conn_params);
|
|
|
|
if (!ret || !d->connection->IsOpen())
|
|
{
|
|
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
|
|
setOpenError(true);
|
|
d->connection->Close();
|
|
delete d->connection;
|
|
d->connection = 0;
|
|
return false;
|
|
}
|
|
|
|
setOpen(true);
|
|
setOpenError(false);
|
|
return true;
|
|
}
|
|
|
|
void QInformixDriver::close()
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
if (isOpen())
|
|
{
|
|
d->connection->Close();
|
|
setOpen(false);
|
|
setOpenError(false);
|
|
}
|
|
delete d->connection;
|
|
d->connection = 0;
|
|
}
|
|
|
|
bool QInformixDriver::isOpen() const
|
|
{
|
|
return (d->connection);
|
|
|
|
}
|
|
|
|
QSqlResult *QInformixDriver::createResult() const
|
|
{
|
|
return new QInformixResult(this, d);
|
|
}
|
|
|
|
QStringList QInformixDriver::tables(QSql::TableType type) const
|
|
{
|
|
DEBUG_FUNCTION
|
|
|
|
QStringList tl;
|
|
if (!isOpen())
|
|
return tl;
|
|
QSqlQuery t(createResult());
|
|
t.setForwardOnly(true);
|
|
|
|
if (type == 0)
|
|
type = QSql::Tables;
|
|
QString cmd;
|
|
if ((type & (QSql::SystemTables | QSql::Tables)) == (QSql::SystemTables | QSql::Tables))
|
|
cmd = QStringLiteral("select tabname from systables where tabid >= 0");
|
|
else if ((type & QSql::SystemTables) == QSql::SystemTables)
|
|
cmd = QStringLiteral("select tabname from systables where tabid < 100");
|
|
else
|
|
cmd = QStringLiteral("select tabname from systables where tabid >= 100");
|
|
|
|
if ((type & (QSql::Tables | QSql::Views)) == (QSql::Tables | QSql::Views))
|
|
cmd += QStringLiteral(" and (tabtype = 'T' or tabtype = 'V')");
|
|
else if ((type & QSql::Tables) == QSql::Tables)
|
|
cmd += QStringLiteral(" and tabtype = 'T'");
|
|
else if ((type & QSql::Views) == QSql::Views)
|
|
cmd += QStringLiteral(" and tabtype = 'V'");
|
|
cmd += QStringLiteral(" order by tabid");
|
|
|
|
t.exec(cmd);
|
|
while (t.next())
|
|
tl.append(t.value(0).toString());
|
|
|
|
return tl;
|
|
}
|
|
|
|
static void qSplitTableName(QString &tablename, QString &schema)
|
|
{
|
|
int dot = tablename.indexOf(QLatin1Char('.'));
|
|
if (dot == -1)
|
|
return;
|
|
schema = tablename.left(dot);
|
|
tablename = tablename.mid(dot + 1);
|
|
}
|
|
|
|
QSqlIndex QInformixDriver::primaryIndex(const QString &tablename) const
|
|
{
|
|
QSqlIndex idx(tablename);
|
|
if (!isOpen())
|
|
return idx;
|
|
QSqlQuery i(createResult());
|
|
|
|
QString tbl = tablename;
|
|
QString schema;
|
|
qSplitTableName(tbl, schema);
|
|
|
|
QString cmd(QStringLiteral("select syscolumns.colname, syscolumns.coltype, si.idxname "
|
|
"from syscolumns "
|
|
"left outer join sysindexes si on syscolumns.tabid = si.tabid "
|
|
"where syscolumns.tabid = (select tabid from systables where tabname='%1') "
|
|
"and colno = (select part1 from sysindexes where tabid = ( "
|
|
"select tabid from systables where tabname='%1') "
|
|
"and idxname = (select idxname from sysconstraints where tabid=( "
|
|
"select tabid from systables where tabname='%1') "
|
|
"and constrtype='P'))"
|
|
));
|
|
|
|
i.exec(cmd.arg(tbl.toLower()));
|
|
while (i.isActive() && i.next())
|
|
{
|
|
QSqlField f(i.value(0).toString(), qSqlType2Variant(i.value(1).toInt()));
|
|
idx.append(f);
|
|
idx.setName(i.value(2).toString());
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
QSqlRecord QInformixDriver::record(const QString &tablename) const
|
|
{
|
|
QSqlRecord info;
|
|
if (!isOpen())
|
|
return info;
|
|
|
|
QString tbl = tablename;
|
|
QString schema;
|
|
qSplitTableName(tbl, schema);
|
|
|
|
QString cmd(QStringLiteral("select "
|
|
"syscolumns.colname, syscolumns.coltype, syscolumns.collength "
|
|
"from systables, syscolumns "
|
|
"where systables.tabname = '%1' "
|
|
"and systables.tabid = syscolumns.tabid"));
|
|
|
|
QSqlQuery query(createResult());
|
|
query.exec(cmd.arg(tbl.toLower()));
|
|
|
|
while (query.next())
|
|
{
|
|
const int sqltype = query.value(1).toInt();
|
|
QSqlField f(query.value(0).toString(), qSqlType2Variant(sqltype));
|
|
f.setRequired((sqltype & SQLNONULL) == SQLNONULL);
|
|
f.setLength(query.value(2).toInt());
|
|
|
|
|
|
f.setSqlType(sqltype & SQLTYPE);
|
|
f.setAutoValue((sqltype & SQLTYPE) == SQLSERIAL || (sqltype & SQLTYPE) == SQLSERIAL8);
|
|
|
|
info.append(f);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
QString QInformixDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
|
|
{
|
|
QString res = identifier;
|
|
res.replace(QLatin1Char('"'), QStringLiteral("\"\""));
|
|
res.replace(QLatin1Char('.'), QStringLiteral("\".\""));
|
|
return res;
|
|
}
|
|
|
|
QString QInformixDriver::formatValue(const QSqlField &field, bool trimStrings) const
|
|
{
|
|
QString r;
|
|
switch (field.type())
|
|
{
|
|
case QVariant::DateTime:
|
|
if (field.value().toDateTime().isValid())
|
|
{
|
|
const QDate dt = field.value().toDateTime().date();
|
|
const QTime tm = field.value().toDateTime().time();
|
|
|
|
r = QString::number(dt.year()) + QLatin1Char('-')
|
|
+ QString::number(dt.month()).rightJustified(2, QLatin1Char('0')) + QLatin1Char('-')
|
|
+ QString::number(dt.day()).rightJustified(2, QLatin1Char('0')) + QLatin1Char(' ')
|
|
+ tm.toString();
|
|
|
|
}
|
|
else
|
|
r = QStringLiteral("NULL");
|
|
break;
|
|
case QVariant::Date:
|
|
if (field.value().toDate().isValid())
|
|
{
|
|
const QDate dt = field.value().toDateTime().date();
|
|
|
|
r = QString::number(dt.year()) + QLatin1Char('-')
|
|
+ QString::number(dt.month()).rightJustified(2, QLatin1Char('0')) + QLatin1Char('-')
|
|
+ QString::number(dt.day()).rightJustified(2, QLatin1Char('0'));
|
|
|
|
}
|
|
else
|
|
r = QStringLiteral("NULL");
|
|
break;
|
|
case QVariant::String:
|
|
return trimStrings ? field.value().toString().trimmed() : field.value().toString();
|
|
default:
|
|
return QSqlDriver::formatValue(field, trimStrings);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
bool QInformixDriver::beginTransaction()
|
|
{
|
|
if (!isOpen())
|
|
{
|
|
qWarning("QInformixDriver::beginTransaction: Database not open");
|
|
return false;
|
|
}
|
|
d->addConnection();
|
|
|
|
if (!d->connection->SetTransaction(ITConnection::Begin))
|
|
{
|
|
setLastError(qMakeError(tr("Could not begin transaction"),
|
|
QSqlError::TransactionError, d));
|
|
d->removeConnection();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QInformixDriver::commitTransaction()
|
|
{
|
|
if (!isOpen())
|
|
{
|
|
qWarning("QInformixDriver::commitTransaction: Database not open");
|
|
return false;
|
|
}
|
|
|
|
if (!d->connection->SetTransaction(ITConnection::Commit))
|
|
{
|
|
setLastError(qMakeError(tr("Could not commit transaction"),
|
|
QSqlError::TransactionError, d));
|
|
d->removeConnection();
|
|
return false;
|
|
}
|
|
d->removeConnection();
|
|
return true;
|
|
}
|
|
|
|
bool QInformixDriver::rollbackTransaction()
|
|
{
|
|
if (!isOpen())
|
|
{
|
|
qWarning("QInformixDriver::rollbackTransaction: Database not open");
|
|
return false;
|
|
}
|
|
|
|
if (!d->connection->SetTransaction(ITConnection::Abort))
|
|
{
|
|
setLastError(qMakeError(tr("Could not rollback transaction"),
|
|
QSqlError::TransactionError, d));
|
|
d->removeConnection();
|
|
return false;
|
|
}
|
|
d->removeConnection();
|
|
return true;
|
|
}
|
|
|
|
void QInformixDriver::timeout()
|
|
{
|
|
#ifdef DEBUG_AUTODISCONNECT
|
|
fprintf(stderr, "%p %s private: %p\n", this, __PRETTY_FUNCTION__, d);
|
|
#endif
|
|
d->closeConnection();
|
|
}
|