• Mark Brand's avatar
    QSqlResult: replace virtual hooks with virtual functions · fc15a1d5
    Mark Brand authored
    
    Instead of virtual functions which would have broken binary
    compatibility, virtual_hook() was used to implement the virtual
    functionality. Now, since the step to Qt 5.0 allows breaking
    binary compatibility, we take the opporunity to simplify the code
    using real virtual functions.
    
    SetNumericalPrecision --> setNumericalPrecisionPolicy()
    NextResult --> nextResult()
    DetachFromResultSet --> detachFromResultSet()
    BatchOperation --> execBatch()
    
    Task-number: QTBUG-25252
    Change-Id: Idd3a870f876d8b8a7457559d5f31ec2073786a75
    Reviewed-by: default avatarBill King <bill.king@nokia.com>
    Reviewed-by: default avatarHarald Fernengel <harald.fernengel@nokia.com>
    fc15a1d5
qsql_sqlite.cpp 22.76 KiB
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the QtSql module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** 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.1, 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.
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
** $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_OPAQUE_POINTER(sqlite3*)
Q_DECLARE_METATYPE(sqlite3*)
Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*)
Q_DECLARE_METATYPE(sqlite3_stmt*)
QT_BEGIN_NAMESPACE
static QString _q_escapeIdentifier(const QString &identifier) 
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
{ 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; } 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 == QLatin1String("real") || typeName.startsWith(QLatin1String("numeric"))) return QVariant::Double; if (typeName == QLatin1String("blob")) return QVariant::ByteArray; if (typeName == QLatin1String("boolean") || typeName == QLatin1String("bool")) return QVariant::Bool; return QVariant::String; } static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type, int errorCode = -1) { return QSqlError(descr, QString(reinterpret_cast<const QChar *>(sqlite3_errmsg16(access))), type, errorCode); } class QSQLiteDriverPrivate { public: inline QSQLiteDriverPrivate() : access(0) {} sqlite3 *access; QList <QSQLiteResult *> results; }; 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; bool skippedStatus; // the status of the fetchNext() that's skipped bool skipRow; // skip the next fetchNext()? QSqlRecord rInf; QVector<QVariant> firstRow; }; QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0), stmt(0), skippedStatus(false), skipRow(false)
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
{ } 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(reinterpret_cast<const QChar *>( sqlite3_column_name16(stmt, i)) ).remove(QLatin1Char('"')); // must use typeName for resolving the type to match QSqliteDriver::record QString typeName = QString(reinterpret_cast<const QChar *>( sqlite3_column_decltype16(stmt, i))); // sqlite3_column_type is documented to have undefined behavior if the result set is empty int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i); QVariant::Type fieldType; if (!typeName.isEmpty()) { fieldType = qGetColumnType(typeName); } else { // Get the proper type for the field based on stp value switch (stp) { case SQLITE_INTEGER: fieldType = QVariant::Int; break; case SQLITE_FLOAT: fieldType = QVariant::Double; break; case SQLITE_BLOB: fieldType = QVariant::ByteArray; break; case SQLITE_TEXT: fieldType = QVariant::String; break; case SQLITE_NULL: default: fieldType = QVariant::Invalid; break; } } int dotIdx = colName.lastIndexOf(QLatin1Char('.')); QSqlField fld(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1), fieldType);
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
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; for(int i=0;i<firstRow.count();i++) values[i]=firstRow[i]; return skippedStatus; } skipRow = initialFetch; if(initialFetch) { firstRow.clear(); firstRow.resize(sqlite3_column_count(stmt)); } 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(q->numericalPrecisionPolicy()) { 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: case QSql::HighPrecision: default: values[i + idx] = sqlite3_column_double(stmt, i); break; }; break; case SQLITE_NULL: values[i + idx] = QVariant(QVariant::String); break; default:
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
values[i + idx] = QString(reinterpret_cast<const QChar *>( sqlite3_column_text16(stmt, i)), sqlite3_column_bytes16(stmt, i) / sizeof(QChar)); 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_CONSTRAINT: 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; db->d->results.append(this); } QSQLiteResult::~QSQLiteResult() { const QSqlDriver *sqlDriver = driver(); if (sqlDriver) qobject_cast<const QSQLiteDriver *>(sqlDriver)->d->results.removeOne(this); d->cleanup(); delete d; } void QSQLiteResult::virtual_hook(int id, void *data) { switch (id) { default: QSqlCachedResult::virtual_hook(id, data); } } bool QSQLiteResult::reset(const QString &query) { if (!prepare(query)) return false; return exec(); } bool QSQLiteResult::prepare(const QString &query) {
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
if (!driver() || !driver()->isOpen() || driver()->isOpenError()) return false; d->cleanup(); setSelect(false); const void *pzTail = NULL; #if (SQLITE_VERSION_NUMBER >= 3003011) int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), &d->stmt, &pzTail); #else int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar), &d->stmt, &pzTail); #endif if (res != SQLITE_OK) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to execute statement"), QSqlError::StatementError, res)); d->finalize(); return false; } else if (pzTail && !QString(reinterpret_cast<const QChar *>(pzTail)).trimmed().isEmpty()) { setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult", "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE)); 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: case QVariant::Bool: 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;
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
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(d->firstRow, 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();
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
return d->rInf; } void QSQLiteResult::detachFromResultSet() { if (d->stmt) sqlite3_reset(d->stmt); } QVariant QSQLiteResult::handle() const { return QVariant::fromValue(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: case LowPrecisionNumbers: return true; case QuerySize: case NamedPlaceholders: case BatchOperations: case EventNotifications: case MultipleResultSets: return false; } return false; } /* 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())
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
return false; bool sharedCache = false; int openMode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, timeOut=5000; QStringList opts=QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';')); foreach(const QString &option, opts) { if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { bool ok; int nt = option.mid(21).toInt(&ok); if (ok) timeOut = nt; } if (option == QLatin1String("QSQLITE_OPEN_READONLY")) openMode = SQLITE_OPEN_READONLY; if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) sharedCache = true; } sqlite3_enable_shared_cache(sharedCache); if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) { sqlite3_busy_timeout(d->access, timeOut); setOpen(true); setOpenError(false); return true; } else { if (d->access) { sqlite3_close(d->access); d->access = 0; } setLastError(qMakeError(d->access, tr("Error opening database"), QSqlError::ConnectionError)); setOpenError(true); return false; } } void QSQLiteDriver::close() { if (isOpen()) { foreach (QSQLiteResult *result, d->results) { result->d->finalize(); } 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; }
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
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);
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
int indexOfSeparator = tableName.indexOf(QLatin1Char('.')); if (indexOfSeparator > -1) { schema = tableName.left(indexOfSeparator).append(QLatin1Char('.')); table = tableName.mid(indexOfSeparator + 1); } q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(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(); QString table = tblname; if (isIdentifierEscaped(table, QSqlDriver::TableName)) table = stripDelimiters(table, QSqlDriver::TableName); QSqlQuery q(createResult()); q.setForwardOnly(true); return qGetTableInfo(q, table, true); } QSqlRecord QSQLiteDriver::record(const QString &tbl) const { if (!isOpen()) return QSqlRecord(); QString table = tbl; if (isIdentifierEscaped(table, QSqlDriver::TableName)) table = stripDelimiters(table, QSqlDriver::TableName); QSqlQuery q(createResult()); q.setForwardOnly(true); return qGetTableInfo(q, table); } QVariant QSQLiteDriver::handle() const { return QVariant::fromValue(d->access); } QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const { Q_UNUSED(type); return _q_escapeIdentifier(identifier); } QT_END_NAMESPACE