// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "sqlite3_fwd.h" #include "sqliteglobal.h" #include "sourcelocation.h" #include "sqliteblob.h" #include "sqliteexception.h" #include "sqliteids.h" #include "sqlitetracing.h" #include "sqlitetransaction.h" #include "sqlitevalue.h" #include #include #include #include #include #include #include #include #include #include #include using std::int64_t; namespace Sqlite { class Database; class DatabaseBackend; enum class Type : char { Invalid, Integer, Float, Text, Blob, Null }; class SQLITE_EXPORT BaseStatement { public: using Database = ::Sqlite::Database; explicit BaseStatement(Utils::SmallStringView sqlStatement, Database &database, const source_location &sourceLocation); BaseStatement(const BaseStatement &) = delete; BaseStatement &operator=(const BaseStatement &) = delete; BaseStatement(BaseStatement &&) = default; bool next(const source_location &sourceLocation) const; void step(const source_location &sourceLocation) const; void reset() const noexcept; Type fetchType(int column) const; int fetchIntValue(int column) const; long fetchLongValue(int column) const; long long fetchLongLongValue(int column) const; double fetchDoubleValue(int column) const; Utils::SmallStringView fetchSmallStringViewValue(int column) const; ValueView fetchValueView(int column) const; BlobView fetchBlobValue(int column) const; template Type fetchValue(int column) const; void bindNull(int index, const source_location &sourceLocation); void bind(int index, NullValue, const source_location &sourceLocation); void bind(int index, int value, const source_location &sourceLocation); void bind(int index, long long value, const source_location &sourceLocation); void bind(int index, double value, const source_location &sourceLocation); void bind(int index, void *pointer, const source_location &sourceLocation); void bind(int index, Utils::span values, const source_location &sourceLocation); void bind(int index, Utils::span values, const source_location &sourceLocation); void bind(int index, Utils::span values, const source_location &sourceLocation); void bind(int index, Utils::span values, const source_location &sourceLocation); void bind(int index, Utils::SmallStringView value, const source_location &sourceLocation); void bind(int index, const Value &value, const source_location &sourceLocation); void bind(int index, ValueView value, const source_location &sourceLocation); void bind(int index, BlobView blobView, const source_location &sourceLocation); template = true> void bind(int index, Type id, const source_location &sourceLocation) { if (!id.isNull()) bind(index, id.internalId(), sourceLocation); else bindNull(index, sourceLocation); } template, bool> = true> void bind(int index, Enumeration enumeration, const source_location &sourceLocation) { bind(index, Utils::to_underlying(enumeration), sourceLocation); } void bind(int index, uint value, const source_location &sourceLocation) { bind(index, static_cast(value), sourceLocation); } void bind(int index, long value, const source_location &sourceLocation) { bind(index, static_cast(value), sourceLocation); } void prepare(Utils::SmallStringView sqlStatement, const source_location &sourceLocation); void waitForUnlockNotify(const source_location &sourceLocation) const; sqlite3 *sqliteDatabaseHandle(const source_location &sourceLocation) const; void setIfIsReadyToFetchValues(int resultCode) const; void checkBindingName(int index) const; void checkBindingParameterCount(int bindingParameterCount, const source_location &sourceLocation) const; void checkColumnCount(int columnCount, const source_location &sourceLocation) const; bool isReadOnlyStatement() const; QString columnName(int column) const; Database &database() const { return m_database; } protected: ~BaseStatement() = default; std::uintptr_t handle() const { return reinterpret_cast(m_compiledStatement.get()); } private: struct Deleter { SQLITE_EXPORT void operator()(sqlite3_stmt *statement); }; private: std::unique_ptr m_compiledStatement; Database &m_database; }; template<> SQLITE_EXPORT int BaseStatement::fetchValue(int column) const; template<> SQLITE_EXPORT long BaseStatement::fetchValue(int column) const; template<> SQLITE_EXPORT long long BaseStatement::fetchValue(int column) const; template<> SQLITE_EXPORT double BaseStatement::fetchValue(int column) const; extern template SQLITE_EXPORT Utils::SmallStringView BaseStatement::fetchValue( int column) const; extern template SQLITE_EXPORT Utils::SmallString BaseStatement::fetchValue( int column) const; extern template SQLITE_EXPORT Utils::PathString BaseStatement::fetchValue( int column) const; template class StatementImplementation : public BaseStatement { struct Resetter; public: using BaseStatement::BaseStatement; StatementImplementation(StatementImplementation &&) = default; void execute(const source_location &sourceLocation = source_location::current()) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{ "execute", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle()), }; Resetter resetter{this}; BaseStatement::next(sourceLocation); } template void bindValues(const source_location &sourceLocation, const ValueType &...values) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"bind", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; static_assert(BindParameterCount == sizeof...(values), "Wrong binding parameter count!"); int index = 0; (BaseStatement::bind(++index, values, sourceLocation), ...); } template void write(const source_location &sourceLocation, const ValueType &...values) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"write", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; bindValues(sourceLocation, values...); BaseStatement::next(sourceLocation); } template void write(const ValueType &...values) { static constexpr auto sourceLocation = source_location::current(); write(sourceLocation, values...); } template struct is_container : std::false_type {}; template struct is_container> : std::true_type {}; template struct is_container> : std::true_type {}; template struct is_container> : std::true_type {}; template struct is_small_container : std::false_type {}; template struct is_small_container> : std::true_type {}; template::value, bool> = true, typename... QueryTypes> auto values(const source_location &sourceLocation, const QueryTypes &...queryValues) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"values", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; Container resultValues; using size_tupe = typename Container::size_type; if constexpr (!is_small_container::value) resultValues.reserve(static_cast(std::max(capacity, m_maximumResultCount))); bindValues(sourceLocation, queryValues...); while (BaseStatement::next(sourceLocation)) emplaceBackValues(resultValues); setMaximumResultCount(static_cast(resultValues.size())); return resultValues; } template::value, bool> = true, typename... QueryTypes> auto values(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return value(sourceLocation, queryValues...); } template typename Container = std::vector, typename std::enable_if_t::value, bool> = true, typename... QueryTypes> auto values(const source_location &sourceLocation, const QueryTypes &...queryValues) { return values, capacity>(sourceLocation, queryValues...); } template typename Container = std::vector, typename std::enable_if_t::value, bool> = true, typename... QueryTypes> auto values(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return values(sourceLocation, queryValues...); } template auto value(const source_location &sourceLocation, const QueryTypes &...queryValues) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"value", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; ResultType resultValue{}; bindValues(sourceLocation, queryValues...); if (BaseStatement::next(sourceLocation)) resultValue = createValue(); return resultValue; } template auto value(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return value(sourceLocation, queryValues...); } template auto optionalValue(const source_location &sourceLocation, const QueryTypes &...queryValues) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"optionalValue", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; std::optional resultValue; bindValues(sourceLocation, queryValues...); if (BaseStatement::next(sourceLocation)) resultValue = createOptionalValue>(); return resultValue; } template auto optionalValue(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return optionalValue(sourceLocation, queryValues...); } template static auto toValue(Utils::SmallStringView sqlStatement, Database &database, const source_location &sourceLocation = source_location::current()) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"toValue", sqliteHighLevelCategory()}; StatementImplementation statement(sqlStatement, database, sourceLocation); statement.checkColumnCount(1, sourceLocation); statement.next(sourceLocation); return statement.template fetchValue(0); } template void readCallback(Callable &&callable, const source_location &sourceLocation, const QueryTypes &...queryValues) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"readCallback", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; bindValues(sourceLocation, queryValues...); while (BaseStatement::next(sourceLocation)) { auto control = callCallable(callable); if (control == CallbackControl::Abort) break; } } template void readCallback(Callable &&callable, const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); readCallback(callable, sourceLocation, queryValues...); } template void readTo(Container &container, const source_location &sourceLocation, const QueryTypes &...queryValues) { using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"readTo", sqliteHighLevelCategory(), keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; bindValues(sourceLocation, queryValues...); while (BaseStatement::next(sourceLocation)) emplaceBackValues(container); } template void readTo(Container &container, const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); readTo(container, sourceLocation, queryValues...); } template auto range(const source_location &sourceLocation, const QueryTypes &...queryValues) { return SqliteResultRange{*this, sourceLocation, queryValues...}; } template auto range(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return SqliteResultRange{*this, sourceLocation, queryValues...}; } template auto rangeWithTransaction(const source_location &sourceLocation, const QueryTypes &...queryValues) { return SqliteResultRangeWithTransaction{*this, sourceLocation, queryValues...}; } template auto rangeWithTransaction(const QueryTypes &...queryValues) { static constexpr auto sourceLocation = source_location::current(); return SqliteResultRangeWithTransaction{*this, sourceLocation, queryValues...}; } template class BaseSqliteResultRange { public: class SqliteResultSentinel {}; class SqliteResultIteratator { public: using iterator_category = std::input_iterator_tag; using difference_type = int; using value_type = ResultType; using pointer = ResultType *; using reference = ResultType &; SqliteResultIteratator() = default; SqliteResultIteratator(StatementImplementation &statement, const source_location &sourceLocation) : m_statement{&statement} , m_sourceLocation{&sourceLocation} , m_hasNext{m_statement->next(sourceLocation)} {} SqliteResultIteratator(StatementImplementation &statement, const source_location &sourceLocation, bool hasNext) : m_statement{&statement} , m_sourceLocation{&sourceLocation} , m_hasNext{hasNext} {} SqliteResultIteratator(const SqliteResultIteratator &) = delete; SqliteResultIteratator &operator=(const SqliteResultIteratator &) = delete; SqliteResultIteratator(SqliteResultIteratator &&other) noexcept : m_statement{other.m_statement} , m_sourceLocation{other.m_sourceLocation} , m_hasNext{std::exchange(other.m_hasNext, false)} {} SqliteResultIteratator &operator=(SqliteResultIteratator &&other) noexcept { m_statement = other.m_statement; m_sourceLocation = other.m_sourceLocation; m_hasNext = std::exchange(other.m_hasNext, false); } SqliteResultIteratator &operator++() { m_hasNext = m_statement->next(*m_sourceLocation); return *this; } friend bool operator==(const SqliteResultIteratator &first, SqliteResultSentinel) { return !first.m_hasNext; } friend bool operator!=(const SqliteResultIteratator &first, SqliteResultSentinel) { return first.m_hasNext; } void operator++(int) { m_hasNext = m_statement->next(*m_sourceLocation); } value_type operator*() const { return m_statement->createValue(); } public: StatementImplementation *m_statement = nullptr; const source_location *m_sourceLocation = nullptr; bool m_hasNext = false; }; using value_type = ResultType; using iterator = SqliteResultIteratator; using const_iterator = iterator; template BaseSqliteResultRange(StatementImplementation &statement, const source_location &sourceLocation) : m_statement{statement} , m_sourceLocation{sourceLocation} {} BaseSqliteResultRange(const BaseSqliteResultRange &) = default; BaseSqliteResultRange(BaseSqliteResultRange &&) = default; BaseSqliteResultRange &operator=(const BaseSqliteResultRange &) = default; BaseSqliteResultRange &operator=(BaseSqliteResultRange &&) = default; iterator begin() & { return iterator{m_statement, m_sourceLocation}; } auto end() & { return SqliteResultSentinel{}; } const_iterator begin() const & { return iterator{m_statement}; } auto end() const & { return SqliteResultSentinel{}; } private: using TracerCategory = std::decay_t; StatementImplementation &m_statement; const source_location &m_sourceLocation; NanotraceHR::Tracer tracer{ "range", sqliteHighLevelCategory(), NanotraceHR::keyValue("sqlite statement", m_statement.handle())}; }; template class SqliteResultRange : public BaseSqliteResultRange { public: template SqliteResultRange(StatementImplementation &statement, const source_location &sourceLocation, const QueryTypes &...queryValues) : BaseSqliteResultRange{statement, sourceLocation} , resetter{&statement} { statement.bindValues(sourceLocation, queryValues...); } private: Resetter resetter; }; template class SqliteResultRangeWithTransaction : public BaseSqliteResultRange { public: template SqliteResultRangeWithTransaction(StatementImplementation &statement, const source_location &sourceLocation, const QueryTypes &...queryValues) : BaseSqliteResultRange{statement, sourceLocation} , m_transaction{statement.database(), sourceLocation} , resetter{&statement} , sourceLocation{sourceLocation} { statement.bindValues(sourceLocation, queryValues...); } ~SqliteResultRangeWithTransaction() { resetter.reset(); if (!std::uncaught_exceptions()) { m_transaction.commit(sourceLocation); } } private: DeferredTransaction m_transaction; Resetter resetter; source_location sourceLocation; }; protected: ~StatementImplementation() = default; private: struct Resetter { Resetter(StatementImplementation *statement) : statement(statement) { #ifndef QT_NO_DEBUG if (statement && !statement->database().isLocked()) throw DatabaseIsNotLocked{}; #endif } Resetter(Resetter &) = delete; Resetter &operator=(Resetter &) = delete; Resetter(Resetter &&other) noexcept : statement{std::exchange(other.statement, nullptr)} {} Resetter &operator=(Resetter &&other) noexcept { statement = std::exchange(other.statement, nullptr); return *this; } void reset() noexcept { if (statement) statement->reset(); statement = nullptr; } ~Resetter() noexcept { reset(); } StatementImplementation *statement = nullptr; }; struct ValueGetter { ValueGetter(StatementImplementation &statement, int column) : statement(statement) , column(column) {} explicit operator bool() const { return statement.fetchIntValue(column); } operator int() const { return statement.fetchIntValue(column); } operator long() const { return statement.fetchLongValue(column); } operator long long() const { return statement.fetchLongLongValue(column); } operator double() const { return statement.fetchDoubleValue(column); } operator Utils::SmallStringView() { return statement.fetchSmallStringViewValue(column); } operator BlobView() { return statement.fetchBlobValue(column); } operator ValueView() { return statement.fetchValueView(column); } template> constexpr operator ConversionType() { if (statement.fetchType(column) == Type::Integer) { if constexpr (std::is_same_v) return ConversionType::create(statement.fetchIntValue(column)); else return ConversionType::create(statement.fetchLongLongValue(column)); } return ConversionType{}; } template, bool> = true> constexpr operator Enumeration() { return static_cast(statement.fetchLongLongValue(column)); } StatementImplementation &statement; int column; }; template void emplaceBackValues(ContainerType &container, std::integer_sequence) { container.emplace_back(ValueGetter(*this, ColumnIndices)...); } template void emplaceBackValues(ContainerType &container) { emplaceBackValues(container, std::make_integer_sequence{}); } template ResultOptionalType createOptionalValue(std::integer_sequence) { return ResultOptionalType(std::in_place, ValueGetter(*this, ColumnIndices)...); } template ResultOptionalType createOptionalValue() { return createOptionalValue(std::make_integer_sequence{}); } template ResultType createValue(std::integer_sequence) { return ResultType(ValueGetter(*this, ColumnIndices)...); } template ResultType createValue() { return createValue(std::make_integer_sequence{}); } template CallbackControl invokeCallable(Callable &&callable, Arguments &&...arguments) { if constexpr (std::is_void_v>) { std::invoke(std::forward(callable), std::forward(arguments)...); return CallbackControl::Continue; } else { return std::invoke(std::forward(callable), std::forward(arguments)...); } } template CallbackControl callCallable(Callable &&callable, std::integer_sequence) { return invokeCallable(std::forward(callable), ValueGetter(*this, ColumnIndices)...); } template CallbackControl callCallable(Callable &&callable) { return callCallable(std::forward(callable), std::make_integer_sequence{}); } void setMaximumResultCount(std::size_t count) { m_maximumResultCount = std::max(m_maximumResultCount, count); } public: std::size_t m_maximumResultCount = 0; }; } // namespace Sqlite