/**************************************************************************** ** ** 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 #if defined (Q_OS_WIN32) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& input, int size=-1) { QString result; 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 toSQLTCHAR(const QString &input) { QVarLengthArray 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 fieldCache; int fieldCacheIdx; int disconnectCount; bool hasSQLFetchScroll; bool isStmtHandleValid(); void updateStmtHandleState(); }; bool QODBCResultPrivate::isStmtHandleValid() { return disconnectCount == drv_d_func()->disconnectCount; } void QODBCResultPrivate::updateStmtHandleState() { disconnectCount = drv_d_func()->disconnectCount; } 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 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, qODBCWarn(p), 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 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 // colSize-1: remove 0 termination when there is more data to fetch 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 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 // colSize-1: remove 0 termination when there is more data to fetch int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator; fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize); 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 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(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 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 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()->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& values = boundValues(); QVector tmpStorage(values.count(), QByteArray()); // holds temporary buffers QVarLengthArray 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(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(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(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(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(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(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(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(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(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(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 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(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(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(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(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(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(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 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"), &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 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 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 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 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(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(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"), &d->hDbc); } QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const { Q_D(const QODBCDriver); QChar quote = const_cast(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(d)->quoteChar(); return identifier.size() > 2 && identifier.startsWith(quote) //left delimited && identifier.endsWith(quote); //right delimited } QT_END_NAMESPACE