src/sql/drivers/sqlite/qsql_sqlite.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sql/drivers/sqlite/qsql_sqlite.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,714 @@
+/****************************************************************************
+**
+** 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 <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::fromUtf16(static_cast<const ushort *>(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;
+
+    uint skippedStatus: 1; // the status of the fetchNext() that's skipped
+    uint skipRow: 1; // skip the next fetchNext()?
+    uint utf8: 1;
+    QSqlRecord rInf;
+};
+
+QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult* res) : q(res), access(0),
+    stmt(0), skippedStatus(false), skipRow(false), utf8(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<const ushort *>(sqlite3_column_name16(stmt, i))
+                    ).remove(QLatin1Char('"'));
+
+        // must use typeName for resolving the type to match QSqliteDriver::record
+        QString typeName = QString::fromUtf16(
+                    static_cast<const ushort *>(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;
+        return skippedStatus;
+    }
+    skipRow = initialFetch;
+
+    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:
+                        values[i + idx] = sqlite3_column_double(stmt, i);
+                        break;
+                    case QSql::HighPrecision:
+                    default:
+                        values[i + idx] = QString::fromUtf16(static_cast<const ushort *>(
+                                            sqlite3_column_text16(stmt, i)),
+                                            sqlite3_column_bytes16(stmt, i) / sizeof(ushort));
+                        break;
+                };
+                break;
+            case SQLITE_NULL:
+                values[i + idx] = QVariant(QVariant::String);
+                break;
+            default:
+                values[i + idx] = QString::fromUtf16(static_cast<const ushort *>(
+                            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<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(cache(), 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