diff options
Diffstat (limited to 'src/libs/sqlite/sqlitestatement.cpp')
-rw-r--r-- | src/libs/sqlite/sqlitestatement.cpp | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/src/libs/sqlite/sqlitestatement.cpp b/src/libs/sqlite/sqlitestatement.cpp new file mode 100644 index 0000000000..4b8e3338a4 --- /dev/null +++ b/src/libs/sqlite/sqlitestatement.cpp @@ -0,0 +1,676 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing +** +** This file is part of Qt Creator. +** +** 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 Digia. For licensing terms and +** conditions see http://www.qt.io/licensing. For further information +** use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "sqlitestatement.h" + +#include <QMutex> +#include <QWaitCondition> +#include <QVariant> +#include <QtGlobal> + +#include "sqlite3.h" + +#include "sqlitedatabasebackend.h" +#include "sqliteexception.h" + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + +SqliteStatement::SqliteStatement(const Utf8String &sqlStatementUtf8) + : compiledStatement(nullptr, deleteCompiledStatement), + bindingParameterCount(0), + columnCount_(0), + isReadyToFetchValues(false) +{ + prepare(sqlStatementUtf8); + setBindingParameterCount(); + setBindingColumnNamesFromStatement(); + setColumnCount(); +} + +void SqliteStatement::deleteCompiledStatement(sqlite3_stmt *compiledStatement) +{ + if (compiledStatement) + sqlite3_finalize(compiledStatement); +} + +class UnlockNotification { + +public: + UnlockNotification() : fired(false) {}; + + static void unlockNotifyCallBack(void **arguments, int argumentCount) + { + for (int index = 0; index < argumentCount; index++) { + UnlockNotification *unlockNotification = static_cast<UnlockNotification *>(arguments[index]); + unlockNotification->wakeupWaitCondition(); + } + } + + void wakeupWaitCondition() + { + mutex.lock(); + fired = 1; + waitCondition.wakeAll(); + mutex.unlock(); + } + + void wait() + { + mutex.lock(); + + if (!fired) { + waitCondition.wait(&mutex); + } + + mutex.unlock(); + } + +private: + bool fired; + QWaitCondition waitCondition; + QMutex mutex; +}; + +void SqliteStatement::waitForUnlockNotify() const +{ + UnlockNotification unlockNotification; + int resultCode = sqlite3_unlock_notify(sqliteDatabaseHandle(), UnlockNotification::unlockNotifyCallBack, &unlockNotification); + + if (resultCode == SQLITE_OK) + unlockNotification.wait(); + else + throwException("SqliteStatement::waitForUnlockNotify: database is in a dead lock!"); +} + +void SqliteStatement::reset() const +{ + int resultCode = sqlite3_reset(compiledStatement.get()); + if (resultCode != SQLITE_OK) + throwException("SqliteStatement::reset: can't reset statement!"); + + isReadyToFetchValues = false; +} + +bool SqliteStatement::next() const +{ + int resultCode; + + do { + resultCode = sqlite3_step(compiledStatement.get()); + if (resultCode == SQLITE_LOCKED) { + waitForUnlockNotify(); + sqlite3_reset(compiledStatement.get()); + } + + } while (resultCode == SQLITE_LOCKED); + + setIfIsReadyToFetchValues(resultCode); + + return checkForStepError(resultCode); +} + +void SqliteStatement::step() const +{ + next(); +} + +void SqliteStatement::write(const RowDictionary &rowDictionary) +{ + bind(rowDictionary); + step(); + reset(); +} + +void SqliteStatement::writeUnchecked(const RowDictionary &rowDictionary) +{ + bindUnchecked(rowDictionary); + step(); + reset(); +} + +int SqliteStatement::columnCount() const +{ + return columnCount_; +} + +Utf8StringVector SqliteStatement::columnNames() const +{ + Utf8StringVector columnNames; + int columnCount = SqliteStatement::columnCount(); + columnNames.reserve(columnCount); + for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) + columnNames.append(Utf8String(sqlite3_column_origin_name(compiledStatement.get(), columnIndex), -1)); + + return columnNames; +} + +void SqliteStatement::bind(int index, int value) +{ + int resultCode = sqlite3_bind_int(compiledStatement.get(), index, value); + if (resultCode != SQLITE_OK) + throwException("SqliteStatement::bind: cant' bind 32 bit integer!"); +} + +void SqliteStatement::bind(int index, qint64 value) +{ + int resultCode = sqlite3_bind_int64(compiledStatement.get(), index, value); + if (resultCode != SQLITE_OK) + throwException("SqliteStatement::bind: cant' bind 64 bit integer!"); +} + +void SqliteStatement::bind(int index, double value) +{ + int resultCode = sqlite3_bind_double(compiledStatement.get(), index, value); + if (resultCode != SQLITE_OK) + throwException("SqliteStatement::bind: cant' bind double!"); +} + +void SqliteStatement::bind(int index, const QString &text) +{ + int resultCode; + if (databaseTextEncoding() == Utf8) { + QByteArray textUtf8 = text.toUtf8(); + resultCode = sqlite3_bind_text(compiledStatement.get(), index, textUtf8.constData(), textUtf8.size(), SQLITE_TRANSIENT); + } else { + resultCode = sqlite3_bind_text16(compiledStatement.get(), index, text.constData(), text.size() * 2, SQLITE_TRANSIENT); + } + + if (resultCode != SQLITE_OK) + throwException("SqliteStatement::bind: cant' not bind text!"); +} + +void SqliteStatement::bind(int index, const QByteArray &blob) +{ + sqlite3_bind_blob(compiledStatement.get(), index, blob.constData(), blob.size(), SQLITE_TRANSIENT); +} + +void SqliteStatement::bind(int index, const QVariant &value) +{ + checkBindingIndex(index); + + switch (value.type()) { + case QVariant::Bool: + case QVariant::Int: + bind(index, value.toInt()); + break; + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + bind(index, value.toLongLong()); + break; + case QVariant::Double: + bind(index, value.toDouble()); + break; + case QVariant::String: + bind(index, value.toString()); + break; + case QVariant::ByteArray: + bind(index, value.toByteArray()); + break; + default: + sqlite3_bind_null(compiledStatement.get(), index); + } +} + +template <typename Type> +void SqliteStatement::bind(const Utf8String &name, const Type &value) +{ + int index = bindingIndexForName(name); + checkBindingName(index); + bind(index, value); +} + +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const int &value); +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const qint64 &value); +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const double &value); +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const QString &text); +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const QByteArray &blob); +template SQLITE_EXPORT void SqliteStatement::bind(const Utf8String &name, const QVariant &value); + +int SqliteStatement::bindingIndexForName(const Utf8String &name) +{ + return sqlite3_bind_parameter_index(compiledStatement.get(), name.constData()); +} + +void SqliteStatement::bind(const RowDictionary &rowDictionary) +{ + checkBindingValueMapIsEmpty(rowDictionary); + + int columnIndex = 1; + foreach (const Utf8String &columnName, bindingColumnNames_) { + checkParameterCanBeBound(rowDictionary, columnName); + QVariant value = rowDictionary.value(columnName); + bind(columnIndex, value); + columnIndex += 1; + } +} + +void SqliteStatement::bindUnchecked(const RowDictionary &rowDictionary) +{ + checkBindingValueMapIsEmpty(rowDictionary); + + int columnIndex = 1; + foreach (const Utf8String &columnName, bindingColumnNames_) { + if (rowDictionary.contains(columnName)) { + QVariant value = rowDictionary.value(columnName); + bind(columnIndex, value); + } + columnIndex += 1; + } +} + +void SqliteStatement::setBindingColumnNames(const Utf8StringVector &bindingColumnNames) +{ + bindingColumnNames_ = bindingColumnNames; +} + +const Utf8StringVector &SqliteStatement::bindingColumnNames() const +{ + return bindingColumnNames_; +} + +void SqliteStatement::execute(const Utf8String &sqlStatementUtf8) +{ + SqliteStatement statement(sqlStatementUtf8); + statement.step(); +} + +void SqliteStatement::prepare(const Utf8String &sqlStatementUtf8) +{ + int resultCode; + + do { + sqlite3_stmt *sqliteStatement = nullptr; + resultCode = sqlite3_prepare_v2(sqliteDatabaseHandle(), sqlStatementUtf8.constData(), sqlStatementUtf8.byteSize(), &sqliteStatement, nullptr); + compiledStatement.reset(sqliteStatement); + + if (resultCode == SQLITE_LOCKED) + waitForUnlockNotify(); + + } while (resultCode == SQLITE_LOCKED); + + checkForPrepareError(resultCode); +} + +sqlite3 *SqliteStatement::sqliteDatabaseHandle() +{ +return SqliteDatabaseBackend::sqliteDatabaseHandle(); +} + +TextEncoding SqliteStatement::databaseTextEncoding() +{ + if (SqliteDatabaseBackend::threadLocalInstance()) + return SqliteDatabaseBackend::threadLocalInstance()->textEncoding(); + + throwException("SqliteStatement::databaseTextEncoding: database backend instance is null!"); + + Q_UNREACHABLE(); +} + +bool SqliteStatement::checkForStepError(int resultCode) const +{ + switch (resultCode) { + case SQLITE_ROW: return true; + case SQLITE_DONE: return false; + case SQLITE_BUSY: throwException("SqliteStatement::stepStatement: database engine was unable to acquire the database locks!"); + case SQLITE_ERROR : throwException("SqliteStatement::stepStatement: run-time error (such as a constraint violation) has occurred!"); + case SQLITE_MISUSE: throwException("SqliteStatement::stepStatement: was called inappropriately!"); + case SQLITE_CONSTRAINT: throwException("SqliteStatement::stepStatement: contraint prevent insert or update!"); + } + + throwException("SqliteStatement::stepStatement: unknown error has happen!"); + + Q_UNREACHABLE(); +} + +void SqliteStatement::checkForPrepareError(int resultCode) const +{ + switch (resultCode) { + case SQLITE_OK: return; + case SQLITE_BUSY: throwException("SqliteStatement::prepareStatement: database engine was unable to acquire the database locks!"); + case SQLITE_ERROR : throwException("SqliteStatement::prepareStatement: run-time error (such as a constraint violation) has occurred!"); + case SQLITE_MISUSE: throwException("SqliteStatement::prepareStatement: was called inappropriately!"); + } + + throwException("SqliteStatement::prepareStatement: unknown error has happen!"); +} + +void SqliteStatement::setIfIsReadyToFetchValues(int resultCode) const +{ + if (resultCode == SQLITE_ROW) + isReadyToFetchValues = true; + else + isReadyToFetchValues = false; + +} + +void SqliteStatement::checkIfIsReadyToFetchValues() const +{ + if (!isReadyToFetchValues) + throwException("SqliteStatement::value: there are no values to fetch!"); +} + +void SqliteStatement::checkColumnsAreValid(const QVector<int> &columns) const +{ + foreach (int column, columns) { + if (column < 0 || column >= columnCount_) + throwException("SqliteStatement::values: column index out of bound!"); + } +} + +void SqliteStatement::checkColumnIsValid(int column) const +{ + if (column < 0 || column >= columnCount_) + throwException("SqliteStatement::values: column index out of bound!"); +} + +void SqliteStatement::checkBindingIndex(int index) const +{ + if (index <= 0 || index > bindingParameterCount) + throwException("SqliteStatement::bind: binding index is out of bound!"); +} + +void SqliteStatement::checkBindingName(int index) const +{ + if (index <= 0 || index > bindingParameterCount) + throwException("SqliteStatement::bind: binding name are not exists in this statement!"); +} + +void SqliteStatement::checkParameterCanBeBound(const RowDictionary &rowDictionary, const Utf8String &columnName) +{ + if (!rowDictionary.contains(columnName)) + throwException("SqliteStatement::bind: Not all parameters are bound!"); +} + +void SqliteStatement::setBindingParameterCount() +{ + bindingParameterCount = sqlite3_bind_parameter_count(compiledStatement.get()); +} + +Utf8String chopFirstLetter(const char *rawBindingName) +{ + QByteArray bindingName(rawBindingName); + bindingName = bindingName.mid(1); + + return Utf8String::fromByteArray(bindingName); +} + +void SqliteStatement::setBindingColumnNamesFromStatement() +{ + for (int index = 1; index <= bindingParameterCount; index++) { + Utf8String bindingName = chopFirstLetter(sqlite3_bind_parameter_name(compiledStatement.get(), index)); + bindingColumnNames_.append(bindingName); + } +} + +void SqliteStatement::setColumnCount() +{ + columnCount_ = sqlite3_column_count(compiledStatement.get()); +} + +void SqliteStatement::checkBindingValueMapIsEmpty(const RowDictionary &rowDictionary) const +{ + if (rowDictionary.isEmpty()) + throwException("SqliteStatement::bind: can't bind empty row!"); +} + +bool SqliteStatement::isReadOnlyStatement() const +{ + return sqlite3_stmt_readonly(compiledStatement.get()); +} + +void SqliteStatement::throwException(const char *whatHasHappened) +{ + throw SqliteException(whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())); +} + +QString SqliteStatement::columnName(int column) const +{ + return QString::fromUtf8(sqlite3_column_name(compiledStatement.get(), column)); +} + +static bool columnIsBlob(sqlite3_stmt *sqlStatment, int column) +{ + return sqlite3_column_type(sqlStatment, column) == SQLITE_BLOB; +} + +static QByteArray byteArrayForColumn(sqlite3_stmt *sqlStatment, int column) +{ + if (columnIsBlob(sqlStatment, column)) { + const char *blob = static_cast<const char*>(sqlite3_column_blob(sqlStatment, column)); + int size = sqlite3_column_bytes(sqlStatment, column); + + return QByteArray(blob, size); + } + + return QByteArray(); +} + +static QString textForColumn(sqlite3_stmt *sqlStatment, int column) +{ + const QChar *text = static_cast<const QChar*>(sqlite3_column_text16(sqlStatment, column)); + int size = sqlite3_column_bytes16(sqlStatment, column) / 2; + + return QString(text, size); +} + +static Utf8String utf8TextForColumn(sqlite3_stmt *sqlStatment, int column) +{ + const char *text = reinterpret_cast<const char*>(sqlite3_column_text(sqlStatment, column)); + int size = sqlite3_column_bytes(sqlStatment, column); + + return Utf8String(text, size); +} + + +static Utf8String convertedToUtf8StringForColumn(sqlite3_stmt *sqlStatment, int column) +{ + int dataType = sqlite3_column_type(sqlStatment, column); + switch (dataType) { + case SQLITE_INTEGER: return Utf8String::fromByteArray(QByteArray::number(sqlite3_column_int64(sqlStatment, column))); + case SQLITE_FLOAT: return Utf8String::fromByteArray(QByteArray::number(sqlite3_column_double(sqlStatment, column))); + case SQLITE_BLOB: return Utf8String(); + case SQLITE3_TEXT: return utf8TextForColumn(sqlStatment, column); + case SQLITE_NULL: return Utf8String(); + } + + Q_UNREACHABLE(); +} + + +static QVariant variantForColumn(sqlite3_stmt *sqlStatment, int column) +{ + int dataType = sqlite3_column_type(sqlStatment, column); + switch (dataType) { + case SQLITE_INTEGER: return QVariant::fromValue(sqlite3_column_int64(sqlStatment, column)); + case SQLITE_FLOAT: return QVariant::fromValue(sqlite3_column_double(sqlStatment, column)); + case SQLITE_BLOB: return QVariant::fromValue(byteArrayForColumn(sqlStatment, column)); + case SQLITE3_TEXT: return QVariant::fromValue(textForColumn(sqlStatment, column)); + case SQLITE_NULL: return QVariant(); + } + + Q_UNREACHABLE(); +} + +template<> +int SqliteStatement::value<int>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return sqlite3_column_int(compiledStatement.get(), column); +} + +template<> +qint64 SqliteStatement::value<qint64>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return sqlite3_column_int64(compiledStatement.get(), column); +} + +template<> +double SqliteStatement::value<double>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return sqlite3_column_double(compiledStatement.get(), column); +} + +template<> +QByteArray SqliteStatement::value<QByteArray>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return byteArrayForColumn(compiledStatement.get(), column); +} + +template<> +Utf8String SqliteStatement::value<Utf8String>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return convertedToUtf8StringForColumn(compiledStatement.get(), column); +} + +template<> +QString SqliteStatement::value<QString>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return textForColumn(compiledStatement.get(), column); +} + +template<> +QVariant SqliteStatement::value<QVariant>(int column) const +{ + checkIfIsReadyToFetchValues(); + checkColumnIsValid(column); + return variantForColumn(compiledStatement.get(), column); +} + +template <typename ContainerType> + ContainerType SqliteStatement::columnValues(const QVector<int> &columnIndices) const +{ + typedef typename ContainerType::value_type ElementType; + ContainerType valueContainer; + valueContainer.reserve(columnIndices.count()); + for (int columnIndex : columnIndices) + valueContainer += value<ElementType>(columnIndex); + + return valueContainer; +} + +QMap<QString, QVariant> SqliteStatement::rowColumnValueMap() const +{ + QMap<QString, QVariant> values; + + reset(); + + if (next()) { + for (int column = 0; column < columnCount(); column++) + values.insert(columnName(column), variantForColumn(compiledStatement.get(), column)); + } + + return values; +} + +QMap<QString, QVariant> SqliteStatement::twoColumnValueMap() const +{ + QMap<QString, QVariant> values; + + reset(); + + while (next()) + values.insert(textForColumn(compiledStatement.get(), 0), variantForColumn(compiledStatement.get(), 1)); + + return values; +} + +template <typename ContainerType> +ContainerType SqliteStatement::values(const QVector<int> &columns, int size) const +{ + checkColumnsAreValid(columns); + + ContainerType resultValues; + resultValues.reserve(size); + + reset(); + + while (next()) { + resultValues += columnValues<ContainerType>(columns); + } + + return resultValues; +} + +template SQLITE_EXPORT QVector<QVariant> SqliteStatement::values<QVector<QVariant>>(const QVector<int> &columnIndices, int size) const; +template SQLITE_EXPORT QVector<Utf8String> SqliteStatement::values<QVector<Utf8String>>(const QVector<int> &columnIndices, int size) const; + +template <typename ContainerType> +ContainerType SqliteStatement::values(int column) const +{ + typedef typename ContainerType::value_type ElementType; + ContainerType resultValues; + + reset(); + + while (next()) { + resultValues += value<ElementType>(column); + } + + return resultValues; +} + +template SQLITE_EXPORT QVector<qint64> SqliteStatement::values<QVector<qint64>>(int column) const; +template SQLITE_EXPORT QVector<double> SqliteStatement::values<QVector<double>>(int column) const; +template SQLITE_EXPORT QVector<QByteArray> SqliteStatement::values<QVector<QByteArray>>(int column) const; +template SQLITE_EXPORT Utf8StringVector SqliteStatement::values<Utf8StringVector>(int column) const; +template SQLITE_EXPORT QVector<QString> SqliteStatement::values<QVector<QString>>(int column) const; + +template <typename Type> +Type SqliteStatement::toValue(const Utf8String &sqlStatementUtf8) +{ + SqliteStatement statement(sqlStatementUtf8); + + statement.next(); + + return statement.value<Type>(0); +} + +template SQLITE_EXPORT int SqliteStatement::toValue<int>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT qint64 SqliteStatement::toValue<qint64>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT double SqliteStatement::toValue<double>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT QString SqliteStatement::toValue<QString>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT QByteArray SqliteStatement::toValue<QByteArray>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT Utf8String SqliteStatement::toValue<Utf8String>(const Utf8String &sqlStatementUtf8); +template SQLITE_EXPORT QVariant SqliteStatement::toValue<QVariant>(const Utf8String &sqlStatementUtf8); + |