/**************************************************************************** ** ** Copyright (C) 2009 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$ ** 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 Technology Preview License Agreement accompanying ** this package. ** ** 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.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsql_sqlite.h" #include #include #include #include #include #include #include #include #include #if defined Q_OS_WIN # include #else # include #endif #include 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::fromUtf16(static_cast(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 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::fromUtf16( static_cast(sqlite3_column_name16(stmt, i)) ).remove(QLatin1Char('"')); // must use typeName for resolving the type to match QSqliteDriver::record QString typeName = QString::fromUtf16( static_cast(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;isetLastError(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( 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::fromUtf16(static_cast( 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; 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 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(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(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; } static int qGetSqliteTimeout(QString opts) { enum { DefaultTimeout = 5000 }; opts.remove(QLatin1Char(' ')); foreach(QString option, opts.split(QLatin1Char(';'))) { if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { bool ok; int nt = option.mid(21).toInt(&ok); if (ok) return nt; } } return DefaultTimeout; } static int qGetSqliteOpenMode(QString opts) { opts.remove(QLatin1Char(' ')); foreach(QString option, opts.split(QLatin1Char(';'))) { if (option == QLatin1String("QSQLITE_OPEN_READONLY")) return SQLITE_OPEN_READONLY; } return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; } /* 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_open_v2(db.toUtf8().constData(), &d->access, qGetSqliteOpenMode(conOpts), NULL) == 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(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