aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/sqlite/sqlitestatement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/sqlite/sqlitestatement.cpp')
-rw-r--r--src/libs/sqlite/sqlitestatement.cpp676
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);
+