| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSql module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsql_oci_p.h" |
| |
| #include <qcoreapplication.h> |
| #include <qvariant.h> |
| #include <qdatetime.h> |
| #include <qmetatype.h> |
| #include <qregexp.h> |
| #include <qshareddata.h> |
| #include <qsqlerror.h> |
| #include <qsqlfield.h> |
| #include <qsqlindex.h> |
| #include <qsqlquery.h> |
| #include <QtSql/private/qsqlcachedresult_p.h> |
| #include <QtSql/private/qsqldriver_p.h> |
| #include <qstringlist.h> |
| #include <qvarlengtharray.h> |
| #include <qvector.h> |
| #include <qdebug.h> |
| #include <qtimezone.h> |
| |
| // This is needed for oracle oci when compiling with mingw-w64 headers |
| #if defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64) |
| #define _int64 __int64 |
| #endif |
| |
| |
| #include <oci.h> |
| #ifdef max |
| #undef max |
| #endif |
| #ifdef min |
| #undef min |
| #endif |
| |
| #include <stdlib.h> |
| |
| #define QOCI_DYNAMIC_CHUNK_SIZE 65535 |
| #define QOCI_PREFETCH_MEM 10240 |
| |
| // setting this define will allow using a query from a different |
| // thread than its database connection. |
| // warning - this is not fully tested and can lead to race conditions |
| #define QOCI_THREADED |
| |
| //#define QOCI_DEBUG |
| |
| Q_DECLARE_OPAQUE_POINTER(OCIEnv*); |
| Q_DECLARE_METATYPE(OCIEnv*) |
| Q_DECLARE_OPAQUE_POINTER(OCIStmt*); |
| Q_DECLARE_METATYPE(OCIStmt*) |
| |
| QT_BEGIN_NAMESPACE |
| |
| #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
| enum { QOCIEncoding = 2002 }; // AL16UTF16LE |
| #else |
| enum { QOCIEncoding = 2000 }; // AL16UTF16 |
| #endif |
| |
| #ifdef OCI_ATTR_CHARSET_FORM |
| // Always set the OCI_ATTR_CHARSET_FORM to SQLCS_NCHAR is safe |
| // because Oracle server will deal with the implicit Conversion |
| // Between CHAR and NCHAR. |
| // see: http://download.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89857/oci05bnd.htm#422705 |
| static const ub1 qOraCharsetForm = SQLCS_NCHAR; |
| #endif |
| |
| #if defined (OCI_UTF16ID) |
| static const ub2 qOraCharset = OCI_UTF16ID; |
| #else |
| static const ub2 qOraCharset = OCI_UCS2ID; |
| #endif |
| |
| typedef QVarLengthArray<sb2, 32> IndicatorArray; |
| typedef QVarLengthArray<ub2, 32> SizeArray; |
| |
| static QByteArray qMakeOCINumber(const qlonglong &ll, OCIError *err); |
| static QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err); |
| |
| static qlonglong qMakeLongLong(const char* ociNumber, OCIError* err); |
| static qulonglong qMakeULongLong(const char* ociNumber, OCIError* err); |
| |
| static QString qOraWarn(OCIError *err, int *errorCode = 0); |
| |
| #ifndef Q_CC_SUN |
| static // for some reason, Sun CC can't use qOraWarning when it's declared static |
| #endif |
| void qOraWarning(const char* msg, OCIError *err); |
| static QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err); |
| |
| |
| |
| class QOCIRowId: public QSharedData |
| { |
| public: |
| QOCIRowId(OCIEnv *env); |
| ~QOCIRowId(); |
| |
| OCIRowid *id; |
| |
| private: |
| QOCIRowId(const QOCIRowId &other): QSharedData(other) { Q_ASSERT(false); } |
| }; |
| |
| QOCIRowId::QOCIRowId(OCIEnv *env) |
| : id(0) |
| { |
| OCIDescriptorAlloc (env, reinterpret_cast<dvoid **>(&id), |
| OCI_DTYPE_ROWID, 0, 0); |
| } |
| |
| QOCIRowId::~QOCIRowId() |
| { |
| if (id) |
| OCIDescriptorFree(id, OCI_DTYPE_ROWID); |
| } |
| |
| class QOCIDateTime |
| { |
| public: |
| QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt = QDateTime()); |
| ~QOCIDateTime(); |
| OCIDateTime *dateTime; |
| static QDateTime fromOCIDateTime(OCIEnv *env, OCIError *err, OCIDateTime *dt); |
| }; |
| |
| QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt) |
| : dateTime(nullptr) |
| { |
| OCIDescriptorAlloc(env, reinterpret_cast<void**>(&dateTime), OCI_DTYPE_TIMESTAMP_TZ, 0, 0); |
| if (dt.isValid()) { |
| const QDate date = dt.date(); |
| const QTime time = dt.time(); |
| // Zone in +hh:mm format (stripping UTC prefix from OffsetName) |
| QString timeZone = dt.timeZone().displayName(dt, QTimeZone::OffsetName).mid(3); |
| const OraText *tz = reinterpret_cast<const OraText *>(timeZone.utf16()); |
| OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(), |
| time.minute(), time.second(), time.msec() * 1000000, |
| const_cast<OraText *>(tz), timeZone.length() * sizeof(QChar)); |
| } |
| } |
| |
| QOCIDateTime::~QOCIDateTime() |
| { |
| if (dateTime != nullptr) |
| OCIDescriptorFree(dateTime, OCI_DTYPE_TIMESTAMP_TZ); |
| } |
| |
| QDateTime QOCIDateTime::fromOCIDateTime(OCIEnv *env, OCIError *err, OCIDateTime *dateTime) |
| { |
| sb2 year; |
| ub1 month, day, hour, minute, second; |
| ub4 nsec; |
| sb1 tzHour, tzMinute; |
| |
| OCIDateTimeGetDate(env, err, dateTime, &year, &month, &day); |
| OCIDateTimeGetTime(env, err, dateTime, &hour, &minute, &second, &nsec); |
| OCIDateTimeGetTimeZoneOffset(env, err, dateTime, &tzHour, &tzMinute); |
| int secondsOffset = (qAbs(tzHour) * 60 + tzMinute) * 60; |
| if (tzHour < 0) |
| secondsOffset = -secondsOffset; |
| // OCIDateTimeGetTime gives "fractions of second" as nanoseconds |
| return QDateTime(QDate(year, month, day), QTime(hour, minute, second, nsec / 1000000), |
| Qt::OffsetFromUTC, secondsOffset); |
| } |
| |
| struct TempStorage { |
| QList<QByteArray> rawData; |
| QList<QOCIDateTime *> dateTimes; |
| }; |
| |
| typedef QSharedDataPointer<QOCIRowId> QOCIRowIdPointer; |
| QT_BEGIN_INCLUDE_NAMESPACE |
| Q_DECLARE_METATYPE(QOCIRowIdPointer) |
| QT_END_INCLUDE_NAMESPACE |
| |
| class QOCIDriverPrivate : public QSqlDriverPrivate |
| { |
| Q_DECLARE_PUBLIC(QOCIDriver) |
| |
| public: |
| QOCIDriverPrivate(); |
| |
| OCIEnv *env; |
| OCISvcCtx *svc; |
| OCIServer *srvhp; |
| OCISession *authp; |
| OCIError *err; |
| bool transaction; |
| int serverVersion; |
| int prefetchRows; |
| int prefetchMem; |
| QString user; |
| |
| void allocErrorHandle(); |
| }; |
| |
| class QOCICols; |
| class QOCIResultPrivate; |
| |
| class QOCIResult: public QSqlCachedResult |
| { |
| Q_DECLARE_PRIVATE(QOCIResult) |
| friend class QOCIDriver; |
| friend class QOCICols; |
| public: |
| QOCIResult(const QOCIDriver *db); |
| ~QOCIResult(); |
| bool prepare(const QString &query) override; |
| bool exec() override; |
| QVariant handle() const override; |
| |
| protected: |
| bool gotoNext(ValueCache &values, int index) override; |
| bool reset(const QString &query) override; |
| int size() override; |
| int numRowsAffected() override; |
| QSqlRecord record() const override; |
| QVariant lastInsertId() const override; |
| bool execBatch(bool arrayBind = false) override; |
| void virtual_hook(int id, void *data) override; |
| bool fetchNext() override; |
| }; |
| |
| class QOCIResultPrivate: public QSqlCachedResultPrivate |
| { |
| public: |
| Q_DECLARE_PUBLIC(QOCIResult) |
| Q_DECLARE_SQLDRIVER_PRIVATE(QOCIDriver) |
| QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv); |
| ~QOCIResultPrivate(); |
| |
| QOCICols *cols; |
| OCIEnv *env; |
| OCIError *err; |
| OCISvcCtx *&svc; |
| OCIStmt *sql; |
| bool transaction; |
| int serverVersion; |
| int prefetchRows, prefetchMem; |
| |
| void setStatementAttributes(); |
| int bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, |
| const QVariant &val, dvoid *indPtr, ub2 *tmpSize, TempStorage &tmpStorage); |
| int bindValues(QVector<QVariant> &values, IndicatorArray &indicators, SizeArray &tmpSizes, |
| TempStorage &tmpStorage); |
| void outValues(QVector<QVariant> &values, IndicatorArray &indicators, |
| TempStorage &tmpStorage); |
| inline bool isOutValue(int i) const |
| { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Out; } |
| inline bool isBinaryValue(int i) const |
| { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Binary; } |
| |
| void setCharset(dvoid* handle, ub4 type) const |
| { |
| int r = 0; |
| Q_ASSERT(handle); |
| |
| #ifdef OCI_ATTR_CHARSET_FORM |
| r = OCIAttrSet(handle, |
| type, |
| // this const cast is safe since OCI doesn't touch |
| // the charset. |
| const_cast<void *>(static_cast<const void *>(&qOraCharsetForm)), |
| 0, |
| OCI_ATTR_CHARSET_FORM, |
| //Strange Oracle bug: some Oracle servers crash the server process with non-zero error handle (mostly for 10g). |
| //So ignore the error message here. |
| 0); |
| #ifdef QOCI_DEBUG |
| if (r != 0) |
| qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM."); |
| #endif |
| #endif |
| |
| r = OCIAttrSet(handle, |
| type, |
| // this const cast is safe since OCI doesn't touch |
| // the charset. |
| const_cast<void *>(static_cast<const void *>(&qOraCharset)), |
| 0, |
| OCI_ATTR_CHARSET_ID, |
| err); |
| if (r != 0) |
| qOraWarning("QOCIResultPrivate::setCharsetI Couldn't set OCI_ATTR_CHARSET_ID: ", err); |
| |
| } |
| }; |
| |
| void QOCIResultPrivate::setStatementAttributes() |
| { |
| Q_ASSERT(sql); |
| |
| int r = 0; |
| |
| if (prefetchRows >= 0) { |
| r = OCIAttrSet(sql, |
| OCI_HTYPE_STMT, |
| &prefetchRows, |
| 0, |
| OCI_ATTR_PREFETCH_ROWS, |
| err); |
| if (r != 0) |
| qOraWarning("QOCIResultPrivate::setStatementAttributes:" |
| " Couldn't set OCI_ATTR_PREFETCH_ROWS: ", err); |
| } |
| if (prefetchMem >= 0) { |
| r = OCIAttrSet(sql, |
| OCI_HTYPE_STMT, |
| &prefetchMem, |
| 0, |
| OCI_ATTR_PREFETCH_MEMORY, |
| err); |
| if (r != 0) |
| qOraWarning("QOCIResultPrivate::setStatementAttributes:" |
| " Couldn't set OCI_ATTR_PREFETCH_MEMORY: ", err); |
| } |
| } |
| |
| int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, |
| const QVariant &val, dvoid *indPtr, ub2 *tmpSize, TempStorage &tmpStorage) |
| { |
| int r = OCI_SUCCESS; |
| void *data = const_cast<void *>(val.constData()); |
| |
| switch (val.type()) { |
| case QVariant::ByteArray: |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| isOutValue(pos) |
| ? const_cast<char *>(reinterpret_cast<QByteArray *>(data)->constData()) |
| : reinterpret_cast<QByteArray *>(data)->data(), |
| reinterpret_cast<QByteArray *>(data)->size(), |
| SQLT_BIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::Time: |
| case QVariant::Date: |
| case QVariant::DateTime: { |
| QOCIDateTime *ptr = new QOCIDateTime(env, err, val.toDateTime()); |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| &ptr->dateTime, |
| sizeof(OCIDateTime *), |
| SQLT_TIMESTAMP_TZ, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| tmpStorage.dateTimes.append(ptr); |
| break; |
| } |
| case QVariant::Int: |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| // if it's an out value, the data is already detached |
| // so the const cast is safe. |
| const_cast<void *>(data), |
| sizeof(int), |
| SQLT_INT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::UInt: |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| // if it's an out value, the data is already detached |
| // so the const cast is safe. |
| const_cast<void *>(data), |
| sizeof(uint), |
| SQLT_UIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::LongLong: |
| { |
| QByteArray ba = qMakeOCINumber(val.toLongLong(), err); |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| ba.data(), |
| ba.size(), |
| SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| tmpStorage.rawData.append(ba); |
| break; |
| } |
| case QVariant::ULongLong: |
| { |
| QByteArray ba = qMakeOCINumber(val.toULongLong(), err); |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| ba.data(), |
| ba.size(), |
| SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| tmpStorage.rawData.append(ba); |
| break; |
| } |
| case QVariant::Double: |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| // if it's an out value, the data is already detached |
| // so the const cast is safe. |
| const_cast<void *>(data), |
| sizeof(double), |
| SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::UserType: |
| if (val.canConvert<QOCIRowIdPointer>() && !isOutValue(pos)) { |
| // use a const pointer to prevent a detach |
| const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val); |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| // it's an IN value, so const_cast is ok |
| const_cast<OCIRowid **>(&rptr->id), |
| -1, |
| SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| } else { |
| qWarning("Unknown bind variable"); |
| r = OCI_ERROR; |
| } |
| break; |
| case QVariant::String: { |
| const QString s = val.toString(); |
| if (isBinaryValue(pos)) { |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| const_cast<ushort *>(s.utf16()), |
| s.length() * sizeof(QChar), |
| SQLT_LNG, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| break; |
| } else if (!isOutValue(pos)) { |
| // don't detach the string |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| // safe since oracle doesn't touch OUT values |
| const_cast<ushort *>(s.utf16()), |
| (s.length() + 1) * sizeof(QChar), |
| SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| if (r == OCI_SUCCESS) |
| setCharset(*hbnd, OCI_HTYPE_BIND); |
| break; |
| } |
| } // fall through for OUT values |
| default: { |
| const QString s = val.toString(); |
| // create a deep-copy |
| QByteArray ba(reinterpret_cast<const char *>(s.utf16()), (s.length() + 1) * sizeof(QChar)); |
| if (isOutValue(pos)) { |
| ba.reserve((s.capacity() + 1) * sizeof(QChar)); |
| *tmpSize = ba.size(); |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| ba.data(), |
| ba.capacity(), |
| SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT); |
| } else { |
| r = OCIBindByPos(sql, hbnd, err, |
| pos + 1, |
| ba.data(), |
| ba.size(), |
| SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); |
| } |
| if (r == OCI_SUCCESS) |
| setCharset(*hbnd, OCI_HTYPE_BIND); |
| tmpStorage.rawData.append(ba); |
| break; |
| } // default case |
| } // switch |
| if (r != OCI_SUCCESS) |
| qOraWarning("QOCIResultPrivate::bindValue:", err); |
| return r; |
| } |
| |
| int QOCIResultPrivate::bindValues(QVector<QVariant> &values, IndicatorArray &indicators, |
| SizeArray &tmpSizes, TempStorage &tmpStorage) |
| { |
| int r = OCI_SUCCESS; |
| for (int i = 0; i < values.count(); ++i) { |
| if (isOutValue(i)) |
| values[i].detach(); |
| const QVariant &val = values.at(i); |
| |
| OCIBind * hbnd = 0; // Oracle handles these automatically |
| sb2 *indPtr = &indicators[i]; |
| *indPtr = val.isNull() ? -1 : 0; |
| |
| bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage); |
| } |
| return r; |
| } |
| |
| // will assign out value and remove its temp storage. |
| static void qOraOutValue(QVariant &value, TempStorage &tmpStorage, OCIEnv *env, OCIError* err) |
| { |
| switch (value.type()) { |
| case QVariant::Time: |
| value = QOCIDateTime::fromOCIDateTime(env, err, |
| tmpStorage.dateTimes.takeFirst()->dateTime).time(); |
| break; |
| case QVariant::Date: |
| value = QOCIDateTime::fromOCIDateTime(env, err, |
| tmpStorage.dateTimes.takeFirst()->dateTime).date(); |
| break; |
| case QVariant::DateTime: |
| value = QOCIDateTime::fromOCIDateTime(env, err, |
| tmpStorage.dateTimes.takeFirst()->dateTime); |
| break; |
| case QVariant::LongLong: |
| value = qMakeLongLong(tmpStorage.rawData.takeFirst(), err); |
| break; |
| case QVariant::ULongLong: |
| value = qMakeULongLong(tmpStorage.rawData.takeFirst(), err); |
| break; |
| case QVariant::String: |
| value = QString( |
| reinterpret_cast<const QChar *>(tmpStorage.rawData.takeFirst().constData())); |
| break; |
| default: |
| break; //nothing |
| } |
| } |
| |
| void QOCIResultPrivate::outValues(QVector<QVariant> &values, IndicatorArray &indicators, |
| TempStorage &tmpStorage) |
| { |
| for (int i = 0; i < values.count(); ++i) { |
| |
| if (!isOutValue(i)) |
| continue; |
| |
| qOraOutValue(values[i], tmpStorage, env, err); |
| |
| QVariant::Type typ = values.at(i).type(); |
| if (indicators[i] == -1) // NULL |
| values[i] = QVariant(typ); |
| else |
| values[i] = QVariant(typ, values.at(i).constData()); |
| } |
| } |
| |
| |
| QOCIDriverPrivate::QOCIDriverPrivate() |
| : QSqlDriverPrivate(), env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false), |
| serverVersion(-1), prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM) |
| { |
| dbmsType = QSqlDriver::Oracle; |
| } |
| |
| void QOCIDriverPrivate::allocErrorHandle() |
| { |
| int r = OCIHandleAlloc(env, |
| reinterpret_cast<void **>(&err), |
| OCI_HTYPE_ERROR, |
| 0, |
| 0); |
| if (r != 0) |
| qWarning("QOCIDriver: unable to allocate error handle"); |
| } |
| |
| struct OraFieldInfo |
| { |
| QString name; |
| QVariant::Type type; |
| ub1 oraIsNull; |
| ub4 oraType; |
| sb1 oraScale; |
| ub4 oraLength; // size in bytes |
| ub4 oraFieldLength; // amount of characters |
| sb2 oraPrecision; |
| }; |
| |
| QString qOraWarn(OCIError *err, int *errorCode) |
| { |
| sb4 errcode; |
| text errbuf[1024]; |
| errbuf[0] = 0; |
| errbuf[1] = 0; |
| |
| OCIErrorGet(err, |
| 1, |
| 0, |
| &errcode, |
| errbuf, |
| sizeof(errbuf), |
| OCI_HTYPE_ERROR); |
| if (errorCode) |
| *errorCode = errcode; |
| return QString(reinterpret_cast<const QChar *>(errbuf)); |
| } |
| |
| void qOraWarning(const char* msg, OCIError *err) |
| { |
| #ifdef QOCI_DEBUG |
| qWarning("%s %s", msg, qPrintable(qOraWarn(err))); |
| #else |
| Q_UNUSED(msg); |
| Q_UNUSED(err); |
| #endif |
| } |
| |
| static int qOraErrorNumber(OCIError *err) |
| { |
| sb4 errcode; |
| OCIErrorGet(err, |
| 1, |
| 0, |
| &errcode, |
| 0, |
| 0, |
| OCI_HTYPE_ERROR); |
| return errcode; |
| } |
| |
| QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err) |
| { |
| int errorCode = 0; |
| const QString oraErrorString = qOraWarn(err, &errorCode); |
| return QSqlError(errString, oraErrorString, type, |
| errorCode != -1 ? QString::number(errorCode) : QString()); |
| } |
| |
| QVariant::Type qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) |
| { |
| QVariant::Type type = QVariant::Invalid; |
| if (ocitype == QLatin1String("VARCHAR2") || ocitype == QLatin1String("VARCHAR") |
| || ocitype.startsWith(QLatin1String("INTERVAL")) |
| || ocitype == QLatin1String("CHAR") || ocitype == QLatin1String("NVARCHAR2") |
| || ocitype == QLatin1String("NCHAR")) |
| type = QVariant::String; |
| else if (ocitype == QLatin1String("NUMBER") |
| || ocitype == QLatin1String("FLOAT") |
| || ocitype == QLatin1String("BINARY_FLOAT") |
| || ocitype == QLatin1String("BINARY_DOUBLE")) { |
| switch(precisionPolicy) { |
| case QSql::LowPrecisionInt32: |
| type = QVariant::Int; |
| break; |
| case QSql::LowPrecisionInt64: |
| type = QVariant::LongLong; |
| break; |
| case QSql::LowPrecisionDouble: |
| type = QVariant::Double; |
| break; |
| case QSql::HighPrecision: |
| default: |
| type = QVariant::String; |
| break; |
| } |
| } |
| else if (ocitype == QLatin1String("LONG") || ocitype == QLatin1String("NCLOB") |
| || ocitype == QLatin1String("CLOB")) |
| type = QVariant::ByteArray; |
| else if (ocitype == QLatin1String("RAW") || ocitype == QLatin1String("LONG RAW") |
| || ocitype == QLatin1String("ROWID") || ocitype == QLatin1String("BLOB") |
| || ocitype == QLatin1String("CFILE") || ocitype == QLatin1String("BFILE")) |
| type = QVariant::ByteArray; |
| else if (ocitype == QLatin1String("DATE") || ocitype.startsWith(QLatin1String("TIME"))) |
| type = QVariant::DateTime; |
| else if (ocitype == QLatin1String("UNDEFINED")) |
| type = QVariant::Invalid; |
| if (type == QVariant::Invalid) |
| qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData()); |
| return type; |
| } |
| |
| QVariant::Type qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) |
| { |
| QVariant::Type type = QVariant::Invalid; |
| switch (ocitype) { |
| case SQLT_STR: |
| case SQLT_VST: |
| case SQLT_CHR: |
| case SQLT_AFC: |
| case SQLT_VCS: |
| case SQLT_AVC: |
| case SQLT_RDD: |
| case SQLT_LNG: |
| #ifdef SQLT_INTERVAL_YM |
| case SQLT_INTERVAL_YM: |
| #endif |
| #ifdef SQLT_INTERVAL_DS |
| case SQLT_INTERVAL_DS: |
| #endif |
| type = QVariant::String; |
| break; |
| case SQLT_INT: |
| type = QVariant::Int; |
| break; |
| case SQLT_FLT: |
| case SQLT_NUM: |
| case SQLT_VNU: |
| case SQLT_UIN: |
| switch(precisionPolicy) { |
| case QSql::LowPrecisionInt32: |
| type = QVariant::Int; |
| break; |
| case QSql::LowPrecisionInt64: |
| type = QVariant::LongLong; |
| break; |
| case QSql::LowPrecisionDouble: |
| type = QVariant::Double; |
| break; |
| case QSql::HighPrecision: |
| default: |
| type = QVariant::String; |
| break; |
| } |
| break; |
| case SQLT_VBI: |
| case SQLT_BIN: |
| case SQLT_LBI: |
| case SQLT_LVC: |
| case SQLT_LVB: |
| case SQLT_BLOB: |
| case SQLT_CLOB: |
| case SQLT_FILE: |
| case SQLT_NTY: |
| case SQLT_REF: |
| case SQLT_RID: |
| type = QVariant::ByteArray; |
| break; |
| case SQLT_DAT: |
| case SQLT_ODT: |
| case SQLT_TIMESTAMP: |
| case SQLT_TIMESTAMP_TZ: |
| case SQLT_TIMESTAMP_LTZ: |
| type = QVariant::DateTime; |
| break; |
| default: |
| type = QVariant::Invalid; |
| qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype); |
| break; |
| } |
| return type; |
| } |
| |
| static QSqlField qFromOraInf(const OraFieldInfo &ofi) |
| { |
| QSqlField f(ofi.name, ofi.type); |
| f.setRequired(ofi.oraIsNull == 0); |
| |
| if (ofi.type == QVariant::String && ofi.oraType != SQLT_NUM && ofi.oraType != SQLT_VNU) |
| f.setLength(ofi.oraFieldLength); |
| else |
| f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision)); |
| |
| f.setPrecision(ofi.oraScale); |
| f.setSqlType(int(ofi.oraType)); |
| return f; |
| } |
| |
| /*! |
| \internal |
| |
| Convert qlonglong to the internal Oracle OCINumber format. |
| */ |
| QByteArray qMakeOCINumber(const qlonglong& ll, OCIError* err) |
| { |
| QByteArray ba(sizeof(OCINumber), 0); |
| |
| OCINumberFromInt(err, |
| &ll, |
| sizeof(qlonglong), |
| OCI_NUMBER_SIGNED, |
| reinterpret_cast<OCINumber*>(ba.data())); |
| return ba; |
| } |
| |
| /*! |
| \internal |
| |
| Convert qulonglong to the internal Oracle OCINumber format. |
| */ |
| QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err) |
| { |
| QByteArray ba(sizeof(OCINumber), 0); |
| |
| OCINumberFromInt(err, |
| &ull, |
| sizeof(qlonglong), |
| OCI_NUMBER_UNSIGNED, |
| reinterpret_cast<OCINumber*>(ba.data())); |
| return ba; |
| } |
| |
| qlonglong qMakeLongLong(const char* ociNumber, OCIError* err) |
| { |
| qlonglong qll = 0; |
| OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qlonglong), |
| OCI_NUMBER_SIGNED, &qll); |
| return qll; |
| } |
| |
| qulonglong qMakeULongLong(const char* ociNumber, OCIError* err) |
| { |
| qulonglong qull = 0; |
| OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qulonglong), |
| OCI_NUMBER_UNSIGNED, &qull); |
| return qull; |
| } |
| |
| class QOCICols |
| { |
| public: |
| QOCICols(int size, QOCIResultPrivate* dp); |
| ~QOCICols(); |
| int readPiecewise(QVector<QVariant> &values, int index = 0); |
| int readLOBs(QVector<QVariant> &values, int index = 0); |
| int fieldFromDefine(OCIDefine* d); |
| void getValues(QVector<QVariant> &v, int index); |
| inline int size() { return fieldInf.size(); } |
| static bool execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind); |
| |
| QSqlRecord rec; |
| |
| private: |
| char* create(int position, int size); |
| OCILobLocator ** createLobLocator(int position, OCIEnv* env); |
| OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const; |
| |
| class OraFieldInf |
| { |
| public: |
| OraFieldInf() : data(0), len(0), ind(0), typ(QVariant::Invalid), oraType(0), def(0), lob(0), dataPtr(nullptr) |
| {} |
| ~OraFieldInf(); |
| char *data; |
| int len; |
| sb2 ind; |
| QVariant::Type typ; |
| ub4 oraType; |
| OCIDefine *def; |
| OCILobLocator *lob; |
| void *dataPtr; |
| }; |
| |
| QVector<OraFieldInf> fieldInf; |
| const QOCIResultPrivate *const d; |
| }; |
| |
| QOCICols::OraFieldInf::~OraFieldInf() |
| { |
| delete [] data; |
| if (lob) { |
| int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB); |
| if (r != 0) |
| qWarning("QOCICols: Cannot free LOB descriptor"); |
| } |
| if (dataPtr) { |
| switch (typ) { |
| case QVariant::Date: |
| case QVariant::Time: |
| case QVariant::DateTime: { |
| int r = OCIDescriptorFree(dataPtr, OCI_DTYPE_TIMESTAMP_TZ); |
| if (r != OCI_SUCCESS) |
| qWarning("QOCICols: Cannot free OCIDateTime descriptor"); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| |
| QOCICols::QOCICols(int size, QOCIResultPrivate* dp) |
| : fieldInf(size), d(dp) |
| { |
| ub4 dataSize = 0; |
| OCIDefine* dfn = 0; |
| int r; |
| |
| OCIParam* param = 0; |
| sb4 parmStatus = 0; |
| ub4 count = 1; |
| int idx = 0; |
| parmStatus = OCIParamGet(d->sql, |
| OCI_HTYPE_STMT, |
| d->err, |
| reinterpret_cast<void **>(¶m), |
| count); |
| |
| while (parmStatus == OCI_SUCCESS) { |
| OraFieldInfo ofi = qMakeOraField(d, param); |
| if (ofi.oraType == SQLT_RDD) |
| dataSize = 50; |
| #ifdef SQLT_INTERVAL_YM |
| #ifdef SQLT_INTERVAL_DS |
| else if (ofi.oraType == SQLT_INTERVAL_YM || ofi.oraType == SQLT_INTERVAL_DS) |
| // since we are binding interval datatype as string, |
| // we are not interested in the number of bytes but characters. |
| dataSize = 50; // magic number |
| #endif //SQLT_INTERVAL_DS |
| #endif //SQLT_INTERVAL_YM |
| else if (ofi.oraType == SQLT_NUM || ofi.oraType == SQLT_VNU){ |
| if (ofi.oraPrecision > 0) |
| dataSize = (ofi.oraPrecision + 1) * sizeof(utext); |
| else |
| dataSize = (38 + 1) * sizeof(utext); |
| } |
| else |
| dataSize = ofi.oraLength; |
| |
| fieldInf[idx].typ = ofi.type; |
| fieldInf[idx].oraType = ofi.oraType; |
| rec.append(qFromOraInf(ofi)); |
| |
| switch (ofi.type) { |
| case QVariant::DateTime: |
| r = OCIDescriptorAlloc(d->env, (void **)&fieldInf[idx].dataPtr, OCI_DTYPE_TIMESTAMP_TZ, 0, 0); |
| if (r != OCI_SUCCESS) { |
| qWarning("QOCICols: Unable to allocate the OCIDateTime descriptor"); |
| break; |
| } |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| &fieldInf[idx].dataPtr, |
| sizeof(OCIDateTime *), |
| SQLT_TIMESTAMP_TZ, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::Double: |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, sizeof(double) - 1), |
| sizeof(double), |
| SQLT_FLT, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::Int: |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, sizeof(qint32) - 1), |
| sizeof(qint32), |
| SQLT_INT, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::LongLong: |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, sizeof(OCINumber)), |
| sizeof(OCINumber), |
| SQLT_VNU, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| break; |
| case QVariant::ByteArray: |
| // RAW and LONG RAW fields can't be bound to LOB locators |
| if (ofi.oraType == SQLT_BIN) { |
| // qDebug("binding SQLT_BIN"); |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, dataSize), |
| dataSize, |
| SQLT_BIN, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DYNAMIC_FETCH); |
| } else if (ofi.oraType == SQLT_LBI) { |
| // qDebug("binding SQLT_LBI"); |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| 0, |
| SB4MAXVAL, |
| SQLT_LBI, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DYNAMIC_FETCH); |
| } else if (ofi.oraType == SQLT_CLOB) { |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| createLobLocator(idx, d->env), |
| -1, |
| SQLT_CLOB, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| } else { |
| // qDebug("binding SQLT_BLOB"); |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| createLobLocator(idx, d->env), |
| -1, |
| SQLT_BLOB, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| } |
| break; |
| case QVariant::String: |
| if (ofi.oraType == SQLT_LNG) { |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| 0, |
| SB4MAXVAL, |
| SQLT_LNG, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DYNAMIC_FETCH); |
| } else { |
| dataSize += dataSize + sizeof(QChar); |
| //qDebug("OCIDefineByPosStr(%d): %d", count, dataSize); |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, dataSize), |
| dataSize, |
| SQLT_STR, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| if (r == 0) |
| d->setCharset(dfn, OCI_HTYPE_DEFINE); |
| } |
| break; |
| default: |
| // this should make enough space even with character encoding |
| dataSize = (dataSize + 1) * sizeof(utext) ; |
| //qDebug("OCIDefineByPosDef(%d): %d", count, dataSize); |
| r = OCIDefineByPos(d->sql, |
| &dfn, |
| d->err, |
| count, |
| create(idx, dataSize), |
| dataSize+1, |
| SQLT_STR, |
| &(fieldInf[idx].ind), |
| 0, 0, OCI_DEFAULT); |
| break; |
| } |
| if (r != 0) |
| qOraWarning("QOCICols::bind:", d->err); |
| fieldInf[idx].def = dfn; |
| ++count; |
| ++idx; |
| parmStatus = OCIParamGet(d->sql, |
| OCI_HTYPE_STMT, |
| d->err, |
| reinterpret_cast<void **>(¶m), |
| count); |
| } |
| } |
| |
| QOCICols::~QOCICols() |
| { |
| } |
| |
| char* QOCICols::create(int position, int size) |
| { |
| char* c = new char[size+1]; |
| // Oracle may not fill fixed width fields |
| memset(c, 0, size+1); |
| fieldInf[position].data = c; |
| fieldInf[position].len = size; |
| return c; |
| } |
| |
| OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env) |
| { |
| OCILobLocator *& lob = fieldInf[position].lob; |
| int r = OCIDescriptorAlloc(env, |
| reinterpret_cast<void **>(&lob), |
| OCI_DTYPE_LOB, |
| 0, |
| 0); |
| if (r != 0) { |
| qWarning("QOCICols: Cannot create LOB locator"); |
| lob = 0; |
| } |
| return &lob; |
| } |
| |
| int QOCICols::readPiecewise(QVector<QVariant> &values, int index) |
| { |
| OCIDefine* dfn; |
| ub4 typep; |
| ub1 in_outp; |
| ub4 iterp; |
| ub4 idxp; |
| ub1 piecep; |
| sword status; |
| text col [QOCI_DYNAMIC_CHUNK_SIZE+1]; |
| int fieldNum = -1; |
| int r = 0; |
| bool nullField; |
| |
| do { |
| r = OCIStmtGetPieceInfo(d->sql, d->err, reinterpret_cast<void **>(&dfn), &typep, |
| &in_outp, &iterp, &idxp, &piecep); |
| if (r != OCI_SUCCESS) |
| qOraWarning("OCIResultPrivate::readPiecewise: unable to get piece info:", d->err); |
| fieldNum = fieldFromDefine(dfn); |
| bool isStringField = fieldInf.at(fieldNum).oraType == SQLT_LNG; |
| ub4 chunkSize = QOCI_DYNAMIC_CHUNK_SIZE; |
| nullField = false; |
| r = OCIStmtSetPieceInfo(dfn, OCI_HTYPE_DEFINE, |
| d->err, col, |
| &chunkSize, piecep, NULL, NULL); |
| if (r != OCI_SUCCESS) |
| qOraWarning("OCIResultPrivate::readPiecewise: unable to set piece info:", d->err); |
| status = OCIStmtFetch (d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); |
| if (status == -1) { |
| sb4 errcode; |
| OCIErrorGet(d->err, 1, 0, &errcode, 0, 0,OCI_HTYPE_ERROR); |
| switch (errcode) { |
| case 1405: /* NULL */ |
| nullField = true; |
| break; |
| default: |
| qOraWarning("OCIResultPrivate::readPiecewise: unable to fetch next:", d->err); |
| break; |
| } |
| } |
| if (status == OCI_NO_DATA) |
| break; |
| if (nullField || !chunkSize) { |
| fieldInf[fieldNum].ind = -1; |
| } else { |
| if (isStringField) { |
| QString str = values.at(fieldNum + index).toString(); |
| str += QString(reinterpret_cast<const QChar *>(col), chunkSize / 2); |
| values[fieldNum + index] = str; |
| fieldInf[fieldNum].ind = 0; |
| } else { |
| QByteArray ba = values.at(fieldNum + index).toByteArray(); |
| int sz = ba.size(); |
| ba.resize(sz + chunkSize); |
| memcpy(ba.data() + sz, reinterpret_cast<char *>(col), chunkSize); |
| values[fieldNum + index] = ba; |
| fieldInf[fieldNum].ind = 0; |
| } |
| } |
| } while (status == OCI_SUCCESS_WITH_INFO || status == OCI_NEED_DATA); |
| return r; |
| } |
| |
| OraFieldInfo QOCICols::qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const |
| { |
| OraFieldInfo ofi; |
| ub2 colType(0); |
| text *colName = 0; |
| ub4 colNameLen(0); |
| sb1 colScale(0); |
| ub2 colLength(0); |
| ub2 colFieldLength(0); |
| sb2 colPrecision(0); |
| ub1 colIsNull(0); |
| int r(0); |
| QVariant::Type type(QVariant::Invalid); |
| |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colType, |
| 0, |
| OCI_ATTR_DATA_TYPE, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colName, |
| &colNameLen, |
| OCI_ATTR_NAME, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colLength, |
| 0, |
| OCI_ATTR_DATA_SIZE, /* in bytes */ |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| |
| #ifdef OCI_ATTR_CHAR_SIZE |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colFieldLength, |
| 0, |
| OCI_ATTR_CHAR_SIZE, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| #else |
| // for Oracle8. |
| colFieldLength = colLength; |
| #endif |
| |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colPrecision, |
| 0, |
| OCI_ATTR_PRECISION, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colScale, |
| 0, |
| OCI_ATTR_SCALE, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colType, |
| 0, |
| OCI_ATTR_DATA_TYPE, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| r = OCIAttrGet(param, |
| OCI_DTYPE_PARAM, |
| &colIsNull, |
| 0, |
| OCI_ATTR_IS_NULL, |
| p->err); |
| if (r != 0) |
| qOraWarning("qMakeOraField:", p->err); |
| |
| type = qDecodeOCIType(colType, p->q_func()->numericalPrecisionPolicy()); |
| |
| if (type == QVariant::Int) { |
| if (colLength == 22 && colPrecision == 0 && colScale == 0) |
| type = QVariant::String; |
| if (colScale > 0) |
| type = QVariant::String; |
| } |
| |
| // bind as double if the precision policy asks for it |
| if (((colType == SQLT_FLT) || (colType == SQLT_NUM)) |
| && (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)) { |
| type = QVariant::Double; |
| } |
| |
| // bind as int32 or int64 if the precision policy asks for it |
| if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN) |
| || (colType == SQLT_INT)) { |
| if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) |
| type = QVariant::LongLong; |
| else if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) |
| type = QVariant::Int; |
| } |
| |
| if (colType == SQLT_BLOB) |
| colLength = 0; |
| |
| // colNameLen is length in bytes |
| ofi.name = QString(reinterpret_cast<const QChar*>(colName), colNameLen / 2); |
| ofi.type = type; |
| ofi.oraType = colType; |
| ofi.oraFieldLength = colFieldLength; |
| ofi.oraLength = colLength; |
| ofi.oraScale = colScale; |
| ofi.oraPrecision = colPrecision; |
| ofi.oraIsNull = colIsNull; |
| |
| return ofi; |
| } |
| |
| struct QOCIBatchColumn |
| { |
| inline QOCIBatchColumn() |
| : bindh(0), bindAs(0), maxLen(0), recordCount(0), |
| data(0), lengths(0), indicators(0), maxarr_len(0), curelep(0) {} |
| |
| OCIBind* bindh; |
| ub2 bindAs; |
| ub4 maxLen; |
| ub4 recordCount; |
| char* data; |
| ub4* lengths; |
| sb2* indicators; |
| ub4 maxarr_len; |
| ub4 curelep; |
| }; |
| |
| struct QOCIBatchCleanupHandler |
| { |
| inline QOCIBatchCleanupHandler(QVector<QOCIBatchColumn> &columns) |
| : col(columns) {} |
| |
| ~QOCIBatchCleanupHandler() |
| { |
| // deleting storage, length and indicator arrays |
| for ( int j = 0; j < col.count(); ++j){ |
| delete[] col[j].lengths; |
| delete[] col[j].indicators; |
| delete[] col[j].data; |
| } |
| } |
| |
| QVector<QOCIBatchColumn> &col; |
| }; |
| |
| bool QOCICols::execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind) |
| { |
| int columnCount = boundValues.count(); |
| if (boundValues.isEmpty() || columnCount == 0) |
| return false; |
| |
| #ifdef QOCI_DEBUG |
| qDebug() << "columnCount:" << columnCount << boundValues; |
| #endif |
| |
| int i; |
| sword r; |
| |
| QVarLengthArray<QVariant::Type> fieldTypes; |
| for (i = 0; i < columnCount; ++i) { |
| QVariant::Type tp = boundValues.at(i).type(); |
| fieldTypes.append(tp == QVariant::List ? boundValues.at(i).toList().value(0).type() |
| : tp); |
| } |
| SizeArray tmpSizes(columnCount); |
| QVector<QOCIBatchColumn> columns(columnCount); |
| QOCIBatchCleanupHandler cleaner(columns); |
| TempStorage tmpStorage; |
| |
| // figuring out buffer sizes |
| for (i = 0; i < columnCount; ++i) { |
| |
| if (boundValues.at(i).type() != QVariant::List) { |
| |
| // not a list - create a deep-copy of the single value |
| QOCIBatchColumn &singleCol = columns[i]; |
| singleCol.indicators = new sb2[1]; |
| *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0; |
| |
| r = d->bindValue(d->sql, &singleCol.bindh, d->err, i, |
| boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage); |
| |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); |
| d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to bind column for batch execute"), |
| QSqlError::StatementError, d->err)); |
| return false; |
| } |
| continue; |
| } |
| |
| QOCIBatchColumn &col = columns[i]; |
| col.recordCount = boundValues.at(i).toList().count(); |
| |
| col.lengths = new ub4[col.recordCount]; |
| col.indicators = new sb2[col.recordCount]; |
| col.maxarr_len = col.recordCount; |
| col.curelep = col.recordCount; |
| |
| switch (fieldTypes[i]) { |
| case QVariant::Time: |
| case QVariant::Date: |
| case QVariant::DateTime: |
| col.bindAs = SQLT_TIMESTAMP_TZ; |
| col.maxLen = sizeof(OCIDateTime *); |
| break; |
| |
| case QVariant::Int: |
| col.bindAs = SQLT_INT; |
| col.maxLen = sizeof(int); |
| break; |
| |
| case QVariant::UInt: |
| col.bindAs = SQLT_UIN; |
| col.maxLen = sizeof(uint); |
| break; |
| |
| case QVariant::LongLong: |
| col.bindAs = SQLT_VNU; |
| col.maxLen = sizeof(OCINumber); |
| break; |
| |
| case QVariant::ULongLong: |
| col.bindAs = SQLT_VNU; |
| col.maxLen = sizeof(OCINumber); |
| break; |
| |
| case QVariant::Double: |
| col.bindAs = SQLT_FLT; |
| col.maxLen = sizeof(double); |
| break; |
| |
| case QVariant::UserType: |
| col.bindAs = SQLT_RDD; |
| col.maxLen = sizeof(OCIRowid*); |
| break; |
| |
| case QVariant::String: { |
| col.bindAs = SQLT_STR; |
| for (uint j = 0; j < col.recordCount; ++j) { |
| uint len; |
| if(d->isOutValue(i)) |
| len = boundValues.at(i).toList().at(j).toString().capacity() + 1; |
| else |
| len = boundValues.at(i).toList().at(j).toString().length() + 1; |
| if (len > col.maxLen) |
| col.maxLen = len; |
| } |
| col.maxLen *= sizeof(QChar); |
| break; } |
| |
| case QVariant::ByteArray: |
| default: { |
| col.bindAs = SQLT_LBI; |
| for (uint j = 0; j < col.recordCount; ++j) { |
| if(d->isOutValue(i)) |
| col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().capacity(); |
| else |
| col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().size(); |
| if (col.lengths[j] > col.maxLen) |
| col.maxLen = col.lengths[j]; |
| } |
| break; } |
| } |
| |
| col.data = new char[col.maxLen * col.recordCount]; |
| memset(col.data, 0, col.maxLen * col.recordCount); |
| |
| // we may now populate column with data |
| for (uint row = 0; row < col.recordCount; ++row) { |
| const QVariant &val = boundValues.at(i).toList().at(row); |
| |
| if (val.isNull() && !d->isOutValue(i)) { |
| columns[i].indicators[row] = -1; |
| columns[i].lengths[row] = 0; |
| } else { |
| columns[i].indicators[row] = 0; |
| char *dataPtr = columns[i].data + (columns[i].maxLen * row); |
| switch (fieldTypes[i]) { |
| case QVariant::Time: |
| case QVariant::Date: |
| case QVariant::DateTime:{ |
| columns[i].lengths[row] = columns[i].maxLen; |
| QOCIDateTime *date = new QOCIDateTime(d->env, d->err, val.toDateTime()); |
| *reinterpret_cast<OCIDateTime**>(dataPtr) = date->dateTime; |
| break; |
| } |
| case QVariant::Int: |
| columns[i].lengths[row] = columns[i].maxLen; |
| *reinterpret_cast<int*>(dataPtr) = val.toInt(); |
| break; |
| |
| case QVariant::UInt: |
| columns[i].lengths[row] = columns[i].maxLen; |
| *reinterpret_cast<uint*>(dataPtr) = val.toUInt(); |
| break; |
| |
| case QVariant::LongLong: |
| { |
| columns[i].lengths[row] = columns[i].maxLen; |
| const QByteArray ba = qMakeOCINumber(val.toLongLong(), d->err); |
| Q_ASSERT(ba.size() == int(columns[i].maxLen)); |
| memcpy(dataPtr, ba.constData(), columns[i].maxLen); |
| break; |
| } |
| case QVariant::ULongLong: |
| { |
| columns[i].lengths[row] = columns[i].maxLen; |
| const QByteArray ba = qMakeOCINumber(val.toULongLong(), d->err); |
| Q_ASSERT(ba.size() == int(columns[i].maxLen)); |
| memcpy(dataPtr, ba.constData(), columns[i].maxLen); |
| break; |
| } |
| case QVariant::Double: |
| columns[i].lengths[row] = columns[i].maxLen; |
| *reinterpret_cast<double*>(dataPtr) = val.toDouble(); |
| break; |
| |
| case QVariant::String: { |
| const QString s = val.toString(); |
| columns[i].lengths[row] = (s.length() + 1) * sizeof(QChar); |
| memcpy(dataPtr, s.utf16(), columns[i].lengths[row]); |
| break; |
| } |
| case QVariant::UserType: |
| if (val.canConvert<QOCIRowIdPointer>()) { |
| const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val); |
| *reinterpret_cast<OCIRowid**>(dataPtr) = rptr->id; |
| columns[i].lengths[row] = 0; |
| break; |
| } |
| case QVariant::ByteArray: |
| default: { |
| const QByteArray ba = val.toByteArray(); |
| columns[i].lengths[row] = ba.size(); |
| memcpy(dataPtr, ba.constData(), ba.size()); |
| break; |
| } |
| } |
| } |
| } |
| |
| QOCIBatchColumn &bindColumn = columns[i]; |
| |
| #ifdef QOCI_DEBUG |
| qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)", |
| d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data, |
| bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths, |
| arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0); |
| |
| for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) { |
| qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii], |
| bindColumn.lengths[ii]); |
| } |
| #endif |
| |
| |
| // binding the column |
| r = OCIBindByPos2( |
| d->sql, &bindColumn.bindh, d->err, i + 1, |
| bindColumn.data, |
| bindColumn.maxLen, |
| bindColumn.bindAs, |
| bindColumn.indicators, |
| bindColumn.lengths, |
| 0, |
| arrayBind ? bindColumn.maxarr_len : 0, |
| arrayBind ? &bindColumn.curelep : 0, |
| OCI_DEFAULT); |
| |
| #ifdef QOCI_DEBUG |
| qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh); |
| #endif |
| |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); |
| d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to bind column for batch execute"), |
| QSqlError::StatementError, d->err)); |
| return false; |
| } |
| |
| r = OCIBindArrayOfStruct ( |
| columns[i].bindh, d->err, |
| columns[i].maxLen, |
| sizeof(columns[i].indicators[0]), |
| sizeof(columns[i].lengths[0]), |
| 0); |
| |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); |
| d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to bind column for batch execute"), |
| QSqlError::StatementError, d->err)); |
| return false; |
| } |
| } |
| |
| //finaly we can execute |
| r = OCIStmtExecute(d->svc, d->sql, d->err, |
| arrayBind ? 1 : columns[0].recordCount, |
| 0, NULL, NULL, |
| d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS); |
| |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIPrivate::execBatch: unable to execute batch statement:", d->err); |
| d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to execute batch statement"), |
| QSqlError::StatementError, d->err)); |
| return false; |
| } |
| |
| // for out parameters we copy data back to value vector |
| for (i = 0; i < columnCount; ++i) { |
| |
| if (!d->isOutValue(i)) |
| continue; |
| |
| QVariant::Type tp = boundValues.at(i).type(); |
| if (tp != QVariant::List) { |
| qOraOutValue(boundValues[i], tmpStorage, d->env, d->err); |
| if (*columns[i].indicators == -1) |
| boundValues[i] = QVariant(tp); |
| continue; |
| } |
| |
| QVariantList *list = static_cast<QVariantList *>(const_cast<void*>(boundValues.at(i).data())); |
| |
| char* data = columns[i].data; |
| for (uint r = 0; r < columns[i].recordCount; ++r){ |
| |
| if (columns[i].indicators[r] == -1) { |
| (*list)[r] = QVariant(fieldTypes[i]); |
| continue; |
| } |
| |
| switch(columns[i].bindAs) { |
| |
| case SQLT_TIMESTAMP_TZ: |
| (*list)[r] = QOCIDateTime::fromOCIDateTime(d->env, d->err, |
| *reinterpret_cast<OCIDateTime **>(data + r * columns[i].maxLen)); |
| break; |
| case SQLT_INT: |
| (*list)[r] = *reinterpret_cast<int*>(data + r * columns[i].maxLen); |
| break; |
| |
| case SQLT_UIN: |
| (*list)[r] = *reinterpret_cast<uint*>(data + r * columns[i].maxLen); |
| break; |
| |
| case SQLT_VNU: |
| { |
| switch (boundValues.at(i).type()) { |
| case QVariant::LongLong: |
| (*list)[r] = qMakeLongLong(data + r * columns[i].maxLen, d->err); |
| break; |
| case QVariant::ULongLong: |
| (*list)[r] = qMakeULongLong(data + r * columns[i].maxLen, d->err); |
| break; |
| default: |
| break; |
| } |
| break; |
| } |
| |
| case SQLT_FLT: |
| (*list)[r] = *reinterpret_cast<double*>(data + r * columns[i].maxLen); |
| break; |
| |
| case SQLT_STR: |
| (*list)[r] = QString(reinterpret_cast<const QChar *>(data |
| + r * columns[i].maxLen)); |
| break; |
| |
| default: |
| (*list)[r] = QByteArray(data + r * columns[i].maxLen, columns[i].maxLen); |
| break; |
| } |
| } |
| } |
| |
| d->q_func()->setSelect(false); |
| d->q_func()->setAt(QSql::BeforeFirstRow); |
| d->q_func()->setActive(true); |
| |
| qDeleteAll(tmpStorage.dateTimes); |
| return true; |
| } |
| |
| template<class T, int sz> |
| int qReadLob(T &buf, const QOCIResultPrivate *d, OCILobLocator *lob) |
| { |
| ub1 csfrm; |
| ub4 amount; |
| int r; |
| |
| // Read this from the database, don't assume we know what it is set to |
| r = OCILobCharSetForm(d->env, d->err, lob, &csfrm); |
| if (r != OCI_SUCCESS) { |
| qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB char set form: ", d->err); |
| csfrm = 0; |
| } |
| |
| // Get the length of the LOB (this is in characters) |
| r = OCILobGetLength(d->svc, d->err, lob, &amount); |
| if (r == OCI_SUCCESS) { |
| if (amount == 0) { |
| // Short cut for null LOBs |
| buf.resize(0); |
| return OCI_SUCCESS; |
| } |
| } else { |
| qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB length: ", d->err); |
| return r; |
| } |
| |
| // Resize the buffer to hold the LOB contents |
| buf.resize(amount); |
| |
| // Read the LOB into the buffer |
| r = OCILobRead(d->svc, |
| d->err, |
| lob, |
| &amount, |
| 1, |
| buf.data(), |
| buf.size() * sz, // this argument is in bytes, not characters |
| 0, |
| 0, |
| // Extract the data from a CLOB in UTF-16 (ie. what QString uses internally) |
| sz == 1 ? ub2(0) : ub2(QOCIEncoding), |
| csfrm); |
| |
| if (r != OCI_SUCCESS) |
| qOraWarning("OCIResultPrivate::readLOBs: Cannot read LOB: ", d->err); |
| |
| return r; |
| } |
| |
| int QOCICols::readLOBs(QVector<QVariant> &values, int index) |
| { |
| OCILobLocator *lob; |
| int r = OCI_SUCCESS; |
| |
| for (int i = 0; i < size(); ++i) { |
| const OraFieldInf &fi = fieldInf.at(i); |
| if (fi.ind == -1 || !(lob = fi.lob)) |
| continue; |
| |
| bool isClob = fi.oraType == SQLT_CLOB; |
| QVariant var; |
| |
| if (isClob) { |
| QString str; |
| r = qReadLob<QString, sizeof(QChar)>(str, d, lob); |
| var = str; |
| } else { |
| QByteArray buf; |
| r = qReadLob<QByteArray, sizeof(char)>(buf, d, lob); |
| var = buf; |
| } |
| if (r == OCI_SUCCESS) |
| values[index + i] = var; |
| else |
| break; |
| } |
| return r; |
| } |
| |
| int QOCICols::fieldFromDefine(OCIDefine* d) |
| { |
| for (int i = 0; i < fieldInf.count(); ++i) { |
| if (fieldInf.at(i).def == d) |
| return i; |
| } |
| return -1; |
| } |
| |
| void QOCICols::getValues(QVector<QVariant> &v, int index) |
| { |
| for (int i = 0; i < fieldInf.size(); ++i) { |
| const OraFieldInf &fld = fieldInf.at(i); |
| |
| if (fld.ind == -1) { |
| // got a NULL value |
| v[index + i] = QVariant(fld.typ); |
| continue; |
| } |
| |
| if (fld.oraType == SQLT_BIN || fld.oraType == SQLT_LBI || fld.oraType == SQLT_LNG) |
| continue; // already fetched piecewise |
| |
| switch (fld.typ) { |
| case QVariant::DateTime: |
| v[index + i] = QVariant(QOCIDateTime::fromOCIDateTime(d->env, d->err, |
| reinterpret_cast<OCIDateTime *>(fld.dataPtr))); |
| break; |
| case QVariant::Double: |
| case QVariant::Int: |
| case QVariant::LongLong: |
| if (d->q_func()->numericalPrecisionPolicy() != QSql::HighPrecision) { |
| if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble) |
| && (fld.typ == QVariant::Double)) { |
| v[index + i] = *reinterpret_cast<double *>(fld.data); |
| break; |
| } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64) |
| && (fld.typ == QVariant::LongLong)) { |
| qint64 qll = 0; |
| int r = OCINumberToInt(d->err, reinterpret_cast<OCINumber *>(fld.data), sizeof(qint64), |
| OCI_NUMBER_SIGNED, &qll); |
| if(r == OCI_SUCCESS) |
| v[index + i] = qll; |
| else |
| v[index + i] = QVariant(); |
| break; |
| } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32) |
| && (fld.typ == QVariant::Int)) { |
| v[index + i] = *reinterpret_cast<int *>(fld.data); |
| break; |
| } |
| } |
| // else fall through |
| case QVariant::String: |
| v[index + i] = QString(reinterpret_cast<const QChar *>(fld.data)); |
| break; |
| case QVariant::ByteArray: |
| if (fld.len > 0) |
| v[index + i] = QByteArray(fld.data, fld.len); |
| else |
| v[index + i] = QVariant(QVariant::ByteArray); |
| break; |
| default: |
| qWarning("QOCICols::value: unknown data type"); |
| break; |
| } |
| } |
| } |
| |
| QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv) |
| : QSqlCachedResultPrivate(q, drv), |
| cols(0), |
| env(drv_d_func()->env), |
| err(0), |
| svc(const_cast<OCISvcCtx*&>(drv_d_func()->svc)), |
| sql(0), |
| transaction(drv_d_func()->transaction), |
| serverVersion(drv_d_func()->serverVersion), |
| prefetchRows(drv_d_func()->prefetchRows), |
| prefetchMem(drv_d_func()->prefetchMem) |
| { |
| int r = OCIHandleAlloc(env, |
| reinterpret_cast<void **>(&err), |
| OCI_HTYPE_ERROR, |
| 0, |
| 0); |
| if (r != 0) |
| qWarning("QOCIResult: unable to alloc error handle"); |
| } |
| |
| QOCIResultPrivate::~QOCIResultPrivate() |
| { |
| delete cols; |
| |
| int r = OCIHandleFree(err, OCI_HTYPE_ERROR); |
| if (r != 0) |
| qWarning("~QOCIResult: unable to free statement handle"); |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////////////// |
| |
| QOCIResult::QOCIResult(const QOCIDriver *db) |
| : QSqlCachedResult(*new QOCIResultPrivate(this, db)) |
| { |
| } |
| |
| QOCIResult::~QOCIResult() |
| { |
| Q_D(QOCIResult); |
| if (d->sql) { |
| int r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); |
| if (r != 0) |
| qWarning("~QOCIResult: unable to free statement handle"); |
| } |
| } |
| |
| QVariant QOCIResult::handle() const |
| { |
| Q_D(const QOCIResult); |
| return QVariant::fromValue(d->sql); |
| } |
| |
| bool QOCIResult::reset (const QString& query) |
| { |
| if (!prepare(query)) |
| return false; |
| return exec(); |
| } |
| |
| bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) |
| { |
| Q_D(QOCIResult); |
| if (at() == QSql::AfterLastRow) |
| return false; |
| |
| bool piecewise = false; |
| int r = OCI_SUCCESS; |
| r = OCIStmtFetch(d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); |
| |
| if (index < 0) //not interested in values |
| return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; |
| |
| switch (r) { |
| case OCI_SUCCESS: |
| break; |
| case OCI_SUCCESS_WITH_INFO: |
| qOraWarning("QOCIResult::gotoNext: SuccessWithInfo: ", d->err); |
| r = OCI_SUCCESS; //ignore it |
| break; |
| case OCI_NO_DATA: |
| // end of rowset |
| return false; |
| case OCI_NEED_DATA: |
| piecewise = true; |
| r = OCI_SUCCESS; |
| break; |
| case OCI_ERROR: |
| if (qOraErrorNumber(d->err) == 1406) { |
| qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData()); |
| r = OCI_SUCCESS; /* ignore it */ |
| break; |
| } |
| // fall through |
| default: |
| qOraWarning("QOCIResult::gotoNext: ", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to goto next"), |
| QSqlError::StatementError, d->err)); |
| break; |
| } |
| |
| // need to read piecewise before assigning values |
| if (r == OCI_SUCCESS && piecewise) |
| r = d->cols->readPiecewise(values, index); |
| |
| if (r == OCI_SUCCESS) |
| d->cols->getValues(values, index); |
| if (r == OCI_SUCCESS) |
| r = d->cols->readLOBs(values, index); |
| if (r != OCI_SUCCESS) |
| setAt(QSql::AfterLastRow); |
| return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; |
| } |
| |
| int QOCIResult::size() |
| { |
| return -1; |
| } |
| |
| int QOCIResult::numRowsAffected() |
| { |
| Q_D(QOCIResult); |
| int rowCount; |
| OCIAttrGet(d->sql, |
| OCI_HTYPE_STMT, |
| &rowCount, |
| NULL, |
| OCI_ATTR_ROW_COUNT, |
| d->err); |
| return rowCount; |
| } |
| |
| bool QOCIResult::prepare(const QString& query) |
| { |
| Q_D(QOCIResult); |
| int r = 0; |
| QSqlResult::prepare(query); |
| |
| delete d->cols; |
| d->cols = 0; |
| QSqlCachedResult::cleanup(); |
| |
| if (d->sql) { |
| r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); |
| if (r != OCI_SUCCESS) |
| qOraWarning("QOCIResult::prepare: unable to free statement handle:", d->err); |
| } |
| if (query.isEmpty()) |
| return false; |
| r = OCIHandleAlloc(d->env, |
| reinterpret_cast<void **>(&d->sql), |
| OCI_HTYPE_STMT, |
| 0, |
| 0); |
| if (r != OCI_SUCCESS) { |
| qOraWarning("QOCIResult::prepare: unable to alloc statement:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to alloc statement"), QSqlError::StatementError, d->err)); |
| return false; |
| } |
| d->setStatementAttributes(); |
| const OraText *txt = reinterpret_cast<const OraText *>(query.utf16()); |
| const int len = query.length() * sizeof(QChar); |
| r = OCIStmtPrepare(d->sql, |
| d->err, |
| txt, |
| len, |
| OCI_NTV_SYNTAX, |
| OCI_DEFAULT); |
| if (r != OCI_SUCCESS) { |
| qOraWarning("QOCIResult::prepare: unable to prepare statement:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to prepare statement"), QSqlError::StatementError, d->err)); |
| return false; |
| } |
| return true; |
| } |
| |
| bool QOCIResult::exec() |
| { |
| Q_D(QOCIResult); |
| int r = 0; |
| ub2 stmtType=0; |
| ub4 iters; |
| ub4 mode; |
| TempStorage tmpStorage; |
| IndicatorArray indicators(boundValueCount()); |
| SizeArray tmpSizes(boundValueCount()); |
| |
| r = OCIAttrGet(d->sql, |
| OCI_HTYPE_STMT, |
| &stmtType, |
| NULL, |
| OCI_ATTR_STMT_TYPE, |
| d->err); |
| |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIResult::exec: Unable to get statement type:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to get statement type"), QSqlError::StatementError, d->err)); |
| #ifdef QOCI_DEBUG |
| qDebug() << "lastQuery()" << lastQuery(); |
| #endif |
| return false; |
| } |
| |
| iters = stmtType == OCI_STMT_SELECT ? 0 : 1; |
| mode = d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; |
| |
| // bind placeholders |
| if (boundValueCount() > 0 |
| && d->bindValues(boundValues(), indicators, tmpSizes, tmpStorage) != OCI_SUCCESS) { |
| qOraWarning("QOCIResult::exec: unable to bind value: ", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"), |
| QSqlError::StatementError, d->err)); |
| #ifdef QOCI_DEBUG |
| qDebug() << "lastQuery()" << lastQuery(); |
| #endif |
| return false; |
| } |
| |
| // execute |
| r = OCIStmtExecute(d->svc, |
| d->sql, |
| d->err, |
| iters, |
| 0, |
| 0, |
| 0, |
| mode); |
| if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { |
| qOraWarning("QOCIResult::exec: unable to execute statement:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIResult", |
| "Unable to execute statement"), QSqlError::StatementError, d->err)); |
| #ifdef QOCI_DEBUG |
| qDebug() << "lastQuery()" << lastQuery(); |
| #endif |
| return false; |
| } |
| |
| if (stmtType == OCI_STMT_SELECT) { |
| ub4 parmCount = 0; |
| int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, reinterpret_cast<void **>(&parmCount), |
| 0, OCI_ATTR_PARAM_COUNT, d->err); |
| if (r == 0 && !d->cols) |
| d->cols = new QOCICols(parmCount, d); |
| setSelect(true); |
| QSqlCachedResult::init(parmCount); |
| } else { /* non-SELECT */ |
| setSelect(false); |
| } |
| setAt(QSql::BeforeFirstRow); |
| setActive(true); |
| |
| if (hasOutValues()) |
| d->outValues(boundValues(), indicators, tmpStorage); |
| qDeleteAll(tmpStorage.dateTimes); |
| return true; |
| } |
| |
| QSqlRecord QOCIResult::record() const |
| { |
| Q_D(const QOCIResult); |
| QSqlRecord inf; |
| if (!isActive() || !isSelect() || !d->cols) |
| return inf; |
| return d->cols->rec; |
| } |
| |
| QVariant QOCIResult::lastInsertId() const |
| { |
| Q_D(const QOCIResult); |
| if (isActive()) { |
| QOCIRowIdPointer ptr(new QOCIRowId(d->env)); |
| |
| int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, ptr.constData()->id, |
| 0, OCI_ATTR_ROWID, d->err); |
| if (r == OCI_SUCCESS) |
| return QVariant::fromValue(ptr); |
| } |
| return QVariant(); |
| } |
| |
| bool QOCIResult::execBatch(bool arrayBind) |
| { |
| Q_D(QOCIResult); |
| QOCICols::execBatch(d, boundValues(), arrayBind); |
| resetBindCount(); |
| return lastError().type() == QSqlError::NoError; |
| } |
| |
| void QOCIResult::virtual_hook(int id, void *data) |
| { |
| Q_ASSERT(data); |
| |
| QSqlCachedResult::virtual_hook(id, data); |
| } |
| |
| bool QOCIResult::fetchNext() |
| { |
| Q_D(QOCIResult); |
| if (isForwardOnly()) |
| d->cache.clear(); |
| return QSqlCachedResult::fetchNext(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| |
| |
| QOCIDriver::QOCIDriver(QObject* parent) |
| : QSqlDriver(*new QOCIDriverPrivate, parent) |
| { |
| Q_D(QOCIDriver); |
| #ifdef QOCI_THREADED |
| const ub4 mode = OCI_UTF16 | OCI_OBJECT | OCI_THREADED; |
| #else |
| const ub4 mode = OCI_UTF16 | OCI_OBJECT; |
| #endif |
| int r = OCIEnvCreate(&d->env, |
| mode, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| 0, |
| NULL); |
| if (r != 0) { |
| qWarning("QOCIDriver: unable to create environment"); |
| setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"), |
| QSqlError::ConnectionError, d->err)); |
| return; |
| } |
| |
| d->allocErrorHandle(); |
| } |
| |
| QOCIDriver::QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent) |
| : QSqlDriver(*new QOCIDriverPrivate, parent) |
| { |
| Q_D(QOCIDriver); |
| d->env = env; |
| d->svc = ctx; |
| |
| d->allocErrorHandle(); |
| |
| if (env && ctx) { |
| setOpen(true); |
| setOpenError(false); |
| } |
| } |
| |
| QOCIDriver::~QOCIDriver() |
| { |
| Q_D(QOCIDriver); |
| if (isOpen()) |
| close(); |
| int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR); |
| if (r != OCI_SUCCESS) |
| qWarning("Unable to free Error handle: %d", r); |
| r = OCIHandleFree(d->env, OCI_HTYPE_ENV); |
| if (r != OCI_SUCCESS) |
| qWarning("Unable to free Environment handle: %d", r); |
| } |
| |
| bool QOCIDriver::hasFeature(DriverFeature f) const |
| { |
| Q_D(const QOCIDriver); |
| switch (f) { |
| case Transactions: |
| case LastInsertId: |
| case BLOB: |
| case PreparedQueries: |
| case NamedPlaceholders: |
| case BatchOperations: |
| case LowPrecisionNumbers: |
| return true; |
| case QuerySize: |
| case PositionalPlaceholders: |
| case SimpleLocking: |
| case EventNotifications: |
| case FinishQuery: |
| case CancelQuery: |
| case MultipleResultSets: |
| return false; |
| case Unicode: |
| return d->serverVersion >= 9; |
| } |
| return false; |
| } |
| |
| static void qParseOpts(const QString &options, QOCIDriverPrivate *d) |
| { |
| const QStringList opts(options.split(QLatin1Char(';'), QString::SkipEmptyParts)); |
| for (int i = 0; i < opts.count(); ++i) { |
| const QString tmp(opts.at(i)); |
| int idx; |
| if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { |
| qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'", |
| tmp.toLocal8Bit().constData()); |
| continue; |
| } |
| const QString opt = tmp.left(idx); |
| const QString val = tmp.mid(idx + 1).simplified(); |
| bool ok; |
| if (opt == QLatin1String("OCI_ATTR_PREFETCH_ROWS")) { |
| d->prefetchRows = val.toInt(&ok); |
| if (!ok) |
| d->prefetchRows = -1; |
| } else if (opt == QLatin1String("OCI_ATTR_PREFETCH_MEMORY")) { |
| d->prefetchMem = val.toInt(&ok); |
| if (!ok) |
| d->prefetchMem = -1; |
| } else { |
| qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'", |
| opt.toLocal8Bit().constData()); |
| } |
| } |
| } |
| |
| bool QOCIDriver::open(const QString & db, |
| const QString & user, |
| const QString & password, |
| const QString & hostname, |
| int port, |
| const QString &opts) |
| { |
| Q_D(QOCIDriver); |
| int r; |
| |
| if (isOpen()) |
| close(); |
| |
| qParseOpts(opts, d); |
| |
| // Connect without tnsnames.ora if a hostname is given |
| QString connectionString = db; |
| if (!hostname.isEmpty()) |
| connectionString = |
| QString::fromLatin1("(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=%1)(Port=%2))" |
| "(CONNECT_DATA=(SID=%3)))").arg(hostname).arg((port > -1 ? port : 1521)).arg(db); |
| |
| r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->srvhp), OCI_HTYPE_SERVER, 0, 0); |
| if (r == OCI_SUCCESS) |
| r = OCIServerAttach(d->srvhp, d->err, reinterpret_cast<const OraText *>(connectionString.utf16()), |
| connectionString.length() * sizeof(QChar), OCI_DEFAULT); |
| if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) |
| r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->svc), OCI_HTYPE_SVCCTX, 0, 0); |
| if (r == OCI_SUCCESS) |
| r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->srvhp, 0, OCI_ATTR_SERVER, d->err); |
| if (r == OCI_SUCCESS) |
| r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->authp), OCI_HTYPE_SESSION, 0, 0); |
| if (r == OCI_SUCCESS) |
| r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(user.utf16()), |
| user.length() * sizeof(QChar), OCI_ATTR_USERNAME, d->err); |
| if (r == OCI_SUCCESS) |
| r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(password.utf16()), |
| password.length() * sizeof(QChar), OCI_ATTR_PASSWORD, d->err); |
| |
| OCITrans* trans; |
| if (r == OCI_SUCCESS) |
| r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&trans), OCI_HTYPE_TRANS, 0, 0); |
| if (r == OCI_SUCCESS) |
| r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, trans, 0, OCI_ATTR_TRANS, d->err); |
| |
| if (r == OCI_SUCCESS) { |
| if (user.isEmpty() && password.isEmpty()) |
| r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT); |
| else |
| r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT); |
| } |
| if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) |
| r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err); |
| |
| if (r != OCI_SUCCESS) { |
| setLastError(qMakeError(tr("Unable to logon"), QSqlError::ConnectionError, d->err)); |
| setOpenError(true); |
| if (d->authp) |
| OCIHandleFree(d->authp, OCI_HTYPE_SESSION); |
| d->authp = 0; |
| if (d->srvhp) |
| OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); |
| d->srvhp = 0; |
| return false; |
| } |
| |
| // get server version |
| char vertxt[512]; |
| r = OCIServerVersion(d->svc, |
| d->err, |
| reinterpret_cast<OraText *>(vertxt), |
| sizeof(vertxt), |
| OCI_HTYPE_SVCCTX); |
| if (r != 0) { |
| qWarning("QOCIDriver::open: could not get Oracle server version."); |
| } else { |
| QString versionStr; |
| versionStr = QString(reinterpret_cast<const QChar *>(vertxt)); |
| QRegExp vers(QLatin1String("([0-9]+)\\.[0-9\\.]+[0-9]")); |
| if (vers.indexIn(versionStr) >= 0) |
| d->serverVersion = vers.cap(1).toInt(); |
| if (d->serverVersion == 0) |
| d->serverVersion = -1; |
| } |
| |
| setOpen(true); |
| setOpenError(false); |
| d->user = user; |
| |
| return true; |
| } |
| |
| void QOCIDriver::close() |
| { |
| Q_D(QOCIDriver); |
| if (!isOpen()) |
| return; |
| |
| OCISessionEnd(d->svc, d->err, d->authp, OCI_DEFAULT); |
| OCIServerDetach(d->srvhp, d->err, OCI_DEFAULT); |
| OCIHandleFree(d->authp, OCI_HTYPE_SESSION); |
| d->authp = 0; |
| OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); |
| d->srvhp = 0; |
| OCIHandleFree(d->svc, OCI_HTYPE_SVCCTX); |
| d->svc = 0; |
| setOpen(false); |
| setOpenError(false); |
| } |
| |
| QSqlResult *QOCIDriver::createResult() const |
| { |
| return new QOCIResult(this); |
| } |
| |
| bool QOCIDriver::beginTransaction() |
| { |
| Q_D(QOCIDriver); |
| if (!isOpen()) { |
| qWarning("QOCIDriver::beginTransaction: Database not open"); |
| return false; |
| } |
| int r = OCITransStart(d->svc, |
| d->err, |
| 2, |
| OCI_TRANS_READWRITE); |
| if (r == OCI_ERROR) { |
| qOraWarning("QOCIDriver::beginTransaction: ", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", |
| "Unable to begin transaction"), QSqlError::TransactionError, d->err)); |
| return false; |
| } |
| d->transaction = true; |
| return true; |
| } |
| |
| bool QOCIDriver::commitTransaction() |
| { |
| Q_D(QOCIDriver); |
| if (!isOpen()) { |
| qWarning("QOCIDriver::commitTransaction: Database not open"); |
| return false; |
| } |
| int r = OCITransCommit(d->svc, |
| d->err, |
| 0); |
| if (r == OCI_ERROR) { |
| qOraWarning("QOCIDriver::commitTransaction:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", |
| "Unable to commit transaction"), QSqlError::TransactionError, d->err)); |
| return false; |
| } |
| d->transaction = false; |
| return true; |
| } |
| |
| bool QOCIDriver::rollbackTransaction() |
| { |
| Q_D(QOCIDriver); |
| if (!isOpen()) { |
| qWarning("QOCIDriver::rollbackTransaction: Database not open"); |
| return false; |
| } |
| int r = OCITransRollback(d->svc, |
| d->err, |
| 0); |
| if (r == OCI_ERROR) { |
| qOraWarning("QOCIDriver::rollbackTransaction:", d->err); |
| setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", |
| "Unable to rollback transaction"), QSqlError::TransactionError, d->err)); |
| return false; |
| } |
| d->transaction = false; |
| return true; |
| } |
| |
| enum Expression { |
| OrExpression, |
| AndExpression |
| }; |
| |
| static QString make_where_clause(const QString &user, Expression e) |
| { |
| static const char sysUsers[][8] = { |
| "MDSYS", |
| "LBACSYS", |
| "SYS", |
| "SYSTEM", |
| "WKSYS", |
| "CTXSYS", |
| "WMSYS", |
| }; |
| static const char joinC[][4] = { "or" , "and" }; |
| static Q_CONSTEXPR QLatin1Char bang[] = { QLatin1Char(' '), QLatin1Char('!') }; |
| |
| const QLatin1String join(joinC[e]); |
| |
| QString result; |
| result.reserve(sizeof sysUsers / sizeof *sysUsers * |
| // max-sizeof(owner != <sysuser> and ) |
| (9 + sizeof *sysUsers + 5)); |
| for (const auto &sysUser : sysUsers) { |
| const QLatin1String l1(sysUser); |
| if (l1 != user) |
| result += QLatin1String("owner ") + bang[e] + QLatin1String("= '") + l1 + QLatin1String("' ") + join + QLatin1Char(' '); |
| } |
| |
| result.chop(join.size() + 2); // remove final " <join> " |
| |
| return result; |
| } |
| |
| QStringList QOCIDriver::tables(QSql::TableType type) const |
| { |
| Q_D(const QOCIDriver); |
| QStringList tl; |
| |
| QString user = d->user; |
| if ( isIdentifierEscaped(user, QSqlDriver::TableName)) |
| user = stripDelimiters(user, QSqlDriver::TableName); |
| else |
| user = user.toUpper(); |
| |
| if (!isOpen()) |
| return tl; |
| |
| QSqlQuery t(createResult()); |
| t.setForwardOnly(true); |
| if (type & QSql::Tables) { |
| const QLatin1String tableQuery("select owner, table_name from all_tables where "); |
| const QString where = make_where_clause(user, AndExpression); |
| t.exec(tableQuery + where); |
| while (t.next()) { |
| if (t.value(0).toString().toUpper() != user.toUpper()) |
| tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); |
| else |
| tl.append(t.value(1).toString()); |
| } |
| |
| // list all table synonyms as well |
| const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); |
| t.exec(synonymQuery + where); |
| while (t.next()) { |
| if (t.value(0).toString() != d->user) |
| tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); |
| else |
| tl.append(t.value(1).toString()); |
| } |
| } |
| if (type & QSql::Views) { |
| const QLatin1String query("select owner, view_name from all_views where "); |
| const QString where = make_where_clause(user, AndExpression); |
| t.exec(query + where); |
| while (t.next()) { |
| if (t.value(0).toString().toUpper() != d->user.toUpper()) |
| tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); |
| else |
| tl.append(t.value(1).toString()); |
| } |
| } |
| if (type & QSql::SystemTables) { |
| t.exec(QLatin1String("select table_name from dictionary")); |
| while (t.next()) { |
| tl.append(t.value(0).toString()); |
| } |
| const QLatin1String tableQuery("select owner, table_name from all_tables where "); |
| const QString where = make_where_clause(user, OrExpression); |
| t.exec(tableQuery + where); |
| while (t.next()) { |
| if (t.value(0).toString().toUpper() != user.toUpper()) |
| tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); |
| else |
| tl.append(t.value(1).toString()); |
| } |
| |
| // list all table synonyms as well |
| const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where "); |
| t.exec(synonymQuery + where); |
| while (t.next()) { |
| if (t.value(0).toString() != d->user) |
| tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString()); |
| else |
| tl.append(t.value(1).toString()); |
| } |
| } |
| return tl; |
| } |
| |
| void qSplitTableAndOwner(const QString & tname, QString * tbl, |
| QString * owner) |
| { |
| int i = tname.indexOf(QLatin1Char('.')); // prefixed with owner? |
| if (i != -1) { |
| *tbl = tname.right(tname.length() - i - 1); |
| *owner = tname.left(i); |
| } else { |
| *tbl = tname; |
| } |
| } |
| |
| QSqlRecord QOCIDriver::record(const QString& tablename) const |
| { |
| Q_D(const QOCIDriver); |
| QSqlRecord fil; |
| if (!isOpen()) |
| return fil; |
| |
| QSqlQuery t(createResult()); |
| // using two separate queries for this is A LOT faster than using |
| // eg. a sub-query on the sys.synonyms table |
| QString stmt(QLatin1String("select column_name, data_type, data_length, " |
| "data_precision, data_scale, nullable, data_default%1" |
| "from all_tab_columns a " |
| "where a.table_name=%2")); |
| if (d->serverVersion >= 9) |
| stmt = stmt.arg(QLatin1String(", char_length ")); |
| else |
| stmt = stmt.arg(QLatin1String(" ")); |
| bool buildRecordInfo = false; |
| QString table, owner, tmpStmt; |
| qSplitTableAndOwner(tablename, &table, &owner); |
| |
| if (isIdentifierEscaped(table, QSqlDriver::TableName)) |
| table = stripDelimiters(table, QSqlDriver::TableName); |
| else |
| table = table.toUpper(); |
| |
| tmpStmt = stmt.arg(QLatin1Char('\'') + table + QLatin1Char('\'')); |
| if (owner.isEmpty()) { |
| owner = d->user; |
| } |
| |
| if (isIdentifierEscaped(owner, QSqlDriver::TableName)) |
| owner = stripDelimiters(owner, QSqlDriver::TableName); |
| else |
| owner = owner.toUpper(); |
| |
| tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); |
| t.setForwardOnly(true); |
| t.exec(tmpStmt); |
| if (!t.next()) { // try and see if the tablename is a synonym |
| stmt = stmt + QLatin1String(" join all_synonyms b " |
| "on a.owner=b.table_owner and a.table_name=b.table_name " |
| "where b.owner='") + owner + |
| QLatin1String("' and b.synonym_name='") + table + |
| QLatin1Char('\''); |
| t.setForwardOnly(true); |
| t.exec(stmt); |
| if (t.next()) |
| buildRecordInfo = true; |
| } else { |
| buildRecordInfo = true; |
| } |
| QStringList keywords = QStringList() << QLatin1String("NUMBER") << QLatin1String("FLOAT") << QLatin1String("BINARY_FLOAT") |
| << QLatin1String("BINARY_DOUBLE"); |
| if (buildRecordInfo) { |
| do { |
| QVariant::Type ty = qDecodeOCIType(t.value(1).toString(), t.numericalPrecisionPolicy()); |
| QSqlField f(t.value(0).toString(), ty); |
| f.setRequired(t.value(5).toString() == QLatin1String("N")); |
| f.setPrecision(t.value(4).toInt()); |
| if (d->serverVersion >= 9 && (ty == QVariant::String) && !t.isNull(3) && !keywords.contains(t.value(1).toString())) { |
| // Oracle9: data_length == size in bytes, char_length == amount of characters |
| f.setLength(t.value(7).toInt()); |
| } else { |
| f.setLength(t.value(t.isNull(3) ? 2 : 3).toInt()); |
| } |
| f.setDefaultValue(t.value(6)); |
| fil.append(f); |
| } while (t.next()); |
| } |
| return fil; |
| } |
| |
| QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const |
| { |
| Q_D(const QOCIDriver); |
| QSqlIndex idx(tablename); |
| if (!isOpen()) |
| return idx; |
| QSqlQuery t(createResult()); |
| QString stmt(QLatin1String("select b.column_name, b.index_name, a.table_name, a.owner " |
| "from all_constraints a, all_ind_columns b " |
| "where a.constraint_type='P' " |
| "and b.index_name = a.index_name " |
| "and b.index_owner = a.owner")); |
| |
| bool buildIndex = false; |
| QString table, owner, tmpStmt; |
| qSplitTableAndOwner(tablename, &table, &owner); |
| |
| if (isIdentifierEscaped(table, QSqlDriver::TableName)) |
| table = stripDelimiters(table, QSqlDriver::TableName); |
| else |
| table = table.toUpper(); |
| |
| tmpStmt = stmt + QLatin1String(" and a.table_name='") + table + QLatin1Char('\''); |
| if (owner.isEmpty()) { |
| owner = d->user; |
| } |
| |
| if (isIdentifierEscaped(owner, QSqlDriver::TableName)) |
| owner = stripDelimiters(owner, QSqlDriver::TableName); |
| else |
| owner = owner.toUpper(); |
| |
| tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\''); |
| t.setForwardOnly(true); |
| t.exec(tmpStmt); |
| |
| if (!t.next()) { |
| stmt += QLatin1String(" and a.table_name=(select tname from sys.synonyms " |
| "where sname='") + table + QLatin1String("' and creator=a.owner)"); |
| t.setForwardOnly(true); |
| t.exec(stmt); |
| if (t.next()) { |
| owner = t.value(3).toString(); |
| buildIndex = true; |
| } |
| } else { |
| buildIndex = true; |
| } |
| if (buildIndex) { |
| QSqlQuery tt(createResult()); |
| tt.setForwardOnly(true); |
| idx.setName(t.value(1).toString()); |
| do { |
| tt.exec(QLatin1String("select data_type from all_tab_columns where table_name='") + |
| t.value(2).toString() + QLatin1String("' and column_name='") + |
| t.value(0).toString() + QLatin1String("' and owner='") + |
| owner + QLatin1Char('\'')); |
| if (!tt.next()) { |
| return QSqlIndex(); |
| } |
| QSqlField f(t.value(0).toString(), qDecodeOCIType(tt.value(0).toString(), t.numericalPrecisionPolicy())); |
| idx.append(f); |
| } while (t.next()); |
| return idx; |
| } |
| return QSqlIndex(); |
| } |
| |
| QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const |
| { |
| switch (field.type()) { |
| case QVariant::DateTime: { |
| QDateTime datetime = field.value().toDateTime(); |
| QString datestring; |
| if (datetime.isValid()) { |
| datestring = QLatin1String("TO_DATE('") + 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()) |
| + QLatin1String("','YYYY-MM-DD HH24:MI:SS')"); |
| } else { |
| datestring = QLatin1String("NULL"); |
| } |
| return datestring; |
| } |
| case QVariant::Time: { |
| QDateTime datetime = field.value().toDateTime(); |
| QString datestring; |
| if (datetime.isValid()) { |
| datestring = QLatin1String("TO_DATE('") |
| + QString::number(datetime.time().hour()) + QLatin1Char(':') |
| + QString::number(datetime.time().minute()) + QLatin1Char(':') |
| + QString::number(datetime.time().second()) |
| + QLatin1String("','HH24:MI:SS')"); |
| } else { |
| datestring = QLatin1String("NULL"); |
| } |
| return datestring; |
| } |
| case QVariant::Date: { |
| QDate date = field.value().toDate(); |
| QString datestring; |
| if (date.isValid()) { |
| datestring = QLatin1String("TO_DATE('") + QString::number(date.year()) + |
| QLatin1Char('-') + |
| QString::number(date.month()) + QLatin1Char('-') + |
| QString::number(date.day()) + QLatin1String("','YYYY-MM-DD')"); |
| } else { |
| datestring = QLatin1String("NULL"); |
| } |
| return datestring; |
| } |
| default: |
| break; |
| } |
| return QSqlDriver::formatValue(field, trimStrings); |
| } |
| |
| QVariant QOCIDriver::handle() const |
| { |
| Q_D(const QOCIDriver); |
| return QVariant::fromValue(d->env); |
| } |
| |
| QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const |
| { |
| QString res = identifier; |
| if(!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) { |
| res.replace(QLatin1Char('"'), QLatin1String("\"\"")); |
| res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); |
| res.replace(QLatin1Char('.'), QLatin1String("\".\"")); |
| } |
| return res; |
| } |
| |
| QT_END_NAMESPACE |