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