blob: 7709b13cd15b9219d924683d19a8c4f642e825ce [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_odbc_p.h"
#include <qsqlrecord.h>
#if defined (Q_OS_WIN32)
#include <qt_windows.h>
#endif
#include <qcoreapplication.h>
#include <qvariant.h>
#include <qdatetime.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
#include <qstringlist.h>
#include <qvarlengtharray.h>
#include <qvector.h>
#include <qmath.h>
#include <QDebug>
#include <QSqlQuery>
#include <QtSql/private/qsqldriver_p.h>
#include <QtSql/private/qsqlresult_p.h>
QT_BEGIN_NAMESPACE
// undefine this to prevent initial check of the ODBC driver
#define ODBC_CHECK_DRIVER
static const int COLNAMESIZE = 256;
static const SQLSMALLINT TABLENAMESIZE = 128;
//Map Qt parameter types to ODBC types
static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, int size=-1)
{
QString result;
// Remove any trailing \0 as some drivers misguidedly append one
int realsize = qMin(size, input.size());
if(realsize > 0 && input[realsize-1] == 0)
realsize--;
switch(sizeof(SQLTCHAR)) {
case 1:
result=QString::fromUtf8((const char *)input.constData(), realsize);
break;
case 2:
result=QString::fromUtf16((const ushort *)input.constData(), realsize);
break;
case 4:
result=QString::fromUcs4((const uint *)input.constData(), realsize);
break;
default:
qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
}
return result;
}
inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input)
{
QVarLengthArray<SQLTCHAR> result;
result.resize(input.size());
switch(sizeof(SQLTCHAR)) {
case 1:
memcpy(result.data(), input.toUtf8().data(), input.size());
break;
case 2:
memcpy(result.data(), input.unicode(), input.size() * 2);
break;
case 4:
memcpy(result.data(), input.toUcs4().data(), input.size() * 4);
break;
default:
qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
}
result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't.
return result;
}
class QODBCDriverPrivate : public QSqlDriverPrivate
{
Q_DECLARE_PUBLIC(QODBCDriver)
public:
enum DefaultCase{Lower, Mixed, Upper, Sensitive};
QODBCDriverPrivate()
: QSqlDriverPrivate(), hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), datetime_precision(19),
isFreeTDSDriver(false), hasSQLFetchScroll(true), hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"'))
{
}
SQLHANDLE hEnv;
SQLHANDLE hDbc;
bool unicode;
bool useSchema;
int disconnectCount;
int datetime_precision;
bool isFreeTDSDriver;
bool hasSQLFetchScroll;
bool hasMultiResultSets;
bool checkDriver() const;
void checkUnicode();
void checkDBMS();
void checkHasSQLFetchScroll();
void checkHasMultiResults();
void checkSchemaUsage();
void checkDateTimePrecision();
bool setConnectionOptions(const QString& connOpts);
void splitTableQualifier(const QString &qualifier, QString &catalog,
QString &schema, QString &table);
DefaultCase defaultCase() const;
QString adjustCase(const QString&) const;
QChar quoteChar();
private:
bool isQuoteInitialized;
QChar quote;
};
class QODBCResultPrivate;
class QODBCResult: public QSqlResult
{
Q_DECLARE_PRIVATE(QODBCResult)
public:
QODBCResult(const QODBCDriver *db);
virtual ~QODBCResult();
bool prepare(const QString &query) override;
bool exec() override;
QVariant lastInsertId() const override;
QVariant handle() const override;
protected:
bool fetchNext() override;
bool fetchFirst() override;
bool fetchLast() override;
bool fetchPrevious() override;
bool fetch(int i) override;
bool reset(const QString &query) override;
QVariant data(int field) override;
bool isNull(int field) override;
int size() override;
int numRowsAffected() override;
QSqlRecord record() const override;
void virtual_hook(int id, void *data) override;
void detachFromResultSet() override;
bool nextResult() override;
};
class QODBCResultPrivate: public QSqlResultPrivate
{
Q_DECLARE_PUBLIC(QODBCResult)
public:
Q_DECLARE_SQLDRIVER_PRIVATE(QODBCDriver)
QODBCResultPrivate(QODBCResult *q, const QODBCDriver *db)
: QSqlResultPrivate(q, db),
hStmt(0),
useSchema(false),
hasSQLFetchScroll(true)
{
unicode = drv_d_func()->unicode;
useSchema = drv_d_func()->useSchema;
disconnectCount = drv_d_func()->disconnectCount;
hasSQLFetchScroll = drv_d_func()->hasSQLFetchScroll;
}
inline void clearValues()
{ fieldCache.fill(QVariant()); fieldCacheIdx = 0; }
SQLHANDLE dpEnv() const { return drv_d_func() ? drv_d_func()->hEnv : 0;}
SQLHANDLE dpDbc() const { return drv_d_func() ? drv_d_func()->hDbc : 0;}
SQLHANDLE hStmt;
bool unicode;
bool useSchema;
QSqlRecord rInf;
QVector<QVariant> fieldCache;
int fieldCacheIdx;
int disconnectCount;
bool hasSQLFetchScroll;
bool isStmtHandleValid() const;
void updateStmtHandleState();
};
bool QODBCResultPrivate::isStmtHandleValid() const
{
return drv_d_func() && disconnectCount == drv_d_func()->disconnectCount;
}
void QODBCResultPrivate::updateStmtHandleState()
{
disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0;
}
static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0)
{
SQLINTEGER nativeCode_ = 0;
SQLSMALLINT msgLen = 0;
SQLRETURN r = SQL_NO_DATA;
SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH);
QString result;
int i = 1;
description_[0] = 0;
do {
r = SQLGetDiagRec(handleType,
handle,
i,
state_,
&nativeCode_,
0,
0,
&msgLen);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0)
description_.resize(msgLen+1);
r = SQLGetDiagRec(handleType,
handle,
i,
state_,
&nativeCode_,
description_.data(),
description_.size(),
&msgLen);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (nativeCode)
*nativeCode = nativeCode_;
const QString tmpstore = fromSQLTCHAR(description_, msgLen);
if(result != tmpstore) {
if(!result.isEmpty())
result += QLatin1Char(' ');
result += tmpstore;
}
} else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
return result;
}
++i;
} while (r != SQL_NO_DATA);
return result;
}
static QString qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle = 0,
const SQLHANDLE pDbC = 0, int *nativeCode = 0)
{
QString result;
if (envHandle)
result += qWarnODBCHandle(SQL_HANDLE_ENV, envHandle, nativeCode);
if (pDbC) {
const QString dMessage = qWarnODBCHandle(SQL_HANDLE_DBC, pDbC, nativeCode);
if (!dMessage.isEmpty()) {
if (!result.isEmpty())
result += QLatin1Char(' ');
result += dMessage;
}
}
if (hStmt) {
const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode);
if (!hMessage.isEmpty()) {
if (!result.isEmpty())
result += QLatin1Char(' ');
result += hMessage;
}
}
return result;
}
static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = 0)
{
return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode);
}
static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0)
{
return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode);
}
static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc)
{
qWarning() << message << "\tError:" << qODBCWarn(odbc);
}
static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
{
qWarning() << message << "\tError:" << qODBCWarn(odbc);
}
static void qSqlWarning(const QString &message, const SQLHANDLE hStmt)
{
qWarning() << message << "\tError:" << qODBCWarn(hStmt);
}
static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* p)
{
int nativeCode = -1;
QString message = qODBCWarn(p, &nativeCode);
return QSqlError(QLatin1String("QODBC3: ") + err, message, type,
nativeCode != -1 ? QString::number(nativeCode) : QString());
}
static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
const QODBCDriverPrivate* p)
{
int nativeCode = -1;
QString message = qODBCWarn(p, &nativeCode);
return QSqlError(QLatin1String("QODBC3: ") + err, message, type,
nativeCode != -1 ? QString::number(nativeCode) : QString());
}
static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
{
QVariant::Type type = QVariant::Invalid;
switch (sqltype) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
type = QVariant::Double;
break;
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_BIT:
type = isSigned ? QVariant::Int : QVariant::UInt;
break;
case SQL_TINYINT:
type = QVariant::UInt;
break;
case SQL_BIGINT:
type = isSigned ? QVariant::LongLong : QVariant::ULongLong;
break;
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
type = QVariant::ByteArray;
break;
case SQL_DATE:
case SQL_TYPE_DATE:
type = QVariant::Date;
break;
case SQL_TIME:
case SQL_TYPE_TIME:
type = QVariant::Time;
break;
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP:
type = QVariant::DateTime;
break;
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
type = QVariant::String;
break;
case SQL_CHAR:
case SQL_VARCHAR:
#if (ODBCVER >= 0x0350)
case SQL_GUID:
#endif
case SQL_LONGVARCHAR:
type = QVariant::String;
break;
default:
type = QVariant::ByteArray;
break;
}
return type;
}
static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
SQLLEN lengthIndicator = 0;
// NB! colSize must be a multiple of 2 for unicode enabled DBs
if (colSize <= 0) {
colSize = 256;
} else if (colSize > 65536) { // limit buffer size to 64 KB
colSize = 65536;
} else {
colSize++; // make sure there is room for more than the 0 termination
}
if(unicode) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1);
QVarLengthArray<SQLTCHAR> buf(colSize);
memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
(SQLPOINTER)buf.data(),
colSize*sizeof(SQLTCHAR),
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA) {
fieldVal.clear();
break;
}
// starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
// instead of the length (which sometimes was wrong in older versions)
// see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
// if length indicator equals SQL_NO_TOTAL, indicating that
// more data can be fetched, but size not known, collect data
// and fetch next block
if (lengthIndicator == SQL_NO_TOTAL) {
fieldVal += fromSQLTCHAR(buf, colSize);
continue;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR));
fieldVal += fromSQLTCHAR(buf, rSize);
if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
} else {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator + 1;
QVarLengthArray<SQLCHAR> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
(SQLPOINTER)buf.data(),
colSize,
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
fieldVal.clear();
break;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
// Remove any trailing \0 as some drivers misguidedly append one
int realsize = qMin(rSize, buf.size());
if (realsize > 0 && buf[realsize - 1] == 0)
realsize--;
fieldVal += QString::fromUtf8(reinterpret_cast<const char *>(buf.constData()), realsize);
if (lengthIndicator < SQLLEN(colSize)) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
}
return fieldVal;
}
static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
{
QByteArray fieldVal;
SQLSMALLINT colNameLen;
SQLSMALLINT colType;
SQLULEN colSize;
SQLSMALLINT colScale;
SQLSMALLINT nullable;
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQL_ERROR;
QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
r = SQLDescribeCol(hStmt,
column + 1,
colName.data(),
COLNAMESIZE,
&colNameLen,
&colType,
&colSize,
&colScale,
&nullable);
if (r != SQL_SUCCESS)
qWarning() << "qGetBinaryData: Unable to describe column" << column;
// SQLDescribeCol may return 0 if size cannot be determined
if (!colSize)
colSize = 255;
else if (colSize > 65536) // read the field in 64 KB chunks
colSize = 65536;
fieldVal.resize(colSize);
ulong read = 0;
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_BINARY,
const_cast<char *>(fieldVal.constData() + read),
colSize,
&lengthIndicator);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
break;
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QVariant::ByteArray);
if (lengthIndicator > SQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) {
read += colSize;
colSize = 65536;
} else {
read += lengthIndicator;
}
if (r == SQL_SUCCESS) { // the whole field was read in one chunk
fieldVal.resize(read);
break;
}
fieldVal.resize(fieldVal.size() + colSize);
}
return fieldVal;
}
static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
{
SQLINTEGER intbuf = 0;
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQLGetData(hStmt,
column+1,
isSigned ? SQL_C_SLONG : SQL_C_ULONG,
(SQLPOINTER)&intbuf,
sizeof(intbuf),
&lengthIndicator);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
return QVariant(QVariant::Invalid);
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QVariant::Int);
if (isSigned)
return int(intbuf);
else
return uint(intbuf);
}
static QVariant qGetDoubleData(SQLHANDLE hStmt, int column)
{
SQLDOUBLE dblbuf;
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQLGetData(hStmt,
column+1,
SQL_C_DOUBLE,
(SQLPOINTER) &dblbuf,
0,
&lengthIndicator);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
return QVariant(QVariant::Invalid);
}
if(lengthIndicator == SQL_NULL_DATA)
return QVariant(QVariant::Double);
return (double) dblbuf;
}
static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
{
SQLBIGINT lngbuf = 0;
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQLGetData(hStmt,
column+1,
isSigned ? SQL_C_SBIGINT : SQL_C_UBIGINT,
(SQLPOINTER) &lngbuf,
sizeof(lngbuf),
&lengthIndicator);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
return QVariant(QVariant::Invalid);
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QVariant::LongLong);
if (isSigned)
return qint64(lngbuf);
else
return quint64(lngbuf);
}
static bool isAutoValue(const SQLHANDLE hStmt, int column)
{
SQLLEN nNumericAttribute = 0; // Check for auto-increment
const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE,
0, 0, 0, &nNumericAttribute);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
qSqlWarning(QStringLiteral("qMakeField: Unable to get autovalue attribute for column ")
+ QString::number(column), hStmt);
return false;
}
return nNumericAttribute != SQL_FALSE;
}
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage);
// creates a QSqlField from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p)
{
QString fname = qGetStringData(hStmt, 3, -1, p->unicode);
int type = qGetIntData(hStmt, 4).toInt(); // column type
QSqlField f(fname, qDecodeODBCType(type, p));
QVariant var = qGetIntData(hStmt, 6);
f.setLength(var.isNull() ? -1 : var.toInt()); // column size
var = qGetIntData(hStmt, 8).toInt();
f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
f.setSqlType(type);
int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag
// required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
if (required == SQL_NO_NULLS)
f.setRequired(true);
else if (required == SQL_NULLABLE)
f.setRequired(false);
// else we don't know
return f;
}
static QSqlField qMakeFieldInfo(const QODBCResultPrivate* p, int i )
{
QString errorMessage;
const QSqlField result = qMakeFieldInfo(p->hStmt, i, &errorMessage);
if (!errorMessage.isEmpty())
qSqlWarning(errorMessage, p);
return result;
}
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage)
{
SQLSMALLINT colNameLen;
SQLSMALLINT colType;
SQLULEN colSize;
SQLSMALLINT colScale;
SQLSMALLINT nullable;
SQLRETURN r = SQL_ERROR;
QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
errorMessage->clear();
r = SQLDescribeCol(hStmt,
i+1,
colName.data(),
(SQLSMALLINT)COLNAMESIZE,
&colNameLen,
&colType,
&colSize,
&colScale,
&nullable);
if (r != SQL_SUCCESS) {
*errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i);
return QSqlField();
}
SQLLEN unsignedFlag = SQL_FALSE;
r = SQLColAttribute (hStmt,
i + 1,
SQL_DESC_UNSIGNED,
0,
0,
0,
&unsignedFlag);
if (r != SQL_SUCCESS) {
qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ")
+ QString::number(i), hStmt);
}
const QString qColName(fromSQLTCHAR(colName, colNameLen));
// nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
QVariant::Type type = qDecodeODBCType(colType, unsignedFlag == SQL_FALSE);
QSqlField f(qColName, type);
f.setSqlType(colType);
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
if (nullable == SQL_NO_NULLS)
f.setRequired(true);
else if (nullable == SQL_NULLABLE)
f.setRequired(false);
// else we don't know
f.setAutoValue(isAutoValue(hStmt, i));
QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE);
SQLSMALLINT tableNameLen;
r = SQLColAttribute(hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName.data(),
TABLENAMESIZE, &tableNameLen, 0);
if (r == SQL_SUCCESS)
f.setTableName(fromSQLTCHAR(tableName, tableNameLen));
return f;
}
static size_t qGetODBCVersion(const QString &connOpts)
{
if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive))
return SQL_OV_ODBC3;
return SQL_OV_ODBC2;
}
QChar QODBCDriverPrivate::quoteChar()
{
if (!isQuoteInitialized) {
SQLTCHAR driverResponse[4];
SQLSMALLINT length;
int r = SQLGetInfo(hDbc,
SQL_IDENTIFIER_QUOTE_CHAR,
&driverResponse,
sizeof(driverResponse),
&length);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
quote = QChar(driverResponse[0]);
else
quote = QLatin1Char('"');
isQuoteInitialized = true;
}
return quote;
}
bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
{
// Set any connection attributes
const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
SQLRETURN r = SQL_SUCCESS;
for (int i = 0; i < opts.count(); ++i) {
const QString tmp(opts.at(i));
int idx;
if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\'';
continue;
}
const QString opt(tmp.left(idx));
const QString val(tmp.mid(idx + 1).simplified());
SQLUINTEGER v = 0;
r = SQL_SUCCESS;
if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) {
if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) {
v = SQL_MODE_READ_ONLY;
} else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) {
v = SQL_MODE_READ_WRITE;
} else {
qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) {
val.utf16(); // 0 terminate
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
toSQLTCHAR(val).data(),
val.length()*sizeof(SQLTCHAR));
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) {
if (val.toUpper() == QLatin1String("SQL_TRUE")) {
v = SQL_TRUE;
} else if (val.toUpper() == QLatin1String("SQL_FALSE")) {
v = SQL_FALSE;
} else {
qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) {
val.utf16(); // 0 terminate
r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
toSQLTCHAR(val).data(),
val.length()*sizeof(SQLTCHAR));
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) {
if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) {
v = SQL_OPT_TRACE_OFF;
} else if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_ON")) {
v = SQL_OPT_TRACE_ON;
} else {
qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_POOLING")) {
if (val == QLatin1String("SQL_CP_OFF"))
v = SQL_CP_OFF;
else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_DRIVER"))
v = SQL_CP_ONE_PER_DRIVER;
else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_HENV"))
v = SQL_CP_ONE_PER_HENV;
else if (val.toUpper() == QLatin1String("SQL_CP_DEFAULT"))
v = SQL_CP_DEFAULT;
else {
qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) {
if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH"))
v = SQL_CP_STRICT_MATCH;
else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH"))
v = SQL_CP_RELAXED_MATCH;
else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT"))
v = SQL_CP_MATCH_DEFAULT;
else {
qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0);
} else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) {
// Already handled in QODBCDriver::open()
continue;
} else {
qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\'';
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg(
opt), this);
}
return true;
}
void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog,
QString &schema, QString &table)
{
if (!useSchema) {
table = qualifier;
return;
}
QStringList l = qualifier.split(QLatin1Char('.'));
if (l.count() > 3)
return; // can't possibly be a valid table qualifier
int i = 0, n = l.count();
if (n == 1) {
table = qualifier;
} else {
for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
if (n == 3) {
if (i == 0) {
catalog = *it;
} else if (i == 1) {
schema = *it;
} else if (i == 2) {
table = *it;
}
} else if (n == 2) {
if (i == 0) {
schema = *it;
} else if (i == 1) {
table = *it;
}
}
i++;
}
}
}
QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
{
DefaultCase ret;
SQLUSMALLINT casing;
int r = SQLGetInfo(hDbc,
SQL_IDENTIFIER_CASE,
&casing,
sizeof(casing),
NULL);
if ( r != SQL_SUCCESS)
ret = Mixed;//arbitrary case if driver cannot be queried
else {
switch (casing) {
case (SQL_IC_UPPER):
ret = Upper;
break;
case (SQL_IC_LOWER):
ret = Lower;
break;
case (SQL_IC_SENSITIVE):
ret = Sensitive;
break;
case (SQL_IC_MIXED):
default:
ret = Mixed;
break;
}
}
return ret;
}
/*
Adjust the casing of an identifier to match what the
database engine would have done to it.
*/
QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
{
QString ret = identifier;
switch(defaultCase()) {
case (Lower):
ret = identifier.toLower();
break;
case (Upper):
ret = identifier.toUpper();
break;
case(Mixed):
case(Sensitive):
default:
ret = identifier;
}
return ret;
}
////////////////////////////////////////////////////////////////////////////
QODBCResult::QODBCResult(const QODBCDriver *db)
: QSqlResult(*new QODBCResultPrivate(this, db))
{
}
QODBCResult::~QODBCResult()
{
Q_D(QODBCResult);
if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) {
SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ")
+ QString::number(r), d);
}
}
bool QODBCResult::reset (const QString& query)
{
Q_D(QODBCResult);
setActive(false);
setAt(QSql::BeforeFirstRow);
d->rInf.clear();
d->fieldCache.clear();
d->fieldCacheIdx = 0;
// Always reallocate the statement handle - the statement attributes
// are not reset if SQLFreeStmt() is called which causes some problems.
SQLRETURN r;
if (d->hStmt && d->isStmtHandleValid()) {
r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d);
return false;
}
}
r = SQLAllocHandle(SQL_HANDLE_STMT,
d->dpDbc(),
&d->hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d);
return false;
}
d->updateStmtHandleState();
if (isForwardOnly()) {
r = SQLSetStmtAttr(d->hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
} else {
r = SQLSetStmtAttr(d->hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
return false;
}
r = SQLExecDirect(d->hStmt,
toSQLTCHAR(query).data(),
(SQLINTEGER) query.length());
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
}
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
for (int i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
} else {
setSelect(false);
}
setActive(true);
return true;
}
bool QODBCResult::fetch(int i)
{
Q_D(QODBCResult);
if (!driver()->isOpen())
return false;
if (isForwardOnly() && i < at())
return false;
if (i == at())
return true;
d->clearValues();
int actualIdx = i + 1;
if (actualIdx <= 0) {
setAt(QSql::BeforeFirstRow);
return false;
}
SQLRETURN r;
if (isForwardOnly()) {
bool ok = true;
while (ok && i > at())
ok = fetchNext();
return ok;
} else {
r = SQLFetchScroll(d->hStmt,
SQL_FETCH_ABSOLUTE,
actualIdx);
}
if (r != SQL_SUCCESS) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch"), QSqlError::ConnectionError, d));
return false;
}
setAt(i);
return true;
}
bool QODBCResult::fetchNext()
{
Q_D(QODBCResult);
SQLRETURN r;
d->clearValues();
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(d->hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(d->hStmt);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch next"), QSqlError::ConnectionError, d));
return false;
}
setAt(at() + 1);
return true;
}
bool QODBCResult::fetchFirst()
{
Q_D(QODBCResult);
if (isForwardOnly() && at() != QSql::BeforeFirstRow)
return false;
SQLRETURN r;
d->clearValues();
if (isForwardOnly()) {
return fetchNext();
}
r = SQLFetchScroll(d->hStmt,
SQL_FETCH_FIRST,
0);
if (r != SQL_SUCCESS) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch first"), QSqlError::ConnectionError, d));
return false;
}
setAt(0);
return true;
}
bool QODBCResult::fetchPrevious()
{
Q_D(QODBCResult);
if (isForwardOnly())
return false;
SQLRETURN r;
d->clearValues();
r = SQLFetchScroll(d->hStmt,
SQL_FETCH_PRIOR,
0);
if (r != SQL_SUCCESS) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch previous"), QSqlError::ConnectionError, d));
return false;
}
setAt(at() - 1);
return true;
}
bool QODBCResult::fetchLast()
{
Q_D(QODBCResult);
SQLRETURN r;
d->clearValues();
if (isForwardOnly()) {
// cannot seek to last row in forwardOnly mode, so we have to use brute force
int i = at();
if (i == QSql::AfterLastRow)
return false;
if (i == QSql::BeforeFirstRow)
i = 0;
while (fetchNext())
++i;
setAt(i);
return true;
}
r = SQLFetchScroll(d->hStmt,
SQL_FETCH_LAST,
0);
if (r != SQL_SUCCESS) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch last"), QSqlError::ConnectionError, d));
return false;
}
SQLULEN currRow = 0;
r = SQLGetStmtAttr(d->hStmt,
SQL_ROW_NUMBER,
&currRow,
SQL_IS_INTEGER,
0);
if (r != SQL_SUCCESS)
return false;
setAt(currRow-1);
return true;
}
QVariant QODBCResult::data(int field)
{
Q_D(QODBCResult);
if (field >= d->rInf.count() || field < 0) {
qWarning() << "QODBCResult::data: column" << field << "out of range";
return QVariant();
}
if (field < d->fieldCacheIdx)
return d->fieldCache.at(field);
SQLRETURN r(0);
SQLLEN lengthIndicator = 0;
for (int i = d->fieldCacheIdx; i <= field; ++i) {
// some servers do not support fetching column n after we already
// fetched column n+1, so cache all previous columns here
const QSqlField info = d->rInf.field(i);
switch (info.type()) {
case QVariant::LongLong:
d->fieldCache[i] = qGetBigIntData(d->hStmt, i);
break;
case QVariant::ULongLong:
d->fieldCache[i] = qGetBigIntData(d->hStmt, i, false);
break;
case QVariant::Int:
d->fieldCache[i] = qGetIntData(d->hStmt, i);
break;
case QVariant::UInt:
d->fieldCache[i] = qGetIntData(d->hStmt, i, false);
break;
case QVariant::Date:
DATE_STRUCT dbuf;
r = SQLGetData(d->hStmt,
i + 1,
SQL_C_DATE,
(SQLPOINTER)&dbuf,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
else
d->fieldCache[i] = QVariant(QVariant::Date);
break;
case QVariant::Time:
TIME_STRUCT tbuf;
r = SQLGetData(d->hStmt,
i + 1,
SQL_C_TIME,
(SQLPOINTER)&tbuf,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
else
d->fieldCache[i] = QVariant(QVariant::Time);
break;
case QVariant::DateTime:
TIMESTAMP_STRUCT dtbuf;
r = SQLGetData(d->hStmt,
i + 1,
SQL_C_TIMESTAMP,
(SQLPOINTER)&dtbuf,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
else
d->fieldCache[i] = QVariant(QVariant::DateTime);
break;
case QVariant::ByteArray:
d->fieldCache[i] = qGetBinaryData(d->hStmt, i);
break;
case QVariant::String:
d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode);
break;
case QVariant::Double:
switch(numericalPrecisionPolicy()) {
case QSql::LowPrecisionInt32:
d->fieldCache[i] = qGetIntData(d->hStmt, i);
break;
case QSql::LowPrecisionInt64:
d->fieldCache[i] = qGetBigIntData(d->hStmt, i);
break;
case QSql::LowPrecisionDouble:
d->fieldCache[i] = qGetDoubleData(d->hStmt, i);
break;
case QSql::HighPrecision:
d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false);
break;
}
break;
default:
d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false));
break;
}
d->fieldCacheIdx = field + 1;
}
return d->fieldCache[field];
}
bool QODBCResult::isNull(int field)
{
Q_D(const QODBCResult);
if (field < 0 || field >= d->fieldCache.size())
return true;
if (field <= d->fieldCacheIdx) {
// since there is no good way to find out whether the value is NULL
// without fetching the field we'll fetch it here.
// (data() also sets the NULL flag)
data(field);
}
return d->fieldCache.at(field).isNull();
}
int QODBCResult::size()
{
return -1;
}
int QODBCResult::numRowsAffected()
{
Q_D(QODBCResult);
SQLLEN affectedRowCount = 0;
SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
if (r == SQL_SUCCESS)
return affectedRowCount;
else
qSqlWarning(QLatin1String("QODBCResult::numRowsAffected: Unable to count affected rows"), d);
return -1;
}
bool QODBCResult::prepare(const QString& query)
{
Q_D(QODBCResult);
setActive(false);
setAt(QSql::BeforeFirstRow);
SQLRETURN r;
d->rInf.clear();
if (d->hStmt && d->isStmtHandleValid()) {
r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d);
return false;
}
}
r = SQLAllocHandle(SQL_HANDLE_STMT,
d->dpDbc(),
&d->hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d);
return false;
}
d->updateStmtHandleState();
if (isForwardOnly()) {
r = SQLSetStmtAttr(d->hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
} else {
r = SQLSetStmtAttr(d->hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
return false;
}
r = SQLPrepare(d->hStmt,
toSQLTCHAR(query).data(),
(SQLINTEGER) query.length());
if (r != SQL_SUCCESS) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to prepare statement"), QSqlError::StatementError, d));
return false;
}
return true;
}
bool QODBCResult::exec()
{
Q_D(QODBCResult);
setActive(false);
setAt(QSql::BeforeFirstRow);
d->rInf.clear();
d->fieldCache.clear();
d->fieldCacheIdx = 0;
if (!d->hStmt) {
qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d);
return false;
}
if (isSelect())
SQLCloseCursor(d->hStmt);
QVector<QVariant>& values = boundValues();
QVector<QByteArray> tmpStorage(values.count(), QByteArray()); // holds temporary buffers
QVarLengthArray<SQLLEN, 32> indicators(values.count());
memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
// bind parameters - only positional binding allowed
int i;
SQLRETURN r;
for (i = 0; i < values.count(); ++i) {
if (bindValueType(i) & QSql::Out)
values[i].detach();
const QVariant &val = values.at(i);
SQLLEN *ind = &indicators[i];
if (val.isNull())
*ind = SQL_NULL_DATA;
switch (val.type()) {
case QVariant::Date: {
QByteArray &ba = tmpStorage[i];
ba.resize(sizeof(DATE_STRUCT));
DATE_STRUCT *dt = (DATE_STRUCT *)const_cast<char *>(ba.constData());
QDate qdt = val.toDate();
dt->year = qdt.year();
dt->month = qdt.month();
dt->day = qdt.day();
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_DATE,
SQL_DATE,
0,
0,
(void *) dt,
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break; }
case QVariant::Time: {
QByteArray &ba = tmpStorage[i];
ba.resize(sizeof(TIME_STRUCT));
TIME_STRUCT *dt = (TIME_STRUCT *)const_cast<char *>(ba.constData());
QTime qdt = val.toTime();
dt->hour = qdt.hour();
dt->minute = qdt.minute();
dt->second = qdt.second();
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TIME,
SQL_TIME,
0,
0,
(void *) dt,
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break; }
case QVariant::DateTime: {
QByteArray &ba = tmpStorage[i];
ba.resize(sizeof(TIMESTAMP_STRUCT));
TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)const_cast<char *>(ba.constData());
QDateTime qdt = val.toDateTime();
dt->year = qdt.date().year();
dt->month = qdt.date().month();
dt->day = qdt.date().day();
dt->hour = qdt.time().hour();
dt->minute = qdt.time().minute();
dt->second = qdt.time().second();
int precision = d->drv_d_func()->datetime_precision - 20; // (20 includes a separating period)
if (precision <= 0) {
dt->fraction = 0;
} else {
dt->fraction = qdt.time().msec() * 1000000;
// (How many leading digits do we want to keep? With SQL Server 2005, this should be 3: 123000000)
int keep = (int)qPow(10.0, 9 - qMin(9, precision));
dt->fraction = (dt->fraction / keep) * keep;
}
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TIMESTAMP,
SQL_TIMESTAMP,
d->drv_d_func()->datetime_precision,
precision,
(void *) dt,
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break; }
case QVariant::Int:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_SLONG,
SQL_INTEGER,
0,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::UInt:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_ULONG,
SQL_NUMERIC,
15,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::Double:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_DOUBLE,
SQL_DOUBLE,
0,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::LongLong:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_SBIGINT,
SQL_BIGINT,
0,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::ULongLong:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_UBIGINT,
SQL_BIGINT,
0,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::ByteArray:
if (*ind != SQL_NULL_DATA) {
*ind = val.toByteArray().size();
}
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_BINARY,
SQL_LONGVARBINARY,
val.toByteArray().size(),
0,
const_cast<char *>(val.toByteArray().constData()),
val.toByteArray().size(),
ind);
break;
case QVariant::Bool:
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_BIT,
SQL_BIT,
0,
0,
const_cast<void *>(val.constData()),
0,
*ind == SQL_NULL_DATA ? ind : NULL);
break;
case QVariant::String:
if (d->unicode) {
QByteArray &ba = tmpStorage[i];
QString str = val.toString();
if (*ind != SQL_NULL_DATA)
*ind = str.length() * sizeof(SQLTCHAR);
int strSize = str.length() * sizeof(SQLTCHAR);
if (bindValueType(i) & QSql::Out) {
const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str));
ba = QByteArray((const char *)a.constData(), a.size() * sizeof(SQLTCHAR));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
0, // god knows... don't change this!
0,
ba.data(),
ba.size(),
ind);
break;
}
ba = QByteArray ((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
strSize,
0,
const_cast<char *>(ba.constData()),
ba.size(),
ind);
break;
}
else
{
QByteArray &str = tmpStorage[i];
str = val.toString().toUtf8();
if (*ind != SQL_NULL_DATA)
*ind = str.length();
int strSize = str.length();
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_CHAR,
strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR,
strSize,
0,
const_cast<char *>(str.constData()),
strSize,
ind);
break;
}
// fall through
default: {
QByteArray &ba = tmpStorage[i];
if (*ind != SQL_NULL_DATA)
*ind = ba.size();
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_BINARY,
SQL_VARBINARY,
ba.length() + 1,
0,
const_cast<char *>(ba.constData()),
ba.length() + 1,
ind);
break; }
}
if (r != SQL_SUCCESS) {
qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to bind variable"), QSqlError::StatementError, d));
return false;
}
}
r = SQLExecute(d->hStmt);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
}
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
for (int i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
} else {
setSelect(false);
}
setActive(true);
//get out parameters
if (!hasOutValues())
return true;
for (i = 0; i < values.count(); ++i) {
switch (values.at(i).type()) {
case QVariant::Date: {
DATE_STRUCT ds = *((DATE_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
break; }
case QVariant::Time: {
TIME_STRUCT dt = *((TIME_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
break; }
case QVariant::DateTime: {
TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*)
const_cast<char *>(tmpStorage.at(i).constData()));
values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
break; }
case QVariant::Bool:
case QVariant::Int:
case QVariant::UInt:
case QVariant::Double:
case QVariant::ByteArray:
case QVariant::LongLong:
case QVariant::ULongLong:
//nothing to do
break;
case QVariant::String:
if (d->unicode) {
if (bindValueType(i) & QSql::Out) {
const QByteArray &first = tmpStorage.at(i);
QVarLengthArray<SQLTCHAR> array;
array.append((const SQLTCHAR *)first.constData(), first.size());
values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR));
}
break;
}
// fall through
default: {
if (bindValueType(i) & QSql::Out)
values[i] = tmpStorage.at(i);
break; }
}
if (indicators[i] == SQL_NULL_DATA)
values[i] = QVariant(values[i].type());
}
return true;
}
QSqlRecord QODBCResult::record() const
{
Q_D(const QODBCResult);
if (!isActive() || !isSelect())
return QSqlRecord();
return d->rInf;
}
QVariant QODBCResult::lastInsertId() const
{
Q_D(const QODBCResult);
QString sql;
switch (driver()->dbmsType()) {
case QSqlDriver::MSSqlServer:
case QSqlDriver::Sybase:
sql = QLatin1String("SELECT @@IDENTITY;");
break;
case QSqlDriver::MySqlServer:
sql = QLatin1String("SELECT LAST_INSERT_ID();");
break;
case QSqlDriver::PostgreSQL:
sql = QLatin1String("SELECT lastval();");
break;
default:
break;
}
if (!sql.isEmpty()) {
QSqlQuery qry(driver()->createResult());
if (qry.exec(sql) && qry.next())
return qry.value(0);
qSqlWarning(QLatin1String("QODBCResult::lastInsertId: Unable to get lastInsertId"), d);
} else {
qSqlWarning(QLatin1String("QODBCResult::lastInsertId: not implemented for this DBMS"), d);
}
return QVariant();
}
QVariant QODBCResult::handle() const
{
Q_D(const QODBCResult);
return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
}
bool QODBCResult::nextResult()
{
Q_D(QODBCResult);
setActive(false);
setAt(QSql::BeforeFirstRow);
d->rInf.clear();
d->fieldCache.clear();
d->fieldCacheIdx = 0;
setSelect(false);
SQLRETURN r = SQLMoreResults(d->hStmt);
if (r != SQL_SUCCESS) {
if (r == SQL_SUCCESS_WITH_INFO) {
int nativeCode = -1;
QString message = qODBCWarn(d, &nativeCode);
qWarning() << "QODBCResult::nextResult():" << message;
} else {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch last"), QSqlError::ConnectionError, d));
return false;
}
}
SQLSMALLINT count = 0;
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
for (int i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
} else {
setSelect(false);
}
setActive(true);
return true;
}
void QODBCResult::virtual_hook(int id, void *data)
{
QSqlResult::virtual_hook(id, data);
}
void QODBCResult::detachFromResultSet()
{
Q_D(QODBCResult);
if (d->hStmt)
SQLCloseCursor(d->hStmt);
}
////////////////////////////////////////
QODBCDriver::QODBCDriver(QObject *parent)
: QSqlDriver(*new QODBCDriverPrivate, parent)
{
}
QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject *parent)
: QSqlDriver(*new QODBCDriverPrivate, parent)
{
Q_D(QODBCDriver);
d->hEnv = env;
d->hDbc = con;
if (env && con) {
setOpen(true);
setOpenError(false);
}
}
QODBCDriver::~QODBCDriver()
{
cleanup();
}
bool QODBCDriver::hasFeature(DriverFeature f) const
{
Q_D(const QODBCDriver);
switch (f) {
case Transactions: {
if (!d->hDbc)
return false;
SQLUSMALLINT txn;
SQLSMALLINT t;
int r = SQLGetInfo(d->hDbc,
(SQLUSMALLINT)SQL_TXN_CAPABLE,
&txn,
sizeof(txn),
&t);
if (r != SQL_SUCCESS || txn == SQL_TC_NONE)
return false;
else
return true;
}
case Unicode:
return d->unicode;
case PreparedQueries:
case PositionalPlaceholders:
case FinishQuery:
case LowPrecisionNumbers:
return true;
case QuerySize:
case NamedPlaceholders:
case BatchOperations:
case SimpleLocking:
case EventNotifications:
case CancelQuery:
return false;
case LastInsertId:
return (d->dbmsType == MSSqlServer)
|| (d->dbmsType == Sybase)
|| (d->dbmsType == MySqlServer)
|| (d->dbmsType == PostgreSQL);
case MultipleResultSets:
return d->hasMultiResultSets;
case BLOB: {
if (d->dbmsType == MySqlServer)
return true;
else
return false;
}
}
return false;
}
bool QODBCDriver::open(const QString & db,
const QString & user,
const QString & password,
const QString &,
int,
const QString& connOpts)
{
Q_D(QODBCDriver);
if (isOpen())
close();
SQLRETURN r;
r = SQLAllocHandle(SQL_HANDLE_ENV,
SQL_NULL_HANDLE,
&d->hEnv);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d);
setOpenError(true);
return false;
}
r = SQLSetEnvAttr(d->hEnv,
SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)qGetODBCVersion(connOpts),
SQL_IS_UINTEGER);
r = SQLAllocHandle(SQL_HANDLE_DBC,
d->hEnv,
&d->hDbc);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d);
setOpenError(true);
cleanup();
return false;
}
if (!d->setConnectionOptions(connOpts)) {
cleanup();
return false;
}
// Create the connection string
QString connQStr;
// support the "DRIVER={SQL SERVER};SERVER=blah" syntax
if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive))
connQStr = QLatin1String("FILEDSN=") + db;
else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive)
|| db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive))
connQStr = db;
else
connQStr = QLatin1String("DSN=") + db;
if (!user.isEmpty())
connQStr += QLatin1String(";UID=") + user;
if (!password.isEmpty())
connQStr += QLatin1String(";PWD=") + password;
SQLSMALLINT cb;
QVarLengthArray<SQLTCHAR> connOut(1024);
memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
r = SQLDriverConnect(d->hDbc,
NULL,
toSQLTCHAR(connQStr).data(),
(SQLSMALLINT)connQStr.length(),
connOut.data(),
1024,
&cb,
/*SQL_DRIVER_NOPROMPT*/0);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
setOpenError(true);
cleanup();
return false;
}
if (!d->checkDriver()) {
setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
"functionality required"), QSqlError::ConnectionError, d));
setOpenError(true);
cleanup();
return false;
}
d->checkUnicode();
d->checkSchemaUsage();
d->checkDBMS();
d->checkHasSQLFetchScroll();
d->checkHasMultiResults();
d->checkDateTimePrecision();
setOpen(true);
setOpenError(false);
if (d->dbmsType == MSSqlServer) {
QSqlQuery i(createResult());
i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON"));
}
return true;
}
void QODBCDriver::close()
{
cleanup();
setOpen(false);
setOpenError(false);
}
void QODBCDriver::cleanup()
{
Q_D(QODBCDriver);
SQLRETURN r;
if(d->hDbc) {
// Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
if (isOpen()) {
r = SQLDisconnect(d->hDbc);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d);
else
d->disconnectCount++;
}
r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d);
d->hDbc = 0;
}
if (d->hEnv) {
r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d);
d->hEnv = 0;
}
}
// checks whether the server can return char, varchar and longvarchar
// as two byte unicode characters
void QODBCDriverPrivate::checkUnicode()
{
SQLRETURN r;
SQLUINTEGER fFunc;
unicode = false;
r = SQLGetInfo(hDbc,
SQL_CONVERT_CHAR,
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) {
unicode = true;
return;
}
r = SQLGetInfo(hDbc,
SQL_CONVERT_VARCHAR,
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) {
unicode = true;
return;
}
r = SQLGetInfo(hDbc,
SQL_CONVERT_LONGVARCHAR,
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
unicode = true;
return;
}
SQLHANDLE hStmt;
r = SQLAllocHandle(SQL_HANDLE_STMT,
hDbc,
&hStmt);
r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS);
if(r == SQL_SUCCESS) {
r = SQLFetch(hStmt);
if(r == SQL_SUCCESS) {
QVarLengthArray<SQLWCHAR> buffer(10);
r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL);
if(r == SQL_SUCCESS && fromSQLTCHAR(buffer) == QLatin1String("test")) {
unicode = true;
}
}
}
r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
bool QODBCDriverPrivate::checkDriver() const
{
#ifdef ODBC_CHECK_DRIVER
static const SQLUSMALLINT reqFunc[] = {
SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0
};
// these functions are optional
static const SQLUSMALLINT optFunc[] = {
SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0
};
SQLRETURN r;
SQLUSMALLINT sup;
int i;
// check the required functions
for (i = 0; reqFunc[i] != 0; ++i) {
r = SQLGetFunctions(hDbc, reqFunc[i], &sup);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this);
return false;
}
if (sup == SQL_FALSE) {
qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] <<
").\nPlease look at the Qt SQL Module Driver documentation for more information.";
return false;
}
}
// these functions are optional and just generate a warning
for (i = 0; optFunc[i] != 0; ++i) {
r = SQLGetFunctions(hDbc, optFunc[i], &sup);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this);
return false;
}
if (sup == SQL_FALSE) {
qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')';
return true;
}
}
#endif //ODBC_CHECK_DRIVER
return true;
}
void QODBCDriverPrivate::checkSchemaUsage()
{
SQLRETURN r;
SQLUINTEGER val;
r = SQLGetInfo(hDbc,
SQL_SCHEMA_USAGE,
(SQLPOINTER) &val,
sizeof(val),
NULL);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
useSchema = (val != 0);
}
void QODBCDriverPrivate::checkDBMS()
{
SQLRETURN r;
QVarLengthArray<SQLTCHAR> serverString(200);
SQLSMALLINT t;
memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR));
r = SQLGetInfo(hDbc,
SQL_DBMS_NAME,
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
if (serverType.contains(QLatin1String("PostgreSQL"), Qt::CaseInsensitive))
dbmsType = QSqlDriver::PostgreSQL;
else if (serverType.contains(QLatin1String("Oracle"), Qt::CaseInsensitive))
dbmsType = QSqlDriver::Oracle;
else if (serverType.contains(QLatin1String("MySql"), Qt::CaseInsensitive))
dbmsType = QSqlDriver::MySqlServer;
else if (serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive))
dbmsType = QSqlDriver::MSSqlServer;
else if (serverType.contains(QLatin1String("Sybase"), Qt::CaseInsensitive))
dbmsType = QSqlDriver::Sybase;
}
r = SQLGetInfo(hDbc,
SQL_DRIVER_NAME,
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
isFreeTDSDriver = serverType.contains(QLatin1String("tdsodbc"), Qt::CaseInsensitive);
unicode = unicode && !isFreeTDSDriver;
}
}
void QODBCDriverPrivate::checkHasSQLFetchScroll()
{
SQLUSMALLINT sup;
SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) {
hasSQLFetchScroll = false;
qWarning("QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries");
}
}
void QODBCDriverPrivate::checkHasMultiResults()
{
QVarLengthArray<SQLTCHAR> driverResponse(2);
SQLSMALLINT length;
SQLRETURN r = SQLGetInfo(hDbc,
SQL_MULT_RESULT_SETS,
driverResponse.data(),
SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
&length);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(QLatin1Char('Y'));
}
void QODBCDriverPrivate::checkDateTimePrecision()
{
SQLINTEGER columnSize;
SQLHANDLE hStmt;
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
if (r != SQL_SUCCESS) {
return;
}
r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
r = SQLFetch(hStmt);
if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
{
if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) {
datetime_precision = (int)columnSize;
}
}
}
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
QSqlResult *QODBCDriver::createResult() const
{
return new QODBCResult(this);
}
bool QODBCDriver::beginTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
qWarning("QODBCDriver::beginTransaction: Database not open");
return false;
}
SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
SQLRETURN r = SQLSetConnectAttr(d->hDbc,
SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)size_t(ac),
sizeof(ac));
if (r != SQL_SUCCESS) {
setLastError(qMakeError(tr("Unable to disable autocommit"),
QSqlError::TransactionError, d));
return false;
}
return true;
}
bool QODBCDriver::commitTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
qWarning("QODBCDriver::commitTransaction: Database not open");
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
d->hDbc,
SQL_COMMIT);
if (r != SQL_SUCCESS) {
setLastError(qMakeError(tr("Unable to commit transaction"),
QSqlError::TransactionError, d));
return false;
}
return endTrans();
}
bool QODBCDriver::rollbackTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
qWarning("QODBCDriver::rollbackTransaction: Database not open");
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
d->hDbc,
SQL_ROLLBACK);
if (r != SQL_SUCCESS) {
setLastError(qMakeError(tr("Unable to rollback transaction"),
QSqlError::TransactionError, d));
return false;
}
return endTrans();
}
bool QODBCDriver::endTrans()
{
Q_D(QODBCDriver);
SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
SQLRETURN r = SQLSetConnectAttr(d->hDbc,
SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)size_t(ac),
sizeof(ac));
if (r != SQL_SUCCESS) {
setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d));
return false;
}
return true;
}
QStringList QODBCDriver::tables(QSql::TableType type) const
{
Q_D(const QODBCDriver);
QStringList tl;
if (!isOpen())
return tl;
SQLHANDLE hStmt;
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
d->hDbc,
&hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d);
return tl;
}
r = SQLSetStmtAttr(hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
QStringList tableType;
if (type & QSql::Tables)
tableType += QLatin1String("TABLE");
if (type & QSql::Views)
tableType += QLatin1String("VIEW");
if (type & QSql::SystemTables)
tableType += QLatin1String("SYSTEM TABLE");
if (tableType.isEmpty())
return tl;
QString joinedTableTypeString = tableType.join(QLatin1Char(','));
r = SQLTables(hStmt,
NULL,
0,
NULL,
0,
NULL,
0,
toSQLTCHAR(joinedTableTypeString).data(),
joinedTableTypeString.length() /* characters, not bytes */);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d);
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")";
return QStringList();
}
while (r == SQL_SUCCESS) {
QString fieldVal = qGetStringData(hStmt, 2, -1, false);
tl.append(fieldVal);
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
}
r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
if (r!= SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d);
return tl;
}
QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
{
Q_D(const QODBCDriver);
QSqlIndex index(tablename);
if (!isOpen())
return index;
bool usingSpecialColumns = false;
QSqlRecord rec = record(tablename);
SQLHANDLE hStmt;
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
d->hDbc,
&hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d);
return index;
}
QString catalog, schema, table;
const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table);
if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
catalog = stripDelimiters(catalog, QSqlDriver::TableName);
else
catalog = d->adjustCase(catalog);
if (isIdentifierEscaped(schema, QSqlDriver::TableName))
schema = stripDelimiters(schema, QSqlDriver::TableName);
else
schema = d->adjustCase(schema);
if (isIdentifierEscaped(table, QSqlDriver::TableName))
table = stripDelimiters(table, QSqlDriver::TableName);
else
table = d->adjustCase(table);
r = SQLSetStmtAttr(hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
r = SQLPrimaryKeys(hStmt,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length() /* in characters, not in bytes */);
// if the SQLPrimaryKeys() call does not succeed (e.g the driver
// does not support it) - try an alternative method to get hold of
// the primary index (e.g MS Access and FoxPro)
if (r != SQL_SUCCESS) {
r = SQLSpecialColumns(hStmt,
SQL_BEST_ROWID,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length(),
SQL_SCOPE_CURROW,
SQL_NULLABLE);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d);
} else {
usingSpecialColumns = true;
}
}
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
int fakeId = 0;
QString cName, idxName;
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
if (usingSpecialColumns) {
cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name
idxName = QString::number(fakeId++); // invent a fake index name
} else {
cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name
idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name
}
index.append(rec.field(cName));
index.setName(idxName);
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
}
r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
if (r!= SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d);
return index;
}
QSqlRecord QODBCDriver::record(const QString& tablename) const
{
Q_D(const QODBCDriver);
QSqlRecord fil;
if (!isOpen())
return fil;
SQLHANDLE hStmt;
QString catalog, schema, table;
const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table);
if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
catalog = stripDelimiters(catalog, QSqlDriver::TableName);
else
catalog = d->adjustCase(catalog);
if (isIdentifierEscaped(schema, QSqlDriver::TableName))
schema = stripDelimiters(schema, QSqlDriver::TableName);
else
schema = d->adjustCase(schema);
if (isIdentifierEscaped(table, QSqlDriver::TableName))
table = stripDelimiters(table, QSqlDriver::TableName);
else
table = d->adjustCase(table);
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
d->hDbc,
&hStmt);
if (r != SQL_SUCCESS) {
qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d);
return fil;
}
r = SQLSetStmtAttr(hStmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
SQL_IS_UINTEGER);
r = SQLColumns(hStmt,
catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
catalog.length(),
schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
schema.length(),
toSQLTCHAR(table).data(),
table.length(),
NULL,
0);
if (r != SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d);
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
fil.append(qMakeFieldInfo(hStmt, d));
if (d->hasSQLFetchScroll)
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);
else
r = SQLFetch(hStmt);
}
r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
if (r!= SQL_SUCCESS)
qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d);
return fil;
}
QString QODBCDriver::formatValue(const QSqlField &field,
bool trimStrings) const
{
QString r;
if (field.isNull()) {
r = QLatin1String("NULL");
} else if (field.type() == QVariant::DateTime) {
// Use an escape sequence for the datetime fields
if (field.value().toDateTime().isValid()){
QDate dt = field.value().toDateTime().date();
QTime tm = field.value().toDateTime().time();
// Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
r = QLatin1String("{ ts '") +
QString::number(dt.year()) + QLatin1Char('-') +
QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) +
QLatin1Char('-') +
QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) +
QLatin1Char(' ') +
tm.toString() +
QLatin1String("' }");
} else
r = QLatin1String("NULL");
} else if (field.type() == QVariant::ByteArray) {
QByteArray ba = field.value().toByteArray();
QString res;
static const char hexchars[] = "0123456789abcdef";
for (int i = 0; i < ba.size(); ++i) {
uchar s = (uchar) ba[i];
res += QLatin1Char(hexchars[s >> 4]);
res += QLatin1Char(hexchars[s & 0x0f]);
}
r = QLatin1String("0x") + res;
} else {
r = QSqlDriver::formatValue(field, trimStrings);
}
return r;
}
QVariant QODBCDriver::handle() const
{
Q_D(const QODBCDriver);
return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
}
QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
{
Q_D(const QODBCDriver);
QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
QString res = identifier;
if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
res.replace(quote, QString(quote)+QString(quote));
res.prepend(quote).append(quote);
res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote));
}
return res;
}
bool QODBCDriver::isIdentifierEscaped(const QString &identifier, IdentifierType) const
{
Q_D(const QODBCDriver);
QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
return identifier.size() > 2
&& identifier.startsWith(quote) //left delimited
&& identifier.endsWith(quote); //right delimited
}
QT_END_NAMESPACE