From 0452c25aedb7d2778e5b26fad924ed61b72bbe33 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Fri, 18 Mar 2016 13:35:40 +0100 Subject: Always compile sql drivers as plugins Compiling the drivers into Qt Sql does not make a lot of sense anymore, as we handle plugins well enough in the build system these days. [ChangeLog][Build system] SQL drivers are now always compiled as plugins. Change-Id: I364b82a480849399d1fafe4b20e9f08922569260 Reviewed-by: Oswald Buddenhagen Reviewed-by: Mark Brand --- src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp | 615 ++++++++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp (limited to 'src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp') diff --git a/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp new file mode 100644 index 0000000000..67c24e4168 --- /dev/null +++ b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsql_sqlite2_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined Q_OS_WIN +# include +#endif +#include + +typedef struct sqlite_vm sqlite_vm; + +Q_DECLARE_OPAQUE_POINTER(sqlite_vm*) +Q_DECLARE_METATYPE(sqlite_vm*) + +Q_DECLARE_OPAQUE_POINTER(sqlite*) +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 QSqlDriverPrivate +{ + Q_DECLARE_PUBLIC(QSQLite2Driver) + +public: + QSQLite2DriverPrivate(); + sqlite *access; + bool utf8; +}; + +QSQLite2DriverPrivate::QSQLite2DriverPrivate() : QSqlDriverPrivate(), access(0) +{ + utf8 = (qstrcmp(sqlite_encoding, "UTF-8") == 0); + dbmsType = QSqlDriver::SQLite; +} + +class QSQLite2ResultPrivate; + +class QSQLite2Result : public QSqlCachedResult +{ + Q_DECLARE_PRIVATE(QSQLite2Result) + friend class QSQLite2Driver; + +public: + explicit QSQLite2Result(const QSQLite2Driver* db); + ~QSQLite2Result(); + QVariant handle() const Q_DECL_OVERRIDE; + +protected: + bool gotoNext(QSqlCachedResult::ValueCache &row, int idx) Q_DECL_OVERRIDE; + bool reset(const QString &query) Q_DECL_OVERRIDE; + int size() Q_DECL_OVERRIDE; + int numRowsAffected() Q_DECL_OVERRIDE; + QSqlRecord record() const Q_DECL_OVERRIDE; + void detachFromResultSet() Q_DECL_OVERRIDE; + void virtual_hook(int id, void *data) Q_DECL_OVERRIDE; +}; + +class QSQLite2ResultPrivate: public QSqlCachedResultPrivate +{ + Q_DECLARE_PUBLIC(QSQLite2Result) + +public: + Q_DECLARE_SQLDRIVER_PRIVATE(QSQLite2Driver); + QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv); + 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(); + + // and we have too keep our own struct for the data (sqlite works via + // callback. + const char *currentTail; + sqlite_vm *currentMachine; + + bool skippedStatus; // the status of the fetchNext() that's skipped + bool skipRow; // skip the next fetchNext()? + QSqlRecord rInf; + QVector firstRow; +}; + +QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv) + : QSqlCachedResultPrivate(q, drv), + currentTail(0), + currentMachine(0), + skippedStatus(false), + skipRow(false) +{ +} + +void QSQLite2ResultPrivate::cleanup() +{ + Q_Q(QSQLite2Result); + finalize(); + rInf.clear(); + currentTail = 0; + currentMachine = 0; + skippedStatus = false; + skipRow = false; + q->setAt(QSql::BeforeFirstRow); + q->setActive(false); + q->cleanup(); +} + +void QSQLite2ResultPrivate::finalize() +{ + Q_Q(QSQLite2Result); + 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::fromLatin1(err), + QSqlError::StatementError, res)); + sqlite_freemem(err); + } + currentMachine = 0; +} + +// called on first fetch +void QSQLite2ResultPrivate::init(const char **cnames, int numCols) +{ + Q_Q(QSQLite2Result); + 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]; + + //remove quotations around the field name if any + QString fieldStr = QString::fromLatin1(fieldName); + QLatin1Char quote('\"'); + if ( fieldStr.length() > 2 && fieldStr.startsWith(quote) && fieldStr.endsWith(quote)) { + fieldStr = fieldStr.mid(1); + fieldStr.chop(1); + } + rInf.append(QSqlField(fieldStr, + nameToType(QString::fromLatin1(cnames[i+numCols])))); + } +} + +bool QSQLite2ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch) +{ + Q_Q(QSQLite2Result); + // may be caching. + const char **fvals; + const char **cnames; + int colNum; + int res; + int i; + + if (skipRow) { + // already fetched + Q_ASSERT(!initialFetch); + skipRow = false; + for(int i=0;iutf8 ? QString::fromUtf8(fvals[i]) : QString::fromLatin1(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(*new QSQLite2ResultPrivate(this, db)) +{ +} + +QSQLite2Result::~QSQLite2Result() +{ + Q_D(QSQLite2Result); + d->cleanup(); +} + +void QSQLite2Result::virtual_hook(int id, void *data) +{ + QSqlCachedResult::virtual_hook(id, data); +} + +/* + Execute \a query. +*/ +bool QSQLite2Result::reset (const QString& query) +{ + Q_D(QSQLite2Result); + // 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->drv_d_func()->access, + d->drv_d_func()->utf8 ? query.toUtf8().constData() + : query.toLatin1().constData(), + &(d->currentTail), + &(d->currentMachine), + &err); + if (res != SQLITE_OK || err) { + setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result", + "Unable to execute statement"), QString::fromLatin1(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(d->firstRow, 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) +{ + Q_D(QSQLite2Result); + return d->fetchNext(row, idx, false); +} + +int QSQLite2Result::size() +{ + return -1; +} + +int QSQLite2Result::numRowsAffected() +{ + Q_D(QSQLite2Result); + return sqlite_changes(d->drv_d_func()->access); +} + +QSqlRecord QSQLite2Result::record() const +{ + Q_D(const QSQLite2Result); + if (!isActive() || !isSelect()) + return QSqlRecord(); + return d->rInf; +} + +void QSQLite2Result::detachFromResultSet() +{ + Q_D(QSQLite2Result); + d->finalize(); +} + +QVariant QSQLite2Result::handle() const +{ + Q_D(const QSQLite2Result); + return QVariant::fromValue(d->currentMachine); +} + +///////////////////////////////////////////////////////// + +QSQLite2Driver::QSQLite2Driver(QObject *parent) + : QSqlDriver(*new QSQLite2DriverPrivate, parent) +{ +} + +QSQLite2Driver::QSQLite2Driver(sqlite *connection, QObject *parent) + : QSqlDriver(*new QSQLite2DriverPrivate, parent) +{ + Q_D(QSQLite2Driver); + d->access = connection; + setOpen(true); + setOpenError(false); +} + + +QSQLite2Driver::~QSQLite2Driver() +{ +} + +bool QSQLite2Driver::hasFeature(DriverFeature f) const +{ + Q_D(const QSQLite2Driver); + 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 &) +{ + Q_D(QSQLite2Driver); + 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 opening database"), QString::fromLatin1(err), + QSqlError::ConnectionError)); + sqlite_freemem(err); + err = 0; + } + + if (d->access) { + setOpen(true); + setOpenError(false); + return true; + } + setOpenError(true); + return false; +} + +void QSQLite2Driver::close() +{ + Q_D(QSQLite2Driver); + if (isOpen()) { + sqlite_close(d->access); + d->access = 0; + setOpen(false); + setOpenError(false); + } +} + +QSqlResult *QSQLite2Driver::createResult() const +{ + return new QSQLite2Result(this); +} + +bool QSQLite2Driver::beginTransaction() +{ + Q_D(QSQLite2Driver); + 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::fromLatin1(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::commitTransaction() +{ + Q_D(QSQLite2Driver); + 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::fromLatin1(err), QSqlError::TransactionError, res)); + sqlite_freemem(err); + return false; +} + +bool QSQLite2Driver::rollbackTransaction() +{ + Q_D(QSQLite2Driver); + 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::fromLatin1(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); + QString table = tblname; + if (isIdentifierEscaped(table, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + // finrst find a UNIQUE INDEX + q.exec(QLatin1String("PRAGMA index_list('") + table + 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(table, 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(); + QString table = tbl; + if (isIdentifierEscaped(tbl, QSqlDriver::TableName)) + table = stripDelimiters(table, QSqlDriver::TableName); + + QSqlQuery q(createResult()); + q.setForwardOnly(true); + q.exec(QLatin1String("SELECT * FROM ") + tbl + QLatin1String(" LIMIT 1")); + return q.record(); +} + +QVariant QSQLite2Driver::handle() const +{ + Q_D(const QSQLite2Driver); + return QVariant::fromValue(d->access); +} + +QString QSQLite2Driver::escapeIdentifier(const QString &identifier, IdentifierType /*type*/) const +{ + QString res = identifier; + if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) { + res.replace(QLatin1Char('"'), QLatin1String("\"\"")); + res.prepend(QLatin1Char('"')).append(QLatin1Char('"')); + res.replace(QLatin1Char('.'), QLatin1String("\".\"")); + } + return res; +} + +QT_END_NAMESPACE -- cgit v1.2.3