/**************************************************************************** ** ** 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 override; protected: bool gotoNext(QSqlCachedResult::ValueCache &row, int idx) override; bool reset(const QString &query) override; int size() override; int numRowsAffected() override; QSqlRecord record() const override; void detachFromResultSet() override; void virtual_hook(int id, void *data) 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 != -1 ? QString::number(res) : QString())); 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, tblname)); } 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