| /**************************************************************************** |
| ** |
| ** 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 |