/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtSql module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsql_db2.h" #include #include #include #include #include #include #include #include #include #include #if defined(Q_CC_BOR) // DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE // and SQL_BIGUINT_TYPE to wrong the types for Borland; so do the defines to // the right type before including the header #define SQL_BIGINT_TYPE qint64 #define SQL_BIGUINT_TYPE quint64 #endif #define UNICODE #include #include QT_BEGIN_NAMESPACE static const int COLNAMESIZE = 255; static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT }; class QDB2DriverPrivate { public: QDB2DriverPrivate(): hEnv(0), hDbc(0) {} SQLHANDLE hEnv; SQLHANDLE hDbc; QString user; }; class QDB2ResultPrivate { public: QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0) {} ~QDB2ResultPrivate() { emptyValueCache(); } void clearValueCache() { for (int i = 0; i < valueCache.count(); ++i) { delete valueCache[i]; valueCache[i] = NULL; } } void emptyValueCache() { clearValueCache(); valueCache.clear(); } const QDB2DriverPrivate* dp; SQLHANDLE hStmt; QSqlRecord recInf; QVector valueCache; }; static QString qFromTChar(SQLTCHAR* str) { return QString::fromUtf16(str); } // dangerous!! (but fast). Don't use in functions that // require out parameters! static SQLTCHAR* qToTChar(const QString& str) { return (SQLTCHAR*)str.utf16(); } static QString qWarnDB2Handle(int handleType, SQLHANDLE handle) { SQLINTEGER nativeCode; SQLSMALLINT msgLen; SQLRETURN r = SQL_ERROR; SQLTCHAR state[SQL_SQLSTATE_SIZE + 1]; SQLTCHAR description[SQL_MAX_MESSAGE_LENGTH]; r = SQLGetDiagRec(handleType, handle, 1, (SQLTCHAR*) state, &nativeCode, (SQLTCHAR*) description, SQL_MAX_MESSAGE_LENGTH - 1, /* in bytes, not in characters */ &msgLen); if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) return QString(qFromTChar(description)); return QString(); } static QString qDB2Warn(const QDB2DriverPrivate* d) { return (qWarnDB2Handle(SQL_HANDLE_ENV, d->hEnv) + QLatin1Char(' ') + qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc)); } static QString qDB2Warn(const QDB2ResultPrivate* d) { return (qWarnDB2Handle(SQL_HANDLE_ENV, d->dp->hEnv) + QLatin1Char(' ') + qWarnDB2Handle(SQL_HANDLE_DBC, d->dp->hDbc) + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt)); } static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d) { qWarning("%s\tError: %s", message.toLocal8Bit().constData(), qDB2Warn(d).toLocal8Bit().constData()); } static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d) { qWarning("%s\tError: %s", message.toLocal8Bit().constData(), qDB2Warn(d).toLocal8Bit().constData()); } static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QDB2DriverPrivate* p) { return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type); } static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QDB2ResultPrivate* p) { return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type); } static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype) { QVariant::Type type = QVariant::Invalid; switch (sqltype) { case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: case SQL_DECIMAL: case SQL_NUMERIC: type = QVariant::Double; break; case SQL_SMALLINT: case SQL_INTEGER: case SQL_BIT: case SQL_TINYINT: type = QVariant::Int; break; case SQL_BIGINT: type = QVariant::LongLong; break; case SQL_BLOB: case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: case SQL_CLOB: case SQL_DBCLOB: 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: case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: type = QVariant::String; break; default: type = QVariant::ByteArray; break; } return type; } static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i) { SQLSMALLINT colNameLen; SQLSMALLINT colType; SQLUINTEGER colSize; SQLSMALLINT colScale; SQLSMALLINT nullable; SQLRETURN r = SQL_ERROR; SQLTCHAR colName[COLNAMESIZE]; r = SQLDescribeCol(d->hStmt, i+1, colName, (SQLSMALLINT) COLNAMESIZE, &colNameLen, &colType, &colSize, &colScale, &nullable); if (r != SQL_SUCCESS) { qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d); return QSqlField(); } QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType)); // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN if (nullable == SQL_NO_NULLS) f.setRequired(true); else if (nullable == SQL_NULLABLE) f.setRequired(false); // else required is unknown f.setLength(colSize == 0 ? -1 : int(colSize)); f.setPrecision(colScale == 0 ? -1 : int(colScale)); f.setSqlType(int(colType)); return f; } static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull) { SQLINTEGER intbuf; isNull = false; SQLINTEGER lengthIndicator = 0; SQLRETURN r = SQLGetData(hStmt, column + 1, SQL_C_SLONG, (SQLPOINTER) &intbuf, 0, &lengthIndicator); if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) { isNull = true; return 0; } return int(intbuf); } static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull) { SQLDOUBLE dblbuf; isNull = false; SQLINTEGER lengthIndicator = 0; SQLRETURN r = SQLGetData(hStmt, column+1, SQL_C_DOUBLE, (SQLPOINTER) &dblbuf, 0, &lengthIndicator); if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) { isNull = true; return 0.0; } return (double) dblbuf; } static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull) { SQLBIGINT lngbuf = Q_INT64_C(0); isNull = false; SQLINTEGER lengthIndicator = 0; SQLRETURN r = SQLGetData(hStmt, column+1, SQL_C_SBIGINT, (SQLPOINTER) &lngbuf, 0, &lengthIndicator); if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) isNull = true; return lngbuf; } static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull) { QString fieldVal; SQLRETURN r = SQL_ERROR; SQLINTEGER lengthIndicator = 0; if (colSize <= 0) colSize = 255; 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 SQLTCHAR* buf = new SQLTCHAR[colSize]; while (true) { r = SQLGetData(hStmt, column + 1, SQL_C_WCHAR, (SQLPOINTER)buf, colSize * sizeof(SQLTCHAR), &lengthIndicator); if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { fieldVal.clear(); isNull = true; break; } fieldVal += qFromTChar(buf); } else if (r == SQL_NO_DATA) { break; } else { qWarning("qGetStringData: Error while fetching data (%d)", r); fieldVal.clear(); break; } } delete[] buf; return fieldVal; } static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLINTEGER& lengthIndicator, bool& isNull) { QByteArray fieldVal; SQLSMALLINT colNameLen; SQLSMALLINT colType; SQLUINTEGER colSize; SQLSMALLINT colScale; SQLSMALLINT nullable; SQLRETURN r = SQL_ERROR; SQLTCHAR colName[COLNAMESIZE]; r = SQLDescribeCol(hStmt, column+1, colName, COLNAMESIZE, &colNameLen, &colType, &colSize, &colScale, &nullable); if (r != SQL_SUCCESS) qWarning("qGetBinaryData: Unable to describe column %d", 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; char * buf = new char[colSize]; while (true) { r = SQLGetData(hStmt, column+1, colType == SQL_DBCLOB ? SQL_C_CHAR : SQL_C_BINARY, (SQLPOINTER) buf, colSize, &lengthIndicator); if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { if (lengthIndicator == SQL_NULL_DATA) { isNull = true; break; } else { int rSize; r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize; if (lengthIndicator == SQL_NO_TOTAL) // size cannot be determined rSize = colSize; fieldVal.append(QByteArray(buf, rSize)); if (r == SQL_SUCCESS) // the whole field was read in one chunk break; } } else { break; } } delete [] buf; return fieldVal; } static void qSplitTableQualifier(const QString & qualifier, QString * catalog, QString * schema, QString * table) { if (!catalog || !schema || !table) 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++; } } } // 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) { bool isNull; int type = qGetIntData(hStmt, 4, isNull); QSqlField f(qGetStringData(hStmt, 3, -1, isNull), qDecodeDB2Type(type)); int required = qGetIntData(hStmt, 10, isNull); // 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. f.setLength(qGetIntData(hStmt, 6, isNull)); // column size f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision f.setSqlType(type); return f; } static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwardOnly = true) { SQLRETURN r; if (!d->hStmt) { r = SQLAllocHandle(SQL_HANDLE_STMT, d->dp->hDbc, &d->hStmt); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d); return false; } } else { r = SQLFreeStmt(d->hStmt, SQL_CLOSE); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d); return false; } } if (!setForwardOnly) return true; if (forwardOnly) { 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) { qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg( forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY") : QLatin1String("SQL_CURSOR_STATIC")), d); return false; } return true; } QVariant QDB2Result::handle() const { return QVariant(qRegisterMetaType("SQLHANDLE"), &d->hStmt); } /************************************/ QDB2Result::QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp) : QSqlResult(dr) { d = new QDB2ResultPrivate(dp); } QDB2Result::~QDB2Result() { if (d->hStmt) { SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + QString::number(r), d); } delete d; } bool QDB2Result::reset (const QString& query) { setActive(false); setAt(QSql::BeforeFirstRow); SQLRETURN r; d->recInf.clear(); d->emptyValueCache(); if (!qMakeStatement(d, isForwardOnly())) return false; r = SQLExecDirect(d->hStmt, qToTChar(query), (SQLINTEGER) query.length()); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to execute statement"), QSqlError::StatementError, d)); return false; } SQLSMALLINT count; r = SQLNumResultCols(d->hStmt, &count); if (count) { setSelect(true); for (int i = 0; i < count; ++i) { d->recInf.append(qMakeFieldInfo(d, i)); } } else { setSelect(false); } d->valueCache.resize(count); d->valueCache.fill(NULL); setActive(true); return true; } bool QDB2Result::prepare(const QString& query) { setActive(false); setAt(QSql::BeforeFirstRow); SQLRETURN r; d->recInf.clear(); d->emptyValueCache(); if (!qMakeStatement(d, isForwardOnly())) return false; r = SQLPrepare(d->hStmt, qToTChar(query), (SQLINTEGER) query.length()); if (r != SQL_SUCCESS) { setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to prepare statement"), QSqlError::StatementError, d)); return false; } return true; } bool QDB2Result::exec() { QList tmpStorage; // holds temporary ptrs QVarLengthArray indicators(boundValues().count()); memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER)); setActive(false); setAt(QSql::BeforeFirstRow); SQLRETURN r; d->recInf.clear(); d->emptyValueCache(); if (!qMakeStatement(d, isForwardOnly(), false)) return false; QVector &values = boundValues(); int i; for (i = 0; i < values.count(); ++i) { // bind parameters - only positional binding allowed SQLINTEGER *ind = &indicators[i]; if (values.at(i).isNull()) *ind = SQL_NULL_DATA; if (bindValueType(i) & QSql::Out) values[i].detach(); switch (values.at(i).type()) { case QVariant::Date: { QByteArray ba; ba.resize(sizeof(DATE_STRUCT)); DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData(); QDate qdt = values.at(i).toDate(); dt->year = qdt.year(); dt->month = qdt.month(); dt->day = qdt.day(); r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_DATE, SQL_DATE, 0, 0, (void *) dt, 0, *ind == SQL_NULL_DATA ? ind : NULL); tmpStorage.append(ba); break; } case QVariant::Time: { QByteArray ba; ba.resize(sizeof(TIME_STRUCT)); TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData(); QTime qdt = values.at(i).toTime(); dt->hour = qdt.hour(); dt->minute = qdt.minute(); dt->second = qdt.second(); r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_TIME, SQL_TIME, 0, 0, (void *) dt, 0, *ind == SQL_NULL_DATA ? ind : NULL); tmpStorage.append(ba); break; } case QVariant::DateTime: { QByteArray ba; ba.resize(sizeof(TIMESTAMP_STRUCT)); TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData(); QDateTime qdt = values.at(i).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(); dt->fraction = qdt.time().msec() * 1000000; r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_TIMESTAMP, SQL_TIMESTAMP, 0, 0, (void *) dt, 0, *ind == SQL_NULL_DATA ? ind : NULL); tmpStorage.append(ba); break; } case QVariant::Int: r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_SLONG, SQL_INTEGER, 0, 0, (void *)values.at(i).constData(), 0, *ind == SQL_NULL_DATA ? ind : NULL); break; case QVariant::Double: r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, (void *)values.at(i).constData(), 0, *ind == SQL_NULL_DATA ? ind : NULL); break; case QVariant::ByteArray: { int len = values.at(i).toByteArray().size(); if (*ind != SQL_NULL_DATA) *ind = len; r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_BINARY, SQL_LONGVARBINARY, len, 0, (void *)values.at(i).toByteArray().constData(), len, ind); break; } case QVariant::String: { QString str(values.at(i).toString()); if (*ind != SQL_NULL_DATA) *ind = str.length() * sizeof(QChar); if (bindValueType(i) & QSql::Out) { QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar)); r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_WCHAR, SQL_WVARCHAR, str.length(), 0, (void *)ba.constData(), ba.size(), ind); tmpStorage.append(ba); } else { void *data = (void*)str.utf16(); int len = str.length(); r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_WCHAR, SQL_WVARCHAR, len, 0, data, len * sizeof(QChar), ind); } break; } default: { QByteArray ba = values.at(i).toString().toAscii(); int len = ba.length() + 1; if (*ind != SQL_NULL_DATA) *ind = ba.length(); r = SQLBindParameter(d->hStmt, i + 1, qParamType[(QFlag)(bindValueType(i)) & 3], SQL_C_CHAR, SQL_VARCHAR, len, 0, (void *) ba.constData(), len, ind); tmpStorage.append(ba); break; } } if (r != SQL_SUCCESS) { qWarning("QDB2Result::exec: unable to bind variable: %s", qDB2Warn(d).toLocal8Bit().constData()); setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to bind variable"), QSqlError::StatementError, d)); return false; } } r = SQLExecute(d->hStmt); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { qWarning("QDB2Result::exec: Unable to execute statement: %s", qDB2Warn(d).toLocal8Bit().constData()); setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to execute statement"), QSqlError::StatementError, d)); return false; } SQLSMALLINT count; r = SQLNumResultCols(d->hStmt, &count); if (count) { setSelect(true); for (int i = 0; i < count; ++i) { d->recInf.append(qMakeFieldInfo(d, i)); } } else { setSelect(false); } setActive(true); d->valueCache.resize(count); d->valueCache.fill(NULL); //get out parameters if (!hasOutValues()) return true; for (i = 0; i < values.count(); ++i) { switch (values[i].type()) { case QVariant::Date: { DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData()); values[i] = QVariant(QDate(ds.year, ds.month, ds.day)); break; } case QVariant::Time: { TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData()); values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second)); break; } case QVariant::DateTime: { TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().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::Int: case QVariant::Double: case QVariant::ByteArray: break; case QVariant::String: if (bindValueType(i) & QSql::Out) values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData()); break; default: { values[i] = QString::fromAscii(tmpStorage.takeFirst().constData()); break; } } if (indicators[i] == SQL_NULL_DATA) values[i] = QVariant(values[i].type()); } return true; } bool QDB2Result::fetch(int i) { if (isForwardOnly() && i < at()) return false; if (i == at()) return true; d->clearValueCache(); 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 && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) { setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch record %1").arg(i), QSqlError::StatementError, d)); return false; } else if (r == SQL_NO_DATA) return false; setAt(i); return true; } bool QDB2Result::fetchNext() { SQLRETURN r; d->clearValueCache(); r = SQLFetchScroll(d->hStmt, SQL_FETCH_NEXT, 0); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { if (r != SQL_NO_DATA) setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch next"), QSqlError::StatementError, d)); return false; } setAt(at() + 1); return true; } bool QDB2Result::fetchFirst() { if (isForwardOnly() && at() != QSql::BeforeFirstRow) return false; if (isForwardOnly()) return fetchNext(); d->clearValueCache(); SQLRETURN r; r = SQLFetchScroll(d->hStmt, SQL_FETCH_FIRST, 0); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"), QSqlError::StatementError, d)); return false; } setAt(0); return true; } bool QDB2Result::fetchLast() { d->clearValueCache(); int i = at(); if (i == QSql::AfterLastRow) { if (isForwardOnly()) { return false; } else { if (!fetch(0)) return false; i = at(); } } while (fetchNext()) ++i; if (i == QSql::BeforeFirstRow) { setAt(QSql::AfterLastRow); return false; } if (!isForwardOnly()) return fetch(i); setAt(i); return true; } QVariant QDB2Result::data(int field) { if (field >= d->recInf.count()) { qWarning("QDB2Result::data: column %d out of range", field); return QVariant(); } SQLRETURN r = 0; SQLINTEGER lengthIndicator = 0; bool isNull = false; const QSqlField info = d->recInf.field(field); if (!info.isValid() || field >= d->valueCache.size()) return QVariant(); if (d->valueCache[field]) return *d->valueCache[field]; QVariant* v = 0; switch (info.type()) { case QVariant::LongLong: v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull)); break; case QVariant::Int: v = new QVariant(qGetIntData(d->hStmt, field, isNull)); break; case QVariant::Date: { DATE_STRUCT dbuf; r = SQLGetData(d->hStmt, field + 1, SQL_C_DATE, (SQLPOINTER) &dbuf, 0, &lengthIndicator); if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day)); } else { v = new QVariant(QDate()); isNull = true; } break; } case QVariant::Time: { TIME_STRUCT tbuf; r = SQLGetData(d->hStmt, field + 1, SQL_C_TIME, (SQLPOINTER) &tbuf, 0, &lengthIndicator); if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second)); } else { v = new QVariant(QTime()); isNull = true; } break; } case QVariant::DateTime: { TIMESTAMP_STRUCT dtbuf; r = SQLGetData(d->hStmt, field + 1, SQL_C_TIMESTAMP, (SQLPOINTER) &dtbuf, 0, &lengthIndicator); if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) { v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day), QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000))); } else { v = new QVariant(QDateTime()); isNull = true; } break; } case QVariant::ByteArray: v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull)); break; case QVariant::Double: { QString value=qGetStringData(d->hStmt, field, info.length() + 1, isNull); switch(numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: v = new QVariant(qGetIntData(d->hStmt, field, isNull)); break; case QSql::LowPrecisionInt64: v = new QVariant(qGetBigIntData(d->hStmt, field, isNull)); break; case QSql::LowPrecisionDouble: v = new QVariant(qGetDoubleData(d->hStmt, field, isNull)); break; case QSql::HighPrecision: default: // length + 1 for the comma v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull)); break; } break; } case QVariant::String: default: v = new QVariant(qGetStringData(d->hStmt, field, info.length(), isNull)); break; } if (isNull) *v = QVariant(info.type()); d->valueCache[field] = v; return *v; } bool QDB2Result::isNull(int i) { if (i >= d->valueCache.size()) return true; if (d->valueCache[i]) return d->valueCache[i]->isNull(); return data(i).isNull(); } int QDB2Result::numRowsAffected() { SQLINTEGER affectedRowCount = 0; SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount); if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) return affectedRowCount; else qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d); return -1; } int QDB2Result::size() { return -1; } QSqlRecord QDB2Result::record() const { if (isActive()) return d->recInf; return QSqlRecord(); } bool QDB2Result::nextResult() { setActive(false); setAt(QSql::BeforeFirstRow); d->recInf.clear(); d->emptyValueCache(); setSelect(false); SQLRETURN r = SQLMoreResults(d->hStmt); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { if (r != SQL_NO_DATA) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to fetch last"), QSqlError::ConnectionError, d)); } return false; } SQLSMALLINT fieldCount; r = SQLNumResultCols(d->hStmt, &fieldCount); setSelect(fieldCount > 0); for (int i = 0; i < fieldCount; ++i) d->recInf.append(qMakeFieldInfo(d, i)); d->valueCache.resize(fieldCount); d->valueCache.fill(NULL); setActive(true); return true; } void QDB2Result::virtual_hook(int id, void *data) { switch (id) { case QSqlResult::NextResult: Q_ASSERT(data); *static_cast(data) = nextResult(); break; case QSqlResult::DetachFromResultSet: if (d->hStmt) SQLCloseCursor(d->hStmt); break; default: QSqlResult::virtual_hook(id, data); } } /************************************/ QDB2Driver::QDB2Driver(QObject* parent) : QSqlDriver(parent) { d = new QDB2DriverPrivate; } QDB2Driver::QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent) : QSqlDriver(parent) { d = new QDB2DriverPrivate; d->hEnv = (SQLHANDLE)env; d->hDbc = (SQLHANDLE)con; if (env && con) { setOpen(true); setOpenError(false); } } QDB2Driver::~QDB2Driver() { close(); delete d; } bool QDB2Driver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port, const QString& connOpts) { 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("QDB2Driver::open: Unable to allocate environment"), d); setOpenError(true); return false; } r = SQLAllocHandle(SQL_HANDLE_DBC, d->hEnv, &d->hDbc); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate connection"), d); setOpenError(true); return false; } QString protocol; // Set connection attributes const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); for (int i = 0; i < opts.count(); ++i) { const QString tmp(opts.at(i)); int idx; if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { qWarning("QDB2Driver::open: Illegal connect option value '%s'", tmp.toLocal8Bit().constData()); continue; } const QString opt(tmp.left(idx)); const QString val(tmp.mid(idx + 1).simplified()); SQLUINTEGER v = 0; r = SQL_SUCCESS; if (opt == QLatin1String("SQL_ATTR_ACCESS_MODE")) { if (val == QLatin1String("SQL_MODE_READ_ONLY")) { v = SQL_MODE_READ_ONLY; } else if (val == QLatin1String("SQL_MODE_READ_WRITE")) { v = SQL_MODE_READ_WRITE; } else { qWarning("QDB2Driver::open: Unknown option value '%s'", tmp.toLocal8Bit().constData()); continue; } r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0); } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { v = val.toUInt(); r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0); } else if (opt.compare(QLatin1String("PROTOCOL"), Qt::CaseInsensitive) == 0) { protocol = tmp; } else { qWarning("QDB2Driver::open: Unknown connection attribute '%s'", tmp.toLocal8Bit().constData()); } if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) qSqlWarning(QString::fromLatin1("QDB2Driver::open: " "Unable to set connection attribute '%1'").arg(opt), d); } if (protocol.isEmpty()) protocol = QLatin1String("PROTOCOL=TCPIP"); if (port < 0 ) port = 50000; QString connQStr; connQStr = protocol + QLatin1String(";DATABASE=") + db + QLatin1String(";HOSTNAME=") + host + QLatin1String(";PORT=") + QString::number(port) + QLatin1String(";UID=") + user + QLatin1String(";PWD=") + password; SQLTCHAR connOut[SQL_MAX_OPTION_STRING_LENGTH]; SQLSMALLINT cb; r = SQLDriverConnect(d->hDbc, NULL, qToTChar(connQStr), (SQLSMALLINT) connQStr.length(), connOut, SQL_MAX_OPTION_STRING_LENGTH, &cb, SQL_DRIVER_NOPROMPT); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); setOpenError(true); return false; } d->user = user; setOpen(true); setOpenError(false); return true; } void QDB2Driver::close() { 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("QDB2Driver::close: Unable to disconnect datasource"), d); } r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver::close: 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("QDB2Driver::close: Unable to free environment handle"), d); d->hEnv = 0; } setOpen(false); setOpenError(false); } QSqlResult *QDB2Driver::createResult() const { return new QDB2Result(this, d); } QSqlRecord QDB2Driver::record(const QString& tableName) const { QSqlRecord fil; if (!isOpen()) return fil; SQLHANDLE hStmt; QString catalog, schema, table; qSplitTableQualifier(tableName, &catalog, &schema, &table); if (schema.isEmpty()) schema = d->user; if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) catalog = stripDelimiters(catalog, QSqlDriver::TableName); else catalog = catalog.toUpper(); if (isIdentifierEscaped(schema, QSqlDriver::TableName)) schema = stripDelimiters(schema, QSqlDriver::TableName); else schema = schema.toUpper(); if (isIdentifierEscaped(table, QSqlDriver::TableName)) table = stripDelimiters(table, QSqlDriver::TableName); else table = table.toUpper(); SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, d->hDbc, &hStmt); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QDB2Driver::record: Unable to allocate handle"), d); return fil; } r = SQLSetStmtAttr(hStmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY, SQL_IS_UINTEGER); //Aside: szSchemaName and szTableName parameters of SQLColumns //are case sensitive search patterns, so no escaping is used. r = SQLColumns(hStmt, NULL, 0, qToTChar(schema), schema.length(), qToTChar(table), table.length(), NULL, 0); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver::record: Unable to execute column list"), d); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); while (r == SQL_SUCCESS) { fil.append(qMakeFieldInfo(hStmt)); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); } r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + QString::number(r), d); return fil; } QStringList QDB2Driver::tables(QSql::TableType type) const { QStringList tl; if (!isOpen()) return tl; SQLHANDLE hStmt; SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, d->hDbc, &hStmt); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to allocate handle"), d); return tl; } r = SQLSetStmtAttr(hStmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, SQL_IS_UINTEGER); QString 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; tableType.chop(1); r = SQLTables(hStmt, NULL, 0, NULL, 0, NULL, 0, qToTChar(tableType), tableType.length()); if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to execute table list"), d); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); while (r == SQL_SUCCESS) { bool isNull; QString fieldVal = qGetStringData(hStmt, 2, -1, isNull); QString userVal = qGetStringData(hStmt, 1, -1, isNull); QString user = d->user; if ( isIdentifierEscaped(user, QSqlDriver::TableName)) user = stripDelimiters(user, QSqlDriver::TableName); else user = user.toUpper(); if (userVal != user) fieldVal = userVal + QLatin1Char('.') + fieldVal; tl.append(fieldVal); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); } r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to free statement handle ") + QString::number(r), d); return tl; } QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const { QSqlIndex index(tablename); if (!isOpen()) return index; QSqlRecord rec = record(tablename); SQLHANDLE hStmt; SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, d->hDbc, &hStmt); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QDB2Driver::primaryIndex: Unable to list primary key"), d); return index; } QString catalog, schema, table; qSplitTableQualifier(tablename, &catalog, &schema, &table); if (isIdentifierEscaped(catalog, QSqlDriver::TableName)) catalog = stripDelimiters(catalog, QSqlDriver::TableName); else catalog = catalog.toUpper(); if (isIdentifierEscaped(schema, QSqlDriver::TableName)) schema = stripDelimiters(schema, QSqlDriver::TableName); else schema = schema.toUpper(); if (isIdentifierEscaped(table, QSqlDriver::TableName)) table = stripDelimiters(table, QSqlDriver::TableName); else table = table.toUpper(); r = SQLSetStmtAttr(hStmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, SQL_IS_UINTEGER); r = SQLPrimaryKeys(hStmt, NULL, 0, qToTChar(schema), schema.length(), qToTChar(table), table.length()); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); bool isNull; QString cName, idxName; // Store all fields in a StringList because the driver can't detail fields in this FETCH loop while (r == SQL_SUCCESS) { cName = qGetStringData(hStmt, 3, -1, isNull); // column name idxName = qGetStringData(hStmt, 5, -1, isNull); // pk index name index.append(rec.field(cName)); index.setName(idxName); r = SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0); } r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt); if (r!= SQL_SUCCESS) qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ") + QString::number(r), d); return index; } bool QDB2Driver::hasFeature(DriverFeature f) const { switch (f) { case QuerySize: case NamedPlaceholders: case BatchOperations: case LastInsertId: case SimpleLocking: case EventNotifications: return false; case BLOB: case Transactions: case MultipleResultSets: case PreparedQueries: case PositionalPlaceholders: case LowPrecisionNumbers: case FinishQuery: return true; case Unicode: return true; } return false; } bool QDB2Driver::beginTransaction() { if (!isOpen()) { qWarning("QDB2Driver::beginTransaction: Database not open"); return false; } return setAutoCommit(false); } bool QDB2Driver::commitTransaction() { if (!isOpen()) { qWarning("QDB2Driver::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 setAutoCommit(true); } bool QDB2Driver::rollbackTransaction() { if (!isOpen()) { qWarning("QDB2Driver::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 setAutoCommit(true); } bool QDB2Driver::setAutoCommit(bool autoCommit) { SQLUINTEGER ac = autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF; SQLRETURN r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)ac, sizeof(ac)); if (r != SQL_SUCCESS) { setLastError(qMakeError(tr("Unable to set autocommit"), QSqlError::TransactionError, d)); return false; } return true; } QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const { if (field.isNull()) return QLatin1String("NULL"); switch (field.type()) { case 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 return QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-') + QString::number(dt.month()) + QLatin1Char('-') + QString::number(dt.day()) + QLatin1Char('-') + QString::number(tm.hour()) + QLatin1Char('.') + QString::number(tm.minute()).rightJustified(2, QLatin1Char('0'), true) + QLatin1Char('.') + QString::number(tm.second()).rightJustified(2, QLatin1Char('0'), true) + QLatin1Char('.') + QString::number(tm.msec() * 1000).rightJustified(6, QLatin1Char('0'), true) + QLatin1Char('\''); } else { return QLatin1String("NULL"); } } case QVariant::ByteArray: { QByteArray ba = field.value().toByteArray(); QString res = QString::fromLatin1("BLOB(X'"); 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]); } res += QLatin1String("')"); return res; } default: return QSqlDriver::formatValue(field, trimStrings); } } QVariant QDB2Driver::handle() const { return QVariant(qRegisterMetaType("SQLHANDLE"), &d->hDbc); } QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const { QString res = identifier; if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { res.replace(QLatin1Char('"'), QLatin1String("\"\"")); res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); res.replace(QLatin1Char('.'), QLatin1String("\".\"")); } return res; } QT_END_NAMESPACE