| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** 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_METATYPE(sqlite3*) |
| Q_DECLARE_METATYPE(sqlite3_stmt*) |
| |
| QT_BEGIN_NAMESPACE |
| |
| static QString _q_escapeIdentifier(const QString &identifier) |
| { |
| 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.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(reinterpret_cast<const QChar *>(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; |
| |
| 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) |
| { |
| } |
| |
| 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))); |
| |
| 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; |
| 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: |
| 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; |
| } |
| |
| 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; |
| 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) |
| { |
| 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(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(); |
| 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: |
| 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()) |
| 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 { |
| 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(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 qVariantFromValue(d->access); |
| } |
| |
| QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const |
| { |
| Q_UNUSED(type); |
| return _q_escapeIdentifier(identifier); |
| } |
| |
| QT_END_NAMESPACE |