/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtSql module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qsql_ibase.h" | |
#include <qcoreapplication.h> | |
#include <qdatetime.h> | |
#include <qvariant.h> | |
#include <qsqlerror.h> | |
#include <qsqlfield.h> | |
#include <qsqlindex.h> | |
#include <qsqlquery.h> | |
#include <qlist.h> | |
#include <qvector.h> | |
#include <qtextcodec.h> | |
#include <qmutex.h> | |
#include <stdlib.h> | |
#include <limits.h> | |
#include <math.h> | |
#include <qdebug.h> | |
#include <QVarLengthArray> | |
QT_BEGIN_NAMESPACE | |
#define FBVERSION SQL_DIALECT_V6 | |
#ifndef SQLDA_CURRENT_VERSION | |
#define SQLDA_CURRENT_VERSION SQLDA_VERSION1 | |
#endif | |
enum { QIBaseChunkSize = SHRT_MAX / 2 }; | |
#if defined(FB_API_VER) && FB_API_VER >= 20 | |
static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc) | |
#else | |
static bool getIBaseError(QString& msg, ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc) | |
#endif | |
{ | |
if (status[0] != 1 || status[1] <= 0) | |
return false; | |
msg.clear(); | |
sqlcode = isc_sqlcode(status); | |
char buf[512]; | |
#if defined(FB_API_VER) && FB_API_VER >= 20 | |
while(fb_interpret(buf, 512, &status)) { | |
#else | |
while(isc_interprete(buf, &status)) { | |
#endif | |
if(!msg.isEmpty()) | |
msg += QLatin1String(" - "); | |
if (tc) | |
msg += tc->toUnicode(buf); | |
else | |
msg += QString::fromUtf8(buf); | |
} | |
return true; | |
} | |
static void createDA(XSQLDA *&sqlda) | |
{ | |
sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(1)); | |
if (sqlda == (XSQLDA*)0) return; | |
sqlda->sqln = 1; | |
sqlda->sqld = 0; | |
sqlda->version = SQLDA_CURRENT_VERSION; | |
sqlda->sqlvar[0].sqlind = 0; | |
sqlda->sqlvar[0].sqldata = 0; | |
} | |
static void enlargeDA(XSQLDA *&sqlda, int n) | |
{ | |
if (sqlda != (XSQLDA*)0) | |
free(sqlda); | |
sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(n)); | |
if (sqlda == (XSQLDA*)0) return; | |
sqlda->sqln = n; | |
sqlda->version = SQLDA_CURRENT_VERSION; | |
} | |
static void initDA(XSQLDA *sqlda) | |
{ | |
for (int i = 0; i < sqlda->sqld; ++i) { | |
switch (sqlda->sqlvar[i].sqltype & ~1) { | |
case SQL_INT64: | |
case SQL_LONG: | |
case SQL_SHORT: | |
case SQL_FLOAT: | |
case SQL_DOUBLE: | |
case SQL_TIMESTAMP: | |
case SQL_TYPE_TIME: | |
case SQL_TYPE_DATE: | |
case SQL_TEXT: | |
case SQL_BLOB: | |
sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen]; | |
break; | |
case SQL_ARRAY: | |
sqlda->sqlvar[i].sqldata = new char[sizeof(ISC_QUAD)]; | |
memset(sqlda->sqlvar[i].sqldata, 0, sizeof(ISC_QUAD)); | |
break; | |
case SQL_VARYING: | |
sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen + sizeof(short)]; | |
break; | |
default: | |
// not supported - do not bind. | |
sqlda->sqlvar[i].sqldata = 0; | |
break; | |
} | |
if (sqlda->sqlvar[i].sqltype & 1) { | |
sqlda->sqlvar[i].sqlind = new short[1]; | |
*(sqlda->sqlvar[i].sqlind) = 0; | |
} else { | |
sqlda->sqlvar[i].sqlind = 0; | |
} | |
} | |
} | |
static void delDA(XSQLDA *&sqlda) | |
{ | |
if (!sqlda) | |
return; | |
for (int i = 0; i < sqlda->sqld; ++i) { | |
delete [] sqlda->sqlvar[i].sqlind; | |
delete [] sqlda->sqlvar[i].sqldata; | |
} | |
free(sqlda); | |
sqlda = 0; | |
} | |
static QVariant::Type qIBaseTypeName(int iType, bool hasScale) | |
{ | |
switch (iType) { | |
case blr_varying: | |
case blr_varying2: | |
case blr_text: | |
case blr_cstring: | |
case blr_cstring2: | |
return QVariant::String; | |
case blr_sql_time: | |
return QVariant::Time; | |
case blr_sql_date: | |
return QVariant::Date; | |
case blr_timestamp: | |
return QVariant::DateTime; | |
case blr_blob: | |
return QVariant::ByteArray; | |
case blr_quad: | |
case blr_short: | |
case blr_long: | |
return (hasScale ? QVariant::Double : QVariant::Int); | |
case blr_int64: | |
return (hasScale ? QVariant::Double : QVariant::LongLong); | |
case blr_float: | |
case blr_d_float: | |
case blr_double: | |
return QVariant::Double; | |
} | |
qWarning("qIBaseTypeName: unknown datatype: %d", iType); | |
return QVariant::Invalid; | |
} | |
static QVariant::Type qIBaseTypeName2(int iType, bool hasScale) | |
{ | |
switch(iType & ~1) { | |
case SQL_VARYING: | |
case SQL_TEXT: | |
return QVariant::String; | |
case SQL_LONG: | |
case SQL_SHORT: | |
return (hasScale ? QVariant::Double : QVariant::Int); | |
case SQL_INT64: | |
return (hasScale ? QVariant::Double : QVariant::LongLong); | |
case SQL_FLOAT: | |
case SQL_DOUBLE: | |
return QVariant::Double; | |
case SQL_TIMESTAMP: | |
return QVariant::DateTime; | |
case SQL_TYPE_TIME: | |
return QVariant::Time; | |
case SQL_TYPE_DATE: | |
return QVariant::Date; | |
case SQL_ARRAY: | |
return QVariant::List; | |
case SQL_BLOB: | |
return QVariant::ByteArray; | |
default: | |
return QVariant::Invalid; | |
} | |
} | |
static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) | |
{ | |
static const QTime midnight(0, 0, 0, 0); | |
static const QDate basedate(1858, 11, 17); | |
ISC_TIMESTAMP ts; | |
ts.timestamp_time = midnight.msecsTo(dt.time()) * 10; | |
ts.timestamp_date = basedate.daysTo(dt.date()); | |
return ts; | |
} | |
static QDateTime fromTimeStamp(char *buffer) | |
{ | |
static const QDate bd(1858, 11, 17); | |
QTime t; | |
QDate d; | |
// have to demangle the structure ourselves because isc_decode_time | |
// strips the msecs | |
t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10)); | |
d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); | |
return QDateTime(d, t); | |
} | |
static ISC_TIME toTime(const QTime &t) | |
{ | |
static const QTime midnight(0, 0, 0, 0); | |
return (ISC_TIME)midnight.msecsTo(t) * 10; | |
} | |
static QTime fromTime(char *buffer) | |
{ | |
QTime t; | |
// have to demangle the structure ourselves because isc_decode_time | |
// strips the msecs | |
t = t.addMSecs(int((*(ISC_TIME*)buffer) / 10)); | |
return t; | |
} | |
static ISC_DATE toDate(const QDate &t) | |
{ | |
static const QDate basedate(1858, 11, 17); | |
ISC_DATE date; | |
date = basedate.daysTo(t); | |
return date; | |
} | |
static QDate fromDate(char *buffer) | |
{ | |
static const QDate bd(1858, 11, 17); | |
QDate d; | |
// have to demangle the structure ourselves because isc_decode_time | |
// strips the msecs | |
d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); | |
return d; | |
} | |
static QByteArray encodeString(QTextCodec *tc, const QString &str) | |
{ | |
if (tc) | |
return tc->fromUnicode(str); | |
return str.toUtf8(); | |
} | |
struct QIBaseEventBuffer { | |
#if defined(FB_API_VER) && FB_API_VER >= 20 | |
ISC_UCHAR *eventBuffer; | |
ISC_UCHAR *resultBuffer; | |
#else | |
char *eventBuffer; | |
char *resultBuffer; | |
#endif | |
ISC_LONG bufferLength; | |
ISC_LONG eventId; | |
enum QIBaseSubscriptionState { Starting, Subscribed, Finished }; | |
QIBaseSubscriptionState subscriptionState; | |
}; | |
class QIBaseDriverPrivate | |
{ | |
public: | |
QIBaseDriverPrivate(QIBaseDriver *d) : q(d), ibase(0), trans(0), tc(0) {} | |
bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) | |
{ | |
QString imsg; | |
ISC_LONG sqlcode; | |
if (!getIBaseError(imsg, status, sqlcode, tc)) | |
return false; | |
q->setLastError(QSqlError(QCoreApplication::translate("QIBaseDriver", msg), | |
imsg, typ, int(sqlcode))); | |
return true; | |
} | |
public: | |
QIBaseDriver* q; | |
isc_db_handle ibase; | |
isc_tr_handle trans; | |
QTextCodec *tc; | |
ISC_STATUS status[20]; | |
QMap<QString, QIBaseEventBuffer*> eventBuffers; | |
}; | |
typedef QMap<void *, QIBaseDriver *> QIBaseBufferDriverMap; | |
Q_GLOBAL_STATIC(QIBaseBufferDriverMap, qBufferDriverMap) | |
Q_GLOBAL_STATIC(QMutex, qMutex); | |
static void qFreeEventBuffer(QIBaseEventBuffer* eBuffer) | |
{ | |
qMutex()->lock(); | |
qBufferDriverMap()->remove(reinterpret_cast<void *>(eBuffer->resultBuffer)); | |
qMutex()->unlock(); | |
delete eBuffer; | |
} | |
class QIBaseResultPrivate | |
{ | |
public: | |
QIBaseResultPrivate(QIBaseResult *d, const QIBaseDriver *ddb); | |
~QIBaseResultPrivate() { cleanup(); } | |
void cleanup(); | |
bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) | |
{ | |
QString imsg; | |
ISC_LONG sqlcode; | |
if (!getIBaseError(imsg, status, sqlcode, tc)) | |
return false; | |
q->setLastError(QSqlError(QCoreApplication::translate("QIBaseResult", msg), | |
imsg, typ, int(sqlcode))); | |
return true; | |
} | |
bool transaction(); | |
bool commit(); | |
bool isSelect(); | |
QVariant fetchBlob(ISC_QUAD *bId); | |
bool writeBlob(int i, const QByteArray &ba); | |
QVariant fetchArray(int pos, ISC_QUAD *arr); | |
bool writeArray(int i, const QList<QVariant> &list); | |
public: | |
QIBaseResult *q; | |
const QIBaseDriver *db; | |
ISC_STATUS status[20]; | |
isc_tr_handle trans; | |
//indicator whether we have a local transaction or a transaction on driver level | |
bool localTransaction; | |
isc_stmt_handle stmt; | |
isc_db_handle ibase; | |
XSQLDA *sqlda; // output sqlda | |
XSQLDA *inda; // input parameters | |
int queryType; | |
QTextCodec *tc; | |
}; | |
QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *d, const QIBaseDriver *ddb): | |
q(d), db(ddb), trans(0), stmt(0), ibase(ddb->d->ibase), sqlda(0), inda(0), queryType(-1), tc(ddb->d->tc) | |
{ | |
localTransaction = (ddb->d->ibase == 0); | |
} | |
void QIBaseResultPrivate::cleanup() | |
{ | |
commit(); | |
if (!localTransaction) | |
trans = 0; | |
if (stmt) { | |
isc_dsql_free_statement(status, &stmt, DSQL_drop); | |
stmt = 0; | |
} | |
delDA(sqlda); | |
delDA(inda); | |
queryType = -1; | |
q->cleanup(); | |
} | |
bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba) | |
{ | |
isc_blob_handle handle = 0; | |
ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata; | |
isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0); | |
if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"), | |
QSqlError::StatementError)) { | |
int i = 0; | |
while (i < ba.size()) { | |
isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)), | |
const_cast<char*>(ba.data()) + i); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB"))) | |
return false; | |
i += qMin(ba.size() - i, int(QIBaseChunkSize)); | |
} | |
} | |
isc_close_blob(status, &handle); | |
return true; | |
} | |
QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) | |
{ | |
isc_blob_handle handle = 0; | |
isc_open_blob2(status, &ibase, &trans, &handle, bId, 0, 0); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to open BLOB"), | |
QSqlError::StatementError)) | |
return QVariant(); | |
unsigned short len = 0; | |
QByteArray ba; | |
int chunkSize = QIBaseChunkSize; | |
ba.resize(chunkSize); | |
int read = 0; | |
while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) { | |
read += len; | |
ba.resize(read + chunkSize); | |
} | |
ba.resize(read); | |
bool isErr = (status[1] == isc_segstr_eof ? false : | |
isError(QT_TRANSLATE_NOOP("QIBaseResult", | |
"Unable to read BLOB"), | |
QSqlError::StatementError)); | |
isc_close_blob(status, &handle); | |
if (isErr) | |
return QVariant(); | |
ba.resize(read); | |
return ba; | |
} | |
template<typename T> | |
static QList<QVariant> toList(char** buf, int count, T* = 0) | |
{ | |
QList<QVariant> res; | |
for (int i = 0; i < count; ++i) { | |
res.append(*(T*)(*buf)); | |
*buf += sizeof(T); | |
} | |
return res; | |
} | |
/* char** ? seems like bad influence from oracle ... */ | |
template<> | |
QList<QVariant> toList<long>(char** buf, int count, long*) | |
{ | |
QList<QVariant> res; | |
for (int i = 0; i < count; ++i) { | |
if (sizeof(int) == sizeof(long)) | |
res.append(int((*(long*)(*buf)))); | |
else | |
res.append((qint64)(*(long*)(*buf))); | |
*buf += sizeof(long); | |
} | |
return res; | |
} | |
static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, | |
short* numElements, ISC_ARRAY_DESC *arrayDesc, | |
QTextCodec *tc) | |
{ | |
const short dim = arrayDesc->array_desc_dimensions - 1; | |
const unsigned char dataType = arrayDesc->array_desc_dtype; | |
QList<QVariant> valList; | |
unsigned short strLen = arrayDesc->array_desc_length; | |
if (curDim != dim) { | |
for(int i = 0; i < numElements[curDim]; ++i) | |
buffer = readArrayBuffer(list, buffer, curDim + 1, numElements, | |
arrayDesc, tc); | |
} else { | |
switch(dataType) { | |
case blr_varying: | |
case blr_varying2: | |
strLen += 2; // for the two terminating null values | |
case blr_text: | |
case blr_text2: { | |
int o; | |
for (int i = 0; i < numElements[dim]; ++i) { | |
for(o = 0; o < strLen && buffer[o]!=0; ++o ) | |
; | |
if (tc) | |
valList.append(tc->toUnicode(buffer, o)); | |
else | |
valList.append(QString::fromUtf8(buffer, o)); | |
buffer += strLen; | |
} | |
break; } | |
case blr_long: | |
valList = toList<long>(&buffer, numElements[dim], static_cast<long *>(0)); | |
break; | |
case blr_short: | |
valList = toList<short>(&buffer, numElements[dim]); | |
break; | |
case blr_int64: | |
valList = toList<qint64>(&buffer, numElements[dim]); | |
break; | |
case blr_float: | |
valList = toList<float>(&buffer, numElements[dim]); | |
break; | |
case blr_double: | |
valList = toList<double>(&buffer, numElements[dim]); | |
break; | |
case blr_timestamp: | |
for(int i = 0; i < numElements[dim]; ++i) { | |
valList.append(fromTimeStamp(buffer)); | |
buffer += sizeof(ISC_TIMESTAMP); | |
} | |
break; | |
case blr_sql_time: | |
for(int i = 0; i < numElements[dim]; ++i) { | |
valList.append(fromTime(buffer)); | |
buffer += sizeof(ISC_TIME); | |
} | |
break; | |
case blr_sql_date: | |
for(int i = 0; i < numElements[dim]; ++i) { | |
valList.append(fromDate(buffer)); | |
buffer += sizeof(ISC_DATE); | |
} | |
break; | |
} | |
} | |
if (dim > 0) | |
list.append(valList); | |
else | |
list += valList; | |
return buffer; | |
} | |
QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) | |
{ | |
QList<QVariant> list; | |
ISC_ARRAY_DESC desc; | |
if (!arr) | |
return list; | |
QByteArray relname(sqlda->sqlvar[pos].relname, sqlda->sqlvar[pos].relname_length); | |
QByteArray sqlname(sqlda->sqlvar[pos].aliasname, sqlda->sqlvar[pos].aliasname_length); | |
isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), | |
QSqlError::StatementError)) | |
return list; | |
int arraySize = 1, subArraySize; | |
short dimensions = desc.array_desc_dimensions; | |
QVarLengthArray<short> numElements(dimensions); | |
for(int i = 0; i < dimensions; ++i) { | |
subArraySize = (desc.array_desc_bounds[i].array_bound_upper - | |
desc.array_desc_bounds[i].array_bound_lower + 1); | |
numElements[i] = subArraySize; | |
arraySize = subArraySize * arraySize; | |
} | |
ISC_LONG bufLen; | |
QByteArray ba; | |
/* varying arrayelements are stored with 2 trailing null bytes | |
indicating the length of the string | |
*/ | |
if (desc.array_desc_dtype == blr_varying | |
|| desc.array_desc_dtype == blr_varying2) { | |
desc.array_desc_length += 2; | |
bufLen = desc.array_desc_length * arraySize * sizeof(short); | |
} else { | |
bufLen = desc.array_desc_length * arraySize; | |
} | |
ba.resize(int(bufLen)); | |
isc_array_get_slice(status, &ibase, &trans, arr, &desc, ba.data(), &bufLen); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get array data"), | |
QSqlError::StatementError)) | |
return list; | |
readArrayBuffer(list, ba.data(), 0, numElements.data(), &desc, tc); | |
return QVariant(list); | |
} | |
template<typename T> | |
static char* fillList(char *buffer, const QList<QVariant> &list, T* = 0) | |
{ | |
for (int i = 0; i < list.size(); ++i) { | |
T val; | |
val = qvariant_cast<T>(list.at(i)); | |
memcpy(buffer, &val, sizeof(T)); | |
buffer += sizeof(T); | |
} | |
return buffer; | |
} | |
template<> | |
char* fillList<float>(char *buffer, const QList<QVariant> &list, float*) | |
{ | |
for (int i = 0; i < list.size(); ++i) { | |
double val; | |
float val2 = 0; | |
val = qvariant_cast<double>(list.at(i)); | |
val2 = (float)val; | |
memcpy(buffer, &val2, sizeof(float)); | |
buffer += sizeof(float); | |
} | |
return buffer; | |
} | |
static char* qFillBufferWithString(char *buffer, const QString& string, | |
short buflen, bool varying, bool array, | |
QTextCodec *tc) | |
{ | |
QByteArray str = encodeString(tc, string); // keep a copy of the string alive in this scope | |
if (varying) { | |
short tmpBuflen = buflen; | |
if (str.length() < buflen) | |
buflen = str.length(); | |
if (array) { // interbase stores varying arrayelements different than normal varying elements | |
memcpy(buffer, str.data(), buflen); | |
memset(buffer + buflen, 0, tmpBuflen - buflen); | |
} else { | |
*(short*)buffer = buflen; // first two bytes is the length | |
memcpy(buffer + sizeof(short), str.data(), buflen); | |
} | |
buffer += tmpBuflen; | |
} else { | |
str = str.leftJustified(buflen, ' ', true); | |
memcpy(buffer, str.data(), buflen); | |
buffer += buflen; | |
} | |
return buffer; | |
} | |
static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, | |
QVariant::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc, | |
QString& error, QTextCodec *tc) | |
{ | |
int i; | |
ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds; | |
short dim = arrayDesc->array_desc_dimensions - 1; | |
int elements = (bounds[curDim].array_bound_upper - | |
bounds[curDim].array_bound_lower + 1); | |
if (list.size() != elements) { // size mismatch | |
error = QLatin1String("Expected size: %1. Supplied size: %2"); | |
error = QLatin1String("Array size mismatch. Fieldname: %1 ") | |
+ error.arg(elements).arg(list.size()); | |
return 0; | |
} | |
if (curDim != dim) { | |
for(i = 0; i < list.size(); ++i) { | |
if (list.at(i).type() != QVariant::List) { // dimensions mismatch | |
error = QLatin1String("Array dimensons mismatch. Fieldname: %1"); | |
return 0; | |
} | |
buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1, | |
arrayDesc, error, tc); | |
if (!buffer) | |
return 0; | |
} | |
} else { | |
switch(type) { | |
case QVariant::Int: | |
case QVariant::UInt: | |
if (arrayDesc->array_desc_dtype == blr_short) | |
buffer = fillList<short>(buffer, list); | |
else | |
buffer = fillList<int>(buffer, list); | |
break; | |
case QVariant::Double: | |
if (arrayDesc->array_desc_dtype == blr_float) | |
buffer = fillList<float>(buffer, list, static_cast<float *>(0)); | |
else | |
buffer = fillList<double>(buffer, list); | |
break; | |
case QVariant::LongLong: | |
buffer = fillList<qint64>(buffer, list); | |
break; | |
case QVariant::ULongLong: | |
buffer = fillList<quint64>(buffer, list); | |
break; | |
case QVariant::String: | |
for (i = 0; i < list.size(); ++i) | |
buffer = qFillBufferWithString(buffer, list.at(i).toString(), | |
arrayDesc->array_desc_length, | |
arrayDesc->array_desc_dtype == blr_varying, | |
true, tc); | |
break; | |
case QVariant::Date: | |
for (i = 0; i < list.size(); ++i) { | |
*((ISC_DATE*)buffer) = toDate(list.at(i).toDate()); | |
buffer += sizeof(ISC_DATE); | |
} | |
break; | |
case QVariant::Time: | |
for (i = 0; i < list.size(); ++i) { | |
*((ISC_TIME*)buffer) = toTime(list.at(i).toTime()); | |
buffer += sizeof(ISC_TIME); | |
} | |
break; | |
case QVariant::DateTime: | |
for (i = 0; i < list.size(); ++i) { | |
*((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime()); | |
buffer += sizeof(ISC_TIMESTAMP); | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
return buffer; | |
} | |
bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) | |
{ | |
QString error; | |
ISC_QUAD *arrayId = (ISC_QUAD*) inda->sqlvar[column].sqldata; | |
ISC_ARRAY_DESC desc; | |
QByteArray relname(inda->sqlvar[column].relname, inda->sqlvar[column].relname_length); | |
QByteArray sqlname(inda->sqlvar[column].aliasname, inda->sqlvar[column].aliasname_length); | |
isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), | |
QSqlError::StatementError)) | |
return false; | |
short arraySize = 1; | |
ISC_LONG bufLen; | |
QList<QVariant> subList = list; | |
short dimensions = desc.array_desc_dimensions; | |
for(int i = 0; i < dimensions; ++i) { | |
arraySize *= (desc.array_desc_bounds[i].array_bound_upper - | |
desc.array_desc_bounds[i].array_bound_lower + 1); | |
} | |
/* varying arrayelements are stored with 2 trailing null bytes | |
indicating the length of the string | |
*/ | |
if (desc.array_desc_dtype == blr_varying || | |
desc.array_desc_dtype == blr_varying2) | |
desc.array_desc_length += 2; | |
bufLen = desc.array_desc_length * arraySize; | |
QByteArray ba; | |
ba.resize(int(bufLen)); | |
if (list.size() > arraySize) { | |
error = QLatin1String("Array size missmatch: size of %1 is %2, size of provided list is %3"); | |
error = error.arg(QLatin1String(sqlname)).arg(arraySize).arg(list.size()); | |
q->setLastError(QSqlError(error, QLatin1String(""), QSqlError::StatementError)); | |
return false; | |
} | |
if (!createArrayBuffer(ba.data(), list, | |
qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0), | |
0, &desc, error, tc)) { | |
q->setLastError(QSqlError(error.arg(QLatin1String(sqlname)), QLatin1String(""), | |
QSqlError::StatementError)); | |
return false; | |
} | |
/* readjust the buffer size*/ | |
if (desc.array_desc_dtype == blr_varying | |
|| desc.array_desc_dtype == blr_varying2) | |
desc.array_desc_length -= 2; | |
isc_array_put_slice(status, &ibase, &trans, arrayId, &desc, ba.data(), &bufLen); | |
return true; | |
} | |
bool QIBaseResultPrivate::isSelect() | |
{ | |
char acBuffer[9]; | |
char qType = isc_info_sql_stmt_type; | |
isc_dsql_sql_info(status, &stmt, 1, &qType, sizeof(acBuffer), acBuffer); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get query info"), | |
QSqlError::StatementError)) | |
return false; | |
int iLength = isc_vax_integer(&acBuffer[1], 2); | |
queryType = isc_vax_integer(&acBuffer[3], iLength); | |
return (queryType == isc_info_sql_stmt_select || queryType == isc_info_sql_stmt_exec_procedure); | |
} | |
bool QIBaseResultPrivate::transaction() | |
{ | |
if (trans) | |
return true; | |
if (db->d->trans) { | |
localTransaction = false; | |
trans = db->d->trans; | |
return true; | |
} | |
localTransaction = true; | |
isc_start_transaction(status, &trans, 1, &ibase, 0, NULL); | |
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not start transaction"), | |
QSqlError::TransactionError)) | |
return false; | |
return true; | |
} | |
// does nothing if the transaction is on the | |
// driver level | |
bool QIBaseResultPrivate::commit() | |
{ | |
if (!trans) | |
return false; | |
// don't commit driver's transaction, the driver will do it for us | |
if (!localTransaction) | |
return true; | |
isc_commit_transaction(status, &trans); | |
trans = 0; | |
return !isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to commit transaction"), | |
QSqlError::TransactionError); | |
} | |
////////// | |
QIBaseResult::QIBaseResult(const QIBaseDriver* db): | |
QSqlCachedResult(db) | |
{ | |
d = new QIBaseResultPrivate(this, db); | |
} | |
QIBaseResult::~QIBaseResult() | |
{ | |
delete d; | |
} | |
bool QIBaseResult::prepare(const QString& query) | |
{ | |
// qDebug("prepare: %s", qPrintable(query)); | |
if (!driver() || !driver()->isOpen() || driver()->isOpenError()) | |
return false; | |
d->cleanup(); | |
setActive(false); | |
setAt(QSql::BeforeFirstRow); | |
createDA(d->sqlda); | |
if (d->sqlda == (XSQLDA*)0) { | |
qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; | |
return false; | |
} | |
createDA(d->inda); | |
if (d->inda == (XSQLDA*)0){ | |
qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory"; | |
return false; | |
} | |
if (!d->transaction()) | |
return false; | |
isc_dsql_allocate_statement(d->status, &d->ibase, &d->stmt); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not allocate statement"), | |
QSqlError::StatementError)) | |
return false; | |
isc_dsql_prepare(d->status, &d->trans, &d->stmt, 0, | |
const_cast<char*>(encodeString(d->tc, query).constData()), FBVERSION, d->sqlda); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not prepare statement"), | |
QSqlError::StatementError)) | |
return false; | |
isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", | |
"Could not describe input statement"), QSqlError::StatementError)) | |
return false; | |
if (d->inda->sqld > d->inda->sqln) { | |
enlargeDA(d->inda, d->inda->sqld); | |
if (d->inda == (XSQLDA*)0) { | |
qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory"; | |
return false; | |
} | |
isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", | |
"Could not describe input statement"), QSqlError::StatementError)) | |
return false; | |
} | |
initDA(d->inda); | |
if (d->sqlda->sqld > d->sqlda->sqln) { | |
// need more field descriptors | |
enlargeDA(d->sqlda, d->sqlda->sqld); | |
if (d->sqlda == (XSQLDA*)0) { | |
qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory"; | |
return false; | |
} | |
isc_dsql_describe(d->status, &d->stmt, FBVERSION, d->sqlda); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not describe statement"), | |
QSqlError::StatementError)) | |
return false; | |
} | |
initDA(d->sqlda); | |
setSelect(d->isSelect()); | |
if (!isSelect()) { | |
free(d->sqlda); | |
d->sqlda = 0; | |
} | |
return true; | |
} | |
bool QIBaseResult::exec() | |
{ | |
bool ok = true; | |
if (!d->trans) | |
d->transaction(); | |
if (!driver() || !driver()->isOpen() || driver()->isOpenError()) | |
return false; | |
setActive(false); | |
setAt(QSql::BeforeFirstRow); | |
if (d->inda) { | |
QVector<QVariant>& values = boundValues(); | |
int i; | |
if (values.count() > d->inda->sqld) { | |
qWarning("QIBaseResult::exec: Parameter mismatch, expected %d, got %d parameters", | |
d->inda->sqld, values.count()); | |
return false; | |
} | |
int para = 0; | |
for (i = 0; i < values.count(); ++i) { | |
para = i; | |
if (!d->inda->sqlvar[para].sqldata) | |
// skip unknown datatypes | |
continue; | |
const QVariant val(values[i]); | |
if (d->inda->sqlvar[para].sqltype & 1) { | |
if (val.isNull()) { | |
// set null indicator | |
*(d->inda->sqlvar[para].sqlind) = -1; | |
// and set the value to 0, otherwise it would count as empty string. | |
// it seems to be working with just setting sqlind to -1 | |
//*((char*)d->inda->sqlvar[para].sqldata) = 0; | |
continue; | |
} | |
// a value of 0 means non-null. | |
*(d->inda->sqlvar[para].sqlind) = 0; | |
} | |
switch(d->inda->sqlvar[para].sqltype & ~1) { | |
case SQL_INT64: | |
if (d->inda->sqlvar[para].sqlscale < 0) | |
*((qint64*)d->inda->sqlvar[para].sqldata) = | |
(qint64)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); | |
else | |
*((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); | |
break; | |
case SQL_LONG: | |
if (d->inda->sqlvar[para].sqlscale < 0) | |
*((long*)d->inda->sqlvar[para].sqldata) = | |
(long)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); | |
else | |
*((long*)d->inda->sqlvar[para].sqldata) = (long)val.toLongLong(); | |
break; | |
case SQL_SHORT: | |
if (d->inda->sqlvar[para].sqlscale < 0) | |
*((short*)d->inda->sqlvar[para].sqldata) = | |
(short)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); | |
else | |
*((short*)d->inda->sqlvar[para].sqldata) = (short)val.toInt(); | |
break; | |
case SQL_FLOAT: | |
*((float*)d->inda->sqlvar[para].sqldata) = (float)val.toDouble(); | |
break; | |
case SQL_DOUBLE: | |
*((double*)d->inda->sqlvar[para].sqldata) = val.toDouble(); | |
break; | |
case SQL_TIMESTAMP: | |
*((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime()); | |
break; | |
case SQL_TYPE_TIME: | |
*((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime()); | |
break; | |
case SQL_TYPE_DATE: | |
*((ISC_DATE*)d->inda->sqlvar[para].sqldata) = toDate(val.toDate()); | |
break; | |
case SQL_VARYING: | |
case SQL_TEXT: | |
qFillBufferWithString(d->inda->sqlvar[para].sqldata, val.toString(), | |
d->inda->sqlvar[para].sqllen, | |
(d->inda->sqlvar[para].sqltype & ~1) == SQL_VARYING, false, d->tc); | |
break; | |
case SQL_BLOB: | |
ok &= d->writeBlob(para, val.toByteArray()); | |
break; | |
case SQL_ARRAY: | |
ok &= d->writeArray(para, val.toList()); | |
break; | |
default: | |
qWarning("QIBaseResult::exec: Unknown datatype %d", | |
d->inda->sqlvar[para].sqltype & ~1); | |
break; | |
} | |
} | |
} | |
if (ok) { | |
if (colCount() && d->queryType != isc_info_sql_stmt_exec_procedure) { | |
isc_dsql_free_statement(d->status, &d->stmt, DSQL_close); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to close statement"))) | |
return false; | |
cleanup(); | |
} | |
if (d->queryType == isc_info_sql_stmt_exec_procedure) | |
isc_dsql_execute2(d->status, &d->trans, &d->stmt, FBVERSION, d->inda, d->sqlda); | |
else | |
isc_dsql_execute(d->status, &d->trans, &d->stmt, FBVERSION, d->inda); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to execute query"))) | |
return false; | |
// Not all stored procedures necessarily return values. | |
if (d->queryType == isc_info_sql_stmt_exec_procedure && d->sqlda && d->sqlda->sqld == 0) | |
delDA(d->sqlda); | |
if (d->sqlda) | |
init(d->sqlda->sqld); | |
if (!isSelect()) | |
d->commit(); | |
setActive(true); | |
return true; | |
} | |
return false; | |
} | |
bool QIBaseResult::reset (const QString& query) | |
{ | |
if (!prepare(query)) | |
return false; | |
return exec(); | |
} | |
bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) | |
{ | |
ISC_STATUS stat = 0; | |
// Stored Procedures are special - they populate our d->sqlda when executing, | |
// so we don't have to call isc_dsql_fetch | |
if (d->queryType == isc_info_sql_stmt_exec_procedure) { | |
// the first "fetch" shall succeed, all consecutive ones will fail since | |
// we only have one row to fetch for stored procedures | |
if (rowIdx != 0) | |
stat = 100; | |
} else { | |
stat = isc_dsql_fetch(d->status, &d->stmt, FBVERSION, d->sqlda); | |
} | |
if (stat == 100) { | |
// no more rows | |
setAt(QSql::AfterLastRow); | |
return false; | |
} | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not fetch next item"), | |
QSqlError::StatementError)) | |
return false; | |
if (rowIdx < 0) // not interested in actual values | |
return true; | |
for (int i = 0; i < d->sqlda->sqld; ++i) { | |
int idx = rowIdx + i; | |
char *buf = d->sqlda->sqlvar[i].sqldata; | |
int size = d->sqlda->sqlvar[i].sqllen; | |
Q_ASSERT(buf); | |
if ((d->sqlda->sqlvar[i].sqltype & 1) && *d->sqlda->sqlvar[i].sqlind) { | |
// null value | |
QVariant v; | |
v.convert(qIBaseTypeName2(d->sqlda->sqlvar[i].sqltype, d->sqlda->sqlvar[i].sqlscale < 0)); | |
if(v.type() == QVariant::Double) { | |
switch(numericalPrecisionPolicy()) { | |
case QSql::LowPrecisionInt32: | |
v.convert(QVariant::Int); | |
break; | |
case QSql::LowPrecisionInt64: | |
v.convert(QVariant::LongLong); | |
break; | |
case QSql::HighPrecision: | |
v.convert(QVariant::String); | |
break; | |
} | |
} | |
row[idx] = v; | |
continue; | |
} | |
switch(d->sqlda->sqlvar[i].sqltype & ~1) { | |
case SQL_VARYING: | |
// pascal strings - a short with a length information followed by the data | |
if (d->tc) | |
row[idx] = d->tc->toUnicode(buf + sizeof(short), *(short*)buf); | |
else | |
row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf); | |
break; | |
case SQL_INT64: | |
if (d->sqlda->sqlvar[i].sqlscale < 0) | |
row[idx] = *(qint64*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale); | |
else | |
row[idx] = QVariant(*(qint64*)buf); | |
break; | |
case SQL_LONG: | |
if (d->sqlda->sqlvar[i].sqllen == 4) | |
if (d->sqlda->sqlvar[i].sqlscale < 0) | |
row[idx] = QVariant(*(qint32*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); | |
else | |
row[idx] = QVariant(*(qint32*)buf); | |
else | |
row[idx] = QVariant(*(qint64*)buf); | |
break; | |
case SQL_SHORT: | |
if (d->sqlda->sqlvar[i].sqlscale < 0) | |
row[idx] = QVariant(long((*(short*)buf)) * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); | |
else | |
row[idx] = QVariant(int((*(short*)buf))); | |
break; | |
case SQL_FLOAT: | |
row[idx] = QVariant(double((*(float*)buf))); | |
break; | |
case SQL_DOUBLE: | |
row[idx] = QVariant(*(double*)buf); | |
break; | |
case SQL_TIMESTAMP: | |
row[idx] = fromTimeStamp(buf); | |
break; | |
case SQL_TYPE_TIME: | |
row[idx] = fromTime(buf); | |
break; | |
case SQL_TYPE_DATE: | |
row[idx] = fromDate(buf); | |
break; | |
case SQL_TEXT: | |
if (d->tc) | |
row[idx] = d->tc->toUnicode(buf, size); | |
else | |
row[idx] = QString::fromUtf8(buf, size); | |
break; | |
case SQL_BLOB: | |
row[idx] = d->fetchBlob((ISC_QUAD*)buf); | |
break; | |
case SQL_ARRAY: | |
row[idx] = d->fetchArray(i, (ISC_QUAD*)buf); | |
break; | |
default: | |
// unknown type - don't even try to fetch | |
row[idx] = QVariant(); | |
break; | |
} | |
if (d->sqlda->sqlvar[i].sqlscale < 0) { | |
QVariant v = row[idx]; | |
switch(numericalPrecisionPolicy()) { | |
case QSql::LowPrecisionInt32: | |
if(v.convert(QVariant::Int)) | |
row[idx]=v; | |
break; | |
case QSql::LowPrecisionInt64: | |
if(v.convert(QVariant::LongLong)) | |
row[idx]=v; | |
break; | |
case QSql::LowPrecisionDouble: | |
if(v.convert(QVariant::Double)) | |
row[idx]=v; | |
break; | |
case QSql::HighPrecision: | |
if(v.convert(QVariant::String)) | |
row[idx]=v; | |
break; | |
} | |
} | |
} | |
return true; | |
} | |
int QIBaseResult::size() | |
{ | |
return -1; | |
#if 0 /// ### FIXME | |
static char sizeInfo[] = {isc_info_sql_records}; | |
char buf[64]; | |
//qDebug() << sizeInfo; | |
if (!isActive() || !isSelect()) | |
return -1; | |
char ct; | |
short len; | |
int val = 0; | |
// while(val == 0) { | |
isc_dsql_sql_info(d->status, &d->stmt, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); | |
// isc_database_info(d->status, &d->ibase, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); | |
for(int i = 0; i < 66; ++i) | |
qDebug() << QString::number(buf[i]); | |
for (char* c = buf + 3; *c != isc_info_end; /*nothing*/) { | |
ct = *(c++); | |
len = isc_vax_integer(c, 2); | |
c += 2; | |
val = isc_vax_integer(c, len); | |
c += len; | |
qDebug() << "size" << val; | |
if (ct == isc_info_req_select_count) | |
return val; | |
} | |
//qDebug() << "size -1"; | |
return -1; | |
unsigned int i, result_size; | |
if (buf[0] == isc_info_sql_records) { | |
i = 3; | |
result_size = isc_vax_integer(&buf[1],2); | |
while (buf[i] != isc_info_end && i < result_size) { | |
len = (short)isc_vax_integer(&buf[i+1],2); | |
if (buf[i] == isc_info_req_select_count) | |
return (isc_vax_integer(&buf[i+3],len)); | |
i += len+3; | |
} | |
} | |
// } | |
return -1; | |
#endif | |
} | |
int QIBaseResult::numRowsAffected() | |
{ | |
static char acCountInfo[] = {isc_info_sql_records}; | |
char cCountType; | |
switch (d->queryType) { | |
case isc_info_sql_stmt_select: | |
cCountType = isc_info_req_select_count; | |
break; | |
case isc_info_sql_stmt_update: | |
cCountType = isc_info_req_update_count; | |
break; | |
case isc_info_sql_stmt_delete: | |
cCountType = isc_info_req_delete_count; | |
break; | |
case isc_info_sql_stmt_insert: | |
cCountType = isc_info_req_insert_count; | |
break; | |
default: | |
qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")"; | |
return -1; | |
} | |
char acBuffer[33]; | |
int iResult = -1; | |
isc_dsql_sql_info(d->status, &d->stmt, sizeof(acCountInfo), acCountInfo, sizeof(acBuffer), acBuffer); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get statement info"), | |
QSqlError::StatementError)) | |
return -1; | |
for (char *pcBuf = acBuffer + 3; *pcBuf != isc_info_end; /*nothing*/) { | |
char cType = *pcBuf++; | |
short sLength = isc_vax_integer (pcBuf, 2); | |
pcBuf += 2; | |
int iValue = isc_vax_integer (pcBuf, sLength); | |
pcBuf += sLength; | |
if (cType == cCountType) { | |
iResult = iValue; | |
break; | |
} | |
} | |
return iResult; | |
} | |
QSqlRecord QIBaseResult::record() const | |
{ | |
QSqlRecord rec; | |
if (!isActive() || !d->sqlda) | |
return rec; | |
XSQLVAR v; | |
for (int i = 0; i < d->sqlda->sqld; ++i) { | |
v = d->sqlda->sqlvar[i]; | |
QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(), | |
qIBaseTypeName2(v.sqltype, v.sqlscale < 0)); | |
f.setLength(v.sqllen); | |
f.setPrecision(qAbs(v.sqlscale)); | |
f.setRequiredStatus((v.sqltype & 1) == 0 ? QSqlField::Required : QSqlField::Optional); | |
if(v.sqlscale < 0) { | |
QSqlQuery q(new QIBaseResult(d->db)); | |
q.setForwardOnly(true); | |
q.exec(QLatin1String("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG " | |
"FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " | |
"WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " | |
"AND a.RDB$RELATION_NAME = '") + QString::fromAscii(v.relname, v.relname_length).toUpper() + QLatin1String("' " | |
"AND a.RDB$FIELD_NAME = '") + QString::fromAscii(v.sqlname, v.sqlname_length).toUpper() + QLatin1String("' ")); | |
if(q.first()) { | |
if(v.sqlscale < 0) { | |
f.setLength(q.value(0).toInt()); | |
f.setPrecision(qAbs(q.value(1).toInt())); | |
} else { | |
f.setLength(q.value(2).toInt()); | |
f.setPrecision(0); | |
} | |
f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional); | |
} | |
} | |
f.setSqlType(v.sqltype); | |
rec.append(f); | |
} | |
return rec; | |
} | |
QVariant QIBaseResult::handle() const | |
{ | |
return QVariant(qRegisterMetaType<isc_stmt_handle>("isc_stmt_handle"), &d->stmt); | |
} | |
/*********************************/ | |
QIBaseDriver::QIBaseDriver(QObject * parent) | |
: QSqlDriver(parent) | |
{ | |
d = new QIBaseDriverPrivate(this); | |
} | |
QIBaseDriver::QIBaseDriver(isc_db_handle connection, QObject *parent) | |
: QSqlDriver(parent) | |
{ | |
d = new QIBaseDriverPrivate(this); | |
d->ibase = connection; | |
setOpen(true); | |
setOpenError(false); | |
} | |
QIBaseDriver::~QIBaseDriver() | |
{ | |
delete d; | |
} | |
bool QIBaseDriver::hasFeature(DriverFeature f) const | |
{ | |
switch (f) { | |
case QuerySize: | |
case NamedPlaceholders: | |
case LastInsertId: | |
case BatchOperations: | |
case SimpleLocking: | |
case FinishQuery: | |
case MultipleResultSets: | |
return false; | |
case Transactions: | |
case PreparedQueries: | |
case PositionalPlaceholders: | |
case Unicode: | |
case BLOB: | |
case EventNotifications: | |
case LowPrecisionNumbers: | |
return true; | |
} | |
return false; | |
} | |
bool QIBaseDriver::open(const QString & db, | |
const QString & user, | |
const QString & password, | |
const QString & host, | |
int /*port*/, | |
const QString & connOpts) | |
{ | |
if (isOpen()) | |
close(); | |
const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); | |
QString encString; | |
QByteArray role; | |
for (int i = 0; i < opts.count(); ++i) { | |
QString tmp(opts.at(i).simplified()); | |
int idx; | |
if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { | |
QString val = tmp.mid(idx + 1).simplified(); | |
QString opt = tmp.left(idx).simplified(); | |
if (opt.toUpper() == QLatin1String("ISC_DPB_LC_CTYPE")) | |
encString = val; | |
else if (opt.toUpper() == QLatin1String("ISC_DPB_SQL_ROLE_NAME")) { | |
role = val.toLocal8Bit(); | |
role.truncate(255); | |
} | |
} | |
} | |
// Use UNICODE_FSS when no ISC_DPB_LC_CTYPE is provided | |
if (encString.isEmpty()) | |
encString = QLatin1String("UNICODE_FSS"); | |
else { | |
d->tc = QTextCodec::codecForName(encString.toLocal8Bit()); | |
if (!d->tc) { | |
qWarning("Unsupported encoding: %s. Using UNICODE_FFS for ISC_DPB_LC_CTYPE.", encString.toLocal8Bit().constData()); | |
encString = QLatin1String("UNICODE_FSS"); // Fallback to UNICODE_FSS | |
} | |
} | |
QByteArray enc = encString.toLocal8Bit(); | |
QByteArray usr = user.toLocal8Bit(); | |
QByteArray pass = password.toLocal8Bit(); | |
enc.truncate(255); | |
usr.truncate(255); | |
pass.truncate(255); | |
QByteArray ba; | |
ba.resize(usr.length() + pass.length() + enc.length() + role.length() + 6); | |
int i = -1; | |
ba[++i] = isc_dpb_version1; | |
ba[++i] = isc_dpb_user_name; | |
ba[++i] = usr.length(); | |
memcpy(ba.data() + ++i, usr.data(), usr.length()); | |
i += usr.length(); | |
ba[i] = isc_dpb_password; | |
ba[++i] = pass.length(); | |
memcpy(ba.data() + ++i, pass.data(), pass.length()); | |
i += pass.length(); | |
ba[i] = isc_dpb_lc_ctype; | |
ba[++i] = enc.length(); | |
memcpy(ba.data() + ++i, enc.data(), enc.length()); | |
i += enc.length(); | |
if (!role.isEmpty()) { | |
ba[i] = isc_dpb_sql_role_name; | |
ba[++i] = role.length(); | |
memcpy(ba.data() + ++i, role.data(), role.length()); | |
i += role.length(); | |
} | |
QString ldb; | |
if (!host.isEmpty()) | |
ldb += host + QLatin1Char(':'); | |
ldb += db; | |
isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()), | |
&d->ibase, i, ba.data()); | |
if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"), | |
QSqlError::ConnectionError)) { | |
setOpenError(true); | |
return false; | |
} | |
setOpen(true); | |
return true; | |
} | |
void QIBaseDriver::close() | |
{ | |
if (isOpen()) { | |
if (d->eventBuffers.size()) { | |
ISC_STATUS status[20]; | |
QMap<QString, QIBaseEventBuffer *>::const_iterator i; | |
for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { | |
QIBaseEventBuffer *eBuffer = i.value(); | |
eBuffer->subscriptionState = QIBaseEventBuffer::Finished; | |
isc_cancel_events(status, &d->ibase, &eBuffer->eventId); | |
qFreeEventBuffer(eBuffer); | |
} | |
d->eventBuffers.clear(); | |
#if defined(FB_API_VER) | |
// Workaround for Firebird crash | |
QTime timer; | |
timer.start(); | |
while (timer.elapsed() < 500) | |
QCoreApplication::processEvents(); | |
#endif | |
} | |
isc_detach_database(d->status, &d->ibase); | |
d->ibase = 0; | |
setOpen(false); | |
setOpenError(false); | |
} | |
} | |
QSqlResult *QIBaseDriver::createResult() const | |
{ | |
return new QIBaseResult(this); | |
} | |
bool QIBaseDriver::beginTransaction() | |
{ | |
if (!isOpen() || isOpenError()) | |
return false; | |
if (d->trans) | |
return false; | |
isc_start_transaction(d->status, &d->trans, 1, &d->ibase, 0, NULL); | |
return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Could not start transaction"), | |
QSqlError::TransactionError); | |
} | |
bool QIBaseDriver::commitTransaction() | |
{ | |
if (!isOpen() || isOpenError()) | |
return false; | |
if (!d->trans) | |
return false; | |
isc_commit_transaction(d->status, &d->trans); | |
d->trans = 0; | |
return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to commit transaction"), | |
QSqlError::TransactionError); | |
} | |
bool QIBaseDriver::rollbackTransaction() | |
{ | |
if (!isOpen() || isOpenError()) | |
return false; | |
if (!d->trans) | |
return false; | |
isc_rollback_transaction(d->status, &d->trans); | |
d->trans = 0; | |
return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to rollback transaction"), | |
QSqlError::TransactionError); | |
} | |
QStringList QIBaseDriver::tables(QSql::TableType type) const | |
{ | |
QStringList res; | |
if (!isOpen()) | |
return res; | |
QString typeFilter; | |
if (type == QSql::SystemTables) { | |
typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0"); | |
} else if (type == (QSql::SystemTables | QSql::Views)) { | |
typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL"); | |
} else { | |
if (!(type & QSql::SystemTables)) | |
typeFilter += QLatin1String("RDB$SYSTEM_FLAG = 0 AND "); | |
if (!(type & QSql::Views)) | |
typeFilter += QLatin1String("RDB$VIEW_BLR IS NULL AND "); | |
if (!(type & QSql::Tables)) | |
typeFilter += QLatin1String("RDB$VIEW_BLR IS NOT NULL AND "); | |
if (!typeFilter.isEmpty()) | |
typeFilter.chop(5); | |
} | |
if (!typeFilter.isEmpty()) | |
typeFilter.prepend(QLatin1String("where ")); | |
QSqlQuery q(createResult()); | |
q.setForwardOnly(true); | |
if (!q.exec(QLatin1String("select rdb$relation_name from rdb$relations ") + typeFilter)) | |
return res; | |
while(q.next()) | |
res << q.value(0).toString().simplified(); | |
return res; | |
} | |
QSqlRecord QIBaseDriver::record(const QString& tablename) const | |
{ | |
QSqlRecord rec; | |
if (!isOpen()) | |
return rec; | |
QSqlQuery q(createResult()); | |
q.setForwardOnly(true); | |
QString table = tablename; | |
if (isIdentifierEscaped(table, QSqlDriver::TableName)) | |
table = stripDelimiters(table, QSqlDriver::TableName); | |
else | |
table = table.toUpper(); | |
q.exec(QLatin1String("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, " | |
"b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG " | |
"FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " | |
"WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " | |
"AND a.RDB$RELATION_NAME = '") + table + QLatin1String("' " | |
"ORDER BY a.RDB$FIELD_POSITION")); | |
while (q.next()) { | |
int type = q.value(1).toInt(); | |
bool hasScale = q.value(3).toInt() < 0; | |
QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale)); | |
if(hasScale) { | |
f.setLength(q.value(4).toInt()); | |
f.setPrecision(qAbs(q.value(3).toInt())); | |
} else { | |
f.setLength(q.value(2).toInt()); | |
f.setPrecision(0); | |
} | |
f.setRequired(q.value(5).toInt() > 0 ? true : false); | |
f.setSqlType(type); | |
rec.append(f); | |
} | |
return rec; | |
} | |
QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const | |
{ | |
QSqlIndex index(table); | |
if (!isOpen()) | |
return index; | |
QString tablename = table; | |
if (isIdentifierEscaped(tablename, QSqlDriver::TableName)) | |
tablename = stripDelimiters(tablename, QSqlDriver::TableName); | |
else | |
tablename = tablename.toUpper(); | |
QSqlQuery q(createResult()); | |
q.setForwardOnly(true); | |
q.exec(QLatin1String("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE " | |
"FROM RDB$RELATION_CONSTRAINTS a, RDB$INDEX_SEGMENTS b, RDB$RELATION_FIELDS c, RDB$FIELDS d " | |
"WHERE a.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' " | |
"AND a.RDB$RELATION_NAME = '") + tablename + | |
QLatin1String(" 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME " | |
"AND c.RDB$RELATION_NAME = a.RDB$RELATION_NAME " | |
"AND c.RDB$FIELD_NAME = b.RDB$FIELD_NAME " | |
"AND d.RDB$FIELD_NAME = c.RDB$FIELD_SOURCE " | |
"ORDER BY b.RDB$FIELD_POSITION")); | |
while (q.next()) { | |
QSqlField field(q.value(1).toString().simplified(), qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0)); | |
index.append(field); //TODO: asc? desc? | |
index.setName(q.value(0).toString()); | |
} | |
return index; | |
} | |
QString QIBaseDriver::formatValue(const QSqlField &field, bool trimStrings) const | |
{ | |
switch (field.type()) { | |
case QVariant::DateTime: { | |
QDateTime datetime = field.value().toDateTime(); | |
if (datetime.isValid()) | |
return QLatin1Char('\'') + QString::number(datetime.date().year()) + QLatin1Char('-') + | |
QString::number(datetime.date().month()) + QLatin1Char('-') + | |
QString::number(datetime.date().day()) + QLatin1Char(' ') + | |
QString::number(datetime.time().hour()) + QLatin1Char(':') + | |
QString::number(datetime.time().minute()) + QLatin1Char(':') + | |
QString::number(datetime.time().second()) + QLatin1Char('.') + | |
QString::number(datetime.time().msec()).rightJustified(3, QLatin1Char('0'), true) + | |
QLatin1Char('\''); | |
else | |
return QLatin1String("NULL"); | |
} | |
case QVariant::Time: { | |
QTime time = field.value().toTime(); | |
if (time.isValid()) | |
return QLatin1Char('\'') + QString::number(time.hour()) + QLatin1Char(':') + | |
QString::number(time.minute()) + QLatin1Char(':') + | |
QString::number(time.second()) + QLatin1Char('.') + | |
QString::number(time.msec()).rightJustified(3, QLatin1Char('0'), true) + | |
QLatin1Char('\''); | |
else | |
return QLatin1String("NULL"); | |
} | |
case QVariant::Date: { | |
QDate date = field.value().toDate(); | |
if (date.isValid()) | |
return QLatin1Char('\'') + QString::number(date.year()) + QLatin1Char('-') + | |
QString::number(date.month()) + QLatin1Char('-') + | |
QString::number(date.day()) + QLatin1Char('\''); | |
else | |
return QLatin1String("NULL"); | |
} | |
default: | |
return QSqlDriver::formatValue(field, trimStrings); | |
} | |
} | |
QVariant QIBaseDriver::handle() const | |
{ | |
return QVariant(qRegisterMetaType<isc_db_handle>("isc_db_handle"), &d->ibase); | |
} | |
#if defined(FB_API_VER) && FB_API_VER >= 20 | |
static ISC_EVENT_CALLBACK qEventCallback(char *result, ISC_USHORT length, const ISC_UCHAR *updated) | |
#else | |
static isc_callback qEventCallback(char *result, short length, char *updated) | |
#endif | |
{ | |
if (!updated) | |
return 0; | |
memcpy(result, updated, length); | |
qMutex()->lock(); | |
QIBaseDriver *driver = qBufferDriverMap()->value(result); | |
qMutex()->unlock(); | |
// We use an asynchronous call (i.e., queued connection) because the event callback | |
// is executed in a different thread than the one in which the driver lives. | |
if (driver) | |
QMetaObject::invokeMethod(driver, "qHandleEventNotification", Qt::QueuedConnection, Q_ARG(void *, reinterpret_cast<void *>(result))); | |
return 0; | |
} | |
bool QIBaseDriver::subscribeToNotificationImplementation(const QString &name) | |
{ | |
if (!isOpen()) { | |
qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open."); | |
return false; | |
} | |
if (d->eventBuffers.contains(name)) { | |
qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", | |
qPrintable(name)); | |
return false; | |
} | |
QIBaseEventBuffer *eBuffer = new QIBaseEventBuffer; | |
eBuffer->subscriptionState = QIBaseEventBuffer::Starting; | |
eBuffer->bufferLength = isc_event_block(&eBuffer->eventBuffer, | |
&eBuffer->resultBuffer, | |
1, | |
name.toLocal8Bit().constData()); | |
qMutex()->lock(); | |
qBufferDriverMap()->insert(eBuffer->resultBuffer, this); | |
qMutex()->unlock(); | |
d->eventBuffers.insert(name, eBuffer); | |
ISC_STATUS status[20]; | |
isc_que_events(status, | |
&d->ibase, | |
&eBuffer->eventId, | |
eBuffer->bufferLength, | |
eBuffer->eventBuffer, | |
#if defined (FB_API_VER) && FB_API_VER >= 20 | |
(ISC_EVENT_CALLBACK)qEventCallback, | |
#else | |
(isc_callback)qEventCallback, | |
#endif | |
eBuffer->resultBuffer); | |
if (status[0] == 1 && status[1]) { | |
setLastError(QSqlError(QString::fromLatin1("Could not subscribe to event notifications for %1.").arg(name))); | |
d->eventBuffers.remove(name); | |
qFreeEventBuffer(eBuffer); | |
return false; | |
} | |
return true; | |
} | |
bool QIBaseDriver::unsubscribeFromNotificationImplementation(const QString &name) | |
{ | |
if (!isOpen()) { | |
qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open."); | |
return false; | |
} | |
if (!d->eventBuffers.contains(name)) { | |
qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.", | |
qPrintable(name)); | |
return false; | |
} | |
QIBaseEventBuffer *eBuffer = d->eventBuffers.value(name); | |
ISC_STATUS status[20]; | |
eBuffer->subscriptionState = QIBaseEventBuffer::Finished; | |
isc_cancel_events(status, &d->ibase, &eBuffer->eventId); | |
if (status[0] == 1 && status[1]) { | |
setLastError(QSqlError(QString::fromLatin1("Could not unsubscribe from event notifications for %1.").arg(name))); | |
return false; | |
} | |
d->eventBuffers.remove(name); | |
qFreeEventBuffer(eBuffer); | |
return true; | |
} | |
QStringList QIBaseDriver::subscribedToNotificationsImplementation() const | |
{ | |
return QStringList(d->eventBuffers.keys()); | |
} | |
void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer) | |
{ | |
QMap<QString, QIBaseEventBuffer *>::const_iterator i; | |
for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { | |
QIBaseEventBuffer* eBuffer = i.value(); | |
if (reinterpret_cast<void *>(eBuffer->resultBuffer) != updatedResultBuffer) | |
continue; | |
ISC_ULONG counts[20]; | |
memset(counts, 0, sizeof(counts)); | |
isc_event_counts(counts, eBuffer->bufferLength, eBuffer->eventBuffer, eBuffer->resultBuffer); | |
if (counts[0]) { | |
if (eBuffer->subscriptionState == QIBaseEventBuffer::Subscribed) | |
emit notification(i.key()); | |
else if (eBuffer->subscriptionState == QIBaseEventBuffer::Starting) | |
eBuffer->subscriptionState = QIBaseEventBuffer::Subscribed; | |
ISC_STATUS status[20]; | |
isc_que_events(status, | |
&d->ibase, | |
&eBuffer->eventId, | |
eBuffer->bufferLength, | |
eBuffer->eventBuffer, | |
#if defined (FB_API_VER) && FB_API_VER >= 20 | |
(ISC_EVENT_CALLBACK)qEventCallback, | |
#else | |
(isc_callback)qEventCallback, | |
#endif | |
eBuffer->resultBuffer); | |
if (status[0] == 1 && status[1]) { | |
qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'", | |
qPrintable(i.key())); | |
} | |
return; | |
} | |
} | |
} | |
QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType) const | |
{ | |
QString res = identifier; | |
if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { | |
res.replace(QLatin1Char('"'), QLatin1String("\"\"")); | |
res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); | |
res.replace(QLatin1Char('.'), QLatin1String("\".\"")); | |
} | |
return res; | |
} | |
QT_END_NAMESPACE |