From 0452c25aedb7d2778e5b26fad924ed61b72bbe33 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Fri, 18 Mar 2016 13:35:40 +0100 Subject: Always compile sql drivers as plugins Compiling the drivers into Qt Sql does not make a lot of sense anymore, as we handle plugins well enough in the build system these days. [ChangeLog][Build system] SQL drivers are now always compiled as plugins. Change-Id: I364b82a480849399d1fafe4b20e9f08922569260 Reviewed-by: Oswald Buddenhagen Reviewed-by: Mark Brand --- src/plugins/sqldrivers/oci/main.cpp | 2 +- src/plugins/sqldrivers/oci/oci.pro | 12 +- src/plugins/sqldrivers/oci/qsql_oci.cpp | 2725 +++++++++++++++++++++++++++++++ src/plugins/sqldrivers/oci/qsql_oci_p.h | 106 ++ 4 files changed, 2842 insertions(+), 3 deletions(-) create mode 100644 src/plugins/sqldrivers/oci/qsql_oci.cpp create mode 100644 src/plugins/sqldrivers/oci/qsql_oci_p.h (limited to 'src/plugins/sqldrivers/oci') diff --git a/src/plugins/sqldrivers/oci/main.cpp b/src/plugins/sqldrivers/oci/main.cpp index 980116123d..e576928d31 100644 --- a/src/plugins/sqldrivers/oci/main.cpp +++ b/src/plugins/sqldrivers/oci/main.cpp @@ -39,7 +39,7 @@ #include #include -#include "../../../sql/drivers/oci/qsql_oci_p.h" +#include "qsql_oci_p.h" QT_BEGIN_NAMESPACE diff --git a/src/plugins/sqldrivers/oci/oci.pro b/src/plugins/sqldrivers/oci/oci.pro index 96a0dd9ab6..a22d1181bf 100644 --- a/src/plugins/sqldrivers/oci/oci.pro +++ b/src/plugins/sqldrivers/oci/oci.pro @@ -1,8 +1,16 @@ TARGET = qsqloci -SOURCES = main.cpp +HEADERS += $$PWD/qsql_oci_p.h +SOURCES += $$PWD/qsql_oci.cpp $$PWD/main.cpp + +unix { + !contains(LIBS, .*clnts.*):LIBS += -lclntsh +} else { + LIBS *= -loci +} +darwin:QMAKE_LFLAGS += -Wl,-flat_namespace,-U,_environ + OTHER_FILES += oci.json -include(../../../sql/drivers/oci/qsql_oci.pri) PLUGIN_CLASS_NAME = QOCIDriverPlugin include(../qsqldriverbase.pri) diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp new file mode 100644 index 0000000000..47d6db7ea4 --- /dev/null +++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp @@ -0,0 +1,2725 @@ +/**************************************************************************** +** +** 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 + +// 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 qMakeOraDate(const QDateTime& dt); +static QDateTime qMakeDate(const char* oraDate); + +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); +} + +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) Q_DECL_OVERRIDE; + bool exec() Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(ValueCache &values, int index) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + QVariant lastInsertId() const Q_DECL_OVERRIDE; + bool execBatch(bool arrayBind = false) Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_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, QList &tmpStorage); + int bindValues(QVector &values, IndicatorArray &indicators, SizeArray &tmpSizes, + QList &tmpStorage); + void outValues(QVector &values, IndicatorArray &indicators, + QList &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, QList &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: { + QByteArray ba = qMakeOraDate(val.toDateTime()); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_DAT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + tmpStorage.append(ba); + 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.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.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.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, QList &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, QList &storage, OCIError* err) +{ + switch (value.type()) { + case QVariant::Time: + value = qMakeDate(storage.takeFirst()).time(); + break; + case QVariant::Date: + value = qMakeDate(storage.takeFirst()).date(); + break; + case QVariant::DateTime: + value = qMakeDate(storage.takeFirst()); + break; + case QVariant::LongLong: + value = qMakeLongLong(storage.takeFirst(), err); + break; + case QVariant::ULongLong: + value = qMakeULongLong(storage.takeFirst(), err); + break; + case QVariant::String: + value = QString( + reinterpret_cast(storage.takeFirst().constData())); + break; + default: + break; //nothing + } +} + +void QOCIResultPrivate::outValues(QVector &values, IndicatorArray &indicators, + QList &tmpStorage) +{ + for (int i = 0; i < values.count(); ++i) { + + if (!isOutValue(i)) + continue; + + qOraOutValue(values[i], tmpStorage, 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); +} + +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: +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + case SQLT_TIMESTAMP_TZ: + case SQLT_TIMESTAMP_LTZ: +#endif + 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 QDateTime to the internal Oracle DATE format NB! + It does not handle BCE dates. +*/ +QByteArray qMakeOraDate(const QDateTime& dt) +{ + QByteArray ba; + ba.resize(7); + int year = dt.date().year(); + ba[0]= (year / 100) + 100; // century + ba[1]= (year % 100) + 100; // year + ba[2]= dt.date().month(); + ba[3]= dt.date().day(); + ba[4]= dt.time().hour() + 1; + ba[5]= dt.time().minute() + 1; + ba[6]= dt.time().second() + 1; + return ba; +} + +/*! + \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; +} + +QDateTime qMakeDate(const char* oraDate) +{ + int century = uchar(oraDate[0]); + if(century >= 100){ + int year = uchar(oraDate[1]); + year = ((century-100)*100) + (year-100); + int month = oraDate[2]; + int day = oraDate[3]; + int hour = oraDate[4] - 1; + int min = oraDate[5] - 1; + int sec = oraDate[6] - 1; + return QDateTime(QDate(year,month,day), QTime(hour,min,sec)); + } + return QDateTime(); +} + +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) + {} + ~OraFieldInf(); + char *data; + int len; + sb2 ind; + QVariant::Type typ; + ub4 oraType; + OCIDefine *def; + OCILobLocator *lob; + }; + + 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"); + } +} + +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 = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize+1), + dataSize+1, + SQLT_DAT, + &(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; + ub2* 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); + } + + QList tmpStorage; + SizeArray tmpSizes(columnCount); + QVector columns(columnCount); + QOCIBatchCleanupHandler cleaner(columns); + + // 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 ub2[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_DAT; + col.maxLen = 7; + 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()){ + 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; + const QByteArray ba = qMakeOraDate(val.toDateTime()); + Q_ASSERT(ba.size() == int(columns[i].maxLen)); + memcpy(dataPtr, ba.constData(), columns[i].maxLen); + 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 = OCIBindByPos( + 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->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(); + continue; + } + + switch(columns[i].bindAs) { + + case SQLT_DAT: + (*list)[r] = qMakeDate(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); + + 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(qMakeDate(fld.data)); + 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; + QList 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); + + 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); +} + +//////////////////////////////////////////////////////////////////////////// + + +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], -1); // -1: force strlen call + + 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, -1); // -1: force strlen call + if (l1 != user) + result += QLatin1String("owner ") + bang[e] + QLatin1String("= '") + l1 + QLatin1Char(' ') + 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.constraint_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 diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h new file mode 100644 index 0000000000..69911f4bee --- /dev/null +++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QSQL_OCI_H +#define QSQL_OCI_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_OCI +#else +#define Q_EXPORT_SQLDRIVER_OCI Q_SQL_EXPORT +#endif + +typedef struct OCIEnv OCIEnv; +typedef struct OCISvcCtx OCISvcCtx; + +QT_BEGIN_NAMESPACE + +class QSqlResult; +class QOCIDriverPrivate; + +class Q_EXPORT_SQLDRIVER_OCI QOCIDriver : public QSqlDriver +{ + Q_DECLARE_PRIVATE(QOCIDriver) + Q_OBJECT + friend class QOCICols; + friend class QOCIResultPrivate; + +public: + explicit QOCIDriver(QObject* parent = 0); + QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent = 0); + ~QOCIDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString &db, + const QString &user, + const QString &password, + const QString &host, + int port, + const QString &connOpts) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + QSqlResult *createResult() const Q_DECL_OVERRIDE; + QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE; + QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE; + QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE; + QString formatValue(const QSqlField &field, + bool trimStrings) const Q_DECL_OVERRIDE; + QVariant handle() const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE; + +protected: + bool beginTransaction() Q_DECL_OVERRIDE; + bool commitTransaction() Q_DECL_OVERRIDE; + bool rollbackTransaction() Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QSQL_OCI_H -- cgit v1.2.3