diff options
author | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
---|---|---|
committer | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
commit | 8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76 (patch) | |
tree | a17e1a767a89542ab59907462206d7dcf2e504b2 /src/sql | |
download | Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.zip Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.gz Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.bz2 |
Long live Qt for S60!
Diffstat (limited to 'src/sql')
55 files changed, 25829 insertions, 0 deletions
diff --git a/src/sql/README.module b/src/sql/README.module new file mode 100644 index 0000000..511d90e --- /dev/null +++ b/src/sql/README.module @@ -0,0 +1,37 @@ +Before building the Qt library, the Qt SQL module can be enabled for +specific databases using 'configure'. 'configure' is located at the +top of your QTDIR. + +Specific databases drivers can be enabled using one of the following +options: + + ./configure [-qt-sql-<driver>] [-plugin-sql-<driver>] + +or disabled using the following option: + + ./configure [-no-sql-<driver>] + +Where <driver> is the name of the driver, for example 'psql'. This +will configure the Qt library to compile the specified driver into +the Qt lib itself. + +For example, to build the PostgreSQL driver directly into the Qt +library, configure Qt like this: + + ./configure -qt-sql-psql + +In addition, you may need to specify an extra include path, as some +database drivers require headers for the database they are using, +for example: + + ./configure -qt-sql-psql -I/usr/local/include + +If instead you need to build the PostgreSQL driver as a dynamically +loaded plugin, configure Qt like this: + + ./configure -plugin-sql-psql + +To compile drivers as dynamically loaded plugins, see the +QTDIR/plugins/src/sqldrivers directory. Use 'configure -help' +for a complete list of configure options. See the Qt documentation +for a complete list of supported database drivers. diff --git a/src/sql/drivers/db2/qsql_db2.cpp b/src/sql/drivers/db2/qsql_db2.cpp new file mode 100644 index 0000000..69383f7 --- /dev/null +++ b/src/sql/drivers/db2/qsql_db2.cpp @@ -0,0 +1,1608 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_db2.h" +#include <qcoreapplication.h> +#include <qdatetime.h> +#include <qsqlfield.h> +#include <qsqlerror.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> + +#ifndef UNICODE +#define UNICODE +#endif + +#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 + +#include <sqlcli1.h> + +#include <string.h> + +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), precisionPolicy(QSql::HighPrecision) + {} + ~QDB2ResultPrivate() + { + for (int i = 0; i < valueCache.count(); ++i) + delete valueCache[i]; + } + + const QDB2DriverPrivate* dp; + SQLHANDLE hStmt; + QSqlRecord recInf; + QVector<QVariant*> valueCache; + QSql::NumericalPrecisionPolicy precisionPolicy; +}; + +static QString qFromTChar(SQLTCHAR* str) +{ +#ifdef UNICODE + return QString::fromUtf16(str); +#else + return QString::fromLocal8Bit((const char*) str); +#endif +} + +// dangerous!! (but fast). Don't use in functions that +// require out parameters! +static SQLTCHAR* qToTChar(const QString& str) +{ +#ifdef UNICODE + return (SQLTCHAR*)str.utf16(); +#else + return (unsigned char*) str.ascii(); +#endif +} + +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, +#ifdef UNICODE + SQL_C_WCHAR, +#else + SQL_C_CHAR, +#endif + (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>("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->valueCache.clear(); + + 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); + setActive(true); + return true; +} + +bool QDB2Result::prepare(const QString& query) +{ + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->recInf.clear(); + d->valueCache.clear(); + + 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<QByteArray> tmpStorage; // holds temporary ptrs + QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count()); + + memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER)); + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->recInf.clear(); + d->valueCache.clear(); + + if (!qMakeStatement(d, isForwardOnly(), false)) + return false; + + + QVector<QVariant> &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: +#ifdef UNICODE + { + 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; + } +#endif + // fall through + 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); + + //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: +#ifdef UNICODE + if (bindValueType(i) & QSql::Out) + values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData()); + break; +#endif + // fall through + 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->valueCache.fill(0); + 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) { + setLastError(qMakeError(QCoreApplication::translate("QDB2Result", + "Unable to fetch record %1").arg(i), QSqlError::StatementError, d)); + return false; + } + setAt(i); + return true; +} + +bool QDB2Result::fetchNext() +{ + SQLRETURN r; + d->valueCache.fill(0); + 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->valueCache.fill(0); + 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->valueCache.fill(0); + + 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); + bool ok=false; + switch(d->precisionPolicy) { + case QSql::LowPrecisionInt32: + v = new QVariant(value.toInt(&ok)); + break; + case QSql::LowPrecisionInt64: + v = new QVariant(value.toLongLong(&ok)); + break; + case QSql::LowPrecisionDouble: + v = new QVariant(value.toDouble(&ok)); + break; + case QSql::HighPrecision: + default: + // length + 1 for the comma + v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull)); + ok = true; + break; + } + if(!ok) + v = new QVariant(); + 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->valueCache.clear(); + 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); + setActive(true); + + return true; +} + +void QDB2Result::virtual_hook(int id, void *data) +{ + switch (id) { + case QSqlResult::NextResult: + Q_ASSERT(data); + *static_cast<bool*>(data) = nextResult(); + break; + case QSqlResult::SetNumericalPrecision: + Q_ASSERT(data); + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + 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&, int, + 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; + } + // 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 { + 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); + } + + QString connQStr; + connQStr = QLatin1String("DSN=") + db + 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.toUpper(); + 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.toUpper(), &catalog, &schema, &table); + if (schema.isEmpty()) + schema = d->user; + + 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); + + 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); + if (userVal != d->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.toUpper(), &catalog, &schema, &table); + 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 LowPrecisionNumbers: + case EventNotifications: + return false; + case BLOB: + case Transactions: + case MultipleResultSets: + case PreparedQueries: + case PositionalPlaceholders: + return true; + case Unicode: + // this is the query that shows the codepage for the types: + // select typename, codepage from syscat.datatypes +#ifdef UNICODE + return true; +#else + return false; +#endif + } + 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>("SQLHANDLE"), &d->hDbc); +} + +QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/db2/qsql_db2.h b/src/sql/drivers/db2/qsql_db2.h new file mode 100644 index 0000000..6923027 --- /dev/null +++ b/src/sql/drivers/db2/qsql_db2.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_DB2_H +#define QSQL_DB2_H + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_DB2 +#else +#define Q_EXPORT_SQLDRIVER_DB2 Q_SQL_EXPORT +#endif + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +class QDB2Driver; +class QDB2DriverPrivate; +class QDB2ResultPrivate; +class QSqlRecord; + +class QDB2Result : public QSqlResult +{ +public: + QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp); + ~QDB2Result(); + bool prepare(const QString& query); + bool exec(); + QVariant handle() const; + +protected: + QVariant data(int field); + bool reset (const QString& query); + bool fetch(int i); + bool fetchNext(); + bool fetchFirst(); + bool fetchLast(); + bool isNull(int i); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + void virtual_hook(int id, void *data); + bool nextResult(); + +private: + QDB2ResultPrivate* d; +}; + +class Q_EXPORT_SQLDRIVER_DB2 QDB2Driver : public QSqlDriver +{ + Q_OBJECT +public: + explicit QDB2Driver(QObject* parent = 0); + QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent = 0); + ~QDB2Driver(); + bool hasFeature(DriverFeature) const; + void close(); + QSqlRecord record(const QString& tableName) const; + QStringList tables(QSql::TableType type) const; + QSqlResult *createResult() const; + QSqlIndex primaryIndex(const QString& tablename) const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QString formatValue(const QSqlField &field, bool trimStrings) const; + QVariant handle() const; + bool open(const QString& db, + const QString& user, + const QString& password, + const QString& host, + int port, + const QString& connOpts); + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + +private: + bool setAutoCommit(bool autoCommit); + QDB2DriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_DB2_H diff --git a/src/sql/drivers/drivers.pri b/src/sql/drivers/drivers.pri new file mode 100644 index 0000000..184eca9 --- /dev/null +++ b/src/sql/drivers/drivers.pri @@ -0,0 +1,123 @@ +contains(sql-drivers, all ) { + sql-driver += psql mysql odbc oci tds db2 sqlite ibase +} + +contains(sql-drivers, psql) { + HEADERS += drivers/psql/qsql_psql.h + SOURCES += drivers/psql/qsql_psql.cpp + + unix { + !isEmpty(QT_LFLAGS_PSQL) { + LIBS *= $$QT_LFLAGS_PSQL + QMAKE_CXXFLAGS *= $$QT_CFLAGS_PSQL + } + !contains(LIBS, .*pq.*):LIBS *= -lpq + } + + win32 { + !win32-g++:!contains( LIBS, .*pq.* ):LIBS *= -llibpq + win32-g++:!contains( LIBS, .*pq.* ):LIBS *= -lpq + LIBS *= -lws2_32 -ladvapi32 + } +} + +contains(sql-drivers, mysql) { + HEADERS += drivers/mysql/qsql_mysql.h + SOURCES += drivers/mysql/qsql_mysql.cpp + + unix { + isEmpty(QT_LFLAGS_MYSQL) { + !contains(LIBS, .*mysqlclient.*):!contains(LIBS, .*mysqld.*) { + use_libmysqlclient_r:LIBS *= -lmysqlclient_r + else:LIBS *= -lmysqlclient + } + } else { + LIBS *= $$QT_LFLAGS_MYSQL + QMAKE_CXXFLAGS *= $$QT_CFLAGS_MYSQL + } + } + + win32:!contains(LIBS, .*mysql.*):!contains(LIBS, .*mysqld.*) { + !win32-g++:LIBS *= -llibmysql + win32-g++:LIBS *= -lmysql + } +} + +contains(sql-drivers, odbc) { + HEADERS += drivers/odbc/qsql_odbc.h + SOURCES += drivers/odbc/qsql_odbc.cpp + + mac:!contains( LIBS, .*odbc.* ):LIBS *= -liodbc + unix:!contains( LIBS, .*odbc.* ):LIBS *= -lodbc + + win32 { + !win32-borland:LIBS *= -lodbc32 + win32-borland:LIBS *= $(BCB)/lib/PSDK/odbc32.lib + } +} + +contains(sql-drivers, oci) { + HEADERS += drivers/oci/qsql_oci.h + SOURCES += drivers/oci/qsql_oci.cpp + + unix:!contains( LIBS, .*clnts.* ):LIBS += -lclntsh + + win32:LIBS += -loci +} + +contains(sql-drivers, tds) { + HEADERS += drivers/tds/qsql_tds.h + SOURCES += drivers/tds/qsql_tds.cpp + + unix:LIBS += -L$SYBASE/lib -lsybdb + + win32 { + !win32-borland:LIBS += -lNTWDBLIB + win32-borland:LIBS += $(BCB)/lib/PSDK/NTWDBLIB.LIB + } +} + +contains(sql-drivers, db2) { + HEADERS += drivers/db2/qsql_db2.h + SOURCES += drivers/db2/qsql_db2.cpp + + unix:LIBS += -ldb2 + + win32 { + !win32-borland:LIBS += -ldb2cli +# win32-borland:LIBS += $(BCB)/lib/PSDK/db2cli.lib + } +} + +contains(sql-drivers, ibase) { + HEADERS += drivers/ibase/qsql_ibase.h + SOURCES += drivers/ibase/qsql_ibase.cpp + + unix:LIBS *= -lgds + + win32 { + !win32-borland:LIBS *= -lgds32_ms + win32-borland:LIBS += gds32.lib + } +} + +contains(sql-drivers, sqlite2) { + HEADERS += drivers/sqlite2/qsql_sqlite2.h + SOURCES += drivers/sqlite2/qsql_sqlite2.cpp + !contains(LIBS, .*sqlite.*):LIBS *= -lsqlite +} + +contains(sql-drivers, sqlite) { + !system-sqlite:!contains( LIBS, .*sqlite3.* ) { + CONFIG(release, debug|release):DEFINES *= NDEBUG + DEFINES += SQLITE_OMIT_LOAD_EXTENSION SQLITE_OMIT_COMPLETE + INCLUDEPATH += ../3rdparty/sqlite + SOURCES += ../3rdparty/sqlite/sqlite3.c + } else { + LIBS *= $$QT_LFLAGS_SQLITE + QMAKE_CXXFLAGS *= $$QT_CFLAGS_SQLITE + } + + HEADERS += drivers/sqlite/qsql_sqlite.h + SOURCES += drivers/sqlite/qsql_sqlite.cpp +} diff --git a/src/sql/drivers/ibase/qsql_ibase.cpp b/src/sql/drivers/ibase/qsql_ibase.cpp new file mode 100644 index 0000000..64f13b5 --- /dev/null +++ b/src/sql/drivers/ibase/qsql_ibase.cpp @@ -0,0 +1,1813 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_ibase.h" +#include <qcoreapplication.h> +#include <qdatetime.h> +#include <qvariant.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qlist.h> +#include <qvector.h> +#include <qtextcodec.h> +#include <qmutex.h> +#include <stdlib.h> +#include <limits.h> +#include <math.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define FBVERSION SQL_DIALECT_V6 + +#ifndef SQLDA_CURRENT_VERSION +#define SQLDA_CURRENT_VERSION SQLDA_VERSION1 +#endif + +enum { QIBaseChunkSize = SHRT_MAX / 2 }; + +static bool getIBaseError(QString& msg, ISC_STATUS* status, ISC_LONG &sqlcode, + QTextCodec *tc) +{ + if (status[0] != 1 || status[1] <= 0) + return false; + + msg.clear(); + sqlcode = isc_sqlcode(status); + char buf[512]; + while(isc_interprete(buf, &status)) { + if(!msg.isEmpty()) + msg += QLatin1String(" - "); + if (tc) + msg += tc->toUnicode(buf); + else + msg += QString::fromUtf8(buf); + } + return true; +} + +static void createDA(XSQLDA *&sqlda) +{ + sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(1)); + sqlda->sqln = 1; + sqlda->sqld = 0; + sqlda->version = SQLDA_CURRENT_VERSION; + sqlda->sqlvar[0].sqlind = 0; + sqlda->sqlvar[0].sqldata = 0; +} + +static void enlargeDA(XSQLDA *&sqlda, int n) +{ + free(sqlda); + sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(n)); + sqlda->sqln = n; + sqlda->version = SQLDA_CURRENT_VERSION; +} + +static void initDA(XSQLDA *sqlda) +{ + for (int i = 0; i < sqlda->sqld; ++i) { + switch (sqlda->sqlvar[i].sqltype & ~1) { + case SQL_INT64: + case SQL_LONG: + case SQL_SHORT: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + case SQL_TEXT: + case SQL_BLOB: + sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen); + break; + case SQL_ARRAY: + sqlda->sqlvar[i].sqldata = (char*)malloc(sizeof(ISC_QUAD)); + memset(sqlda->sqlvar[i].sqldata, 0, sizeof(ISC_QUAD)); + break; + case SQL_VARYING: + sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen + sizeof(short)); + break; + default: + // not supported - do not bind. + sqlda->sqlvar[i].sqldata = 0; + break; + } + if (sqlda->sqlvar[i].sqltype & 1) { + sqlda->sqlvar[i].sqlind = (short*)malloc(sizeof(short)); + *(sqlda->sqlvar[i].sqlind) = 0; + } else { + sqlda->sqlvar[i].sqlind = 0; + } + } +} + +static void delDA(XSQLDA *&sqlda) +{ + if (!sqlda) + return; + for (int i = 0; i < sqlda->sqld; ++i) { + free(sqlda->sqlvar[i].sqlind); + free(sqlda->sqlvar[i].sqldata); + } + free(sqlda); + sqlda = 0; +} + +static QVariant::Type qIBaseTypeName(int iType, bool hasScale) +{ + switch (iType) { + case blr_varying: + case blr_varying2: + case blr_text: + case blr_cstring: + case blr_cstring2: + return QVariant::String; + case blr_sql_time: + return QVariant::Time; + case blr_sql_date: + return QVariant::Date; + case blr_timestamp: + return QVariant::DateTime; + case blr_blob: + return QVariant::ByteArray; + case blr_quad: + case blr_short: + case blr_long: + return (hasScale ? QVariant::Double : QVariant::Int); + case blr_int64: + return (hasScale ? QVariant::Double : QVariant::LongLong); + case blr_float: + case blr_d_float: + case blr_double: + return QVariant::Double; + } + qWarning("qIBaseTypeName: unknown datatype: %d", iType); + return QVariant::Invalid; +} + +static QVariant::Type qIBaseTypeName2(int iType, bool hasScale) +{ + switch(iType & ~1) { + case SQL_VARYING: + case SQL_TEXT: + return QVariant::String; + case SQL_LONG: + case SQL_SHORT: + return (hasScale ? QVariant::Double : QVariant::Int); + case SQL_INT64: + return (hasScale ? QVariant::Double : QVariant::LongLong); + case SQL_FLOAT: + case SQL_DOUBLE: + return QVariant::Double; + case SQL_TIMESTAMP: + return QVariant::DateTime; + case SQL_TYPE_TIME: + return QVariant::Time; + case SQL_TYPE_DATE: + return QVariant::Date; + case SQL_ARRAY: + return QVariant::List; + case SQL_BLOB: + return QVariant::ByteArray; + default: + return QVariant::Invalid; + } +} + +static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) +{ + static const QTime midnight(0, 0, 0, 0); + static const QDate basedate(1858, 11, 17); + ISC_TIMESTAMP ts; + ts.timestamp_time = midnight.msecsTo(dt.time()) * 10; + ts.timestamp_date = basedate.daysTo(dt.date()); + return ts; +} + +static QDateTime fromTimeStamp(char *buffer) +{ + static const QDate bd(1858, 11, 17); + QTime t; + QDate d; + + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10)); + d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); + + return QDateTime(d, t); +} + +static ISC_TIME toTime(const QTime &t) +{ + static const QTime midnight(0, 0, 0, 0); + return (ISC_TIME)midnight.msecsTo(t) * 10; +} + +static QTime fromTime(char *buffer) +{ + QTime t; + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + t = t.addMSecs(int((*(ISC_TIME*)buffer) / 10)); + + return t; +} + +static ISC_DATE toDate(const QDate &t) +{ + static const QDate basedate(1858, 11, 17); + ISC_DATE date; + + date = basedate.daysTo(t); + return date; +} + +static QDate fromDate(char *buffer) +{ + static const QDate bd(1858, 11, 17); + QDate d; + + // have to demangle the structure ourselves because isc_decode_time + // strips the msecs + d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); + + return d; +} + +static QByteArray encodeString(QTextCodec *tc, const QString &str) +{ + if (tc) + return tc->fromUnicode(str); + return str.toUtf8(); +} + +struct QIBaseEventBuffer { +#if defined(FB_API_VER) && FB_API_VER >= 20 + ISC_UCHAR *eventBuffer; + ISC_UCHAR *resultBuffer; +#else + char *eventBuffer; + char *resultBuffer; +#endif + ISC_LONG bufferLength; + ISC_LONG eventId; + + enum QIBaseSubscriptionState { Starting, Subscribed, Finished }; + QIBaseSubscriptionState subscriptionState; +}; + +class QIBaseDriverPrivate +{ +public: + QIBaseDriverPrivate(QIBaseDriver *d) : q(d), ibase(0), trans(0), tc(0) {} + + bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) + { + QString imsg; + ISC_LONG sqlcode; + if (!getIBaseError(imsg, status, sqlcode, tc)) + return false; + + q->setLastError(QSqlError(QCoreApplication::translate("QIBaseDriver", msg), + imsg, typ, int(sqlcode))); + return true; + } + +public: + QIBaseDriver* q; + isc_db_handle ibase; + isc_tr_handle trans; + QTextCodec *tc; + ISC_STATUS status[20]; + QMap<QString, QIBaseEventBuffer*> eventBuffers; +}; + +typedef QMap<void *, QIBaseDriver *> QIBaseBufferDriverMap; +Q_GLOBAL_STATIC(QIBaseBufferDriverMap, qBufferDriverMap) +Q_GLOBAL_STATIC(QMutex, qMutex); + +static void qFreeEventBuffer(QIBaseEventBuffer* eBuffer) +{ + qMutex()->lock(); + qBufferDriverMap()->remove(reinterpret_cast<void *>(eBuffer->resultBuffer)); + qMutex()->unlock(); + delete eBuffer; +} + +class QIBaseResultPrivate +{ +public: + QIBaseResultPrivate(QIBaseResult *d, const QIBaseDriver *ddb); + ~QIBaseResultPrivate() { cleanup(); } + + void cleanup(); + bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError) + { + QString imsg; + ISC_LONG sqlcode; + if (!getIBaseError(imsg, status, sqlcode, tc)) + return false; + + q->setLastError(QSqlError(QCoreApplication::translate("QIBaseResult", msg), + imsg, typ, int(sqlcode))); + return true; + } + + bool transaction(); + bool commit(); + + bool isSelect(); + QVariant fetchBlob(ISC_QUAD *bId); + bool writeBlob(int i, const QByteArray &ba); + QVariant fetchArray(int pos, ISC_QUAD *arr); + bool writeArray(int i, const QList<QVariant> &list); + +public: + QIBaseResult *q; + const QIBaseDriver *db; + ISC_STATUS status[20]; + isc_tr_handle trans; + //indicator whether we have a local transaction or a transaction on driver level + bool localTransaction; + isc_stmt_handle stmt; + isc_db_handle ibase; + XSQLDA *sqlda; // output sqlda + XSQLDA *inda; // input parameters + int queryType; + QTextCodec *tc; +}; + + +QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *d, const QIBaseDriver *ddb): + q(d), db(ddb), trans(0), stmt(0), ibase(ddb->d->ibase), sqlda(0), inda(0), queryType(-1), tc(ddb->d->tc) +{ + localTransaction = (ddb->d->ibase == 0); +} + +void QIBaseResultPrivate::cleanup() +{ + commit(); + if (!localTransaction) + trans = 0; + + if (stmt) { + isc_dsql_free_statement(status, &stmt, DSQL_drop); + stmt = 0; + } + + delDA(sqlda); + delDA(inda); + + queryType = -1; + q->cleanup(); +} + +bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba) +{ + isc_blob_handle handle = 0; + ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata; + isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0); + if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"), + QSqlError::StatementError)) { + int i = 0; + while (i < ba.size()) { + isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)), + const_cast<char*>(ba.data()) + i); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB"))) + return false; + i += qMin(ba.size() - i, int(QIBaseChunkSize)); + } + } + isc_close_blob(status, &handle); + return true; +} + +QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) +{ + isc_blob_handle handle = 0; + + isc_open_blob2(status, &ibase, &trans, &handle, bId, 0, 0); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to open BLOB"), + QSqlError::StatementError)) + return QVariant(); + + unsigned short len = 0; + QByteArray ba; + int chunkSize = QIBaseChunkSize; + ba.resize(chunkSize); + int read = 0; + while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) { + read += len; + ba.resize(read + chunkSize); + } + ba.resize(read); + + bool isErr = (status[1] == isc_segstr_eof ? false : + isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Unable to read BLOB"), + QSqlError::StatementError)); + + isc_close_blob(status, &handle); + + if (isErr) + return QVariant(); + + ba.resize(read); + return ba; +} + +template<typename T> +static QList<QVariant> toList(char** buf, int count, T* = 0) +{ + QList<QVariant> res; + for (int i = 0; i < count; ++i) { + res.append(*(T*)(*buf)); + *buf += sizeof(T); + } + return res; +} +/* char** ? seems like bad influence from oracle ... */ +template<> +QList<QVariant> toList<long>(char** buf, int count, long*) +{ + QList<QVariant> res; + for (int i = 0; i < count; ++i) { + if (sizeof(int) == sizeof(long)) + res.append(int((*(long*)(*buf)))); + else + res.append((qint64)(*(long*)(*buf))); + *buf += sizeof(long); + } + return res; +} + +static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, + short* numElements, ISC_ARRAY_DESC *arrayDesc, + QTextCodec *tc) +{ + const short dim = arrayDesc->array_desc_dimensions - 1; + const unsigned char dataType = arrayDesc->array_desc_dtype; + QList<QVariant> valList; + unsigned short strLen = arrayDesc->array_desc_length; + + if (curDim != dim) { + for(int i = 0; i < numElements[curDim]; ++i) + buffer = readArrayBuffer(list, buffer, curDim + 1, numElements, + arrayDesc, tc); + } else { + switch(dataType) { + case blr_varying: + case blr_varying2: + strLen += 2; // for the two terminating null values + case blr_text: + case blr_text2: { + int o; + for (int i = 0; i < numElements[dim]; ++i) { + for(o = 0; o < strLen && buffer[o]!=0; ++o ) + ; + + if (tc) + valList.append(tc->toUnicode(buffer, o)); + else + valList.append(QString::fromUtf8(buffer, o)); + + buffer += strLen; + } + break; } + case blr_long: + valList = toList<long>(&buffer, numElements[dim], static_cast<long *>(0)); + break; + case blr_short: + valList = toList<short>(&buffer, numElements[dim]); + break; + case blr_int64: + valList = toList<qint64>(&buffer, numElements[dim]); + break; + case blr_float: + valList = toList<float>(&buffer, numElements[dim]); + break; + case blr_double: + valList = toList<double>(&buffer, numElements[dim]); + break; + case blr_timestamp: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromTimeStamp(buffer)); + buffer += sizeof(ISC_TIMESTAMP); + } + break; + case blr_sql_time: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromTime(buffer)); + buffer += sizeof(ISC_TIME); + } + break; + case blr_sql_date: + for(int i = 0; i < numElements[dim]; ++i) { + valList.append(fromDate(buffer)); + buffer += sizeof(ISC_DATE); + } + break; + } + } + if (dim > 0) + list.append(valList); + else + list += valList; + return buffer; +} + +QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) +{ + QList<QVariant> list; + ISC_ARRAY_DESC desc; + + if (!arr) + return list; + + QByteArray relname(sqlda->sqlvar[pos].relname, sqlda->sqlvar[pos].relname_length); + QByteArray sqlname(sqlda->sqlvar[pos].aliasname, sqlda->sqlvar[pos].aliasname_length); + + isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), + QSqlError::StatementError)) + return list; + + + int arraySize = 1, subArraySize; + short dimensions = desc.array_desc_dimensions; + short *numElements = new short[dimensions]; + + for(int i = 0; i < dimensions; ++i) { + subArraySize = (desc.array_desc_bounds[i].array_bound_upper - + desc.array_desc_bounds[i].array_bound_lower + 1); + numElements[i] = subArraySize; + arraySize = subArraySize * arraySize; + } + + ISC_LONG bufLen; + QByteArray ba; + /* varying arrayelements are stored with 2 trailing null bytes + indicating the length of the string + */ + if (desc.array_desc_dtype == blr_varying + || desc.array_desc_dtype == blr_varying2) { + desc.array_desc_length += 2; + bufLen = desc.array_desc_length * arraySize * sizeof(short); + } else { + bufLen = desc.array_desc_length * arraySize; + } + + + ba.resize(int(bufLen)); + isc_array_get_slice(status, &ibase, &trans, arr, &desc, ba.data(), &bufLen); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get array data"), + QSqlError::StatementError)) + return list; + + readArrayBuffer(list, ba.data(), 0, numElements, &desc, tc); + + delete[] numElements; + + return QVariant(list); +} + +template<typename T> +static char* fillList(char *buffer, const QList<QVariant> &list, T* = 0) +{ + for (int i = 0; i < list.size(); ++i) { + T val; + val = qvariant_cast<T>(list.at(i)); + memcpy(buffer, &val, sizeof(T)); + buffer += sizeof(T); + } + return buffer; +} + +template<> +char* fillList<float>(char *buffer, const QList<QVariant> &list, float*) +{ + for (int i = 0; i < list.size(); ++i) { + double val; + float val2 = 0; + val = qvariant_cast<double>(list.at(i)); + val2 = (float)val; + memcpy(buffer, &val2, sizeof(float)); + buffer += sizeof(float); + } + return buffer; +} + +static char* qFillBufferWithString(char *buffer, const QString& string, + short buflen, bool varying, bool array, + QTextCodec *tc) +{ + QByteArray str = encodeString(tc, string); // keep a copy of the string alive in this scope + if (varying) { + short tmpBuflen = buflen; + if (str.length() < buflen) + buflen = str.length(); + if (array) { // interbase stores varying arrayelements different than normal varying elements + memcpy(buffer, str.data(), buflen); + memset(buffer + buflen, 0, tmpBuflen - buflen); + } else { + *(short*)buffer = buflen; // first two bytes is the length + memcpy(buffer + sizeof(short), str.data(), buflen); + } + buffer += tmpBuflen; + } else { + str = str.leftJustified(buflen, ' ', true); + memcpy(buffer, str.data(), buflen); + buffer += buflen; + } + return buffer; +} + +static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, + QVariant::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc, + QString& error, QTextCodec *tc) +{ + int i; + ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds; + short dim = arrayDesc->array_desc_dimensions - 1; + + int elements = (bounds[curDim].array_bound_upper - + bounds[curDim].array_bound_lower + 1); + + if (list.size() != elements) { // size mismatch + error = QLatin1String("Expected size: %1. Supplied size: %2"); + error = QLatin1String("Array size mismatch. Fieldname: %1 ") + + error.arg(elements).arg(list.size()); + return 0; + } + + if (curDim != dim) { + for(i = 0; i < list.size(); ++i) { + + if (list.at(i).type() != QVariant::List) { // dimensions mismatch + error = QLatin1String("Array dimensons mismatch. Fieldname: %1"); + return 0; + } + + buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1, + arrayDesc, error, tc); + if (!buffer) + return 0; + } + } else { + switch(type) { + case QVariant::Int: + case QVariant::UInt: + if (arrayDesc->array_desc_dtype == blr_short) + buffer = fillList<short>(buffer, list); + else + buffer = fillList<int>(buffer, list); + break; + case QVariant::Double: + if (arrayDesc->array_desc_dtype == blr_float) + buffer = fillList<float>(buffer, list, static_cast<float *>(0)); + else + buffer = fillList<double>(buffer, list); + break; + case QVariant::LongLong: + buffer = fillList<qint64>(buffer, list); + break; + case QVariant::ULongLong: + buffer = fillList<quint64>(buffer, list); + break; + case QVariant::String: + for (i = 0; i < list.size(); ++i) + buffer = qFillBufferWithString(buffer, list.at(i).toString(), + arrayDesc->array_desc_length, + arrayDesc->array_desc_dtype == blr_varying, + true, tc); + break; + case QVariant::Date: + for (i = 0; i < list.size(); ++i) { + *((ISC_DATE*)buffer) = toDate(list.at(i).toDate()); + buffer += sizeof(ISC_DATE); + } + break; + case QVariant::Time: + for (i = 0; i < list.size(); ++i) { + *((ISC_TIME*)buffer) = toTime(list.at(i).toTime()); + buffer += sizeof(ISC_TIME); + } + break; + + case QVariant::DateTime: + for (i = 0; i < list.size(); ++i) { + *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime()); + buffer += sizeof(ISC_TIMESTAMP); + } + break; + default: + break; + } + } + return buffer; +} + +bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list) +{ + QString error; + ISC_QUAD *arrayId = (ISC_QUAD*) inda->sqlvar[column].sqldata; + ISC_ARRAY_DESC desc; + + QByteArray relname(inda->sqlvar[column].relname, inda->sqlvar[column].relname_length); + QByteArray sqlname(inda->sqlvar[column].aliasname, inda->sqlvar[column].aliasname_length); + + isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), + QSqlError::StatementError)) + return false; + + short arraySize = 1; + ISC_LONG bufLen; + QList<QVariant> subList = list; + + short dimensions = desc.array_desc_dimensions; + for(int i = 0; i < dimensions; ++i) { + arraySize *= (desc.array_desc_bounds[i].array_bound_upper - + desc.array_desc_bounds[i].array_bound_lower + 1); + } + + /* varying arrayelements are stored with 2 trailing null bytes + indicating the length of the string + */ + if (desc.array_desc_dtype == blr_varying || + desc.array_desc_dtype == blr_varying2) + desc.array_desc_length += 2; + + bufLen = desc.array_desc_length * arraySize; + QByteArray ba; + ba.resize(int(bufLen)); + + if (list.size() > arraySize) { + error = QLatin1String("Array size missmatch: size of %1 is %2, size of provided list is %3"); + error = error.arg(QLatin1String(sqlname)).arg(arraySize).arg(list.size()); + q->setLastError(QSqlError(error, QLatin1String(""), QSqlError::StatementError)); + return false; + } + + if (!createArrayBuffer(ba.data(), list, + qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0), + 0, &desc, error, tc)) { + q->setLastError(QSqlError(error.arg(QLatin1String(sqlname)), QLatin1String(""), + QSqlError::StatementError)); + return false; + } + + /* readjust the buffer size*/ + if (desc.array_desc_dtype == blr_varying + || desc.array_desc_dtype == blr_varying2) + desc.array_desc_length -= 2; + + isc_array_put_slice(status, &ibase, &trans, arrayId, &desc, ba.data(), &bufLen); + return true; +} + + +bool QIBaseResultPrivate::isSelect() +{ + char acBuffer[9]; + char qType = isc_info_sql_stmt_type; + isc_dsql_sql_info(status, &stmt, 1, &qType, sizeof(acBuffer), acBuffer); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get query info"), + QSqlError::StatementError)) + return false; + int iLength = isc_vax_integer(&acBuffer[1], 2); + queryType = isc_vax_integer(&acBuffer[3], iLength); + return (queryType == isc_info_sql_stmt_select || queryType == isc_info_sql_stmt_exec_procedure); +} + +bool QIBaseResultPrivate::transaction() +{ + if (trans) + return true; + if (db->d->trans) { + localTransaction = false; + trans = db->d->trans; + return true; + } + localTransaction = true; + + isc_start_transaction(status, &trans, 1, &ibase, 0, NULL); + if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not start transaction"), + QSqlError::TransactionError)) + return false; + + return true; +} + +// does nothing if the transaction is on the +// driver level +bool QIBaseResultPrivate::commit() +{ + if (!trans) + return false; + // don't commit driver's transaction, the driver will do it for us + if (!localTransaction) + return true; + + isc_commit_transaction(status, &trans); + trans = 0; + return !isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to commit transaction"), + QSqlError::TransactionError); +} + +////////// + +QIBaseResult::QIBaseResult(const QIBaseDriver* db): + QSqlCachedResult(db) +{ + d = new QIBaseResultPrivate(this, db); +} + +QIBaseResult::~QIBaseResult() +{ + delete d; +} + +bool QIBaseResult::prepare(const QString& query) +{ + //qDebug("prepare: %s\n", qPrintable(query)); + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + d->cleanup(); + setActive(false); + setAt(QSql::BeforeFirstRow); + + createDA(d->sqlda); + createDA(d->inda); + + if (!d->transaction()) + return false; + + isc_dsql_allocate_statement(d->status, &d->ibase, &d->stmt); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not allocate statement"), + QSqlError::StatementError)) + return false; + isc_dsql_prepare(d->status, &d->trans, &d->stmt, 0, + const_cast<char*>(encodeString(d->tc, query).constData()), FBVERSION, d->sqlda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not prepare statement"), + QSqlError::StatementError)) + return false; + + isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Could not describe input statement"), QSqlError::StatementError)) + return false; + if (d->inda->sqld > d->inda->sqln) { + enlargeDA(d->inda, d->inda->sqld); + + isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", + "Could not describe input statement"), QSqlError::StatementError)) + return false; + } + initDA(d->inda); + if (d->sqlda->sqld > d->sqlda->sqln) { + // need more field descriptors + enlargeDA(d->sqlda, d->sqlda->sqld); + + isc_dsql_describe(d->status, &d->stmt, FBVERSION, d->sqlda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not describe statement"), + QSqlError::StatementError)) + return false; + } + initDA(d->sqlda); + + setSelect(d->isSelect()); + if (!isSelect()) { + free(d->sqlda); + d->sqlda = 0; + } + + return true; +} + + +bool QIBaseResult::exec() +{ + bool ok = true; + + if (!d->trans) + d->transaction(); + + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + setActive(false); + setAt(QSql::BeforeFirstRow); + + if (d->inda) { + QVector<QVariant>& values = boundValues(); + int i; + if (values.count() > d->inda->sqld) { + qWarning("QIBaseResult::exec: Parameter mismatch, expected %d, got %d parameters", + d->inda->sqld, values.count()); + return false; + } + int para = 0; + for (i = 0; i < values.count(); ++i) { + para = i; + if (!d->inda->sqlvar[para].sqldata) + // skip unknown datatypes + continue; + const QVariant val(values[i]); + if (d->inda->sqlvar[para].sqltype & 1) { + if (val.isNull()) { + // set null indicator + *(d->inda->sqlvar[para].sqlind) = -1; + // and set the value to 0, otherwise it would count as empty string. + // it seems to be working with just setting sqlind to -1 + //*((char*)d->inda->sqlvar[para].sqldata) = 0; + continue; + } + // a value of 0 means non-null. + *(d->inda->sqlvar[para].sqlind) = 0; + } + switch(d->inda->sqlvar[para].sqltype & ~1) { + case SQL_INT64: + if (d->inda->sqlvar[para].sqlscale < 0) + *((qint64*)d->inda->sqlvar[para].sqldata) = + (qint64)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); + break; + case SQL_LONG: + if (d->inda->sqlvar[para].sqlscale < 0) + *((long*)d->inda->sqlvar[para].sqldata) = + (long)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((long*)d->inda->sqlvar[para].sqldata) = (long)val.toLongLong(); + break; + case SQL_SHORT: + if (d->inda->sqlvar[para].sqlscale < 0) + *((short*)d->inda->sqlvar[para].sqldata) = + (short)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); + else + *((short*)d->inda->sqlvar[para].sqldata) = (short)val.toInt(); + break; + case SQL_FLOAT: + *((float*)d->inda->sqlvar[para].sqldata) = (float)val.toDouble(); + break; + case SQL_DOUBLE: + *((double*)d->inda->sqlvar[para].sqldata) = val.toDouble(); + break; + case SQL_TIMESTAMP: + *((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime()); + break; + case SQL_TYPE_TIME: + *((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime()); + break; + case SQL_TYPE_DATE: + *((ISC_DATE*)d->inda->sqlvar[para].sqldata) = toDate(val.toDate()); + break; + case SQL_VARYING: + case SQL_TEXT: + qFillBufferWithString(d->inda->sqlvar[para].sqldata, val.toString(), + d->inda->sqlvar[para].sqllen, + (d->inda->sqlvar[para].sqltype & ~1) == SQL_VARYING, false, d->tc); + break; + case SQL_BLOB: + ok &= d->writeBlob(para, val.toByteArray()); + break; + case SQL_ARRAY: + ok &= d->writeArray(para, val.toList()); + break; + default: + qWarning("QIBaseResult::exec: Unknown datatype %d", + d->inda->sqlvar[para].sqltype & ~1); + break; + } + } + } + + if (ok) { + if (colCount()) { + isc_dsql_free_statement(d->status, &d->stmt, DSQL_close); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to close statement"))) + return false; + cleanup(); + } + if (d->queryType == isc_info_sql_stmt_exec_procedure) + isc_dsql_execute2(d->status, &d->trans, &d->stmt, FBVERSION, d->inda, d->sqlda); + else + isc_dsql_execute(d->status, &d->trans, &d->stmt, FBVERSION, d->inda); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to execute query"))) + return false; + + // Not all stored procedures necessarily return values. + if (d->queryType == isc_info_sql_stmt_exec_procedure && d->sqlda->sqld == 0) + delDA(d->sqlda); + + if (d->sqlda) + init(d->sqlda->sqld); + + if (!isSelect()) + d->commit(); + + setActive(true); + return true; + } + return false; +} + +bool QIBaseResult::reset (const QString& query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) +{ + ISC_STATUS stat = 0; + + // Stored Procedures are special - they populate our d->sqlda when executing, + // so we don't have to call isc_dsql_fetch + if (d->queryType == isc_info_sql_stmt_exec_procedure) { + // the first "fetch" shall succeed, all consecutive ones will fail since + // we only have one row to fetch for stored procedures + if (rowIdx != 0) + stat = 100; + } else { + stat = isc_dsql_fetch(d->status, &d->stmt, FBVERSION, d->sqlda); + } + + if (stat == 100) { + // no more rows + setAt(QSql::AfterLastRow); + return false; + } + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not fetch next item"), + QSqlError::StatementError)) + return false; + if (rowIdx < 0) // not interested in actual values + return true; + + for (int i = 0; i < d->sqlda->sqld; ++i) { + int idx = rowIdx + i; + char *buf = d->sqlda->sqlvar[i].sqldata; + int size = d->sqlda->sqlvar[i].sqllen; + Q_ASSERT(buf); + + if ((d->sqlda->sqlvar[i].sqltype & 1) && *d->sqlda->sqlvar[i].sqlind) { + // null value + QVariant v; + v.convert(qIBaseTypeName2(d->sqlda->sqlvar[i].sqltype, d->sqlda->sqlvar[i].sqlscale < 0)); + row[idx] = v; + continue; + } + + switch(d->sqlda->sqlvar[i].sqltype & ~1) { + case SQL_VARYING: + // pascal strings - a short with a length information followed by the data + if (d->tc) + row[idx] = d->tc->toUnicode(buf + sizeof(short), *(short*)buf); + else + row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf); + break; + case SQL_INT64: + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = *(qint64*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale); + else + row[idx] = QVariant(*(qint64*)buf); + break; + case SQL_LONG: + if (d->sqlda->sqlvar[i].sqllen == 4) + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = QVariant(*(qint32*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); + else + row[idx] = QVariant(*(qint32*)buf); + else + row[idx] = QVariant(*(qint64*)buf); + break; + case SQL_SHORT: + if (d->sqlda->sqlvar[i].sqlscale < 0) + row[idx] = QVariant(long((*(short*)buf)) * pow(10.0, d->sqlda->sqlvar[i].sqlscale)); + else + row[idx] = QVariant(int((*(short*)buf))); + break; + case SQL_FLOAT: + row[idx] = QVariant(double((*(float*)buf))); + break; + case SQL_DOUBLE: + row[idx] = QVariant(*(double*)buf); + break; + case SQL_TIMESTAMP: + row[idx] = fromTimeStamp(buf); + break; + case SQL_TYPE_TIME: + row[idx] = fromTime(buf); + break; + case SQL_TYPE_DATE: + row[idx] = fromDate(buf); + break; + case SQL_TEXT: + if (d->tc) + row[idx] = d->tc->toUnicode(buf, size); + else + row[idx] = QString::fromUtf8(buf, size); + break; + case SQL_BLOB: + row[idx] = d->fetchBlob((ISC_QUAD*)buf); + break; + case SQL_ARRAY: + row[idx] = d->fetchArray(i, (ISC_QUAD*)buf); + break; + default: + // unknown type - don't even try to fetch + row[idx] = QVariant(); + break; + } + } + + return true; +} + +int QIBaseResult::size() +{ + return -1; + +#if 0 /// ### FIXME + static char sizeInfo[] = {isc_info_sql_records}; + char buf[64]; + + //qDebug() << sizeInfo; + if (!isActive() || !isSelect()) + return -1; + + char ct; + short len; + int val = 0; +// while(val == 0) { + isc_dsql_sql_info(d->status, &d->stmt, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); +// isc_database_info(d->status, &d->ibase, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf); + + for(int i = 0; i < 66; ++i) + qDebug() << QString::number(buf[i]); + + for (char* c = buf + 3; *c != isc_info_end; /*nothing*/) { + ct = *(c++); + len = isc_vax_integer(c, 2); + c += 2; + val = isc_vax_integer(c, len); + c += len; + qDebug() << "size" << val; + if (ct == isc_info_req_select_count) + return val; + } + //qDebug() << "size -1"; + return -1; + + unsigned int i, result_size; + if (buf[0] == isc_info_sql_records) { + i = 3; + result_size = isc_vax_integer(&buf[1],2); + while (buf[i] != isc_info_end && i < result_size) { + len = (short)isc_vax_integer(&buf[i+1],2); + if (buf[i] == isc_info_req_select_count) + return (isc_vax_integer(&buf[i+3],len)); + i += len+3; + } + } +// } + return -1; +#endif +} + +int QIBaseResult::numRowsAffected() +{ + static char acCountInfo[] = {isc_info_sql_records}; + char cCountType; + + switch (d->queryType) { + case isc_info_sql_stmt_select: + cCountType = isc_info_req_select_count; + break; + case isc_info_sql_stmt_update: + cCountType = isc_info_req_update_count; + break; + case isc_info_sql_stmt_delete: + cCountType = isc_info_req_delete_count; + break; + case isc_info_sql_stmt_insert: + cCountType = isc_info_req_insert_count; + break; + } + + char acBuffer[33]; + int iResult = -1; + isc_dsql_sql_info(d->status, &d->stmt, sizeof(acCountInfo), acCountInfo, sizeof(acBuffer), acBuffer); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get statement info"), + QSqlError::StatementError)) + return -1; + for (char *pcBuf = acBuffer + 3; *pcBuf != isc_info_end; /*nothing*/) { + char cType = *pcBuf++; + short sLength = isc_vax_integer (pcBuf, 2); + pcBuf += 2; + int iValue = isc_vax_integer (pcBuf, sLength); + pcBuf += sLength; + + if (cType == cCountType) { + iResult = iValue; + break; + } + } + return iResult; +} + +QSqlRecord QIBaseResult::record() const +{ + QSqlRecord rec; + if (!isActive() || !d->sqlda) + return rec; + + XSQLVAR v; + for (int i = 0; i < d->sqlda->sqld; ++i) { + v = d->sqlda->sqlvar[i]; + QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(), + qIBaseTypeName2(v.sqltype, v.sqlscale < 0)); + QSqlQuery q(new QIBaseResult(d->db)); + q.setForwardOnly(true); + q.exec(QLatin1String("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG " + "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " + "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " + "AND a.RDB$RELATION_NAME = '") + QString::fromAscii(v.relname, v.relname_length).toUpper() + QLatin1String("' " + "AND a.RDB$FIELD_NAME = '") + QString::fromAscii(v.sqlname, v.sqlname_length).toUpper() + QLatin1String("' ")); + if(q.first()) { + if(v.sqlscale < 0) { + f.setLength(q.value(0).toInt()); + f.setPrecision(qAbs(q.value(1).toInt())); + } else { + f.setLength(q.value(2).toInt()); + f.setPrecision(0); + } + f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional); + } + else { + f.setLength(0); + f.setPrecision(0); + f.setRequiredStatus(QSqlField::Unknown); + } + f.setSqlType(v.sqltype); + rec.append(f); + } + return rec; +} + +QVariant QIBaseResult::handle() const +{ + return QVariant(qRegisterMetaType<isc_stmt_handle>("isc_stmt_handle"), &d->stmt); +} + +/*********************************/ + +QIBaseDriver::QIBaseDriver(QObject * parent) + : QSqlDriver(parent) +{ + d = new QIBaseDriverPrivate(this); +} + +QIBaseDriver::QIBaseDriver(isc_db_handle connection, QObject *parent) + : QSqlDriver(parent) +{ + d = new QIBaseDriverPrivate(this); + d->ibase = connection; + setOpen(true); + setOpenError(false); +} + +QIBaseDriver::~QIBaseDriver() +{ + delete d; +} + +bool QIBaseDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case QuerySize: + case NamedPlaceholders: + case LastInsertId: + case BatchOperations: + case SimpleLocking: + case LowPrecisionNumbers: + case FinishQuery: + case MultipleResultSets: + return false; + case Transactions: + case PreparedQueries: + case PositionalPlaceholders: + case Unicode: + case BLOB: + case EventNotifications: + return true; + } + return false; +} + +bool QIBaseDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int /*port*/, + const QString & connOpts) +{ + if (isOpen()) + close(); + + const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); + + QString encString; + QByteArray role; + for (int i = 0; i < opts.count(); ++i) { + QString tmp(opts.at(i).simplified()); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { + QString val = tmp.mid(idx + 1).simplified(); + QString opt = tmp.left(idx).simplified(); + if (opt.toUpper() == QLatin1String("ISC_DPB_LC_CTYPE")) + encString = val; + else if (opt.toUpper() == QLatin1String("ISC_DPB_SQL_ROLE_NAME")) { + role = val.toLocal8Bit(); + role.truncate(255); + } + } + } + + // Use UNICODE_FSS when no ISC_DPB_LC_CTYPE is provided + if (encString.isEmpty()) + encString = QLatin1String("UNICODE_FSS"); + else { + d->tc = QTextCodec::codecForName(encString.toLocal8Bit()); + if (!d->tc) { + qWarning("Unsupported encoding: %s. Using UNICODE_FFS for ISC_DPB_LC_CTYPE.", encString.toLocal8Bit().constData()); + encString = QLatin1String("UNICODE_FSS"); // Fallback to UNICODE_FSS + } + } + + QByteArray enc = encString.toLocal8Bit(); + QByteArray usr = user.toLocal8Bit(); + QByteArray pass = password.toLocal8Bit(); + enc.truncate(255); + usr.truncate(255); + pass.truncate(255); + + QByteArray ba; + ba.resize(usr.length() + pass.length() + enc.length() + role.length() + 6); + int i = -1; + ba[++i] = isc_dpb_version1; + ba[++i] = isc_dpb_user_name; + ba[++i] = usr.length(); + memcpy(ba.data() + ++i, usr.data(), usr.length()); + i += usr.length(); + ba[i] = isc_dpb_password; + ba[++i] = pass.length(); + memcpy(ba.data() + ++i, pass.data(), pass.length()); + i += pass.length(); + ba[i] = isc_dpb_lc_ctype; + ba[++i] = enc.length(); + memcpy(ba.data() + ++i, enc.data(), enc.length()); + i += enc.length(); + + if (!role.isEmpty()) { + ba[i] = isc_dpb_sql_role_name; + ba[++i] = role.length(); + memcpy(ba.data() + ++i, role.data(), role.length()); + i += role.length(); + } + + QString ldb; + if (!host.isEmpty()) + ldb += host + QLatin1Char(':'); + ldb += db; + isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()), + &d->ibase, i, ba.data()); + if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"), + QSqlError::ConnectionError)) { + setOpenError(true); + return false; + } + + setOpen(true); + return true; +} + +void QIBaseDriver::close() +{ + if (isOpen()) { + + if (d->eventBuffers.size()) { + ISC_STATUS status[20]; + QMap<QString, QIBaseEventBuffer *>::const_iterator i; + for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { + QIBaseEventBuffer *eBuffer = i.value(); + eBuffer->subscriptionState = QIBaseEventBuffer::Finished; + isc_cancel_events(status, &d->ibase, &eBuffer->eventId); + qFreeEventBuffer(eBuffer); + } + d->eventBuffers.clear(); + +#if defined(FB_API_VER) + // Workaround for Firebird crash + QTime timer; + timer.start(); + while (timer.elapsed() < 500) + QCoreApplication::processEvents(); +#endif + } + + isc_detach_database(d->status, &d->ibase); + d->ibase = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QIBaseDriver::createResult() const +{ + return new QIBaseResult(this); +} + +bool QIBaseDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + if (d->trans) + return false; + + isc_start_transaction(d->status, &d->trans, 1, &d->ibase, 0, NULL); + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Could not start transaction"), + QSqlError::TransactionError); +} + +bool QIBaseDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + if (!d->trans) + return false; + + isc_commit_transaction(d->status, &d->trans); + d->trans = 0; + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to commit transaction"), + QSqlError::TransactionError); +} + +bool QIBaseDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + if (!d->trans) + return false; + + isc_rollback_transaction(d->status, &d->trans); + d->trans = 0; + return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to rollback transaction"), + QSqlError::TransactionError); +} + +QStringList QIBaseDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QString typeFilter; + + if (type == QSql::SystemTables) { + typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0"); + } else if (type == (QSql::SystemTables | QSql::Views)) { + typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL"); + } else { + if (!(type & QSql::SystemTables)) + typeFilter += QLatin1String("RDB$SYSTEM_FLAG = 0 AND "); + if (!(type & QSql::Views)) + typeFilter += QLatin1String("RDB$VIEW_BLR IS NULL AND "); + if (!(type & QSql::Tables)) + typeFilter += QLatin1String("RDB$VIEW_BLR IS NOT NULL AND "); + if (!typeFilter.isEmpty()) + typeFilter.chop(5); + } + if (!typeFilter.isEmpty()) + typeFilter.prepend(QLatin1String("where ")); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + if (!q.exec(QLatin1String("select rdb$relation_name from rdb$relations ") + typeFilter)) + return res; + while(q.next()) + res << q.value(0).toString().simplified(); + + return res; +} + +QSqlRecord QIBaseDriver::record(const QString& tablename) const +{ + QSqlRecord rec; + if (!isOpen()) + return rec; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + q.exec(QLatin1String("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, " + "b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG " + "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b " + "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE " + "AND a.RDB$RELATION_NAME = '") + tablename.toUpper() + QLatin1String("' " + "ORDER BY a.RDB$FIELD_POSITION")); + + while (q.next()) { + int type = q.value(1).toInt(); + bool hasScale = q.value(3).toInt() < 0; + QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale)); + if(hasScale) { + f.setLength(q.value(4).toInt()); + f.setPrecision(qAbs(q.value(3).toInt())); + } else { + f.setLength(q.value(2).toInt()); + f.setPrecision(0); + } + f.setRequired(q.value(5).toInt() > 0 ? true : false); + f.setSqlType(type); + + rec.append(f); + } + return rec; +} + +QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const +{ + QSqlIndex index(table); + if (!isOpen()) + return index; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE " + "FROM RDB$RELATION_CONSTRAINTS a, RDB$INDEX_SEGMENTS b, RDB$RELATION_FIELDS c, RDB$FIELDS d " + "WHERE a.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' " + "AND a.RDB$RELATION_NAME = '") + table.toUpper() + + QLatin1String(" 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME " + "AND c.RDB$RELATION_NAME = a.RDB$RELATION_NAME " + "AND c.RDB$FIELD_NAME = b.RDB$FIELD_NAME " + "AND d.RDB$FIELD_NAME = c.RDB$FIELD_SOURCE " + "ORDER BY b.RDB$FIELD_POSITION")); + + while (q.next()) { + QSqlField field(q.value(1).toString().simplified(), qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0)); + index.append(field); //TODO: asc? desc? + index.setName(q.value(0).toString()); + } + + return index; +} + +QString QIBaseDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + switch (field.type()) { + case QVariant::DateTime: { + QDateTime datetime = field.value().toDateTime(); + if (datetime.isValid()) + return QLatin1Char('\'') + QString::number(datetime.date().year()) + QLatin1Char('-') + + QString::number(datetime.date().month()) + QLatin1Char('-') + + QString::number(datetime.date().day()) + QLatin1Char(' ') + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + QLatin1Char('.') + + QString::number(datetime.time().msec()).rightJustified(3, QLatin1Char('0'), true) + + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + case QVariant::Time: { + QTime time = field.value().toTime(); + if (time.isValid()) + return QLatin1Char('\'') + QString::number(time.hour()) + QLatin1Char(':') + + QString::number(time.minute()) + QLatin1Char(':') + + QString::number(time.second()) + QLatin1Char('.') + + QString::number(time.msec()).rightJustified(3, QLatin1Char('0'), true) + + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + case QVariant::Date: { + QDate date = field.value().toDate(); + if (date.isValid()) + return QLatin1Char('\'') + QString::number(date.year()) + QLatin1Char('-') + + QString::number(date.month()) + QLatin1Char('-') + + QString::number(date.day()) + QLatin1Char('\''); + else + return QLatin1String("NULL"); + } + default: + return QSqlDriver::formatValue(field, trimStrings); + } +} + +QVariant QIBaseDriver::handle() const +{ + return QVariant(qRegisterMetaType<isc_db_handle>("isc_db_handle"), &d->ibase); +} + +#if defined(FB_API_VER) && FB_API_VER >= 20 +static ISC_EVENT_CALLBACK qEventCallback(char *result, ISC_USHORT length, const ISC_UCHAR *updated) +#else +static isc_callback qEventCallback(char *result, short length, char *updated) +#endif +{ + if (!updated) + return 0; + + + memcpy(result, updated, length); + qMutex()->lock(); + QIBaseDriver *driver = qBufferDriverMap()->value(result); + qMutex()->unlock(); + + // We use an asynchronous call (i.e., queued connection) because the event callback + // is executed in a different thread than the one in which the driver lives. + if (driver) + QMetaObject::invokeMethod(driver, "qHandleEventNotification", Qt::QueuedConnection, Q_ARG(void *, reinterpret_cast<void *>(result))); + + return 0; +} + +bool QIBaseDriver::subscribeToNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open."); + return false; + } + + if (d->eventBuffers.contains(name)) { + qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", + qPrintable(name)); + return false; + } + + QIBaseEventBuffer *eBuffer = new QIBaseEventBuffer; + eBuffer->subscriptionState = QIBaseEventBuffer::Starting; + eBuffer->bufferLength = isc_event_block(&eBuffer->eventBuffer, + &eBuffer->resultBuffer, + 1, + name.toLocal8Bit().constData()); + + qMutex()->lock(); + qBufferDriverMap()->insert(eBuffer->resultBuffer, this); + qMutex()->unlock(); + + d->eventBuffers.insert(name, eBuffer); + + ISC_STATUS status[20]; + isc_que_events(status, + &d->ibase, + &eBuffer->eventId, + eBuffer->bufferLength, + eBuffer->eventBuffer, +#if defined (FB_API_VER) && FB_API_VER >= 20 + (ISC_EVENT_CALLBACK)qEventCallback, +#else + (isc_callback)qEventCallback, +#endif + eBuffer->resultBuffer); + + if (status[0] == 1 && status[1]) { + setLastError(QSqlError(QString(QLatin1String("Could not subscribe to event notifications for %1.")).arg(name))); + d->eventBuffers.remove(name); + qFreeEventBuffer(eBuffer); + return false; + } + + return true; +} + +bool QIBaseDriver::unsubscribeFromNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open."); + return false; + } + + if (!d->eventBuffers.contains(name)) { + qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.", + qPrintable(name)); + return false; + } + + QIBaseEventBuffer *eBuffer = d->eventBuffers.value(name); + ISC_STATUS status[20]; + eBuffer->subscriptionState = QIBaseEventBuffer::Finished; + isc_cancel_events(status, &d->ibase, &eBuffer->eventId); + + if (status[0] == 1 && status[1]) { + setLastError(QSqlError(QString(QLatin1String("Could not unsubscribe from event notifications for %1.")).arg(name))); + return false; + } + + d->eventBuffers.remove(name); + qFreeEventBuffer(eBuffer); + + return true; +} + +QStringList QIBaseDriver::subscribedToNotificationsImplementation() const +{ + return QStringList(d->eventBuffers.keys()); +} + +void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer) +{ + QMap<QString, QIBaseEventBuffer *>::const_iterator i; + for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) { + QIBaseEventBuffer* eBuffer = i.value(); + if (reinterpret_cast<void *>(eBuffer->resultBuffer) != updatedResultBuffer) + continue; + + ISC_ULONG counts[20]; + memset(counts, 0, sizeof(counts)); + isc_event_counts(counts, eBuffer->bufferLength, eBuffer->eventBuffer, eBuffer->resultBuffer); + if (counts[0]) { + + if (eBuffer->subscriptionState == QIBaseEventBuffer::Subscribed) + emit notification(i.key()); + else if (eBuffer->subscriptionState == QIBaseEventBuffer::Starting) + eBuffer->subscriptionState = QIBaseEventBuffer::Subscribed; + + ISC_STATUS status[20]; + isc_que_events(status, + &d->ibase, + &eBuffer->eventId, + eBuffer->bufferLength, + eBuffer->eventBuffer, +#if defined (FB_API_VER) && FB_API_VER >= 20 + (ISC_EVENT_CALLBACK)qEventCallback, +#else + (isc_callback)qEventCallback, +#endif + eBuffer->resultBuffer); + if (status[0] == 1 && status[1]) { + qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'", + qPrintable(i.key())); + } + + return; + } + } +} + +QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/ibase/qsql_ibase.h b/src/sql/drivers/ibase/qsql_ibase.h new file mode 100644 index 0000000..bf883b9 --- /dev/null +++ b/src/sql/drivers/ibase/qsql_ibase.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_IBASE_H +#define QSQL_IBASE_H + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> +#include <QtSql/private/qsqlcachedresult_p.h> +#include <ibase.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +class QIBaseDriverPrivate; +class QIBaseResultPrivate; +class QIBaseDriver; + +class QIBaseResult : public QSqlCachedResult +{ + friend class QIBaseResultPrivate; + +public: + explicit QIBaseResult(const QIBaseDriver* db); + virtual ~QIBaseResult(); + + bool prepare(const QString& query); + bool exec(); + QVariant handle() const; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + +private: + QIBaseResultPrivate* d; +}; + +class QIBaseDriver : public QSqlDriver +{ + Q_OBJECT + friend class QIBaseDriverPrivate; + friend class QIBaseResultPrivate; +public: + explicit QIBaseDriver(QObject *parent = 0); + explicit QIBaseDriver(isc_db_handle connection, QObject *parent = 0); + virtual ~QIBaseDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts); + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port) { return open (db, user, password, host, port, QString()); } + void close(); + QSqlResult *createResult() const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QStringList tables(QSql::TableType) const; + + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString &table) const; + + QString formatValue(const QSqlField &field, bool trimStrings) const; + QVariant handle() const; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + +protected Q_SLOTS: + bool subscribeToNotificationImplementation(const QString &name); + bool unsubscribeFromNotificationImplementation(const QString &name); + QStringList subscribedToNotificationsImplementation() const; + +private Q_SLOTS: + void qHandleEventNotification(void* updatedResultBuffer); + +private: + QIBaseDriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER +#endif // QSQL_IBASE_H diff --git a/src/sql/drivers/mysql/qsql_mysql.cpp b/src/sql/drivers/mysql/qsql_mysql.cpp new file mode 100644 index 0000000..9b57f3c --- /dev/null +++ b/src/sql/drivers/mysql/qsql_mysql.cpp @@ -0,0 +1,1446 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_mysql.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qsqlrecord.h> +#include <qstringlist.h> +#include <qtextcodec.h> +#include <qvector.h> + +#include <qdebug.h> + +#ifdef Q_OS_WIN32 +// comment the next line out if you want to use MySQL/embedded on Win32 systems. +// note that it will crash if you don't statically link to the mysql/e library! +# define Q_NO_MYSQL_EMBEDDED +#endif + +Q_DECLARE_METATYPE(MYSQL_RES*) +Q_DECLARE_METATYPE(MYSQL*) + +#if MYSQL_VERSION_ID >= 40108 +Q_DECLARE_METATYPE(MYSQL_STMT*) +#endif + +#if MYSQL_VERSION_ID >= 40100 +# define Q_CLIENT_MULTI_STATEMENTS CLIENT_MULTI_STATEMENTS +#else +# define Q_CLIENT_MULTI_STATEMENTS 0 +#endif + +QT_BEGIN_NAMESPACE + +class QMYSQLDriverPrivate +{ +public: + QMYSQLDriverPrivate() : mysql(0), +#ifndef QT_NO_TEXTCODEC + tc(QTextCodec::codecForLocale()), +#else + tc(0), +#endif + preparedQuerys(false), preparedQuerysEnabled(false) {} + MYSQL *mysql; + QTextCodec *tc; + + bool preparedQuerys; + bool preparedQuerysEnabled; +}; + +static inline QString toUnicode(QTextCodec *tc, const char *str) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return QString::fromLatin1(str); +#else + return tc->toUnicode(str); +#endif +} + +static inline QString toUnicode(QTextCodec *tc, const char *str, int length) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return QString::fromLatin1(str, length); +#else + return tc->toUnicode(str, length); +#endif +} + +static inline QByteArray fromUnicode(QTextCodec *tc, const QString &str) +{ +#ifdef QT_NO_TEXTCODEC + Q_UNUSED(tc); + return str.toLatin1(); +#else + return tc->fromUnicode(str); +#endif +} + +static inline QVariant qDateFromString(const QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QDate()); + return QVariant(QDate::fromString(val, Qt::ISODate)); +#endif +} + +static inline QVariant qTimeFromString(const QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QTime()); + return QVariant(QTime::fromString(val, Qt::ISODate)); +#endif +} + +static inline QVariant qDateTimeFromString(QString &val) +{ +#ifdef QT_NO_DATESTRING + Q_UNUSED(val); + return QVariant(val); +#else + if (val.isEmpty()) + return QVariant(QDateTime()); + if (val.length() == 14) + // TIMESTAMPS have the format yyyyMMddhhmmss + val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10, + QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':')); + return QVariant(QDateTime::fromString(val, Qt::ISODate)); +#endif +} + +class QMYSQLResultPrivate +{ +public: + QMYSQLResultPrivate(QMYSQLDriverPrivate* dp) : d(dp), result(0), + rowsAffected(0), hasBlobs(false) +#if MYSQL_VERSION_ID >= 40108 + , stmt(0), meta(0), inBinds(0), outBinds(0) +#endif + , precisionPolicy(QSql::HighPrecision) + {} + + QMYSQLDriverPrivate* d; + MYSQL_RES *result; + MYSQL_ROW row; + + int rowsAffected; + + bool bindInValues(); + void bindBlobs(); + + bool hasBlobs; + struct QMyField + { + QMyField() + : outField(0), nullIndicator(false), bufLength(0ul), + myField(0), type(QVariant::Invalid) + {} + char *outField; + my_bool nullIndicator; + ulong bufLength; + MYSQL_FIELD *myField; + QVariant::Type type; + }; + + QVector<QMyField> fields; + +#if MYSQL_VERSION_ID >= 40108 + MYSQL_STMT* stmt; + MYSQL_RES* meta; + + MYSQL_BIND *inBinds; + MYSQL_BIND *outBinds; +#endif + QSql::NumericalPrecisionPolicy precisionPolicy; +}; + +#ifndef QT_NO_TEXTCODEC +static QTextCodec* codec(MYSQL* mysql) +{ +#if MYSQL_VERSION_ID >= 32321 + QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql)); + if (heuristicCodec) + return heuristicCodec; +#endif + return QTextCodec::codecForLocale(); +} +#endif // QT_NO_TEXTCODEC + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QMYSQLDriverPrivate* p) +{ + const char *cerr = mysql_error(p->mysql); + return QSqlError(QLatin1String("QMYSQL: ") + err, + p->tc ? toUnicode(p->tc, cerr) : QString::fromLatin1(cerr), + type, mysql_errno(p->mysql)); +} + + +static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags) +{ + QVariant::Type type; + switch (mysqltype) { + case FIELD_TYPE_TINY : + case FIELD_TYPE_SHORT : + case FIELD_TYPE_LONG : + case FIELD_TYPE_INT24 : + type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int; + break; + case FIELD_TYPE_YEAR : + type = QVariant::Int; + break; + case FIELD_TYPE_LONGLONG : + type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong; + break; + case FIELD_TYPE_FLOAT : + case FIELD_TYPE_DOUBLE : + type = QVariant::Double; + break; + case FIELD_TYPE_DATE : + type = QVariant::Date; + break; + case FIELD_TYPE_TIME : + type = QVariant::Time; + break; + case FIELD_TYPE_DATETIME : + case FIELD_TYPE_TIMESTAMP : + type = QVariant::DateTime; + break; + case FIELD_TYPE_STRING : + case FIELD_TYPE_VAR_STRING : + case FIELD_TYPE_BLOB : + case FIELD_TYPE_TINY_BLOB : + case FIELD_TYPE_MEDIUM_BLOB : + case FIELD_TYPE_LONG_BLOB : + type = (flags & BINARY_FLAG) ? QVariant::ByteArray : QVariant::String; + break; + default: + case FIELD_TYPE_ENUM : + case FIELD_TYPE_SET : + case FIELD_TYPE_DECIMAL : + type = QVariant::String; + break; + } + return type; +} + +static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc) +{ + QSqlField f(toUnicode(tc, field->name), + qDecodeMYSQLType(int(field->type), field->flags)); + f.setRequired(IS_NOT_NULL(field->flags)); + f.setLength(field->length); + f.setPrecision(field->decimals); + f.setSqlType(field->type); + f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG); + return f; +} + +#if MYSQL_VERSION_ID >= 40108 + +static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type, + MYSQL_STMT* stmt) +{ + const char *cerr = mysql_stmt_error(stmt); + return QSqlError(QLatin1String("QMYSQL3: ") + err, + QString::fromLatin1(cerr), + type, mysql_stmt_errno(stmt)); +} + +static bool qIsBlob(int t) +{ + return t == MYSQL_TYPE_TINY_BLOB + || t == MYSQL_TYPE_BLOB + || t == MYSQL_TYPE_MEDIUM_BLOB + || t == MYSQL_TYPE_LONG_BLOB; +} + +void QMYSQLResultPrivate::bindBlobs() +{ + int i; + MYSQL_FIELD *fieldInfo; + MYSQL_BIND *bind; + + for(i = 0; i < fields.count(); ++i) { + fieldInfo = fields.at(i).myField; + if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) { + bind = &inBinds[i]; + bind->buffer_length = fieldInfo->max_length; + delete[] static_cast<char*>(bind->buffer); + bind->buffer = new char[fieldInfo->max_length]; + fields[i].outField = static_cast<char*>(bind->buffer); + } + } +} + +bool QMYSQLResultPrivate::bindInValues() +{ + MYSQL_BIND *bind; + char *field; + int i = 0; + + if (!meta) + meta = mysql_stmt_result_metadata(stmt); + if (!meta) + return false; + + fields.resize(mysql_num_fields(meta)); + + inBinds = new MYSQL_BIND[fields.size()]; + memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND)); + + MYSQL_FIELD *fieldInfo; + + while((fieldInfo = mysql_fetch_field(meta))) { + QMyField &f = fields[i]; + f.myField = fieldInfo; + f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags); + if (qIsBlob(fieldInfo->type)) { + // the size of a blob-field is available as soon as we call + // mysql_stmt_store_result() + // after mysql_stmt_exec() in QMYSQLResult::exec() + fieldInfo->length = 0; + hasBlobs = true; + } else { + fieldInfo->type = MYSQL_TYPE_STRING; + } + bind = &inBinds[i]; + field = new char[fieldInfo->length + 1]; + memset(field, 0, fieldInfo->length + 1); + + bind->buffer_type = fieldInfo->type; + bind->buffer = field; + bind->buffer_length = f.bufLength = fieldInfo->length + 1; + bind->is_null = &f.nullIndicator; + bind->length = &f.bufLength; + f.outField=field; + + ++i; + } + return true; +} +#endif + +QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db) +: QSqlResult(db) +{ + d = new QMYSQLResultPrivate(db->d); +} + +QMYSQLResult::~QMYSQLResult() +{ + cleanup(); + delete d; +} + +QVariant QMYSQLResult::handle() const +{ +#if MYSQL_VERSION_ID >= 40108 + if(d->d->preparedQuerys) + return d->meta ? qVariantFromValue(d->meta) : qVariantFromValue(d->stmt); + else +#endif + return qVariantFromValue(d->result); +} + +void QMYSQLResult::cleanup() +{ + if (d->result) + mysql_free_result(d->result); + +// must iterate trough leftover result sets from multi-selects or stored procedures +// if this isn't done subsequent queries will fail with "Commands out of sync" +#if MYSQL_VERSION_ID >= 40100 + while (d->d->mysql && mysql_next_result(d->d->mysql) == 0) { + MYSQL_RES *res = mysql_store_result(d->d->mysql); + if (res) + mysql_free_result(res); + } +#endif + +#if MYSQL_VERSION_ID >= 40108 + if (d->stmt) { + if (mysql_stmt_close(d->stmt)) + qWarning("QMYSQLResult::cleanup: unable to free statement handle"); + d->stmt = 0; + } + + if (d->meta) { + mysql_free_result(d->meta); + d->meta = 0; + } + + int i; + for (i = 0; i < d->fields.count(); ++i) + delete[] d->fields[i].outField; + + if (d->outBinds) { + delete[] d->outBinds; + d->outBinds = 0; + } + + if (d->inBinds) { + delete[] d->inBinds; + d->inBinds = 0; + } +#endif + + d->hasBlobs = false; + d->fields.clear(); + d->result = NULL; + d->row = NULL; + setAt(-1); + setActive(false); + + d->d->preparedQuerys = d->d->preparedQuerysEnabled; +} + +bool QMYSQLResult::fetch(int i) +{ + if (isForwardOnly()) { // fake a forward seek + if (at() < i) { + int x = i - at(); + while (--x && fetchNext()) {}; + return fetchNext(); + } else { + return false; + } + } + if (at() == i) + return true; + if (d->d->preparedQuerys) { +#if MYSQL_VERSION_ID >= 40108 + mysql_stmt_data_seek(d->stmt, i); + + int nRC = mysql_stmt_fetch(d->stmt); + if (nRC) { +#ifdef MYSQL_DATA_TRUNCATED + if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED) +#else + if (nRC == 1) +#endif + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to fetch data"), QSqlError::StatementError, d->stmt)); + return false; + } +#else + return false; +#endif + } else { + mysql_data_seek(d->result, i); + d->row = mysql_fetch_row(d->result); + if (!d->row) + return false; + } + + setAt(i); + return true; +} + +bool QMYSQLResult::fetchNext() +{ + if (d->d->preparedQuerys) { +#if MYSQL_VERSION_ID >= 40108 + if (mysql_stmt_fetch(d->stmt)) + return false; +#else + return false; +#endif + } else { + d->row = mysql_fetch_row(d->result); + if (!d->row) + return false; + } + setAt(at() + 1); + return true; +} + +bool QMYSQLResult::fetchLast() +{ + if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries + bool success = fetchNext(); // did we move at all? + while (fetchNext()) {}; + return success; + } + + my_ulonglong numRows; + if (d->d->preparedQuerys) { +#if MYSQL_VERSION_ID >= 40108 + numRows = mysql_stmt_num_rows(d->stmt); +#else + numRows = 0; +#endif + } else { + numRows = mysql_num_rows(d->result); + } + if (at() == int(numRows)) + return true; + if (!numRows) + return false; + return fetch(numRows - 1); +} + +bool QMYSQLResult::fetchFirst() +{ + if (at() == 0) + return true; + + if (isForwardOnly()) + return (at() == QSql::BeforeFirstRow) ? fetchNext() : false; + return fetch(0); +} + +QVariant QMYSQLResult::data(int field) +{ + + if (!isSelect() || field >= d->fields.count()) { + qWarning("QMYSQLResult::data: column %d out of range", field); + return QVariant(); + } + + int fieldLength = 0; + const QMYSQLResultPrivate::QMyField &f = d->fields.at(field); + QString val; + if (d->d->preparedQuerys) { + if (f.nullIndicator) + return QVariant(f.type); + + if (f.type != QVariant::ByteArray) + val = toUnicode(d->d->tc, f.outField, f.bufLength); + } else { + if (d->row[field] == NULL) { + // NULL value + return QVariant(f.type); + } + fieldLength = mysql_fetch_lengths(d->result)[field]; + if (f.type != QVariant::ByteArray) + val = toUnicode(d->d->tc, d->row[field], fieldLength); + } + + switch(f.type) { + case QVariant::LongLong: + return QVariant(val.toLongLong()); + case QVariant::ULongLong: + return QVariant(val.toULongLong()); + case QVariant::Int: + return QVariant(val.toInt()); + case QVariant::UInt: + return QVariant(val.toUInt()); + case QVariant::Double: { + QVariant v; + bool ok=false; + switch(d->precisionPolicy) { + case QSql::LowPrecisionInt32: + v=val.toInt(&ok); + break; + case QSql::LowPrecisionInt64: + v = val.toLongLong(&ok); + break; + case QSql::LowPrecisionDouble: + v = val.toDouble(&ok); + break; + case QSql::HighPrecision: + default: + v = val; + ok = true; + break; + } + if(ok) + return v; + else + return QVariant(); + } + case QVariant::Date: + return qDateFromString(val); + case QVariant::Time: + return qTimeFromString(val); + case QVariant::DateTime: + return qDateTimeFromString(val); + case QVariant::ByteArray: { + + QByteArray ba; + if (d->d->preparedQuerys) { + ba = QByteArray(f.outField, f.bufLength); + } else { + ba = QByteArray(d->row[field], fieldLength); + } + return QVariant(ba); + } + default: + case QVariant::String: + return QVariant(val); + } + qWarning("QMYSQLResult::data: unknown data type"); + return QVariant(); +} + +bool QMYSQLResult::isNull(int field) +{ + if (d->d->preparedQuerys) + return d->fields.at(field).nullIndicator; + else + return d->row[field] == NULL; +} + +bool QMYSQLResult::reset (const QString& query) +{ + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + if(d->d->preparedQuerysEnabled && prepare(query)) { + d->d->preparedQuerys = true; + return exec(); + } + d->d->preparedQuerys = false; + + const QByteArray encQuery(fromUnicode(d->d->tc, query)); + if (mysql_real_query(d->d->mysql, encQuery.data(), encQuery.length())) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"), + QSqlError::StatementError, d->d)); + return false; + } + d->result = mysql_store_result(d->d->mysql); + if (!d->result && mysql_field_count(d->d->mysql) > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"), + QSqlError::StatementError, d->d)); + return false; + } + int numFields = mysql_field_count(d->d->mysql); + setSelect(numFields != 0); + d->fields.resize(numFields); + d->rowsAffected = mysql_affected_rows(d->d->mysql); + if (isSelect()) { + for(int i = 0; i < numFields; i++) { + MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + } + setAt(QSql::BeforeFirstRow); + } + setActive(true); + return isActive(); +} + +int QMYSQLResult::size() +{ + if (isSelect()) + if (d->d->preparedQuerys) +#if MYSQL_VERSION_ID >= 40108 + return mysql_stmt_num_rows(d->stmt); +#else + return -1; +#endif + else + return int(mysql_num_rows(d->result)); + else + return -1; +} + +int QMYSQLResult::numRowsAffected() +{ + return d->rowsAffected; +} + +QVariant QMYSQLResult::lastInsertId() const +{ + if (!isActive()) + return QVariant(); + + if (d->d->preparedQuerys) { +#if MYSQL_VERSION_ID >= 40108 + quint64 id = mysql_stmt_insert_id(d->stmt); + if (id) + return QVariant(id); +#endif + } else { + quint64 id = mysql_insert_id(d->d->mysql); + if (id) + return QVariant(id); + } + return QVariant(); +} + +QSqlRecord QMYSQLResult::record() const +{ + QSqlRecord info; + MYSQL_RES *res; + if (!isActive() || !isSelect()) + return info; + +#if MYSQL_VERSION_ID >= 40108 + res = d->d->preparedQuerys ? d->meta : d->result; +#else + res = d->result; +#endif + + if (!mysql_errno(d->d->mysql)) { + mysql_field_seek(res, 0); + MYSQL_FIELD* field = mysql_fetch_field(res); + while(field) { + info.append(qToField(field, d->d->tc)); + field = mysql_fetch_field(res); + } + } + mysql_field_seek(res, 0); + return info; +} + +bool QMYSQLResult::nextResult() +{ +#if MYSQL_VERSION_ID >= 40100 + setAt(-1); + setActive(false); + + if (d->result && isSelect()) + mysql_free_result(d->result); + d->result = 0; + setSelect(false); + + for (int i = 0; i < d->fields.count(); ++i) + delete[] d->fields[i].outField; + d->fields.clear(); + + int status = mysql_next_result(d->d->mysql); + if (status > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"), + QSqlError::StatementError, d->d)); + return false; + } else if (status == -1) { + return false; // No more result sets + } + + d->result = mysql_store_result(d->d->mysql); + int numFields = mysql_field_count(d->d->mysql); + if (!d->result && numFields > 0) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"), + QSqlError::StatementError, d->d)); + return false; + } + + setSelect(numFields > 0); + d->fields.resize(numFields); + d->rowsAffected = mysql_affected_rows(d->d->mysql); + + if (isSelect()) { + for (int i = 0; i < numFields; i++) { + MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i); + d->fields[i].type = qDecodeMYSQLType(field->type, field->flags); + } + } + + setActive(true); + return true; +#else + return false; +#endif +} + +void QMYSQLResult::virtual_hook(int id, void *data) +{ + switch (id) { + case QSqlResult::NextResult: + Q_ASSERT(data); + *static_cast<bool*>(data) = nextResult(); + break; + case QSqlResult::SetNumericalPrecision: + Q_ASSERT(data); + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + + +#if MYSQL_VERSION_ID >= 40108 + +static MYSQL_TIME *toMySqlDate(QDate date, QTime time, QVariant::Type type) +{ + Q_ASSERT(type == QVariant::Time || type == QVariant::Date + || type == QVariant::DateTime); + + MYSQL_TIME *myTime = new MYSQL_TIME; + memset(myTime, 0, sizeof(MYSQL_TIME)); + + if (type == QVariant::Time || type == QVariant::DateTime) { + myTime->hour = time.hour(); + myTime->minute = time.minute(); + myTime->second = time.second(); + myTime->second_part = time.msec(); + } + if (type == QVariant::Date || type == QVariant::DateTime) { + myTime->year = date.year(); + myTime->month = date.month(); + myTime->day = date.day(); + } + + return myTime; +} + +bool QMYSQLResult::prepare(const QString& query) +{ +#if MYSQL_VERSION_ID >= 40108 + cleanup(); + if (!d->d->preparedQuerys) + return QSqlResult::prepare(query); + + int r; + + if (query.isEmpty()) + return false; + + if (!d->stmt) + d->stmt = mysql_stmt_init(d->d->mysql); + if (!d->stmt) { + setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"), + QSqlError::StatementError, d->d)); + return false; + } + + const QByteArray encQuery(fromUnicode(d->d->tc, query)); + r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length()); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to prepare statement"), QSqlError::StatementError, d->stmt)); + cleanup(); + return false; + } + + if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues + d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)]; + } + + setSelect(d->bindInValues()); + return true; +#else + return false; +#endif +} + +bool QMYSQLResult::exec() +{ + if (!d->d->preparedQuerys) + return QSqlResult::exec(); + if (!d->stmt) + return false; + + int r = 0; + MYSQL_BIND* currBind; + QVector<MYSQL_TIME *> timeVector; + QVector<QByteArray> stringVector; + QVector<my_bool> nullVector; + + const QVector<QVariant> values = boundValues(); + + r = mysql_stmt_reset(d->stmt); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to reset statement"), QSqlError::StatementError, d->stmt)); + return false; + } + + if (mysql_stmt_param_count(d->stmt) > 0 && + mysql_stmt_param_count(d->stmt) == (uint)values.count()) { + + nullVector.resize(values.count()); + for (int i = 0; i < values.count(); ++i) { + const QVariant &val = boundValues().at(i); + void *data = const_cast<void *>(val.constData()); + + currBind = &d->outBinds[i]; + + nullVector[i] = static_cast<my_bool>(val.isNull()); + currBind->is_null = &nullVector[i]; + currBind->length = 0; + + switch (val.type()) { + case QVariant::ByteArray: + currBind->buffer_type = MYSQL_TYPE_BLOB; + currBind->buffer = const_cast<char *>(val.toByteArray().constData()); + currBind->buffer_length = val.toByteArray().size(); + break; + + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: { + MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.type()); + timeVector.append(myTime); + + currBind->buffer = myTime; + switch(val.type()) { + case QVariant::Time: + currBind->buffer_type = MYSQL_TYPE_TIME; + myTime->time_type = MYSQL_TIMESTAMP_TIME; + break; + case QVariant::Date: + currBind->buffer_type = MYSQL_TYPE_DATE; + myTime->time_type = MYSQL_TIMESTAMP_DATE; + break; + case QVariant::DateTime: + currBind->buffer_type = MYSQL_TYPE_DATETIME; + myTime->time_type = MYSQL_TIMESTAMP_DATETIME; + break; + default: + break; + } + currBind->buffer_length = sizeof(MYSQL_TIME); + currBind->length = 0; + break; } + case QVariant::UInt: + case QVariant::Int: + case QVariant::Bool: + currBind->buffer_type = MYSQL_TYPE_LONG; + currBind->buffer = data; + currBind->buffer_length = sizeof(int); + currBind->is_unsigned = (val.type() != QVariant::Int); + break; + case QVariant::Double: + currBind->buffer_type = MYSQL_TYPE_DOUBLE; + currBind->buffer = data; + currBind->buffer_length = sizeof(double); + currBind->is_unsigned = 0; + break; + case QVariant::LongLong: + case QVariant::ULongLong: + currBind->buffer_type = MYSQL_TYPE_LONGLONG; + currBind->buffer = data; + currBind->buffer_length = sizeof(qint64); + currBind->is_unsigned = (val.type() == QVariant::ULongLong); + break; + case QVariant::String: + default: { + QByteArray ba = fromUnicode(d->d->tc, val.toString()); + stringVector.append(ba); + currBind->buffer_type = MYSQL_TYPE_STRING; + currBind->buffer = const_cast<char *>(ba.constData()); + currBind->buffer_length = ba.length(); + currBind->is_unsigned = 0; + break; } + } + } + + r = mysql_stmt_bind_param(d->stmt, d->outBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind value"), QSqlError::StatementError, d->stmt)); + qDeleteAll(timeVector); + return false; + } + } + r = mysql_stmt_execute(d->stmt); + + qDeleteAll(timeVector); + + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to execute statement"), QSqlError::StatementError, d->stmt)); + return false; + } + //if there is meta-data there is also data + setSelect(d->meta); + + d->rowsAffected = mysql_stmt_affected_rows(d->stmt); + + if (isSelect()) { + my_bool update_max_length = true; + + r = mysql_stmt_bind_result(d->stmt, d->inBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind outvalues"), QSqlError::StatementError, d->stmt)); + return false; + } + if (d->hasBlobs) + mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length); + + r = mysql_stmt_store_result(d->stmt); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to store statement results"), QSqlError::StatementError, d->stmt)); + return false; + } + + if (d->hasBlobs) { + // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes + // when called without a preceding call to mysql_stmt_bind_result() + // in versions < 4.1.8 + d->bindBlobs(); + r = mysql_stmt_bind_result(d->stmt, d->inBinds); + if (r != 0) { + setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult", + "Unable to bind outvalues"), QSqlError::StatementError, d->stmt)); + return false; + } + } + setAt(QSql::BeforeFirstRow); + } + setActive(true); + return true; +} +#endif +///////////////////////////////////////////////////////// + +static int qMySqlConnectionCount = 0; +static bool qMySqlInitHandledByUser = false; + +static void qLibraryInit() +{ +#ifndef Q_NO_MYSQL_EMBEDDED +# if MYSQL_VERSION_ID >= 40000 + if (qMySqlInitHandledByUser || qMySqlConnectionCount > 1) + return; + +# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003 + if (mysql_library_init(0, 0, 0)) { +# else + if (mysql_server_init(0, 0, 0)) { +# endif + qWarning("QMYSQLDriver::qServerInit: unable to start server."); + } +# endif // MYSQL_VERSION_ID +#endif // Q_NO_MYSQL_EMBEDDED +} + +static void qLibraryEnd() +{ +#ifndef Q_NO_MYSQL_EMBEDDED +# if MYSQL_VERSION_ID > 40000 +# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +# else + mysql_server_end(); +# endif +# endif +#endif +} + +QMYSQLDriver::QMYSQLDriver(QObject * parent) + : QSqlDriver(parent) +{ + init(); + qLibraryInit(); +} + +/*! + Create a driver instance with the open connection handle, \a con. + The instance's parent (owner) is \a parent. +*/ + +QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent) + : QSqlDriver(parent) +{ + init(); + if (con) { + d->mysql = (MYSQL *) con; +#ifndef QT_NO_TEXTCODEC + d->tc = codec(con); +#endif + setOpen(true); + setOpenError(false); + if (qMySqlConnectionCount == 1) + qMySqlInitHandledByUser = true; + } else { + qLibraryInit(); + } +} + +void QMYSQLDriver::init() +{ + d = new QMYSQLDriverPrivate(); + d->mysql = 0; + qMySqlConnectionCount++; +} + +QMYSQLDriver::~QMYSQLDriver() +{ + qMySqlConnectionCount--; + if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser) + qLibraryEnd(); + delete d; +} + +bool QMYSQLDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: +// CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34 +#ifdef CLIENT_TRANSACTIONS + if (d->mysql) { + if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS) + return true; + } +#endif + return false; + case NamedPlaceholders: + case BatchOperations: + case SimpleLocking: + case LowPrecisionNumbers: + case EventNotifications: + case FinishQuery: + return false; + case QuerySize: + case BLOB: + case LastInsertId: + case Unicode: + return true; + case PreparedQueries: + case PositionalPlaceholders: +#if MYSQL_VERSION_ID >= 40108 + return d->preparedQuerysEnabled; +#else + return false; +#endif + case MultipleResultSets: +#if MYSQL_VERSION_ID >= 40100 + return true; +#else + return false; +#endif + } + return false; +} + +static void setOptionFlag(uint &optionFlags, const QString &opt) +{ + if (opt == QLatin1String("CLIENT_COMPRESS")) + optionFlags |= CLIENT_COMPRESS; + else if (opt == QLatin1String("CLIENT_FOUND_ROWS")) + optionFlags |= CLIENT_FOUND_ROWS; + else if (opt == QLatin1String("CLIENT_IGNORE_SPACE")) + optionFlags |= CLIENT_IGNORE_SPACE; + else if (opt == QLatin1String("CLIENT_INTERACTIVE")) + optionFlags |= CLIENT_INTERACTIVE; + else if (opt == QLatin1String("CLIENT_NO_SCHEMA")) + optionFlags |= CLIENT_NO_SCHEMA; + else if (opt == QLatin1String("CLIENT_ODBC")) + optionFlags |= CLIENT_ODBC; + else if (opt == QLatin1String("CLIENT_SSL")) + optionFlags |= CLIENT_SSL; + else + qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData()); +} + +bool QMYSQLDriver::open(const QString& db, + const QString& user, + const QString& password, + const QString& host, + int port, + const QString& connOpts) +{ + if (isOpen()) + close(); + + /* This is a hack to get MySQL's stored procedure support working. + Since a stored procedure _may_ return multiple result sets, + we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_ + stored procedure call will fail. + */ + unsigned int optionFlags = Q_CLIENT_MULTI_STATEMENTS; + const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts)); + QString unixSocket; + + // extract the real options from the string + for (int i = 0; i < opts.count(); ++i) { + QString tmp(opts.at(i).simplified()); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) { + QString val = tmp.mid(idx + 1).simplified(); + QString opt = tmp.left(idx).simplified(); + if (opt == QLatin1String("UNIX_SOCKET")) + unixSocket = val; + else if (val == QLatin1String("TRUE") || val == QLatin1String("1")) + setOptionFlag(optionFlags, tmp.left(idx).simplified()); + else + qWarning("QMYSQLDriver::open: Illegal connect option value '%s'", + tmp.toLocal8Bit().constData()); + } else { + setOptionFlag(optionFlags, tmp); + } + } + + if ((d->mysql = mysql_init((MYSQL*) 0)) && + mysql_real_connect(d->mysql, + host.isNull() ? static_cast<const char *>(0) + : host.toLocal8Bit().constData(), + user.isNull() ? static_cast<const char *>(0) + : user.toLocal8Bit().constData(), + password.isNull() ? static_cast<const char *>(0) + : password.toLocal8Bit().constData(), + db.isNull() ? static_cast<const char *>(0) + : db.toLocal8Bit().constData(), + (port > -1) ? port : 0, + unixSocket.isNull() ? static_cast<const char *>(0) + : unixSocket.toLocal8Bit().constData(), + optionFlags)) + { + if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) { + setLastError(qMakeError(tr("Unable to open database '") + db + + QLatin1Char('\''), QSqlError::ConnectionError, d)); + mysql_close(d->mysql); + setOpenError(true); + return false; + } + } else { + setLastError(qMakeError(tr("Unable to connect"), + QSqlError::ConnectionError, d)); + mysql_close(d->mysql); + d->mysql = NULL; + setOpenError(true); + return false; + } + +#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007 + // force the communication to be utf8 + mysql_set_character_set(d->mysql, "utf8"); +#endif +#ifndef QT_NO_TEXTCODEC + d->tc = codec(d->mysql); +#endif + +#if MYSQL_VERSION_ID >= 40108 + d->preparedQuerysEnabled = mysql_get_client_version() >= 40108 + && mysql_get_server_version(d->mysql) >= 40100; +#else + d->preparedQuerysEnabled = false; +#endif + + setOpen(true); + setOpenError(false); + return true; +} + +void QMYSQLDriver::close() +{ + if (isOpen()) { + mysql_close(d->mysql); + d->mysql = NULL; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QMYSQLDriver::createResult() const +{ + return new QMYSQLResult(this); +} + +QStringList QMYSQLDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + if (!(type & QSql::Tables)) + return tl; + + MYSQL_RES* tableRes = mysql_list_tables(d->mysql, NULL); + MYSQL_ROW row; + int i = 0; + while (tableRes) { + mysql_data_seek(tableRes, i); + row = mysql_fetch_row(tableRes); + if (!row) + break; + tl.append(toUnicode(d->tc, row[0])); + i++; + } + mysql_free_result(tableRes); + return tl; +} + +QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const +{ + QSqlIndex idx; + bool prepQ; + if (!isOpen()) + return idx; + + prepQ = d->preparedQuerys; + d->preparedQuerys = false; + + QSqlQuery i(createResult()); + QString stmt(QLatin1String("show index from %1;")); + QSqlRecord fil = record(tablename); + i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName))); + while (i.isActive() && i.next()) { + if (i.value(2).toString() == QLatin1String("PRIMARY")) { + idx.append(fil.field(i.value(4).toString())); + idx.setCursorName(i.value(0).toString()); + idx.setName(i.value(2).toString()); + } + } + + d->preparedQuerys = prepQ; + return idx; +} + +QSqlRecord QMYSQLDriver::record(const QString& tablename) const +{ + QSqlRecord info; + if (!isOpen()) + return info; + MYSQL_RES* r = mysql_list_fields(d->mysql, tablename.toLocal8Bit().constData(), 0); + if (!r) { + return info; + } + MYSQL_FIELD* field; + while ((field = mysql_fetch_field(r))) + info.append(qToField(field, d->tc)); + mysql_free_result(r); + return info; +} + +QVariant QMYSQLDriver::handle() const +{ + return qVariantFromValue(d->mysql); +} + +bool QMYSQLDriver::beginTransaction() +{ +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::beginTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "BEGIN WORK")) { + setLastError(qMakeError(tr("Unable to begin transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QMYSQLDriver::commitTransaction() +{ +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::commitTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "COMMIT")) { + setLastError(qMakeError(tr("Unable to commit transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QMYSQLDriver::rollbackTransaction() +{ +#ifndef CLIENT_TRANSACTIONS + return false; +#endif + if (!isOpen()) { + qWarning("QMYSQLDriver::rollbackTransaction: Database not open"); + return false; + } + if (mysql_query(d->mysql, "ROLLBACK")) { + setLastError(qMakeError(tr("Unable to rollback transaction"), + QSqlError::StatementError, d)); + return false; + } + return true; +} + +QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else { + switch(field.type()) { + case QVariant::String: + // Escape '\' characters + r = QSqlDriver::formatValue(field, trimStrings); + r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + break; + case QVariant::ByteArray: + if (isOpen()) { + const QByteArray ba = field.value().toByteArray(); + // buffer has to be at least length*2+1 bytes + char* buffer = new char[ba.size() * 2 + 1]; + int escapedSize = int(mysql_real_escape_string(d->mysql, buffer, + ba.data(), ba.size())); + r.reserve(escapedSize + 3); + r.append(QLatin1Char('\'')).append(toUnicode(d->tc, buffer)).append(QLatin1Char('\'')); + delete[] buffer; + break; + } else { + qWarning("QMYSQLDriver::formatValue: Database not open"); + } + // fall through + default: + r = QSqlDriver::formatValue(field, trimStrings); + } + } + return r; +} + +QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('`')) && identifier.right(1) != QString(QLatin1Char('`')) ) { + res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); + res.replace(QLatin1Char('.'), QLatin1String("`.`")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/mysql/qsql_mysql.h b/src/sql/drivers/mysql/qsql_mysql.h new file mode 100644 index 0000000..97aa346 --- /dev/null +++ b/src/sql/drivers/mysql/qsql_mysql.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_MYSQL_H +#define QSQL_MYSQL_H + +#include <QtSql/qsqldriver.h> +#include <QtSql/qsqlresult.h> + +#if defined (Q_OS_WIN32) +#include <QtCore/qt_windows.h> +#endif + +#include <mysql.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_MYSQL +#else +#define Q_EXPORT_SQLDRIVER_MYSQL Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QMYSQLDriverPrivate; +class QMYSQLResultPrivate; +class QMYSQLDriver; +class QSqlRecordInfo; + +class QMYSQLResult : public QSqlResult +{ + friend class QMYSQLDriver; +public: + explicit QMYSQLResult(const QMYSQLDriver* db); + ~QMYSQLResult(); + + QVariant handle() const; +protected: + void cleanup(); + bool fetch(int i); + bool fetchNext(); + bool fetchLast(); + bool fetchFirst(); + QVariant data(int field); + bool isNull(int field); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QVariant lastInsertId() const; + QSqlRecord record() const; + void virtual_hook(int id, void *data); + bool nextResult(); + +#if MYSQL_VERSION_ID >= 40108 + bool prepare(const QString& stmt); + bool exec(); +#endif +private: + QMYSQLResultPrivate* d; +}; + +class Q_EXPORT_SQLDRIVER_MYSQL QMYSQLDriver : public QSqlDriver +{ + Q_OBJECT + friend class QMYSQLResult; +public: + explicit QMYSQLDriver(QObject *parent=0); + explicit QMYSQLDriver(MYSQL *con, QObject * parent=0); + ~QMYSQLDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts); + void close(); + QSqlResult *createResult() const; + QStringList tables(QSql::TableType) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QSqlRecord record(const QString& tablename) const; + QString formatValue(const QSqlField &field, + bool trimStrings) const; + QVariant handle() const; + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); +private: + void init(); + QMYSQLDriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_MYSQL_H diff --git a/src/sql/drivers/oci/qsql_oci.cpp b/src/sql/drivers/oci/qsql_oci.cpp new file mode 100644 index 0000000..7017d6c --- /dev/null +++ b/src/sql/drivers/oci/qsql_oci.cpp @@ -0,0 +1,2428 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_oci.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qmetatype.h> +#include <qregexp.h> +#include <qshareddata.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> +#include <qdebug.h> + +#include <oci.h> +#ifdef max +#undef max +#endif +#ifdef min +#undef min +#endif + +#include <stdlib.h> + +#define QOCI_DYNAMIC_CHUNK_SIZE 65535 +#define QOCI_PREFETCH_MEM 10240 + +// setting this define will allow using a query from a different +// thread than its database connection. +// warning - this is not fully tested and can lead to race conditions +#define QOCI_THREADED + +//#define QOCI_DEBUG + +Q_DECLARE_METATYPE(OCIEnv*) +Q_DECLARE_METATYPE(OCIStmt*) + +QT_BEGIN_NAMESPACE + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +enum { QOCIEncoding = 2002 }; // AL16UTF16LE +#else +enum { QOCIEncoding = 2000 }; // AL16UTF16 +#endif + +static const ub1 CSID_NCHAR = SQLCS_NCHAR; +static const ub2 qOraCharset = OCI_UCS2ID; + +typedef QVarLengthArray<sb2, 32> IndicatorArray; +typedef QVarLengthArray<ub2, 32> SizeArray; + +static QByteArray qMakeOraDate(const QDateTime& dt); +static QDateTime qMakeDate(const char* oraDate); +static QString qOraWarn(OCIError *err, int *errorCode = 0); +#ifndef Q_CC_SUN +static // for some reason, Sun CC can't use qOraWarning when it's declared static +#endif +void qOraWarning(const char* msg, OCIError *err); +static QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err); + +class QOCIRowId: public QSharedData +{ +public: + QOCIRowId(OCIEnv *env); + ~QOCIRowId(); + + OCIRowid *id; + +private: + QOCIRowId(const QOCIRowId &other): QSharedData(other) { Q_ASSERT(false); } +}; + +QOCIRowId::QOCIRowId(OCIEnv *env) + : id(0) +{ + OCIDescriptorAlloc (env, reinterpret_cast<dvoid **>(&id), + OCI_DTYPE_ROWID, 0, 0); +} + +QOCIRowId::~QOCIRowId() +{ + if (id) + OCIDescriptorFree(id, OCI_DTYPE_ROWID); +} + +typedef QSharedDataPointer<QOCIRowId> QOCIRowIdPointer; +QT_BEGIN_INCLUDE_NAMESPACE +Q_DECLARE_METATYPE(QOCIRowIdPointer) +QT_END_INCLUDE_NAMESPACE + +class QOCICols; + +struct QOCIResultPrivate +{ + QOCIResultPrivate(QOCIResult *result, const QOCIDriverPrivate *driver); + ~QOCIResultPrivate(); + + QOCICols *cols; + QOCIResult *q; + OCIEnv *env; + OCIError *err; + OCISvcCtx *&svc; + OCIStmt *sql; + bool transaction; + int serverVersion; + int prefetchRows, prefetchMem; + QSql::NumericalPrecisionPolicy precisionPolicy; + + void setCharset(OCIBind* hbnd); + void setStatementAttributes(); + int bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage); + int bindValues(QVector<QVariant> &values, IndicatorArray &indicators, SizeArray &tmpSizes, + QList<QByteArray> &tmpStorage); + void outValues(QVector<QVariant> &values, IndicatorArray &indicators, + QList<QByteArray> &tmpStorage); + inline bool isOutValue(int i) const + { return q->bindValueType(i) & QSql::Out; } + inline bool isBinaryValue(int i) const + { return q->bindValueType(i) & QSql::Binary; } +}; + +void QOCIResultPrivate::setStatementAttributes() +{ + Q_ASSERT(sql); + + int r = 0; + + if (prefetchRows >= 0) { + r = OCIAttrSet(sql, + OCI_HTYPE_STMT, + &prefetchRows, + 0, + OCI_ATTR_PREFETCH_ROWS, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setStatementAttributes:" + " Couldn't set OCI_ATTR_PREFETCH_ROWS: ", err); + } + if (prefetchMem >= 0) { + r = OCIAttrSet(sql, + OCI_HTYPE_STMT, + &prefetchMem, + 0, + OCI_ATTR_PREFETCH_MEMORY, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setStatementAttributes:" + " Couldn't set OCI_ATTR_PREFETCH_MEMORY: ", err); + } +} + +void QOCIResultPrivate::setCharset(OCIBind* hbnd) +{ + int r = 0; + + Q_ASSERT(hbnd); + + r = OCIAttrSet(hbnd, + OCI_HTYPE_BIND, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast<void *>(static_cast<const void *>(&qOraCharset)), + 0, + OCI_ATTR_CHARSET_ID, + err); + if (r != 0) + qOraWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_ID: ", err); +} + +int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage) +{ + int r = OCI_SUCCESS; + void *data = const_cast<void *>(val.constData()); + + switch (val.type()) { + case QVariant::ByteArray: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + isOutValue(pos) + ? const_cast<char *>(reinterpret_cast<QByteArray *>(data)->constData()) + : reinterpret_cast<QByteArray *>(data)->data(), + reinterpret_cast<QByteArray *>(data)->size(), + SQLT_BIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: { + QByteArray ba = qMakeOraDate(val.toDateTime()); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_DAT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + tmpStorage.append(ba); + break; } + case QVariant::Int: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(int), + SQLT_INT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::UInt: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(uint), + SQLT_UIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::Double: + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast<void *>(data), + sizeof(double), + SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + case QVariant::UserType: + if (qVariantCanConvert<QOCIRowIdPointer>(val) && !isOutValue(pos)) { + // use a const pointer to prevent a detach + const QOCIRowIdPointer rptr = qVariantValue<QOCIRowIdPointer>(val); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // it's an IN value, so const_cast is ok + const_cast<OCIRowid **>(&rptr->id), + -1, + SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + } else { + qWarning("Unknown bind variable"); + r = OCI_ERROR; + } + break; + case QVariant::String: { + const QString s = val.toString(); + if (isBinaryValue(pos)) { + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + const_cast<ushort *>(s.utf16()), + s.length() * sizeof(QChar), + SQLT_LNG, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + break; + } else if (!isOutValue(pos)) { + // don't detach the string + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + // safe since oracle doesn't touch OUT values + const_cast<ushort *>(s.utf16()), + (s.length() + 1) * sizeof(QChar), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + if (r == OCI_SUCCESS) + setCharset(*hbnd); + break; + } + } // fall through for OUT values + default: { + const QString s = val.toString(); + // create a deep-copy + QByteArray ba(reinterpret_cast<const char *>(s.utf16()), (s.length() + 1) * sizeof(QChar)); + if (isOutValue(pos)) { + ba.reserve((s.capacity() + 1) * sizeof(QChar)); + *tmpSize = ba.size(); + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.capacity(), + SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT); + } else { + r = OCIBindByPos(sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT); + } + if (r == OCI_SUCCESS) + setCharset(*hbnd); + tmpStorage.append(ba); + break; + } // default case + } // switch + if (r != OCI_SUCCESS) + qOraWarning("QOCIResultPrivate::bindValue:", err); + return r; +} + +int QOCIResultPrivate::bindValues(QVector<QVariant> &values, IndicatorArray &indicators, + SizeArray &tmpSizes, QList<QByteArray> &tmpStorage) +{ + int r = OCI_SUCCESS; + for (int i = 0; i < values.count(); ++i) { + if (isOutValue(i)) + values[i].detach(); + const QVariant &val = values.at(i); + + OCIBind * hbnd = 0; // Oracle handles these automatically + sb2 *indPtr = &indicators[i]; + *indPtr = val.isNull() ? -1 : 0; + + bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage); + } + return r; +} + +// will assign out value and remove its temp storage. +static void qOraOutValue(QVariant &value, QList<QByteArray> &storage) +{ + switch (value.type()) { + case QVariant::Time: + value = qMakeDate(storage.takeFirst()).time(); + break; + case QVariant::Date: + value = qMakeDate(storage.takeFirst()).date(); + break; + case QVariant::DateTime: + value = qMakeDate(storage.takeFirst()); + break; + case QVariant::String: + value = QString::fromUtf16( + reinterpret_cast<const ushort *>(storage.takeFirst().constData())); + break; + default: + break; //nothing + } +} + +void QOCIResultPrivate::outValues(QVector<QVariant> &values, IndicatorArray &indicators, + QList<QByteArray> &tmpStorage) +{ + for (int i = 0; i < values.count(); ++i) { + + if (!isOutValue(i)) + continue; + + qOraOutValue(values[i], tmpStorage); + + QVariant::Type typ = values.at(i).type(); + if (indicators[i] == -1) // NULL + values[i] = QVariant(typ); + else + values[i] = QVariant(typ, values.at(i).constData()); + } +} + + +struct QOCIDriverPrivate +{ + QOCIDriverPrivate(); + + OCIEnv *env; + OCISvcCtx *svc; + OCIServer *srvhp; + OCISession *authp; + OCIError *err; + bool transaction; + int serverVersion; + ub4 prefetchRows; + ub2 prefetchMem; + QSql::NumericalPrecisionPolicy precisionPolicy; + QString user; + + void allocErrorHandle(); +}; + +QOCIDriverPrivate::QOCIDriverPrivate() + : env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false), serverVersion(-1), + prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM), precisionPolicy(QSql::HighPrecision) +{ +} + +void QOCIDriverPrivate::allocErrorHandle() +{ + int r = OCIHandleAlloc(env, + reinterpret_cast<void **>(&err), + OCI_HTYPE_ERROR, + 0, + 0); + if (r != 0) + qWarning("QOCIDriver: unable to allocate error handle"); +} + +struct OraFieldInfo +{ + QString name; + QVariant::Type type; + ub1 oraIsNull; + ub4 oraType; + sb1 oraScale; + ub4 oraLength; // size in bytes + ub4 oraFieldLength; // amount of characters + sb2 oraPrecision; +}; + +QString qOraWarn(OCIError *err, int *errorCode) +{ + sb4 errcode; + text errbuf[1024]; + errbuf[0] = 0; + errbuf[1] = 0; + + OCIErrorGet(err, + 1, + 0, + &errcode, + errbuf, + sizeof(errbuf), + OCI_HTYPE_ERROR); + if (errorCode) + *errorCode = errcode; + return QString::fromUtf16(reinterpret_cast<const ushort *>(errbuf)); +} + +void qOraWarning(const char* msg, OCIError *err) +{ +#ifdef QOCI_DEBUG + qWarning("%s %s", msg, qPrintable(qOraWarn(err))); +#else + Q_UNUSED(msg); + Q_UNUSED(err); +#endif +} + +static int qOraErrorNumber(OCIError *err) +{ + sb4 errcode; + OCIErrorGet(err, + 1, + 0, + &errcode, + 0, + 0, + OCI_HTYPE_ERROR); + return errcode; +} + +QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err) +{ + int errorCode = 0; + const QString oraErrorString = qOraWarn(err, &errorCode); + return QSqlError(errString, oraErrorString, type, errorCode); +} + +QVariant::Type qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) +{ + QVariant::Type type = QVariant::Invalid; + if (ocitype == QLatin1String("VARCHAR2") || ocitype == QLatin1String("VARCHAR") + || ocitype.startsWith(QLatin1String("INTERVAL")) + || ocitype == QLatin1String("CHAR") || ocitype == QLatin1String("NVARCHAR2") + || ocitype == QLatin1String("NCHAR")) + type = QVariant::String; + else if (ocitype == QLatin1String("NUMBER") + || ocitype == QLatin1String("FLOAT") + || ocitype == QLatin1String("BINARY_FLOAT") + || ocitype == QLatin1String("BINARY_DOUBLE")) { + switch(precisionPolicy) { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + } + else if (ocitype == QLatin1String("LONG") || ocitype == QLatin1String("NCLOB") + || ocitype == QLatin1String("CLOB")) + type = QVariant::ByteArray; + else if (ocitype == QLatin1String("RAW") || ocitype == QLatin1String("LONG RAW") + || ocitype == QLatin1String("ROWID") || ocitype == QLatin1String("BLOB") + || ocitype == QLatin1String("CFILE") || ocitype == QLatin1String("BFILE")) + type = QVariant::ByteArray; + else if (ocitype == QLatin1String("DATE") || ocitype.startsWith(QLatin1String("TIME"))) + type = QVariant::DateTime; + else if (ocitype == QLatin1String("UNDEFINED")) + type = QVariant::Invalid; + if (type == QVariant::Invalid) + qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData()); + return type; +} + +QVariant::Type qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPolicy) +{ + QVariant::Type type = QVariant::Invalid; + switch (ocitype) { + case SQLT_STR: + case SQLT_VST: + case SQLT_CHR: + case SQLT_AFC: + case SQLT_VCS: + case SQLT_AVC: + case SQLT_RDD: + case SQLT_LNG: +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: +#endif + type = QVariant::String; + break; + case SQLT_INT: + type = QVariant::Int; + break; + case SQLT_FLT: + case SQLT_NUM: + case SQLT_VNU: + case SQLT_UIN: + switch(precisionPolicy) { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + break; + case SQLT_VBI: + case SQLT_BIN: + case SQLT_LBI: + case SQLT_LVC: + case SQLT_LVB: + case SQLT_BLOB: + case SQLT_FILE: + case SQLT_NTY: + case SQLT_REF: + case SQLT_RID: + case SQLT_CLOB: + type = QVariant::ByteArray; + break; + case SQLT_DAT: + case SQLT_ODT: +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + case SQLT_TIMESTAMP_TZ: + case SQLT_TIMESTAMP_LTZ: +#endif + type = QVariant::DateTime; + break; + default: + type = QVariant::Invalid; + qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype); + break; + } + return type; +} + +static QSqlField qFromOraInf(const OraFieldInfo &ofi) +{ + QSqlField f(ofi.name, ofi.type); + f.setRequired(ofi.oraIsNull == 0); + + if (ofi.type == QVariant::String) + f.setLength(ofi.oraFieldLength); + else + f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision)); + + f.setPrecision(ofi.oraScale); + f.setSqlType(int(ofi.oraType)); + return f; +} + +static OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) +{ + OraFieldInfo ofi; + ub2 colType(0); + text *colName = 0; + ub4 colNameLen(0); + sb1 colScale(0); + ub2 colLength(0); + ub2 colFieldLength(0); + sb2 colPrecision(0); + ub1 colIsNull(0); + int r(0); + QVariant::Type type(QVariant::Invalid); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colName, + &colNameLen, + OCI_ATTR_NAME, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colLength, + 0, + OCI_ATTR_DATA_SIZE, /* in bytes */ + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + +#ifdef OCI_ATTR_CHAR_SIZE + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colFieldLength, + 0, + OCI_ATTR_CHAR_SIZE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); +#else + // for Oracle8. + colFieldLength = colLength; +#endif + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colPrecision, + 0, + OCI_ATTR_PRECISION, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colScale, + 0, + OCI_ATTR_SCALE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + r = OCIAttrGet(param, + OCI_DTYPE_PARAM, + &colIsNull, + 0, + OCI_ATTR_IS_NULL, + p->err); + if (r != 0) + qOraWarning("qMakeOraField:", p->err); + + type = qDecodeOCIType(colType, p->precisionPolicy); + + if (type == QVariant::Int) { + if (colLength == 22 && colPrecision == 0 && colScale == 0) + type = QVariant::String; + if (colScale > 0) + type = QVariant::String; + } + + // bind as double if the precision policy asks for it + if (((colType == SQLT_FLT) || (colType == SQLT_NUM)) + && (p->precisionPolicy == QSql::LowPrecisionDouble)) { + type = QVariant::Double; + } + + // bind as int32 or int64 if the precision policy asks for it + if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN) + || (colType == SQLT_INT)) { + if (p->precisionPolicy == QSql::LowPrecisionInt64) + type = QVariant::LongLong; + else if (p->precisionPolicy == QSql::LowPrecisionInt32) + type = QVariant::Int; + } + + if (colType == SQLT_BLOB) + colLength = 0; + + // colNameLen is length in bytes + ofi.name = QString(reinterpret_cast<const QChar*>(colName), colNameLen / 2); + ofi.type = type; + ofi.oraType = colType; + ofi.oraFieldLength = colFieldLength; + ofi.oraLength = colLength; + ofi.oraScale = colScale; + ofi.oraPrecision = colPrecision; + ofi.oraIsNull = colIsNull; + + return ofi; +} + + +/*! + \internal + + Convert QDateTime to the internal Oracle DATE format NB! + It does not handle BCE dates. +*/ +QByteArray qMakeOraDate(const QDateTime& dt) +{ + QByteArray ba; + ba.resize(7); + int year = dt.date().year(); + ba[0]= (year / 100) + 100; // century + ba[1]= (year % 100) + 100; // year + ba[2]= dt.date().month(); + ba[3]= dt.date().day(); + ba[4]= dt.time().hour() + 1; + ba[5]= dt.time().minute() + 1; + ba[6]= dt.time().second() + 1; + return ba; +} + +QDateTime qMakeDate(const char* oraDate) +{ + int century = oraDate[0]; + if(century >= 100){ + int year = uchar(oraDate[1]); + year = ((century-100)*100) + (year-100); + int month = oraDate[2]; + int day = oraDate[3]; + int hour = oraDate[4] - 1; + int min = oraDate[5] - 1; + int sec = oraDate[6] - 1; + return QDateTime(QDate(year,month,day), QTime(hour,min,sec)); + } + return QDateTime(); +} + +class QOCICols +{ +public: + QOCICols(int size, QOCIResultPrivate* dp); + ~QOCICols(); + void setCharset(OCIDefine* dfn); + int readPiecewise(QVector<QVariant> &values, int index = 0); + int readLOBs(QVector<QVariant> &values, int index = 0); + int fieldFromDefine(OCIDefine* d); + void getValues(QVector<QVariant> &v, int index); + inline int size() { return fieldInf.size(); } + static bool execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind); + + QSqlRecord rec; + +private: + char* create(int position, int size); + OCILobLocator ** createLobLocator(int position, OCIEnv* env); + + class OraFieldInf + { + public: + OraFieldInf(): data(0), len(0), ind(0), typ(QVariant::Invalid), oraType(0), def(0), lob(0) + {} + ~OraFieldInf(); + char *data; + int len; + sb2 ind; + QVariant::Type typ; + ub4 oraType; + OCIDefine *def; + OCILobLocator *lob; + }; + + QVector<OraFieldInf> fieldInf; + const QOCIResultPrivate *const d; +}; + +QOCICols::OraFieldInf::~OraFieldInf() +{ + delete [] data; + if (lob) { + int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB); + if (r != 0) + qWarning("QOCICols: Cannot free LOB descriptor"); + } +} + +QOCICols::QOCICols(int size, QOCIResultPrivate* dp) + : fieldInf(size), d(dp) +{ + ub4 dataSize = 0; + OCIDefine* dfn = 0; + int r; + + OCIParam* param = 0; + sb4 parmStatus = 0; + ub4 count = 1; + int idx = 0; + parmStatus = OCIParamGet(d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast<void **>(¶m), + count); + + while (parmStatus == OCI_SUCCESS) { + OraFieldInfo ofi = qMakeOraField(d, param); + if (ofi.oraType == SQLT_RDD) + dataSize = 50; +#ifdef SQLT_INTERVAL_YM +#ifdef SQLT_INTERVAL_DS + else if (ofi.oraType == SQLT_INTERVAL_YM || ofi.oraType == SQLT_INTERVAL_DS) + // since we are binding interval datatype as string, + // we are not interested in the number of bytes but characters. + dataSize = 50; // magic number +#endif //SQLT_INTERVAL_DS +#endif //SQLT_INTERVAL_YM + else if (ofi.oraType == SQLT_NUM || ofi.oraType == SQLT_VNU){ + if (ofi.oraPrecision > 0) + dataSize = (ofi.oraPrecision + 1) * sizeof(utext); + else + dataSize = (38 + 1) * sizeof(utext); + } + else + dataSize = ofi.oraLength; + + fieldInf[idx].typ = ofi.type; + fieldInf[idx].oraType = ofi.oraType; + rec.append(qFromOraInf(ofi)); + + switch (ofi.type) { + case QVariant::DateTime: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize+1), + dataSize+1, + SQLT_DAT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::Double: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(double) - 1), + sizeof(double), + SQLT_FLT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::Int: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(qint32) - 1), + sizeof(qint32), + SQLT_INT, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::LongLong: + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, sizeof(OCINumber)), + sizeof(OCINumber), + SQLT_VNU, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + case QVariant::ByteArray: + // RAW and LONG RAW fields can't be bound to LOB locators + if (ofi.oraType == SQLT_BIN) { +// qDebug("binding SQLT_BIN"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize, + SQLT_BIN, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else if (ofi.oraType == SQLT_LBI) { +// qDebug("binding SQLT_LBI"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LBI, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else if (ofi.oraType == SQLT_CLOB) { + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + createLobLocator(idx, d->env), + -1, + SQLT_CLOB, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + } else { +// qDebug("binding SQLT_BLOB"); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + createLobLocator(idx, d->env), + -1, + SQLT_BLOB, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + } + break; + case QVariant::String: + if (ofi.oraType == SQLT_LNG) { + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LNG, + &(fieldInf[idx].ind), + 0, 0, OCI_DYNAMIC_FETCH); + } else { + dataSize += dataSize + sizeof(QChar); + //qDebug("OCIDefineByPosStr(%d): %d", count, dataSize); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize, + SQLT_STR, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + if (r == 0) + setCharset(dfn); + } + break; + default: + // this should make enough space even with character encoding + dataSize = (dataSize + 1) * sizeof(utext) ; + //qDebug("OCIDefineByPosDef(%d): %d", count, dataSize); + r = OCIDefineByPos(d->sql, + &dfn, + d->err, + count, + create(idx, dataSize), + dataSize+1, + SQLT_STR, + &(fieldInf[idx].ind), + 0, 0, OCI_DEFAULT); + break; + } + if (r != 0) + qOraWarning("QOCICols::bind:", d->err); + fieldInf[idx].def = dfn; + ++count; + ++idx; + parmStatus = OCIParamGet(d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast<void **>(¶m), + count); + } +} + +QOCICols::~QOCICols() +{ +} + +char* QOCICols::create(int position, int size) +{ + char* c = new char[size+1]; + // Oracle may not fill fixed width fields + memset(c, 0, size+1); + fieldInf[position].data = c; + fieldInf[position].len = size; + return c; +} + +OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env) +{ + OCILobLocator *& lob = fieldInf[position].lob; + int r = OCIDescriptorAlloc(env, + reinterpret_cast<void **>(&lob), + OCI_DTYPE_LOB, + 0, + 0); + if (r != 0) { + qWarning("QOCICols: Cannot create LOB locator"); + lob = 0; + } + return &lob; +} + +void QOCICols::setCharset(OCIDefine* dfn) +{ + int r = 0; + + Q_ASSERT(dfn); + + r = OCIAttrSet(dfn, + OCI_HTYPE_DEFINE, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast<void *>(static_cast<const void *>(&qOraCharset)), + 0, + OCI_ATTR_CHARSET_ID, + d->err); + if (r != 0) + qOraWarning("QOCICols::setCharset: Couldn't set OCI_ATTR_CHARSET_ID: ", d->err); +} + +int QOCICols::readPiecewise(QVector<QVariant> &values, int index) +{ + OCIDefine* dfn; + ub4 typep; + ub1 in_outp; + ub4 iterp; + ub4 idxp; + ub1 piecep; + sword status; + text col [QOCI_DYNAMIC_CHUNK_SIZE+1]; + int fieldNum = -1; + int r = 0; + bool nullField; + + do { + r = OCIStmtGetPieceInfo(d->sql, d->err, reinterpret_cast<void **>(&dfn), &typep, + &in_outp, &iterp, &idxp, &piecep); + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readPiecewise: unable to get piece info:", d->err); + fieldNum = fieldFromDefine(dfn); + bool isStringField = fieldInf.at(fieldNum).oraType == SQLT_LNG; + ub4 chunkSize = QOCI_DYNAMIC_CHUNK_SIZE; + nullField = false; + r = OCIStmtSetPieceInfo(dfn, OCI_HTYPE_DEFINE, + d->err, col, + &chunkSize, piecep, NULL, NULL); + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readPiecewise: unable to set piece info:", d->err); + status = OCIStmtFetch (d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); + if (status == -1) { + sb4 errcode; + OCIErrorGet(d->err, 1, 0, &errcode, 0, 0,OCI_HTYPE_ERROR); + switch (errcode) { + case 1405: /* NULL */ + nullField = true; + break; + default: + qOraWarning("OCIResultPrivate::readPiecewise: unable to fetch next:", d->err); + break; + } + } + if (status == OCI_NO_DATA) + break; + if (nullField || !chunkSize) { + fieldInf[fieldNum].ind = -1; + } else { + if (isStringField) { + QString str = values.at(fieldNum + index).toString(); + str += QString::fromUtf16(reinterpret_cast<const ushort *>(col), + chunkSize / 2); + values[fieldNum + index] = str; + fieldInf[fieldNum].ind = 0; + } else { + QByteArray ba = values.at(fieldNum + index).toByteArray(); + int sz = ba.size(); + ba.resize(sz + chunkSize); + memcpy(ba.data() + sz, reinterpret_cast<char *>(col), chunkSize); + values[fieldNum + index] = ba; + fieldInf[fieldNum].ind = 0; + } + } + } while (status == OCI_SUCCESS_WITH_INFO || status == OCI_NEED_DATA); + return r; +} + +struct QOCIBatchColumn +{ + inline QOCIBatchColumn() + : bindh(0), bindAs(0), maxLen(0), recordCount(0), + data(0), lengths(0), indicators(0), maxarr_len(0), curelep(0) {} + + OCIBind* bindh; + ub2 bindAs; + ub4 maxLen; + ub4 recordCount; + char* data; + ub2* lengths; + sb2* indicators; + ub4 maxarr_len; + ub4 curelep; +}; + +struct QOCIBatchCleanupHandler +{ + inline QOCIBatchCleanupHandler(QVector<QOCIBatchColumn> &columns) + : col(columns) {} + + ~QOCIBatchCleanupHandler() + { + // deleting storage, length and indicator arrays + for ( int j = 0; j < col.count(); ++j){ + delete[] col[j].lengths; + delete[] col[j].indicators; + delete[] col[j].data; + } + } + + QVector<QOCIBatchColumn> &col; +}; + +bool QOCICols::execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind) +{ + int columnCount = boundValues.count(); + if (boundValues.isEmpty() || columnCount == 0) + return false; + +#ifdef QOCI_DEBUG + qDebug() << "columnCount:" << columnCount << boundValues; +#endif + + int i; + sword r; + + QVarLengthArray<QVariant::Type> fieldTypes; + for (i = 0; i < columnCount; ++i) { + QVariant::Type tp = boundValues.at(i).type(); + fieldTypes.append(tp == QVariant::List ? boundValues.at(i).toList().value(0).type() + : tp); + } + + QList<QByteArray> tmpStorage; + SizeArray tmpSizes(columnCount); + QVector<QOCIBatchColumn> columns(columnCount); + QOCIBatchCleanupHandler cleaner(columns); + + // figuring out buffer sizes + for (i = 0; i < columnCount; ++i) { + + if (boundValues.at(i).type() != QVariant::List) { + + // not a list - create a deep-copy of the single value + QOCIBatchColumn &singleCol = columns[i]; + singleCol.indicators = new sb2[1]; + *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0; + + r = d->bindValue(d->sql, &singleCol.bindh, d->err, i, + boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + continue; + } + + QOCIBatchColumn &col = columns[i]; + col.recordCount = boundValues.at(i).toList().count(); + + col.lengths = new ub2[col.recordCount]; + col.indicators = new sb2[col.recordCount]; + col.maxarr_len = col.recordCount; + col.curelep = col.recordCount; + + switch (fieldTypes[i]) { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: + col.bindAs = SQLT_DAT; + col.maxLen = 7; + break; + + case QVariant::Int: + col.bindAs = SQLT_INT; + col.maxLen = sizeof(int); + break; + + case QVariant::UInt: + col.bindAs = SQLT_UIN; + col.maxLen = sizeof(uint); + break; + + case QVariant::Double: + col.bindAs = SQLT_FLT; + col.maxLen = sizeof(double); + break; + + case QVariant::UserType: + col.bindAs = SQLT_RDD; + col.maxLen = sizeof(OCIRowid*); + break; + + case QVariant::String: { + col.bindAs = SQLT_STR; + for (uint j = 0; j < col.recordCount; ++j) { + uint len = boundValues.at(i).toList().at(j).toString().length() + 1; + if (len > col.maxLen) + col.maxLen = len; + } + col.maxLen *= sizeof(QChar); + break; } + + case QVariant::ByteArray: + default: { + col.bindAs = SQLT_LBI; + for (uint j = 0; j < col.recordCount; ++j) { + col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().size(); + if (col.lengths[j] > col.maxLen) + col.maxLen = col.lengths[j]; + } + break; } + } + + col.data = new char[col.maxLen * col.recordCount]; + memset(col.data, 0, col.maxLen * col.recordCount); + + // we may now populate column with data + for (uint row = 0; row < col.recordCount; ++row) { + const QVariant &val = boundValues.at(i).toList().at(row); + + if (val.isNull()){ + columns[i].indicators[row] = -1; + columns[i].lengths[row] = 0; + } else { + columns[i].indicators[row] = 0; + char *dataPtr = columns[i].data + (columns[i].maxLen * row); + switch (fieldTypes[i]) { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime:{ + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOraDate(val.toDateTime()); + Q_ASSERT(ba.size() == int(columns[i].maxLen)); + memcpy(dataPtr, ba.constData(), columns[i].maxLen); + break; + } + case QVariant::Int: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<int*>(dataPtr) = val.toInt(); + break; + + case QVariant::UInt: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<uint*>(dataPtr) = val.toUInt(); + break; + + case QVariant::Double: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast<double*>(dataPtr) = val.toDouble(); + break; + + case QVariant::String: { + const QString s = val.toString(); + columns[i].lengths[row] = (s.length() + 1) * sizeof(QChar); + memcpy(dataPtr, s.utf16(), columns[i].lengths[row]); + break; + } + case QVariant::UserType: + if (qVariantCanConvert<QOCIRowIdPointer>(val)) { + const QOCIRowIdPointer rptr = qVariantValue<QOCIRowIdPointer>(val); + *reinterpret_cast<OCIRowid**>(dataPtr) = rptr->id; + columns[i].lengths[row] = 0; + break; + } + case QVariant::ByteArray: + default: { + const QByteArray ba = val.toByteArray(); + columns[i].lengths[row] = ba.size(); + memcpy(dataPtr, ba.constData(), ba.size()); + break; + } + } + } + } + + QOCIBatchColumn &bindColumn = columns[i]; + +#ifdef QOCI_DEBUG + qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)", + d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data, + bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths, + arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0); + + for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) { + qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii], + bindColumn.lengths[ii]); + } +#endif + + + // binding the column + r = OCIBindByPos( + d->sql, &bindColumn.bindh, d->err, i + 1, + bindColumn.data, + bindColumn.maxLen, + bindColumn.bindAs, + bindColumn.indicators, + bindColumn.lengths, + 0, + arrayBind ? bindColumn.maxarr_len : 0, + arrayBind ? &bindColumn.curelep : 0, + OCI_DEFAULT); + +#ifdef QOCI_DEBUG + qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh); +#endif + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + + r = OCIBindArrayOfStruct ( + columns[i].bindh, d->err, + columns[i].maxLen, + sizeof(columns[i].indicators[0]), + sizeof(columns[i].lengths[0]), + 0); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err); + d->q->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to bind column for batch execute"), + QSqlError::StatementError, d->err)); + return false; + } + } + + //finaly we can execute + r = OCIStmtExecute(d->svc, d->sql, d->err, + arrayBind ? 1 : columns[0].recordCount, + 0, NULL, NULL, + d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS); + + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIPrivate::execBatch: unable to execute batch statement:", d->err); + d->q->setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to execute batch statement"), + QSqlError::StatementError, d->err)); + return false; + } + + // for out parameters we copy data back to value vector + for (i = 0; i < columnCount; ++i) { + + if (!d->isOutValue(i)) + continue; + + QVariant::Type tp = boundValues.at(i).type(); + if (tp != QVariant::List) { + qOraOutValue(boundValues[i], tmpStorage); + if (*columns[i].indicators == -1) + boundValues[i] = QVariant(tp); + continue; + } + + QVariantList *list = static_cast<QVariantList *>(const_cast<void*>(boundValues.at(i).data())); + + char* data = columns[i].data; + for (uint r = 0; r < columns[i].recordCount; ++r){ + + if (columns[i].indicators[r] == -1) { + (*list)[r] = QVariant(); + continue; + } + + switch(columns[i].bindAs) { + + case SQLT_DAT: + (*list)[r] = qMakeDate(data + r * columns[i].maxLen); + break; + + case SQLT_INT: + (*list)[r] = *reinterpret_cast<int*>(data + r * columns[i].maxLen); + break; + + case SQLT_UIN: + (*list)[r] = *reinterpret_cast<uint*>(data + r * columns[i].maxLen); + break; + + case SQLT_FLT: + (*list)[r] = *reinterpret_cast<double*>(data + r * columns[i].maxLen); + break; + + case SQLT_STR: + (*list)[r] = QString::fromUtf16(reinterpret_cast<ushort *>(data + + r * columns[i].maxLen)); + break; + + default: + (*list)[r] = QByteArray(data + r * columns[i].maxLen, columns[i].maxLen); + break; + } + } + } + + d->q->setSelect(false); + d->q->setAt(QSql::BeforeFirstRow); + d->q->setActive(true); + + return true; +} + +template<class T, int sz> +int qReadLob(T &buf, const QOCIResultPrivate *d, OCILobLocator *lob) +{ + ub1 csfrm; + ub4 amount; + int r; + + // Read this from the database, don't assume we know what it is set to + r = OCILobCharSetForm(d->env, d->err, lob, &csfrm); + if (r != OCI_SUCCESS) { + qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB char set form: ", d->err); + csfrm = 0; + } + + // Get the length of the LOB (this is in characters) + r = OCILobGetLength(d->svc, d->err, lob, &amount); + if (r == OCI_SUCCESS) { + if (amount == 0) { + // Short cut for null LOBs + buf.resize(0); + return OCI_SUCCESS; + } + } else { + qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB length: ", d->err); + return r; + } + + // Resize the buffer to hold the LOB contents + buf.resize(amount); + + // Read the LOB into the buffer + r = OCILobRead(d->svc, + d->err, + lob, + &amount, + 1, + buf.data(), + buf.size() * sz, // this argument is in bytes, not characters + 0, + 0, + // Extract the data from a CLOB in UTF-16 (ie. what QString uses internally) + sz == 1 ? ub2(0) : ub2(QOCIEncoding), + csfrm); + + if (r != OCI_SUCCESS) + qOraWarning("OCIResultPrivate::readLOBs: Cannot read LOB: ", d->err); + + return r; +} + +int QOCICols::readLOBs(QVector<QVariant> &values, int index) +{ + OCILobLocator *lob; + int r = OCI_SUCCESS; + + for (int i = 0; i < size(); ++i) { + const OraFieldInf &fi = fieldInf.at(i); + if (fi.ind == -1 || !(lob = fi.lob)) + continue; + + bool isClob = fi.oraType == SQLT_CLOB; + QVariant var; + + if (isClob) { + QString str; + r = qReadLob<QString, sizeof(QChar)>(str, d, lob); + var = str; + } else { + QByteArray buf; + r = qReadLob<QByteArray, sizeof(char)>(buf, d, lob); + var = buf; + } + if (r == OCI_SUCCESS) + values[index + i] = var; + else + break; + } + return r; +} + +int QOCICols::fieldFromDefine(OCIDefine* d) +{ + for (int i = 0; i < fieldInf.count(); ++i) { + if (fieldInf.at(i).def == d) + return i; + } + return -1; +} + +void QOCICols::getValues(QVector<QVariant> &v, int index) +{ + for (int i = 0; i < fieldInf.size(); ++i) { + const OraFieldInf &fld = fieldInf.at(i); + + if (fld.ind == -1) { + // got a NULL value + v[index + i] = QVariant(fld.typ); + continue; + } + + if (fld.oraType == SQLT_BIN || fld.oraType == SQLT_LBI || fld.oraType == SQLT_LNG) + continue; // already fetched piecewise + + switch (fld.typ) { + case QVariant::DateTime: + v[index + i] = QVariant(qMakeDate(fld.data)); + break; + case QVariant::Double: + case QVariant::Int: + case QVariant::LongLong: + if (d->precisionPolicy != QSql::HighPrecision) { + if ((d->precisionPolicy == QSql::LowPrecisionDouble) + && (fld.typ == QVariant::Double)) { + v[index + i] = *reinterpret_cast<double *>(fld.data); + break; + } else if ((d->precisionPolicy == QSql::LowPrecisionInt64) + && (fld.typ == QVariant::LongLong)) { + qint64 qll = 0; + OCINumberToInt(d->err, reinterpret_cast<OCINumber *>(fld.data), sizeof(qint64), + OCI_NUMBER_SIGNED, &qll); + v[index + i] = qll; + break; + } else if ((d->precisionPolicy == QSql::LowPrecisionInt32) + && (fld.typ == QVariant::Int)) { + v[index + i] = *reinterpret_cast<int *>(fld.data); + break; + } + } + // else fall through + case QVariant::String: + v[index + i] = QString::fromUtf16(reinterpret_cast<const ushort *>(fld.data)); + break; + case QVariant::ByteArray: + if (fld.len > 0) + v[index + i] = QByteArray(fld.data, fld.len); + else + v[index + i] = QVariant(QVariant::ByteArray); + break; + default: + qWarning("QOCICols::value: unknown data type"); + break; + } + } +} + +QOCIResultPrivate::QOCIResultPrivate(QOCIResult *result, const QOCIDriverPrivate *driver) + : cols(0), q(result), env(driver->env), err(0), svc(const_cast<OCISvcCtx*&>(driver->svc)), + sql(0), transaction(driver->transaction), serverVersion(driver->serverVersion), + prefetchRows(driver->prefetchRows), prefetchMem(driver->prefetchMem), + precisionPolicy(driver->precisionPolicy) +{ + int r = OCIHandleAlloc(env, + reinterpret_cast<void **>(&err), + OCI_HTYPE_ERROR, + 0, + 0); + if (r != 0) + qWarning("QOCIResult: unable to alloc error handle"); +} + +QOCIResultPrivate::~QOCIResultPrivate() +{ + delete cols; + + int r = OCIHandleFree(err, OCI_HTYPE_ERROR); + if (r != 0) + qWarning("~QOCIResult: unable to free statement handle"); +} + + +//////////////////////////////////////////////////////////////////////////// + +QOCIResult::QOCIResult(const QOCIDriver * db, const QOCIDriverPrivate* p) + : QSqlCachedResult(db) +{ + d = new QOCIResultPrivate(this, p); +} + +QOCIResult::~QOCIResult() +{ + if (d->sql) { + int r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); + if (r != 0) + qWarning("~QOCIResult: unable to free statement handle"); + } + delete d; +} + +QVariant QOCIResult::handle() const +{ + return qVariantFromValue(d->sql); +} + +bool QOCIResult::reset (const QString& query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) +{ + if (at() == QSql::AfterLastRow) + return false; + + bool piecewise = false; + int r = OCI_SUCCESS; + r = OCIStmtFetch(d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); + + if (index < 0) //not interested in values + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; + + switch (r) { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + qOraWarning("QOCIResult::gotoNext: SuccessWithInfo: ", d->err); + r = OCI_SUCCESS; //ignore it + break; + case OCI_NO_DATA: + // end of rowset + return false; + case OCI_NEED_DATA: + piecewise = true; + r = OCI_SUCCESS; + break; + case OCI_ERROR: + if (qOraErrorNumber(d->err) == 1406) { + qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData()); + r = OCI_SUCCESS; /* ignore it */ + break; + } + // fall through + default: + qOraWarning("QOCIResult::gotoNext: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to goto next"), + QSqlError::StatementError, d->err)); + break; + } + + // need to read piecewise before assigning values + if (r == OCI_SUCCESS && piecewise) + r = d->cols->readPiecewise(values, index); + + if (r == OCI_SUCCESS) + d->cols->getValues(values, index); + if (r == OCI_SUCCESS) + r = d->cols->readLOBs(values, index); + if (r != OCI_SUCCESS) + setAt(QSql::AfterLastRow); + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; +} + +int QOCIResult::size() +{ + return -1; +} + +int QOCIResult::numRowsAffected() +{ + int rowCount; + OCIAttrGet(d->sql, + OCI_HTYPE_STMT, + &rowCount, + NULL, + OCI_ATTR_ROW_COUNT, + d->err); + return rowCount; +} + +bool QOCIResult::prepare(const QString& query) +{ + int r = 0; + QSqlResult::prepare(query); + + delete d->cols; + d->cols = 0; + QSqlCachedResult::cleanup(); + + if (d->sql) { + r = OCIHandleFree(d->sql, OCI_HTYPE_STMT); + if (r != OCI_SUCCESS) + qOraWarning("QOCIResult::prepare: unable to free statement handle:", d->err); + } + if (query.isEmpty()) + return false; + r = OCIHandleAlloc(d->env, + reinterpret_cast<void **>(&d->sql), + OCI_HTYPE_STMT, + 0, + 0); + if (r != OCI_SUCCESS) { + qOraWarning("QOCIResult::prepare: unable to alloc statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to alloc statement"), QSqlError::StatementError, d->err)); + return false; + } + d->setStatementAttributes(); + const OraText *txt = reinterpret_cast<const OraText *>(query.utf16()); + const int len = query.length() * sizeof(QChar); + r = OCIStmtPrepare(d->sql, + d->err, + txt, + len, + OCI_NTV_SYNTAX, + OCI_DEFAULT); + if (r != OCI_SUCCESS) { + qOraWarning("QOCIResult::prepare: unable to prepare statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to prepare statement"), QSqlError::StatementError, d->err)); + return false; + } + return true; +} + +bool QOCIResult::exec() +{ + int r = 0; + ub2 stmtType; + ub4 iters; + ub4 mode; + QList<QByteArray> tmpStorage; + IndicatorArray indicators(boundValueCount()); + SizeArray tmpSizes(boundValueCount()); + + r = OCIAttrGet(d->sql, + OCI_HTYPE_STMT, + &stmtType, + NULL, + OCI_ATTR_STMT_TYPE, + d->err); + + if (stmtType == OCI_STMT_SELECT) { + iters = 0; + mode = OCI_DEFAULT; + } else { + iters = 1; + mode = d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + } + + // bind placeholders + if (boundValueCount() > 0 + && d->bindValues(boundValues(), indicators, tmpSizes, tmpStorage) != OCI_SUCCESS) { + qOraWarning("QOCIResult::exec: unable to bind value: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"), + QSqlError::StatementError, d->err)); +#ifdef QOCI_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + // execute + r = OCIStmtExecute(d->svc, + d->sql, + d->err, + iters, + 0, + 0, + 0, + mode); + if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) { + qOraWarning("QOCIResult::exec: unable to execute statement:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIResult", + "Unable to execute statement"), QSqlError::StatementError, d->err)); +#ifdef QOCI_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + if (stmtType == OCI_STMT_SELECT) { + ub4 parmCount = 0; + int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, reinterpret_cast<void **>(&parmCount), + 0, OCI_ATTR_PARAM_COUNT, d->err); + if (r == 0 && !d->cols) + d->cols = new QOCICols(parmCount, d); + setSelect(true); + QSqlCachedResult::init(parmCount); + } else { /* non-SELECT */ + setSelect(false); + } + setAt(QSql::BeforeFirstRow); + setActive(true); + + if (hasOutValues()) + d->outValues(boundValues(), indicators, tmpStorage); + + return true; +} + +QSqlRecord QOCIResult::record() const +{ + QSqlRecord inf; + if (!isActive() || !isSelect() || !d->cols) + return inf; + return d->cols->rec; +} + +QVariant QOCIResult::lastInsertId() const +{ + if (isActive()) { + QOCIRowIdPointer ptr(new QOCIRowId(d->env)); + + int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, ptr.constData()->id, + 0, OCI_ATTR_ROWID, d->err); + if (r == OCI_SUCCESS) + return qVariantFromValue(ptr); + } + return QVariant(); +} + +void QOCIResult::virtual_hook(int id, void *data) +{ + Q_ASSERT(data); + + switch (id) { + case QSqlResult::BatchOperation: + QOCICols::execBatch(d, boundValues(), *reinterpret_cast<bool *>(data)); + break; + case QSqlResult::SetNumericalPrecision: + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +//////////////////////////////////////////////////////////////////////////// + + +QOCIDriver::QOCIDriver(QObject* parent) + : QSqlDriver(parent) +{ + d = new QOCIDriverPrivate(); + +#ifdef QOCI_THREADED + const ub4 mode = OCI_UTF16 | OCI_OBJECT | OCI_THREADED; +#else + const ub4 mode = OCI_UTF16 | OCI_OBJECT; +#endif + int r = OCIEnvCreate(&d->env, + mode, + NULL, + NULL, + NULL, + NULL, + 0, + NULL); + if (r != 0) { + qWarning("QOCIDriver: unable to create environment"); + setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"), + QSqlError::ConnectionError, d->err)); + return; + } + + d->allocErrorHandle(); +} + +QOCIDriver::QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent) + : QSqlDriver(parent) +{ + d = new QOCIDriverPrivate(); + d->env = env; + d->svc = ctx; + + d->allocErrorHandle(); + + if (env && ctx) { + setOpen(true); + setOpenError(false); + } +} + +QOCIDriver::~QOCIDriver() +{ + if (isOpen()) + close(); + int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR); + if (r != OCI_SUCCESS) + qWarning("Unable to free Error handle: %d", r); + r = OCIHandleFree(d->env, OCI_HTYPE_ENV); + if (r != OCI_SUCCESS) + qWarning("Unable to free Environment handle: %d", r); + + delete d; +} + +bool QOCIDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case LastInsertId: + case BLOB: + case PreparedQueries: + case NamedPlaceholders: + case BatchOperations: + case LowPrecisionNumbers: + return true; + case QuerySize: + case PositionalPlaceholders: + case SimpleLocking: + case EventNotifications: + case FinishQuery: + case MultipleResultSets: + return false; + case Unicode: + return d->serverVersion >= 9; + } + return false; +} + +static void qParseOpts(const QString &options, QOCIDriverPrivate *d) +{ + const QStringList opts(options.split(QLatin1Char(';'), QString::SkipEmptyParts)); + for (int i = 0; i < opts.count(); ++i) { + const QString tmp(opts.at(i)); + int idx; + if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) { + qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'", + tmp.toLocal8Bit().constData()); + continue; + } + const QString opt = tmp.left(idx); + const QString val = tmp.mid(idx + 1).simplified(); + bool ok; + if (opt == QLatin1String("OCI_ATTR_PREFETCH_ROWS")) { + d->prefetchRows = val.toInt(&ok); + if (!ok) + d->prefetchRows = -1; + } else if (opt == QLatin1String("OCI_ATTR_PREFETCH_MEMORY")) { + d->prefetchMem = val.toInt(&ok); + if (!ok) + d->prefetchMem = -1; + } else { + qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'", + opt.toLocal8Bit().constData()); + } + } +} + +bool QOCIDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & hostname, + int port, + const QString &opts) +{ + int r; + + if (isOpen()) + close(); + + qParseOpts(opts, d); + + // Connect without tnsnames.ora if a hostname is given + QString connectionString = db; + if (!hostname.isEmpty()) + connectionString = + QString(QLatin1String("(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=%1)(Port=%2))" + "(CONNECT_DATA=(SID=%3)))")).arg(hostname).arg((port > -1 ? port : 1521)).arg(db); + + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->srvhp), OCI_HTYPE_SERVER, 0, 0); + if (r == OCI_SUCCESS) + r = OCIServerAttach(d->srvhp, d->err, reinterpret_cast<const OraText *>(connectionString.utf16()), + connectionString.length() * sizeof(QChar), OCI_DEFAULT); + if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->svc), OCI_HTYPE_SVCCTX, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->srvhp, 0, OCI_ATTR_SERVER, d->err); + if (r == OCI_SUCCESS) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->authp), OCI_HTYPE_SESSION, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(user.utf16()), + user.length() * sizeof(QChar), OCI_ATTR_USERNAME, d->err); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(password.utf16()), + password.length() * sizeof(QChar), OCI_ATTR_PASSWORD, d->err); + + OCITrans* trans; + if (r == OCI_SUCCESS) + r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&trans), OCI_HTYPE_TRANS, 0, 0); + if (r == OCI_SUCCESS) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, trans, 0, OCI_ATTR_TRANS, d->err); + + if (r == OCI_SUCCESS) { + if (user.isEmpty() && password.isEmpty()) + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT); + else + r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT); + } + if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO) + r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err); + + if (r != OCI_SUCCESS) { + setLastError(qMakeError(tr("Unable to logon"), QSqlError::ConnectionError, d->err)); + setOpenError(true); + if (d->authp) + OCIHandleFree(d->authp, OCI_HTYPE_SESSION); + d->authp = 0; + if (d->srvhp) + OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); + d->srvhp = 0; + return false; + } + + // get server version + char vertxt[512]; + r = OCIServerVersion(d->svc, + d->err, + reinterpret_cast<OraText *>(vertxt), + sizeof(vertxt), + OCI_HTYPE_SVCCTX); + if (r != 0) { + qWarning("QOCIDriver::open: could not get Oracle server version."); + } else { + QString versionStr; + versionStr = QString::fromUtf16(reinterpret_cast<ushort *>(vertxt)); + QRegExp vers(QLatin1String("([0-9]+)\\.[0-9\\.]+[0-9]")); + if (vers.indexIn(versionStr) >= 0) + d->serverVersion = vers.cap(1).toInt(); + if (d->serverVersion == 0) + d->serverVersion = -1; + } + + setOpen(true); + setOpenError(false); + d->user = user.toUpper(); + + return true; +} + +void QOCIDriver::close() +{ + if (!isOpen()) + return; + + OCISessionEnd(d->svc, d->err, d->authp, OCI_DEFAULT); + OCIServerDetach(d->srvhp, d->err, OCI_DEFAULT); + OCIHandleFree(d->authp, OCI_HTYPE_SESSION); + d->authp = 0; + OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER); + d->srvhp = 0; + OCIHandleFree(d->svc, OCI_HTYPE_SVCCTX); + d->svc = 0; + setOpen(false); + setOpenError(false); +} + +QSqlResult *QOCIDriver::createResult() const +{ + return new QOCIResult(this, d); +} + +bool QOCIDriver::beginTransaction() +{ + if (!isOpen()) { + qWarning("QOCIDriver::beginTransaction: Database not open"); + return false; + } + int r = OCITransStart(d->svc, + d->err, + 2, + OCI_TRANS_READWRITE); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::beginTransaction: ", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to begin transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = true; + return true; +} + +bool QOCIDriver::commitTransaction() +{ + if (!isOpen()) { + qWarning("QOCIDriver::commitTransaction: Database not open"); + return false; + } + int r = OCITransCommit(d->svc, + d->err, + 0); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::commitTransaction:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to commit transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = false; + return true; +} + +bool QOCIDriver::rollbackTransaction() +{ + if (!isOpen()) { + qWarning("QOCIDriver::rollbackTransaction: Database not open"); + return false; + } + int r = OCITransRollback(d->svc, + d->err, + 0); + if (r == OCI_ERROR) { + qOraWarning("QOCIDriver::rollbackTransaction:", d->err); + setLastError(qMakeError(QCoreApplication::translate("QOCIDriver", + "Unable to rollback transaction"), QSqlError::TransactionError, d->err)); + return false; + } + d->transaction = false; + return true; +} + +QStringList QOCIDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + if (type & QSql::Tables) { + t.exec(QLatin1String("select owner, table_name from all_tables " + "where owner != 'MDSYS' " + "and owner != 'LBACSYS' " + "and owner != 'SYS' " + "and owner != 'SYSTEM' " + "and owner != 'WKSYS'" + "and owner != 'CTXSYS'" + "and owner != 'WMSYS'")); + while (t.next()) { + if (t.value(0).toString() != d->user) + tl.append(t.value(0).toString() + QLatin1String(".") + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + } + if (type & QSql::Views) { + t.exec(QLatin1String("select owner, view_name from all_views " + "where owner != 'MDSYS' " + "and owner != 'LBACSYS' " + "and owner != 'SYS' " + "and owner != 'SYSTEM' " + "and owner != 'WKSYS'" + "and owner != 'CTXSYS'" + "and owner != 'WMSYS'")); + while (t.next()) { + if (t.value(0).toString() != d->user) + tl.append(t.value(0).toString() + QLatin1String(".") + t.value(1).toString()); + else + tl.append(t.value(1).toString()); + } + } + if (type & QSql::SystemTables) { + t.exec(QLatin1String("select table_name from dictionary")); + while (t.next()) { + tl.append(t.value(0).toString()); + } + } + return tl; +} + +void qSplitTableAndOwner(const QString & tname, QString * tbl, + QString * owner) +{ + int i = tname.indexOf(QLatin1Char('.')); // prefixed with owner? + if (i != -1) { + *tbl = tname.right(tname.length() - i - 1).toUpper(); + *owner = tname.left(i).toUpper(); + } else { + *tbl = tname.toUpper(); + } +} + +QSqlRecord QOCIDriver::record(const QString& tablename) const +{ + QSqlRecord fil; + if (!isOpen()) + return fil; + + QSqlQuery t(createResult()); + // using two separate queries for this is A LOT faster than using + // eg. a sub-query on the sys.synonyms table + QString stmt(QLatin1String("select column_name, data_type, data_length, " + "data_precision, data_scale, nullable, data_default%1" + "from all_tab_columns " + "where upper(table_name)=%2")); + if (d->serverVersion >= 9) + stmt = stmt.arg(QLatin1String(", char_length ")); + else + stmt = stmt.arg(QLatin1String(" ")); + bool buildRecordInfo = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner(tablename, &table, &owner); + tmpStmt = stmt.arg(QLatin1Char('\'') + table + QLatin1Char('\'')); + if (owner.isEmpty()) { + owner = d->user; + } + tmpStmt += QLatin1String(" and upper(owner)='") + owner + QLatin1String("'"); + t.setForwardOnly(true); + t.exec(tmpStmt); + if (!t.next()) { // try and see if the tablename is a synonym + stmt= stmt.arg(QLatin1String("(select tname from sys.synonyms where sname='") + + table + QLatin1String("' and creator=owner)")); + t.setForwardOnly(true); + t.exec(stmt); + if (t.next()) + buildRecordInfo = true; + } else { + buildRecordInfo = true; + } + QStringList keywords = QStringList() << QLatin1String("NUMBER") << QLatin1String("FLOAT") << QLatin1String("BINARY_FLOAT") + << QLatin1String("BINARY_DOUBLE"); + if (buildRecordInfo) { + do { + QVariant::Type ty = qDecodeOCIType(t.value(1).toString(),t.numericalPrecisionPolicy()); + QSqlField f(t.value(0).toString(), ty); + f.setRequired(t.value(5).toString() == QLatin1String("N")); + f.setPrecision(t.value(4).toInt()); + if (d->serverVersion >= 9 && (ty == QVariant::String) && !t.isNull(3) && !keywords.contains(t.value(1).toString())) { + // Oracle9: data_length == size in bytes, char_length == amount of characters + f.setLength(t.value(7).toInt()); + } else { + f.setLength(t.value(t.isNull(3) ? 2 : 3).toInt()); + } + f.setDefaultValue(t.value(6)); + fil.append(f); + } while (t.next()); + } + return fil; +} + +QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const +{ + QSqlIndex idx(tablename); + if (!isOpen()) + return idx; + QSqlQuery t(createResult()); + QString stmt(QLatin1String("select b.column_name, b.index_name, a.table_name, a.owner " + "from all_constraints a, all_ind_columns b " + "where a.constraint_type='P' " + "and b.index_name = a.constraint_name " + "and b.index_owner = a.owner")); + + bool buildIndex = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner(tablename, &table, &owner); + tmpStmt = stmt + QLatin1String(" and upper(a.table_name)='") + table + QLatin1String("'"); + if (owner.isEmpty()) { + owner = d->user; + } + tmpStmt += QLatin1String(" and upper(a.owner)='") + owner + QLatin1String("'"); + t.setForwardOnly(true); + t.exec(tmpStmt); + + if (!t.next()) { + stmt += QLatin1String(" and a.table_name=(select tname from sys.synonyms " + "where sname='") + table + QLatin1String("' and creator=a.owner)"); + t.setForwardOnly(true); + t.exec(stmt); + if (t.next()) { + owner = t.value(3).toString(); + buildIndex = true; + } + } else { + buildIndex = true; + } + if (buildIndex) { + QSqlQuery tt(createResult()); + tt.setForwardOnly(true); + idx.setName(t.value(1).toString()); + do { + tt.exec(QLatin1String("select data_type from all_tab_columns where table_name='") + + t.value(2).toString() + QLatin1String("' and column_name='") + + t.value(0).toString() + QLatin1String("' and owner='") + + owner +QLatin1String("'")); + if (!tt.next()) { + return QSqlIndex(); + } + QSqlField f(t.value(0).toString(), qDecodeOCIType(tt.value(0).toString(), t.numericalPrecisionPolicy())); + idx.append(f); + } while (t.next()); + return idx; + } + return QSqlIndex(); +} + +QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + switch (field.type()) { + case QVariant::DateTime: { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if (datetime.isValid()) { + datestring = QLatin1String("TO_DATE('") + QString::number(datetime.date().year()) + + QLatin1Char('-') + + QString::number(datetime.date().month()) + QLatin1Char('-') + + QString::number(datetime.date().day()) + QLatin1Char(' ') + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + + QLatin1String("','YYYY-MM-DD HH24:MI:SS')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + case QVariant::Time: { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if (datetime.isValid()) { + datestring = QLatin1String("TO_DATE('") + + QString::number(datetime.time().hour()) + QLatin1Char(':') + + QString::number(datetime.time().minute()) + QLatin1Char(':') + + QString::number(datetime.time().second()) + + QLatin1String("','HH24:MI:SS')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + case QVariant::Date: { + QDate date = field.value().toDate(); + QString datestring; + if (date.isValid()) { + datestring = QLatin1String("TO_DATE('") + QString::number(date.year()) + + QLatin1Char('-') + + QString::number(date.month()) + QLatin1Char('-') + + QString::number(date.day()) + QLatin1String("','YYYY-MM-DD')"); + } else { + datestring = QLatin1String("NULL"); + } + return datestring; + } + default: + break; + } + return QSqlDriver::formatValue(field, trimStrings); +} + +QVariant QOCIDriver::handle() const +{ + return qVariantFromValue(d->env); +} + +QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType /* type */) const +{ + QString res = identifier; + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + if (identifier.indexOf(QLatin1Char(' ')) != -1) + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); +// res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/oci/qsql_oci.h b/src/sql/drivers/oci/qsql_oci.h new file mode 100644 index 0000000..069bfdc --- /dev/null +++ b/src/sql/drivers/oci/qsql_oci.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_OCI_H +#define QSQL_OCI_H + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> +#include <QtSql/private/qsqlcachedresult_p.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_OCI +#else +#define Q_EXPORT_SQLDRIVER_OCI Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QOCIDriver; +class QOCICols; +struct QOCIDriverPrivate; +struct QOCIResultPrivate; + +typedef struct OCIEnv OCIEnv; +typedef struct OCISvcCtx OCISvcCtx; + +class Q_EXPORT_SQLDRIVER_OCI QOCIResult : public QSqlCachedResult +{ + friend class QOCIDriver; + friend struct QOCIResultPrivate; + friend class QOCICols; +public: + QOCIResult(const QOCIDriver * db, const QOCIDriverPrivate* p); + ~QOCIResult(); + bool prepare(const QString& query); + bool exec(); + QVariant handle() const; + +protected: + bool gotoNext(ValueCache &values, int index); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + QVariant lastInsertId() const; + void virtual_hook(int id, void *data); + +private: + QOCIResultPrivate *d; +}; + +class Q_EXPORT_SQLDRIVER_OCI QOCIDriver : public QSqlDriver +{ + Q_OBJECT + friend struct QOCIResultPrivate; + friend class QOCIPrivate; +public: + explicit QOCIDriver(QObject* parent = 0); + QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent = 0); + ~QOCIDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts); + void close(); + QSqlResult *createResult() const; + QStringList tables(QSql::TableType) const; + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QString formatValue(const QSqlField &field, + bool trimStrings) const; + QVariant handle() const; + QString escapeIdentifier(const QString &identifier, IdentifierType) const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); +private: + QOCIDriverPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_OCI_H diff --git a/src/sql/drivers/odbc/qsql_odbc.cpp b/src/sql/drivers/odbc/qsql_odbc.cpp new file mode 100644 index 0000000..f6710db --- /dev/null +++ b/src/sql/drivers/odbc/qsql_odbc.cpp @@ -0,0 +1,2312 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_odbc.h" +#include <qsqlrecord.h> + +#if defined (Q_OS_WIN32) +#include <qt_windows.h> +#endif +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qstringlist.h> +#include <qvarlengtharray.h> +#include <qvector.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +// undefine this to prevent initial check of the ODBC driver +#define ODBC_CHECK_DRIVER + +#if defined(Q_ODBC_VERSION_2) +//crude hack to get non-unicode capable driver managers to work +# undef UNICODE +# define SQLTCHAR SQLCHAR +# define SQL_C_WCHAR SQL_C_CHAR +#endif + +// newer platform SDKs use SQLLEN instead of SQLINTEGER +#if defined(SQLLEN) || defined(Q_OS_WIN64) +# define QSQLLEN SQLLEN +#else +# define QSQLLEN SQLINTEGER +#endif + +#if defined(SQLULEN) || defined(Q_OS_WIN64) +# define QSQLULEN SQLULEN +#else +# define QSQLULEN SQLUINTEGER +#endif + +static const int COLNAMESIZE = 256; +//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 }; + +class QODBCDriverPrivate +{ +public: + QODBCDriverPrivate() + : hEnv(0), hDbc(0), useSchema(false), disconnectCount(0), isMySqlServer(false), + isMSSqlServer(false), hasSQLFetchScroll(true), hasMultiResultSets(false) + { + sql_char_type = sql_varchar_type = sql_longvarchar_type = QVariant::ByteArray; + unicode = false; + } + + SQLHANDLE hEnv; + SQLHANDLE hDbc; + + uint unicode :1; + uint useSchema :1; + QVariant::Type sql_char_type; + QVariant::Type sql_varchar_type; + QVariant::Type sql_longvarchar_type; + int disconnectCount; + bool isMySqlServer; + bool isMSSqlServer; + bool hasSQLFetchScroll; + bool hasMultiResultSets; + + bool checkDriver() const; + void checkUnicode(); + void checkSqlServer(); + void checkHasSQLFetchScroll(); + void checkHasMultiResults(); + void checkSchemaUsage(); + bool setConnectionOptions(const QString& connOpts); + void splitTableQualifier(const QString &qualifier, QString &catalog, + QString &schema, QString &table); +}; + +class QODBCPrivate +{ +public: + QODBCPrivate() + : hEnv(0), hDbc(0), hStmt(0), useSchema(false), hasSQLFetchScroll(true), precisionPolicy(QSql::HighPrecision) + { + sql_char_type = sql_varchar_type = sql_longvarchar_type = QVariant::ByteArray; + unicode = false; + } + + inline void clearValues() + { fieldCache.fill(QVariant()); fieldCacheIdx = 0; } + + SQLHANDLE hEnv; + SQLHANDLE hDbc; + SQLHANDLE hStmt; + + uint unicode :1; + uint useSchema :1; + QVariant::Type sql_char_type; + QVariant::Type sql_varchar_type; + QVariant::Type sql_longvarchar_type; + + QSqlRecord rInf; + QVector<QVariant> fieldCache; + int fieldCacheIdx; + int disconnectCount; + bool hasSQLFetchScroll; + QSql::NumericalPrecisionPolicy precisionPolicy; + + bool isStmtHandleValid(const QSqlDriver *driver); + void updateStmtHandleState(const QSqlDriver *driver); +}; + +bool QODBCPrivate::isStmtHandleValid(const QSqlDriver *driver) +{ + const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver); + return disconnectCount == odbcdriver->d->disconnectCount; +} + +void QODBCPrivate::updateStmtHandleState(const QSqlDriver *driver) +{ + const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver); + disconnectCount = odbcdriver->d->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]; + SQLTCHAR description_[SQL_MAX_MESSAGE_LENGTH]; + QString result; + int i = 1; + + description_[0] = 0; + do { + r = SQLGetDiagRec(handleType, + handle, + i, + (SQLTCHAR*)state_, + &nativeCode_, + (SQLTCHAR*)description_, + SQL_MAX_MESSAGE_LENGTH, /* in bytes, not in characters */ + &msgLen); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (nativeCode) + *nativeCode = nativeCode_; + QString tmpstore; +#ifdef UNICODE + tmpstore = QString((const QChar*)description_, msgLen); +#else + tmpstore = QString::fromLocal8Bit((const char*)description_, msgLen); +#endif + 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 QODBCPrivate* odbc, int *nativeCode = 0) +{ + return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1String(" ") + + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc) + QLatin1String(" ") + + qWarnODBCHandle(SQL_HANDLE_STMT, odbc->hStmt, nativeCode)); +} + +static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0) +{ + return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1String(" ") + + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc, nativeCode)); +} + +static void qSqlWarning(const QString& message, const QODBCPrivate* odbc) +{ + qWarning() << message << "\tError:" << qODBCWarn(odbc); +} + +static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc) +{ + qWarning() << message << "\tError:" << qODBCWarn(odbc); +} + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCPrivate* p) +{ + int nativeCode = -1; + QString message = qODBCWarn(p, &nativeCode); + return QSqlError(QLatin1String("QODBC3: ") + err, message, type, nativeCode); +} + +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); +} + +template<class T> +static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, const T* p, 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: + case SQL_TINYINT: + type = isSigned ? QVariant::Int : 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; +#ifndef Q_ODBC_VERSION_2 + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + type = QVariant::String; + break; +#endif + case SQL_CHAR: + type = p->sql_char_type; + break; + case SQL_VARCHAR: + case SQL_GUID: + type = p->sql_varchar_type; + break; + case SQL_LONGVARCHAR: + type = p->sql_longvarchar_type; + 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; + QSQLLEN 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) { + colSize *= 2; // a tiny bit faster, since it saves a SQLGetData() call + } + } + char* buf = new char[colSize]; + while (true) { + r = SQLGetData(hStmt, + column+1, + unicode ? SQL_C_WCHAR : SQL_C_CHAR, + (SQLPOINTER)buf, + 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) ? (unicode ? colSize-2 : colSize-1) : lengthIndicator; + if (unicode) { + fieldVal += QString((QChar*) buf, rSize / 2); + } else { + fieldVal += QString::fromAscii(buf, rSize); + } + if (fieldVal.size() + lengthIndicator >= 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; + } + } + delete[] buf; + return fieldVal; +} + +static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) +{ + QByteArray fieldVal; + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + QSQLULEN colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + QSQLLEN lengthIndicator = 0; + 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" << 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, + (SQLPOINTER)(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 > QSQLLEN(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; + QSQLLEN 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 qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true) +{ + SQLBIGINT lngbuf = 0; + QSQLLEN 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); +} + +// 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)); + 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 + 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); + return f; +} + +static QSqlField qMakeFieldInfo(const QODBCPrivate* p, int i ) +{ + SQLSMALLINT colNameLen; + SQLSMALLINT colType; + QSQLULEN colSize; + SQLSMALLINT colScale; + SQLSMALLINT nullable; + SQLRETURN r = SQL_ERROR; + SQLTCHAR colName[COLNAMESIZE]; + r = SQLDescribeCol(p->hStmt, + i+1, + colName, + (SQLSMALLINT)COLNAMESIZE, + &colNameLen, + &colType, + &colSize, + &colScale, + &nullable); + + if (r != SQL_SUCCESS) { + qSqlWarning(QString::fromLatin1("qMakeField: Unable to describe column %1").arg(i), p); + return QSqlField(); + } + + QSQLLEN unsignedFlag = SQL_FALSE; + r = SQLColAttribute (p->hStmt, + i + 1, + SQL_DESC_UNSIGNED, + 0, + 0, + 0, + &unsignedFlag); + if (r != SQL_SUCCESS) { + qSqlWarning(QString::fromLatin1("qMakeField: Unable to get column attributes for column %1").arg(i), p); + } + +#ifdef UNICODE + QString qColName((const QChar*)colName, colNameLen); +#else + QString qColName = QString::fromLocal8Bit((const char*)colName); +#endif + // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN + int required = -1; + if (nullable == SQL_NO_NULLS) { + required = 1; + } else if (nullable == SQL_NULLABLE) { + required = 0; + } + QVariant::Type type = qDecodeODBCType(colType, p, 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 + return f; +} + +static int qGetODBCVersion(const QString &connOpts) +{ +#ifndef Q_ODBC_VERSION_2 + if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive)) + return SQL_OV_ODBC3; +#endif + return SQL_OV_ODBC2; +} + +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) v, 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) v, 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) { + val.utf16(); // 0 terminate + r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, +#ifdef UNICODE + (SQLWCHAR*) val.unicode(), +#else + (SQLCHAR*) val.toLatin1().constData(), +#endif + SQL_NTS); + } 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) v, 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) { + v = val.toUInt(); + r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) v, 0); + } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) { + val.utf16(); // 0 terminate + r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, +#ifdef UNICODE + (SQLWCHAR*) val.unicode(), +#else + (SQLCHAR*) val.toLatin1().constData(), +#endif + SQL_NTS); + } 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) v, 0); +#ifndef Q_ODBC_VERSION_2 + } 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)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)v, 0); +#endif + } 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++; + } + } +} + +//////////////////////////////////////////////////////////////////////////// + +QODBCResult::QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p) +: QSqlResult(db) +{ + d = new QODBCPrivate(); + d->hEnv = p->hEnv; + d->hDbc = p->hDbc; + d->unicode = p->unicode; + d->useSchema = p->useSchema; + d->sql_char_type = p->sql_char_type; + d->sql_varchar_type = p->sql_varchar_type; + d->sql_longvarchar_type = p->sql_longvarchar_type; + d->disconnectCount = p->disconnectCount; + d->hasSQLFetchScroll = p->hasSQLFetchScroll; +} + +QODBCResult::~QODBCResult() +{ + if (d->hStmt && d->isStmtHandleValid(driver()) && driver()->isOpen()) { + SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); + if (r != SQL_SUCCESS) + qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + + QString::number(r), d); + } + + delete d; +} + +bool QODBCResult::reset (const QString& query) +{ + 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(driver())) { + 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->hDbc, + &d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d); + return false; + } + + d->updateStmtHandleState(driver()); + + 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; + } + +#ifdef UNICODE + r = SQLExecDirect(d->hStmt, + (SQLWCHAR*) query.unicode(), + (SQLINTEGER) query.length()); +#else + QByteArray query8 = query.toLocal8Bit(); + r = SQLExecDirect(d->hStmt, + (SQLCHAR*) query8.constData(), + (SQLINTEGER) query8.length()); +#endif + if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + + SQLSMALLINT count; + 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) +{ + 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() +{ + 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() +{ + 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() +{ + 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() +{ + 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; + } + SQLINTEGER currRow; + 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) +{ + 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); + QSQLLEN 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(), true); + break; + case QVariant::Double: + { + QString value=qGetStringData(d->hStmt, i, info.length(), false); + bool ok=false; + switch(d->precisionPolicy) { + case QSql::LowPrecisionInt32: + d->fieldCache[i] = value.toInt(&ok); + break; + case QSql::LowPrecisionInt64: + d->fieldCache[i] = value.toLongLong(&ok); + break; + case QSql::LowPrecisionDouble: + d->fieldCache[i] = value.toDouble(&ok); + break; + case QSql::HighPrecision: + default: + d->fieldCache[i] = value; + ok=true; + break; + } + if(ok==false) + d->fieldCache[i] = QVariant(); + 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) +{ + 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() +{ + QSQLLEN 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) +{ + setActive(false); + setAt(QSql::BeforeFirstRow); + SQLRETURN r; + + d->rInf.clear(); + if (d->hStmt && d->isStmtHandleValid(driver())) { + 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->hDbc, + &d->hStmt); + if (r != SQL_SUCCESS) { + qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d); + return false; + } + + d->updateStmtHandleState(driver()); + + 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; + } + +#ifdef UNICODE + r = SQLPrepare(d->hStmt, + (SQLWCHAR*) query.unicode(), + (SQLINTEGER) query.length()); +#else + QByteArray query8 = query.toLocal8Bit(); + r = SQLPrepare(d->hStmt, + (SQLCHAR*) query8.constData(), + (SQLINTEGER) query8.length()); +#endif + + if (r != SQL_SUCCESS) { + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to prepare statement"), QSqlError::StatementError, d)); + return false; + } + return true; +} + +bool QODBCResult::exec() +{ + 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); + + QList<QByteArray> tmpStorage; // holds temporary buffers + QVarLengthArray<QSQLLEN, 32> indicators(boundValues().count()); + memset(indicators.data(), 0, indicators.size() * sizeof(QSQLLEN)); + + // bind parameters - only positional binding allowed + QVector<QVariant>& values = boundValues(); + 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); + QSQLLEN *ind = &indicators[i]; + if (val.isNull()) + *ind = SQL_NULL_DATA; + switch (val.type()) { + case QVariant::Date: { + QByteArray ba; + ba.resize(sizeof(DATE_STRUCT)); + DATE_STRUCT *dt = (DATE_STRUCT *)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[(QFlag)(bindValueType(i)) & QSql::InOut], + 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 = val.toTime(); + dt->hour = qdt.hour(); + dt->minute = qdt.minute(); + dt->second = qdt.second(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + 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 = 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(); + dt->fraction = qdt.time().msec() * 1000000; + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_TIMESTAMP, + SQL_TIMESTAMP, + 19, + 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)) & QSql::InOut], + SQL_C_SLONG, + SQL_INTEGER, + 0, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::UInt: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_ULONG, + SQL_NUMERIC, + 15, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::Double: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_DOUBLE, + SQL_DOUBLE, + 0, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::LongLong: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_SBIGINT, + SQL_BIGINT, + 0, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::ULongLong: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_UBIGINT, + SQL_BIGINT, + 0, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::ByteArray: + if (*ind != SQL_NULL_DATA) { + *ind = val.toByteArray().size(); + } + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_BINARY, + SQL_LONGVARBINARY, + val.toByteArray().size(), + 0, + (void *) val.toByteArray().constData(), + val.toByteArray().size(), + ind); + break; + case QVariant::Bool: + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_BIT, + SQL_BIT, + 0, + 0, + (void *) val.constData(), + 0, + *ind == SQL_NULL_DATA ? ind : NULL); + break; + case QVariant::String: +#ifndef Q_ODBC_VERSION_2 + if (d->unicode) { + QString str = val.toString(); + str.utf16(); + if (*ind != SQL_NULL_DATA) + *ind = str.length() * sizeof(QChar); + int strSize = str.length() * sizeof(QChar); + + if (bindValueType(i) & QSql::Out) { + QByteArray ba((char*)str.constData(), str.capacity() * sizeof(QChar)); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_WCHAR, + strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + 0, // god knows... don't change this! + 0, + (void *)ba.constData(), + ba.size(), + ind); + tmpStorage.append(ba); + break; + } + + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_WCHAR, + strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + strSize, + 0, + (void *)str.constData(), + strSize, + ind); + break; + } + else +#endif + { + QByteArray str = val.toString().toUtf8(); + if (*ind != SQL_NULL_DATA) + *ind = str.length(); + int strSize = str.length(); + + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_CHAR, + strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR, + strSize, + 0, + (void *)str.constData(), + strSize, + ind); + tmpStorage.append(str); + break; + } + // fall through + default: { + QByteArray ba = val.toByteArray(); + if (*ind != SQL_NULL_DATA) + *ind = ba.size(); + r = SQLBindParameter(d->hStmt, + i + 1, + qParamType[(QFlag)(bindValueType(i)) & QSql::InOut], + SQL_C_BINARY, + SQL_VARBINARY, + ba.length() + 1, + 0, + (void *) ba.constData(), + ba.length() + 1, + ind); + tmpStorage.append(ba); + 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) { + qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d); + setLastError(qMakeError(QCoreApplication::translate("QODBCResult", + "Unable to execute statement"), QSqlError::StatementError, d)); + return false; + } + + SQLSMALLINT count; + 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 *)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::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) + values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData()); + break; + } + // fall through + default: { + QByteArray ba = tmpStorage.takeFirst(); + if (bindValueType(i) & QSql::Out) + values[i] = QString::fromAscii(ba.constData()); + break; } + } + if (indicators[i] == SQL_NULL_DATA) + values[i] = QVariant(values[i].type()); + } + return true; +} + +QSqlRecord QODBCResult::record() const +{ + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +QVariant QODBCResult::handle() const +{ + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt); +} + +bool QODBCResult::nextResult() +{ + 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; + 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) +{ + switch (id) { + case QSqlResult::DetachFromResultSet: + if (d->hStmt) + SQLCloseCursor(d->hStmt); + break; + case QSqlResult::NextResult: + Q_ASSERT(data); + *static_cast<bool*>(data) = nextResult(); + break; + case QSqlResult::SetNumericalPrecision: + Q_ASSERT(data); + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +//////////////////////////////////////// + + +QODBCDriver::QODBCDriver(QObject *parent) + : QSqlDriver(parent) +{ + init(); +} + +QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent) + : QSqlDriver(parent) +{ + init(); + d->hEnv = env; + d->hDbc = con; + if (env && con) { + setOpen(true); + setOpenError(false); + } +} + +void QODBCDriver::init() +{ + d = new QODBCDriverPrivate(); +} + +QODBCDriver::~QODBCDriver() +{ + cleanup(); + delete d; +} + +bool QODBCDriver::hasFeature(DriverFeature f) const +{ + 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 LastInsertId: + case BatchOperations: + case SimpleLocking: + case EventNotifications: + return false; + case MultipleResultSets: + return d->hasMultiResultSets; + case BLOB: { + if(d->isMySqlServer) + 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) +{ + 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); + return false; + } + + if (!d->setConnectionOptions(connOpts)) + 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; + SQLTCHAR connOut[1024]; + r = SQLDriverConnect(d->hDbc, + NULL, +#ifdef UNICODE + (SQLWCHAR*)connQStr.unicode(), +#else + (SQLCHAR*)connQStr.toLatin1().constData(), +#endif + (SQLSMALLINT)connQStr.length(), + connOut, + 1024, + &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; + } + + if (!d->checkDriver()) { + setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all " + "needed functionality"), QSqlError::ConnectionError, d)); + setOpenError(true); + return false; + } + + d->checkUnicode(); + d->checkSchemaUsage(); + d->checkSqlServer(); + d->checkHasSQLFetchScroll(); + d->checkHasMultiResults(); + setOpen(true); + setOpenError(false); + return true; +} + +void QODBCDriver::close() +{ + cleanup(); + setOpen(false); + setOpenError(false); +} + +void QODBCDriver::cleanup() +{ + SQLRETURN r; + if (!d) + return; + + 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() +{ +#if defined(Q_ODBC_VERSION_2) + unicode = false; + return; +#endif +#if defined(Q_WS_WIN) + QT_WA( + {}, + { + unicode = false; + return; + }) +#endif + 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)) { + sql_char_type = QVariant::String; + unicode = true; + } + + r = SQLGetInfo(hDbc, + SQL_CONVERT_VARCHAR, + (SQLPOINTER)&fFunc, + sizeof(fFunc), + NULL); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) { + sql_varchar_type = QVariant::String; + unicode = true; + } + + r = SQLGetInfo(hDbc, + SQL_CONVERT_LONGVARCHAR, + (SQLPOINTER)&fFunc, + sizeof(fFunc), + NULL); + if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) { + sql_longvarchar_type = QVariant::String; + unicode = true; + } +} + +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::checkSqlServer() +{ + SQLRETURN r; + char serverString[200]; + SQLSMALLINT t; + + r = SQLGetInfo(hDbc, + SQL_DBMS_NAME, + serverString, + sizeof(serverString), + &t); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + QString serverType; +#ifdef UNICODE + serverType = QString(reinterpret_cast<const QChar*>(serverString), t/sizeof(QChar)); +#else + serverType = QString::fromLocal8Bit(serverString, t); +#endif + isMySqlServer = serverType.contains(QLatin1String("mysql"), Qt::CaseInsensitive); + isMSSqlServer = serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive); + } +} + +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() +{ + char driverResponse[4]; + SQLSMALLINT length; + SQLRETURN r = SQLGetInfo(hDbc, + SQL_MULT_RESULT_SETS, + driverResponse, + sizeof(driverResponse), + &length); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) +#ifdef UNICODE + hasMultiResultSets = QString(reinterpret_cast<const QChar*>(driverResponse), length/sizeof(QChar)).startsWith(QLatin1Char('Y')); +#else + hasMultiResultSets = QString::fromLocal8Bit(driverResponse, length).startsWith(QLatin1Char('Y')); +#endif +} + +QSqlResult *QODBCDriver::createResult() const +{ + return new QODBCResult(this, d); +} + +bool QODBCDriver::beginTransaction() +{ + if (!isOpen()) { + qWarning() << "QODBCDriver::beginTransaction: Database not open"; + return false; + } + SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF); + SQLRETURN r = SQLSetConnectAttr(d->hDbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)ac, + sizeof(ac)); + if (r != SQL_SUCCESS) { + setLastError(qMakeError(tr("Unable to disable autocommit"), + QSqlError::TransactionError, d)); + return false; + } + return true; +} + +bool QODBCDriver::commitTransaction() +{ + 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() +{ + 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() +{ + SQLUINTEGER ac(SQL_AUTOCOMMIT_ON); + SQLRETURN r = SQLSetConnectAttr(d->hDbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)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 +{ + 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(QLatin1String(",")); + + r = SQLTables(hStmt, + NULL, + 0, + NULL, + 0, + NULL, + 0, +#ifdef UNICODE + (SQLWCHAR*)joinedTableTypeString.unicode(), +#else + (SQLCHAR*)joinedTableTypeString.toLatin1().constData(), +#endif + 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); + + 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 +{ + 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; + d->splitTableQualifier(tablename, catalog, schema, table); + r = SQLSetStmtAttr(hStmt, + SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, + SQL_IS_UINTEGER); + r = SQLPrimaryKeys(hStmt, +#ifdef UNICODE + catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(), +#else + catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(), +#endif + catalog.length(), +#ifdef UNICODE + schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(), +#else + schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(), +#endif + schema.length(), +#ifdef UNICODE + (SQLWCHAR*)table.unicode(), +#else + (SQLCHAR*)table.toLatin1().constData(), +#endif + 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, +#ifdef UNICODE + catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(), +#else + catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(), +#endif + catalog.length(), +#ifdef UNICODE + schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(), +#else + schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(), +#endif + schema.length(), +#ifdef UNICODE + (SQLWCHAR*)table.unicode(), +#else + (SQLCHAR*)table.toLatin1().constData(), +#endif + 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 +{ + QSqlRecord fil; + if (!isOpen()) + return fil; + + SQLHANDLE hStmt; + QString catalog, schema, table; + d->splitTableQualifier(tablename, catalog, schema, 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, +#ifdef UNICODE + catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(), +#else + catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(), +#endif + catalog.length(), +#ifdef UNICODE + schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(), +#else + schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(), +#endif + schema.length(), +#ifdef UNICODE + (SQLWCHAR*)table.unicode(), +#else + (SQLCHAR*)table.toLatin1().constData(), +#endif + 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 +{ + return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc); +} + +QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if (d->isMySqlServer) { + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('`')) && identifier.right(1) != QString(QLatin1Char('`')) ) { + res.prepend(QLatin1Char('`')).append(QLatin1Char('`')); + res.replace(QLatin1Char('.'), QLatin1String("`.`")); + } + } else { + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/odbc/qsql_odbc.h b/src/sql/drivers/odbc/qsql_odbc.h new file mode 100644 index 0000000..4148007 --- /dev/null +++ b/src/sql/drivers/odbc/qsql_odbc.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_ODBC_H +#define QSQL_ODBC_H + +#include <QtSql/qsqldriver.h> +#include <QtSql/qsqlresult.h> + +#if defined (Q_OS_WIN32) +#include <QtCore/qt_windows.h> +#endif + +#if defined (Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_3) +// assume we use iodbc on MACX +// comment next line out if you use a +// unicode compatible manager +# define Q_ODBC_VERSION_2 +#endif + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_ODBC +#else +#define Q_EXPORT_SQLDRIVER_ODBC Q_SQL_EXPORT +#endif + +#ifdef Q_OS_UNIX +#define HAVE_LONG_LONG 1 // force UnixODBC NOT to fall back to a struct for BIGINTs +#endif + +#if defined(Q_CC_BOR) +// workaround for Borland to make sure that SQLBIGINT is defined +# define _MSC_VER 900 +#endif +#include <sql.h> +#if defined(Q_CC_BOR) +# undef _MSC_VER +#endif + +#ifndef Q_ODBC_VERSION_2 +#include <sqlucode.h> +#endif + +#include <sqlext.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QODBCPrivate; +class QODBCDriverPrivate; +class QODBCDriver; +class QSqlRecordInfo; + +class QODBCResult : public QSqlResult +{ +public: + QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p); + virtual ~QODBCResult(); + + bool prepare(const QString& query); + bool exec(); + + QVariant handle() const; + +protected: + bool fetchNext(); + bool fetchFirst(); + bool fetchLast(); + bool fetchPrevious(); + bool fetch(int i); + bool reset (const QString& query); + QVariant data(int field); + bool isNull(int field); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + void virtual_hook(int id, void *data); + bool nextResult(); + +private: + QODBCPrivate *d; +}; + +class Q_EXPORT_SQLDRIVER_ODBC QODBCDriver : public QSqlDriver +{ + Q_OBJECT +public: + explicit QODBCDriver(QObject *parent=0); + QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent=0); + virtual ~QODBCDriver(); + bool hasFeature(DriverFeature f) const; + void close(); + QSqlResult *createResult() const; + QStringList tables(QSql::TableType) const; + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QVariant handle() const; + QString formatValue(const QSqlField &field, + bool trimStrings) const; + bool open(const QString& db, + const QString& user, + const QString& password, + const QString& host, + int port, + const QString& connOpts); + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); +private: + void init(); + bool endTrans(); + void cleanup(); + QODBCDriverPrivate* d; + friend class QODBCPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_ODBC_H diff --git a/src/sql/drivers/psql/qsql_psql.cpp b/src/sql/drivers/psql/qsql_psql.cpp new file mode 100644 index 0000000..e33dd95 --- /dev/null +++ b/src/sql/drivers/psql/qsql_psql.cpp @@ -0,0 +1,1250 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_psql.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlrecord.h> +#include <qsqlquery.h> +#include <qsocketnotifier.h> +#include <qstringlist.h> +#include <qmutex.h> + +#include <libpq-fe.h> +#include <pg_config.h> + +#include <stdlib.h> +#include <math.h> + +// workaround for postgres defining their OIDs in a private header file +#define QBOOLOID 16 +#define QINT8OID 20 +#define QINT2OID 21 +#define QINT4OID 23 +#define QNUMERICOID 1700 +#define QFLOAT4OID 700 +#define QFLOAT8OID 701 +#define QABSTIMEOID 702 +#define QRELTIMEOID 703 +#define QDATEOID 1082 +#define QTIMEOID 1083 +#define QTIMETZOID 1266 +#define QTIMESTAMPOID 1114 +#define QTIMESTAMPTZOID 1184 +#define QOIDOID 2278 +#define QBYTEAOID 17 +#define QREGPROCOID 24 +#define QXIDOID 28 +#define QCIDOID 29 + +/* This is a compile time switch - if PQfreemem is declared, the compiler will use that one, + otherwise it'll run in this template */ +template <typename T> +inline void PQfreemem(T *t, int = 0) { free(t); } + +Q_DECLARE_METATYPE(PGconn*) +Q_DECLARE_METATYPE(PGresult*) + +QT_BEGIN_NAMESPACE + +inline void qPQfreemem(void *buffer) +{ + PQfreemem(buffer); +} + +class QPSQLDriverPrivate +{ +public: + QPSQLDriverPrivate() : connection(0), isUtf8(false), pro(QPSQLDriver::Version6), sn(0) {} + PGconn *connection; + bool isUtf8; + QPSQLDriver::Protocol pro; + QSocketNotifier *sn; + QStringList seid; + + void appendTables(QStringList &tl, QSqlQuery &t, QChar type); +}; + +void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type) +{ + QString query; + if (pro >= QPSQLDriver::Version73) { + query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class " + "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) " + "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') " + "and (pg_class.relname !~ '^pg_') " + "and (pg_namespace.nspname != 'information_schema') ").arg(type); + } else { + query = QString::fromLatin1("select relname, null from pg_class where (relkind = '%1') " + "and (relname !~ '^Inv') " + "and (relname !~ '^pg_') ").arg(type); + } + t.exec(query); + while (t.next()) { + QString schema = t.value(1).toString(); + if (schema.isEmpty() || schema == QLatin1String("public")) + tl.append(t.value(0).toString()); + else + tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema)); + } +} + +class QPSQLResultPrivate +{ +public: + QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), precisionPolicy(QSql::HighPrecision) {} + + QPSQLResult *q; + const QPSQLDriverPrivate *driver; + PGresult *result; + int currentSize; + QSql::NumericalPrecisionPolicy precisionPolicy; + bool preparedQueriesEnabled; + QString preparedStmtId; + + bool processResults(); +}; + +static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, + const QPSQLDriverPrivate *p) +{ + const char *s = PQerrorMessage(p->connection); + QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s); + return QSqlError(QLatin1String("QPSQL: ") + err, msg, type); +} + +bool QPSQLResultPrivate::processResults() +{ + if (!result) + return false; + + int status = PQresultStatus(result); + if (status == PGRES_TUPLES_OK) { + q->setSelect(true); + q->setActive(true); + currentSize = PQntuples(result); + return true; + } else if (status == PGRES_COMMAND_OK) { + q->setSelect(false); + q->setActive(true); + currentSize = -1; + return true; + } + q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to create query"), QSqlError::StatementError, driver)); + return false; +} + +static QVariant::Type qDecodePSQLType(int t) +{ + QVariant::Type type = QVariant::Invalid; + switch (t) { + case QBOOLOID: + type = QVariant::Bool; + break; + case QINT8OID: + type = QVariant::LongLong; + break; + case QINT2OID: + case QINT4OID: + case QOIDOID: + case QREGPROCOID: + case QXIDOID: + case QCIDOID: + type = QVariant::Int; + break; + case QNUMERICOID: + case QFLOAT4OID: + case QFLOAT8OID: + type = QVariant::Double; + break; + case QABSTIMEOID: + case QRELTIMEOID: + case QDATEOID: + type = QVariant::Date; + break; + case QTIMEOID: + case QTIMETZOID: + type = QVariant::Time; + break; + case QTIMESTAMPOID: + case QTIMESTAMPTZOID: + type = QVariant::DateTime; + break; + case QBYTEAOID: + type = QVariant::ByteArray; + break; + default: + type = QVariant::String; + break; + } + return type; +} + +static void qDeallocatePreparedStmt(QPSQLResultPrivate *d) +{ + const QString stmt = QLatin1String("DEALLOCATE ") + d->preparedStmtId; + PGresult *result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + qWarning("Unable to free statement: %s", PQerrorMessage(d->driver->connection)); + PQclear(result); + d->preparedStmtId.clear(); +} + +QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p) + : QSqlResult(db) +{ + d = new QPSQLResultPrivate(this); + d->driver = p; + d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries); +} + +QPSQLResult::~QPSQLResult() +{ + cleanup(); + + if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull()) + qDeallocatePreparedStmt(d); + + delete d; +} + +QVariant QPSQLResult::handle() const +{ + return qVariantFromValue(d->result); +} + +void QPSQLResult::cleanup() +{ + if (d->result) + PQclear(d->result); + d->result = 0; + setAt(QSql::BeforeFirstRow); + d->currentSize = -1; + setActive(false); +} + +bool QPSQLResult::fetch(int i) +{ + if (!isActive()) + return false; + if (i < 0) + return false; + if (i >= d->currentSize) + return false; + if (at() == i) + return true; + setAt(i); + return true; +} + +bool QPSQLResult::fetchFirst() +{ + return fetch(0); +} + +bool QPSQLResult::fetchLast() +{ + return fetch(PQntuples(d->result) - 1); +} + +QVariant QPSQLResult::data(int i) +{ + if (i >= PQnfields(d->result)) { + qWarning("QPSQLResult::data: column %d out of range", i); + return QVariant(); + } + int ptype = PQftype(d->result, i); + QVariant::Type type = qDecodePSQLType(ptype); + const char *val = PQgetvalue(d->result, at(), i); + if (PQgetisnull(d->result, at(), i)) + return QVariant(type); + switch (type) { + case QVariant::Bool: + return QVariant((bool)(val[0] == 't')); + case QVariant::String: + return d->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(val); + case QVariant::LongLong: + if (val[0] == '-') + return QString::fromLatin1(val).toLongLong(); + else + return QString::fromLatin1(val).toULongLong(); + case QVariant::Int: + return atoi(val); + case QVariant::Double: + if (ptype == QNUMERICOID) { + if (d->precisionPolicy != QSql::HighPrecision) { + QVariant retval; + bool convert; + if (d->precisionPolicy == QSql::LowPrecisionInt64) + retval = QString::fromAscii(val).toLongLong(&convert); + else if (d->precisionPolicy == QSql::LowPrecisionInt32) + retval = QString::fromAscii(val).toInt(&convert); + else if (d->precisionPolicy == QSql::LowPrecisionDouble) + retval = QString::fromAscii(val).toDouble(&convert); + if (!convert) + return QVariant(); + return retval; + } + return QString::fromAscii(val); + } + return strtod(val, 0); + case QVariant::Date: + if (val[0] == '\0') { + return QVariant(QDate()); + } else { +#ifndef QT_NO_DATESTRING + return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate)); +#else + return QVariant(QString::fromLatin1(val)); +#endif + } + case QVariant::Time: { + const QString str = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (str.isEmpty()) + return QVariant(QTime()); + if (str.at(str.length() - 3) == QLatin1Char('+')) + // strip the timezone + return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate)); + return QVariant(QTime::fromString(str, Qt::ISODate)); +#else + return QVariant(str); +#endif + } + case QVariant::DateTime: { + QString dtval = QString::fromLatin1(val); +#ifndef QT_NO_DATESTRING + if (dtval.length() < 10) + return QVariant(QDateTime()); + // remove the timezone + if (dtval.at(dtval.length() - 3) == QLatin1Char('+')) + dtval.chop(3); + // milliseconds are sometimes returned with 2 digits only + if (dtval.at(dtval.length() - 3).isPunct()) + dtval += QLatin1Char('0'); + if (dtval.isEmpty()) + return QVariant(QDateTime()); + else + return QVariant(QDateTime::fromString(dtval, Qt::ISODate)); +#else + return QVariant(dtval); +#endif + } + case QVariant::ByteArray: { + size_t len; + unsigned char *data = PQunescapeBytea((unsigned char*)val, &len); + QByteArray ba((const char*)data, len); + qPQfreemem(data); + return QVariant(ba); + } + default: + case QVariant::Invalid: + qWarning("QPSQLResult::data: unknown data type"); + } + return QVariant(); +} + +bool QPSQLResult::isNull(int field) +{ + PQgetvalue(d->result, at(), field); + return PQgetisnull(d->result, at(), field); +} + +bool QPSQLResult::reset (const QString& query) +{ + cleanup(); + if (!driver()) + return false; + if (!driver()->isOpen() || driver()->isOpenError()) + return false; + d->result = PQexec(d->driver->connection, + d->driver->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()); + return d->processResults(); +} + +int QPSQLResult::size() +{ + return d->currentSize; +} + +int QPSQLResult::numRowsAffected() +{ + return QString::fromLatin1(PQcmdTuples(d->result)).toInt(); +} + +QVariant QPSQLResult::lastInsertId() const +{ + if (isActive()) { + Oid id = PQoidValue(d->result); + if (id != InvalidOid) + return QVariant(id); + } + return QVariant(); +} + +QSqlRecord QPSQLResult::record() const +{ + QSqlRecord info; + if (!isActive() || !isSelect()) + return info; + + int count = PQnfields(d->result); + for (int i = 0; i < count; ++i) { + QSqlField f; + if (d->driver->isUtf8) + f.setName(QString::fromUtf8(PQfname(d->result, i))); + else + f.setName(QString::fromLocal8Bit(PQfname(d->result, i))); + f.setType(qDecodePSQLType(PQftype(d->result, i))); + int len = PQfsize(d->result, i); + int precision = PQfmod(d->result, i); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + f.setLength(len); + f.setPrecision(precision); + f.setSqlType(PQftype(d->result, i)); + info.append(f); + } + return info; +} + +void QPSQLResult::virtual_hook(int id, void *data) +{ + Q_ASSERT(data); + + switch (id) { + case QSqlResult::SetNumericalPrecision: + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +static QString qReplacePlaceholderMarkers(const QString &query) +{ + const int originalLength = query.length(); + bool inQuote = false; + int markerIdx = 0; + QString result; + result.reserve(originalLength + 23); + for (int i = 0; i < originalLength; ++i) { + const QChar ch = query.at(i); + if (ch == QLatin1Char('?') && !inQuote) { + result += QLatin1Char('$'); + result += QString::number(++markerIdx); + } else { + if (ch == QLatin1Char('\'')) + inQuote = !inQuote; + result += ch; + } + } + + result.squeeze(); + return result; +} + +static QString qCreateParamString(const QVector<QVariant> boundValues, const QSqlDriver *driver) +{ + if (boundValues.isEmpty()) + return QString(); + + QString params; + QSqlField f; + for (int i = 0; i < boundValues.count(); ++i) { + const QVariant &val = boundValues.at(i); + + f.setType(val.type()); + if (val.isNull()) + f.clear(); + else + f.setValue(val); + if(!params.isNull()) + params.append(QLatin1String(", ")); + params.append(driver->formatValue(f)); + } + return params; +} + +Q_GLOBAL_STATIC(QMutex, qMutex) +QString qMakePreparedStmtId() +{ + qMutex()->lock(); + static unsigned int qPreparedStmtCount = 0; + QString id = QLatin1String("qpsqlpstmt_") + QString::number(++qPreparedStmtCount, 16); + qMutex()->unlock(); + return id; +} + +bool QPSQLResult::prepare(const QString &query) +{ + if (!d->preparedQueriesEnabled) + return QSqlResult::prepare(query); + + cleanup(); + + if (!d->preparedStmtId.isEmpty()) + qDeallocatePreparedStmt(d); + + const QString stmtId = qMakePreparedStmtId(); + const QString stmt = QString(QLatin1String("PREPARE %1 AS ")).arg(stmtId).append(qReplacePlaceholderMarkers(query)); + + PGresult *result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) { + setLastError(qMakeError(QCoreApplication::translate("QPSQLResult", + "Unable to prepare statement"), QSqlError::StatementError, d->driver)); + PQclear(result); + d->preparedStmtId.clear(); + return false; + } + + PQclear(result); + d->preparedStmtId = stmtId; + return true; +} + +bool QPSQLResult::exec() +{ + if (!d->preparedQueriesEnabled) + return QSqlResult::exec(); + + cleanup(); + + QString stmt; + const QString params = qCreateParamString(boundValues(), d->q->driver()); + if (params.isEmpty()) + stmt = QString(QLatin1String("EXECUTE %1")).arg(d->preparedStmtId); + else + stmt = QString(QLatin1String("EXECUTE %1 (%2)")).arg(d->preparedStmtId).arg(params); + + d->result = PQexec(d->driver->connection, + d->driver->isUtf8 ? stmt.toUtf8().constData() + : stmt.toLocal8Bit().constData()); + + return d->processResults(); +} + +/////////////////////////////////////////////////////////////////// + +static bool setEncodingUtf8(PGconn* connection) +{ + PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'"); + int status = PQresultStatus(result); + PQclear(result); + return status == PGRES_COMMAND_OK; +} + +static void setDatestyle(PGconn* connection) +{ + PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'"); + int status = PQresultStatus(result); + if (status != PGRES_COMMAND_OK) + qWarning("%s", PQerrorMessage(connection)); + PQclear(result); +} + +static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection) +{ + QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6; + PGresult* result = PQexec(connection, "select version()"); + int status = PQresultStatus(result); + if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) { + QString val = QString::fromAscii(PQgetvalue(result, 0, 0)); + PQclear(result); + QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)")); + rx.setMinimal(true); // enforce non-greedy RegExp + if (rx.indexIn(val) != -1) { + int vMaj = rx.cap(1).toInt(); + int vMin = rx.cap(2).toInt(); + + switch (vMaj) { + case 7: + switch (vMin) { + case 0: + serverVersion = QPSQLDriver::Version7; + break; + case 1: + case 2: + serverVersion = QPSQLDriver::Version71; + break; + default: + serverVersion = QPSQLDriver::Version73; + break; + } + break; + case 8: + switch (vMin) { + case 0: + serverVersion = QPSQLDriver::Version8; + break; + case 1: + serverVersion = QPSQLDriver::Version81; + break; + case 2: + default: + serverVersion = QPSQLDriver::Version82; + break; + } + break; + default: + break; + } + } + } + + if (serverVersion < QPSQLDriver::Version71) + qWarning("This version of PostgreSQL is not supported and may not work."); + + return serverVersion; +} + +QPSQLDriver::QPSQLDriver(QObject *parent) + : QSqlDriver(parent) +{ + init(); +} + +QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent) + : QSqlDriver(parent) +{ + init(); + d->connection = conn; + if (conn) { + d->pro = getPSQLVersion(d->connection); + setOpen(true); + setOpenError(false); + } +} + +void QPSQLDriver::init() +{ + d = new QPSQLDriverPrivate(); +} + +QPSQLDriver::~QPSQLDriver() +{ + if (d->connection) + PQfinish(d->connection); + delete d; +} + +QVariant QPSQLDriver::handle() const +{ + return qVariantFromValue(d->connection); +} + +bool QPSQLDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case QuerySize: + case LastInsertId: + case LowPrecisionNumbers: + case EventNotifications: + return true; + case PreparedQueries: + case PositionalPlaceholders: + return d->pro >= QPSQLDriver::Version82; + case BatchOperations: + case NamedPlaceholders: + case SimpleLocking: + case FinishQuery: + case MultipleResultSets: + return false; + case BLOB: + return d->pro >= QPSQLDriver::Version71; + case Unicode: + return d->isUtf8; + } + return false; +} + +/* + Quote a string for inclusion into the connection string + \ -> \\ + ' -> \' + surround string by single quotes + */ +static QString qQuote(QString s) +{ + s.replace(QLatin1Char('\\'), QLatin1String("\\\\")); + s.replace(QLatin1Char('\''), QLatin1String("\\'")); + s.append(QLatin1Char('\'')).prepend(QLatin1Char('\'')); + return s; +} + +bool QPSQLDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts) +{ + if (isOpen()) + close(); + QString connectString; + if (!host.isEmpty()) + connectString.append(QLatin1String("host=")).append(qQuote(host)); + if (!db.isEmpty()) + connectString.append(QLatin1String(" dbname=")).append(qQuote(db)); + if (!user.isEmpty()) + connectString.append(QLatin1String(" user=")).append(qQuote(user)); + if (!password.isEmpty()) + connectString.append(QLatin1String(" password=")).append(qQuote(password)); + if (port != -1) + connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port))); + + // add any connect options - the server will handle error detection + if (!connOpts.isEmpty()) { + QString opt = connOpts; + opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive); + connectString.append(QLatin1Char(' ')).append(opt); + } + + d->connection = PQconnectdb(connectString.toLocal8Bit().constData()); + if (PQstatus(d->connection) == CONNECTION_BAD) { + setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); + setOpenError(true); + PQfinish(d->connection); + d->connection = 0; + return false; + } + + d->pro = getPSQLVersion(d->connection); + d->isUtf8 = setEncodingUtf8(d->connection); + setDatestyle(d->connection); + + setOpen(true); + setOpenError(false); + return true; +} + +void QPSQLDriver::close() +{ + if (isOpen()) { + + d->seid.clear(); + if (d->sn) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + if (d->connection) + PQfinish(d->connection); + d->connection = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QPSQLDriver::createResult() const +{ + return new QPSQLResult(this, d); +} + +bool QPSQLDriver::beginTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::beginTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "BEGIN"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + setLastError(qMakeError(tr("Could not begin transaction"), + QSqlError::TransactionError, d)); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::commitTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::commitTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "COMMIT"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + setLastError(qMakeError(tr("Could not commit transaction"), + QSqlError::TransactionError, d)); + return false; + } + PQclear(res); + return true; +} + +bool QPSQLDriver::rollbackTransaction() +{ + if (!isOpen()) { + qWarning("QPSQLDriver::rollbackTransaction: Database not open"); + return false; + } + PGresult* res = PQexec(d->connection, "ROLLBACK"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Could not rollback transaction"), + QSqlError::TransactionError, d)); + PQclear(res); + return false; + } + PQclear(res); + return true; +} + +QStringList QPSQLDriver::tables(QSql::TableType type) const +{ + QStringList tl; + if (!isOpen()) + return tl; + QSqlQuery t(createResult()); + t.setForwardOnly(true); + + if (type & QSql::Tables) + d->appendTables(tl, t, QLatin1Char('r')); + if (type & QSql::Views) + d->appendTables(tl, t, QLatin1Char('v')); + if (type & QSql::SystemTables) { + t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') " + "and (relname like 'pg_%') ")); + while (t.next()) + tl.append(t.value(0).toString()); + } + + return tl; +} + +static void qSplitTableName(QString &tablename, QString &schema) +{ + int dot = tablename.indexOf(QLatin1Char('.')); + if (dot == -1) + return; + schema = tablename.left(dot); + tablename = tablename.mid(dot + 1); +} + +QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const +{ + QSqlIndex idx(tablename); + if (!isOpen()) + return idx; + QSqlQuery i(createResult()); + QString stmt; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where lower(pg_cl.relname) = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version7: + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname " + "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind " + "where lower(pg_cl.relname) = '%1_pkey' " + "and pg_cl.oid = pg_ind.indexrelid " + "and pg_att2.attrelid = pg_ind.indexrelid " + "and pg_att1.attrelid = pg_ind.indrelid " + "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] " + "order by pg_att2.attnum"); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_class.relname " + "FROM pg_attribute, pg_class " + "WHERE %1 pg_class.oid IN " + "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN " + " (SELECT oid FROM pg_class WHERE lower(relname) = '%2')) " + "AND pg_attribute.attrelid = pg_class.oid " + "AND pg_attribute.attisdropped = false " + "ORDER BY pg_attribute.attnum"); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid) AND")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema.toLower())); + break; + } + + i.exec(stmt.arg(tbl.toLower())); + while (i.isActive() && i.next()) { + QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt())); + idx.append(f); + idx.setName(i.value(2).toString()); + } + return idx; +} + +QSqlRecord QPSQLDriver::record(const QString& tablename) const +{ + QSqlRecord info; + if (!isOpen()) + return info; + + QString tbl = tablename; + QString schema; + qSplitTableName(tbl, schema); + + QString stmt; + switch(d->pro) { + case QPSQLDriver::Version6: + stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "int(pg_attribute.attrelid), pg_attribute.attnum " + "from pg_class, pg_attribute " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version7: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attribute.attrelid::int, pg_attribute.attnum " + "from pg_class, pg_attribute " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid "); + break; + case QPSQLDriver::Version71: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where lower(pg_class.relname) = '%1' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "order by pg_attribute.attnum "); + break; + case QPSQLDriver::Version73: + case QPSQLDriver::Version74: + case QPSQLDriver::Version8: + case QPSQLDriver::Version81: + case QPSQLDriver::Version82: + stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, " + "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, " + "pg_attrdef.adsrc " + "from pg_class, pg_attribute " + "left join pg_attrdef on (pg_attrdef.adrelid = " + "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) " + "where %1 " + "and lower(pg_class.relname) = '%2' " + "and pg_attribute.attnum > 0 " + "and pg_attribute.attrelid = pg_class.oid " + "and pg_attribute.attisdropped = false " + "order by pg_attribute.attnum "); + if (schema.isEmpty()) + stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid)")); + else + stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from " + "pg_namespace where pg_namespace.nspname = '%1')").arg(schema.toLower())); + break; + } + + QSqlQuery query(createResult()); + query.exec(stmt.arg(tbl.toLower())); + if (d->pro >= QPSQLDriver::Version71) { + while (query.next()) { + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QString defVal = query.value(5).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } else { + // Postgres < 7.1 cannot handle outer joins + while (query.next()) { + QString defVal; + QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where " + "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 "); + QSqlQuery query2(createResult()); + query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt())); + if (query2.isActive() && query2.next()) + defVal = query2.value(0).toString(); + if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\'')) + defVal = defVal.mid(1, defVal.length() - 2); + int len = query.value(3).toInt(); + int precision = query.value(4).toInt(); + // swap length and precision if length == -1 + if (len == -1 && precision > -1) { + len = precision - 4; + precision = -1; + } + QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt())); + f.setRequired(query.value(2).toBool()); + f.setLength(len); + f.setPrecision(precision); + f.setDefaultValue(defVal); + f.setSqlType(query.value(1).toInt()); + info.append(f); + } + } + + return info; +} + +QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + QString r; + if (field.isNull()) { + r = QLatin1String("NULL"); + } else { + switch (field.type()) { + case QVariant::DateTime: +#ifndef QT_NO_DATESTRING + if (field.value().toDateTime().isValid()) { + QDate dt = field.value().toDateTime().date(); + QTime tm = field.value().toDateTime().time(); + // msecs need to be right aligned otherwise psql + // interpretes them wrong + r = QLatin1String("'") + QString::number(dt.year()) + QLatin1String("-") + + QString::number(dt.month()) + QLatin1String("-") + + QString::number(dt.day()) + QLatin1String(" ") + + tm.toString() + QLatin1String(".") + + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0')) + + QLatin1String("'"); + } else { + r = QLatin1String("NULL"); + } +#else + r = QLatin1String("NULL"); +#endif // QT_NO_DATESTRING + break; + case QVariant::Time: +#ifndef QT_NO_DATESTRING + if (field.value().toTime().isValid()) { + r = field.value().toTime().toString(Qt::ISODate); + } else +#endif + { + r = QLatin1String("NULL"); + } + case QVariant::String: + { + // Escape '\' characters + r = QSqlDriver::formatValue(field, trimStrings); + r.replace(QLatin1String("\\"), QLatin1String("\\\\")); + break; + } + case QVariant::Bool: + if (field.value().toBool()) + r = QLatin1String("TRUE"); + else + r = QLatin1String("FALSE"); + break; + case QVariant::ByteArray: { + QByteArray ba(field.value().toByteArray()); + size_t len; +#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200 + unsigned char *data = PQescapeByteaConn(d->connection, (unsigned char*)ba.constData(), ba.size(), &len); +#else + unsigned char *data = PQescapeBytea((unsigned char*)ba.constData(), ba.size(), &len); +#endif + r += QLatin1Char('\''); + r += QLatin1String((const char*)data); + r += QLatin1Char('\''); + qPQfreemem(data); + break; + } + default: + r = QSqlDriver::formatValue(field, trimStrings); + break; + } + } + return r; +} + +QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +bool QPSQLDriver::isOpen() const +{ + return PQstatus(d->connection) == CONNECTION_OK; +} + +QPSQLDriver::Protocol QPSQLDriver::protocol() const +{ + return d->pro; +} + +bool QPSQLDriver::subscribeToNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open."); + return false; + } + + if (d->seid.contains(name)) { + qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.", + qPrintable(name)); + return false; + } + + int socket = PQsocket(d->connection); + if (socket) { + QString query = QString(QLatin1String("LISTEN %1")).arg(escapeIdentifier(name, QSqlDriver::TableName)); + if (PQresultStatus(PQexec(d->connection, + d->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()) + ) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d)); + return false; + } + + if (!d->sn) { + d->sn = new QSocketNotifier(socket, QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + } + } + + d->seid << name; + return true; +} + +bool QPSQLDriver::unsubscribeFromNotificationImplementation(const QString &name) +{ + if (!isOpen()) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open."); + return false; + } + + if (!d->seid.contains(name)) { + qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.", + qPrintable(name)); + return false; + } + + QString query = QString(QLatin1String("UNLISTEN %1")).arg(escapeIdentifier(name, QSqlDriver::TableName)); + if (PQresultStatus(PQexec(d->connection, + d->isUtf8 ? query.toUtf8().constData() + : query.toLocal8Bit().constData()) + ) != PGRES_COMMAND_OK) { + setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d)); + return false; + } + + d->seid.removeAll(name); + + if (d->seid.isEmpty()) { + disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int))); + delete d->sn; + d->sn = 0; + } + + return true; +} + +QStringList QPSQLDriver::subscribedToNotificationsImplementation() const +{ + return d->seid; +} + +void QPSQLDriver::_q_handleNotification(int) +{ + PQconsumeInput(d->connection); + PGnotify *notify = PQnotifies(d->connection); + if (notify) { + QString name(QLatin1String(notify->relname)); + + if (d->seid.contains(name)) + emit notification(name); + else + qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.", + qPrintable(name)); + + qPQfreemem(notify); + } +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/psql/qsql_psql.h b/src/sql/drivers/psql/qsql_psql.h new file mode 100644 index 0000000..ca4dedf --- /dev/null +++ b/src/sql/drivers/psql/qsql_psql.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_PSQL_H +#define QSQL_PSQL_H + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_PSQL +#else +#define Q_EXPORT_SQLDRIVER_PSQL Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +typedef struct pg_conn PGconn; +typedef struct pg_result PGresult; + +QT_BEGIN_NAMESPACE + +class QPSQLResultPrivate; +class QPSQLDriverPrivate; +class QPSQLDriver; +class QSqlRecordInfo; + +class QPSQLResult : public QSqlResult +{ + friend class QPSQLResultPrivate; +public: + QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p); + ~QPSQLResult(); + + QVariant handle() const; + void virtual_hook(int id, void *data); + +protected: + void cleanup(); + bool fetch(int i); + bool fetchFirst(); + bool fetchLast(); + QVariant data(int i); + bool isNull(int field); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + QVariant lastInsertId() const; + bool prepare(const QString& query); + bool exec(); + +private: + QPSQLResultPrivate *d; +}; + +class Q_EXPORT_SQLDRIVER_PSQL QPSQLDriver : public QSqlDriver +{ + Q_OBJECT +public: + enum Protocol { + Version6 = 6, + Version7 = 7, + Version71 = 8, + Version73 = 9, + Version74 = 10, + Version8 = 11, + Version81 = 12, + Version82 = 13 + }; + + explicit QPSQLDriver(QObject *parent=0); + explicit QPSQLDriver(PGconn *conn, QObject *parent=0); + ~QPSQLDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts); + bool isOpen() const; + void close(); + QSqlResult *createResult() const; + QStringList tables(QSql::TableType) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QSqlRecord record(const QString& tablename) const; + + Protocol protocol() const; + QVariant handle() const; + + QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + QString formatValue(const QSqlField &field, bool trimStrings) const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + +protected Q_SLOTS: + bool subscribeToNotificationImplementation(const QString &name); + bool unsubscribeFromNotificationImplementation(const QString &name); + QStringList subscribedToNotificationsImplementation() const; + +private Q_SLOTS: + void _q_handleNotification(int); + +private: + void init(); + QPSQLDriverPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_PSQL_H diff --git a/src/sql/drivers/sqlite/qsql_sqlite.cpp b/src/sql/drivers/sqlite/qsql_sqlite.cpp new file mode 100644 index 0000000..605c4e8 --- /dev/null +++ b/src/sql/drivers/sqlite/qsql_sqlite.cpp @@ -0,0 +1,695 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qstringlist.h> +#include <qvector.h> +#include <qdebug.h> + +#if defined Q_OS_WIN +# include <qt_windows.h> +#else +# include <unistd.h> +#endif + +#include <sqlite3.h> + +Q_DECLARE_METATYPE(sqlite3*) +Q_DECLARE_METATYPE(sqlite3_stmt*) + +QT_BEGIN_NAMESPACE + +static QVariant::Type qGetColumnType(const QString &tpName) +{ + const QString typeName = tpName.toLower(); + + if (typeName == QLatin1String("integer") + || typeName == QLatin1String("int")) + return QVariant::Int; + if (typeName == QLatin1String("double") + || typeName == QLatin1String("float") + || typeName.startsWith(QLatin1String("numeric"))) + return QVariant::Double; + if (typeName == QLatin1String("blob")) + return QVariant::ByteArray; + return QVariant::String; +} + +static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, + int errorCode = -1) +{ + return QSqlError(descr, + QString::fromUtf16(static_cast<const ushort *>(sqlite3_errmsg16(access))), + type, errorCode); +} + +class QSQLiteDriverPrivate +{ +public: + inline QSQLiteDriverPrivate() : access(0) {} + sqlite3 *access; +}; + + +class QSQLiteResultPrivate +{ +public: + QSQLiteResultPrivate(QSQLiteResult *res); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + // initializes the recordInfo and the cache + void initColumns(bool emptyResultset); + void finalize(); + + QSQLiteResult* q; + sqlite3 *access; + + sqlite3_stmt *stmt; + + uint skippedStatus: 1; // the status of the fetchNext() that's skipped + uint skipRow: 1; // skip the next fetchNext()? + uint utf8: 1; + QSqlRecord rInf; + QSql::NumericalPrecisionPolicy precisionPolicy; +}; + +static const uint initial_cache_size = 128; + +QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0), + stmt(0), skippedStatus(false), skipRow(false), utf8(false), precisionPolicy(QSql::HighPrecision) +{ +} + +void QSQLiteResultPrivate::cleanup() +{ + finalize(); + rInf.clear(); + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLiteResultPrivate::finalize() +{ + if (!stmt) + return; + + sqlite3_finalize(stmt); + stmt = 0; +} + +void QSQLiteResultPrivate::initColumns(bool emptyResultset) +{ + int nCols = sqlite3_column_count(stmt); + if (nCols <= 0) + return; + + q->init(nCols); + + for (int i = 0; i < nCols; ++i) { + QString colName = QString::fromUtf16( + static_cast<const ushort *>(sqlite3_column_name16(stmt, i)) + ).remove(QLatin1Char('"')); + + // must use typeName for resolving the type to match QSqliteDriver::record + QString typeName = QString::fromUtf16( + static_cast<const ushort *>(sqlite3_column_decltype16(stmt, i))); + + int dotIdx = colName.lastIndexOf(QLatin1Char('.')); + QSqlField fld(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1), qGetColumnType(typeName)); + + // sqlite3_column_type is documented to have undefined behavior if the result set is empty + int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); + fld.setSqlType(stp); + rInf.append(fld); + } +} + +bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + return skippedStatus; + } + skipRow = initialFetch; + + if (!stmt) { + q->setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"), + QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError)); + q->setAt(QSql::AfterLastRow); + return false; + } + res = sqlite3_step(stmt); + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + initColumns(false); + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < rInf.count(); ++i) { + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_BLOB: + values[i + idx] = QByteArray(static_cast<const char *>( + sqlite3_column_blob(stmt, i)), + sqlite3_column_bytes(stmt, i)); + break; + case SQLITE_INTEGER: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case SQLITE_FLOAT: + switch(precisionPolicy) { + case QSql::LowPrecisionInt32: + values[i + idx] = sqlite3_column_int(stmt, i); + break; + case QSql::LowPrecisionInt64: + values[i + idx] = sqlite3_column_int64(stmt, i); + break; + case QSql::LowPrecisionDouble: + values[i + idx] = sqlite3_column_double(stmt, i); + break; + case QSql::HighPrecision: + default: + values[i + idx] = QString::fromUtf16(static_cast<const ushort *>( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(ushort)); + break; + }; + break; + case SQLITE_NULL: + values[i + idx] = QVariant(QVariant::String); + break; + default: + values[i + idx] = QString::fromUtf16(static_cast<const ushort *>( + sqlite3_column_text16(stmt, i)), + sqlite3_column_bytes16(stmt, i) / sizeof(ushort)); + break; + } + } + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + initColumns(true); + q->setAt(QSql::AfterLastRow); + sqlite3_reset(stmt); + return false; + case SQLITE_ERROR: + // SQLITE_ERROR is a generic error code and we must call sqlite3_reset() + // to get the specific error message. + res = sqlite3_reset(stmt); + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + // something wrong, don't get col info, but still return false + q->setLastError(qMakeError(access, QCoreApplication::translate("QSQLiteResult", + "Unable to fetch row"), QSqlError::ConnectionError, res)); + sqlite3_reset(stmt); + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db) + : QSqlCachedResult(db) +{ + d = new QSQLiteResultPrivate(this); + d->access = db->d->access; +} + +QSQLiteResult::~QSQLiteResult() +{ + d->cleanup(); + delete d; +} + +void QSQLiteResult::virtual_hook(int id, void *data) +{ + switch (id) { + case QSqlResult::DetachFromResultSet: + if (d->stmt) + sqlite3_reset(d->stmt); + break; + case QSqlResult::SetNumericalPrecision: + Q_ASSERT(data); + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +bool QSQLiteResult::reset(const QString &query) +{ + if (!prepare(query)) + return false; + return exec(); +} + +bool QSQLiteResult::prepare(const QString &query) +{ + if (!driver() || !driver()->isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + setSelect(false); + +#if (SQLITE_VERSION_NUMBER >= 3003011) + int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, 0); +#else + int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), + &d->stmt, 0); +#endif + + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to execute statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + return true; +} + +bool QSQLiteResult::exec() +{ + const QVector<QVariant> values = boundValues(); + + d->skippedStatus = false; + d->skipRow = false; + d->rInf.clear(); + clearValues(); + setLastError(QSqlError()); + + int res = sqlite3_reset(d->stmt); + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to reset statement"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + int paramCount = sqlite3_bind_parameter_count(d->stmt); + if (paramCount == values.count()) { + for (int i = 0; i < paramCount; ++i) { + res = SQLITE_OK; + const QVariant value = values.at(i); + + if (value.isNull()) { + res = sqlite3_bind_null(d->stmt, i + 1); + } else { + switch (value.type()) { + case QVariant::ByteArray: { + const QByteArray *ba = static_cast<const QByteArray*>(value.constData()); + res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(), + ba->size(), SQLITE_STATIC); + break; } + case QVariant::Int: + res = sqlite3_bind_int(d->stmt, i + 1, value.toInt()); + break; + case QVariant::Double: + res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble()); + break; + case QVariant::UInt: + case QVariant::LongLong: + res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong()); + break; + case QVariant::String: { + // lifetime of string == lifetime of its qvariant + const QString *str = static_cast<const QString*>(value.constData()); + res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(), + (str->size()) * sizeof(QChar), SQLITE_STATIC); + break; } + default: { + QString str = value.toString(); + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(), + (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + break; } + } + } + if (res != SQLITE_OK) { + setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", + "Unable to bind parameters"), QSqlError::StatementError, res)); + d->finalize(); + return false; + } + } + } else { + setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", + "Parameter count mismatch"), QString(), QSqlError::StatementError)); + return false; + } + d->skippedStatus = d->fetchNext(cache(), 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + return d->fetchNext(row, idx, false); +} + +int QSQLiteResult::size() +{ + return -1; +} + +int QSQLiteResult::numRowsAffected() +{ + return sqlite3_changes(d->access); +} + +QVariant QSQLiteResult::lastInsertId() const +{ + if (isActive()) { + qint64 id = sqlite3_last_insert_rowid(d->access); + if (id) + return id; + } + return QVariant(); +} + +QSqlRecord QSQLiteResult::record() const +{ + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +QVariant QSQLiteResult::handle() const +{ + return qVariantFromValue(d->stmt); +} + +///////////////////////////////////////////////////////// + +QSQLiteDriver::QSQLiteDriver(QObject * parent) + : QSqlDriver(parent) +{ + d = new QSQLiteDriverPrivate(); +} + +QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent) + : QSqlDriver(parent) +{ + d = new QSQLiteDriverPrivate(); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLiteDriver::~QSQLiteDriver() +{ + delete d; +} + +bool QSQLiteDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case BLOB: + case Transactions: + case Unicode: + case LastInsertId: + case PreparedQueries: + case PositionalPlaceholders: + case SimpleLocking: + case FinishQuery: + return true; + case QuerySize: + case NamedPlaceholders: + case BatchOperations: + case LowPrecisionNumbers: + case EventNotifications: + case MultipleResultSets: + return false; + } + return false; +} + +static int qGetSqliteTimeout(QString opts) +{ + enum { DefaultTimeout = 5000 }; + + opts.remove(QLatin1Char(' ')); + if (opts.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { + bool ok; + int nt = opts.mid(21).toInt(&ok); + if (ok) + return nt; + } + return DefaultTimeout; +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts) +{ + if (isOpen()) + close(); + + if (db.isEmpty()) + return false; + + if (sqlite3_open16(db.constData(), &d->access) == SQLITE_OK) { + sqlite3_busy_timeout(d->access, qGetSqliteTimeout(conOpts)); + setOpen(true); + setOpenError(false); + return true; + } else { + setLastError(qMakeError(d->access, tr("Error opening database"), + QSqlError::ConnectionError)); + setOpenError(true); + return false; + } +} + +void QSQLiteDriver::close() +{ + if (isOpen()) { + if (sqlite3_close(d->access) != SQLITE_OK) + setLastError(qMakeError(d->access, tr("Error closing database"), + QSqlError::ConnectionError)); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLiteDriver::createResult() const +{ + return new QSQLiteResult(this); +} + +bool QSQLiteDriver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("BEGIN"))) { + setLastError(QSqlError(tr("Unable to begin transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("COMMIT"))) { + setLastError(QSqlError(tr("Unable to commit transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +bool QSQLiteDriver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + QSqlQuery q(createResult()); + if (!q.exec(QLatin1String("ROLLBACK"))) { + setLastError(QSqlError(tr("Unable to rollback transaction"), + q.lastError().databaseText(), QSqlError::TransactionError)); + return false; + } + + return true; +} + +QStringList QSQLiteDriver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + + QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 " + "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1"); + if ((type & QSql::Tables) && (type & QSql::Views)) + sql = sql.arg(QLatin1String("type='table' OR type='view'")); + else if (type & QSql::Tables) + sql = sql.arg(QLatin1String("type='table'")); + else if (type & QSql::Views) + sql = sql.arg(QLatin1String("type='view'")); + else + sql.clear(); + + if (!sql.isEmpty() && q.exec(sql)) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false) +{ + QString schema; + QString table(tableName); + int indexOfSeparator = tableName.indexOf(QLatin1String(".")); + if (indexOfSeparator > -1) { + schema = tableName.left(indexOfSeparator).append(QLatin1String(".")); + table = tableName.mid(indexOfSeparator + 1); + } + q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info ('") + table + QLatin1String("')")); + + QSqlIndex ind; + while (q.next()) { + bool isPk = q.value(5).toInt(); + if (onlyPIndex && !isPk) + continue; + QString typeName = q.value(2).toString().toLower(); + QSqlField fld(q.value(1).toString(), qGetColumnType(typeName)); + if (isPk && (typeName == QLatin1String("integer"))) + // INTEGER PRIMARY KEY fields are auto-generated in sqlite + // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY! + fld.setAutoValue(true); + fld.setRequired(q.value(3).toInt() != 0); + fld.setDefaultValue(q.value(4)); + ind.append(fld); + } + return ind; +} + +QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const +{ + if (!isOpen()) + return QSqlIndex(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, tblname, true); +} + +QSqlRecord QSQLiteDriver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + return qGetTableInfo(q, tbl); +} + +QVariant QSQLiteDriver::handle() const +{ + return qVariantFromValue(d->access); +} + +QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType /*type*/) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/sqlite/qsql_sqlite.h b/src/sql/drivers/sqlite/qsql_sqlite.h new file mode 100644 index 0000000..459ea3b --- /dev/null +++ b/src/sql/drivers/sqlite/qsql_sqlite.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE_H +#define QSQL_SQLITE_H + +#include <QtSql/qsqldriver.h> +#include <QtSql/qsqlresult.h> +#include <QtSql/private/qsqlcachedresult_p.h> + +struct sqlite3; + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_SQLITE +#else +#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +class QSQLiteDriverPrivate; +class QSQLiteResultPrivate; +class QSQLiteDriver; + +class QSQLiteResult : public QSqlCachedResult +{ + friend class QSQLiteDriver; + friend class QSQLiteResultPrivate; +public: + explicit QSQLiteResult(const QSQLiteDriver* db); + ~QSQLiteResult(); + QVariant handle() const; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx); + bool reset(const QString &query); + bool prepare(const QString &query); + bool exec(); + int size(); + int numRowsAffected(); + QVariant lastInsertId() const; + QSqlRecord record() const; + void virtual_hook(int id, void *data); + +private: + QSQLiteResultPrivate* d; +}; + +class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver +{ + Q_OBJECT + friend class QSQLiteResult; +public: + explicit QSQLiteDriver(QObject *parent = 0); + explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0); + ~QSQLiteDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts); + void close(); + QSqlResult *createResult() const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QStringList tables(QSql::TableType) const; + + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString &table) const; + QVariant handle() const; + QString escapeIdentifier(const QString &identifier, IdentifierType) const; + +private: + QSQLiteDriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_SQLITE_H diff --git a/src/sql/drivers/sqlite2/qsql_sqlite2.cpp b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp new file mode 100644 index 0000000..ff73caa --- /dev/null +++ b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp @@ -0,0 +1,558 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite2.h" + +#include <qcoreapplication.h> +#include <qvariant.h> +#include <qdatetime.h> +#include <qfile.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qstringlist.h> +#include <qvector.h> + +#if !defined Q_WS_WIN32 +# include <unistd.h> +#endif +#include <sqlite.h> + +typedef struct sqlite_vm sqlite_vm; + +Q_DECLARE_METATYPE(sqlite_vm*) +Q_DECLARE_METATYPE(sqlite*) + +QT_BEGIN_NAMESPACE + +static QVariant::Type nameToType(const QString& typeName) +{ + QString tName = typeName.toUpper(); + if (tName.startsWith(QLatin1String("INT"))) + return QVariant::Int; + if (tName.startsWith(QLatin1String("FLOAT")) || tName.startsWith(QLatin1String("NUMERIC"))) + return QVariant::Double; + if (tName.startsWith(QLatin1String("BOOL"))) + return QVariant::Bool; + // SQLite is typeless - consider everything else as string + return QVariant::String; +} + +class QSQLite2DriverPrivate +{ +public: + QSQLite2DriverPrivate(); + sqlite *access; + bool utf8; +}; + +QSQLite2DriverPrivate::QSQLite2DriverPrivate() : access(0) +{ + utf8 = (qstrcmp(sqlite_encoding, "UTF-8") == 0); +} + +class QSQLite2ResultPrivate +{ +public: + QSQLite2ResultPrivate(QSQLite2Result *res); + void cleanup(); + bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch); + bool isSelect(); + // initializes the recordInfo and the cache + void init(const char **cnames, int numCols); + void finalize(); + + QSQLite2Result* q; + sqlite *access; + + // and we have too keep our own struct for the data (sqlite works via + // callback. + const char *currentTail; + sqlite_vm *currentMachine; + + uint skippedStatus: 1; // the status of the fetchNext() that's skipped + uint skipRow: 1; // skip the next fetchNext()? + uint utf8: 1; + QSqlRecord rInf; + QSql::NumericalPrecisionPolicy precisionPolicy; +}; + +static const uint initial_cache_size = 128; + +QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result* res) : q(res), access(0), currentTail(0), + currentMachine(0), skippedStatus(false), skipRow(false), utf8(false), precisionPolicy(QSql::HighPrecision) +{ +} + +void QSQLite2ResultPrivate::cleanup() +{ + finalize(); + rInf.clear(); + currentTail = 0; + currentMachine = 0; + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLite2ResultPrivate::finalize() +{ + if (!currentMachine) + return; + + char* err = 0; + int res = sqlite_finalize(currentMachine, &err); + if (err) { + q->setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result", + "Unable to fetch results"), QString::fromAscii(err), + QSqlError::StatementError, res)); + sqlite_freemem(err); + } + currentMachine = 0; +} + +// called on first fetch +void QSQLite2ResultPrivate::init(const char **cnames, int numCols) +{ + if (!cnames) + return; + + rInf.clear(); + if (numCols <= 0) + return; + q->init(numCols); + + for (int i = 0; i < numCols; ++i) { + const char* lastDot = strrchr(cnames[i], '.'); + const char* fieldName = lastDot ? lastDot + 1 : cnames[i]; + rInf.append(QSqlField(QString::fromAscii(fieldName), + nameToType(QString::fromAscii(cnames[i+numCols])))); + } +} + +bool QSQLite2ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + // may be caching. + const char **fvals; + const char **cnames; + int colNum; + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + return skippedStatus; + } + skipRow = initialFetch; + + if (!currentMachine) + return false; + + // keep trying while busy, wish I could implement this better. + while ((res = sqlite_step(currentMachine, &colNum, &fvals, &cnames)) == SQLITE_BUSY) { + // sleep instead requesting result again immidiately. +#if defined Q_WS_WIN32 + Sleep(1000); +#else + sleep(1); +#endif + } + + switch(res) { + case SQLITE_ROW: + // check to see if should fill out columns + if (rInf.isEmpty()) + // must be first call. + init(cnames, colNum); + if (!fvals) + return false; + if (idx < 0 && !initialFetch) + return true; + for (i = 0; i < colNum; ++i) + values[i + idx] = utf8 ? QString::fromUtf8(fvals[i]) : QString::fromAscii(fvals[i]); + return true; + case SQLITE_DONE: + if (rInf.isEmpty()) + // must be first call. + init(cnames, colNum); + q->setAt(QSql::AfterLastRow); + return false; + case SQLITE_ERROR: + case SQLITE_MISUSE: + default: + // something wrong, don't get col info, but still return false + finalize(); // finalize to get the error message. + q->setAt(QSql::AfterLastRow); + return false; + } + return false; +} + +QSQLite2Result::QSQLite2Result(const QSQLite2Driver* db) +: QSqlCachedResult(db) +{ + d = new QSQLite2ResultPrivate(this); + d->access = db->d->access; + d->utf8 = db->d->utf8; +} + +QSQLite2Result::~QSQLite2Result() +{ + d->cleanup(); + delete d; +} + +void QSQLite2Result::virtual_hook(int id, void *data) +{ + switch (id) { + case QSqlResult::DetachFromResultSet: + d->finalize(); + break; + case QSqlResult::SetNumericalPrecision: + Q_ASSERT(data); + d->precisionPolicy = *reinterpret_cast<QSql::NumericalPrecisionPolicy *>(data); + break; + default: + QSqlResult::virtual_hook(id, data); + } +} + +/* + Execute \a query. +*/ +bool QSQLite2Result::reset (const QString& query) +{ + // this is where we build a query. + if (!driver()) + return false; + if (!driver()-> isOpen() || driver()->isOpenError()) + return false; + + d->cleanup(); + + // Um, ok. callback based so.... pass private static function for this. + setSelect(false); + char *err = 0; + int res = sqlite_compile(d->access, + d->utf8 ? query.toUtf8().constData() + : query.toAscii().constData(), + &(d->currentTail), + &(d->currentMachine), + &err); + if (res != SQLITE_OK || err) { + setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result", + "Unable to execute statement"), QString::fromAscii(err), + QSqlError::StatementError, res)); + sqlite_freemem(err); + } + //if (*d->currentTail != '\000' then there is more sql to eval + if (!d->currentMachine) { + setActive(false); + return false; + } + // we have to fetch one row to find out about + // the structure of the result set + d->skippedStatus = d->fetchNext(cache(), 0, true); + if (lastError().isValid()) { + setSelect(false); + setActive(false); + return false; + } + setSelect(!d->rInf.isEmpty()); + setActive(true); + return true; +} + +bool QSQLite2Result::gotoNext(QSqlCachedResult::ValueCache& row, int idx) +{ + return d->fetchNext(row, idx, false); +} + +int QSQLite2Result::size() +{ + return -1; +} + +int QSQLite2Result::numRowsAffected() +{ + return sqlite_changes(d->access); +} + +QSqlRecord QSQLite2Result::record() const +{ + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +QVariant QSQLite2Result::handle() const +{ + return qVariantFromValue(d->currentMachine); +} + +///////////////////////////////////////////////////////// + +QSQLite2Driver::QSQLite2Driver(QObject * parent) + : QSqlDriver(parent) +{ + d = new QSQLite2DriverPrivate(); +} + +QSQLite2Driver::QSQLite2Driver(sqlite *connection, QObject *parent) + : QSqlDriver(parent) +{ + d = new QSQLite2DriverPrivate(); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLite2Driver::~QSQLite2Driver() +{ + delete d; +} + +bool QSQLite2Driver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case SimpleLocking: + return true; + case Unicode: + return d->utf8; + default: + return false; + } +} + +/* + SQLite dbs have no user name, passwords, hosts or ports. + just file names. +*/ +bool QSQLite2Driver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &) +{ + if (isOpen()) + close(); + + if (db.isEmpty()) + return false; + + char* err = 0; + d->access = sqlite_open(QFile::encodeName(db), 0, &err); + if (err) { + setLastError(QSqlError(tr("Error to open database"), QString::fromAscii(err), + QSqlError::ConnectionError)); + sqlite_freemem(err); + err = 0; + } + + if (d->access) { + setOpen(true); + setOpenError(false); + return true; + } + setOpenError(true); + return false; +} + +void QSQLite2Driver::close() +{ + if (isOpen()) { + sqlite_close(d->access); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLite2Driver::createResult() const +{ + return new QSQLite2Result(this); +} + +bool QSQLite2Driver::beginTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "BEGIN", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to begin transaction"), + QString::fromAscii(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::commitTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "COMMIT", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to commit transaction"), + QString::fromAscii(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::rollbackTransaction() +{ + if (!isOpen() || isOpenError()) + return false; + + char* err; + int res = sqlite_exec(d->access, "ROLLBACK", 0, this, &err); + + if (res == SQLITE_OK) + return true; + + setLastError(QSqlError(tr("Unable to rollback Transaction"), + QString::fromAscii(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +QStringList QSQLite2Driver::tables(QSql::TableType type) const +{ + QStringList res; + if (!isOpen()) + return res; + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + if ((type & QSql::Tables) && (type & QSql::Views)) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table' OR type='view'")); + else if (type & QSql::Tables) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table'")); + else if (type & QSql::Views) + q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='view'")); + + if (q.isActive()) { + while(q.next()) + res.append(q.value(0).toString()); + } + + if (type & QSql::SystemTables) { + // there are no internal tables beside this one: + res.append(QLatin1String("sqlite_master")); + } + + return res; +} + +QSqlIndex QSQLite2Driver::primaryIndex(const QString &tblname) const +{ + QSqlRecord rec(record(tblname)); // expensive :( + + if (!isOpen()) + return QSqlIndex(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + // finrst find a UNIQUE INDEX + q.exec(QLatin1String("PRAGMA index_list('") + tblname + QLatin1String("');")); + QString indexname; + while(q.next()) { + if (q.value(2).toInt()==1) { + indexname = q.value(1).toString(); + break; + } + } + if (indexname.isEmpty()) + return QSqlIndex(); + + q.exec(QLatin1String("PRAGMA index_info('") + indexname + QLatin1String("');")); + + QSqlIndex index(tblname, indexname); + while(q.next()) { + QString name = q.value(2).toString(); + QVariant::Type type = QVariant::Invalid; + if (rec.contains(name)) + type = rec.field(name).type(); + index.append(QSqlField(name, type)); + } + return index; +} + +QSqlRecord QSQLite2Driver::record(const QString &tbl) const +{ + if (!isOpen()) + return QSqlRecord(); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("SELECT * FROM ") + tbl + QLatin1String(" LIMIT 1")); + return q.record(); +} + +QVariant QSQLite2Driver::handle() const +{ + return qVariantFromValue(d->access); +} + +QString QSQLite2Driver::escapeIdentifier(const QString &identifier, IdentifierType /*type*/) const +{ + QString res = identifier; + if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/sqlite2/qsql_sqlite2.h b/src/sql/drivers/sqlite2/qsql_sqlite2.h new file mode 100644 index 0000000..9f039e2 --- /dev/null +++ b/src/sql/drivers/sqlite2/qsql_sqlite2.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_SQLITE2_H +#define QSQL_SQLITE2_H + +#include <QtSql/qsqldriver.h> +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqlrecord.h> +#include <QtSql/qsqlindex.h> +#include <QtSql/private/qsqlcachedresult_p.h> + +#if defined (Q_OS_WIN32) +# include <QtCore/qt_windows.h> +#endif + +struct sqlite; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSQLite2DriverPrivate; +class QSQLite2ResultPrivate; +class QSQLite2Driver; + +class QSQLite2Result : public QSqlCachedResult +{ + friend class QSQLite2Driver; + friend class QSQLite2ResultPrivate; +public: + explicit QSQLite2Result(const QSQLite2Driver* db); + ~QSQLite2Result(); + QVariant handle() const; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache& row, int idx); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + void virtual_hook(int id, void *data); + +private: + QSQLite2ResultPrivate* d; +}; + +class QSQLite2Driver : public QSqlDriver +{ + Q_OBJECT + friend class QSQLite2Result; +public: + explicit QSQLite2Driver(QObject *parent = 0); + explicit QSQLite2Driver(sqlite *connection, QObject *parent = 0); + ~QSQLite2Driver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString & connOpts); + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port) { return open (db, user, password, host, port, QString()); } + void close(); + QSqlResult *createResult() const; + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + QStringList tables(QSql::TableType) const; + + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString &table) const; + QVariant handle() const; + QString escapeIdentifier(const QString &identifier, IdentifierType) const; + +private: + QSQLite2DriverPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_SQLITE2_H diff --git a/src/sql/drivers/tds/qsql_tds.cpp b/src/sql/drivers/tds/qsql_tds.cpp new file mode 100644 index 0000000..46e4a0b --- /dev/null +++ b/src/sql/drivers/tds/qsql_tds.cpp @@ -0,0 +1,797 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> +#ifdef Q_OS_WIN32 // We assume that MS SQL Server is used. Set Q_USE_SYBASE to force Sybase. +// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h +#define _WINSCARD_H_ +#include <windows.h> +#else +#define Q_USE_SYBASE +#endif + +#include "qsql_tds.h" + +#include <qvariant.h> +#include <qdatetime.h> +#include <qhash.h> +#include <qregexp.h> +#include <qsqlerror.h> +#include <qsqlfield.h> +#include <qsqlindex.h> +#include <qsqlquery.h> +#include <qstringlist.h> +#include <qvector.h> + +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +#ifdef DBNTWIN32 +#define QMSGHANDLE DBMSGHANDLE_PROC +#define QERRHANDLE DBERRHANDLE_PROC +#define QTDSCHAR SQLCHAR +#define QTDSDATETIME4 SQLDATETIM4 +#define QTDSDATETIME SQLDATETIME +#define QTDSDATETIME_N SQLDATETIMN +#define QTDSDECIMAL SQLDECIMAL +#define QTDSFLT4 SQLFLT4 +#define QTDSFLT8 SQLFLT8 +#define QTDSFLT8_N SQLFLTN +#define QTDSINT1 SQLINT1 +#define QTDSINT2 SQLINT2 +#define QTDSINT4 SQLINT4 +#define QTDSINT4_N SQLINTN +#define QTDSMONEY4 SQLMONEY4 +#define QTDSMONEY SQLMONEY +#define QTDSMONEY_N SQLMONEYN +#define QTDSNUMERIC SQLNUMERIC +#define QTDSTEXT SQLTEXT +#define QTDSVARCHAR SQLVARCHAR +#define QTDSBIT SQLBIT +#define QTDSBINARY SQLBINARY +#define QTDSVARBINARY SQLVARBINARY +#define QTDSIMAGE SQLIMAGE +#else +#define QMSGHANDLE MHANDLEFUNC +#define QERRHANDLE EHANDLEFUNC +#define QTDSCHAR SYBCHAR +#define QTDSDATETIME4 SYBDATETIME4 +#define QTDSDATETIME SYBDATETIME +#define QTDSDATETIME_N SYBDATETIMN +#define QTDSDECIMAL SYBDECIMAL +#define QTDSFLT8 SYBFLT8 +#define QTDSFLT8_N SYBFLTN +#define QTDSFLT4 SYBREAL +#define QTDSINT1 SYBINT1 +#define QTDSINT2 SYBINT2 +#define QTDSINT4 SYBINT4 +#define QTDSINT4_N SYBINTN +#define QTDSMONEY4 SYBMONEY4 +#define QTDSMONEY SYBMONEY +#define QTDSMONEY_N SYBMONEYN +#define QTDSNUMERIC SYBNUMERIC +#define QTDSTEXT SYBTEXT +#define QTDSVARCHAR SYBVARCHAR +#define QTDSBIT SYBBIT +#define QTDSBINARY SYBBINARY +#define QTDSVARBINARY SYBVARBINARY +#define QTDSIMAGE SYBIMAGE +// magic numbers not defined anywhere in Sybase headers +#define QTDSDECIMAL_2 55 +#define QTDSNUMERIC_2 63 +#endif //DBNTWIN32 + +#define TDS_CURSOR_SIZE 50 + +// workaround for FreeTDS +#ifndef CS_PUBLIC +#define CS_PUBLIC +#endif + +QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, int errNo = -1) +{ + return QSqlError(QLatin1String("QTDS: ") + err, QString(), type, errNo); +} + +class QTDSDriverPrivate +{ +public: + QTDSDriverPrivate(): login(0) {} + LOGINREC* login; // login information + QString hostName; + QString db; +}; + + +class QTDSResultPrivate +{ +public: + QTDSResultPrivate():login(0), dbproc(0) {} + LOGINREC* login; // login information + DBPROCESS* dbproc; // connection from app to server + QSqlError lastError; + void addErrorMsg(QString& errMsg) { errorMsgs.append(errMsg); } + QString getErrorMsgs() { return errorMsgs.join(QLatin1String("\n")); } + void clearErrorMsgs() { errorMsgs.clear(); } + QVector<void *> buffer; + QSqlRecord rec; + +private: + QStringList errorMsgs; +}; + +typedef QHash<DBPROCESS *, QTDSResultPrivate *> QTDSErrorHash; +Q_GLOBAL_STATIC(QTDSErrorHash, errs) + +extern "C" { +static int CS_PUBLIC qTdsMsgHandler (DBPROCESS* dbproc, + DBINT /*msgno*/, + int msgstate, + int severity, + char* msgtext, + char* /*srvname*/, + char* /*procname*/, + int /*line*/) +{ + QTDSResultPrivate* p = errs()->value(dbproc); + + if (!p) { +// ### umm... temporary disabled since this throws a lot of warnings... +// qWarning("QTDSDriver warning (%d): [%s] from server [%s]", msgstate, msgtext, srvname); + return INT_CANCEL; + } + + if (severity > 0) { + QString errMsg = QString(QLatin1String("%1 (%2)")).arg(QString::fromAscii(msgtext)).arg( + msgstate); + p->addErrorMsg(errMsg); + } + + return INT_CANCEL; +} + +static int CS_PUBLIC qTdsErrHandler(DBPROCESS* dbproc, + int /*severity*/, + int dberr, + int /*oserr*/, + char* dberrstr, + char* oserrstr) +{ + QTDSResultPrivate* p = errs()->value(dbproc); + if (!p) { + qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr); + return INT_CANCEL; + } + /* + * If the process is dead or NULL and + * we are not in the middle of logging in... + */ + if((dbproc == NULL || DBDEAD(dbproc))) { + qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr); + return INT_CANCEL; + } + + + QString errMsg = QString(QLatin1String("%1 %2\n")).arg(QString::fromAscii(dberrstr)).arg( + QString::fromAscii(oserrstr)); + errMsg += p->getErrorMsgs(); + p->lastError = qMakeError(errMsg, QSqlError::UnknownError, dberr); + p->clearErrorMsgs(); + + return INT_CANCEL ; +} + +} //extern "C" + + +QVariant::Type qDecodeTDSType(int type) +{ + QVariant::Type t = QVariant::Invalid; + switch (type) { + case QTDSCHAR: + case QTDSTEXT: + case QTDSVARCHAR: + t = QVariant::String; + break; + case QTDSINT1: + case QTDSINT2: + case QTDSINT4: + case QTDSINT4_N: + case QTDSBIT: + t = QVariant::Int; + break; + case QTDSFLT4: + case QTDSFLT8: + case QTDSFLT8_N: + case QTDSMONEY4: + case QTDSMONEY: + case QTDSDECIMAL: + case QTDSNUMERIC: +#ifdef QTDSNUMERIC_2 + case QTDSNUMERIC_2: +#endif +#ifdef QTDSDECIMAL_2 + case QTDSDECIMAL_2: +#endif + case QTDSMONEY_N: + t = QVariant::Double; + break; + case QTDSDATETIME4: + case QTDSDATETIME: + case QTDSDATETIME_N: + t = QVariant::DateTime; + break; + case QTDSBINARY: + case QTDSVARBINARY: + case QTDSIMAGE: + t = QVariant::ByteArray; + break; + default: + t = QVariant::Invalid; + break; + } + return t; +} + +QVariant::Type qFieldType(QTDSResultPrivate* d, int i) +{ + QVariant::Type type = qDecodeTDSType(dbcoltype(d->dbproc, i+1)); + return type; +} + + +QTDSResult::QTDSResult(const QTDSDriver* db) + : QSqlCachedResult(db) +{ + d = new QTDSResultPrivate(); + d->login = db->d->login; + + d->dbproc = dbopen(d->login, const_cast<char*>(db->d->hostName.toLatin1().constData())); + if (!d->dbproc) + return; + if (dbuse(d->dbproc, const_cast<char*>(db->d->db.toLatin1().constData())) == FAIL) + return; + + // insert d in error handler dict + errs()->insert(d->dbproc, d); +} + +QTDSResult::~QTDSResult() +{ + cleanup(); + if (d->dbproc) + dbclose(d->dbproc); + errs()->remove(d->dbproc); + delete d; +} + +void QTDSResult::cleanup() +{ + d->clearErrorMsgs(); + d->rec.clear(); + for (int i = 0; i < d->buffer.size() / 2; ++i) + free(d->buffer.at(i * 2)); + d->buffer.clear(); + // "can" stands for "cancel"... very clever. + dbcanquery(d->dbproc); + dbfreebuf(d->dbproc); + + QSqlCachedResult::cleanup(); +} + +QVariant QTDSResult::handle() const +{ + return QVariant(qRegisterMetaType<DBPROCESS *>("DBPROCESS*"), &d->dbproc); +} + +static inline bool qIsNull(const void *ind) +{ + return *reinterpret_cast<const DBINT *>(&ind) == -1; +} + +bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache &values, int index) +{ + STATUS stat = dbnextrow(d->dbproc); + if (stat == NO_MORE_ROWS) { + setAt(QSql::AfterLastRow); + return false; + } + if ((stat == FAIL) || (stat == BUF_FULL)) { + setLastError(d->lastError); + return false; + } + + if (index < 0) + return true; + + for (int i = 0; i < d->rec.count(); ++i) { + int idx = index + i; + switch (d->rec.field(i).type()) { + case QVariant::DateTime: + if (qIsNull(d->buffer.at(i * 2 + 1))) { + values[idx] = QVariant(QVariant::DateTime); + } else { + DBDATETIME *bdt = (DBDATETIME*) d->buffer.at(i * 2); + QDate date = QDate::fromString(QLatin1String("1900-01-01"), Qt::ISODate); + QTime time = QTime::fromString(QLatin1String("00:00:00"), Qt::ISODate); + values[idx] = QDateTime(date.addDays(bdt->dtdays), time.addMSecs(int(bdt->dttime / 0.3))); + } + break; + case QVariant::Int: + if (qIsNull(d->buffer.at(i * 2 + 1))) + values[idx] = QVariant(QVariant::Int); + else + values[idx] = *((int*)d->buffer.at(i * 2)); + break; + case QVariant::Double: + case QVariant::String: + if (qIsNull(d->buffer.at(i * 2 + 1))) + values[idx] = QVariant(QVariant::String); + else + values[idx] = QString::fromLocal8Bit((const char*)d->buffer.at(i * 2)); + break; + case QVariant::ByteArray: { + if (qIsNull(d->buffer.at(i * 2 + 1))) + values[idx] = QVariant(QVariant::ByteArray); + else + values[idx] = QByteArray((const char*)d->buffer.at(i * 2)); + break; + } + default: + // should never happen, and we already fired + // a warning while binding. + values[idx] = QVariant(); + break; + } + } + + return true; +} + +bool QTDSResult::reset (const QString& query) +{ + cleanup(); + if (!driver() || !driver()-> isOpen() || driver()->isOpenError()) + return false; + setActive(false); + setAt(QSql::BeforeFirstRow); + if (dbcmd(d->dbproc, const_cast<char*>(query.toLocal8Bit().constData())) == FAIL) { + setLastError(d->lastError); + return false; + } + + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbresults(d->dbproc) != SUCCEED) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + + setSelect((DBCMDROW(d->dbproc) == SUCCEED)); // decide whether or not we are dealing with a SELECT query + int numCols = dbnumcols(d->dbproc); + if (numCols > 0) { + d->buffer.resize(numCols * 2); + init(numCols); + } + for (int i = 0; i < numCols; ++i) { + int dbType = dbcoltype(d->dbproc, i+1); + QVariant::Type vType = qDecodeTDSType(dbType); + QSqlField f(QString::fromAscii(dbcolname(d->dbproc, i+1)), vType); + f.setSqlType(dbType); + f.setLength(dbcollen(d->dbproc, i+1)); + d->rec.append(f); + + RETCODE ret = -1; + void* p = 0; + switch (vType) { + case QVariant::Int: + p = malloc(4); + ret = dbbind(d->dbproc, i+1, INTBIND, (DBINT) 4, (unsigned char *)p); + break; + case QVariant::Double: + // use string binding to prevent loss of precision + p = malloc(50); + ret = dbbind(d->dbproc, i+1, STRINGBIND, 50, (unsigned char *)p); + break; + case QVariant::String: + p = malloc(dbcollen(d->dbproc, i+1) + 1); + ret = dbbind(d->dbproc, i+1, STRINGBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p); + break; + case QVariant::DateTime: + p = malloc(8); + ret = dbbind(d->dbproc, i+1, DATETIMEBIND, (DBINT) 8, (unsigned char *)p); + break; + case QVariant::ByteArray: + p = malloc(dbcollen(d->dbproc, i+1) + 1); + ret = dbbind(d->dbproc, i+1, BINARYBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p); + break; + default: //don't bind the field since we do not support it + qWarning("QTDSResult::reset: Unsupported type for field \"%s\"", dbcolname(d->dbproc, i+1)); + break; + } + if (ret == SUCCEED) { + d->buffer[i * 2] = p; + ret = dbnullbind(d->dbproc, i+1, (DBINT*)(&d->buffer[i * 2 + 1])); + } else { + d->buffer[i * 2] = 0; + d->buffer[i * 2 + 1] = 0; + free(p); + } + if ((ret != SUCCEED) && (ret != -1)) { + setLastError(d->lastError); + return false; + } + } + + setActive(true); + return true; +} + +int QTDSResult::size() +{ + return -1; +} + +int QTDSResult::numRowsAffected() +{ +#ifdef DBNTWIN32 + if (dbiscount(d->dbproc)) { + return DBCOUNT(d->dbproc); + } + return -1; +#else + return DBCOUNT(d->dbproc); +#endif +} + +QSqlRecord QTDSResult::record() const +{ + return d->rec; +} + +/////////////////////////////////////////////////////////////////// + +QTDSDriver::QTDSDriver(QObject* parent) + : QSqlDriver(parent) +{ + init(); +} + +QTDSDriver::QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent) + : QSqlDriver(parent) +{ + init(); + d->login = rec; + d->hostName = host; + d->db = db; + if (rec) { + setOpen(true); + setOpenError(false); + } +} + +QVariant QTDSDriver::handle() const +{ + return QVariant(qRegisterMetaType<LOGINREC *>("LOGINREC*"), &d->login); +} + +void QTDSDriver::init() +{ + d = new QTDSDriverPrivate(); + // the following two code-lines will fail compilation on some FreeTDS versions + // just comment them out if you have FreeTDS (you won't get any errors and warnings then) + dberrhandle((QERRHANDLE)qTdsErrHandler); + dbmsghandle((QMSGHANDLE)qTdsMsgHandler); +} + +QTDSDriver::~QTDSDriver() +{ + dberrhandle(0); + dbmsghandle(0); + // dbexit also calls dbclose if necessary + dbexit(); + delete d; +} + +bool QTDSDriver::hasFeature(DriverFeature f) const +{ + switch (f) { + case Transactions: + case QuerySize: + case Unicode: + case SimpleLocking: + case EventNotifications: + case MultipleResultSets: + return false; + case BLOB: + return true; + default: + return false; + } +} + +bool QTDSDriver::open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int /*port*/, + const QString& /*connOpts*/) +{ + if (isOpen()) + close(); + if (!dbinit()) { + setOpenError(true); + return false; + } + d->login = dblogin(); + if (!d->login) { + setOpenError(true); + return false; + } + DBSETLPWD(d->login, const_cast<char*>(password.toLocal8Bit().constData())); + DBSETLUSER(d->login, const_cast<char*>(user.toLocal8Bit().constData())); + + // Now, try to open and use the database. If this fails, return false. + DBPROCESS* dbproc; + + dbproc = dbopen(d->login, const_cast<char*>(host.toLatin1().constData())); + if (!dbproc) { + setLastError(qMakeError(tr("Unable to open connection"), QSqlError::ConnectionError, -1)); + setOpenError(true); + return false; + } + if (dbuse(dbproc, const_cast<char*>(db.toLatin1().constData())) == FAIL) { + setLastError(qMakeError(tr("Unable to use database"), QSqlError::ConnectionError, -1)); + setOpenError(true); + return false; + } + dbclose( dbproc ); + + setOpen(true); + setOpenError(false); + d->hostName = host; + d->db = db; + return true; +} + +void QTDSDriver::close() +{ + if (isOpen()) { +#ifdef Q_USE_SYBASE + dbloginfree(d->login); +#else + dbfreelogin(d->login); +#endif + d->login = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QTDSDriver::createResult() const +{ + return new QTDSResult(this); +} + +bool QTDSDriver::beginTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::beginTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "BEGIN TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = true; + return true; +*/ +} + +bool QTDSDriver::commitTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::commitTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "COMMIT TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = false; + return true; +*/ +} + +bool QTDSDriver::rollbackTransaction() +{ + return false; +/* + if (!isOpen()) { + qWarning("QTDSDriver::rollbackTransaction: Database not open"); + return false; + } + if (dbcmd(d->dbproc, "ROLLBACK TRANSACTION") == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + if (dbsqlexec(d->dbproc) == FAIL) { + setLastError(d->lastError); + dbfreebuf(d->dbproc); + return false; + } + while(dbresults(d->dbproc) == NO_MORE_RESULTS) {} + dbfreebuf(d->dbproc); + inTransaction = false; + return true; +*/ +} + +QSqlRecord QTDSDriver::record(const QString& tablename) const +{ + QSqlRecord info; + if (!isOpen()) + return info; + QSqlQuery t(createResult()); + t.setForwardOnly(true); + QString stmt (QLatin1String("select name, type, length, prec from syscolumns " + "where id = (select id from sysobjects where name = '%1')")); + t.exec(stmt.arg(tablename)); + while (t.next()) { + QSqlField f(t.value(0).toString().simplified(), qDecodeTDSType(t.value(1).toInt())); + f.setLength(t.value(2).toInt()); + f.setPrecision(t.value(3).toInt()); + f.setSqlType(t.value(1).toInt()); + info.append(f); + } + return info; +} + +QStringList QTDSDriver::tables(QSql::TableType type) const +{ + QStringList list; + + if (!isOpen()) + return list; + + QStringList typeFilter; + + if (type & QSql::Tables) + typeFilter += QLatin1String("type='U'"); + if (type & QSql::SystemTables) + typeFilter += QLatin1String("type='S'"); + if (type & QSql::Views) + typeFilter += QLatin1String("type='V'"); + + if (typeFilter.isEmpty()) + return list; + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + t.exec(QLatin1String("select name from sysobjects where ") + typeFilter.join(QLatin1String(" or "))); + while (t.next()) + list.append(t.value(0).toString().simplified()); + + return list; +} + +QString QTDSDriver::formatValue(const QSqlField &field, + bool trim) const +{ + QString r; + if (field.isNull()) + r = QLatin1String("NULL"); + else if (field.type() == QVariant::DateTime) { + if (field.value().toDateTime().isValid()){ + r = field.value().toDateTime().toString(QLatin1String("'yyyyMMdd hh:mm:ss'")); + } 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, trim); + } + return r; +} + +QSqlIndex QTDSDriver::primaryIndex(const QString& tablename) const +{ + QSqlRecord rec = record(tablename); + + QSqlIndex idx(tablename); + if ((!isOpen()) || (tablename.isEmpty())) + return QSqlIndex(); + + QSqlQuery t(createResult()); + t.setForwardOnly(true); + t.exec(QString::fromLatin1("sp_helpindex '%1'").arg(tablename)); + if (t.next()) { + QStringList fNames = t.value(2).toString().simplified().split(QLatin1Char(',')); + QRegExp regx(QLatin1String("\\s*(\\S+)(?:\\s+(DESC|desc))?\\s*")); + for(QStringList::Iterator it = fNames.begin(); it != fNames.end(); ++it) { + regx.indexIn(*it); + QSqlField f(regx.cap(1), rec.field(regx.cap(1)).type()); + if (regx.cap(2).toLower() == QLatin1String("desc")) { + idx.append(f, true); + } else { + idx.append(f, false); + } + } + idx.setName(t.value(0).toString().simplified()); + } + return idx; +} + +QT_END_NAMESPACE diff --git a/src/sql/drivers/tds/qsql_tds.h b/src/sql/drivers/tds/qsql_tds.h new file mode 100644 index 0000000..3a5dc65 --- /dev/null +++ b/src/sql/drivers/tds/qsql_tds.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_TDS_H +#define QSQL_TDS_H + +#include <QtSql/qsqlresult.h> +#include <QtSql/qsqldriver.h> +#include <QtSql/private/qsqlcachedresult_p.h> + +#ifdef Q_OS_WIN32 +#define WIN32_LEAN_AND_MEAN +#define DBNTWIN32 // indicates 32bit windows dblib +#include <QtCore/qt_windows.h> +#include <sqlfront.h> +#include <sqldb.h> +#define CS_PUBLIC +#else +#include <sybfront.h> +#include <sybdb.h> +#endif //Q_OS_WIN32 + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_TDS +#else +#define Q_EXPORT_SQLDRIVER_TDS Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QTDSDriverPrivate; +class QTDSResultPrivate; +class QTDSDriver; + +class QTDSResult : public QSqlCachedResult +{ +public: + explicit QTDSResult(const QTDSDriver* db); + ~QTDSResult(); + QVariant handle() const; + +protected: + void cleanup(); + bool reset (const QString& query); + int size(); + int numRowsAffected(); + bool gotoNext(QSqlCachedResult::ValueCache &values, int index); + QSqlRecord record() const; + +private: + QTDSResultPrivate* d; +}; + +class Q_EXPORT_SQLDRIVER_TDS QTDSDriver : public QSqlDriver +{ + Q_OBJECT + friend class QTDSResult; +public: + explicit QTDSDriver(QObject* parent = 0); + QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent = 0); + ~QTDSDriver(); + bool hasFeature(DriverFeature f) const; + bool open(const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts); + void close(); + QStringList tables(QSql::TableType) const; + QSqlResult *createResult() const; + QSqlRecord record(const QString& tablename) const; + QSqlIndex primaryIndex(const QString& tablename) const; + + QString formatValue(const QSqlField &field, + bool trimStrings) const; + QVariant handle() const; + +protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); +private: + void init(); + QTDSDriverPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_TDS_H diff --git a/src/sql/kernel/kernel.pri b/src/sql/kernel/kernel.pri new file mode 100644 index 0000000..c6fe404 --- /dev/null +++ b/src/sql/kernel/kernel.pri @@ -0,0 +1,24 @@ +HEADERS += kernel/qsql.h \ + kernel/qsqlquery.h \ + kernel/qsqldatabase.h \ + kernel/qsqlfield.h \ + kernel/qsqlrecord.h \ + kernel/qsqldriver.h \ + kernel/qsqlnulldriver_p.h \ + kernel/qsqldriverplugin.h \ + kernel/qsqlerror.h \ + kernel/qsqlresult.h \ + kernel/qsqlcachedresult_p.h \ + kernel/qsqlindex.h + +SOURCES += kernel/qsqlquery.cpp \ + kernel/qsqldatabase.cpp \ + kernel/qsqlfield.cpp \ + kernel/qsqlrecord.cpp \ + kernel/qsqldriver.cpp \ + kernel/qsqldriverplugin.cpp \ + kernel/qsqlerror.cpp \ + kernel/qsqlresult.cpp \ + kernel/qsqlindex.cpp \ + kernel/qsqlcachedresult.cpp + diff --git a/src/sql/kernel/qsql.h b/src/sql/kernel/qsql.h new file mode 100644 index 0000000..0e3388a --- /dev/null +++ b/src/sql/kernel/qsql.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_H +#define QSQL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +namespace QSql +{ + enum Location + { + BeforeFirstRow = -1, + AfterLastRow = -2 +#ifdef QT3_SUPPORT + , BeforeFirst = BeforeFirstRow, + AfterLast = AfterLastRow +#endif + }; + + enum ParamTypeFlag + { + In = 0x00000001, + Out = 0x00000002, + InOut = In | Out, + Binary = 0x00000004 + }; + Q_DECLARE_FLAGS(ParamType, ParamTypeFlag) + + enum TableType + { + Tables = 0x01, + SystemTables = 0x02, + Views = 0x04, + AllTables = 0xff + }; + + enum NumericalPrecisionPolicy + { + LowPrecisionInt32 = 0x01, + LowPrecisionInt64 = 0x02, + LowPrecisionDouble = 0x04, + + HighPrecision = 0 + }; + +#ifdef QT3_SUPPORT + enum Op { + None = -1, + Insert = 0, + Update = 1, + Delete = 2 + }; + + enum Confirm { + Cancel = -1, + No = 0, + Yes = 1 + }; +#endif +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSql::ParamType) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_H diff --git a/src/sql/kernel/qsqlcachedresult.cpp b/src/sql/kernel/qsqlcachedresult.cpp new file mode 100644 index 0000000..e85229f --- /dev/null +++ b/src/sql/kernel/qsqlcachedresult.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qsqlcachedresult_p.h" + +#include <qvariant.h> +#include <qdatetime.h> +#include <qvector.h> + +QT_BEGIN_NAMESPACE + +/* + QSqlCachedResult is a convenience class for databases that only allow + forward only fetching. It will cache all the results so we can iterate + backwards over the results again. + + All you need to do is to inherit from QSqlCachedResult and reimplement + gotoNext(). gotoNext() will have a reference to the internal cache and + will give you an index where you can start filling in your data. Special + case: If the user actually wants a forward-only query, idx will be -1 + to indicate that we are not interested in the actual values. +*/ + +static const uint initial_cache_size = 128; + +class QSqlCachedResultPrivate +{ +public: + QSqlCachedResultPrivate(); + bool canSeek(int i) const; + inline int cacheCount() const; + void init(int count, bool fo); + void cleanup(); + int nextIndex(); + void revertLast(); + + QSqlCachedResult::ValueCache cache; + int rowCacheEnd; + int colCount; + bool forwardOnly; + bool atEnd; +}; + +QSqlCachedResultPrivate::QSqlCachedResultPrivate(): + rowCacheEnd(0), colCount(0), forwardOnly(false), atEnd(false) +{ +} + +void QSqlCachedResultPrivate::cleanup() +{ + cache.clear(); + forwardOnly = false; + atEnd = false; + colCount = 0; + rowCacheEnd = 0; +} + +void QSqlCachedResultPrivate::init(int count, bool fo) +{ + Q_ASSERT(count); + cleanup(); + forwardOnly = fo; + colCount = count; + if (fo) { + cache.resize(count); + rowCacheEnd = count; + } else { + cache.resize(initial_cache_size * count); + } +} + +int QSqlCachedResultPrivate::nextIndex() +{ + if (forwardOnly) + return 0; + int newIdx = rowCacheEnd; + if (newIdx + colCount > cache.size()) + cache.resize(qMin(cache.size() * 2, cache.size() + 10000)); + rowCacheEnd += colCount; + + return newIdx; +} + +bool QSqlCachedResultPrivate::canSeek(int i) const +{ + if (forwardOnly || i < 0) + return false; + return rowCacheEnd >= (i + 1) * colCount; +} + +void QSqlCachedResultPrivate::revertLast() +{ + if (forwardOnly) + return; + rowCacheEnd -= colCount; +} + +inline int QSqlCachedResultPrivate::cacheCount() const +{ + Q_ASSERT(!forwardOnly); + Q_ASSERT(colCount); + return rowCacheEnd / colCount; +} + +////////////// + +QSqlCachedResult::QSqlCachedResult(const QSqlDriver * db): QSqlResult (db) +{ + d = new QSqlCachedResultPrivate(); +} + +QSqlCachedResult::~QSqlCachedResult() +{ + delete d; +} + +void QSqlCachedResult::init(int colCount) +{ + d->init(colCount, isForwardOnly()); +} + +bool QSqlCachedResult::fetch(int i) +{ + if ((!isActive()) || (i < 0)) + return false; + if (at() == i) + return true; + if (d->forwardOnly) { + // speed hack - do not copy values if not needed + if (at() > i || at() == QSql::AfterLastRow) + return false; + while(at() < i - 1) { + if (!gotoNext(d->cache, -1)) + return false; + setAt(at() + 1); + } + if (!gotoNext(d->cache, 0)) + return false; + setAt(at() + 1); + return true; + } + if (d->canSeek(i)) { + setAt(i); + return true; + } + if (d->rowCacheEnd > 0) + setAt(d->cacheCount()); + while (at() < i + 1) { + if (!cacheNext()) + return false; + } + setAt(i); + + return true; +} + +bool QSqlCachedResult::fetchNext() +{ + if (d->canSeek(at() + 1)) { + setAt(at() + 1); + return true; + } + return cacheNext(); +} + +bool QSqlCachedResult::fetchPrevious() +{ + return fetch(at() - 1); +} + +bool QSqlCachedResult::fetchFirst() +{ + if (d->forwardOnly && at() != QSql::BeforeFirstRow) { + return false; + } + if (d->canSeek(0)) { + setAt(0); + return true; + } + return cacheNext(); +} + +bool QSqlCachedResult::fetchLast() +{ + if (d->atEnd) { + if (d->forwardOnly) + return false; + else + return fetch(d->cacheCount() - 1); + } + + int i = at(); + while (fetchNext()) + ++i; /* brute force */ + if (d->forwardOnly && at() == QSql::AfterLastRow) { + setAt(i); + return true; + } else { + return fetch(i); + } +} + +QVariant QSqlCachedResult::data(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return QVariant(); + + return d->cache.at(idx); +} + +bool QSqlCachedResult::isNull(int i) +{ + int idx = d->forwardOnly ? i : at() * d->colCount + i; + if (i > d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd) + return true; + + return d->cache.at(idx).isNull(); +} + +void QSqlCachedResult::cleanup() +{ + setAt(QSql::BeforeFirstRow); + setActive(false); + d->cleanup(); +} + +void QSqlCachedResult::clearValues() +{ + setAt(QSql::BeforeFirstRow); + d->rowCacheEnd = 0; + d->atEnd = false; +} + +bool QSqlCachedResult::cacheNext() +{ + if (d->atEnd) + return false; + + if (!gotoNext(d->cache, d->nextIndex())) { + d->revertLast(); + d->atEnd = true; + return false; + } + setAt(at() + 1); + return true; +} + +int QSqlCachedResult::colCount() const +{ + return d->colCount; +} + +QSqlCachedResult::ValueCache &QSqlCachedResult::cache() +{ + return d->cache; +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlcachedresult_p.h b/src/sql/kernel/qsqlcachedresult_p.h new file mode 100644 index 0000000..8a4869c --- /dev/null +++ b/src/sql/kernel/qsqlcachedresult_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLCACHEDRESULT_P_H +#define QSQLCACHEDRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtSql/qsqlresult.h" + +QT_BEGIN_NAMESPACE + +class QVariant; +template <typename T> class QVector; + +class QSqlCachedResultPrivate; + +class Q_SQL_EXPORT QSqlCachedResult: public QSqlResult +{ +public: + virtual ~QSqlCachedResult(); + + typedef QVector<QVariant> ValueCache; + +protected: + QSqlCachedResult(const QSqlDriver * db); + + void init(int colCount); + void cleanup(); + void clearValues(); + + virtual bool gotoNext(ValueCache &values, int index) = 0; + + QVariant data(int i); + bool isNull(int i); + bool fetch(int i); + bool fetchNext(); + bool fetchPrevious(); + bool fetchFirst(); + bool fetchLast(); + + int colCount() const; + ValueCache &cache(); + +private: + bool cacheNext(); + QSqlCachedResultPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QSQLCACHEDRESULT_P_H diff --git a/src/sql/kernel/qsqldatabase.cpp b/src/sql/kernel/qsqldatabase.cpp new file mode 100644 index 0000000..6232452 --- /dev/null +++ b/src/sql/kernel/qsqldatabase.cpp @@ -0,0 +1,1487 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqldatabase.h" +#include "qsqlquery.h" + +#ifdef Q_OS_WIN32 +// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h +#define _WINSCARD_H_ +#endif + +#ifdef QT_SQL_PSQL +#include "../drivers/psql/qsql_psql.h" +#endif +#ifdef QT_SQL_MYSQL +#include "../drivers/mysql/qsql_mysql.h" +#endif +#ifdef QT_SQL_ODBC +#include "../drivers/odbc/qsql_odbc.h" +#endif +#ifdef QT_SQL_OCI +#include "../drivers/oci/qsql_oci.h" +#endif +#ifdef QT_SQL_TDS +#include "../drivers/tds/qsql_tds.h" +#endif +#ifdef QT_SQL_DB2 +#include "../drivers/db2/qsql_db2.h" +#endif +#ifdef QT_SQL_SQLITE +#include "../drivers/sqlite/qsql_sqlite.h" +#endif +#ifdef QT_SQL_SQLITE2 +#include "../drivers/sqlite2/qsql_sqlite2.h" +#endif +#ifdef QT_SQL_IBASE +#undef SQL_FLOAT // avoid clash with ODBC +#undef SQL_DOUBLE +#undef SQL_TIMESTAMP +#undef SQL_TYPE_TIME +#undef SQL_TYPE_DATE +#undef SQL_DATE +#define SCHAR IBASE_SCHAR // avoid clash with ODBC (older versions of ibase.h with Firebird) +#include "../drivers/ibase/qsql_ibase.h" +#undef SCHAR +#endif + +#include "qdebug.h" +#include "qcoreapplication.h" +#include "qreadwritelock.h" +#include "qsqlresult.h" +#include "qsqldriver.h" +#include "qsqldriverplugin.h" +#include "qsqlindex.h" +#include "private/qfactoryloader_p.h" +#include "private/qsqlnulldriver_p.h" +#include "qmutex.h" +#include "qhash.h" +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QSqlDriverFactoryInterface_iid, + QLatin1String("/sqldrivers"))) +#endif + +QT_STATIC_CONST_IMPL char *QSqlDatabase::defaultConnection = "qt_sql_default_connection"; + +typedef QHash<QString, QSqlDriverCreatorBase*> DriverDict; + +class QConnectionDict: public QHash<QString, QSqlDatabase> +{ +public: + inline bool contains_ts(const QString &key) + { + QReadLocker locker(&lock); + return contains(key); + } + inline QStringList keys_ts() const + { + QReadLocker locker(&lock); + return keys(); + } + + mutable QReadWriteLock lock; +}; +Q_GLOBAL_STATIC(QConnectionDict, dbDict) + +class QSqlDatabasePrivate +{ +public: + QSqlDatabasePrivate(QSqlDriver *dr = 0): + driver(dr), + port(-1) + { + ref = 1; + } + QSqlDatabasePrivate(const QSqlDatabasePrivate &other); + ~QSqlDatabasePrivate(); + void init(const QString& type); + void copy(const QSqlDatabasePrivate *other); + void disable(); + + QAtomicInt ref; + QSqlDriver* driver; + QString dbname; + QString uname; + QString pword; + QString hname; + QString drvName; + int port; + QString connOptions; + QString connName; + + static QSqlDatabasePrivate *shared_null(); + static QSqlDatabase database(const QString& name, bool open); + static void addDatabase(const QSqlDatabase &db, const QString & name); + static void removeDatabase(const QString& name); + static void invalidateDb(const QSqlDatabase &db, const QString &name); + static DriverDict &driverDict(); + static void cleanConnections(); +}; + +QSqlDatabasePrivate::QSqlDatabasePrivate(const QSqlDatabasePrivate &other) +{ + ref = 1; + dbname = other.dbname; + uname = other.uname; + pword = other.pword; + hname = other.hname; + drvName = other.drvName; + port = other.port; + connOptions = other.connOptions; + driver = other.driver; +} + +QSqlDatabasePrivate::~QSqlDatabasePrivate() +{ + if (driver != shared_null()->driver) + delete driver; +} + +void QSqlDatabasePrivate::cleanConnections() +{ + QConnectionDict *dict = dbDict(); + Q_ASSERT(dict); + QWriteLocker locker(&dict->lock); + + QConnectionDict::iterator it = dict->begin(); + while (it != dict->end()) { + invalidateDb(it.value(), it.key()); + ++it; + } + dict->clear(); +} + +static bool qDriverDictInit = false; +static void cleanDriverDict() +{ + qDeleteAll(QSqlDatabasePrivate::driverDict()); + QSqlDatabasePrivate::driverDict().clear(); + QSqlDatabasePrivate::cleanConnections(); + qDriverDictInit = false; +} + +DriverDict &QSqlDatabasePrivate::driverDict() +{ + static DriverDict dict; + if (!qDriverDictInit) { + qDriverDictInit = true; + qAddPostRoutine(cleanDriverDict); + } + return dict; +} + +QSqlDatabasePrivate *QSqlDatabasePrivate::shared_null() +{ + static QSqlNullDriver dr; + static QSqlDatabasePrivate n(&dr); + return &n; +} + +void QSqlDatabasePrivate::invalidateDb(const QSqlDatabase &db, const QString &name) +{ + if (db.d->ref != 1) { + qWarning("QSqlDatabasePrivate::removeDatabase: connection '%s' is still in use, " + "all queries will cease to work.", name.toLocal8Bit().constData()); + db.d->disable(); + db.d->connName.clear(); + } +} + +void QSqlDatabasePrivate::removeDatabase(const QString &name) +{ + QConnectionDict *dict = dbDict(); + Q_ASSERT(dict); + QWriteLocker locker(&dict->lock); + + if (!dict->contains(name)) + return; + + invalidateDb(dict->take(name), name); +} + +void QSqlDatabasePrivate::addDatabase(const QSqlDatabase &db, const QString &name) +{ + QConnectionDict *dict = dbDict(); + Q_ASSERT(dict); + QWriteLocker locker(&dict->lock); + + if (dict->contains(name)) { + invalidateDb(dict->take(name), name); + qWarning("QSqlDatabasePrivate::addDatabase: duplicate connection name '%s', old " + "connection removed.", name.toLocal8Bit().data()); + } + dict->insert(name, db); + db.d->connName = name; +} + +/*! \internal +*/ +QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open) +{ + const QConnectionDict *dict = dbDict(); + Q_ASSERT(dict); + + dict->lock.lockForRead(); + QSqlDatabase db = dict->value(name); + dict->lock.unlock(); + if (db.isValid() && !db.isOpen() && open) { + if (!db.open()) + qWarning() << "QSqlDatabasePrivate::database: unable to open database:" << db.lastError().text(); + + } + return db; +} + + +/*! \internal + Copies the connection data from \a other. +*/ +void QSqlDatabasePrivate::copy(const QSqlDatabasePrivate *other) +{ + dbname = other->dbname; + uname = other->uname; + pword = other->pword; + hname = other->hname; + drvName = other->drvName; + port = other->port; + connOptions = other->connOptions; +} + +void QSqlDatabasePrivate::disable() +{ + if (driver != shared_null()->driver) { + delete driver; + driver = shared_null()->driver; + } +} + +/*! + \class QSqlDriverCreatorBase + \brief The QSqlDriverCreatorBase class is the base class for + SQL driver factories. + + \ingroup database + \inmodule QtSql + + Reimplement createObject() to return an instance of the specific + QSqlDriver subclass that you want to provide. + + See QSqlDatabase::registerSqlDriver() for details. + + \sa QSqlDriverCreator +*/ + +/*! + \fn QSqlDriverCreatorBase::~QSqlDriverCreatorBase() + + Destroys the SQL driver creator object. +*/ + +/*! + \fn QSqlDriver *QSqlDriverCreatorBase::createObject() const + + Reimplement this function to returns a new instance of a + QSqlDriver subclass. +*/ + +/*! + \class QSqlDriverCreator + \brief The QSqlDriverCreator class is a template class that + provides a SQL driver factory for a specific driver type. + + \ingroup database + \inmodule QtSql + + QSqlDriverCreator<T> instantiates objects of type T, where T is a + QSqlDriver subclass. + + See QSqlDatabase::registerSqlDriver() for details. +*/ + +/*! + \fn QSqlDriver *QSqlDriverCreator::createObject() const + \reimp +*/ + +/*! + \class QSqlDatabase + \brief The QSqlDatabase class represents a connection to + a database. + + \ingroup database + \mainclass + \inmodule QtSql + + The QSqlDatabase class provides an interface for accessing a + database through a connection. An instance of QSqlDatabase + represents the connection. The connection provides access to the + database via one of the \l{SQL Database Drivers#Supported + Databases} {supported database drivers}, which are derived from + QSqlDriver. Alternatively, you can subclass your own database + driver from QSqlDriver. See \l{How to Write Your Own Database + Driver} for more information. + + Create a connection (i.e., an instance of QSqlDatabase) by calling + one of the static addDatabase() functions, where you specify + \l{SQL Database Drivers#Supported Databases} {the driver or type + of driver} to use (i.e., what kind of database will you access?) + and a connection name. A connection is known by its own name, + \e{not} by the name of the database it connects to. You can have + multiple connections to one database. QSqlDatabase also supports + the concept of a \e{default} connection, which is the unnamed + connection. To create the default connection, don't pass the + connection name argument when you call addDatabase(). + Subsequently, when you call any static member function that takes + the connection name argument, if you don't pass the connection + name argument, the default connection is assumed. The following + snippet shows how to create and open a default connection to a + MySQL database: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 0 + + Once the QSqlDatabase object has been created, set the connection + parameters with setDatabaseName(), setUserName(), setPassword(), + setHostName(), setPort(), and setConnectOptions(). Then call + open() to activate the physical connection to the database. The + connection is not usable until you open it. + + The connection defined above will be the \e{default} connection, + because we didn't give a connection name to \l{QSqlDatabase::} + {addDatabase()}. Subsequently, you can get the default connection + by calling database() without the connection name argument: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 1 + + QSqlDatabase is a value class. Changes made to a database + connection via one instance of QSqlDatabase will affect other + instances of QSqlDatabase that represent the same connection. Use + cloneDatabase() to create an independent database connection based + on an existing one. + + If you create multiple database connections, specify a unique + connection name for each one, when you call addDatabase(). Use + database() with a connection name to get that connection. Use + removeDatabase() with a connection name to remove a connection. + QSqlDatabase outputs a warning if you try to remove a connection + referenced by other QSqlDatabase objects. Use contains() to see if + a given connection name is in the list of connections. + + Once a connection is established, you can call tables() to get the + list of tables in the database, call primaryIndex() to get a + table's primary index, and call record() to get meta-information + about a table's fields (e.g., field names). + + \note QSqlDatabase::exec() is deprecated. Use QSqlQuery::exec() + instead. + + If the driver supports transactions, use transaction() to start a + transaction, and commit() or rollback() to complete it. Use + \l{QSqlDriver::} {hasFeature()} to ask if the driver supports + transactions. \note When using transactions, you must start the + transaction before you create your query. + + If an error occurrs, lastError() will return information about it. + + Get the names of the available SQL drivers with drivers(). Check + for the presence of a particular driver with isDriverAvailable(). + If you have created your own custom driver, you must register it + with registerSqlDriver(). + + \sa QSqlDriver, QSqlQuery, {QtSql Module}, {Threads and the SQL Module} +*/ + +/*! \fn QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName) + \threadsafe + + Adds a database to the list of database connections using the + driver \a type and the connection name \a connectionName. If + there already exists a database connection called \a + connectionName, that connection is removed. + + The database connection is referred to by \a connectionName. The + newly added database connection is returned. + + If \a connectionName is not specified, the new connection becomes + the default connection for the application, and subsequent calls + to database() without the connection name argument will return the + default connection. If a \a connectionName is provided here, use + database(\a connectionName) to retrieve the connection. + + \warning If you add a connection with the same name as an existing + connection, the new connection replaces the old one. If you call + this function more than once without specifying \a connectionName, + the default connection will be the one replaced. + + Before using the connection, it must be initialized. e.g., call + some or all of setDatabaseName(), setUserName(), setPassword(), + setHostName(), setPort(), and setConnectOptions(), and, finally, + open(). + + \sa database() removeDatabase() {Threads and the SQL Module} +*/ +QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName) +{ + QSqlDatabase db(type); + QSqlDatabasePrivate::addDatabase(db, connectionName); + return db; +} + +/*! + \threadsafe + + Returns the database connection called \a connectionName. The + database connection must have been previously added with + addDatabase(). If \a open is true (the default) and the database + connection is not already open it is opened now. If no \a + connectionName is specified the default connection is used. If \a + connectionName does not exist in the list of databases, an invalid + connection is returned. + + \sa isOpen() {Threads and the SQL Module} +*/ + +QSqlDatabase QSqlDatabase::database(const QString& connectionName, bool open) +{ + return QSqlDatabasePrivate::database(connectionName, open); +} + +/*! + \threadsafe + + Removes the database connection \a connectionName from the list of + database connections. + + \warning There should be no open queries on the database + connection when this function is called, otherwise a resource leak + will occur. + + Example: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 0 + + The correct way to do it: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 1 + + To remove the default connection, which may have been created with a + call to addDatabase() not specifying a connection name, you can + retrieve the default connection name by calling connectionName() on + the database returned by database(). Note that if a default database + hasn't been created an invalid database will be returned. + + \sa database() connectionName() {Threads and the SQL Module} +*/ + +void QSqlDatabase::removeDatabase(const QString& connectionName) +{ + QSqlDatabasePrivate::removeDatabase(connectionName); +} + +/*! + Returns a list of all the available database drivers. + + \sa registerSqlDriver() +*/ + +QStringList QSqlDatabase::drivers() +{ + QStringList list; + +#ifdef QT_SQL_PSQL + list << QLatin1String("QPSQL7"); + list << QLatin1String("QPSQL"); +#endif +#ifdef QT_SQL_MYSQL + list << QLatin1String("QMYSQL3"); + list << QLatin1String("QMYSQL"); +#endif +#ifdef QT_SQL_ODBC + list << QLatin1String("QODBC3"); + list << QLatin1String("QODBC"); +#endif +#ifdef QT_SQL_OCI + list << QLatin1String("QOCI8"); + list << QLatin1String("QOCI"); +#endif +#ifdef QT_SQL_TDS + list << QLatin1String("QTDS7"); + list << QLatin1String("QTDS"); +#endif +#ifdef QT_SQL_DB2 + list << QLatin1String("QDB2"); +#endif +#ifdef QT_SQL_SQLITE + list << QLatin1String("QSQLITE"); +#endif +#ifdef QT_SQL_SQLITE2 + list << QLatin1String("QSQLITE2"); +#endif +#ifdef QT_SQL_IBASE + list << QLatin1String("QIBASE"); +#endif + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + if (QFactoryLoader *fl = loader()) { + QStringList keys = fl->keys(); + for (QStringList::const_iterator i = keys.constBegin(); i != keys.constEnd(); ++i) { + if (!list.contains(*i)) + list << *i; + } + } +#endif + + DriverDict dict = QSqlDatabasePrivate::driverDict(); + for (DriverDict::const_iterator i = dict.constBegin(); i != dict.constEnd(); ++i) { + if (!list.contains(i.key())) + list << i.key(); + } + + return list; +} + +/*! + This function registers a new SQL driver called \a name, within + the SQL framework. This is useful if you have a custom SQL driver + and don't want to compile it as a plugin. + + Example: + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 2 + + QSqlDatabase takes ownership of the \a creator pointer, so you + mustn't delete it yourself. + + \sa drivers() +*/ +void QSqlDatabase::registerSqlDriver(const QString& name, QSqlDriverCreatorBase *creator) +{ + delete QSqlDatabasePrivate::driverDict().take(name); + if (creator) + QSqlDatabasePrivate::driverDict().insert(name, creator); +} + +/*! + \threadsafe + + Returns true if the list of database connections contains \a + connectionName; otherwise returns false. + + \sa connectionNames(), database(), {Threads and the SQL Module} +*/ + +bool QSqlDatabase::contains(const QString& connectionName) +{ + return dbDict()->contains_ts(connectionName); +} + +/*! + \threadsafe + + Returns a list containing the names of all connections. + + \sa contains(), database(), {Threads and the SQL Module} +*/ +QStringList QSqlDatabase::connectionNames() +{ + return dbDict()->keys_ts(); +} + +/*! + \overload + + Creates a QSqlDatabase connection that uses the driver referred + to by \a type. If the \a type is not recognized, the database + connection will have no functionality. + + The currently available driver types are: + + \table + \header \i Driver Type \i Description + \row \i QDB2 \i IBM DB2 + \row \i QIBASE \i Borland InterBase Driver + \row \i QMYSQL \i MySQL Driver + \row \i QOCI \i Oracle Call Interface Driver + \row \i QODBC \i ODBC Driver (includes Microsoft SQL Server) + \row \i QPSQL \i PostgreSQL Driver + \row \i QSQLITE \i SQLite version 3 or above + \row \i QSQLITE2 \i SQLite version 2 + \row \i QTDS \i Sybase Adaptive Server + \endtable + + Additional third party drivers, including your own custom + drivers, can be loaded dynamically. + + \sa {SQL Database Drivers}, registerSqlDriver(), drivers() +*/ + +QSqlDatabase::QSqlDatabase(const QString &type) +{ + d = new QSqlDatabasePrivate(); + d->init(type); +} + +/*! + \overload + + Creates a database connection using the given \a driver. +*/ + +QSqlDatabase::QSqlDatabase(QSqlDriver *driver) +{ + d = new QSqlDatabasePrivate(driver); +} + +/*! + Creates an empty, invalid QSqlDatabase object. Use addDatabase(), + removeDatabase(), and database() to get valid QSqlDatabase + objects. +*/ +QSqlDatabase::QSqlDatabase() +{ + d = QSqlDatabasePrivate::shared_null(); + d->ref.ref(); +} + +/*! + Creates a copy of \a other. +*/ +QSqlDatabase::QSqlDatabase(const QSqlDatabase &other) +{ + d = other.d; + d->ref.ref(); +} + +/*! + Assigns \a other to this object. +*/ +QSqlDatabase &QSqlDatabase::operator=(const QSqlDatabase &other) +{ + qAtomicAssign(d, other.d); + return *this; +} + +/*! + \internal + + Create the actual driver instance \a type. +*/ + +void QSqlDatabasePrivate::init(const QString &type) +{ + drvName = type; + + if (!driver) { +#ifdef QT_SQL_PSQL + if (type == QLatin1String("QPSQL") || type == QLatin1String("QPSQL7")) + driver = new QPSQLDriver(); +#endif +#ifdef QT_SQL_MYSQL + if (type == QLatin1String("QMYSQL") || type == QLatin1String("QMYSQL3")) + driver = new QMYSQLDriver(); +#endif +#ifdef QT_SQL_ODBC + if (type == QLatin1String("QODBC") || type == QLatin1String("QODBC3")) + driver = new QODBCDriver(); +#endif +#ifdef QT_SQL_OCI + if (type == QLatin1String("QOCI") || type == QLatin1String("QOCI8")) + driver = new QOCIDriver(); +#endif +#ifdef QT_SQL_TDS + if (type == QLatin1String("QTDS") || type == QLatin1String("QTDS7")) + driver = new QTDSDriver(); +#endif +#ifdef QT_SQL_DB2 + if (type == QLatin1String("QDB2")) + driver = new QDB2Driver(); +#endif +#ifdef QT_SQL_SQLITE + if (type == QLatin1String("QSQLITE")) + driver = new QSQLiteDriver(); +#endif +#ifdef QT_SQL_SQLITE2 + if (type == QLatin1String("QSQLITE2")) + driver = new QSQLite2Driver(); +#endif +#ifdef QT_SQL_IBASE + if (type == QLatin1String("QIBASE")) + driver = new QIBaseDriver(); +#endif + } + + if (!driver) { + DriverDict dict = QSqlDatabasePrivate::driverDict(); + for (DriverDict::const_iterator it = dict.constBegin(); + it != dict.constEnd() && !driver; ++it) { + if (type == it.key()) { + driver = ((QSqlDriverCreatorBase*)(*it))->createObject(); + } + } + } + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + if (!driver && loader()) { + if (QSqlDriverFactoryInterface *factory = qobject_cast<QSqlDriverFactoryInterface*>(loader()->instance(type))) + driver = factory->create(type); + } +#endif // QT_NO_LIBRARY + + if (!driver) { + qWarning("QSqlDatabase: %s driver not loaded", type.toLatin1().data()); + qWarning("QSqlDatabase: available drivers: %s", + QSqlDatabase::drivers().join(QLatin1String(" ")).toLatin1().data()); + if (QCoreApplication::instance() == 0) + qWarning("QSqlDatabase: an instance of QCoreApplication is required for loading driver plugins"); + driver = shared_null()->driver; + } +} + +/*! + Destroys the object and frees any allocated resources. + + If this is the last QSqlDatabase object that uses a certain + database connection, the is automatically closed. + + \sa close() +*/ + +QSqlDatabase::~QSqlDatabase() +{ + if (!d->ref.deref()) { + close(); + delete d; + } +} + +/*! + Executes a SQL statement on the database and returns a QSqlQuery + object. Use lastError() to retrieve error information. If \a + query is empty, an empty, invalid query is returned and + lastError() is not affected. + + \sa QSqlQuery, lastError() +*/ + +QSqlQuery QSqlDatabase::exec(const QString & query) const +{ + QSqlQuery r(d->driver->createResult()); + if (!query.isEmpty()) { + r.exec(query); + d->driver->setLastError(r.lastError()); + } + return r; +} + +/*! + Opens the database connection using the current connection + values. Returns true on success; otherwise returns false. Error + information can be retrieved using lastError(). + + \sa lastError() setDatabaseName() setUserName() setPassword() + \sa setHostName() setPort() setConnectOptions() +*/ + +bool QSqlDatabase::open() +{ + return d->driver->open(d->dbname, d->uname, d->pword, d->hname, + d->port, d->connOptions); +} + +/*! + \overload + + Opens the database connection using the given \a user name and \a + password. Returns true on success; otherwise returns false. Error + information can be retrieved using the lastError() function. + + This function does not store the password it is given. Instead, + the password is passed directly to the driver for opening the + connection and it is then discarded. + + \sa lastError() +*/ + +bool QSqlDatabase::open(const QString& user, const QString& password) +{ + setUserName(user); + return d->driver->open(d->dbname, user, password, d->hname, + d->port, d->connOptions); +} + +/*! + Closes the database connection, freeing any resources acquired, and + invalidating any existing QSqlQuery objects that are used with the + database. + + This will also affect copies of this QSqlDatabase object. + + \sa removeDatabase() +*/ + +void QSqlDatabase::close() +{ + d->driver->close(); +} + +/*! + Returns true if the database connection is currently open; + otherwise returns false. +*/ + +bool QSqlDatabase::isOpen() const +{ + return d->driver->isOpen(); +} + +/*! + Returns true if there was an error opening the database + connection; otherwise returns false. Error information can be + retrieved using the lastError() function. +*/ + +bool QSqlDatabase::isOpenError() const +{ + return d->driver->isOpenError(); +} + +/*! + Begins a transaction on the database if the driver supports + transactions. Returns \c{true} if the operation succeeded. + Otherwise it returns \c{false}. + + \sa QSqlDriver::hasFeature(), commit(), rollback() +*/ +bool QSqlDatabase::transaction() +{ + if (!d->driver->hasFeature(QSqlDriver::Transactions)) + return false; + return d->driver->beginTransaction(); +} + +/*! + Commits a transaction to the database if the driver supports + transactions and a transaction() has been started. Returns \c{true} + if the operation succeeded. Otherwise it returns \c{false}. + + \note For some databases, the commit will fail and return \c{false} + if there is an \l{QSqlQuery::isActive()} {active query} using the + database for a \c{SELECT}. Make the query \l{QSqlQuery::isActive()} + {inactive} before doing the commit. + + Call lastError() to get information about errors. + + \sa QSqlQuery::isActive() QSqlDriver::hasFeature() rollback() +*/ +bool QSqlDatabase::commit() +{ + if (!d->driver->hasFeature(QSqlDriver::Transactions)) + return false; + return d->driver->commitTransaction(); +} + +/*! + Rolls back a transaction on the database, if the driver supports + transactions and a transaction() has been started. Returns \c{true} + if the operation succeeded. Otherwise it returns \c{false}. + + \note For some databases, the rollback will fail and return + \c{false} if there is an \l{QSqlQuery::isActive()} {active query} + using the database for a \c{SELECT}. Make the query + \l{QSqlQuery::isActive()} {inactive} before doing the rollback. + + Call lastError() to get information about errors. + + \sa QSqlQuery::isActive() QSqlDriver::hasFeature() commit() +*/ +bool QSqlDatabase::rollback() +{ + if (!d->driver->hasFeature(QSqlDriver::Transactions)) + return false; + return d->driver->rollbackTransaction(); +} + +/*! + Sets the connection's database name to \a name. To have effect, + the database name must be set \e{before} the connection is + \l{open()} {opened}. Alternatively, you can close() the + connection, set the database name, and call open() again. \note + The \e{database name} is not the \e{connection name}. The + connection name must be passed to addDatabase() at connection + object create time. + + For the QOCI (Oracle) driver, the database name is the TNS + Service Name. + + For the QODBC driver, the \a name can either be a DSN, a DSN + filename (in which case the file must have a \c .dsn extension), + or a connection string. + + For example, Microsoft Access users can use the following + connection string to open an \c .mdb file directly, instead of + having to create a DSN entry in the ODBC manager: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 3 + + There is no default value. + + \sa databaseName() setUserName() setPassword() setHostName() + \sa setPort() setConnectOptions() open() +*/ + +void QSqlDatabase::setDatabaseName(const QString& name) +{ + if (isValid()) + d->dbname = name; +} + +/*! + Sets the connection's user name to \a name. To have effect, the + user name must be set \e{before} the connection is \l{open()} + {opened}. Alternatively, you can close() the connection, set the + user name, and call open() again. + + There is no default value. + + \sa userName() setDatabaseName() setPassword() setHostName() + \sa setPort() setConnectOptions() open() +*/ + +void QSqlDatabase::setUserName(const QString& name) +{ + if (isValid()) + d->uname = name; +} + +/*! + Sets the connection's password to \a password. To have effect, the + password must be set \e{before} the connection is \l{open()} + {opened}. Alternatively, you can close() the connection, set the + password, and call open() again. + + There is no default value. + + \warning This function stores the password in plain text within + Qt. Use the open() call that takes a password as parameter to + avoid this behavior. + + \sa password() setUserName() setDatabaseName() setHostName() + \sa setPort() setConnectOptions() open() +*/ + +void QSqlDatabase::setPassword(const QString& password) +{ + if (isValid()) + d->pword = password; +} + +/*! + Sets the connection's host name to \a host. To have effect, the + host name must be set \e{before} the connection is \l{open()} + {opened}. Alternatively, you can close() the connection, set the + host name, and call open() again. + + There is no default value. + + \sa hostName() setUserName() setPassword() setDatabaseName() + \sa setPort() setConnectOptions() open() +*/ + +void QSqlDatabase::setHostName(const QString& host) +{ + if (isValid()) + d->hname = host; +} + +/*! + Sets the connection's port number to \a port. To have effect, the + port number must be set \e{before} the connection is \l{open()} + {opened}. Alternatively, you can close() the connection, set the + port number, and call open() again.. + + There is no default value. + + \sa port() setUserName() setPassword() setHostName() + \sa setDatabaseName() setConnectOptions() open() +*/ + +void QSqlDatabase::setPort(int port) +{ + if (isValid()) + d->port = port; +} + +/*! + Returns the connection's database name, which may be empty. + \note The database name is not the connection name. + + \sa setDatabaseName() +*/ +QString QSqlDatabase::databaseName() const +{ + return d->dbname; +} + +/*! + Returns the connection's user name; it may be empty. + + \sa setUserName() +*/ +QString QSqlDatabase::userName() const +{ + return d->uname; +} + +/*! + Returns the connection's password. If the password was not set + with setPassword(), and if the password was given in the open() + call, or if no password was used, an empty string is returned. +*/ +QString QSqlDatabase::password() const +{ + return d->pword; +} + +/*! + Returns the connection's host name; it may be empty. + + \sa setHostName() +*/ +QString QSqlDatabase::hostName() const +{ + return d->hname; +} + +/*! + Returns the connection's driver name. + + \sa addDatabase(), driver() +*/ +QString QSqlDatabase::driverName() const +{ + return d->drvName; +} + +/*! + Returns the connection's port number. The value is undefined if + the port number has not been set. + + \sa setPort() +*/ +int QSqlDatabase::port() const +{ + return d->port; +} + +/*! + Returns the database driver used to access the database + connection. + + \sa addDatabase() drivers() +*/ + +QSqlDriver* QSqlDatabase::driver() const +{ + return d->driver; +} + +/*! + Returns information about the last error that occurred on the + database. + + Failures that occur in conjunction with an individual query are + reported by QSqlQuery::lastError(). + + \sa QSqlError, QSqlQuery::lastError() +*/ + +QSqlError QSqlDatabase::lastError() const +{ + return d->driver->lastError(); +} + + +/*! + Returns a list of the database's tables, system tables and views, + as specified by the parameter \a type. + + \sa primaryIndex(), record() +*/ + +QStringList QSqlDatabase::tables(QSql::TableType type) const +{ + return d->driver->tables(type); +} + +/*! + Returns the primary index for table \a tablename. If no primary + index exists an empty QSqlIndex is returned. + + \sa tables(), record() +*/ + +QSqlIndex QSqlDatabase::primaryIndex(const QString& tablename) const +{ + return d->driver->primaryIndex(tablename); +} + + +/*! + Returns a QSqlRecord populated with the names of all the fields in + the table (or view) called \a tablename. The order in which the + fields appear in the record is undefined. If no such table (or + view) exists, an empty record is returned. +*/ + +QSqlRecord QSqlDatabase::record(const QString& tablename) const +{ + return d->driver->record(tablename); +} + + +/*! + Sets database-specific \a options. This must be done before the + connection is opened or it has no effect (or you can close() the + connection, call this function and open() the connection again). + + The format of the \a options string is a semicolon separated list + of option names or option=value pairs. The options depend on the + database client used: + + \table + \header \i ODBC \i MySQL \i PostgreSQL + \row + + \i + \list + \i SQL_ATTR_ACCESS_MODE + \i SQL_ATTR_LOGIN_TIMEOUT + \i SQL_ATTR_CONNECTION_TIMEOUT + \i SQL_ATTR_CURRENT_CATALOG + \i SQL_ATTR_METADATA_ID + \i SQL_ATTR_PACKET_SIZE + \i SQL_ATTR_TRACEFILE + \i SQL_ATTR_TRACE + \i SQL_ATTR_CONNECTION_POOLING + \i SQL_ATTR_ODBC_VERSION + \endlist + + \i + \list + \i CLIENT_COMPRESS + \i CLIENT_FOUND_ROWS + \i CLIENT_IGNORE_SPACE + \i CLIENT_SSL + \i CLIENT_ODBC + \i CLIENT_NO_SCHEMA + \i CLIENT_INTERACTIVE + \i UNIX_SOCKET + \endlist + + \i + \list + \i connect_timeout + \i options + \i tty + \i requiressl + \i service + \endlist + + \header \i DB2 \i OCI \i TDS + \row + + \i + \list + \i SQL_ATTR_ACCESS_MODE + \i SQL_ATTR_LOGIN_TIMEOUT + \endlist + + \i + \list + \i OCI_ATTR_PREFETCH_ROWS + \i OCI_ATTR_PREFETCH_MEMORY + \endlist + + \i + \e none + + \header \i SQLite \i Interbase + \row + + \i + \list + \i QSQLITE_BUSY_TIMEOUT + \endlist + + \i + \list + \i ISC_DPB_LC_CTYPE + \i ISC_DPB_SQL_ROLE_NAME + \endlist + + \endtable + + Examples: + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 4 + + Refer to the client library documentation for more information + about the different options. + + \sa connectOptions() +*/ + +void QSqlDatabase::setConnectOptions(const QString &options) +{ + if (isValid()) + d->connOptions = options; +} + +/*! + Returns the connection options string used for this connection. + The string may be empty. + + \sa setConnectOptions() + */ +QString QSqlDatabase::connectOptions() const +{ + return d->connOptions; +} + +/*! + Returns true if a driver called \a name is available; otherwise + returns false. + + \sa drivers() +*/ + +bool QSqlDatabase::isDriverAvailable(const QString& name) +{ + return drivers().contains(name); +} + +/*! \fn QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver* driver, const QString& connectionName) + + This overload is useful when you want to create a database + connection with a \l{QSqlDriver} {driver} you instantiated + yourself. It might be your own database driver, or you might just + need to instantiate one of the Qt drivers yourself. If you do + this, it is recommended that you include the driver code in your + application. For example, you can create a PostgreSQL connection + with your own QPSQL driver like this: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 5 + \codeline + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 6 + + The above code sets up a PostgreSQL connection and instantiates a + QPSQLDriver object. Next, addDatabase() is called to add the + connection to the known connections so that it can be used by the + Qt SQL classes. When a driver is instantiated with a connection + handle (or set of handles), Qt assumes that you have already + opened the database connection. + + \note We assume that \c qtdir is the directory where Qt is + installed. This will pull in the code that is needed to use the + PostgreSQL client library and to instantiate a QPSQLDriver object, + assuming that you have the PostgreSQL headers somewhere in your + include search path. + + Remember that you must link your application against the database + client library. Make sure the client library is in your linker's + search path, and add lines like these to your \c{.pro} file: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 7 + + The method described works for all the supplied drivers. The only + difference will be in the driver constructor arguments. Here is a + table of the drivers included with Qt, their source code files, + and their constructor arguments: + + \table + \header \i Driver \i Class name \i Constructor arguments \i File to include + \row + \i QPSQL + \i QPSQLDriver + \i PGconn *connection + \i \c qsql_psql.cpp + \row + \i QMYSQL + \i QMYSQLDriver + \i MYSQL *connection + \i \c qsql_mysql.cpp + \row + \i QOCI + \i QOCIDriver + \i OCIEnv *environment, OCISvcCtx *serviceContext + \i \c qsql_oci.cpp + \row + \i QODBC + \i QODBCDriver + \i SQLHANDLE environment, SQLHANDLE connection + \i \c qsql_odbc.cpp + \row + \i QDB2 + \i QDB2 + \i SQLHANDLE environment, SQLHANDLE connection + \i \c qsql_db2.cpp + \row + \i QTDS + \i QTDSDriver + \i LOGINREC *loginRecord, DBPROCESS *dbProcess, const QString &hostName + \i \c qsql_tds.cpp + \row + \i QSQLITE + \i QSQLiteDriver + \i sqlite *connection + \i \c qsql_sqlite.cpp + \row + \i QIBASE + \i QIBaseDriver + \i isc_db_handle connection + \i \c qsql_ibase.cpp + \endtable + + The host name (or service name) is needed when constructing the + QTDSDriver for creating new connections for internal queries. This + is to prevent blocking when several QSqlQuery objects are used + simultaneously. + + \warning Adding a database connection with the same connection + name as an existing connection, causes the existing connection to + be replaced by the new one. + + \warning The SQL framework takes ownership of the \a driver. It + must not be deleted. To remove the connection, use + removeDatabase(). + + \sa drivers() +*/ +QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver* driver, const QString& connectionName) +{ + QSqlDatabase db(driver); + QSqlDatabasePrivate::addDatabase(db, connectionName); + return db; +} + +/*! + Returns true if the QSqlDatabase has a valid driver. + + Example: + \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 8 +*/ +bool QSqlDatabase::isValid() const +{ + return d->driver && d->driver != d->shared_null()->driver; +} + +#ifdef QT3_SUPPORT +/*! + Use query.record() instead. +*/ +QSqlRecord QSqlDatabase::record(const QSqlQuery& query) const +{ return query.record(); } + +/*! + Use query.record() instead. +*/ +QSqlRecord QSqlDatabase::recordInfo(const QSqlQuery& query) const +{ return query.record(); } + +/*! + \fn QSqlRecord QSqlDatabase::recordInfo(const QString& tablename) const + + Use record() instead. +*/ +#endif + +/*! + Clones the database connection \a other and and stores it as \a + connectionName. All the settings from the original database, e.g. + databaseName(), hostName(), etc., are copied across. Does nothing + if \a other is an invalid database. Returns the newly created + database connection. + + \note The new connection has not been opened. Before using the new + connection, you must call open(). +*/ +QSqlDatabase QSqlDatabase::cloneDatabase(const QSqlDatabase &other, const QString &connectionName) +{ + if (!other.isValid()) + return QSqlDatabase(); + + QSqlDatabase db(other.driverName()); + db.d->copy(other.d); + QSqlDatabasePrivate::addDatabase(db, connectionName); + return db; +} + +/*! + \since 4.4 + + Returns the connection name, which may be empty. \note The + connection name is not the \l{databaseName()} {database name}. + + \sa addDatabase() +*/ +QString QSqlDatabase::connectionName() const +{ + return d->connName; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QSqlDatabase &d) +{ + if (!d.isValid()) { + dbg.nospace() << "QSqlDatabase(invalid)"; + return dbg.space(); + } + + dbg.nospace() << "QSqlDatabase(driver=\"" << d.driverName() << "\", database=\"" + << d.databaseName() << "\", host=\"" << d.hostName() << "\", port=" << d.port() + << ", user=\"" << d.userName() << "\", open=" << d.isOpen() << ")"; + return dbg.space(); +} +#endif + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqldatabase.h b/src/sql/kernel/qsqldatabase.h new file mode 100644 index 0000000..ca6f0b0 --- /dev/null +++ b/src/sql/kernel/qsqldatabase.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLDATABASE_H +#define QSQLDATABASE_H + +#include <QtCore/qstring.h> +#include <QtSql/qsql.h> +#ifdef QT3_SUPPORT +#include <QtSql/qsqlrecord.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlError; +class QSqlDriver; +class QSqlIndex; +class QSqlRecord; +class QSqlQuery; +class QSqlDatabasePrivate; + +class Q_SQL_EXPORT QSqlDriverCreatorBase +{ +public: + virtual ~QSqlDriverCreatorBase() {} + virtual QSqlDriver *createObject() const = 0; +}; + +template <class T> +class QSqlDriverCreator : public QSqlDriverCreatorBase +{ +public: + QSqlDriver *createObject() const { return new T; } +}; + +class Q_SQL_EXPORT QSqlDatabase +{ +public: + QSqlDatabase(); + QSqlDatabase(const QSqlDatabase &other); + ~QSqlDatabase(); + + QSqlDatabase &operator=(const QSqlDatabase &other); + + bool open(); + bool open(const QString& user, const QString& password); + void close(); + bool isOpen() const; + bool isOpenError() const; + QStringList tables(QSql::TableType type = QSql::Tables) const; + QSqlIndex primaryIndex(const QString& tablename) const; + QSqlRecord record(const QString& tablename) const; +#ifdef QT3_SUPPORT + QT3_SUPPORT QSqlRecord record(const QSqlQuery& query) const; + inline QT3_SUPPORT QSqlRecord recordInfo(const QString& tablename) const + { return record(tablename); } + QT3_SUPPORT QSqlRecord recordInfo(const QSqlQuery& query) const; +#endif + QSqlQuery exec(const QString& query = QString()) const; + QSqlError lastError() const; + bool isValid() const; + + bool transaction(); + bool commit(); + bool rollback(); + + void setDatabaseName(const QString& name); + void setUserName(const QString& name); + void setPassword(const QString& password); + void setHostName(const QString& host); + void setPort(int p); + void setConnectOptions(const QString& options = QString()); + QString databaseName() const; + QString userName() const; + QString password() const; + QString hostName() const; + QString driverName() const; + int port() const; + QString connectOptions() const; + QString connectionName() const; + + QSqlDriver* driver() const; + + QT_STATIC_CONST char *defaultConnection; + + static QSqlDatabase addDatabase(const QString& type, + const QString& connectionName = QLatin1String(defaultConnection)); + static QSqlDatabase addDatabase(QSqlDriver* driver, + const QString& connectionName = QLatin1String(defaultConnection)); + static QSqlDatabase cloneDatabase(const QSqlDatabase &other, const QString& connectionName); + static QSqlDatabase database(const QString& connectionName = QLatin1String(defaultConnection), + bool open = true); + static void removeDatabase(const QString& connectionName); + static bool contains(const QString& connectionName = QLatin1String(defaultConnection)); + static QStringList drivers(); + static QStringList connectionNames(); + static void registerSqlDriver(const QString &name, QSqlDriverCreatorBase *creator); + static bool isDriverAvailable(const QString &name); + +protected: + explicit QSqlDatabase(const QString& type); + explicit QSqlDatabase(QSqlDriver* driver); + +private: + friend class QSqlDatabasePrivate; + QSqlDatabasePrivate *d; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlDatabase &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLDATABASE_H diff --git a/src/sql/kernel/qsqldriver.cpp b/src/sql/kernel/qsqldriver.cpp new file mode 100644 index 0000000..ddebe45 --- /dev/null +++ b/src/sql/kernel/qsqldriver.cpp @@ -0,0 +1,803 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqldriver.h" + +#include "qdatetime.h" +#include "qsqlerror.h" +#include "qsqlfield.h" +#include "qsqlindex.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QSqlDriverPrivate : public QObjectPrivate +{ +public: + QSqlDriverPrivate(); + virtual ~QSqlDriverPrivate(); + +public: + // @CHECK: this member is never used. It was named q, which expanded to q_func(). + QSqlDriver *q_func(); + uint isOpen : 1; + uint isOpenError : 1; + QSqlError error; +}; + +inline QSqlDriverPrivate::QSqlDriverPrivate() + : QObjectPrivate(), isOpen(false), isOpenError(false) +{ +} + +QSqlDriverPrivate::~QSqlDriverPrivate() +{ +} + +/*! + \class QSqlDriver + \brief The QSqlDriver class is an abstract base class for accessing + specific SQL databases. + + \ingroup database + \inmodule QtSql + + This class should not be used directly. Use QSqlDatabase instead. + + If you want to create your own SQL drivers, you can subclass this + class and reimplement its pure virtual functions and those + virtual functions that you need. See \l{How to Write Your Own + Database Driver} for more information. + + \sa QSqlDatabase, QSqlResult +*/ + +/*! + Constructs a new driver with the given \a parent. +*/ + +QSqlDriver::QSqlDriver(QObject *parent) + : QObject(*new QSqlDriverPrivate, parent) +{ +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlDriver::~QSqlDriver() +{ +} + +/*! + \since 4.4 + + \fn QSqlDriver::notification(const QString &name) + + This signal is emitted when the database posts an event notification + that the driver subscribes to. \a name identifies the event notification. + + \sa subscribeToNotification() +*/ + +/*! + \fn bool QSqlDriver::open(const QString &db, const QString &user, const QString& password, + const QString &host, int port, const QString &options) + + Derived classes must reimplement this pure virtual function to + open a database connection on database \a db, using user name \a + user, password \a password, host \a host, port \a port and + connection options \a options. + + The function must return true on success and false on failure. + + \sa setOpen() +*/ + +/*! + \fn bool QSqlDriver::close() + + Derived classes must reimplement this pure virtual function in + order to close the database connection. Return true on success, + false on failure. + + \sa open(), setOpen() +*/ + +/*! + \fn QSqlResult *QSqlDriver::createResult() const + + Creates an empty SQL result on the database. Derived classes must + reimplement this function and return a QSqlResult object + appropriate for their database to the caller. +*/ + +/*! + Returns true if the database connection is open; otherwise returns + false. +*/ + +bool QSqlDriver::isOpen() const +{ + return d_func()->isOpen; +} + +/*! + Returns true if the there was an error opening the database + connection; otherwise returns false. +*/ + +bool QSqlDriver::isOpenError() const +{ + return d_func()->isOpenError; +} + +/*! + \enum QSqlDriver::DriverFeature + + This enum contains a list of features a driver might support. Use + hasFeature() to query whether a feature is supported or not. + + \value Transactions Whether the driver supports SQL transactions. + \value QuerySize Whether the database is capable of reporting the size + of a query. Note that some databases do not support returning the size + (i.e. number of rows returned) of a query, in which case + QSqlQuery::size() will return -1. + \value BLOB Whether the driver supports Binary Large Object fields. + \value Unicode Whether the driver supports Unicode strings if the + database server does. + \value PreparedQueries Whether the driver supports prepared query execution. + \value NamedPlaceholders Whether the driver supports the use of named placeholders. + \value PositionalPlaceholders Whether the driver supports the use of positional placeholders. + \value LastInsertId Whether the driver supports returning the Id of the last touched row. + \value BatchOperations Whether the driver supports batched operations, see QSqlQuery::execBatch() + \value SimpleLocking Whether the driver disallows a write lock on a table while other queries have a read lock on it. + \value LowPrecisionNumbers Whether the driver allows fetching numerical values with low precision. + \value EventNotifications Whether the driver supports database event notifications. + \value FinishQuery Whether the driver can do any low-level resource cleanup when QSqlQuery::finish() is called. + \value MultipleResultSets Whether the driver can access multiple result sets returned from batched statements or stored procedures. + + More information about supported features can be found in the + \l{sql-driver.html}{Qt SQL driver} documentation. + + \sa hasFeature() +*/ + +/*! + \enum QSqlDriver::StatementType + + This enum contains a list of SQL statement (or clause) types the + driver can create. + + \value WhereStatement An SQL \c WHERE statement (e.g., \c{WHERE f = 5}). + \value SelectStatement An SQL \c SELECT statement (e.g., \c{SELECT f FROM t}). + \value UpdateStatement An SQL \c UPDATE statement (e.g., \c{UPDATE TABLE t set f = 1}). + \value InsertStatement An SQL \c INSERT statement (e.g., \c{INSERT INTO t (f) values (1)}). + \value DeleteStatement An SQL \c DELETE statement (e.g., \c{DELETE FROM t}). + + \sa sqlStatement() +*/ + +/*! + \enum QSqlDriver::IdentifierType + + This enum contains a list of SQL identifier types. + + \value FieldName A SQL field name + \value TableName A SQL table name +*/ + +/*! + \fn bool QSqlDriver::hasFeature(DriverFeature feature) const + + Returns true if the driver supports feature \a feature; otherwise + returns false. + + Note that some databases need to be open() before this can be + determined. + + \sa DriverFeature +*/ + +/*! + This function sets the open state of the database to \a open. + Derived classes can use this function to report the status of + open(). + + \sa open(), setOpenError() +*/ + +void QSqlDriver::setOpen(bool open) +{ + d_func()->isOpen = open; +} + +/*! + This function sets the open error state of the database to \a + error. Derived classes can use this function to report the status + of open(). Note that if \a error is true the open state of the + database is set to closed (i.e., isOpen() returns false). + + \sa open(), setOpen() +*/ + +void QSqlDriver::setOpenError(bool error) +{ + d_func()->isOpenError = error; + if (error) + d_func()->isOpen = false; +} + +/*! + This function is called to begin a transaction. If successful, + return true, otherwise return false. The default implementation + does nothing and returns false. + + \sa commitTransaction(), rollbackTransaction() +*/ + +bool QSqlDriver::beginTransaction() +{ + return false; +} + +/*! + This function is called to commit a transaction. If successful, + return true, otherwise return false. The default implementation + does nothing and returns false. + + \sa beginTransaction(), rollbackTransaction() +*/ + +bool QSqlDriver::commitTransaction() +{ + return false; +} + +/*! + This function is called to rollback a transaction. If successful, + return true, otherwise return false. The default implementation + does nothing and returns false. + + \sa beginTransaction(), commitTransaction() +*/ + +bool QSqlDriver::rollbackTransaction() +{ + return false; +} + +/*! + This function is used to set the value of the last error, \a error, + that occurred on the database. + + \sa lastError() +*/ + +void QSqlDriver::setLastError(const QSqlError &error) +{ + d_func()->error = error; +} + +/*! + Returns a QSqlError object which contains information about the + last error that occurred on the database. +*/ + +QSqlError QSqlDriver::lastError() const +{ + return d_func()->error; +} + +/*! + Returns a list of the names of the tables in the database. The + default implementation returns an empty list. + + The \a tableType argument describes what types of tables + should be returned. Due to binary compatibility, the string + contains the value of the enum QSql::TableTypes as text. + An empty string should be treated as QSql::Tables for + backward compatibility. +*/ + +QStringList QSqlDriver::tables(QSql::TableType) const +{ + return QStringList(); +} + +/*! + Returns the primary index for table \a tableName. Returns an empty + QSqlIndex if the table doesn't have a primary index. The default + implementation returns an empty index. +*/ + +QSqlIndex QSqlDriver::primaryIndex(const QString&) const +{ + return QSqlIndex(); +} + + +/*! + Returns a QSqlRecord populated with the names of the fields in + table \a tableName. If no such table exists, an empty record is + returned. The default implementation returns an empty record. +*/ + +QSqlRecord QSqlDriver::record(const QString & /* tableName */) const +{ + return QSqlRecord(); +} + +/*! + Returns the \a identifier escaped according to the database rules. + \a identifier can either be a table name or field name, dependent + on \a type. + + The default implementation does nothing. + */ +QString QSqlDriver::escapeIdentifier(const QString &identifier, IdentifierType) const +{ + return identifier; +} + +/*! + Returns a SQL statement of type \a type for the table \a tableName + with the values from \a rec. If \a preparedStatement is true, the + string will contain placeholders instead of values. + + This method can be used to manipulate tables without having to worry + about database-dependent SQL dialects. For non-prepared statements, + the values will be properly escaped. +*/ +QString QSqlDriver::sqlStatement(StatementType type, const QString &tableName, + const QSqlRecord &rec, bool preparedStatement) const +{ + int i; + QString s; + s.reserve(128); + switch (type) { + case SelectStatement: + for (i = 0; i < rec.count(); ++i) { + if (rec.isGenerated(i)) + s.append(escapeIdentifier(rec.fieldName(i), FieldName)).append(QLatin1String(", ")); + } + if (s.isEmpty()) + return s; + s.chop(2); + s.prepend(QLatin1String("SELECT ")).append(QLatin1String(" FROM ")).append(escapeIdentifier(tableName, TableName)); + break; + case WhereStatement: + if (preparedStatement) { + for (int i = 0; i < rec.count(); ++i) + s.append(escapeIdentifier(rec.fieldName(i), FieldName)).append( + QLatin1String(" = ? AND ")); + } else { + for (i = 0; i < rec.count(); ++i) { + s.append(escapeIdentifier(rec.fieldName(i), FieldName)); + QString val = formatValue(rec.field(i)); + if (val == QLatin1String("NULL")) + s.append(QLatin1String(" IS NULL")); + else + s.append(QLatin1String(" = ")).append(val); + s.append(QLatin1String(" AND ")); + } + } + if (!s.isEmpty()) { + s.prepend(QLatin1String("WHERE ")); + s.chop(5); // remove tailing AND + } + break; + case UpdateStatement: + s.append(QLatin1String("UPDATE ")).append(escapeIdentifier(tableName, TableName)).append( + QLatin1String(" SET ")); + for (i = 0; i < rec.count(); ++i) { + if (!rec.isGenerated(i) || !rec.value(i).isValid()) + continue; + s.append(escapeIdentifier(rec.fieldName(i), FieldName)).append(QLatin1Char('=')); + if (preparedStatement) + s.append(QLatin1Char('?')); + else + s.append(formatValue(rec.field(i))); + s.append(QLatin1String(", ")); + } + if (s.endsWith(QLatin1String(", "))) + s.chop(2); + else + s.clear(); + break; + case DeleteStatement: + s.append(QLatin1String("DELETE FROM ")).append(escapeIdentifier(tableName, TableName)); + break; + case InsertStatement: { + s.append(QLatin1String("INSERT INTO ")).append(escapeIdentifier(tableName, TableName)).append(QLatin1String(" (")); + QString vals; + for (i = 0; i < rec.count(); ++i) { + if (!rec.isGenerated(i) || !rec.value(i).isValid()) + continue; + s.append(escapeIdentifier(rec.fieldName(i), FieldName)).append(QLatin1String(", ")); + if (preparedStatement) + vals.append(QLatin1String("?")); + else + vals.append(formatValue(rec.field(i))); + vals.append(QLatin1String(", ")); + } + if (vals.isEmpty()) { + s.clear(); + } else { + vals.chop(2); // remove trailing comma + s[s.length() - 2] = QLatin1Char(')'); + s.append(QLatin1String("VALUES (")).append(vals).append(QLatin1String(")")); + } + break; } + } + return s; +} + +/*! + Returns a string representation of the \a field value for the + database. This is used, for example, when constructing INSERT and + UPDATE statements. + + The default implementation returns the value formatted as a string + according to the following rules: + + \list + + \i If \a field is character data, the value is returned enclosed + in single quotation marks, which is appropriate for many SQL + databases. Any embedded single-quote characters are escaped + (replaced with two single-quote characters). If \a trimStrings is + true (the default is false), all trailing whitespace is trimmed + from the field. + + \i If \a field is date/time data, the value is formatted in ISO + format and enclosed in single quotation marks. If the date/time + data is invalid, "NULL" is returned. + + \i If \a field is \link QByteArray bytearray\endlink data, and the + driver can edit binary fields, the value is formatted as a + hexadecimal string. + + \i For any other field type, toString() is called on its value + and the result of this is returned. + + \endlist + + \sa QVariant::toString() + +*/ +QString QSqlDriver::formatValue(const QSqlField &field, bool trimStrings) const +{ + const QLatin1String nullTxt("NULL"); + + QString r; + if (field.isNull()) + r = nullTxt; + else { + switch (field.type()) { + case QVariant::Int: + case QVariant::UInt: + if (field.value().type() == QVariant::Bool) + r = field.value().toBool() ? QLatin1String("1") : QLatin1String("0"); + else + r = field.value().toString(); + break; +#ifndef QT_NO_DATESTRING + case QVariant::Date: + if (field.value().toDate().isValid()) + r = QLatin1Char('\'') + field.value().toDate().toString(Qt::ISODate) + + QLatin1Char('\''); + else + r = nullTxt; + break; + case QVariant::Time: + if (field.value().toTime().isValid()) + r = QLatin1Char('\'') + field.value().toTime().toString(Qt::ISODate) + + QLatin1Char('\''); + else + r = nullTxt; + break; + case QVariant::DateTime: + if (field.value().toDateTime().isValid()) + r = QLatin1Char('\'') + + field.value().toDateTime().toString(Qt::ISODate) + QLatin1Char('\''); + else + r = nullTxt; + break; +#endif + case QVariant::String: + case QVariant::Char: + { + QString result = field.value().toString(); + if (trimStrings) { + int end = result.length(); + while (end && result.at(end-1).isSpace()) /* skip white space from end */ + end--; + result.truncate(end); + } + /* escape the "'" character */ + result.replace(QLatin1Char('\''), QLatin1String("''")); + r = QLatin1Char('\'') + result + QLatin1Char('\''); + break; + } + case QVariant::Bool: + if (field.value().toBool()) + r = QLatin1String("1"); + else + r = QLatin1String("0"); + break; + case QVariant::ByteArray : { + if (hasFeature(BLOB)) { + 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 = QLatin1Char('\'') + res + QLatin1Char('\''); + break; + } + } + default: + r = field.value().toString(); + break; + } + } + return r; +} + +/*! + Returns the low-level database handle wrapped in a QVariant or an + invalid variant if there is no handle. + + \warning Use this with uttermost care and only if you know what you're doing. + + \warning The handle returned here can become a stale pointer if the connection + is modified (for example, if you close the connection). + + \warning The handle can be NULL if the connection is not open yet. + + The handle returned here is database-dependent, you should query the type + name of the variant before accessing it. + + This example retrieves the handle for a connection to sqlite: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldriver.cpp 0 + + This snippet returns the handle for PostgreSQL or MySQL: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqldriver.cpp 1 + + \sa QSqlResult::handle() +*/ +QVariant QSqlDriver::handle() const +{ + return QVariant(); +} + +/*! + \fn QSqlRecord QSqlDriver::record(const QSqlQuery& query) const + + Use query.record() instead. +*/ + +/*! + \fn QSqlRecord QSqlDriver::recordInfo(const QString& tablename) const + + Use record() instead. +*/ + +/*! + \fn QSqlRecord QSqlDriver::recordInfo(const QSqlQuery& query) const + + Use query.record() instead. +*/ + +/*! + \fn QString QSqlDriver::nullText() const + + sqlStatement() is now used to generate SQL. Use tr("NULL") for example, instead. +*/ + +/*! + \fn QString QSqlDriver::formatValue(const QSqlField *field, bool trimStrings) const + + Use the other formatValue() overload instead. +*/ + +/*! + This function is called to subscribe to event notifications from the database. + \a name identifies the event notification. + + If successful, return true, otherwise return false. + + The database must be open when this function is called. When the database is closed + by calling close() all subscribed event notifications are automatically unsubscribed. + Note that calling open() on an already open database may implicitly cause close() to + be called, which will cause the driver to unsubscribe from all event notifications. + + When an event notification identified by \a name is posted by the database the + notification() signal is emitted. + + \warning Because of binary compatibility constraints, this function is not virtual. + If you want to provide event notification support in your own QSqlDriver subclass, + reimplement the subscribeToNotificationImplementation() slot in your subclass instead. + The subscribeToNotification() function will dynamically detect the slot and call it. + + \since 4.4 + \sa unsubscribeFromNotification() subscribedToNotifications() QSqlDriver::hasFeature() +*/ +bool QSqlDriver::subscribeToNotification(const QString &name) +{ + bool result; + QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this), + "subscribeToNotificationImplementation", Qt::DirectConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(QString, name)); + return result; +} + +/*! + This function is called to unsubscribe from event notifications from the database. + \a name identifies the event notification. + + If successful, return true, otherwise return false. + + The database must be open when this function is called. All subscribed event + notifications are automatically unsubscribed from when the close() function is called. + + After calling \e this function the notification() signal will no longer be emitted + when an event notification identified by \a name is posted by the database. + + \warning Because of binary compatibility constraints, this function is not virtual. + If you want to provide event notification support in your own QSqlDriver subclass, + reimplement the unsubscribeFromNotificationImplementation() slot in your subclass instead. + The unsubscribeFromNotification() function will dynamically detect the slot and call it. + + \since 4.4 + \sa subscribeToNotification() subscribedToNotifications() +*/ +bool QSqlDriver::unsubscribeFromNotification(const QString &name) +{ + bool result; + QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this), + "unsubscribeFromNotificationImplementation", Qt::DirectConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(QString, name)); + return result; +} + +/*! + Returns a list of the names of the event notifications that are currently subscribed to. + + \warning Because of binary compatibility constraints, this function is not virtual. + If you want to provide event notification support in your own QSqlDriver subclass, + reimplement the subscribedToNotificationsImplementation() slot in your subclass instead. + The subscribedToNotifications() function will dynamically detect the slot and call it. + + \since 4.4 + \sa subscribeToNotification() unsubscribeFromNotification() +*/ +QStringList QSqlDriver::subscribedToNotifications() const +{ + QStringList result; + QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this), + "subscribedToNotificationsImplementation", Qt::DirectConnection, + Q_RETURN_ARG(QStringList, result)); + return result; +} + +/*! + This slot is called to subscribe to event notifications from the database. + \a name identifies the event notification. + + If successful, return true, otherwise return false. + + The database must be open when this \e slot is called. When the database is closed + by calling close() all subscribed event notifications are automatically unsubscribed. + Note that calling open() on an already open database may implicitly cause close() to + be called, which will cause the driver to unsubscribe from all event notifications. + + When an event notification identified by \a name is posted by the database the + notification() signal is emitted. + + Reimplement this slot to provide your own QSqlDriver subclass with event notification + support; because of binary compatibility constraints, the subscribeToNotification() + function (introduced in Qt 4.4) is not virtual. Instead, subscribeToNotification() + will dynamically detect and call \e this slot. The default implementation does nothing + and returns false. + + \since 4.4 + \sa subscribeToNotification() +*/ +bool QSqlDriver::subscribeToNotificationImplementation(const QString &name) +{ + Q_UNUSED(name); + return false; +} + +/*! + This slot is called to unsubscribe from event notifications from the database. + \a name identifies the event notification. + + If successful, return true, otherwise return false. + + The database must be open when \e this slot is called. All subscribed event + notifications are automatically unsubscribed from when the close() function is called. + + After calling \e this slot the notification() signal will no longer be emitted + when an event notification identified by \a name is posted by the database. + + Reimplement this slot to provide your own QSqlDriver subclass with event notification + support; because of binary compatibility constraints, the unsubscribeFromNotification() + function (introduced in Qt 4.4) is not virtual. Instead, unsubscribeFromNotification() + will dynamically detect and call \e this slot. The default implementation does nothing + and returns false. + + \since 4.4 + \sa unsubscribeFromNotification() +*/ +bool QSqlDriver::unsubscribeFromNotificationImplementation(const QString &name) +{ + Q_UNUSED(name); + return false; +} + +/*! + Returns a list of the names of the event notifications that are currently subscribed to. + + Reimplement this slot to provide your own QSqlDriver subclass with event notification + support; because of binary compatibility constraints, the subscribedToNotifications() + function (introduced in Qt 4.4) is not virtual. Instead, subscribedToNotifications() + will dynamically detect and call \e this slot. The default implementation simply + returns an empty QStringList. + + \since 4.4 + \sa subscribedToNotifications() +*/ +QStringList QSqlDriver::subscribedToNotificationsImplementation() const +{ + return QStringList(); +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqldriver.h b/src/sql/kernel/qsqldriver.h new file mode 100644 index 0000000..e763719 --- /dev/null +++ b/src/sql/kernel/qsqldriver.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLDRIVER_H +#define QSQLDRIVER_H + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> +#include <QtSql/qsql.h> +#ifdef QT3_SUPPORT +#include <QtSql/qsqlquery.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlDatabase; +class QSqlDriverPrivate; +class QSqlError; +class QSqlField; +class QSqlIndex; +class QSqlRecord; +class QSqlResult; +class QVariant; + +class Q_SQL_EXPORT QSqlDriver : public QObject +{ + friend class QSqlDatabase; + Q_OBJECT + Q_DECLARE_PRIVATE(QSqlDriver) + +public: + enum DriverFeature { Transactions, QuerySize, BLOB, Unicode, PreparedQueries, + NamedPlaceholders, PositionalPlaceholders, LastInsertId, + BatchOperations, SimpleLocking, LowPrecisionNumbers, + EventNotifications, FinishQuery, MultipleResultSets }; + + enum StatementType { WhereStatement, SelectStatement, UpdateStatement, + InsertStatement, DeleteStatement }; + + enum IdentifierType { FieldName, TableName }; + + explicit QSqlDriver(QObject *parent=0); + ~QSqlDriver(); + virtual bool isOpen() const; + bool isOpenError() const; + + virtual bool beginTransaction(); + virtual bool commitTransaction(); + virtual bool rollbackTransaction(); + virtual QStringList tables(QSql::TableType tableType) const; + virtual QSqlIndex primaryIndex(const QString &tableName) const; + virtual QSqlRecord record(const QString &tableName) const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QSqlRecord record(const QSqlQuery& query) const + { return query.record(); } + inline QT3_SUPPORT QSqlRecord recordInfo(const QString& tablename) const + { return record(tablename); } + inline QT3_SUPPORT QSqlRecord recordInfo(const QSqlQuery& query) const + { return query.record(); } + inline QT3_SUPPORT QString nullText() const { return QLatin1String("NULL"); } + inline QT3_SUPPORT QString formatValue(const QSqlField *field, bool trimStrings = false) const + { return field ? formatValue(*field, trimStrings) : QString(); } +#endif + virtual QString formatValue(const QSqlField& field, bool trimStrings = false) const; + + virtual QString escapeIdentifier(const QString &identifier, IdentifierType type) const; + virtual QString sqlStatement(StatementType type, const QString &tableName, + const QSqlRecord &rec, bool preparedStatement) const; + + QSqlError lastError() const; + + virtual QVariant handle() const; + virtual bool hasFeature(DriverFeature f) const = 0; + virtual void close() = 0; + virtual QSqlResult *createResult() const = 0; + + virtual bool open(const QString& db, + const QString& user = QString(), + const QString& password = QString(), + const QString& host = QString(), + int port = -1, + const QString& connOpts = QString()) = 0; + bool subscribeToNotification(const QString &name); // ### Qt 5: make virtual + bool unsubscribeFromNotification(const QString &name); // ### Qt 5: make virtual + QStringList subscribedToNotifications() const; // ### Qt 5: make virtual + +Q_SIGNALS: + void notification(const QString &name); + +protected: + virtual void setOpen(bool o); + virtual void setOpenError(bool e); + virtual void setLastError(const QSqlError& e); + +protected Q_SLOTS: + bool subscribeToNotificationImplementation(const QString &name); // ### Qt 5: eliminate, see subscribeToNotification() + bool unsubscribeFromNotificationImplementation(const QString &name); // ### Qt 5: eliminate, see unsubscribeFromNotification() + QStringList subscribedToNotificationsImplementation() const; // ### Qt 5: eliminate, see subscribedNotifications() + +private: + Q_DISABLE_COPY(QSqlDriver) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLDRIVER_H diff --git a/src/sql/kernel/qsqldriverplugin.cpp b/src/sql/kernel/qsqldriverplugin.cpp new file mode 100644 index 0000000..b3de2cd --- /dev/null +++ b/src/sql/kernel/qsqldriverplugin.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqldriverplugin.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSqlDriverPlugin + \brief The QSqlDriverPlugin class provides an abstract base for custom QSqlDriver plugins. + + \ingroup plugins + \inmodule QtSql + + The SQL driver plugin is a simple plugin interface that makes it + easy to create your own SQL driver plugins that can be loaded + dynamically by Qt. + + Writing a SQL plugin is achieved by subclassing this base class, + reimplementing the pure virtual functions keys() and create(), and + exporting the class with the Q_EXPORT_PLUGIN2() macro. See the SQL + plugins that come with Qt for example implementations (in the + \c{plugins/src/sqldrivers} subdirectory of the source + distribution). + + \sa {How to Create Qt Plugins} +*/ + +/*! + \fn QStringList QSqlDriverPlugin::keys() const + + Returns the list of drivers (keys) this plugin supports. + + These keys are usually the class names of the custom drivers that + are implemented in the plugin. + + \sa create() +*/ + +/*! + \fn QSqlDriver *QSqlDriverPlugin::create(const QString& key) + + Creates and returns a QSqlDriver object for the driver called \a + key. The driver key is usually the class name of the required + driver. Keys are case sensitive. + + \sa keys() +*/ + +/*! + Constructs a SQL driver plugin and sets the parent to \a parent. + This is invoked automatically by the Q_EXPORT_PLUGIN2() macro. +*/ + +QSqlDriverPlugin::QSqlDriverPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the SQL driver plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QSqlDriverPlugin::~QSqlDriverPlugin() +{ +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqldriverplugin.h b/src/sql/kernel/qsqldriverplugin.h new file mode 100644 index 0000000..1150360 --- /dev/null +++ b/src/sql/kernel/qsqldriverplugin.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLDRIVERPLUGIN_H +#define QSQLDRIVERPLUGIN_H + +#include <QtCore/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlDriver; + +struct Q_SQL_EXPORT QSqlDriverFactoryInterface : public QFactoryInterface +{ + virtual QSqlDriver *create(const QString &name) = 0; +}; + +#define QSqlDriverFactoryInterface_iid "com.trolltech.Qt.QSqlDriverFactoryInterface" +Q_DECLARE_INTERFACE(QSqlDriverFactoryInterface, QSqlDriverFactoryInterface_iid) + +class Q_SQL_EXPORT QSqlDriverPlugin : public QObject, public QSqlDriverFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QSqlDriverFactoryInterface:QFactoryInterface) +public: + explicit QSqlDriverPlugin(QObject *parent = 0); + ~QSqlDriverPlugin(); + + virtual QStringList keys() const = 0; + virtual QSqlDriver *create(const QString &key) = 0; + +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLDRIVERPLUGIN_H diff --git a/src/sql/kernel/qsqlerror.cpp b/src/sql/kernel/qsqlerror.cpp new file mode 100644 index 0000000..14fc050 --- /dev/null +++ b/src/sql/kernel/qsqlerror.cpp @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlerror.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QSqlError &s) +{ + dbg.nospace() << "QSqlError(" << s.number() << ", " << s.driverText() << + ", " << s.databaseText() << ")"; + return dbg.space(); +} +#endif + +/*! + \class QSqlError + \brief The QSqlError class provides SQL database error information. + + \ingroup database + \inmodule QtSql + + A QSqlError object can provide database-specific error data, + including the driverText() and databaseText() messages (or both + concatenated together as text()), and the error number() and + type(). The functions all have setters so that you can create and + return QSqlError objects from your own classes, for example from + your own SQL drivers. + + \sa QSqlDatabase::lastError(), QSqlQuery::lastError() +*/ + +/*! + \enum QSqlError::ErrorType + + This enum type describes the context in which the error occurred, e.g., a connection error, a statement error, etc. + + \value NoError No error occurred. + \value ConnectionError Connection error. + \value StatementError SQL statement syntax error. + \value TransactionError Transaction failed error. + \value UnknownError Unknown error. + + \omitvalue None + \omitvalue Connection + \omitvalue Statement + \omitvalue Transaction + \omitvalue Unknown +*/ + +/*! + Constructs an error containing the driver error text \a + driverText, the database-specific error text \a databaseText, the + type \a type and the optional error number \a number. +*/ + +QSqlError::QSqlError(const QString& driverText, const QString& databaseText, ErrorType type, + int number) + : driverError(driverText), databaseError(databaseText), errorType(type), errorNumber(number) +{ +} + +/*! + Creates a copy of \a other. +*/ +QSqlError::QSqlError(const QSqlError& other) + : driverError(other.driverError), databaseError(other.databaseError), + errorType(other.errorType), + errorNumber(other.errorNumber) +{ +} + +/*! + Assigns the \a other error's values to this error. +*/ + +QSqlError& QSqlError::operator=(const QSqlError& other) +{ + driverError = other.driverError; + databaseError = other.databaseError; + errorType = other.errorType; + errorNumber = other.errorNumber; + return *this; +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlError::~QSqlError() +{ +} + +/*! + Returns the text of the error as reported by the driver. This may + contain database-specific descriptions. It may also be empty. + + \sa setDriverText() databaseText() text() +*/ +QString QSqlError::driverText() const +{ + return driverError; +} + +/*! + Sets the driver error text to the value of \a driverText. + + \sa driverText() setDatabaseText() text() +*/ + +void QSqlError::setDriverText(const QString& driverText) +{ + driverError = driverText; +} + +/*! + Returns the text of the error as reported by the database. This + may contain database-specific descriptions; it may be empty. + + \sa setDatabaseText() driverText() text() +*/ + +QString QSqlError::databaseText() const +{ + return databaseError; +} + +/*! + Sets the database error text to the value of \a databaseText. + + \sa databaseText() setDriverText() text() +*/ + +void QSqlError::setDatabaseText(const QString& databaseText) +{ + databaseError = databaseText; +} + +/*! + Returns the error type, or -1 if the type cannot be determined. + + \sa setType() +*/ + +QSqlError::ErrorType QSqlError::type() const +{ + return errorType; +} + +/*! + Sets the error type to the value of \a type. + + \sa type() +*/ + +void QSqlError::setType(ErrorType type) +{ + errorType = type; +} + +/*! + Returns the database-specific error number, or -1 if it cannot be + determined. + + \sa setNumber() +*/ + +int QSqlError::number() const +{ + return errorNumber; +} + +/*! + Sets the database-specific error number to \a number. + + \sa number() +*/ + +void QSqlError::setNumber(int number) +{ + errorNumber = number; +} + +/*! + This is a convenience function that returns databaseText() and + driverText() concatenated into a single string. + + \sa driverText() databaseText() +*/ + +QString QSqlError::text() const +{ + QString result = databaseError; + if (!databaseError.endsWith(QLatin1String("\n"))) + result += QLatin1Char(' '); + result += driverError; + return result; +} + +/*! + Returns true if an error is set, otherwise false. + + Example: + \snippet doc/src/snippets/code/src_sql_kernel_qsqlerror.cpp 0 + + \sa type() +*/ +bool QSqlError::isValid() const +{ + return errorType != NoError; +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlerror.h b/src/sql/kernel/qsqlerror.h new file mode 100644 index 0000000..9e09c27 --- /dev/null +++ b/src/sql/kernel/qsqlerror.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLERROR_H +#define QSQLERROR_H + +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class Q_SQL_EXPORT QSqlError +{ +public: + enum ErrorType { + NoError, + ConnectionError, + StatementError, + TransactionError, + UnknownError +#ifdef QT3_SUPPORT + , None = NoError, + Connection = ConnectionError, + Statement = StatementError, + Transaction = TransactionError, + Unknown = UnknownError +#endif + }; + QSqlError( const QString& driverText = QString(), + const QString& databaseText = QString(), + ErrorType type = NoError, + int number = -1); + QSqlError(const QSqlError& other); + QSqlError& operator=(const QSqlError& other); + ~QSqlError(); + + QString driverText() const; + void setDriverText(const QString& driverText); + QString databaseText() const; + void setDatabaseText(const QString& databaseText); + ErrorType type() const; + void setType(ErrorType type); + int number() const; + void setNumber(int number); + QString text() const; + bool isValid() const; + +private: + QString driverError; + QString databaseError; + ErrorType errorType; + int errorNumber; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlError &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLERROR_H diff --git a/src/sql/kernel/qsqlfield.cpp b/src/sql/kernel/qsqlfield.cpp new file mode 100644 index 0000000..4c1f1f4 --- /dev/null +++ b/src/sql/kernel/qsqlfield.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlfield.h" +#include "qatomic.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +class QSqlFieldPrivate +{ +public: + QSqlFieldPrivate(const QString &name, + QVariant::Type type) : + ref(1), nm(name), ro(false), type(type), req(QSqlField::Unknown), + len(-1), prec(-1), tp(-1), gen(true), autoval(false) + { + } + + QSqlFieldPrivate(const QSqlFieldPrivate &other) + : ref(1), + nm(other.nm), + ro(other.ro), + type(other.type), + req(other.req), + len(other.len), + prec(other.prec), + def(other.def), + tp(other.tp), + gen(other.gen), + autoval(other.autoval) + {} + + bool operator==(const QSqlFieldPrivate& other) const + { + return (nm == other.nm + && ro == other.ro + && type == other.type + && req == other.req + && len == other.len + && prec == other.prec + && def == other.def + && gen == other.gen + && autoval == other.autoval); + } + + QAtomicInt ref; + QString nm; + uint ro: 1; + QVariant::Type type; + QSqlField::RequiredStatus req; + int len; + int prec; + QVariant def; + int tp; + uint gen: 1; + uint autoval: 1; +}; + + +/*! + \class QSqlField + \brief The QSqlField class manipulates the fields in SQL database tables + and views. + + \ingroup database + \ingroup shared + \inmodule QtSql + + QSqlField represents the characteristics of a single column in a + database table or view, such as the data type and column name. A + field also contains the value of the database column, which can be + viewed or changed. + + Field data values are stored as QVariants. Using an incompatible + type is not permitted. For example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 2 + + However, the field will attempt to cast certain data types to the + field data type where possible: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 3 + + QSqlField objects are rarely created explicitly in application + code. They are usually accessed indirectly through \l{QSqlRecord}s + that already contain a list of fields. For example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 4 + \dots + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 5 + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 6 + + A QSqlField object can provide some meta-data about the field, for + example, its name(), variant type(), length(), precision(), + defaultValue(), typeID(), and its requiredStatus(), + isGenerated() and isReadOnly(). The field's data can be + checked to see if it isNull(), and its value() retrieved. When + editing the data can be set with setValue() or set to NULL with + clear(). + + \sa QSqlRecord +*/ + +/*! + \enum QSqlField::RequiredStatus + + Specifies whether the field is required or optional. + + \value Required The field must be specified when inserting records. + \value Optional The fields doesn't have to be specified when inserting records. + \value Unknown The database driver couldn't determine whether the field is required or + optional. + + \sa requiredStatus() +*/ + +/*! + Constructs an empty field called \a fieldName of variant type \a + type. + + \sa setRequiredStatus() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly() +*/ +QSqlField::QSqlField() +{ + d = new QSqlFieldPrivate(QString(), QVariant::Invalid); +} + +QSqlField::QSqlField(const QString& fieldName) +{ + d = new QSqlFieldPrivate(fieldName, QVariant::Invalid); +} + +QSqlField::QSqlField(const QString& fieldName, QVariant::Type type) +{ + d = new QSqlFieldPrivate(fieldName, type); +} +/*! + Constructs a copy of \a other. +*/ + +QSqlField::QSqlField(const QSqlField& other) +{ + d = other.d; + d->ref.ref(); + val = other.val; +} + +/*! + Sets the field equal to \a other. +*/ + +QSqlField& QSqlField::operator=(const QSqlField& other) +{ + qAtomicAssign(d, other.d); + val = other.val; + return *this; +} + + +/*! \fn bool QSqlField::operator!=(const QSqlField &other) const + Returns true if the field is unequal to \a other; otherwise returns + false. +*/ + +/*! + Returns true if the field is equal to \a other; otherwise returns + false. +*/ +bool QSqlField::operator==(const QSqlField& other) const +{ + return ((d == other.d || *d == *other.d) + && val == other.val); +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlField::~QSqlField() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + Sets the required status of this field to \a required. + + \sa requiredStatus() setType() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly() +*/ +void QSqlField::setRequiredStatus(RequiredStatus required) +{ + detach(); + d->req = required; +} + +/*! \fn void QSqlField::setRequired(bool required) + + Sets the required status of this field to \l Required if \a + required is true; otherwise sets it to \l Optional. + + \sa setRequiredStatus(), requiredStatus() +*/ + +/*! + Sets the field's length to \a fieldLength. For strings this is the + maximum number of characters the string can hold; the meaning + varies for other types. + + \sa length() setType() setRequiredStatus() setPrecision() setDefaultValue() setGenerated() setReadOnly() +*/ +void QSqlField::setLength(int fieldLength) +{ + detach(); + d->len = fieldLength; +} + +/*! + Sets the field's \a precision. This only affects numeric fields. + + \sa precision() setType() setRequiredStatus() setLength() setDefaultValue() setGenerated() setReadOnly() +*/ +void QSqlField::setPrecision(int precision) +{ + detach(); + d->prec = precision; +} + +/*! + Sets the default value used for this field to \a value. + + \sa defaultValue() value() setType() setRequiredStatus() setLength() setPrecision() setGenerated() setReadOnly() +*/ +void QSqlField::setDefaultValue(const QVariant &value) +{ + detach(); + d->def = value; +} + +/*! + \internal +*/ +void QSqlField::setSqlType(int type) +{ + detach(); + d->tp = type; +} + +/*! + Sets the generated state. If \a gen is false, no SQL will + be generated for this field; otherwise, Qt classes such as + QSqlQueryModel and QSqlTableModel will generate SQL for this + field. + + \sa isGenerated() setType() setRequiredStatus() setLength() setPrecision() setDefaultValue() setReadOnly() +*/ +void QSqlField::setGenerated(bool gen) +{ + detach(); + d->gen = gen; +} + + +/*! + Sets the value of the field to \a value. If the field is read-only + (isReadOnly() returns true), nothing happens. + + If the data type of \a value differs from the field's current + data type, an attempt is made to cast it to the proper type. This + preserves the data type of the field in the case of assignment, + e.g. a QString to an integer data type. + + To set the value to NULL, use clear(). + + \sa value() isReadOnly() defaultValue() +*/ + +void QSqlField::setValue(const QVariant& value) +{ + if (isReadOnly()) + return; + val = value; +} + +/*! + Clears the value of the field and sets it to NULL. + If the field is read-only, nothing happens. + + \sa setValue() isReadOnly() requiredStatus() +*/ + +void QSqlField::clear() +{ + if (isReadOnly()) + return; + val = QVariant(type()); +} + +/*! + Sets the name of the field to \a name. + + \sa name() +*/ + +void QSqlField::setName(const QString& name) +{ + detach(); + d->nm = name; +} + +/*! + Sets the read only flag of the field's value to \a readOnly. A + read-only field cannot have its value set with setValue() and + cannot be cleared to NULL with clear(). +*/ +void QSqlField::setReadOnly(bool readOnly) +{ + detach(); + d->ro = readOnly; +} + +/*! + \fn QVariant QSqlField::value() const + + Returns the value of the field as a QVariant. + + Use isNull() to check if the field's value is NULL. +*/ + +/*! + Returns the name of the field. + + \sa setName() +*/ +QString QSqlField::name() const +{ + return d->nm; +} + +/*! + Returns the field's type as stored in the database. + Note that the actual value might have a different type, + Numerical values that are too large to store in a long + int or double are usually stored as strings to prevent + precision loss. + + \sa setType() +*/ +QVariant::Type QSqlField::type() const +{ + return d->type; +} + +/*! + Set's the field's variant type to \a type. + + \sa type() setRequiredStatus() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly() +*/ +void QSqlField::setType(QVariant::Type type) +{ + detach(); + d->type = type; +} + + +/*! + Returns true if the field's value is read-only; otherwise returns + false. + + \sa setReadOnly() type() requiredStatus() length() precision() defaultValue() isGenerated() +*/ +bool QSqlField::isReadOnly() const +{ return d->ro; } + +/*! + Returns true if the field's value is NULL; otherwise returns + false. + + \sa value() +*/ +bool QSqlField::isNull() const +{ return val.isNull(); } + +/*! \internal +*/ +void QSqlField::detach() +{ + qAtomicDetach(d); +} + +/*! + Returns true if this is a required field; otherwise returns false. + An \c INSERT will fail if a required field does not have a value. + + \sa setRequiredStatus() type() length() precision() defaultValue() isGenerated() +*/ +QSqlField::RequiredStatus QSqlField::requiredStatus() const +{ + return d->req; +} + +/*! + Returns the field's length. + + If the returned value is negative, it means that the information + is not available from the database. + + \sa setLength() type() requiredStatus() precision() defaultValue() isGenerated() +*/ +int QSqlField::length() const +{ + return d->len; +} + +/*! + Returns the field's precision; this is only meaningful for numeric + types. + + If the returned value is negative, it means that the information + is not available from the database. + + \sa setPrecision() type() requiredStatus() length() defaultValue() isGenerated() +*/ +int QSqlField::precision() const +{ + return d->prec; +} + +/*! + Returns the field's default value (which may be NULL). + + \sa setDefaultValue() type() requiredStatus() length() precision() isGenerated() +*/ +QVariant QSqlField::defaultValue() const +{ + return d->def; +} + +/*! + \internal + + Returns the type ID for the field. + + If the returned value is negative, it means that the information + is not available from the database. +*/ +int QSqlField::typeID() const +{ + return d->tp; +} + +/*! + Returns true if the field is generated; otherwise returns + false. + + \sa setGenerated() type() requiredStatus() length() precision() defaultValue() +*/ +bool QSqlField::isGenerated() const +{ + return d->gen; +} + +/*! + Returns true if the field's variant type is valid; otherwise + returns false. +*/ +bool QSqlField::isValid() const +{ + return d->type != QVariant::Invalid; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QSqlField &f) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QSqlField(" << f.name() << ", " << QVariant::typeToName(f.type()); + if (f.length() >= 0) + dbg.nospace() << ", length: " << f.length(); + if (f.precision() >= 0) + dbg.nospace() << ", precision: " << f.precision(); + if (f.requiredStatus() != QSqlField::Unknown) + dbg.nospace() << ", required: " + << (f.requiredStatus() == QSqlField::Required ? "yes" : "no"); + dbg.nospace() << ", generated: " << (f.isGenerated() ? "yes" : "no"); + if (f.typeID() >= 0) + dbg.nospace() << ", typeID: " << f.typeID(); + if (!f.defaultValue().isNull()) + dbg.nospace() << ", auto-value: \"" << f.defaultValue() << "\""; + dbg.nospace() << ")"; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QSqlField to QDebug"); + return dbg; + Q_UNUSED(f); +#endif +} +#endif + +/*! + \fn void QSqlField::setNull() + + Use clear() instead. +*/ + +/*! + Returns true if the value is auto-generated by the database, + for example auto-increment primary key values. + + \sa setAutoValue() +*/ +bool QSqlField::isAutoValue() const +{ + return d->autoval; +} + +/*! + Marks the field as an auto-generated value if \a autoVal + is true. + + \sa isAutoValue() + */ +void QSqlField::setAutoValue(bool autoVal) +{ + detach(); + d->autoval = autoVal; +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlfield.h b/src/sql/kernel/qsqlfield.h new file mode 100644 index 0000000..a5bd8c4 --- /dev/null +++ b/src/sql/kernel/qsqlfield.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLFIELD_H +#define QSQLFIELD_H + +#include <QtCore/qvariant.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlFieldPrivate; + +class Q_SQL_EXPORT QSqlField +{ +public: + enum RequiredStatus { Unknown = -1, Optional = 0, Required = 1 }; + + QSqlField(); + QSqlField(const QString& fieldName); + QSqlField(const QString& fieldName, QVariant::Type type); + QSqlField(const QSqlField& other); + QSqlField& operator=(const QSqlField& other); + bool operator==(const QSqlField& other) const; + inline bool operator!=(const QSqlField &other) const { return !operator==(other); } + ~QSqlField(); + + void setValue(const QVariant& value); + inline QVariant value() const + { return val; } + void setName(const QString& name); + QString name() const; + bool isNull() const; + void setReadOnly(bool readOnly); + bool isReadOnly() const; + void clear(); + QVariant::Type type() const; + bool isAutoValue() const; + + void setType(QVariant::Type type); + void setRequiredStatus(RequiredStatus status); + inline void setRequired(bool required) + { setRequiredStatus(required ? Required : Optional); } + void setLength(int fieldLength); + void setPrecision(int precision); + void setDefaultValue(const QVariant &value); + void setSqlType(int type); + void setGenerated(bool gen); + void setAutoValue(bool autoVal); + + RequiredStatus requiredStatus() const; + int length() const; + int precision() const; + QVariant defaultValue() const; + int typeID() const; + bool isGenerated() const; + bool isValid() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void setNull() { clear(); } +#endif + +private: + void detach(); + QVariant val; + QSqlFieldPrivate* d; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlField &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLFIELD_H diff --git a/src/sql/kernel/qsqlindex.cpp b/src/sql/kernel/qsqlindex.cpp new file mode 100644 index 0000000..02cdfb3 --- /dev/null +++ b/src/sql/kernel/qsqlindex.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlindex.h" + +#include "qsqlfield.h" +#include "qstringlist.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSqlIndex + \brief The QSqlIndex class provides functions to manipulate and + describe database indexes. + + \ingroup database + \inmodule QtSql + + An \e index refers to a single table or view in a database. + Information about the fields that comprise the index can be used + to generate SQL statements. +*/ + +/*! + Constructs an empty index using the cursor name \a cursorname and + index name \a name. +*/ + +QSqlIndex::QSqlIndex(const QString& cursorname, const QString& name) + : cursor(cursorname), nm(name) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QSqlIndex::QSqlIndex(const QSqlIndex& other) + : QSqlRecord(other), cursor(other.cursor), nm(other.nm), sorts(other.sorts) +{ +} + +/*! + Sets the index equal to \a other. +*/ + +QSqlIndex& QSqlIndex::operator=(const QSqlIndex& other) +{ + cursor = other.cursor; + nm = other.nm; + sorts = other.sorts; + QSqlRecord::operator=(other); + return *this; +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlIndex::~QSqlIndex() +{ + +} + +/*! + Sets the name of the index to \a name. +*/ + +void QSqlIndex::setName(const QString& name) +{ + nm = name; +} + +/*! + \fn QString QSqlIndex::name() const + + Returns the name of the index. +*/ + +/*! + Appends the field \a field to the list of indexed fields. The + field is appended with an ascending sort order. +*/ + +void QSqlIndex::append(const QSqlField& field) +{ + append(field, false); +} + +/*! + \overload + + Appends the field \a field to the list of indexed fields. The + field is appended with an ascending sort order, unless \a desc is + true. +*/ + +void QSqlIndex::append(const QSqlField& field, bool desc) +{ + sorts.append(desc); + QSqlRecord::append(field); +} + + +/*! + Returns true if field \a i in the index is sorted in descending + order; otherwise returns false. +*/ + +bool QSqlIndex::isDescending(int i) const +{ + if (i >= 0 && i < sorts.size()) + return sorts[i]; + return false; +} + +/*! + If \a desc is true, field \a i is sorted in descending order. + Otherwise, field \a i is sorted in ascending order (the default). + If the field does not exist, nothing happens. +*/ + +void QSqlIndex::setDescending(int i, bool desc) +{ + if (i >= 0 && i < sorts.size()) + sorts[i] = desc; +} + +#ifdef QT3_SUPPORT + +/*! + Returns a comma-separated list of all the index's field names as a + string. This string is suitable, for example, for generating a + SQL SELECT statement. Only generated fields are included in the + list (see \l{isGenerated()}). If a \a prefix is specified, e.g. a + table name, it is prepended before all field names in the form: + + "\a{prefix}.<fieldname>" + + If \a sep is specified, each field is separated by \a sep. If \a + verbose is true (the default), each field contains a suffix + indicating an ASCending or DESCending sort order. +*/ + +QString QSqlIndex::toString(const QString& prefix, const QString& sep, bool verbose) const +{ + QString s; + bool comma = false; + for (int i = 0; i < count(); ++i) { + if(comma) + s += sep + QLatin1Char(' '); + s += createField(i, prefix, verbose); + comma = true; + } + return s; +} + +/*! + Returns a list of all the index's field names. Only generated + fields are included in the list (see \l{isGenerated()}). If a \a + prefix is specified, e.g. a table name, all fields are prefixed in + the form: + + "\a{prefix}.<fieldname>" + + If \a verbose is true (the default), each field contains a suffix + indicating an ASCending or DESCending sort order. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_sql_kernel_qsqlindex.cpp 0 + +*/ +QStringList QSqlIndex::toStringList(const QString& prefix, bool verbose) const +{ + QStringList s; + for (int i = 0; i < count(); ++i) + s += createField(i, prefix, verbose); + return s; +} +#endif + +/*! \internal + + Creates a string representing the field number \a i using prefix \a + prefix. If \a verbose is true, ASC or DESC is included in the field + description if the field is sorted in ASCending or DESCending order. +*/ + +QString QSqlIndex::createField(int i, const QString& prefix, bool verbose) const +{ + QString f; + if (!prefix.isEmpty()) + f += prefix + QLatin1Char('.'); + f += field(i).name(); + if (verbose) + f += QLatin1Char(' ') + QString((isDescending(i) + ? QLatin1String("DESC") : QLatin1String("ASC"))); + return f; +} + +/*! + \fn QString QSqlIndex::cursorName() const + + Returns the name of the cursor which the index is associated with. +*/ + + +/*! + Sets the name of the cursor that the index is associated with to + \a cursorName. +*/ +void QSqlIndex::setCursorName(const QString& cursorName) +{ + cursor = cursorName; +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlindex.h b/src/sql/kernel/qsqlindex.h new file mode 100644 index 0000000..82c0fb9 --- /dev/null +++ b/src/sql/kernel/qsqlindex.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLINDEX_H +#define QSQLINDEX_H + +#include <QtSql/qsqlrecord.h> +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class Q_SQL_EXPORT QSqlIndex : public QSqlRecord +{ +public: + QSqlIndex(const QString &cursorName = QString(), const QString &name = QString()); + QSqlIndex(const QSqlIndex &other); + ~QSqlIndex(); + QSqlIndex &operator=(const QSqlIndex &other); + void setCursorName(const QString &cursorName); + inline QString cursorName() const { return cursor; } + void setName(const QString& name); + inline QString name() const { return nm; } + + void append(const QSqlField &field); + void append(const QSqlField &field, bool desc); + + bool isDescending(int i) const; + void setDescending(int i, bool desc); + +#ifdef QT3_SUPPORT + QT3_SUPPORT QString toString(const QString &prefix = QString(), + const QString &sep = QLatin1String(","), + bool verbose = true) const; + QT3_SUPPORT QStringList toStringList(const QString& prefix = QString(), + bool verbose = true) const; +#endif + +private: + QString createField(int i, const QString& prefix, bool verbose) const; + QString cursor; + QString nm; + QList<bool> sorts; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLINDEX_H diff --git a/src/sql/kernel/qsqlnulldriver_p.h b/src/sql/kernel/qsqlnulldriver_p.h new file mode 100644 index 0000000..e899a0b --- /dev/null +++ b/src/sql/kernel/qsqlnulldriver_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLNULLDRIVER_P_H +#define QSQLNULLDRIVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// + +#include "QtCore/qvariant.h" +#include "QtSql/qsqldriver.h" +#include "QtSql/qsqlerror.h" +#include "QtSql/qsqlresult.h" + +QT_BEGIN_NAMESPACE + +class QSqlNullResult : public QSqlResult +{ +public: + inline explicit QSqlNullResult(const QSqlDriver* d): QSqlResult(d) + { QSqlResult::setLastError( + QSqlError(QLatin1String("Driver not loaded"), QLatin1String("Driver not loaded"), QSqlError::ConnectionError)); } +protected: + inline QVariant data(int) { return QVariant(); } + inline bool reset (const QString&) { return false; } + inline bool fetch(int) { return false; } + inline bool fetchFirst() { return false; } + inline bool fetchLast() { return false; } + inline bool isNull(int) { return false; } + inline int size() { return -1; } + inline int numRowsAffected() { return 0; } + + inline void setAt(int) {} + inline void setActive(bool) {} + inline void setLastError(const QSqlError&) {} + inline void setQuery(const QString&) {} + inline void setSelect(bool) {} + inline void setForwardOnly(bool) {} + + inline bool exec() { return false; } + inline bool prepare(const QString&) { return false; } + inline bool savePrepare(const QString&) { return false; } + inline void bindValue(int, const QVariant&, QSql::ParamType) {} + inline void bindValue(const QString&, const QVariant&, QSql::ParamType) {} +}; + +class QSqlNullDriver : public QSqlDriver +{ +public: + inline QSqlNullDriver(): QSqlDriver() + { QSqlDriver::setLastError( + QSqlError(QLatin1String("Driver not loaded"), QLatin1String("Driver not loaded"), QSqlError::ConnectionError)); } + inline bool hasFeature(DriverFeature) const { return false; } + inline bool open(const QString &, const QString & , const QString & , + const QString &, int, const QString&) + { return false; } + inline void close() {} + inline QSqlResult *createResult() const { return new QSqlNullResult(this); } + +protected: + inline void setOpen(bool) {} + inline void setOpenError(bool) {} + inline void setLastError(const QSqlError&) {} +}; + +QT_END_NAMESPACE + +#endif // QSQLNULLDRIVER_P_H diff --git a/src/sql/kernel/qsqlquery.cpp b/src/sql/kernel/qsqlquery.cpp new file mode 100644 index 0000000..e6729a5 --- /dev/null +++ b/src/sql/kernel/qsqlquery.cpp @@ -0,0 +1,1220 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlquery.h" + +//#define QT_DEBUG_SQL + +#include "qatomic.h" +#include "qsqlrecord.h" +#include "qsqlresult.h" +#include "qsqldriver.h" +#include "qsqldatabase.h" +#include "private/qsqlnulldriver_p.h" +#include "qvector.h" +#include "qmap.h" + +QT_BEGIN_NAMESPACE + +class QSqlQueryPrivate +{ +public: + QSqlQueryPrivate(QSqlResult* result); + ~QSqlQueryPrivate(); + QAtomicInt ref; + QSqlResult* sqlResult; + QSql::NumericalPrecisionPolicy precisionPolicy; + + static QSqlQueryPrivate* shared_null(); +}; + +Q_GLOBAL_STATIC_WITH_ARGS(QSqlQueryPrivate, nullQueryPrivate, (0)) +Q_GLOBAL_STATIC(QSqlNullDriver, nullDriver) +Q_GLOBAL_STATIC_WITH_ARGS(QSqlNullResult, nullResult, (nullDriver())) + +QSqlQueryPrivate* QSqlQueryPrivate::shared_null() +{ + QSqlQueryPrivate *null = nullQueryPrivate(); + null->ref.ref(); + return null; +} + +/*! +\internal +*/ +QSqlQueryPrivate::QSqlQueryPrivate(QSqlResult* result) + : ref(1), sqlResult(result), precisionPolicy(QSql::HighPrecision) +{ + if (!sqlResult) + sqlResult = nullResult(); +} + +QSqlQueryPrivate::~QSqlQueryPrivate() +{ + QSqlResult *nr = nullResult(); + if (!nr || sqlResult == nr) + return; + delete sqlResult; +} + +/*! + \class QSqlQuery + \brief The QSqlQuery class provides a means of executing and + manipulating SQL statements. + + \ingroup database + \ingroup shared + \mainclass + \inmodule QtSql + + QSqlQuery encapsulates the functionality involved in creating, + navigating and retrieving data from SQL queries which are + executed on a \l QSqlDatabase. It can be used to execute DML + (data manipulation language) statements, such as \c SELECT, \c + INSERT, \c UPDATE and \c DELETE, as well as DDL (data definition + language) statements, such as \c{CREATE} \c{TABLE}. It can also + be used to execute database-specific commands which are not + standard SQL (e.g. \c{SET DATESTYLE=ISO} for PostgreSQL). + + Successfully executed SQL statements set the query's state to + active so that isActive() returns true. Otherwise the query's + state is set to inactive. In either case, when executing a new SQL + statement, the query is positioned on an invalid record. An active + query must be navigated to a valid record (so that isValid() + returns true) before values can be retrieved. + + For some databases, if an active query that is a \c{SELECT} + statement exists when you call \l{QSqlDatabase::}{commit()} or + \l{QSqlDatabase::}{rollback()}, the commit or rollback will + fail. See isActive() for details. + + \target QSqlQuery examples + + Navigating records is performed with the following functions: + + \list + \o next() + \o previous() + \o first() + \o last() + \o seek() + \endlist + + These functions allow the programmer to move forward, backward + or arbitrarily through the records returned by the query. If you + only need to move forward through the results (e.g., by using + next()), you can use setForwardOnly(), which will save a + significant amount of memory overhead and improve performance on + some databases. Once an active query is positioned on a valid + record, data can be retrieved using value(). All data is + transferred from the SQL backend using QVariants. + + For example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 7 + + To access the data returned by a query, use value(int). Each + field in the data returned by a \c SELECT statement is accessed + by passing the field's position in the statement, starting from + 0. This makes using \c{SELECT *} queries inadvisable because the + order of the fields returned is indeterminate. + + For the sake of efficiency, there are no functions to access a + field by name (unless you use prepared queries with names, as + explained below). To convert a field name into an index, use + record().\l{QSqlRecord::indexOf()}{indexOf()}, for example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 8 + + QSqlQuery supports prepared query execution and the binding of + parameter values to placeholders. Some databases don't support + these features, so for those, Qt emulates the required + functionality. For example, the Oracle and ODBC drivers have + proper prepared query support, and Qt makes use of it; but for + databases that don't have this support, Qt implements the feature + itself, e.g. by replacing placeholders with actual values when a + query is executed. Use numRowsAffected() to find out how many rows + were affected by a non-\c SELECT query, and size() to find how + many were retrieved by a \c SELECT. + + Oracle databases identify placeholders by using a colon-name + syntax, e.g \c{:name}. ODBC simply uses \c ? characters. Qt + supports both syntaxes, with the restriction that you can't mix + them in the same query. + + You can retrieve the values of all the fields in a single variable + (a map) using boundValues(). + + \section1 Approaches to Binding Values + + Below we present the same example using each of the four + different binding approaches, as well as one example of binding + values to a stored procedure. + + \bold{Named binding using named placeholders:} + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 9 + + \bold{Positional binding using named placeholders:} + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 10 + + \bold{Binding values using positional placeholders (version 1):} + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 11 + + \bold{Binding values using positional placeholders (version 2):} + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 12 + + \bold{Binding values to a stored procedure:} + + This code calls a stored procedure called \c AsciiToInt(), passing + it a character through its in parameter, and taking its result in + the out parameter. + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 13 + + Note that unbound parameters will retain their values. + + Stored procedures that uses the return statement to return values, + or return multiple result sets, are not fully supported. For specific + details see \l{SQL Database Drivers}. + + \warning You must load the SQL driver and open the connection before a + QSqlQuery is created. Also, the connection must remain open while the + query exists; otherwise, the behavior of QSqlQuery is undefined. + + \sa QSqlDatabase, QSqlQueryModel, QSqlTableModel, QVariant +*/ + +/*! + Constructs a QSqlQuery object which uses the QSqlResult \a result + to communicate with a database. +*/ + +QSqlQuery::QSqlQuery(QSqlResult *result) +{ + d = new QSqlQueryPrivate(result); +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlQuery::~QSqlQuery() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + Constructs a copy of \a other. +*/ + +QSqlQuery::QSqlQuery(const QSqlQuery& other) +{ + d = other.d; + d->ref.ref(); +} + +/*! + \internal +*/ +static void qInit(QSqlQuery *q, const QString& query, QSqlDatabase db) +{ + QSqlDatabase database = db; + if (!database.isValid()) + database = QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false); + if (database.isValid()) { + *q = QSqlQuery(database.driver()->createResult()); + } + if (!query.isEmpty()) + q->exec(query); +} + +/*! + Constructs a QSqlQuery object using the SQL \a query and the + database \a db. If \a db is not specified, the application's + default database is used. If \a query is not an empty string, it + will be executed. + + \sa QSqlDatabase +*/ +QSqlQuery::QSqlQuery(const QString& query, QSqlDatabase db) +{ + d = QSqlQueryPrivate::shared_null(); + qInit(this, query, db); +} + +/*! + Constructs a QSqlQuery object using the database \a db. + + \sa QSqlDatabase +*/ + +QSqlQuery::QSqlQuery(QSqlDatabase db) +{ + d = QSqlQueryPrivate::shared_null(); + qInit(this, QString(), db); +} + + +/*! + Assigns \a other to this object. +*/ + +QSqlQuery& QSqlQuery::operator=(const QSqlQuery& other) +{ + qAtomicAssign(d, other.d); + return *this; +} + +/*! + Returns true if the query is \l{isActive()}{active} and positioned + on a valid record and the \a field is NULL; otherwise returns + false. Note that for some drivers, isNull() will not return accurate + information until after an attempt is made to retrieve data. + + \sa isActive(), isValid(), value() +*/ + +bool QSqlQuery::isNull(int field) const +{ + if (d->sqlResult->isActive() && d->sqlResult->isValid()) + return d->sqlResult->isNull(field); + return true; +} + +/*! + + Executes the SQL in \a query. Returns true and sets the query state + to \l{isActive()}{active} if the query was successful; otherwise + returns false. The \a query string must use syntax appropriate for + the SQL database being queried (for example, standard SQL). + + After the query is executed, the query is positioned on an \e + invalid record and must be navigated to a valid record before data + values can be retrieved (for example, using next()). + + Note that the last error for this query is reset when exec() is + called. + + Example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 34 + + \sa isActive(), isValid(), next(), previous(), first(), last(), + seek() +*/ + +bool QSqlQuery::exec(const QString& query) +{ + if (d->ref != 1) { + bool fo = isForwardOnly(); + *this = QSqlQuery(driver()->createResult()); + d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + setForwardOnly(fo); + } else { + d->sqlResult->clear(); + d->sqlResult->setActive(false); + d->sqlResult->setLastError(QSqlError()); + d->sqlResult->setAt(QSql::BeforeFirstRow); + d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + } + d->sqlResult->setQuery(query.trimmed()); + if (!driver()->isOpen() || driver()->isOpenError()) { + qWarning("QSqlQuery::exec: database not open"); + return false; + } + if (query.isEmpty()) { + qWarning("QSqlQuery::exec: empty query"); + return false; + } +#ifdef QT_DEBUG_SQL + qDebug("\n QSqlQuery: %s", query.toLocal8Bit().constData()); +#endif + return d->sqlResult->reset(query); +} + +/*! + Returns the value of field \a index in the current record. + + The fields are numbered from left to right using the text of the + \c SELECT statement, e.g. in + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 0 + + field 0 is \c forename and field 1 is \c + surname. Using \c{SELECT *} is not recommended because the order + of the fields in the query is undefined. + + An invalid QVariant is returned if field \a index does not + exist, if the query is inactive, or if the query is positioned on + an invalid record. + + \sa previous() next() first() last() seek() isActive() isValid() +*/ + +QVariant QSqlQuery::value(int index) const +{ + if (isActive() && isValid() && (index > QSql::BeforeFirstRow)) + return d->sqlResult->data(index); + qWarning("QSqlQuery::value: not positioned on a valid record"); + return QVariant(); +} + +/*! + Returns the current internal position of the query. The first + record is at position zero. If the position is invalid, the + function returns QSql::BeforeFirstRow or + QSql::AfterLastRow, which are special negative values. + + \sa previous() next() first() last() seek() isActive() isValid() +*/ + +int QSqlQuery::at() const +{ + return d->sqlResult->at(); +} + +/*! + Returns the text of the current query being used, or an empty + string if there is no current query text. + + \sa executedQuery() +*/ + +QString QSqlQuery::lastQuery() const +{ + return d->sqlResult->lastQuery(); +} + +/*! + Returns the database driver associated with the query. +*/ + +const QSqlDriver *QSqlQuery::driver() const +{ + return d->sqlResult->driver(); +} + +/*! + Returns the result associated with the query. +*/ + +const QSqlResult* QSqlQuery::result() const +{ + return d->sqlResult; +} + +/*! + Retrieves the record at position \a index, if available, and + positions the query on the retrieved record. The first record is at + position 0. Note that the query must be in an \l{isActive()} + {active} state and isSelect() must return true before calling this + function. + + If \a relative is false (the default), the following rules apply: + + \list + + \o If \a index is negative, the result is positioned before the + first record and false is returned. + + \o Otherwise, an attempt is made to move to the record at position + \a index. If the record at position \a index could not be retrieved, + the result is positioned after the last record and false is + returned. If the record is successfully retrieved, true is returned. + + \endlist + + If \a relative is true, the following rules apply: + + \list + + \o If the result is currently positioned before the first record or + on the first record, and \a index is negative, there is no change, + and false is returned. + + \o If the result is currently located after the last record, and \a + index is positive, there is no change, and false is returned. + + \o If the result is currently located somewhere in the middle, and + the relative offset \a index moves the result below zero, the result + is positioned before the first record and false is returned. + + \o Otherwise, an attempt is made to move to the record \a index + records ahead of the current record (or \a index records behind the + current record if \a index is negative). If the record at offset \a + index could not be retrieved, the result is positioned after the + last record if \a index >= 0, (or before the first record if \a + index is negative), and false is returned. If the record is + successfully retrieved, true is returned. + + \endlist + + \sa next() previous() first() last() at() isActive() isValid() +*/ +bool QSqlQuery::seek(int index, bool relative) +{ + if (!isSelect() || !isActive()) + return false; + int actualIdx; + if (!relative) { // arbitrary seek + if (index < 0) { + d->sqlResult->setAt(QSql::BeforeFirstRow); + return false; + } + actualIdx = index; + } else { + switch (at()) { // relative seek + case QSql::BeforeFirstRow: + if (index > 0) + actualIdx = index; + else { + return false; + } + break; + case QSql::AfterLastRow: + if (index < 0) { + d->sqlResult->fetchLast(); + actualIdx = at() + index; + } else { + return false; + } + break; + default: + if ((at() + index) < 0) { + d->sqlResult->setAt(QSql::BeforeFirstRow); + return false; + } + actualIdx = at() + index; + break; + } + } + // let drivers optimize + if (isForwardOnly() && actualIdx < at()) { + qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query"); + return false; + } + if (actualIdx == (at() + 1) && at() != QSql::BeforeFirstRow) { + if (!d->sqlResult->fetchNext()) { + d->sqlResult->setAt(QSql::AfterLastRow); + return false; + } + return true; + } + if (actualIdx == (at() - 1)) { + if (!d->sqlResult->fetchPrevious()) { + d->sqlResult->setAt(QSql::BeforeFirstRow); + return false; + } + return true; + } + if (!d->sqlResult->fetch(actualIdx)) { + d->sqlResult->setAt(QSql::AfterLastRow); + return false; + } + return true; +} + +/*! + + Retrieves the next record in the result, if available, and positions + the query on the retrieved record. Note that the result must be in + the \l{isActive()}{active} state and isSelect() must return true + before calling this function or it will do nothing and return false. + + The following rules apply: + + \list + + \o If the result is currently located before the first record, + e.g. immediately after a query is executed, an attempt is made to + retrieve the first record. + + \o If the result is currently located after the last record, there + is no change and false is returned. + + \o If the result is located somewhere in the middle, an attempt is + made to retrieve the next record. + + \endlist + + If the record could not be retrieved, the result is positioned after + the last record and false is returned. If the record is successfully + retrieved, true is returned. + + \sa previous() first() last() seek() at() isActive() isValid() +*/ +bool QSqlQuery::next() +{ + if (!isSelect() || !isActive()) + return false; + bool b = false; + switch (at()) { + case QSql::BeforeFirstRow: + b = d->sqlResult->fetchFirst(); + return b; + case QSql::AfterLastRow: + return false; + default: + if (!d->sqlResult->fetchNext()) { + d->sqlResult->setAt(QSql::AfterLastRow); + return false; + } + return true; + } +} + +/*! + + Retrieves the previous record in the result, if available, and + positions the query on the retrieved record. Note that the result + must be in the \l{isActive()}{active} state and isSelect() must + return true before calling this function or it will do nothing and + return false. + + The following rules apply: + + \list + + \o If the result is currently located before the first record, there + is no change and false is returned. + + \o If the result is currently located after the last record, an + attempt is made to retrieve the last record. + + \o If the result is somewhere in the middle, an attempt is made to + retrieve the previous record. + + \endlist + + If the record could not be retrieved, the result is positioned + before the first record and false is returned. If the record is + successfully retrieved, true is returned. + + \sa next() first() last() seek() at() isActive() isValid() +*/ +bool QSqlQuery::previous() +{ + if (!isSelect() || !isActive()) + return false; + if (isForwardOnly()) { + qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query"); + return false; + } + + bool b = false; + switch (at()) { + case QSql::BeforeFirstRow: + return false; + case QSql::AfterLastRow: + b = d->sqlResult->fetchLast(); + return b; + default: + if (!d->sqlResult->fetchPrevious()) { + d->sqlResult->setAt(QSql::BeforeFirstRow); + return false; + } + return true; + } +} + +/*! + Retrieves the first record in the result, if available, and + positions the query on the retrieved record. Note that the result + must be in the \l{isActive()}{active} state and isSelect() must + return true before calling this function or it will do nothing and + return false. Returns true if successful. If unsuccessful the query + position is set to an invalid position and false is returned. + + \sa next() previous() last() seek() at() isActive() isValid() + */ +bool QSqlQuery::first() +{ + if (!isSelect() || !isActive()) + return false; + if (isForwardOnly() && at() > QSql::BeforeFirstRow) { + qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query"); + return false; + } + bool b = false; + b = d->sqlResult->fetchFirst(); + return b; +} + +/*! + + Retrieves the last record in the result, if available, and positions + the query on the retrieved record. Note that the result must be in + the \l{isActive()}{active} state and isSelect() must return true + before calling this function or it will do nothing and return false. + Returns true if successful. If unsuccessful the query position is + set to an invalid position and false is returned. + + \sa next() previous() first() seek() at() isActive() isValid() +*/ + +bool QSqlQuery::last() +{ + if (!isSelect() || !isActive()) + return false; + bool b = false; + b = d->sqlResult->fetchLast(); + return b; +} + +/*! + Returns the size of the result (number of rows returned), or -1 if + the size cannot be determined or if the database does not support + reporting information about query sizes. Note that for non-\c SELECT + statements (isSelect() returns false), size() will return -1. If the + query is not active (isActive() returns false), -1 is returned. + + To determine the number of rows affected by a non-\c SELECT + statement, use numRowsAffected(). + + \sa isActive() numRowsAffected() QSqlDriver::hasFeature() +*/ +int QSqlQuery::size() const +{ + if (isActive() && d->sqlResult->driver()->hasFeature(QSqlDriver::QuerySize)) + return d->sqlResult->size(); + return -1; +} + +/*! + Returns the number of rows affected by the result's SQL statement, + or -1 if it cannot be determined. Note that for \c SELECT + statements, the value is undefined; use size() instead. If the query + is not \l{isActive()}{active}, -1 is returned. + + \sa size() QSqlDriver::hasFeature() +*/ + +int QSqlQuery::numRowsAffected() const +{ + if (isActive()) + return d->sqlResult->numRowsAffected(); + return -1; +} + +/*! + Returns error information about the last error (if any) that + occurred with this query. + + \sa QSqlError, QSqlDatabase::lastError() +*/ + +QSqlError QSqlQuery::lastError() const +{ + return d->sqlResult->lastError(); +} + +/*! + Returns true if the query is currently positioned on a valid + record; otherwise returns false. +*/ + +bool QSqlQuery::isValid() const +{ + return d->sqlResult->isValid(); +} + +/*! + + Returns true if the query is \e{active}. An active QSqlQuery is one + that has been \l{QSqlQuery::exec()} {exec()'d} successfully but not + yet finished with. When you are finished with an active query, you + can make make the query inactive by calling finish() or clear(), or + you can delete the QSqlQuery instance. + + \note Of particular interest is an active query that is a \c{SELECT} + statement. For some databases that support transactions, an active + query that is a \c{SELECT} statement can cause a \l{QSqlDatabase::} + {commit()} or a \l{QSqlDatabase::} {rollback()} to fail, so before + committing or rolling back, you should make your active \c{SELECT} + statement query inactive using one of the ways listed above. + + \sa isSelect() + */ +bool QSqlQuery::isActive() const +{ + return d->sqlResult->isActive(); +} + +/*! + Returns true if the current query is a \c SELECT statement; + otherwise returns false. +*/ + +bool QSqlQuery::isSelect() const +{ + return d->sqlResult->isSelect(); +} + +/*! + Returns true if you can only scroll forward through a result set; + otherwise returns false. + + \sa setForwardOnly(), next() +*/ +bool QSqlQuery::isForwardOnly() const +{ + return d->sqlResult->isForwardOnly(); +} + +/*! + Sets forward only mode to \a forward. If \a forward is true, only + next() and seek() with positive values, are allowed for navigating + the results. + + Forward only mode can be (depending on the driver) more memory + efficient since results do not need to be cached. It will also + improve performance on some databases. For this to be true, you must + call \c setForwardMode() before the query is prepared or executed. + Note that the constructor that takes a query and a database may + execute the query. + + Forward only mode is off by default. + + \sa isForwardOnly(), next(), seek() +*/ +void QSqlQuery::setForwardOnly(bool forward) +{ + d->sqlResult->setForwardOnly(forward); +} + +/*! + Returns a QSqlRecord containing the field information for the + current query. If the query points to a valid row (isValid() returns + true), the record is populated with the row's values. An empty + record is returned when there is no active query (isActive() returns + false). + + To retrieve values from a query, value() should be used since + its index-based lookup is faster. + + In the following example, a \c{SELECT * FROM} query is executed. + Since the order of the columns is not defined, QSqlRecord::indexOf() + is used to obtain the index of a column. + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 1 + + \sa value() +*/ +QSqlRecord QSqlQuery::record() const +{ + QSqlRecord rec = d->sqlResult->record(); + + if (isValid()) { + for (int i = 0; i < rec.count(); ++i) + rec.setValue(i, value(i)); + } + return rec; +} + +/*! + Clears the result set and releases any resources held by the + query. Sets the query state to inactive. You should rarely if ever + need to call this function. +*/ +void QSqlQuery::clear() +{ + *this = QSqlQuery(driver()->createResult()); +} + +/*! + Prepares the SQL query \a query for execution. Returns true if the + query is prepared successfully; otherwise returns false. + + The query may contain placeholders for binding values. Both Oracle + style colon-name (e.g., \c{:surname}), and ODBC style (\c{?}) + placeholders are supported; but they cannot be mixed in the same + query. See the \l{QSqlQuery examples}{Detailed Description} for + examples. + + Portability note: Some databases choose to delay preparing a query + until it is executed the first time. In this case, preparing a + syntactically wrong query succeeds, but every consecutive exec() + will fail. + + Example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 9 + + \sa exec(), bindValue(), addBindValue() +*/ +bool QSqlQuery::prepare(const QString& query) +{ + if (d->ref != 1) { + bool fo = isForwardOnly(); + *this = QSqlQuery(driver()->createResult()); + setForwardOnly(fo); + d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + } else { + d->sqlResult->setActive(false); + d->sqlResult->setLastError(QSqlError()); + d->sqlResult->setAt(QSql::BeforeFirstRow); + d->sqlResult->setNumericalPrecisionPolicy(d->precisionPolicy); + } + if (!driver()) { + qWarning("QSqlQuery::prepare: no driver"); + return false; + } + if (!driver()->isOpen() || driver()->isOpenError()) { + qWarning("QSqlQuery::prepare: database not open"); + return false; + } + if (query.isEmpty()) { + qWarning("QSqlQuery::prepare: empty query"); + return false; + } +#ifdef QT_DEBUG_SQL + qDebug("\n QSqlQuery::prepare: %s", query.toLocal8Bit().constData()); +#endif + return d->sqlResult->savePrepare(query); +} + +/*! + Executes a previously prepared SQL query. Returns true if the query + executed successfully; otherwise returns false. + + Note that the last error for this query is reset when exec() is + called. + + \sa prepare() bindValue() addBindValue() boundValue() boundValues() +*/ +bool QSqlQuery::exec() +{ + d->sqlResult->resetBindCount(); + + if (d->sqlResult->lastError().isValid()) + d->sqlResult->setLastError(QSqlError()); + + return d->sqlResult->exec(); +} + +/*! \enum QSqlQuery::BatchExecutionMode + + \value ValuesAsRows - Updates multiple rows. Treats every entry in a QVariantList as a value for updating the next row. + \value ValuesAsColumns - Updates a single row. Treats every entry in a QVariantList as a single value of an array type. +*/ + +/*! + \since 4.2 + + Executes a previously prepared SQL query in a batch. All the bound + parameters have to be lists of variants. If the database doesn't + support batch executions, the driver will simulate it using + conventional exec() calls. + + Returns true if the query is executed successfully; otherwise + returns false. + + Example: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 2 + + The example above inserts four new rows into \c myTable: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 3 + + To bind NULL values, a null QVariant of the relevant type has to be + added to the bound QVariantList; for example, \c + {QVariant(QVariant::String)} should be used if you are using + strings. + + \note Every bound QVariantList must contain the same amount of + variants. + + \note The type of the QVariants in a list must not change. For + example, you cannot mix integer and string variants within a + QVariantList. + + The \a mode parameter indicates how the bound QVariantList will be + interpreted. If \a mode is \c ValuesAsRows, every variant within + the QVariantList will be interpreted as a value for a new row. \c + ValuesAsColumns is a special case for the Oracle driver. In this + mode, every entry within a QVariantList will be interpreted as + array-value for an IN or OUT value within a stored procedure. Note + that this will only work if the IN or OUT value is a table-type + consisting of only one column of a basic type, for example \c{TYPE + myType IS TABLE OF VARCHAR(64) INDEX BY BINARY_INTEGER;} + + \sa prepare(), bindValue(), addBindValue() +*/ +bool QSqlQuery::execBatch(BatchExecutionMode mode) +{ + return d->sqlResult->execBatch(mode == ValuesAsColumns); +} + +/*! + Set the placeholder \a placeholder to be bound to value \a val in + the prepared statement. Note that the placeholder mark (e.g \c{:}) + must be included when specifying the placeholder name. If \a + paramType is QSql::Out or QSql::InOut, the placeholder will be + overwritten with data from the database after the exec() call. + + To bind a NULL value, use a null QVariant; for example, use + \c {QVariant(QVariant::String)} if you are binding a string. + + \sa addBindValue(), prepare(), exec(), boundValue() boundValues() +*/ +void QSqlQuery::bindValue(const QString& placeholder, const QVariant& val, + QSql::ParamType paramType +) +{ + d->sqlResult->bindValue(placeholder, val, paramType); +} + +/*! + Set the placeholder in position \a pos to be bound to value \a val + in the prepared statement. Field numbering starts at 0. If \a + paramType is QSql::Out or QSql::InOut, the placeholder will be + overwritten with data from the database after the exec() call. +*/ +void QSqlQuery::bindValue(int pos, const QVariant& val, QSql::ParamType paramType) +{ + d->sqlResult->bindValue(pos, val, paramType); +} + +/*! + Adds the value \a val to the list of values when using positional + value binding. The order of the addBindValue() calls determines + which placeholder a value will be bound to in the prepared query. + If \a paramType is QSql::Out or QSql::InOut, the placeholder will be + overwritten with data from the database after the exec() call. + + To bind a NULL value, use a null QVariant; for example, use \c + {QVariant(QVariant::String)} if you are binding a string. + + \sa bindValue(), prepare(), exec(), boundValue() boundValues() +*/ +void QSqlQuery::addBindValue(const QVariant& val, QSql::ParamType paramType) +{ + d->sqlResult->addBindValue(val, paramType); +} + +/*! + Returns the value for the \a placeholder. + + \sa boundValues() bindValue() addBindValue() +*/ +QVariant QSqlQuery::boundValue(const QString& placeholder) const +{ + return d->sqlResult->boundValue(placeholder); +} + +/*! + Returns the value for the placeholder at position \a pos. +*/ +QVariant QSqlQuery::boundValue(int pos) const +{ + return d->sqlResult->boundValue(pos); +} + +/*! + Returns a map of the bound values. + + With named binding, the bound values can be examined in the + following ways: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 14 + + With positional binding, the code becomes: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 15 + + \sa boundValue() bindValue() addBindValue() +*/ +QMap<QString,QVariant> QSqlQuery::boundValues() const +{ + QMap<QString,QVariant> map; + + const QVector<QVariant> values(d->sqlResult->boundValues()); + for (int i = 0; i < values.count(); ++i) + map[d->sqlResult->boundValueName(i)] = values.at(i); + return map; +} + +/*! + Returns the last query that was successfully executed. + + In most cases this function returns the same string as lastQuery(). + If a prepared query with placeholders is executed on a DBMS that + does not support it, the preparation of this query is emulated. The + placeholders in the original query are replaced with their bound + values to form a new query. This function returns the modified + query. It is mostly useful for debugging purposes. + + \sa lastQuery() +*/ +QString QSqlQuery::executedQuery() const +{ + return d->sqlResult->executedQuery(); +} + +/*! + \fn bool QSqlQuery::prev() + + Use previous() instead. +*/ + +/*! + Returns the object ID of the most recent inserted row if the + database supports it. An invalid QVariant will be returned if the + query did not insert any value or if the database does not report + the id back. If more than one row was touched by the insert, the + behavior is undefined. + + For MySQL databases the row's auto-increment field will be returned. + + \note For this function to work in PSQL, the table table must + contain OIDs, which may not have been created by default. Check the + \c default_with_oids configuration variable to be sure. + + \sa QSqlDriver::hasFeature() +*/ +QVariant QSqlQuery::lastInsertId() const +{ + return d->sqlResult->lastInsertId(); +} + +/*! + + Instruct the database driver to return numerical values with a + precision specified by \a precisionPolicy. + + The Oracle driver, for example, retrieves numerical values as + strings by default to prevent the loss of precision. If the high + precision doesn't matter, use this method to increase execution + speed by bypassing string conversions. + + Note: Drivers that don't support fetching numerical values with low + precision will ignore the precision policy. You can use + QSqlDriver::hasFeature() to find out whether a driver supports this + feature. + + Note: Setting the precision policy doesn't affect the currently + active query. Call \l{exec()}{exec(QString)} or prepare() in order + to activate the policy. + + \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy() +*/ +void QSqlQuery::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy) +{ + d->precisionPolicy = precisionPolicy; +} + +/*! + Returns the current precision policy. + + \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy() +*/ +QSql::NumericalPrecisionPolicy QSqlQuery::numericalPrecisionPolicy() const +{ + return d->precisionPolicy; +} + +/*! + \since 4.3.2 + + Instruct the database driver that no more data will be fetched from + this query until it is re-executed. There is normally no need to + call this function, but it may be helpful in order to free resources + such as locks or cursors if you intend to re-use the query at a + later time. + + Sets the query to inactive. Bound values retain their values. + + \sa prepare() exec() isActive() +*/ +void QSqlQuery::finish() +{ + if (isActive()) { + d->sqlResult->setLastError(QSqlError()); + d->sqlResult->setAt(QSql::BeforeFirstRow); + d->sqlResult->detachFromResultSet(); + d->sqlResult->setActive(false); + } +} + +/*! + \since 4.4 + + Discards the current result set and navigates to the next if available. + + Some databases are capable of returning multiple result sets for + stored procedures or SQL batches (a query strings that contains + multiple statements). If multiple result sets are available after + executing a query this function can be used to navigate to the next + result set(s). + + If a new result set is available this function will return true. + The query will be repositioned on an \e invalid record in the new + result set and must be navigated to a valid record before data + values can be retrieved. If a new result set isn't available the + function returns false and the the query is set to inactive. In any + case the old result set will be discarded. + + When one of the statements is a non-select statement a count of + affected rows may be available instead of a result set. + + Note that some databases, i.e. Microsoft SQL Server, requires + non-scrollable cursors when working with multiple result sets. Some + databases may execute all statements at once while others may delay + the execution until the result set is actually accessed, and some + databases may have restrictions on which statements are allowed to + be used in a SQL batch. + + \sa QSqlDriver::hasFeature() setForwardOnly() next() isSelect() numRowsAffected() isActive() lastError() +*/ +bool QSqlQuery::nextResult() +{ + if (isActive()) + return d->sqlResult->nextResult(); + return false; +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlquery.h b/src/sql/kernel/qsqlquery.h new file mode 100644 index 0000000..e042fbd --- /dev/null +++ b/src/sql/kernel/qsqlquery.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLQUERY_H +#define QSQLQUERY_H + +#include <QtSql/qsql.h> +#include <QtSql/qsqldatabase.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QVariant; +class QSqlDriver; +class QSqlError; +class QSqlResult; +class QSqlRecord; +template <class Key, class T> class QMap; +class QSqlQueryPrivate; + +class Q_SQL_EXPORT QSqlQuery +{ +public: + QSqlQuery(QSqlResult *r); + QSqlQuery(const QString& query = QString(), QSqlDatabase db = QSqlDatabase()); + explicit QSqlQuery(QSqlDatabase db); + QSqlQuery(const QSqlQuery& other); + QSqlQuery& operator=(const QSqlQuery& other); + ~QSqlQuery(); + + bool isValid() const; + bool isActive() const; + bool isNull(int field) const; + int at() const; + QString lastQuery() const; + int numRowsAffected() const; + QSqlError lastError() const; + bool isSelect() const; + int size() const; + const QSqlDriver* driver() const; + const QSqlResult* result() const; + bool isForwardOnly() const; + QSqlRecord record() const; + + void setForwardOnly(bool forward); + bool exec(const QString& query); + QVariant value(int i) const; + + void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy); + QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const; + + bool seek(int i, bool relative = false); + bool next(); + bool previous(); +#ifdef QT3_SUPPORT + inline QT3_SUPPORT bool prev() { return previous(); } +#endif + bool first(); + bool last(); + + void clear(); + + // prepared query support + bool exec(); + enum BatchExecutionMode { ValuesAsRows, ValuesAsColumns }; + bool execBatch(BatchExecutionMode mode = ValuesAsRows); + bool prepare(const QString& query); + void bindValue(const QString& placeholder, const QVariant& val, + QSql::ParamType type = QSql::In); + void bindValue(int pos, const QVariant& val, QSql::ParamType type = QSql::In); + void addBindValue(const QVariant& val, QSql::ParamType type = QSql::In); + QVariant boundValue(const QString& placeholder) const; + QVariant boundValue(int pos) const; + QMap<QString, QVariant> boundValues() const; + QString executedQuery() const; + QVariant lastInsertId() const; + void finish(); + bool nextResult(); + +private: + QSqlQueryPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLQUERY_H diff --git a/src/sql/kernel/qsqlrecord.cpp b/src/sql/kernel/qsqlrecord.cpp new file mode 100644 index 0000000..0162664 --- /dev/null +++ b/src/sql/kernel/qsqlrecord.cpp @@ -0,0 +1,605 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlrecord.h" + +#include "qdebug.h" +#include "qstringlist.h" +#include "qatomic.h" +#include "qsqlfield.h" +#include "qstring.h" +#include "qvector.h" + +QT_BEGIN_NAMESPACE + +class QSqlRecordPrivate +{ +public: + QSqlRecordPrivate(); + QSqlRecordPrivate(const QSqlRecordPrivate &other); + + inline bool contains(int index) { return index >= 0 && index < fields.count(); } + QString createField(int index, const QString &prefix) const; + + QVector<QSqlField> fields; + QAtomicInt ref; +}; + +QSqlRecordPrivate::QSqlRecordPrivate() +{ + ref = 1; +} + +QSqlRecordPrivate::QSqlRecordPrivate(const QSqlRecordPrivate &other): fields(other.fields) +{ + ref = 1; +} + +/*! \internal + Just for compat +*/ +QString QSqlRecordPrivate::createField(int index, const QString &prefix) const +{ + QString f; + if (!prefix.isEmpty()) + f = prefix + QLatin1Char('.'); + f += fields.at(index).name(); + return f; +} + +/*! + \class QSqlRecord + \brief The QSqlRecord class encapsulates a database record. + + \ingroup database + \ingroup shared + \inmodule QtSql + + The QSqlRecord class encapsulates the functionality and + characteristics of a database record (usually a row in a table or + view within the database). QSqlRecord supports adding and + removing fields as well as setting and retrieving field values. + + The values of a record's fields' can be set by name or position + with setValue(); if you want to set a field to null use + setNull(). To find the position of a field by name use indexOf(), + and to find the name of a field at a particular position use + fieldName(). Use field() to retrieve a QSqlField object for a + given field. Use contains() to see if the record contains a + particular field name. + + When queries are generated to be executed on the database only + those fields for which isGenerated() is true are included in the + generated SQL. + + A record can have fields added with append() or insert(), replaced + with replace(), and removed with remove(). All the fields can be + removed with clear(). The number of fields is given by count(); + all their values can be cleared (to null) using clearValues(). + + \sa QSqlField, QSqlQuery::record() +*/ + + +/*! + Constructs an empty record. + + \sa isEmpty(), append(), insert() +*/ + +QSqlRecord::QSqlRecord() +{ + d = new QSqlRecordPrivate(); +} + +/*! + Constructs a copy of \a other. + + QSqlRecord is \l{implicitly shared}. This means you can make copies + of a record in \l{constant time}. +*/ + +QSqlRecord::QSqlRecord(const QSqlRecord& other) +{ + d = other.d; + d->ref.ref(); +} + +/*! + Sets the record equal to \a other. + + QSqlRecord is \l{implicitly shared}. This means you can make copies + of a record in \l{constant time}. +*/ + +QSqlRecord& QSqlRecord::operator=(const QSqlRecord& other) +{ + qAtomicAssign(d, other.d); + return *this; +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlRecord::~QSqlRecord() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + \fn bool QSqlRecord::operator!=(const QSqlRecord &other) const + + Returns true if this object is not identical to \a other; + otherwise returns false. + + \sa operator==() +*/ + +/*! + Returns true if this object is identical to \a other (i.e., has + the same fields in the same order); otherwise returns false. + + \sa operator!=() +*/ +bool QSqlRecord::operator==(const QSqlRecord &other) const +{ + return d->fields == other.d->fields; +} + +/*! + Returns the value of the field located at position \a index in + the record. If \a index is out of bounds, an invalid QVariant + is returned. + + \sa fieldName() isNull() +*/ + +QVariant QSqlRecord::value(int index) const +{ + return d->fields.value(index).value(); +} + +/*! + \overload + + Returns the value of the field called \a name in the record. If + field \a name does not exist an invalid variant is returned. + + \sa indexOf() +*/ + +QVariant QSqlRecord::value(const QString& name) const +{ + return value(indexOf(name)); +} + +/*! + Returns the name of the field at position \a index. If the field + does not exist, an empty string is returned. + + \sa indexOf() +*/ + +QString QSqlRecord::fieldName(int index) const +{ + return d->fields.value(index).name(); +} + +/*! + Returns the position of the field called \a name within the + record, or -1 if it cannot be found. Field names are not + case-sensitive. If more than one field matches, the first one is + returned. + + \sa fieldName() +*/ + +int QSqlRecord::indexOf(const QString& name) const +{ + QString nm = name.toUpper(); + for (int i = 0; i < count(); ++i) { + if (d->fields.at(i).name().toUpper() == nm) // TODO: case-insensitive comparison + return i; + } + return -1; +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + Use field() instead +*/ +const QSqlField* QSqlRecord::fieldPtr(int index) const +{ + if (!d->contains(index)) + return 0; + + return &d->fields.at(index); +} + +/*! + \obsolete + Use field() instead +*/ + +const QSqlField* QSqlRecord::fieldPtr(const QString& name) const +{ + int i = indexOf(name); + if (!d->contains(i)) + return 0; + + return &d->fields.at(i); +} +#endif //QT3_SUPPORT + +/*! + Returns the field at position \a index. If the position is out of + range, an empty field is returned. + */ +QSqlField QSqlRecord::field(int index) const +{ + return d->fields.value(index); +} + +/*! \overload + Returns the field called \a name. + */ +QSqlField QSqlRecord::field(const QString &name) const +{ + return field(indexOf(name)); +} + + +/*! + Append a copy of field \a field to the end of the record. + + \sa insert() replace() remove() +*/ + +void QSqlRecord::append(const QSqlField& field) +{ + detach(); + d->fields.append(field); +} + +/*! + Inserts the field \a field at position \a pos in the record. + + \sa append() replace() remove() + */ +void QSqlRecord::insert(int pos, const QSqlField& field) +{ + detach(); + d->fields.insert(pos, field); +} + +/*! + Replaces the field at position \a pos with the given \a field. If + \a pos is out of range, nothing happens. + + \sa append() insert() remove() +*/ + +void QSqlRecord::replace(int pos, const QSqlField& field) +{ + if (!d->contains(pos)) + return; + + detach(); + d->fields[pos] = field; +} + +/*! + Removes the field at position \a pos. If \a pos is out of range, + nothing happens. + + \sa append() insert() replace() +*/ + +void QSqlRecord::remove(int pos) +{ + if (!d->contains(pos)) + return; + + detach(); + d->fields.remove(pos); +} + +/*! + Removes all the record's fields. + + \sa clearValues() isEmpty() +*/ + +void QSqlRecord::clear() +{ + detach(); + d->fields.clear(); +} + +/*! + Returns true if there are no fields in the record; otherwise + returns false. + + \sa append() insert() clear() +*/ + +bool QSqlRecord::isEmpty() const +{ + return d->fields.isEmpty(); +} + + +/*! + Returns true if there is a field in the record called \a name; + otherwise returns false. +*/ + +bool QSqlRecord::contains(const QString& name) const +{ + return indexOf(name) >= 0; +} + +/*! + Clears the value of all fields in the record and sets each field + to null. + + \sa setValue() +*/ + +void QSqlRecord::clearValues() +{ + detach(); + int count = d->fields.count(); + for (int i = 0; i < count; ++i) + d->fields[i].clear(); +} + +/*! + Sets the generated flag for the field called \a name to \a + generated. If the field does not exist, nothing happens. Only + fields that have \a generated set to true are included in the SQL + that is generated by QSqlQueryModel for example. + + \sa isGenerated() +*/ + +void QSqlRecord::setGenerated(const QString& name, bool generated) +{ + setGenerated(indexOf(name), generated); +} + +/*! + \overload + + Sets the generated flag for the field \a index to \a generated. + + \sa isGenerated() +*/ + +void QSqlRecord::setGenerated(int index, bool generated) +{ + if (!d->contains(index)) + return; + detach(); + d->fields[index].setGenerated(generated); +} + +/*! + \overload + + Returns true if the field \a index is null or if there is no field at + position \a index; otherwise returns false. +*/ +bool QSqlRecord::isNull(int index) const +{ + return d->fields.value(index).isNull(); +} + +/*! + Returns true if the field called \a name is null or if there is no + field called \a name; otherwise returns false. + + \sa setNull() +*/ +bool QSqlRecord::isNull(const QString& name) const +{ + return isNull(indexOf(name)); +} + +/*! + Sets the value of field \a index to null. If the field does not exist, + nothing happens. + + \sa setValue() +*/ +void QSqlRecord::setNull(int index) +{ + if (!d->contains(index)) + return; + detach(); + d->fields[index].clear(); +} + +/*! + \overload + + Sets the value of the field called \a name to null. If the field + does not exist, nothing happens. +*/ +void QSqlRecord::setNull(const QString& name) +{ + setNull(indexOf(name)); +} + + +/*! + Returns true if the record has a field called \a name and this + field is to be generated (the default); otherwise returns false. + + \sa setGenerated() +*/ +bool QSqlRecord::isGenerated(const QString& name) const +{ + return isGenerated(indexOf(name)); +} + +/*! \overload + + Returns true if the record has a field at position \a index and this + field is to be generated (the default); otherwise returns false. + + \sa setGenerated() +*/ +bool QSqlRecord::isGenerated(int index) const +{ + return d->fields.value(index).isGenerated(); +} + +#ifdef QT3_SUPPORT +/*! + Returns a list of all the record's field names as a string + separated by \a sep. + + In the unlikely event that you used this function in Qt 3, you + can simulate it using the rest of the QSqlRecord public API. +*/ + +QString QSqlRecord::toString(const QString& prefix, const QString& sep) const +{ + QString pflist; + bool comma = false; + for (int i = 0; i < count(); ++i) { + if (!d->fields.value(i).isGenerated()) { + if (comma) + pflist += sep + QLatin1Char(' '); + pflist += d->createField(i, prefix); + comma = true; + } + } + return pflist; +} + +/*! + Returns a list of all the record's field names, each having the + prefix \a prefix. + + In the unlikely event that you used this function in Qt 3, you + can simulate it using the rest of the QSqlRecord public API. +*/ + +QStringList QSqlRecord::toStringList(const QString& prefix) const +{ + QStringList s; + for (int i = 0; i < count(); ++i) { + if (!d->fields.value(i).isGenerated()) + s += d->createField(i, prefix); + } + return s; +} +#endif // QT3_SUPPORT + +/*! + Returns the number of fields in the record. + + \sa isEmpty() +*/ + +int QSqlRecord::count() const +{ + return d->fields.count(); +} + +/*! + Sets the value of the field at position \a index to \a val. If the + field does not exist, nothing happens. + + \sa setNull() +*/ + +void QSqlRecord::setValue(int index, const QVariant& val) +{ + if (!d->contains(index)) + return; + detach(); + d->fields[index].setValue(val); +} + + +/*! + \overload + + Sets the value of the field called \a name to \a val. If the field + does not exist, nothing happens. +*/ + +void QSqlRecord::setValue(const QString& name, const QVariant& val) +{ + setValue(indexOf(name), val); +} + + +/*! \internal +*/ +void QSqlRecord::detach() +{ + qAtomicDetach(d); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QSqlRecord &r) +{ + dbg << "QSqlRecord(" << r.count() << ")"; + for (int i = 0; i < r.count(); ++i) + dbg << '\n' << QString::fromLatin1("%1:").arg(i, 2) << r.field(i) << r.value(i).toString(); + return dbg; +} +#endif + +/*! + \fn int QSqlRecord::position(const QString& name) const + + Use indexOf() instead. +*/ + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlrecord.h b/src/sql/kernel/qsqlrecord.h new file mode 100644 index 0000000..239ecdd --- /dev/null +++ b/src/sql/kernel/qsqlrecord.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLRECORD_H +#define QSQLRECORD_H + +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlField; +class QStringList; +class QVariant; +class QSqlRecordPrivate; + +class Q_SQL_EXPORT QSqlRecord +{ +public: + QSqlRecord(); + QSqlRecord(const QSqlRecord& other); + QSqlRecord& operator=(const QSqlRecord& other); + ~QSqlRecord(); + + bool operator==(const QSqlRecord &other) const; + inline bool operator!=(const QSqlRecord &other) const { return !operator==(other); } + + QVariant value(int i) const; + QVariant value(const QString& name) const; + void setValue(int i, const QVariant& val); + void setValue(const QString& name, const QVariant& val); + + void setNull(int i); + void setNull(const QString& name); + bool isNull(int i) const; + bool isNull(const QString& name) const; + + int indexOf(const QString &name) const; + QString fieldName(int i) const; + + QSqlField field(int i) const; + QSqlField field(const QString &name) const; + + bool isGenerated(int i) const; + bool isGenerated(const QString& name) const; + void setGenerated(const QString& name, bool generated); + void setGenerated(int i, bool generated); + +#ifdef QT3_SUPPORT + QT3_SUPPORT const QSqlField* fieldPtr(int i) const; + QT3_SUPPORT const QSqlField* fieldPtr(const QString& name) const; + inline QT3_SUPPORT int position(const QString& name) const { return indexOf(name); } + QT3_SUPPORT QString toString(const QString& prefix = QString(), + const QString& sep = QLatin1String(",")) const; + QT3_SUPPORT QStringList toStringList(const QString& prefix = QString()) const; +#endif + + void append(const QSqlField& field); + void replace(int pos, const QSqlField& field); + void insert(int pos, const QSqlField& field); + void remove(int pos); + + bool isEmpty() const; + bool contains(const QString& name) const; + void clear(); + void clearValues(); + int count() const; + +private: + void detach(); + QSqlRecordPrivate* d; +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlRecord &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLRECORD_H diff --git a/src/sql/kernel/qsqlresult.cpp b/src/sql/kernel/qsqlresult.cpp new file mode 100644 index 0000000..cffc570 --- /dev/null +++ b/src/sql/kernel/qsqlresult.cpp @@ -0,0 +1,1015 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvariant.h" +#include "qhash.h" +#include "qregexp.h" +#include "qsqlerror.h" +#include "qsqlfield.h" +#include "qsqlrecord.h" +#include "qsqlresult.h" +#include "qvector.h" +#include "qsqldriver.h" + +QT_BEGIN_NAMESPACE + +struct QHolder { + QHolder(): holderName(QString()), holderPos(-1) {} + QHolder(const QString& hldr): holderName(hldr), holderPos(-1) {} + QHolder(const QString& hldr, int index): holderName(hldr), holderPos(index) {} + bool operator==(const QHolder& h) const { return h.holderPos == holderPos && h.holderName == holderName; } + bool operator!=(const QHolder& h) const { return h.holderPos != holderPos || h.holderName != holderName; } + QString holderName; + int holderPos; +}; + +class QSqlResultPrivate +{ +public: + QSqlResultPrivate(QSqlResult* d) + : q(d), sqldriver(0), idx(QSql::BeforeFirstRow), active(false), + isSel(false), forwardOnly(false), bindCount(0), binds(QSqlResult::PositionalBinding) + {} + + void clearValues() + { + values.clear(); + bindCount = 0; + } + + void resetBindCount() + { + bindCount = 0; + } + + void clearIndex() + { + indexes.clear(); + holders.clear(); + types.clear(); + } + + void clear() + { + clearValues(); + clearIndex();; + } + + QString positionalToNamedBinding(); + QString namedToPositionalBinding(); + QString holderAt(int index) const; + +public: + QSqlResult* q; + const QSqlDriver* sqldriver; + int idx; + QString sql; + bool active; + bool isSel; + QSqlError error; + bool forwardOnly; + + int bindCount; + QSqlResult::BindingSyntax binds; + + QString executedQuery; + QHash<int, QSql::ParamType> types; + QVector<QVariant> values; + typedef QHash<QString, int> IndexMap; + IndexMap indexes; + + typedef QVector<QHolder> QHolderVector; + QHolderVector holders; +}; + +QString QSqlResultPrivate::holderAt(int index) const +{ + return indexes.key(index); +} + +// return a unique id for bound names +static QString qFieldSerial(int i) +{ + ushort arr[] = { ':', 'f', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ushort *ptr = &arr[1]; + + while (i > 0) { + *(++ptr) = 'a' + i % 16; + i >>= 4; + } + + return QString::fromUtf16(arr, int(ptr - arr) + 1); +} + +static bool qIsAlnum(QChar ch) +{ + uint u = uint(ch.unicode()); + // matches [a-zA-Z0-9_] + return u - 'a' < 26 || u - 'A' < 26 || u - '0' < 10 || u == '_'; +} + +QString QSqlResultPrivate::positionalToNamedBinding() +{ + int n = sql.size(); + + QString result; + result.reserve(n * 5 / 4); + bool inQuote = false; + int count = 0; + + for (int i = 0; i < n; ++i) { + QChar ch = sql.at(i); + if (ch == QLatin1Char('?') && !inQuote) { + result += qFieldSerial(count++); + } else { + if (ch == QLatin1Char('\'')) + inQuote = !inQuote; + result += ch; + } + } + result.squeeze(); + return result; +} + +QString QSqlResultPrivate::namedToPositionalBinding() +{ + int n = sql.size(); + + QString result; + result.reserve(n); + bool inQuote = false; + int count = 0; + int i = 0; + + while (i < n) { + QChar ch = sql.at(i); + if (ch == QLatin1Char(':') && !inQuote + && (i == 0 || sql.at(i - 1) != QLatin1Char(':')) + && (i < n - 1 && qIsAlnum(sql.at(i + 1)))) { + int pos = i + 2; + while (pos < n && qIsAlnum(sql.at(pos))) + ++pos; + indexes[sql.mid(i, pos - i)] = count++; + result += QLatin1Char('?'); + i = pos; + } else { + if (ch == QLatin1Char('\'')) + inQuote = !inQuote; + result += ch; + ++i; + } + } + result.squeeze(); + return result; +} + +/*! + \class QSqlResult + \brief The QSqlResult class provides an abstract interface for + accessing data from specific SQL databases. + + \ingroup database + \inmodule QtSql + + Normally, you would use QSqlQuery instead of QSqlResult, since + QSqlQuery provides a generic wrapper for database-specific + implementations of QSqlResult. + + If you are implementing your own SQL driver (by subclassing + QSqlDriver), you will need to provide your own QSqlResult + subclass that implements all the pure virtual functions and other + virtual functions that you need. + + \sa QSqlDriver +*/ + +/*! + \enum QSqlResult::BindingSyntax + + This enum type specifies the different syntaxes for specifying + placeholders in prepared queries. + + \value PositionalBinding Use the ODBC-style positional syntax, with "?" as placeholders. + \value NamedBinding Use the Oracle-style syntax with named placeholders (e.g., ":id") + \omitvalue BindByPosition + \omitvalue BindByName + + \sa bindingSyntax() +*/ + +/*! + \enum QSqlResult::VirtualHookOperation + \internal +*/ + +/*! + Creates a QSqlResult using database driver \a db. The object is + initialized to an inactive state. + + \sa isActive(), driver() +*/ + +QSqlResult::QSqlResult(const QSqlDriver *db) +{ + d = new QSqlResultPrivate(this); + d->sqldriver = db; +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +QSqlResult::~QSqlResult() +{ + delete d; +} + +/*! + Sets the current query for the result to \a query. You must call + reset() to execute the query on the database. + + \sa reset(), lastQuery() +*/ + +void QSqlResult::setQuery(const QString& query) +{ + d->sql = query; +} + +/*! + Returns the current SQL query text, or an empty string if there + isn't one. + + \sa setQuery() +*/ + +QString QSqlResult::lastQuery() const +{ + return d->sql; +} + +/*! + Returns the current (zero-based) row position of the result. May + return the special values QSql::BeforeFirstRow or + QSql::AfterLastRow. + + \sa setAt(), isValid() +*/ +int QSqlResult::at() const +{ + return d->idx; +} + + +/*! + Returns true if the result is positioned on a valid record (that + is, the result is not positioned before the first or after the + last record); otherwise returns false. + + \sa at() +*/ + +bool QSqlResult::isValid() const +{ + return d->idx != QSql::BeforeFirstRow && d->idx != QSql::AfterLastRow; +} + +/*! + \fn bool QSqlResult::isNull(int index) + + Returns true if the field at position \a index in the current row + is null; otherwise returns false. +*/ + +/*! + Returns true if the result has records to be retrieved; otherwise + returns false. +*/ + +bool QSqlResult::isActive() const +{ + return d->active; +} + +/*! + This function is provided for derived classes to set the + internal (zero-based) row position to \a index. + + \sa at() +*/ + +void QSqlResult::setAt(int index) +{ + d->idx = index; +} + + +/*! + This function is provided for derived classes to indicate whether + or not the current statement is a SQL \c SELECT statement. The \a + select parameter should be true if the statement is a \c SELECT + statement; otherwise it should be false. + + \sa isSelect() +*/ + +void QSqlResult::setSelect(bool select) +{ + d->isSel = select; +} + +/*! + Returns true if the current result is from a \c SELECT statement; + otherwise returns false. + + \sa setSelect() +*/ + +bool QSqlResult::isSelect() const +{ + return d->isSel; +} + +/*! + Returns the driver associated with the result. This is the object + that was passed to the constructor. +*/ + +const QSqlDriver *QSqlResult::driver() const +{ + return d->sqldriver; +} + + +/*! + This function is provided for derived classes to set the internal + active state to \a active. + + \sa isActive() +*/ + +void QSqlResult::setActive(bool active) +{ + if (active && d->executedQuery.isEmpty()) + d->executedQuery = d->sql; + + d->active = active; +} + +/*! + This function is provided for derived classes to set the last + error to \a error. + + \sa lastError() +*/ + +void QSqlResult::setLastError(const QSqlError &error) +{ + d->error = error; +} + + +/*! + Returns the last error associated with the result. +*/ + +QSqlError QSqlResult::lastError() const +{ + return d->error; +} + +/*! + \fn int QSqlResult::size() + + Returns the size of the \c SELECT result, or -1 if it cannot be + determined or if the query is not a \c SELECT statement. + + \sa numRowsAffected() +*/ + +/*! + \fn int QSqlResult::numRowsAffected() + + Returns the number of rows affected by the last query executed, or + -1 if it cannot be determined or if the query is a \c SELECT + statement. + + \sa size() +*/ + +/*! + \fn QVariant QSqlResult::data(int index) + + Returns the data for field \a index in the current row as + a QVariant. This function is only called if the result is in + an active state and is positioned on a valid record and \a index is + non-negative. Derived classes must reimplement this function and + return the value of field \a index, or QVariant() if it cannot be + determined. +*/ + +/*! + \fn bool QSqlResult::reset(const QString &query) + + Sets the result to use the SQL statement \a query for subsequent + data retrieval. + + Derived classes must reimplement this function and apply the \a + query to the database. This function is only called after the + result is set to an inactive state and is positioned before the + first record of the new result. Derived classes should return + true if the query was successful and ready to be used, or false + otherwise. + + \sa setQuery() +*/ + +/*! + \fn bool QSqlResult::fetch(int index) + + Positions the result to an arbitrary (zero-based) row \a index. + + This function is only called if the result is in an active state. + Derived classes must reimplement this function and position the + result to the row \a index, and call setAt() with an appropriate + value. Return true to indicate success, or false to signify + failure. + + \sa isActive(), fetchFirst(), fetchLast(), fetchNext(), fetchPrevious() +*/ + +/*! + \fn bool QSqlResult::fetchFirst() + + Positions the result to the first record (row 0) in the result. + + This function is only called if the result is in an active state. + Derived classes must reimplement this function and position the + result to the first record, and call setAt() with an appropriate + value. Return true to indicate success, or false to signify + failure. + + \sa fetch(), fetchLast() +*/ + +/*! + \fn bool QSqlResult::fetchLast() + + Positions the result to the last record (last row) in the result. + + This function is only called if the result is in an active state. + Derived classes must reimplement this function and position the + result to the last record, and call setAt() with an appropriate + value. Return true to indicate success, or false to signify + failure. + + \sa fetch(), fetchFirst() +*/ + +/*! + Positions the result to the next available record (row) in the + result. + + This function is only called if the result is in an active + state. The default implementation calls fetch() with the next + index. Derived classes can reimplement this function and position + the result to the next record in some other way, and call setAt() + with an appropriate value. Return true to indicate success, or + false to signify failure. + + \sa fetch(), fetchPrevious() +*/ + +bool QSqlResult::fetchNext() +{ + return fetch(at() + 1); +} + +/*! + Positions the result to the previous record (row) in the result. + + This function is only called if the result is in an active state. + The default implementation calls fetch() with the previous index. + Derived classes can reimplement this function and position the + result to the next record in some other way, and call setAt() + with an appropriate value. Return true to indicate success, or + false to signify failure. +*/ + +bool QSqlResult::fetchPrevious() +{ + return fetch(at() - 1); +} + +/*! + Returns true if you can only scroll forward through the result + set; otherwise returns false. + + \sa setForwardOnly() +*/ +bool QSqlResult::isForwardOnly() const +{ + return d->forwardOnly; +} + +/*! + Sets forward only mode to \a forward. If \a forward is true, only + fetchNext() is allowed for navigating the results. Forward only + mode needs much less memory since results do not have to be + cached. By default, this feature is disabled. + + \sa isForwardOnly(), fetchNext() +*/ +void QSqlResult::setForwardOnly(bool forward) +{ + d->forwardOnly = forward; +} + +/*! + Prepares the given \a query, using the underlying database + functionality where possible. Returns true if the query is + prepared successfully; otherwise returns false. + + \sa prepare() +*/ +bool QSqlResult::savePrepare(const QString& query) +{ + if (!driver()) + return false; + d->clear(); + d->sql = query; + if (!driver()->hasFeature(QSqlDriver::PreparedQueries)) + return prepare(query); + + if (driver()->hasFeature(QSqlDriver::NamedPlaceholders)) { + // parse the query to memorize parameter location + d->namedToPositionalBinding(); + d->executedQuery = d->positionalToNamedBinding(); + } else { + d->executedQuery = d->namedToPositionalBinding(); + } + return prepare(d->executedQuery); +} + +/*! + Prepares the given \a query for execution; the query will normally + use placeholders so that it can be executed repeatedly. Returns + true if the query is prepared successfully; otherwise returns false. + + \sa exec() +*/ +bool QSqlResult::prepare(const QString& query) +{ + int n = query.size(); + + bool inQuote = false; + int i = 0; + + while (i < n) { + QChar ch = query.at(i); + if (ch == QLatin1Char(':') && !inQuote + && (i == 0 || query.at(i - 1) != QLatin1Char(':')) + && (i < n - 1 && qIsAlnum(query.at(i + 1)))) { + int pos = i + 2; + while (pos < n && qIsAlnum(query.at(pos))) + ++pos; + + d->holders.append(QHolder(query.mid(i, pos - i), i)); + i = pos; + } else { + if (ch == QLatin1Char('\'')) + inQuote = !inQuote; + ++i; + } + } + d->sql = query; + return true; // fake prepares should always succeed +} + +/*! + Executes the query, returning true if successful; otherwise returns + false. + + \sa prepare() +*/ +bool QSqlResult::exec() +{ + bool ret; + // fake preparation - just replace the placeholders.. + QString query = lastQuery(); + if (d->binds == NamedBinding) { + int i; + QVariant val; + QString holder; + for (i = d->holders.count() - 1; i >= 0; --i) { + holder = d->holders.at(i).holderName; + val = d->values.value(d->indexes.value(holder)); + QSqlField f(QLatin1String(""), val.type()); + f.setValue(val); + query = query.replace(d->holders.at(i).holderPos, + holder.length(), driver()->formatValue(f)); + } + } else { + QString val; + int i = 0; + int idx = 0; + for (idx = 0; idx < d->values.count(); ++idx) { + i = query.indexOf(QLatin1Char('?'), i); + if (i == -1) + continue; + QVariant var = d->values.value(idx); + QSqlField f(QLatin1String(""), var.type()); + if (var.isNull()) + f.clear(); + else + f.setValue(var); + val = driver()->formatValue(f); + query = query.replace(i, 1, driver()->formatValue(f)); + i += val.length(); + } + } + + // have to retain the original query with placeholders + QString orig = lastQuery(); + ret = reset(query); + d->executedQuery = query; + setQuery(orig); + d->resetBindCount(); + return ret; +} + +/*! + Binds the value \a val of parameter type \a paramType to position \a index + in the current record (row). + + \sa addBindValue() +*/ +void QSqlResult::bindValue(int index, const QVariant& val, QSql::ParamType paramType) +{ + d->binds = PositionalBinding; + d->indexes[qFieldSerial(index)] = index; + if (d->values.count() <= index) + d->values.resize(index + 1); + d->values[index] = val; + if (paramType != QSql::In || !d->types.isEmpty()) + d->types[index] = paramType; +} + +/*! + \overload + + Binds the value \a val of parameter type \a paramType to the \a + placeholder name in the current record (row). + + Note that binding an undefined placeholder will result in undefined behavior. +*/ +void QSqlResult::bindValue(const QString& placeholder, const QVariant& val, + QSql::ParamType paramType) +{ + d->binds = NamedBinding; + // if the index has already been set when doing emulated named + // bindings - don't reset it + int idx = d->indexes.value(placeholder, -1); + if (idx >= 0) { + if (d->values.count() <= idx) + d->values.resize(idx + 1); + d->values[idx] = val; + } else { + d->values.append(val); + idx = d->values.count() - 1; + d->indexes[placeholder] = idx; + } + + if (paramType != QSql::In || !d->types.isEmpty()) + d->types[idx] = paramType; +} + +/*! + Binds the value \a val of parameter type \a paramType to the next + available position in the current record (row). + + \sa bindValue() +*/ +void QSqlResult::addBindValue(const QVariant& val, QSql::ParamType paramType) +{ + d->binds = PositionalBinding; + bindValue(d->bindCount, val, paramType); + ++d->bindCount; +} + +/*! + Returns the value bound at position \a index in the current record + (row). + + \sa bindValue(), boundValues() +*/ +QVariant QSqlResult::boundValue(int index) const +{ + return d->values.value(index); +} + +/*! + \overload + + Returns the value bound by the given \a placeholder name in the + current record (row). + + \sa bindValueType() +*/ +QVariant QSqlResult::boundValue(const QString& placeholder) const +{ + int idx = d->indexes.value(placeholder, -1); + return d->values.value(idx); +} + +/*! + Returns the parameter type for the value bound at position \a index. + + \sa boundValue() +*/ +QSql::ParamType QSqlResult::bindValueType(int index) const +{ + return d->types.value(index, QSql::In); +} + +/*! + \overload + + Returns the parameter type for the value bound with the given \a + placeholder name. +*/ +QSql::ParamType QSqlResult::bindValueType(const QString& placeholder) const +{ + return d->types.value(d->indexes.value(placeholder, -1), QSql::In); +} + +/*! + Returns the number of bound values in the result. + + \sa boundValues() +*/ +int QSqlResult::boundValueCount() const +{ + return d->values.count(); +} + +/*! + Returns a vector of the result's bound values for the current + record (row). + + \sa boundValueCount() +*/ +QVector<QVariant>& QSqlResult::boundValues() const +{ + return d->values; +} + +/*! + Returns the binding syntax used by prepared queries. +*/ +QSqlResult::BindingSyntax QSqlResult::bindingSyntax() const +{ + return d->binds; +} + +/*! + Clears the entire result set and releases any associated + resources. +*/ +void QSqlResult::clear() +{ + d->clear(); +} + +/*! + Returns the query that was actually executed. This may differ from + the query that was passed, for example if bound values were used + with a prepared query and the underlying database doesn't support + prepared queries. + + \sa exec(), setQuery() +*/ +QString QSqlResult::executedQuery() const +{ + return d->executedQuery; +} + +void QSqlResult::resetBindCount() +{ + d->resetBindCount(); +} + +/*! + Returns the name of the bound value at position \a index in the + current record (row). + + \sa boundValue() +*/ +QString QSqlResult::boundValueName(int index) const +{ + return d->holderAt(index); +} + +/*! + Returns true if at least one of the query's bound values is a \c + QSql::Out or a QSql::InOut; otherwise returns false. + + \sa bindValueType() +*/ +bool QSqlResult::hasOutValues() const +{ + if (d->types.isEmpty()) + return false; + QHash<int, QSql::ParamType>::ConstIterator it; + for (it = d->types.constBegin(); it != d->types.constEnd(); ++it) { + if (it.value() != QSql::In) + return true; + } + return false; +} + +/*! + Returns the current record if the query is active; otherwise + returns an empty QSqlRecord. + + The default implementation always returns an empty QSqlRecord. + + \sa isActive() +*/ +QSqlRecord QSqlResult::record() const +{ + return QSqlRecord(); +} + +/*! + Returns the object ID of the most recent inserted row if the + database supports it. + An invalid QVariant will be returned if the query did not + insert any value or if the database does not report the id back. + If more than one row was touched by the insert, the behavior is + undefined. + + Note that for Oracle databases the row's ROWID will be returned, + while for MySQL databases the row's auto-increment field will + be returned. + + \sa QSqlDriver::hasFeature() +*/ +QVariant QSqlResult::lastInsertId() const +{ + return QVariant(); +} + +/*! \internal +*/ +void QSqlResult::virtual_hook(int, void *) +{ + Q_ASSERT(false); +} + +/*! \internal + \since 4.2 + + Executes a prepared query in batch mode if the driver supports it, + otherwise emulates a batch execution using bindValue() and exec(). + QSqlDriver::hasFeature() can be used to find out whether a driver + supports batch execution. + + Batch execution can be faster for large amounts of data since it + reduces network roundtrips. + + For batch executions, bound values have to be provided as lists + of variants (QVariantList). + + Each list must contain values of the same type. All lists must + contain equal amount of values (rows). + + NULL values are passed in as typed QVariants, for example + \c {QVariant(QVariant::Int)} for an integer NULL value. + + Example: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 0 + + Here, we insert two rows into a SQL table, with each row containing three values. + + \sa exec(), QSqlDriver::hasFeature() +*/ +bool QSqlResult::execBatch(bool arrayBind) +{ + if (driver()->hasFeature(QSqlDriver::BatchOperations)) { + virtual_hook(BatchOperation, &arrayBind); + d->resetBindCount(); + return d->error.type() == QSqlError::NoError; + } else { + QVector<QVariant> values = d->values; + if (values.count() == 0) + return false; + for (int i = 0; i < values.at(0).toList().count(); ++i) { + for (int j = 0; j < values.count(); ++j) + bindValue(j, values.at(j).toList().at(i), QSql::In); + if (!exec()) + return false; + } + return true; + } + return false; +} + +/*! \internal + */ +void QSqlResult::detachFromResultSet() +{ + if (driver()->hasFeature(QSqlDriver::FinishQuery) + || driver()->hasFeature(QSqlDriver::SimpleLocking)) + virtual_hook(DetachFromResultSet, 0); +} + +/*! \internal + */ +void QSqlResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy) +{ + if (driver()->hasFeature(QSqlDriver::LowPrecisionNumbers)) + virtual_hook(SetNumericalPrecision, &policy); +} + +/*! \internal +*/ +bool QSqlResult::nextResult() +{ + if (driver()->hasFeature(QSqlDriver::MultipleResultSets)) { + bool result = false; + virtual_hook(NextResult, &result); + return result; + } + return false; +} + +/*! + Returns the low-level database handle for this result set + wrapped in a QVariant or an invalid QVariant if there is no handle. + + \warning Use this with uttermost care and only if you know what you're doing. + + \warning The handle returned here can become a stale pointer if the result + is modified (for example, if you clear it). + + \warning The handle can be NULL if the result was not executed yet. + + The handle returned here is database-dependent, you should query the type + name of the variant before accessing it. + + This example retrieves the handle for a sqlite result: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 1 + + This snippet returns the handle for PostgreSQL or MySQL: + + \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 2 + + \sa QSqlDriver::handle() +*/ +QVariant QSqlResult::handle() const +{ + return QVariant(); +} + +QT_END_NAMESPACE diff --git a/src/sql/kernel/qsqlresult.h b/src/sql/kernel/qsqlresult.h new file mode 100644 index 0000000..0a3d8b9 --- /dev/null +++ b/src/sql/kernel/qsqlresult.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLRESULT_H +#define QSQLRESULT_H + +#include <QtCore/qvariant.h> +#include <QtCore/qvector.h> +#include <QtSql/qsql.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QString; +class QSqlRecord; +template <typename T> class QVector; +class QVariant; +class QSqlDriver; +class QSqlError; +class QSqlResultPrivate; + +class Q_SQL_EXPORT QSqlResult +{ + friend class QSqlQuery; + friend class QSqlTableModelPrivate; + friend class QSqlResultPrivate; + +public: + virtual ~QSqlResult(); + virtual QVariant handle() const; + +protected: + enum BindingSyntax { + PositionalBinding, + NamedBinding +#ifdef QT3_SUPPORT + , BindByPosition = PositionalBinding, + BindByName = NamedBinding +#endif + }; + + explicit QSqlResult(const QSqlDriver * db); + int at() const; + QString lastQuery() const; + QSqlError lastError() const; + bool isValid() const; + bool isActive() const; + bool isSelect() const; + bool isForwardOnly() const; + const QSqlDriver* driver() const; + virtual void setAt(int at); + virtual void setActive(bool a); + virtual void setLastError(const QSqlError& e); + virtual void setQuery(const QString& query); + virtual void setSelect(bool s); + virtual void setForwardOnly(bool forward); + + // prepared query support + virtual bool exec(); + virtual bool prepare(const QString& query); + virtual bool savePrepare(const QString& sqlquery); + virtual void bindValue(int pos, const QVariant& val, QSql::ParamType type); + virtual void bindValue(const QString& placeholder, const QVariant& val, + QSql::ParamType type); + void addBindValue(const QVariant& val, QSql::ParamType type); + QVariant boundValue(const QString& placeholder) const; + QVariant boundValue(int pos) const; + QSql::ParamType bindValueType(const QString& placeholder) const; + QSql::ParamType bindValueType(int pos) const; + int boundValueCount() const; + QVector<QVariant>& boundValues() const; + QString executedQuery() const; + QString boundValueName(int pos) const; + void clear(); + bool hasOutValues() const; + + BindingSyntax bindingSyntax() const; + + virtual QVariant data(int i) = 0; + virtual bool isNull(int i) = 0; + virtual bool reset(const QString& sqlquery) = 0; + virtual bool fetch(int i) = 0; + virtual bool fetchNext(); + virtual bool fetchPrevious(); + virtual bool fetchFirst() = 0; + virtual bool fetchLast() = 0; + virtual int size() = 0; + virtual int numRowsAffected() = 0; + virtual QSqlRecord record() const; + virtual QVariant lastInsertId() const; + + enum VirtualHookOperation { BatchOperation, DetachFromResultSet, SetNumericalPrecision, NextResult }; + virtual void virtual_hook(int id, void *data); + bool execBatch(bool arrayBind = false); + void detachFromResultSet(); + void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy); + bool nextResult(); + +private: + QSqlResultPrivate* d; + void resetBindCount(); // HACK + +private: + Q_DISABLE_COPY(QSqlResult) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLRESULT_H diff --git a/src/sql/models/models.pri b/src/sql/models/models.pri new file mode 100644 index 0000000..972cf2f --- /dev/null +++ b/src/sql/models/models.pri @@ -0,0 +1,12 @@ +HEADERS += models/qsqlquerymodel.h \ + models/qsqlquerymodel_p.h \ + models/qsqltablemodel.h \ + models/qsqltablemodel_p.h \ + models/qsqlrelationaldelegate.h \ + models/qsqlrelationaltablemodel.h + +SOURCES += models/qsqlquerymodel.cpp \ + models/qsqltablemodel.cpp \ + models/qsqlrelationaldelegate.cpp \ + models/qsqlrelationaltablemodel.cpp + diff --git a/src/sql/models/qsqlquerymodel.cpp b/src/sql/models/qsqlquerymodel.cpp new file mode 100644 index 0000000..973d715 --- /dev/null +++ b/src/sql/models/qsqlquerymodel.cpp @@ -0,0 +1,592 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlquerymodel.h" + +#include <qdebug.h> +#include <qsqldriver.h> +#include <qsqlfield.h> + +#include "qsqlquerymodel_p.h" + +QT_BEGIN_NAMESPACE + +#define QSQL_PREFETCH 255 + +void QSqlQueryModelPrivate::prefetch(int limit) +{ + Q_Q(QSqlQueryModel); + + if (atEnd || limit <= bottom.row() || bottom.column() == -1) + return; + + QModelIndex newBottom; + const int oldBottomRow = qMax(bottom.row(), 0); + + // try to seek directly + if (query.seek(limit)) { + newBottom = q->createIndex(limit, bottom.column()); + } else { + // have to seek back to our old position for MS Access + int i = oldBottomRow; + if (query.seek(i)) { + while (query.next()) + ++i; + newBottom = q->createIndex(i, bottom.column()); + } else { + // empty or invalid query + newBottom = q->createIndex(-1, bottom.column()); + } + atEnd = true; // this is the end. + } + if (newBottom.row() >= 0 && newBottom.row() > bottom.row()) { + q->beginInsertRows(QModelIndex(), bottom.row() + 1, newBottom.row()); + bottom = newBottom; + q->endInsertRows(); + } else { + bottom = newBottom; + } +} + +QSqlQueryModelPrivate::~QSqlQueryModelPrivate() +{ +} + +void QSqlQueryModelPrivate::initColOffsets(int size) +{ + colOffsets.resize(size); + memset(colOffsets.data(), 0, colOffsets.size() * sizeof(int)); +} + +/*! + \class QSqlQueryModel + \brief The QSqlQueryModel class provides a read-only data model for SQL + result sets. + + \ingroup database + \inmodule QtSql + + QSqlQueryModel is a high-level interface for executing SQL + statements and traversing the result set. It is built on top of + the lower-level QSqlQuery and can be used to provide data to + view classes such as QTableView. For example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 16 + + We set the model's query, then we set up the labels displayed in + the view header. + + QSqlQueryModel can also be used to access a database + programmatically, without binding it to a view: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 21 + + The code snippet above extracts the \c salary field from record 4 in + the result set of the query \c{SELECT * from employee}. Assuming + that \c salary is column 2, we can rewrite the last line as follows: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 22 + + The model is read-only by default. To make it read-write, you + must subclass it and reimplement setData() and flags(). Another + option is to use QSqlTableModel, which provides a read-write + model based on a single database table. + + The \l{sql/querymodel} example illustrates how to use + QSqlQueryModel to display the result of a query. It also shows + how to subclass QSqlQueryModel to customize the contents of the + data before showing it to the user, and how to create a + read-write model based on QSqlQueryModel. + + If the database doesn't return the amount of selected rows in + a query, the model will fetch rows incrementally. + See fetchMore() for more information. + + \sa QSqlTableModel, QSqlRelationalTableModel, QSqlQuery, + {Model/View Programming}, {Query Model Example} +*/ + +/*! + Creates an empty QSqlQueryModel with the given \a parent. + */ +QSqlQueryModel::QSqlQueryModel(QObject *parent) + : QAbstractTableModel(*new QSqlQueryModelPrivate, parent) +{ +} + +/*! \internal + */ +QSqlQueryModel::QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent) + : QAbstractTableModel(dd, parent) +{ +} + +/*! + Destroys the object and frees any allocated resources. + + \sa clear() +*/ +QSqlQueryModel::~QSqlQueryModel() +{ +} + +/*! + \since 4.1 + + Fetches more rows from a database. + This only affects databases that don't report back the size of a query + (see QSqlDriver::hasFeature()). + + To force fetching of the entire database, you can use the following: + + \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 0 + + \a parent should always be an invalid QModelIndex. + + \sa canFetchMore() +*/ +void QSqlQueryModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QSqlQueryModel); + if (parent.isValid()) + return; + d->prefetch(qMax(d->bottom.row(), 0) + QSQL_PREFETCH); +} + +/*! + \since 4.1 + + Returns true if it is possible to read more rows from the database. + This only affects databases that don't report back the size of a query + (see QSqlDriver::hasFeature()). + + \a parent should always be an invalid QModelIndex. + + \sa fetchMore() + */ +bool QSqlQueryModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QSqlQueryModel); + return (!parent.isValid() && !d->atEnd); +} + +/*! \fn int QSqlQueryModel::rowCount(const QModelIndex &parent) const + \since 4.1 + + If the database supports returning the size of a query + (see QSqlDriver::hasFeature()), the amount of rows of the current + query is returned. Otherwise, returns the amount of rows + currently cached on the client. + + \a parent should always be an invalid QModelIndex. + + \sa canFetchMore(), QSqlDriver::hasFeature() + */ +int QSqlQueryModel::rowCount(const QModelIndex &index) const +{ + Q_D(const QSqlQueryModel); + return index.isValid() ? 0 : d->bottom.row() + 1; +} + +/*! \reimp + */ +int QSqlQueryModel::columnCount(const QModelIndex &index) const +{ + Q_D(const QSqlQueryModel); + return index.isValid() ? 0 : d->rec.count(); +} + +/*! + Returns the value for the specified \a item and \a role. + + If \a item is out of bounds or if an error occurred, an invalid + QVariant is returned. + + \sa lastError() +*/ +QVariant QSqlQueryModel::data(const QModelIndex &item, int role) const +{ + Q_D(const QSqlQueryModel); + if (!item.isValid()) + return QVariant(); + + QVariant v; + if (role & ~(Qt::DisplayRole | Qt::EditRole)) + return v; + + if (!d->rec.isGenerated(item.column())) + return v; + QModelIndex dItem = indexInQuery(item); + if (dItem.row() > d->bottom.row()) + const_cast<QSqlQueryModelPrivate *>(d)->prefetch(dItem.row()); + + if (!d->query.seek(dItem.row())) { + d->error = d->query.lastError(); + return v; + } + + return d->query.value(dItem.column()); +} + +/*! + Returns the header data for the given \a role in the \a section + of the header with the specified \a orientation. +*/ +QVariant QSqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSqlQueryModel); + if (orientation == Qt::Horizontal) { + QVariant val = d->headers.value(section).value(role); + if (role == Qt::DisplayRole && !val.isValid()) + val = d->headers.value(section).value(Qt::EditRole); + if (val.isValid()) + return val; + if (role == Qt::DisplayRole && d->rec.count() > section) + return d->rec.fieldName(section); + } + return QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + This virtual function is called whenever the query changes. The + default implementation does nothing. + + query() returns the new query. + + \sa query(), setQuery() + */ +void QSqlQueryModel::queryChange() +{ + // do nothing +} + +/*! + Resets the model and sets the data provider to be the given \a + query. Note that the query must be active and must not be + isForwardOnly(). + + lastError() can be used to retrieve verbose information if there + was an error setting the query. + + \sa query(), QSqlQuery::isActive(), QSqlQuery::setForwardOnly(), lastError() +*/ +void QSqlQueryModel::setQuery(const QSqlQuery &query) +{ + Q_D(QSqlQueryModel); + QSqlRecord newRec = query.record(); + bool columnsChanged = (newRec != d->rec); + bool hasQuerySize = query.driver()->hasFeature(QSqlDriver::QuerySize); + + if (d->colOffsets.size() != newRec.count() || columnsChanged) + d->initColOffsets(newRec.count()); + + bool mustClearModel = d->bottom.isValid(); + if (mustClearModel) { + d->atEnd = true; + beginRemoveRows(QModelIndex(), 0, qMax(d->bottom.row(), 0)); + d->bottom = QModelIndex(); + } + + d->error = QSqlError(); + d->query = query; + d->rec = newRec; + + if (mustClearModel) + endRemoveRows(); + + d->atEnd = false; + + if (columnsChanged) + reset(); + + if (!query.isActive() || query.isForwardOnly()) { + d->atEnd = true; + d->bottom = QModelIndex(); + if (query.isForwardOnly()) + d->error = QSqlError(QLatin1String("Forward-only queries " + "cannot be used in a data model"), + QString(), QSqlError::ConnectionError); + else + d->error = query.lastError(); + return; + } + QModelIndex newBottom; + if (hasQuerySize && d->query.size() > 0) { + newBottom = createIndex(d->query.size() - 1, d->rec.count() - 1); + beginInsertRows(QModelIndex(), 0, qMax(0, newBottom.row())); + d->bottom = createIndex(d->query.size() - 1, columnsChanged ? 0 : d->rec.count() - 1); + d->atEnd = true; + endInsertRows(); + } else { + newBottom = createIndex(-1, d->rec.count() - 1); + } + d->bottom = newBottom; + + queryChange(); + + // fetchMore does the rowsInserted stuff for incremental models + fetchMore(); +} + +/*! \overload + + Executes the query \a query for the given database connection \a + db. If no database is specified, the default connection is used. + + lastError() can be used to retrieve verbose information if there + was an error setting the query. + + Example: + \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 1 + + \sa query(), queryChange(), lastError() +*/ +void QSqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db) +{ + setQuery(QSqlQuery(query, db)); +} + +/*! + Clears the model and releases any acquired resource. +*/ +void QSqlQueryModel::clear() +{ + Q_D(QSqlQueryModel); + d->error = QSqlError(); + d->atEnd = true; + d->query.clear(); + d->rec.clear(); + d->colOffsets.clear(); + d->bottom = QModelIndex(); + d->headers.clear(); +} + +/*! + Sets the caption for a horizontal header for the specified \a role to + \a value. This is useful if the model is used to + display data in a view (e.g., QTableView). + + Returns true if \a orientation is Qt::Horizontal and + the \a section refers to a valid section; otherwise returns + false. + + Note that this function cannot be used to modify values in the + database since the model is read-only. + + \sa data() + */ +bool QSqlQueryModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(QSqlQueryModel); + if (orientation != Qt::Horizontal || section < 0) + return false; + + if (d->headers.size() <= section) + d->headers.resize(qMax(section + 1, 16)); + d->headers[section][role] = value; + emit headerDataChanged(orientation, section, section); + return true; +} + +/*! + Returns the QSqlQuery associated with this model. + + \sa setQuery() +*/ +QSqlQuery QSqlQueryModel::query() const +{ + Q_D(const QSqlQueryModel); + return d->query; +} + +/*! + Returns information about the last error that occurred on the + database. + + \sa query() +*/ +QSqlError QSqlQueryModel::lastError() const +{ + Q_D(const QSqlQueryModel); + return d->error; +} + +/*! + Protected function which allows derived classes to set the value of + the last error that occurred on the database to \a error. + + \sa lastError() +*/ +void QSqlQueryModel::setLastError(const QSqlError &error) +{ + Q_D(QSqlQueryModel); + d->error = error; +} + +/*! + Returns the record containing information about the fields of the + current query. If \a row is the index of a valid row, the record + will be populated with values from that row. + + If the model is not initialized, an empty record will be + returned. + + \sa QSqlRecord::isEmpty() +*/ +QSqlRecord QSqlQueryModel::record(int row) const +{ + Q_D(const QSqlQueryModel); + if (row < 0) + return d->rec; + + QSqlRecord rec = d->rec; + for (int i = 0; i < rec.count(); ++i) + rec.setValue(i, data(createIndex(row, i), Qt::EditRole)); + return rec; +} + +/*! \overload + + Returns an empty record containing information about the fields + of the current query. + + If the model is not initialized, an empty record will be + returned. + + \sa QSqlRecord::isEmpty() + */ +QSqlRecord QSqlQueryModel::record() const +{ + Q_D(const QSqlQueryModel); + return d->rec; +} + +/*! + Inserts \a count columns into the model at position \a column. The + \a parent parameter must always be an invalid QModelIndex, since + the model does not support parent-child relationships. + + Returns true if \a column is within bounds; otherwise returns false. + + By default, inserted columns are empty. To fill them with data, + reimplement data() and handle any inserted column separately: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 23 + + \sa removeColumns() +*/ +bool QSqlQueryModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSqlQueryModel); + if (count <= 0 || parent.isValid() || column < 0 || column > d->rec.count()) + return false; + + beginInsertColumns(parent, column, column + count - 1); + for (int c = 0; c < count; ++c) { + QSqlField field; + field.setReadOnly(true); + field.setGenerated(false); + d->rec.insert(column, field); + if (d->colOffsets.size() < d->rec.count()) { + int nVal = d->colOffsets.isEmpty() ? 0 : d->colOffsets[d->colOffsets.size() - 1]; + d->colOffsets.append(nVal); + Q_ASSERT(d->colOffsets.size() >= d->rec.count()); + } + for (int i = column + 1; i < d->colOffsets.count(); ++i) + ++d->colOffsets[i]; + } + endInsertColumns(); + return true; +} + +/*! + Removes \a count columns from the model starting from position \a + column. The \a parent parameter must always be an invalid + QModelIndex, since the model does not support parent-child + relationships. + + Removing columns effectively hides them. It does not affect the + underlying QSqlQuery. + + Returns true if the columns were removed; otherwise returns false. + */ +bool QSqlQueryModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSqlQueryModel); + if (count <= 0 || parent.isValid() || column < 0 || column >= d->rec.count()) + return false; + + beginRemoveColumns(parent, column, column + count - 1); + + int i; + for (i = 0; i < count; ++i) + d->rec.remove(column); + for (i = column; i < d->colOffsets.count(); ++i) + d->colOffsets[i] -= count; + + endRemoveColumns(); + return true; +} + +/*! + Returns the index of the value in the database result set for the + given \a item in the model. + + The return value is identical to \a item if no columns or rows + have been inserted, removed, or moved around. + + Returns an invalid model index if \a item is out of bounds or if + \a item does not point to a value in the result set. + + \sa QSqlTableModel::indexInQuery(), insertColumns(), removeColumns() +*/ +QModelIndex QSqlQueryModel::indexInQuery(const QModelIndex &item) const +{ + Q_D(const QSqlQueryModel); + if (item.column() < 0 || item.column() >= d->rec.count() + || !d->rec.isGenerated(item.column())) + return QModelIndex(); + return createIndex(item.row(), item.column() - d->colOffsets[item.column()], + item.internalPointer()); +} + +QT_END_NAMESPACE diff --git a/src/sql/models/qsqlquerymodel.h b/src/sql/models/qsqlquerymodel.h new file mode 100644 index 0000000..14e01c0 --- /dev/null +++ b/src/sql/models/qsqlquerymodel.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLQUERYMODEL_H +#define QSQLQUERYMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtSql/qsqldatabase.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlQueryModelPrivate; +class QSqlError; +class QSqlRecord; +class QSqlQuery; + +class Q_SQL_EXPORT QSqlQueryModel: public QAbstractTableModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSqlQueryModel) + +public: + explicit QSqlQueryModel(QObject *parent = 0); + virtual ~QSqlQueryModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QSqlRecord record(int row) const; + QSqlRecord record() const; + + QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void setQuery(const QSqlQuery &query); + void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase()); + QSqlQuery query() const; + + virtual void clear(); + + QSqlError lastError() const; + + void fetchMore(const QModelIndex &parent = QModelIndex()); + bool canFetchMore(const QModelIndex &parent = QModelIndex()) const; + +protected: + virtual void queryChange(); + + QModelIndex indexInQuery(const QModelIndex &item) const; + void setLastError(const QSqlError &error); + QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent = 0); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLQUERYMODEL_H diff --git a/src/sql/models/qsqlquerymodel_p.h b/src/sql/models/qsqlquerymodel_p.h new file mode 100644 index 0000000..ef331d7 --- /dev/null +++ b/src/sql/models/qsqlquerymodel_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLQUERYMODEL_P_H +#define QSQLQUERYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qsql*model.h . This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemmodel_p.h" +#include "QtSql/qsqlerror.h" +#include "QtSql/qsqlquery.h" +#include "QtSql/qsqlrecord.h" +#include "QtCore/qhash.h" +#include "QtCore/qvarlengtharray.h" +#include "QtCore/qvector.h" + +QT_BEGIN_NAMESPACE + +class QSqlQueryModelPrivate: public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QSqlQueryModel) +public: + QSqlQueryModelPrivate() : atEnd(false) {} + ~QSqlQueryModelPrivate(); + + void prefetch(int); + void initColOffsets(int size); + + mutable QSqlQuery query; + mutable QSqlError error; + QModelIndex bottom; + QSqlRecord rec; + uint atEnd : 1; + QVector<QHash<int, QVariant> > headers; + QVarLengthArray<int, 56> colOffsets; // used to calculate indexInQuery of columns +}; + +QT_END_NAMESPACE + +#endif // QSQLQUERYMODEL_P_H diff --git a/src/sql/models/qsqlrelationaldelegate.cpp b/src/sql/models/qsqlrelationaldelegate.cpp new file mode 100644 index 0000000..1043ef9 --- /dev/null +++ b/src/sql/models/qsqlrelationaldelegate.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSqlRelationalDelegate + \brief The QSqlRelationalDelegate class provides a delegate that is used to + display and edit data from a QSqlRelationalTableModel. + + Unlike the default delegate, QSqlRelationalDelegate provides a + combobox for fields that are foreign keys into other tables. To + use the class, simply call QAbstractItemView::setItemDelegate() + on the view with an instance of QSqlRelationalDelegate: + + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4 + + The \l{sql/relationaltablemodel}{Relational Table Model} example + (shown below) illustrates how to use QSqlRelationalDelegate in + conjunction with QSqlRelationalTableModel to provide tables with + foreign key support. + + \image relationaltable.png + + \sa QSqlRelationalTableModel, {Model/View Programming} +*/ + + +/*! + \fn QSqlRelationalDelegate::QSqlRelationalDelegate(QObject *parent) + + Constructs a QSqlRelationalDelegate object with the given \a + parent. +*/ + +/*! + \fn QSqlRelationalDelegate::~QSqlRelationalDelegate() + + Destroys the QSqlRelationalDelegate object and frees any + allocated resources. +*/ + +/*! + \fn QWidget *QSqlRelationalDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const + \reimp +*/ + +/*! + \fn void QSqlRelationalDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const + \reimp +*/ + +/*! + \fn void QSqlRelationalDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const + \reimp +*/ + +QT_END_NAMESPACE diff --git a/src/sql/models/qsqlrelationaldelegate.h b/src/sql/models/qsqlrelationaldelegate.h new file mode 100644 index 0000000..dd9ad8f --- /dev/null +++ b/src/sql/models/qsqlrelationaldelegate.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLRELATIONALDELEGATE_H +#define QSQLRELATIONALDELEGATE_H + +#ifdef QT_GUI_LIB + +#include <QtGui/qitemdelegate.h> +#include <QtGui/qlistview.h> +#include <QtGui/qcombobox.h> +#include <QtSql/qsqlrelationaltablemodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlRelationalDelegate: public QItemDelegate +{ +public: + +explicit QSqlRelationalDelegate(QObject *parent = 0) + : QItemDelegate(parent) +{} + +~QSqlRelationalDelegate() +{} + +QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model()); + QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0; + if (!childModel) + return QItemDelegate::createEditor(parent, option, index); + + QComboBox *combo = new QComboBox(parent); + combo->setModel(childModel); + combo->setModelColumn(childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn())); + combo->installEventFilter(const_cast<QSqlRelationalDelegate *>(this)); + + return combo; +} + +void setEditorData(QWidget *editor, const QModelIndex &index) const +{ + const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model()); + QComboBox *combo = qobject_cast<QComboBox *>(editor); + if (!sqlModel || !combo) { + QItemDelegate::setEditorData(editor, index); + return; + } + combo->setCurrentIndex(combo->findText(sqlModel->data(index).toString())); +} + +void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + if (!index.isValid()) + return; + + QSqlRelationalTableModel *sqlModel = qobject_cast<QSqlRelationalTableModel *>(model); + QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0; + QComboBox *combo = qobject_cast<QComboBox *>(editor); + if (!sqlModel || !childModel || !combo) { + QItemDelegate::setModelData(editor, model, index); + return; + } + + int currentItem = combo->currentIndex(); + int childColIndex = childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn()); + int childEditIndex = childModel->fieldIndex(sqlModel->relation(index.column()).indexColumn()); + sqlModel->setData(index, + childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole), + Qt::DisplayRole); + sqlModel->setData(index, + childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole), + Qt::EditRole); +} + +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_GUI_LIB + +#endif // QSQLRELATIONALDELEGATE_H diff --git a/src/sql/models/qsqlrelationaltablemodel.cpp b/src/sql/models/qsqlrelationaltablemodel.cpp new file mode 100644 index 0000000..935466b --- /dev/null +++ b/src/sql/models/qsqlrelationaltablemodel.cpp @@ -0,0 +1,717 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqlrelationaltablemodel.h" + +#include "qhash.h" +#include "qstringlist.h" +#include "qsqldatabase.h" +#include "qsqldriver.h" +#include "qsqlerror.h" +#include "qsqlfield.h" +#include "qsqlindex.h" +#include "qsqlquery.h" +#include "qsqlrecord.h" + +#include "qsqltablemodel_p.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSqlRelation + \brief The QSqlRelation class stores information about an SQL foreign key. + + QSqlRelation is a helper class for QSqlRelationalTableModel. See + QSqlRelationalTableModel::setRelation() and + QSqlRelationalTableModel::relation() for details. + + \sa QSqlRelationalTableModel, QSqlRelationalDelegate, + {Relational Table Model Example} +*/ + +/*! + \fn QSqlRelation::QSqlRelation() + + Constructs an invalid QSqlRelation object. + + For such an object, the tableName(), indexColumn(), and + displayColumn() functions return an empty string. + + \sa isValid() +*/ + +/*! + \fn QSqlRelation::QSqlRelation(const QString &tableName, const QString &indexColumn, + const QString &displayColumn) + + Constructs a QSqlRelation object, where \a tableName is the SQL + table name to which a foreign key refers, \a indexColumn is the + foreign key, and \a displayColumn is the field that should be + presented to the user. + + \sa tableName(), indexColumn(), displayColumn() +*/ + +/*! + \fn QString QSqlRelation::tableName() const + + Returns the name of the table to which a foreign key refers. +*/ + +/*! + \fn QString QSqlRelation::indexColumn() const + + Returns the index column from table tableName() to which a + foreign key refers. +*/ + +/*! + \fn QString QSqlRelation::displayColumn() const + + Returns the column from table tableName() that should be + presented to the user instead of a foreign key. +*/ + +/*! + \fn bool QSqlRelation::isValid() const + + Returns true if the QSqlRelation object is valid; otherwise + returns false. +*/ + +struct QRelation +{ + public: + QRelation(): model(0),m_parent(0),m_dictInitialized(false){} + void init(QSqlRelationalTableModel *parent, const QSqlRelation &relation); + + void populateModel(); + + bool isDictionaryInitialized(); + void populateDictionary(); + void clearDictionary(); + + void clear(); + bool isValid(); + + QSqlRelation rel; + QSqlTableModel *model; + QHash<QString, QVariant> dictionary;//maps keys to display values + + private: + QSqlRelationalTableModel *m_parent; + bool m_dictInitialized; +}; + +/* + A QRelation must be initialized before it is considered valid. + Note: population of the model and dictionary are kept separate + from initialization, and are populated on an as needed basis. +*/ +void QRelation::init(QSqlRelationalTableModel *parent, const QSqlRelation &relation) +{ + Q_ASSERT(parent != NULL); + m_parent = parent; + rel = relation; +} + +void QRelation::populateModel() +{ + if (!isValid()) + return; + Q_ASSERT(m_parent != NULL); + + if (!model) { + model = new QSqlTableModel(m_parent, m_parent->database()); + model->setTable(rel.tableName()); + model->select(); + } +} + +bool QRelation::isDictionaryInitialized() +{ + return m_dictInitialized; +} + +void QRelation::populateDictionary() +{ + if (!isValid()) + return; + + if (model == NULL) + populateModel(); + + QSqlRecord record; + for (int i=0; i < model->rowCount(); ++i) { + record = model->record(i); + dictionary[record.field(rel.indexColumn()).value().toString()] = + record.field(rel.displayColumn()).value(); + } + m_dictInitialized = true; +} + +void QRelation::clearDictionary() +{ + dictionary.clear(); + m_dictInitialized = false; +} + +void QRelation::clear() +{ + delete model; + model = 0; + clearDictionary(); +} + +bool QRelation::isValid() +{ + return (rel.isValid() && m_parent != NULL); +} + +class QSqlRelationalTableModelPrivate: public QSqlTableModelPrivate +{ + Q_DECLARE_PUBLIC(QSqlRelationalTableModel) +public: + QSqlRelationalTableModelPrivate() + : QSqlTableModelPrivate() + {} + QString escapedRelationField(const QString &tableName, const QString &fieldName) const; + + int nameToIndex(const QString &name) const; + mutable QVector<QRelation> relations; + QSqlRecord baseRec; // the record without relations + void clearChanges(); + void clearEditBuffer(); + void clearCache(); + void revertCachedRow(int row); + + void translateFieldNames(int row, QSqlRecord &values) const; +}; + +static void qAppendWhereClause(QString &query, const QString &clause1, const QString &clause2) +{ + if (clause1.isEmpty() && clause2.isEmpty()) + return; + if (clause1.isEmpty() || clause2.isEmpty()) + query.append(QLatin1String(" WHERE (")).append(clause1).append(clause2); + else + query.append(QLatin1String(" WHERE (")).append(clause1).append( + QLatin1String(") AND (")).append(clause2); + query.append(QLatin1String(") ")); +} + +void QSqlRelationalTableModelPrivate::clearChanges() +{ + for (int i = 0; i < relations.count(); ++i) { + QRelation &rel = relations[i]; + rel.clear(); + } +} + +void QSqlRelationalTableModelPrivate::revertCachedRow(int row) +{ + QSqlTableModelPrivate::revertCachedRow(row); +} + +int QSqlRelationalTableModelPrivate::nameToIndex(const QString &name) const +{ + return baseRec.indexOf(name); +} + +void QSqlRelationalTableModelPrivate::clearEditBuffer() +{ + editBuffer = baseRec; +} + +/*! + \reimp +*/ +void QSqlRelationalTableModelPrivate::clearCache() +{ + for (int i = 0; i < relations.count(); ++i) + relations[i].clearDictionary(); + + QSqlTableModelPrivate::clearCache(); +} + +/*! + \class QSqlRelationalTableModel + \brief The QSqlRelationalTableModel class provides an editable + data model for a single database table, with foreign key support. + + \ingroup database + \inmodule QtSql + + QSqlRelationalTableModel acts like QSqlTableModel, but allows + columns to be set as foreign keys into other database tables. + + \table + \row \o \inlineimage noforeignkeys.png + \o \inlineimage foreignkeys.png + \endtable + + The screenshot on the left shows a plain QSqlTableModel in a + QTableView. Foreign keys (\c city and \c country) aren't resolved + to human-readable values. The screenshot on the right shows a + QSqlRelationalTableModel, with foreign keys resolved into + human-readable text strings. + + The following code snippet shows how the QSqlRelationalTableModel + was set up: + + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0 + \codeline + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1 + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 2 + + The setRelation() function calls establish a relationship between + two tables. The first call specifies that column 2 in table \c + employee is a foreign key that maps with field \c id of table \c + city, and that the view should present the \c{city}'s \c name + field to the user. The second call does something similar with + column 3. + + If you use a read-write QSqlRelationalTableModel, you probably + want to use QSqlRelationalDelegate on the view. Unlike the default + delegate, QSqlRelationalDelegate provides a combobox for fields + that are foreign keys into other tables. To use the class, simply + call QAbstractItemView::setItemDelegate() on the view with an + instance of QSqlRelationalDelegate: + + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4 + + The \l{sql/relationaltablemodel} example illustrates how to use + QSqlRelationalTableModel in conjunction with + QSqlRelationalDelegate to provide tables with foreigh key + support. + + \image relationaltable.png + + Notes: + + \list + \o The table must have a primary key declared. + \o The table's primary key may not contain a relation to + another table. + \o If a relational table contains keys that refer to non-existent + rows in the referenced table, the rows containing the invalid + keys will not be exposed through the model. The user or the + database is responsible for keeping referential integrity. + \o If a relation's display column name is also used as a column + name in the main table, or if it is used as display column + name in more than one relation it will be aliased. The alias is + is the relation's table name and display column name joined + by an underscore (e.g. tablename_columnname). All occurrences + of the duplicate display column name are aliased when + duplication is detected, but no aliasing is done to the column + names in the main table. The aliasing doesn't affect + QSqlRelation, so QSqlRelation::displayColumn() will return the + original display column name, but QSqlRecord::fieldName() will + return aliases. + \o When using setData() the role should always be Qt::EditRole, + and when using data() the role should always be Qt::DisplayRole. + \endlist + + \sa QSqlRelation, QSqlRelationalDelegate, + {Relational Table Model Example} +*/ + + +/*! + Creates an empty QSqlRelationalTableModel and sets the parent to \a parent + and the database connection to \a db. If \a db is not valid, the + default database connection will be used. +*/ +QSqlRelationalTableModel::QSqlRelationalTableModel(QObject *parent, QSqlDatabase db) + : QSqlTableModel(*new QSqlRelationalTableModelPrivate, parent, db) +{ +} + +/*! + Destroys the object and frees any allocated resources. +*/ +QSqlRelationalTableModel::~QSqlRelationalTableModel() +{ +} + +/*! + \reimp +*/ +QVariant QSqlRelationalTableModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSqlRelationalTableModel); + + if (role == Qt::DisplayRole && index.column() > 0 && index.column() < d->relations.count() && + d->relations.value(index.column()).isValid()) { + QRelation &relation = d->relations[index.column()]; + if (!relation.isDictionaryInitialized()) + relation.populateDictionary(); + + //only perform a dictionary lookup for the display value + //when the value at index has been changed or added. + //At an unmodified index, the underlying model will + //already have the correct display value. + QVariant v; + switch (d->strategy) { + case OnFieldChange: + break; + case OnRowChange: + if (index.row() == d->editIndex || index.row() == d->insertIndex) { + v = d->editBuffer.value(index.column()); + } + break; + case OnManualSubmit: + const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row()); + v = row.rec.value(index.column()); + break; + } + if (v.isValid()) + return relation.dictionary[v.toString()]; + } + return QSqlTableModel::data(index, role); +} + +/*! + Sets the data for the \a role in the item with the specified \a + index to the \a value given. Depending on the edit strategy, the + value might be applied to the database at once, or it may be + cached in the model. + + Returns true if the value could be set, or false on error (for + example, if \a index is out of bounds). + + For relational columns, \a value must be the index, not the + display value. The index must also exist in the referenced + table, otherwise the function returns false. + + \sa editStrategy(), data(), submit(), revertRow() +*/ +bool QSqlRelationalTableModel::setData(const QModelIndex &index, const QVariant &value, + int role) +{ + Q_D(QSqlRelationalTableModel); + if ( role == Qt::EditRole && index.column() > 0 && index.column() < d->relations.count() + && d->relations.value(index.column()).isValid()) { + QRelation &relation = d->relations[index.column()]; + if (!relation.isDictionaryInitialized()) + relation.populateDictionary(); + if (!relation.dictionary.contains(value.toString())) + return false; + } + return QSqlTableModel::setData(index, value, role); +} + +/*! + Lets the specified \a column be a foreign index specified by \a relation. + + Example: + + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0 + \codeline + \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1 + + The setRelation() call specifies that column 2 in table \c + employee is a foreign key that maps with field \c id of table \c + city, and that the view should present the \c{city}'s \c name + field to the user. + + Note: The table's primary key may not contain a relation to another table. + + \sa relation() +*/ +void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation) +{ + Q_D(QSqlRelationalTableModel); + if (column < 0) + return; + if (d->relations.size() <= column) + d->relations.resize(column + 1); + d->relations[column].init(this, relation); +} + +/*! + Returns the relation for the column \a column, or an invalid + relation if no relation is set. + + \sa setRelation(), QSqlRelation::isValid() +*/ +QSqlRelation QSqlRelationalTableModel::relation(int column) const +{ + Q_D(const QSqlRelationalTableModel); + return d->relations.value(column).rel; +} + +QString QSqlRelationalTableModelPrivate::escapedRelationField(const QString &tableName, + const QString &fieldName) const +{ + QString esc; + esc.reserve(tableName.size() + fieldName.size() + 1); + esc.append(tableName).append(QLatin1Char('.')).append(fieldName); + + return db.driver()->escapeIdentifier(esc, QSqlDriver::FieldName); +} + +/*! + \reimp +*/ +QString QSqlRelationalTableModel::selectStatement() const +{ + Q_D(const QSqlRelationalTableModel); + QString query; + + if (tableName().isEmpty()) + return query; + if (d->relations.isEmpty()) + return QSqlTableModel::selectStatement(); + + QString tList; + QString fList; + QString where; + + QSqlRecord rec = d->baseRec; + QStringList tables; + const QRelation nullRelation; + + // Count how many times each field name occurs in the record + QHash<QString, int> fieldNames; + for (int i = 0; i < rec.count(); ++i) { + QSqlRelation relation = d->relations.value(i, nullRelation).rel; + QString name; + if (relation.isValid()) + // Count the display column name, not the original foreign key + name = relation.displayColumn(); + else + name = rec.fieldName(i); + fieldNames.insert(name, fieldNames.value(name, 0) + 1); + } + + for (int i = 0; i < rec.count(); ++i) { + QSqlRelation relation = d->relations.value(i, nullRelation).rel; + if (relation.isValid()) { + QString relTableAlias = QString::fromLatin1("relTblAl_%1").arg(i); + if (!fList.isEmpty()) + fList.append(QLatin1String(", ")); + fList.append(d->escapedRelationField(relTableAlias, relation.displayColumn())); + + // If there are duplicate field names they must be aliased + if (fieldNames.value(relation.displayColumn()) > 1) { + fList.append(QString::fromLatin1(" AS %1_%2").arg(relation.tableName()).arg(relation.displayColumn())); + } + + // this needs fixing!! the below if is borken. + if (!tables.contains(relation.tableName())) + tables.append(d->db.driver()->escapeIdentifier(relation.tableName(),QSqlDriver::TableName) + .append(QLatin1String(" ")) + .append(d->db.driver()->escapeIdentifier(relTableAlias, QSqlDriver::TableName))); + if(!where.isEmpty()) + where.append(QLatin1String(" AND ")); + where.append(d->escapedRelationField(tableName(), rec.fieldName(i))); + where.append(QLatin1String(" = ")); + where.append(d->escapedRelationField(relTableAlias, relation.indexColumn())); + } else { + if (!fList.isEmpty()) + fList.append(QLatin1String(", ")); + fList.append(d->escapedRelationField(tableName(), rec.fieldName(i))); + } + } + if (!tables.isEmpty()) + tList.append(tables.join(QLatin1String(", "))); + if (fList.isEmpty()) + return query; + if(!tList.isEmpty()) + tList.prepend(QLatin1String(", ")); + tList.prepend(d->db.driver()->escapeIdentifier(tableName(),QSqlDriver::TableName)); + query.append(QLatin1String("SELECT ")); + query.append(fList).append(QLatin1String(" FROM ")).append(tList); + qAppendWhereClause(query, where, filter()); + + QString orderBy = orderByClause(); + if (!orderBy.isEmpty()) + query.append(QLatin1Char(' ')).append(orderBy); + + return query; +} + +/*! + Returns a QSqlTableModel object for accessing the table for which + \a column is a foreign key, or 0 if there is no relation for the + given \a column. + + The returned object is owned by the QSqlRelationalTableModel. + + \sa setRelation(), relation() +*/ +QSqlTableModel *QSqlRelationalTableModel::relationModel(int column) const +{ + Q_D(const QSqlRelationalTableModel); + if ( column < 0 || column >= d->relations.count()) + return 0; + + QRelation &relation = const_cast<QSqlRelationalTableModelPrivate *>(d)->relations[column]; + if (!relation.isValid()) + return 0; + + if (!relation.model) + relation.populateModel(); + return relation.model; +} + +/*! + \reimp +*/ +void QSqlRelationalTableModel::revertRow(int row) +{ + QSqlTableModel::revertRow(row); +} + +/*! + \reimp +*/ +void QSqlRelationalTableModel::clear() +{ + Q_D(QSqlRelationalTableModel); + d->clearChanges(); + d->relations.clear(); + QSqlTableModel::clear(); +} + +/*! + \reimp +*/ +bool QSqlRelationalTableModel::select() +{ + return QSqlTableModel::select(); +} + +/*! + \reimp +*/ +void QSqlRelationalTableModel::setTable(const QString &table) +{ + Q_D(QSqlRelationalTableModel); + + // memorize the table before applying the relations + d->baseRec = d->db.record(table); + + QSqlTableModel::setTable(table); +} + +/*! \internal + */ +void QSqlRelationalTableModelPrivate::translateFieldNames(int row, QSqlRecord &values) const +{ + Q_Q(const QSqlRelationalTableModel); + + for (int i = 0; i < values.count(); ++i) { + int realCol = q->indexInQuery(q->createIndex(row, i)).column(); + if (realCol != -1 && relations.value(realCol).isValid()) { + QVariant v = values.value(i); + values.replace(i, baseRec.field(realCol)); + values.setValue(i, v); + } + } +} + +/*! + \reimp +*/ +bool QSqlRelationalTableModel::updateRowInTable(int row, const QSqlRecord &values) +{ + Q_D(QSqlRelationalTableModel); + + QSqlRecord rec = values; + d->translateFieldNames(row, rec); + + return QSqlTableModel::updateRowInTable(row, rec); +} + +/*! + \reimp +*/ +bool QSqlRelationalTableModel::insertRowIntoTable(const QSqlRecord &values) +{ + Q_D(QSqlRelationalTableModel); + + QSqlRecord rec = values; + d->translateFieldNames(0, rec); + + return QSqlTableModel::insertRowIntoTable(rec); +} + +/*! + \reimp +*/ +QString QSqlRelationalTableModel::orderByClause() const +{ + Q_D(const QSqlRelationalTableModel); + + const QSqlRelation rel = d->relations.value(d->sortColumn).rel; + if (!rel.isValid()) + return QSqlTableModel::orderByClause(); + + QString s = QLatin1String("ORDER BY "); + s.append(d->escapedRelationField(QLatin1String("relTblAl_") + QString::number(d->sortColumn), + rel.displayColumn())); + s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC"); + return s; +} + +/*! + \reimp +*/ +bool QSqlRelationalTableModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSqlRelationalTableModel); + + if (parent.isValid() || column < 0 || column + count > d->rec.count()) + return false; + + for (int i = 0; i < count; ++i) { + d->baseRec.remove(column); + if (d->relations.count() > column) + d->relations.remove(column); + } + return QSqlTableModel::removeColumns(column, count, parent); +} + +QT_END_NAMESPACE diff --git a/src/sql/models/qsqlrelationaltablemodel.h b/src/sql/models/qsqlrelationaltablemodel.h new file mode 100644 index 0000000..fb11bae --- /dev/null +++ b/src/sql/models/qsqlrelationaltablemodel.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLRELATIONALTABLEMODEL_H +#define QSQLRELATIONALTABLEMODEL_H + +#include <QtSql/qsqltablemodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class Q_SQL_EXPORT QSqlRelation +{ +public: + QSqlRelation() {} + QSqlRelation(const QString &aTableName, const QString &indexCol, + const QString &displayCol) + : tName(aTableName), iColumn(indexCol), dColumn(displayCol) {} + inline QString tableName() const + { return tName; } + inline QString indexColumn() const + { return iColumn; } + inline QString displayColumn() const + { return dColumn; } + inline bool isValid() const + { return !(tName.isEmpty() || iColumn.isEmpty() || dColumn.isEmpty()); } +private: + QString tName, iColumn, dColumn; +}; + +class QSqlRelationalTableModelPrivate; + +class Q_SQL_EXPORT QSqlRelationalTableModel: public QSqlTableModel +{ + Q_OBJECT + +public: + explicit QSqlRelationalTableModel(QObject *parent = 0, + QSqlDatabase db = QSqlDatabase()); + virtual ~QSqlRelationalTableModel(); + + QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &item, const QVariant &value, int role = Qt::EditRole); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void clear(); + bool select(); + + void setTable(const QString &tableName); + virtual void setRelation(int column, const QSqlRelation &relation); + QSqlRelation relation(int column) const; + virtual QSqlTableModel *relationModel(int column) const; + +public Q_SLOTS: + void revertRow(int row); + +protected: + QString selectStatement() const; + bool updateRowInTable(int row, const QSqlRecord &values); + bool insertRowIntoTable(const QSqlRecord &values); + QString orderByClause() const; + +private: + Q_DECLARE_PRIVATE(QSqlRelationalTableModel) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLRELATIONALTABLEMODEL_H diff --git a/src/sql/models/qsqltablemodel.cpp b/src/sql/models/qsqltablemodel.cpp new file mode 100644 index 0000000..2fb9b0f --- /dev/null +++ b/src/sql/models/qsqltablemodel.cpp @@ -0,0 +1,1332 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsqltablemodel.h" + +#include "qsqldriver.h" +#include "qsqlerror.h" +#include "qsqlfield.h" +#include "qsqlindex.h" +#include "qsqlquery.h" +#include "qsqlrecord.h" +#include "qsqlresult.h" + +#include "qsqltablemodel_p.h" + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! \internal + Populates our record with values. +*/ +QSqlRecord QSqlTableModelPrivate::record(const QVector<QVariant> &values) const +{ + QSqlRecord r = rec; + for (int i = 0; i < r.count() && i < values.count(); ++i) + r.setValue(i, values.at(i)); + return r; +} + +/*! \internal + Set a record for OnFieldChange and OnRowChange. +*/ +bool QSqlTableModelPrivate::setRecord(int row, const QSqlRecord &record) +{ + Q_Q(QSqlTableModel); + bool isOk = true; + + QSqlTableModel::EditStrategy oldStrategy = strategy; + + // FieldChange strategy makes no sense when setting an entire row + if (strategy == QSqlTableModel::OnFieldChange) + strategy = QSqlTableModel::OnRowChange; + for (int i = 0; i < record.count(); ++i) { + int idx = nameToIndex(record.fieldName(i)); + if (idx == -1) + continue; + QModelIndex cIndex = q->createIndex(row, idx); + QVariant value = record.value(i); + QVariant oldValue = q->data(cIndex); + if (oldValue.isNull() || oldValue != value) + isOk &= q->setData(cIndex, value, Qt::EditRole); + } + if (isOk && oldStrategy == QSqlTableModel::OnFieldChange) + q->submitAll(); + strategy = oldStrategy; + + return isOk; +} + +int QSqlTableModelPrivate::nameToIndex(const QString &name) const +{ + return rec.indexOf(name); +} + +void QSqlTableModelPrivate::initRecordAndPrimaryIndex() +{ + rec = db.record(tableName); + primaryIndex = db.primaryIndex(tableName); +} + +void QSqlTableModelPrivate::clear() +{ + editIndex = -1; + sortColumn = -1; + sortOrder = Qt::AscendingOrder; + tableName.clear(); + editQuery.clear(); + editBuffer.clear(); + cache.clear(); + primaryIndex.clear(); + rec.clear(); + filter.clear(); +} + +void QSqlTableModelPrivate::revertInsertedRow() +{ + Q_Q(QSqlTableModel); + if (insertIndex == -1) + return; + + q->beginRemoveRows(QModelIndex(), insertIndex, insertIndex); + insertIndex = -1; + q->endRemoveRows(); +} + +void QSqlTableModelPrivate::clearEditBuffer() +{ + editBuffer = rec; +} + +void QSqlTableModelPrivate::clearCache() +{ + cache.clear(); +} + +void QSqlTableModelPrivate::revertCachedRow(int row) +{ + Q_Q(QSqlTableModel); + ModifiedRow r = cache.value(row); + switch (r.op) { + case QSqlTableModelPrivate::None: + Q_ASSERT_X(false, "QSqlTableModelPrivate::revertCachedRow()", "Invalid entry in cache map"); + return; + case QSqlTableModelPrivate::Update: + case QSqlTableModelPrivate::Delete: + cache.remove(row); + emit q->dataChanged(q->createIndex(row, 0), + q->createIndex(row, q->columnCount() - 1)); + break; + case QSqlTableModelPrivate::Insert: { + QMap<int, QSqlTableModelPrivate::ModifiedRow>::Iterator it = cache.find(row); + if (it == cache.end()) + return; + q->beginRemoveRows(QModelIndex(), row, row); + it = cache.erase(it); + while (it != cache.end()) { + int oldKey = it.key(); + const QSqlTableModelPrivate::ModifiedRow oldValue = it.value(); + cache.erase(it); + it = cache.insert(oldKey - 1, oldValue); + ++it; + } + q->endRemoveRows(); + break; } + } +} + +bool QSqlTableModelPrivate::exec(const QString &stmt, bool prepStatement, + const QSqlRecord &rec, const QSqlRecord &whereValues) +{ + if (stmt.isEmpty()) + return false; + + // lazy initialization of editQuery + if (editQuery.driver() != db.driver()) + editQuery = QSqlQuery(db); + + // workaround for In-Process databases - remove all read locks + // from the table to make sure the editQuery succeeds + if (db.driver()->hasFeature(QSqlDriver::SimpleLocking)) + const_cast<QSqlResult *>(query.result())->detachFromResultSet(); + + if (prepStatement) { + if (editQuery.lastQuery() != stmt) { + if (!editQuery.prepare(stmt)) { + error = editQuery.lastError(); + return false; + } + } + int i; + for (i = 0; i < rec.count(); ++i) { + if (rec.isGenerated(i) && rec.value(i).type() != QVariant::Invalid) + editQuery.addBindValue(rec.value(i)); + } + for (i = 0; i < whereValues.count(); ++i) { + if (whereValues.isGenerated(i)) + editQuery.addBindValue(whereValues.value(i)); + } + + if (!editQuery.exec()) { + error = editQuery.lastError(); + return false; + } + } else { + if (!editQuery.exec(stmt)) { + error = editQuery.lastError(); + return false; + } + } + return true; +} + +QSqlRecord QSqlTableModelPrivate::primaryValues(int row) +{ + QSqlRecord record; + if (!query.seek(row)) { + error = query.lastError(); + return record; + } + if (primaryIndex.isEmpty()) { + record = rec; + for (int i = 0; i < record.count(); ++i) + record.setValue(i, query.value(i)); + } else { + record = primaryIndex; + for (int i = 0; i < record.count(); ++i) + record.setValue(i, query.value(rec.indexOf(record.fieldName(i)))); + } + return record; +} + +/*! + \class QSqlTableModel + \brief The QSqlTableModel class provides an editable data model + for a single database table. + + \ingroup database + \inmodule QtSql + + QSqlTableModel is a high-level interface for reading and writing + database records from a single table. It is build on top of the + lower-level QSqlQuery and can be used to provide data to view + classes such as QTableView. For example: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 24 + + We set the SQL table's name and the edit strategy, then we set up + the labels displayed in the view header. The edit strategy + dictates when the changes done by the user in the view are + actually applied to the database. The possible values are \l + OnFieldChange, \l OnRowChange, and \l OnManualSubmit. + + QSqlTableModel can also be used to access a database + programmatically, without binding it to a view: + + \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 25 + + The code snippet above extracts the \c salary field from record 4 in + the result set of the query \c{SELECT * from employee}. + + It is possible to set filters using setFilter(), or modify the + sort order using setSort(). At the end, you must call select() to + populate the model with data. + + The \l{sql/tablemodel} example illustrates how to use + QSqlTableModel as the data source for a QTableView. + + QSqlTableModel provides no direct support for foreign keys. Use + the QSqlRelationalTableModel and QSqlRelationalDelegate if you + want to resolve foreign keys. + + \sa QSqlRelationalTableModel, QSqlQuery, {Model/View Programming}, + {Table Model Example}, {Cached Table Example} +*/ + +/*! + \fn QSqlTableModel::beforeDelete(int row) + + This signal is emitted by deleteRowFromTable() before the \a row + is deleted from the currently active database table. +*/ + +/*! + \fn void QSqlTableModel::primeInsert(int row, QSqlRecord &record) + + This signal is emitted by insertRows(), when an insertion is + initiated in the given \a row of the currently active database + table. The \a record parameter can be written to (since it is a + reference), for example to populate some fields with default + values. +*/ + +/*! + \fn QSqlTableModel::beforeInsert(QSqlRecord &record) + + This signal is emitted by insertRowIntoTable() before a new row is + inserted into the currently active database table. The values that + are about to be inserted are stored in \a record and can be + modified before they will be inserted. +*/ + +/*! + \fn QSqlTableModel::beforeUpdate(int row, QSqlRecord &record) + + This signal is emitted by updateRowInTable() before the \a row is + updated in the currently active database table with the values + from \a record. + + Note that only values that are marked as generated will be updated. + The generated flag can be set with \l QSqlRecord::setGenerated() + and checked with \l QSqlRecord::isGenerated(). + + \sa QSqlRecord::isGenerated() +*/ + +/*! + Creates an empty QSqlTableModel and sets the parent to \a parent + and the database connection to \a db. If \a db is not valid, the + default database connection will be used. + + The default edit strategy is \l OnRowChange. +*/ +QSqlTableModel::QSqlTableModel(QObject *parent, QSqlDatabase db) + : QSqlQueryModel(*new QSqlTableModelPrivate, parent) +{ + Q_D(QSqlTableModel); + d->db = db.isValid() ? db : QSqlDatabase::database(); +} + +/*! \internal +*/ +QSqlTableModel::QSqlTableModel(QSqlTableModelPrivate &dd, QObject *parent, QSqlDatabase db) + : QSqlQueryModel(dd, parent) +{ + Q_D(QSqlTableModel); + d->db = db.isValid() ? db : QSqlDatabase::database(); +} + +/*! + Destroys the object and frees any allocated resources. +*/ +QSqlTableModel::~QSqlTableModel() +{ +} + +/*! + Sets the database table on which the model operates to \a + tableName. Does not select data from the table, but fetches its + field information. + + To populate the model with the table's data, call select(). + + Error information can be retrieved with \l lastError(). + + \sa select(), setFilter(), lastError() +*/ +void QSqlTableModel::setTable(const QString &tableName) +{ + Q_D(QSqlTableModel); + clear(); + if(d->db.tables().contains(tableName.toUpper())) + d->tableName = tableName.toUpper(); + else + d->tableName = tableName; + d->initRecordAndPrimaryIndex(); + d->initColOffsets(d->rec.count()); + + if (d->rec.count() == 0) + d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(), + QSqlError::StatementError); +} + +/*! + Returns the name of the currently selected table. +*/ +QString QSqlTableModel::tableName() const +{ + Q_D(const QSqlTableModel); + return d->tableName; +} + +/*! + Populates the model with data from the table that was set via setTable(), using the + specified filter and sort condition, and returns true if successful; otherwise + returns false. + + \sa setTable(), setFilter(), selectStatement() +*/ +bool QSqlTableModel::select() +{ + Q_D(QSqlTableModel); + QString query = selectStatement(); + if (query.isEmpty()) + return false; + + revertAll(); + QSqlQuery qu(query, d->db); + setQuery(qu); + + if (!qu.isActive()) { + // something went wrong - revert to non-select state + d->initRecordAndPrimaryIndex(); + return false; + } + return true; +} + +/*! + \reimp +*/ +QVariant QSqlTableModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSqlTableModel); + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::EditRole)) + return QVariant(); + + QModelIndex item = indexInQuery(index); + + switch (d->strategy) { + case OnFieldChange: + case OnRowChange: + if (index.row() == d->insertIndex) { + QVariant val; + if (item.column() < 0 || item.column() >= d->rec.count()) + return val; + val = d->editBuffer.value(index.column()); + if (val.type() == QVariant::Invalid) + val = QVariant(d->rec.field(item.column()).type()); + return val; + } + if (d->editIndex == item.row()) { + QVariant var = d->editBuffer.value(item.column()); + if (var.isValid()) + return var; + } + break; + case OnManualSubmit: { + const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row()); + const QVariant var = row.rec.value(item.column()); + if (var.isValid() || row.op == QSqlTableModelPrivate::Insert) + return var; + break; } + } + return QSqlQueryModel::data(item, role); +} + +/*! + \reimp +*/ +QVariant QSqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSqlTableModel); + if (orientation == Qt::Vertical && role == Qt::DisplayRole) { + switch (d->strategy) { + case OnFieldChange: + case OnRowChange: + if (d->insertIndex == section) + return QLatin1String("*"); + break; + case OnManualSubmit: + QSqlTableModelPrivate::Op op = d->cache.value(section).op; + if (op == QSqlTableModelPrivate::Insert) + return QLatin1String("*"); + else if (op == QSqlTableModelPrivate::Delete) + return QLatin1String("!"); + break; + } + } + return QSqlQueryModel::headerData(section, orientation, role); +} + +/*! + Returns true if the value at the index \a index is dirty, otherwise false. + Dirty values are values that were modified in the model + but not yet written into the database. + + If \a index is invalid or points to a non-existing row, false is returned. +*/ +bool QSqlTableModel::isDirty(const QModelIndex &index) const +{ + Q_D(const QSqlTableModel); + if (!index.isValid()) + return false; + + switch (d->strategy) { + case OnFieldChange: + return false; + case OnRowChange: + return index.row() == d->editIndex && d->editBuffer.value(index.column()).isValid(); + case OnManualSubmit: { + const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row()); + return row.op == QSqlTableModelPrivate::Insert + || row.op == QSqlTableModelPrivate::Delete + || (row.op == QSqlTableModelPrivate::Update + && row.rec.value(index.column()).isValid()); + } + } + return false; +} + +/*! + Sets the data for the item \a index for the role \a role to \a + value. Depending on the edit strategy, the value might be applied + to the database at once or cached in the model. + + Returns true if the value could be set or false on error, for + example if \a index is out of bounds. + + \sa editStrategy(), data(), submit(), submitAll(), revertRow() +*/ +bool QSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QSqlTableModel); + if (role != Qt::EditRole) + return QSqlQueryModel::setData(index, value, role); + + if (!index.isValid() || index.column() >= d->rec.count() || index.row() >= rowCount()) + return false; + + bool isOk = true; + switch (d->strategy) { + case OnFieldChange: { + if (index.row() == d->insertIndex) { + d->editBuffer.setValue(index.column(), value); + return true; + } + d->clearEditBuffer(); + d->editBuffer.setValue(index.column(), value); + isOk = updateRowInTable(index.row(), d->editBuffer); + if (isOk) + select(); + break; } + case OnRowChange: + if (index.row() == d->insertIndex) { + d->editBuffer.setValue(index.column(), value); + return true; + } + if (d->editIndex != index.row()) { + if (d->editIndex != -1) + submit(); + d->clearEditBuffer(); + } + d->editBuffer.setValue(index.column(), value); + d->editIndex = index.row(); + emit dataChanged(index, index); + break; + case OnManualSubmit: { + QSqlTableModelPrivate::ModifiedRow &row = d->cache[index.row()]; + if (row.op == QSqlTableModelPrivate::None) { + row.op = QSqlTableModelPrivate::Update; + row.rec = d->rec; + row.primaryValues = d->primaryValues(indexInQuery(index).row()); + } + row.rec.setValue(index.column(), value); + emit dataChanged(index, index); + break; } + } + return isOk; +} + +/*! + This function simply calls QSqlQueryModel::setQuery(\a query). + You should normally not call it on a QSqlTableModel. Instead, use + setTable(), setSort(), setFilter(), etc., to set up the query. + + \sa selectStatement() +*/ +void QSqlTableModel::setQuery(const QSqlQuery &query) +{ + QSqlQueryModel::setQuery(query); +} + +/*! + Updates the given \a row in the currently active database table + with the specified \a values. Returns true if successful; otherwise + returns false. + + This is a low-level method that operates directly on the database + and should not be called directly. Use setData() to update values. + The model will decide depending on its edit strategy when to modify + the database. + + Note that only values that have the generated-flag set are updated. + The generated-flag can be set with QSqlRecord::setGenerated() and + tested with QSqlRecord::isGenerated(). + + \sa QSqlRecord::isGenerated(), setData() +*/ +bool QSqlTableModel::updateRowInTable(int row, const QSqlRecord &values) +{ + Q_D(QSqlTableModel); + QSqlRecord rec(values); + emit beforeUpdate(row, rec); + + const QSqlRecord whereValues = d->strategy == OnManualSubmit ? d->cache[row].primaryValues : d->primaryValues(row); + bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); + QString stmt = d->db.driver()->sqlStatement(QSqlDriver::UpdateStatement, d->tableName, + rec, prepStatement); + QString where = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement, d->tableName, + whereValues, prepStatement); + + if (stmt.isEmpty() || where.isEmpty() || row < 0 || row >= rowCount()) { + d->error = QSqlError(QLatin1String("No Fields to update"), QString(), + QSqlError::StatementError); + return false; + } + stmt.append(QLatin1Char(' ')).append(where); + + return d->exec(stmt, prepStatement, rec, whereValues); +} + + +/*! + Inserts the values \a values into the currently active database table. + + This is a low-level method that operates directly on the database + and should not be called directly. Use insertRow() and setData() + to insert values. The model will decide depending on its edit strategy + when to modify the database. + + Returns true if the values could be inserted, otherwise false. + Error information can be retrieved with \l lastError(). + + \sa lastError(), insertRow(), insertRows() +*/ +bool QSqlTableModel::insertRowIntoTable(const QSqlRecord &values) +{ + Q_D(QSqlTableModel); + QSqlRecord rec = values; + emit beforeInsert(rec); + + bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); + QString stmt = d->db.driver()->sqlStatement(QSqlDriver::InsertStatement, d->tableName, + rec, prepStatement); + + if (stmt.isEmpty()) { + d->error = QSqlError(QLatin1String("No Fields to update"), QString(), + QSqlError::StatementError); + return false; + } + + return d->exec(stmt, prepStatement, rec); +} + +/*! + Deletes the given \a row from the currently active database table. + + This is a low-level method that operates directly on the database + and should not be called directly. Use removeRow() or removeRows() + to delete values. The model will decide depending on its edit strategy + when to modify the database. + + Returns true if the row was deleted; otherwise returns false. + + \sa removeRow(), removeRows() +*/ +bool QSqlTableModel::deleteRowFromTable(int row) +{ + Q_D(QSqlTableModel); + emit beforeDelete(row); + + QSqlRecord rec = d->primaryValues(row); + bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); + QString stmt = d->db.driver()->sqlStatement(QSqlDriver::DeleteStatement, + d->tableName, + QSqlRecord(), + prepStatement); + QString where = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement, + d->tableName, + rec, + prepStatement); + + if (stmt.isEmpty() || where.isEmpty()) { + d->error = QSqlError(QLatin1String("Unable to delete row"), QString(), + QSqlError::StatementError); + return false; + } + stmt.append(QLatin1Char(' ')).append(where); + + return d->exec(stmt, prepStatement, rec); +} + +/*! + Submits all pending changes and returns true on success. + Returns false on error, detailed error information can be + obtained with lastError(). + + On success the model will be repopulated. Any views + presenting it will lose their selections. + + Note: In OnManualSubmit mode, already submitted changes won't + be cleared from the cache when submitAll() fails. This allows + transactions to be rolled back and resubmitted again without + losing data. + + \sa revertAll(), lastError() +*/ +bool QSqlTableModel::submitAll() +{ + Q_D(QSqlTableModel); + + switch (d->strategy) { + case OnFieldChange: + if (d->insertIndex == -1) + return true; + // else fall through + case OnRowChange: + if (d->editBuffer.isEmpty()) + return true; + if (d->insertIndex != -1) { + if (!insertRowIntoTable(d->editBuffer)) + return false; + d->bottom = d->bottom.sibling(d->bottom.row() + 1, d->bottom.column()); + } else { + if (!updateRowInTable(d->editIndex, d->editBuffer)) + return false; + } + d->clearEditBuffer(); + d->editIndex = -1; + d->insertIndex = -1; + return select(); + case OnManualSubmit: + for (QSqlTableModelPrivate::CacheMap::ConstIterator it = d->cache.constBegin(); + it != d->cache.constEnd(); ++it) { + switch (it.value().op) { + case QSqlTableModelPrivate::Insert: + if (!insertRowIntoTable(it.value().rec)) + return false; + d->bottom = d->bottom.sibling(d->bottom.row() + 1, d->bottom.column()); + break; + case QSqlTableModelPrivate::Update: + if (!updateRowInTable(it.key(), it.value().rec)) + return false; + break; + case QSqlTableModelPrivate::Delete: + if (!deleteRowFromTable(it.key())) + return false; + break; + case QSqlTableModelPrivate::None: + Q_ASSERT_X(false, "QSqlTableModel::submitAll()", "Invalid cache operation"); + break; + } + } + d->clearCache(); + return select(); + } + return false; +} + +/*! + This reimplemented slot is called by the item delegates when the + user stopped editing the current row. + + Submits the currently edited row if the model's strategy is set + to OnRowChange or OnFieldChange. Does nothing for the OnManualSubmit + strategy. + + Use submitAll() to submit all pending changes for the + OnManualSubmit strategy. + + Returns true on success; otherwise returns false. Use lastError() + to query detailed error information. + + On success the model will be repopulated. Any views + presenting it will lose their selections. + + \sa revert(), revertRow(), submitAll(), revertAll(), lastError() +*/ +bool QSqlTableModel::submit() +{ + Q_D(QSqlTableModel); + if (d->strategy == OnRowChange || d->strategy == OnFieldChange) + return submitAll(); + return true; +} + +/*! + This reimplemented slot is called by the item delegates when the + user canceled editing the current row. + + Reverts the changes if the model's strategy is set to + OnRowChange. Does nothing for the other edit strategies. + + Use revertAll() to revert all pending changes for the + OnManualSubmit strategy or revertRow() to revert a specific row. + + \sa submit(), submitAll(), revertRow(), revertAll() +*/ +void QSqlTableModel::revert() +{ + Q_D(QSqlTableModel); + if (d->strategy == OnRowChange) + revertAll(); +} + +/*! + \enum QSqlTableModel::EditStrategy + + This enum type describes which strategy to choose when editing values in the database. + + \value OnFieldChange All changes to the model will be applied immediately to the database. + \value OnRowChange Changes to a row will be applied when the user selects a different row. + \value OnManualSubmit All changes will be cached in the model until either submitAll() + or revertAll() is called. + + Note: To prevent inserting only partly initialized rows into the database, + \c OnFieldChange will behave like \c OnRowChange for newly inserted rows. + + \sa setEditStrategy() +*/ + + +/*! + Sets the strategy for editing values in the database to \a + strategy. + + This will revert any pending changes. + + \sa editStrategy(), revertAll() +*/ +void QSqlTableModel::setEditStrategy(EditStrategy strategy) +{ + Q_D(QSqlTableModel); + revertAll(); + d->strategy = strategy; +} + +/*! + Returns the current edit strategy. + + \sa setEditStrategy() +*/ +QSqlTableModel::EditStrategy QSqlTableModel::editStrategy() const +{ + Q_D(const QSqlTableModel); + return d->strategy; +} + +/*! + Reverts all pending changes. + + \sa revert(), revertRow(), submitAll() +*/ +void QSqlTableModel::revertAll() +{ + Q_D(QSqlTableModel); + switch (d->strategy) { + case OnFieldChange: + break; + case OnRowChange: + if (d->editIndex != -1) + revertRow(d->editIndex); + else if (d->insertIndex != -1) + revertRow(d->insertIndex); + break; + case OnManualSubmit: + while (!d->cache.isEmpty()) + revertRow(d->cache.constBegin().key()); + break; + } +} + +/*! + Reverts all changes for the specified \a row. + + \sa revert(), revertAll(), submit(), submitAll() +*/ +void QSqlTableModel::revertRow(int row) +{ + if (row < 0) + return; + + Q_D(QSqlTableModel); + switch (d->strategy) { + case OnFieldChange: + break; + case OnRowChange: { + if (d->editIndex == row) { + d->editBuffer.clear(); + int oldIndex = d->editIndex; + d->editIndex = -1; + emit dataChanged(createIndex(oldIndex, 0), createIndex(oldIndex, columnCount())); + } else if (d->insertIndex == row) { + d->revertInsertedRow(); + } + break; } + case OnManualSubmit: + d->revertCachedRow(row); + break; + } +} + +/*! + Returns the primary key for the current table, or an empty + QSqlIndex if the table is not set or has no primary key. + + \sa setTable(), setPrimaryKey(), QSqlDatabase::primaryIndex() +*/ +QSqlIndex QSqlTableModel::primaryKey() const +{ + Q_D(const QSqlTableModel); + return d->primaryIndex; +} + +/*! + Protected method that allows subclasses to set the primary key to + \a key. + + Normally, the primary index is set automatically whenever you + call setTable(). + + \sa primaryKey(), QSqlDatabase::primaryIndex() +*/ +void QSqlTableModel::setPrimaryKey(const QSqlIndex &key) +{ + Q_D(QSqlTableModel); + d->primaryIndex = key; +} + +/*! + Returns a pointer to the used QSqlDatabase or 0 if no database was set. +*/ +QSqlDatabase QSqlTableModel::database() const +{ + Q_D(const QSqlTableModel); + return d->db; +} + +/*! + Sorts the data by \a column with the sort order \a order. + This will immediately select data, use setSort() + to set a sort order without populating the model with data. + + \sa setSort(), select(), orderByClause() +*/ +void QSqlTableModel::sort(int column, Qt::SortOrder order) +{ + setSort(column, order); + select(); +} + +/*! + Sets the sort order for \a column to \a order. This does not + affect the current data, to refresh the data using the new + sort order, call select(). + + \sa select(), orderByClause() +*/ +void QSqlTableModel::setSort(int column, Qt::SortOrder order) +{ + Q_D(QSqlTableModel); + d->sortColumn = column; + d->sortOrder = order; +} + +/*! + Returns an SQL \c{ORDER BY} clause based on the currently set + sort order. + + \sa setSort(), selectStatement() +*/ +QString QSqlTableModel::orderByClause() const +{ + Q_D(const QSqlTableModel); + QString s; + QSqlField f = d->rec.field(d->sortColumn); + if (!f.isValid()) + return s; + + QString table = d->db.driver()->escapeIdentifier(d->tableName, QSqlDriver::TableName); + QString field = d->db.driver()->escapeIdentifier(f.name(), QSqlDriver::FieldName); + s.append(QLatin1String("ORDER BY ")).append(table).append(QLatin1Char('.')).append(field); + s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC"); + + return s; +} + +/*! + Returns the index of the field \a fieldName. +*/ +int QSqlTableModel::fieldIndex(const QString &fieldName) const +{ + Q_D(const QSqlTableModel); + return d->rec.indexOf(fieldName); +} + +/*! + Returns the SQL \c SELECT statement used internally to populate + the model. The statement includes the filter and the \c{ORDER BY} + clause. + + \sa filter(), orderByClause() +*/ +QString QSqlTableModel::selectStatement() const +{ + Q_D(const QSqlTableModel); + QString query; + if (d->tableName.isEmpty()) { + d->error = QSqlError(QLatin1String("No table name given"), QString(), + QSqlError::StatementError); + return query; + } + if (d->rec.isEmpty()) { + d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(), + QSqlError::StatementError); + return query; + } + + query = d->db.driver()->sqlStatement(QSqlDriver::SelectStatement, + d->tableName, + d->rec, + false); + if (query.isEmpty()) { + d->error = QSqlError(QLatin1String("Unable to select fields from table ") + d->tableName, + QString(), QSqlError::StatementError); + return query; + } + if (!d->filter.isEmpty()) + query.append(QLatin1String(" WHERE ")).append(d->filter); + QString orderBy(orderByClause()); + if (!orderBy.isEmpty()) + query.append(QLatin1Char(' ')).append(orderBy); + + return query; +} + +/*! + Removes \a count columns from the \a parent model, starting at + index \a column. + + Returns if the columns were successfully removed; otherwise + returns false. + + \sa removeRows() +*/ +bool QSqlTableModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSqlTableModel); + if (parent.isValid() || column < 0 || column + count > d->rec.count()) + return false; + for (int i = 0; i < count; ++i) + d->rec.remove(column); + if (d->query.isActive()) + return select(); + return true; +} + +/*! + Removes \a count rows starting at \a row. Since this model + does not support hierarchical structures, \a parent must be + an invalid model index. + + Emits the beforeDelete() signal before a row is deleted. When + the edit strategy is OnManualSubmit signal emission is delayed + until submitAll() is called. + + Returns true if all rows could be removed; otherwise returns + false. Detailed error information can be retrieved using + lastError(). + + \sa removeColumns(), insertRows() +*/ +bool QSqlTableModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSqlTableModel); + if (parent.isValid() || row < 0 || count <= 0) + return false; + + int i; + switch (d->strategy) { + case OnFieldChange: + case OnRowChange: + for (i = 0; i < count; ++i) { + if (row + i == d->insertIndex) + d->revertInsertedRow(); + else if (!deleteRowFromTable(row + i)) + return false; + } + select(); + break; + case OnManualSubmit: + for (i = 0; i < count; ++i) { + int idx = row + i; + if (idx >= rowCount()) + return false; + if (d->cache.value(idx).op == QSqlTableModelPrivate::Insert) + revertRow(idx); + else { + d->cache[idx].op = QSqlTableModelPrivate::Delete; + emit headerDataChanged(Qt::Vertical, idx, idx); + } + } + break; + } + return true; +} + +/*! + Inserts \a count empty rows at position \a row. Note that \a + parent must be invalid, since this model does not support + parent-child relations. + + Only one row at a time can be inserted when using the + OnFieldChange or OnRowChange update strategies. + + The primeInsert() signal will be emitted for each new row. + Connect to it if you want to initialize the new row with default + values. + + Returns false if the parameters are out of bounds; otherwise + returns true. + + \sa primeInsert(), insertRecord() +*/ +bool QSqlTableModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSqlTableModel); + if (row < 0 || count <= 0 || row > rowCount() || parent.isValid()) + return false; + + switch (d->strategy) { + case OnFieldChange: + case OnRowChange: + if (count != 1) + return false; + beginInsertRows(parent, row, row); + d->insertIndex = row; + // ### apply dangling changes... + d->clearEditBuffer(); + emit primeInsert(row, d->editBuffer); + break; + case OnManualSubmit: + beginInsertRows(parent, row, row + count - 1); + if (!d->cache.isEmpty()) { + QMap<int, QSqlTableModelPrivate::ModifiedRow>::Iterator it = d->cache.end(); + while (it != d->cache.begin() && (--it).key() >= row) { + int oldKey = it.key(); + const QSqlTableModelPrivate::ModifiedRow oldValue = it.value(); + d->cache.erase(it); + it = d->cache.insert(oldKey + count, oldValue); + } + } + + for (int i = 0; i < count; ++i) { + d->cache[row + i] = QSqlTableModelPrivate::ModifiedRow(QSqlTableModelPrivate::Insert, + d->rec); + emit primeInsert(row + i, d->cache[row + i].rec); + } + break; + } + endInsertRows(); + return true; +} + +/*! + Inserts the \a record after \a row. If \a row is negative, the + record will be appended to the end. Calls insertRows() and + setRecord() internally. + + Returns true if the row could be inserted, otherwise false. + + \sa insertRows(), removeRows() +*/ +bool QSqlTableModel::insertRecord(int row, const QSqlRecord &record) +{ + Q_D(QSqlTableModel); + if (row < 0) + row = rowCount(); + if (!insertRow(row, QModelIndex())) + return false; + if (!setRecord(row, record)) + return false; + if (d->strategy == OnFieldChange || d->strategy == OnRowChange) + return submit(); + return true; +} + +/*! \reimp +*/ +int QSqlTableModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QSqlTableModel); + + if (parent.isValid()) + return 0; + + int rc = QSqlQueryModel::rowCount(); + if (d->strategy == OnManualSubmit) { + for (QSqlTableModelPrivate::CacheMap::ConstIterator it = d->cache.constBegin(); + it != d->cache.constEnd(); ++it) { + if (it.value().op == QSqlTableModelPrivate::Insert) + ++rc; + } + } else if (d->insertIndex >= 0) { + ++rc; + } + return rc; +} + +/*! + Returns the index of the value in the database result set for the + given \a item in the model. + + The return value is identical to \a item if no columns or rows + have been inserted, removed, or moved around. + + Returns an invalid model index if \a item is out of bounds or if + \a item does not point to a value in the result set. + + \sa QSqlQueryModel::indexInQuery() +*/ +QModelIndex QSqlTableModel::indexInQuery(const QModelIndex &item) const +{ + Q_D(const QSqlTableModel); + const QModelIndex it = QSqlQueryModel::indexInQuery(item); + if (d->strategy == OnManualSubmit) { + int rowOffset = 0; + QSqlTableModelPrivate::CacheMap::ConstIterator i = d->cache.constBegin(); + while (i != d->cache.constEnd() && i.key() <= it.row()) { + if (i.value().op == QSqlTableModelPrivate::Insert) + ++rowOffset; + ++i; + } + return createIndex(it.row() - rowOffset, it.column(), it.internalPointer()); + } else { + if (d->insertIndex >= 0 && it.row() >= d->insertIndex) + return createIndex(it.row() - 1, it.column(), it.internalPointer()); + } + return it; +} + +/*! + Returns the currently set filter. + + \sa setFilter(), select() +*/ +QString QSqlTableModel::filter() const +{ + Q_D(const QSqlTableModel); + return d->filter; +} + +/*! + Sets the current filter to \a filter. + + The filter is a SQL \c WHERE clause without the keyword \c WHERE + (for example, \c{name='Josephine')}. + + If the model is already populated with data from a database, + the model re-selects it with the new filter. Otherwise, the filter + will be applied the next time select() is called. + + \sa filter(), select(), selectStatement(), orderByClause() +*/ +void QSqlTableModel::setFilter(const QString &filter) +{ + Q_D(QSqlTableModel); + d->filter = filter; + if (d->query.isActive()) + select(); +} + +/*! \reimp +*/ +void QSqlTableModel::clear() +{ + Q_D(QSqlTableModel); + d->clear(); + QSqlQueryModel::clear(); +} + +/*! \reimp +*/ +Qt::ItemFlags QSqlTableModel::flags(const QModelIndex &index) const +{ + Q_D(const QSqlTableModel); + if (index.internalPointer() || index.column() < 0 || index.column() >= d->rec.count() + || index.row() < 0) + return 0; + if (d->rec.field(index.column()).isReadOnly()) + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; +} + +/*! + Sets the values at the specified \a row to the values of \a + record. Returns true if all the values could be set; otherwise + returns false. + + \sa record() +*/ +bool QSqlTableModel::setRecord(int row, const QSqlRecord &record) +{ + Q_D(QSqlTableModel); + Q_ASSERT_X(row >= 0, "QSqlTableModel::setRecord()", "Cannot set a record to a row less than 0"); + if (row >= rowCount()) + return false; + + bool isOk = true; + switch (d->strategy) { + case OnFieldChange: + case OnRowChange: + return d->setRecord(row, record); + case OnManualSubmit: { + QSqlTableModelPrivate::ModifiedRow &mrow = d->cache[row]; + if (mrow.op == QSqlTableModelPrivate::None) { + mrow.op = QSqlTableModelPrivate::Update; + mrow.rec = d->rec; + mrow.primaryValues = d->primaryValues(indexInQuery(createIndex(row, 0)).row()); + } + for (int i = 0; i < record.count(); ++i) { + int idx = mrow.rec.indexOf(record.fieldName(i)); + if (idx == -1) + isOk = false; + else + mrow.rec.setValue(idx, record.value(i)); + } + return isOk; } + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/sql/models/qsqltablemodel.h b/src/sql/models/qsqltablemodel.h new file mode 100644 index 0000000..14c4c4f --- /dev/null +++ b/src/sql/models/qsqltablemodel.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLTABLEMODEL_H +#define QSQLTABLEMODEL_H + +#include <QtSql/qsqldatabase.h> +#include <QtSql/qsqlquerymodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Sql) + +class QSqlTableModelPrivate; +class QSqlRecord; +class QSqlField; +class QSqlIndex; + +class Q_SQL_EXPORT QSqlTableModel: public QSqlQueryModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSqlTableModel) + +public: + enum EditStrategy {OnFieldChange, OnRowChange, OnManualSubmit}; + + explicit QSqlTableModel(QObject *parent = 0, QSqlDatabase db = QSqlDatabase()); + virtual ~QSqlTableModel(); + + virtual bool select(); + + virtual void setTable(const QString &tableName); + QString tableName() const; + + Qt::ItemFlags flags(const QModelIndex &index) const; + + QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + bool isDirty(const QModelIndex &index) const; + void clear(); + + virtual void setEditStrategy(EditStrategy strategy); + EditStrategy editStrategy() const; + + QSqlIndex primaryKey() const; + QSqlDatabase database() const; + int fieldIndex(const QString &fieldName) const; + + void sort(int column, Qt::SortOrder order); + virtual void setSort(int column, Qt::SortOrder order); + + QString filter() const; + virtual void setFilter(const QString &filter); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + bool insertRecord(int row, const QSqlRecord &record); + bool setRecord(int row, const QSqlRecord &record); + + virtual void revertRow(int row); + +public Q_SLOTS: + bool submit(); + void revert(); + + bool submitAll(); + void revertAll(); + +Q_SIGNALS: + void primeInsert(int row, QSqlRecord &record); + + void beforeInsert(QSqlRecord &record); + void beforeUpdate(int row, QSqlRecord &record); + void beforeDelete(int row); + +protected: + QSqlTableModel(QSqlTableModelPrivate &dd, QObject *parent = 0, QSqlDatabase db = QSqlDatabase()); + + virtual bool updateRowInTable(int row, const QSqlRecord &values); + virtual bool insertRowIntoTable(const QSqlRecord &values); + virtual bool deleteRowFromTable(int row); + virtual QString orderByClause() const; + virtual QString selectStatement() const; + + void setPrimaryKey(const QSqlIndex &key); + void setQuery(const QSqlQuery &query); + QModelIndex indexInQuery(const QModelIndex &item) const; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQLTABLEMODEL_H diff --git a/src/sql/models/qsqltablemodel_p.h b/src/sql/models/qsqltablemodel_p.h new file mode 100644 index 0000000..fa3b44b --- /dev/null +++ b/src/sql/models/qsqltablemodel_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLTABLEMODEL_P_H +#define QSQLTABLEMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qsql*model.h . This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qmap.h" +#include "private/qsqlquerymodel_p.h" + +QT_BEGIN_NAMESPACE + +class QSqlTableModelPrivate: public QSqlQueryModelPrivate +{ + Q_DECLARE_PUBLIC(QSqlTableModel) + +public: + QSqlTableModelPrivate() + : editIndex(-1), insertIndex(-1), sortColumn(-1), + sortOrder(Qt::AscendingOrder), + strategy(QSqlTableModel::OnRowChange) + {} + void clear(); + QSqlRecord primaryValues(int index); + virtual void clearEditBuffer(); + virtual void clearCache(); + QSqlRecord record(const QVector<QVariant> &values) const; + + bool exec(const QString &stmt, bool prepStatement, + const QSqlRecord &rec, const QSqlRecord &whereValues = QSqlRecord()); + virtual void revertCachedRow(int row); + void revertInsertedRow(); + bool setRecord(int row, const QSqlRecord &record); + virtual int nameToIndex(const QString &name) const; + void initRecordAndPrimaryIndex(); + + QSqlDatabase db; + int editIndex; + int insertIndex; + + int sortColumn; + Qt::SortOrder sortOrder; + + QSqlTableModel::EditStrategy strategy; + + QSqlQuery editQuery; + QSqlIndex primaryIndex; + QString tableName; + QString filter; + + enum Op { None, Insert, Update, Delete }; + + struct ModifiedRow + { + ModifiedRow(Op o = None, const QSqlRecord &r = QSqlRecord()): op(o), rec(r) {} + ModifiedRow(const ModifiedRow &other): op(other.op), rec(other.rec), primaryValues(other.primaryValues) {} + Op op; + QSqlRecord rec; + QSqlRecord primaryValues; + }; + + QSqlRecord editBuffer; + + typedef QMap<int, ModifiedRow> CacheMap; + CacheMap cache; +}; + +QT_END_NAMESPACE + +#endif // QSQLTABLEMODEL_P_H diff --git a/src/sql/sql.pro b/src/sql/sql.pro new file mode 100644 index 0000000..f089dd7 --- /dev/null +++ b/src/sql/sql.pro @@ -0,0 +1,21 @@ +TARGET = QtSql +QPRO_PWD = $$PWD +QT = core +DEFINES += QT_BUILD_SQL_LIB +DEFINES += QT_NO_USING_NAMESPACE +win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x62000000 + +unix:QMAKE_PKGCONFIG_REQUIRES = QtCore + +include(../qbase.pri) + +DEFINES += QT_NO_CAST_FROM_ASCII +PRECOMPILED_HEADER = ../corelib/global/qt_pch.h +SQL_P = sql + +include(kernel/kernel.pri) +include(drivers/drivers.pri) +include(models/models.pri) + +symbian:TARGET.UID3=0x2001E61D + |