/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This is needed for oracle oci when compiling with mingw-w64 headers #if defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64) #define _int64 __int64 #endif #include #ifdef max #undef max #endif #ifdef min #undef min #endif #include #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 IndicatorArray; typedef QVarLengthArray 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(&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(&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(timeZone.utf16()); OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec() * 1000000, const_cast(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 rawData; QList dateTimes; }; typedef QSharedDataPointer 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 &values, IndicatorArray &indicators, SizeArray &tmpSizes, TempStorage &tmpStorage); void outValues(QVector &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(static_cast(&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(static_cast(&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(val.constData()); switch (val.type()) { case QVariant::ByteArray: r = OCIBindByPos(sql, hbnd, err, pos + 1, isOutValue(pos) ? const_cast(reinterpret_cast(data)->constData()) : reinterpret_cast(data)->data(), reinterpret_cast(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(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(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(data), sizeof(double), SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); break; case QVariant::UserType: if (val.canConvert() && !isOutValue(pos)) { // use a const pointer to prevent a detach const QOCIRowIdPointer rptr = qvariant_cast(val); r = OCIBindByPos(sql, hbnd, err, pos + 1, // it's an IN value, so const_cast is ok const_cast(&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(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(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(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 &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(tmpStorage.rawData.takeFirst().constData())); break; default: break; //nothing } } void QOCIResultPrivate::outValues(QVector &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(&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(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(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(ba.data())); return ba; } qlonglong qMakeLongLong(const char* ociNumber, OCIError* err) { qlonglong qll = 0; OCINumberToInt(err, reinterpret_cast(ociNumber), sizeof(qlonglong), OCI_NUMBER_SIGNED, &qll); return qll; } qulonglong qMakeULongLong(const char* ociNumber, OCIError* err) { qulonglong qull = 0; OCINumberToInt(err, reinterpret_cast(ociNumber), sizeof(qulonglong), OCI_NUMBER_UNSIGNED, &qull); return qull; } class QOCICols { public: QOCICols(int size, QOCIResultPrivate* dp); ~QOCICols(); int readPiecewise(QVector &values, int index = 0); int readLOBs(QVector &values, int index = 0); int fieldFromDefine(OCIDefine* d); void getValues(QVector &v, int index); inline int size() { return fieldInf.size(); } static bool execBatch(QOCIResultPrivate *d, QVector &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 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(¶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(¶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(&lob), OCI_DTYPE_LOB, 0, 0); if (r != 0) { qWarning("QOCICols: Cannot create LOB locator"); lob = 0; } return &lob; } int QOCICols::readPiecewise(QVector &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(&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(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(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(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 &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 &col; }; bool QOCICols::execBatch(QOCIResultPrivate *d, QVector &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 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 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(dataPtr) = date->dateTime; break; } case QVariant::Int: columns[i].lengths[row] = columns[i].maxLen; *reinterpret_cast(dataPtr) = val.toInt(); break; case QVariant::UInt: columns[i].lengths[row] = columns[i].maxLen; *reinterpret_cast(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(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()) { const QOCIRowIdPointer rptr = qvariant_cast(val); *reinterpret_cast(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(const_cast(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(data + r * columns[i].maxLen)); break; case SQLT_INT: (*list)[r] = *reinterpret_cast(data + r * columns[i].maxLen); break; case SQLT_UIN: (*list)[r] = *reinterpret_cast(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(data + r * columns[i].maxLen); break; case SQLT_STR: (*list)[r] = QString(reinterpret_cast(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 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 &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(str, d, lob); var = str; } else { QByteArray buf; r = qReadLob(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 &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(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(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(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(fld.data); break; } } // else fall through case QVariant::String: v[index + i] = QString(reinterpret_cast(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(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(&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(&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(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(&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(&d->srvhp), OCI_HTYPE_SERVER, 0, 0); if (r == OCI_SUCCESS) r = OCIServerAttach(d->srvhp, d->err, reinterpret_cast(connectionString.utf16()), connectionString.length() * sizeof(QChar), OCI_DEFAULT); if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) r = OCIHandleAlloc(d->env, reinterpret_cast(&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(&d->authp), OCI_HTYPE_SESSION, 0, 0); if (r == OCI_SUCCESS) r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast(user.utf16()), user.length() * sizeof(QChar), OCI_ATTR_USERNAME, d->err); if (r == OCI_SUCCESS) r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast(password.utf16()), password.length() * sizeof(QChar), OCI_ATTR_PASSWORD, d->err); OCITrans* trans; if (r == OCI_SUCCESS) r = OCIHandleAlloc(d->env, reinterpret_cast(&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(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(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 != 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 " " 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