From dde241ee46319535117ebe4daf81395616f7b6d3 Mon Sep 17 00:00:00 2001 From: klaus triendl Date: Fri, 7 Jan 2022 23:16:54 +0200 Subject: [PATCH 001/161] A first implementation of common table expressions --- dev/alias.h | 9 +- dev/ast_iterator.h | 22 ++ dev/column_names_getter.h | 6 +- dev/connection_holder.h | 3 + dev/cte.h | 256 ++++++++++++++++++ dev/cxx_polyfill.h | 39 +++ dev/is_base_of_template.h | 2 +- dev/mapped_type_proxy.h | 4 +- dev/node_tuple.h | 10 + dev/select_constraints.h | 56 ++++ dev/statement_serializator.h | 57 +++- dev/storage.h | 41 ++- dev/storage_base.h | 25 +- dev/storage_impl.h | 89 +++++- dev/storage_traits.h | 114 ++++---- dev/table.h | 19 +- dev/tuple_helper/tuple_helper.h | 2 +- dev/type_traits.h | 10 + .../sqlite_orm/sqlite_orm.h | 1 + 19 files changed, 666 insertions(+), 99 deletions(-) create mode 100644 dev/cte.h create mode 100644 dev/type_traits.h diff --git a/dev/alias.h b/dev/alias.h index b2073dbab..b0af158f6 100644 --- a/dev/alias.h +++ b/dev/alias.h @@ -22,7 +22,7 @@ namespace sqlite_orm { struct table_alias : alias_tag { using type = T; - static char get() { + constexpr static char get() { return A; } }; @@ -70,6 +70,8 @@ namespace sqlite_orm { using expression_type = E; expression_type expression; + // whether to only serialize the alias + bool serializeAlias = false; }; template @@ -94,6 +96,11 @@ namespace sqlite_orm { return {std::move(expression)}; } + template + internal::as_t refed_as(E expression) { + return {std::move(expression), true}; + } + template internal::alias_holder get() { return {}; diff --git a/dev/ast_iterator.h b/dev/ast_iterator.h index ea2571fa8..da5ee67ef 100644 --- a/dev/ast_iterator.h +++ b/dev/ast_iterator.h @@ -190,6 +190,28 @@ namespace sqlite_orm { } }; + template + struct ast_iterator>> { + using node_type = CTE; + + template + void operator()(const node_type& c, const L& l) const { + iterate_ast(c.expression, l); + } + }; + + template + struct ast_iterator>> { + using node_type = With; + + template + void operator()(const node_type& c, const L& l) const { + iterate_ast(c.cte, l); + iterate_ast(c.expression, l); + } + }; + template struct ast_iterator::value>::type> { using node_type = T; diff --git a/dev/column_names_getter.h b/dev/column_names_getter.h index c516d70a9..bcc3853ab 100644 --- a/dev/column_names_getter.h +++ b/dev/column_names_getter.h @@ -54,7 +54,7 @@ namespace sqlite_orm { template std::vector operator()(const expression_type&, const C&) { std::vector res; - res.push_back("*"); + res.emplace_back("*"); return res; } }; @@ -66,7 +66,7 @@ namespace sqlite_orm { template std::vector operator()(const expression_type&, const C&) { std::vector res; - res.push_back("*"); + res.emplace_back("*"); return res; } }; @@ -84,7 +84,7 @@ namespace sqlite_orm { iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { auto columnName = serialize(m, newContext); if(columnName.length()) { - columnNames.push_back(columnName); + columnNames.push_back(std::move(columnName)); } else { throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); } diff --git a/dev/connection_holder.h b/dev/connection_holder.h index e1857a759..7c49c4a16 100644 --- a/dev/connection_holder.h +++ b/dev/connection_holder.h @@ -14,6 +14,9 @@ namespace sqlite_orm { connection_holder(std::string filename_) : filename(move(filename_)) {} + connection_holder(connection_holder* connection) : + filename{connection->filename}, db{connection->db}, _retain_count{++connection->_retain_count} {} + void retain() { ++this->_retain_count; if(1 == this->_retain_count) { diff --git a/dev/cte.h b/dev/cte.h new file mode 100644 index 000000000..6cf973325 --- /dev/null +++ b/dev/cte.h @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "cxx_polyfill.h" +#include "column_result.h" +#include "select_constraints.h" +#include "alias.h" +#include "table.h" +#include "storage.h" +#include "statement_serializator.h" + +namespace sqlite_orm { + namespace internal { + namespace polyfill { + template typename Base, typename... Fs> + Base& as_base_cast(Base& base) { + return base; + } + template typename Base, typename... Fs> + const Base& as_base_cast(const Base& base) { + return base; + } + template typename Base, typename... Fs> + Base&& as_base_cast(Base&& base) { + return std::move(base); + } + template typename Base, typename... Fs> + const Base&& as_base_cast(const Base&& base) { + return std::move(base); + } + + /** @short Deduce template base specialization from a derived type */ + template class Base, class Derived> + using as_base_cast_t = decltype(as_base_cast(std::declval())); + + template class Base, class Derived, typename SFINAE = void> + struct is_template_base_of : std::false_type {}; + + template class Base, class Derived> + struct is_template_base_of>> + : std::true_type {}; + + template class Base, class Derived> + using is_template_base_of_t = typename is_template_base_of::type; + + template class Base, class Derived> + SQLITE_ORM_INLINE_VAR constexpr bool is_template_base_of_v = is_template_base_of::value; + } + } +} + +namespace sqlite_orm { + struct cte_label { + static std::string label() { + return "cte"; + } + }; + + namespace internal { + /* + * + */ + template + class column_results : std::tuple { + public: + using base = std::tuple; + using index_sequence = std::make_index_sequence::value>; + using cte_label_type = cte_label; + + template + decltype(auto) cget() const noexcept { + return std::get(polyfill::as_base_cast(*this)); + } + template + void set(std::tuple_element_t v) noexcept { + std::get(polyfill::as_base_cast(*this)) = std::move(v); + } + }; + + // F = field_type + template + struct create_column_results { + using type = column_results; + }; + + template + struct create_column_results> { + using type = column_results; + }; + + template + using create_column_results_t = typename create_column_results::type; + + template + struct create_cte_column { + using object_type = O; + using field_type = std::tuple_element_t; + using getter_type = decltype(&O::template cget); + using setter_type = decltype(&O::template set); + + using type = column_t; + }; + + template + using create_cte_column_t = typename create_cte_column::type; + + template + struct create_cte_table; + + template + struct create_cte_table, std::index_sequence> { + using object_type = column_results; + using table_type = table_t...>; + + using type = table_type; + }; + + template + using create_cte_table_t = typename create_cte_table::type; + + template + struct cte_column_names_collector { + using expression_type = T; + + template + std::vector operator()(const expression_type& t, const Ctx& context) const { + auto newContext = context; + newContext.skip_table_name = true; + std::string columnName = serialize(t, newContext); + if(columnName.empty()) { + throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + } + return {move(columnName)}; + } + }; + + template + std::vector get_cte_column_names(const T& t, const Ctx& context) { + cte_column_names_collector serializator; + return serializator(t, context); + } + + template + struct cte_column_names_collector, void> { + using expression_type = as_t; + + template + std::vector operator()(const expression_type& /*expression*/, const Ctx& /*context*/) const { + return T::get(); + } + }; + + template + struct cte_column_names_collector, void> { + using expression_type = std::reference_wrapper; + + template + std::vector operator()(const expression_type& expression, const Ctx& context) const { + return get_cte_column_names(expression.get(), context); + } + }; + + template + struct cte_column_names_collector, void> { + using expression_type = asterisk_t; + + template + std::vector operator()(const expression_type&, const Ctx& context) const { + auto& strgImpl = context.impl.get_impl(); + + std::vector columnNames; + columnNames.reserve(size_t(strgImpl.table.elements_count)); + + strgImpl.table.for_each_column([&columnNames](const basic_column& column) { + columnNames.push_back(column.name); + }); + return columnNames; + } + }; + + template + struct cte_column_names_collector, void>; + + template + struct cte_column_names_collector, void> { + using expression_type = columns_t; + + template + std::vector operator()(const expression_type& cols, const Ctx& context) const { + std::vector columnNames; + columnNames.reserve(size_t(cols.count)); + auto newContext = context; + newContext.skip_table_name = true; + iterate_tuple(cols.columns, [&columnNames, &newContext](auto& m) { + std::string columnName; + if constexpr(polyfill::is_specialization_of_v, as_t>) { + columnName = alias_extractor::alias_type>::get(); + } else { + columnName = serialize(m, newContext); + } + if(!columnName.empty()) { + columnNames.push_back(move(columnName)); + } else { + throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + } + }); + return columnNames; + } + }; + + template + std::vector cte_column_names(const Strg& storage, const select_t& sel) { + const serializator_context_builder ctxBuilder(storage); + return get_cte_column_names(sel.col, ctxBuilder()); + } + + template + create_cte_table_t + make_cte_table(const Strg& storage, std::string name, const CTE& cte, std::index_sequence) { + std::vector columnNames = cte_column_names(storage, cte); + assert(columnNames.size() == O::index_sequence::size()); + // unquote + for(std::string& name: columnNames) { + if(!name.empty() && name.front() == '"' && name.back() == '"') { + name.erase(name.end() - 1); + name.erase(name.begin()); + } + } + + return create_cte_table_t{ + move(name), + std::make_tuple<>( + make_column<>(columnNames.at(Is), &(O::template cget), &(O::template set))...)} + .without_rowid(); + } + + template + decltype(auto) make_cte_storage(const Strg& storage, const with_t& e) { + using object_type = + create_column_results_t::type>; + using table_type = create_cte_table_t; + + return storage_cat(storage, + make_cte_table(storage, + static_cast(std::get<0>(e.cte)), + std::get<0>(e.cte).expression, + typename object_type::index_sequence{})); + } + + } +} diff --git a/dev/cxx_polyfill.h b/dev/cxx_polyfill.h index 5f3a804d7..86ab8ce31 100644 --- a/dev/cxx_polyfill.h +++ b/dev/cxx_polyfill.h @@ -13,8 +13,12 @@ namespace sqlite_orm { template using bool_constant = std::integral_constant; + + template + using enable_if_t = typename std::enable_if::type; #else using std::bool_constant; + using std::enable_if_t; using std::void_t; #endif @@ -39,6 +43,41 @@ namespace sqlite_orm { template using index_constant = std::integral_constant; + +#if 1 // library fundamentals TS v2 + struct nonesuch { + ~nonesuch() = delete; + nonesuch(const nonesuch&) = delete; + void operator=(const nonesuch&) = delete; + }; + + template class Op, class... Args> + struct detector { + using value_t = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value_t; + + template class Op, class... Args> + using detected_t = typename detector::type; + + template class Op, class... Args> + using detected_or = detector; + + template class Op, class... Args> + using detected_or_t = typename detected_or::type; + + template class Op, class... Args> + SQLITE_ORM_INLINE_VAR constexpr bool is_detected_v = is_detected::value; +#endif } } } diff --git a/dev/is_base_of_template.h b/dev/is_base_of_template.h index 87a0540c9..0c388fe81 100644 --- a/dev/is_base_of_template.h +++ b/dev/is_base_of_template.h @@ -10,7 +10,7 @@ namespace sqlite_orm { * This is because of bug in MSVC, for more information, please visit * https://stackoverflow.com/questions/34672441/stdis-base-of-for-template-classes/34672753#34672753 */ -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (_MSC_VER < 1920) template class Base, typename Derived> struct is_base_of_template_impl { template diff --git a/dev/mapped_type_proxy.h b/dev/mapped_type_proxy.h index caabd5710..4acbe3f4e 100644 --- a/dev/mapped_type_proxy.h +++ b/dev/mapped_type_proxy.h @@ -7,10 +7,10 @@ namespace sqlite_orm { namespace internal { /** - * If T is alias than mapped_type_proxy::type is alias::type + * If T is alias then mapped_type_proxy::type is alias::type * otherwise T is T. */ - template + template struct mapped_type_proxy { using type = T; }; diff --git a/dev/node_tuple.h b/dev/node_tuple.h index d12322b15..454af2ec4 100644 --- a/dev/node_tuple.h +++ b/dev/node_tuple.h @@ -129,6 +129,16 @@ namespace sqlite_orm { using type = typename conc_tuple::type; }; + template + struct node_tuple::value>::type> { + using node_type = T; + using cte_type = typename node_type::cte_type; + using expression_type = typename node_type::expression_type; + using cte_tuple = typename node_tuple::type; + using expression_tuple = typename node_tuple::type; + using type = typename conc_tuple::type; + }; + template struct node_tuple, void> { using node_type = select_t; diff --git a/dev/select_constraints.h b/dev/select_constraints.h index e5d717fcc..b10421a0c 100644 --- a/dev/select_constraints.h +++ b/dev/select_constraints.h @@ -230,6 +230,48 @@ namespace sqlite_orm { using super::super; }; + struct with_string { + explicit operator std::string() const { + return "WITH"; + } + }; + + /** + * Labeled (aliased) CTE expression. + */ + template + struct common_table_expression { + using label_type = Label; + using expression_type = E; + + expression_type expression; + + common_table_expression(expression_type expression) : expression{std::move(expression)} { + this->expression.highest_level = false; + } + + explicit operator std::string() const { + return Label::label(); + } + }; + + /** + * Expression with CTEs attached. + */ + template + struct with_t : with_string { + using cte_type = std::tuple; + using expression_type = E; + + cte_type cte; + expression_type expression; + + with_t(std::tuple cte, expression_type expression) : + cte{move(cte)}, expression{std::move(expression)} { + this->expression.highest_level = true; + } + }; + /** * Generic way to get DISTINCT value from any type. */ @@ -418,6 +460,20 @@ namespace sqlite_orm { return {std::move(lhs), std::move(rhs)}; } + template + internal::common_table_expression cte(Select sel) { + return {std::move(sel)}; + } + + template + internal::with_t with(std::tuple ctes, E expression) { + return {move(ctes), std::move(expression)}; + } + template + internal::with_t with(CTE cte, E expression) { + return {std::tuple{move(cte)}, std::move(expression)}; + } + /** * Public function for UNION ALL operator. * lhs and rhs are subselect objects. diff --git a/dev/statement_serializator.h b/dev/statement_serializator.h index 582367f0a..ba3cd7353 100644 --- a/dev/statement_serializator.h +++ b/dev/statement_serializator.h @@ -9,6 +9,7 @@ #include #endif // SQLITE_ORM_OPTIONAL_SUPPORTED +#include "cxx_polyfill.h" #include "core_functions.h" #include "constraints.h" #include "conditions.h" @@ -211,7 +212,11 @@ namespace sqlite_orm { template std::string operator()(const statement_type& c, const C& context) const { auto tableAliasString = alias_extractor::get(); - return serialize(c.expression, context) + " AS " + tableAliasString; + if(c.serializeAlias) { + return tableAliasString; + } else { + return serialize(c.expression, context) + " AS " + tableAliasString; + } } }; @@ -471,6 +476,41 @@ namespace sqlite_orm { } }; + template + struct statement_serializator< + CTE, + polyfill::enable_if_t>> { + using statement_type = CTE; + + template + std::string operator()(const statement_type& c, const Ctx& context) const { + Ctx cteContext = context; + cteContext.use_parentheses = true; + + std::stringstream ss; + ss << static_cast(c) << " as "; + ss << serialize(c.expression, cteContext); + return ss.str(); + } + }; + + template + struct statement_serializator>> { + using statement_type = With; + + template + std::string operator()(const statement_type& c, const Ctx& context) const { + Ctx tupleContext = context; + tupleContext.use_parentheses = false; + + std::stringstream ss; + ss << static_cast(c) << " "; + ss << serialize(c.cte, tupleContext) << " "; + ss << serialize(c.expression, context); + return ss.str(); + } + }; + template struct statement_serializator::value>::type> { @@ -1785,11 +1825,10 @@ namespace sqlite_orm { ss << "FROM "; size_t index = 0; iterate_tuple([&context, &ss, &index](auto* itemPointer) { - using mapped_type = typename std::remove_pointer::type; + using from_type = std::remove_cv_t>; - auto aliasString = alias_extractor::get(); - ss << "'" << context.impl.find_table_name(typeid(typename mapped_type_proxy::type)) - << "'"; + auto aliasString = alias_extractor::get(); + ss << "'" << context.impl.get_table_name::type>() << "'"; if(aliasString.length()) { ss << " '" << aliasString << "'"; } @@ -2106,7 +2145,9 @@ namespace sqlite_orm { template std::string operator()(const statement_type& statement, const C& context) const { std::stringstream ss; - ss << '('; + if(context.use_parentheses) { + ss << '('; + } auto index = 0; using TupleSize = std::tuple_size; iterate_tuple(statement, [&context, &index, &ss](auto& value) { @@ -2116,7 +2157,9 @@ namespace sqlite_orm { } ++index; }); - ss << ')'; + if(context.use_parentheses) { + ss << ')'; + } return ss.str(); } }; diff --git a/dev/storage.h b/dev/storage.h index 33061da87..8ab27def1 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -70,7 +70,9 @@ namespace sqlite_orm { storage_t(const std::string& filename, impl_type impl_) : storage_base{filename, foreign_keys_count(impl_)}, impl(std::move(impl_)) {} - storage_t(const storage_t& other) : storage_base(other), impl(other.impl) {} + storage_t(const storage_t&) = default; + + storage_t(const storage_base& base, const Ts&... tables) : storage_base{base, true}, impl{tables...} {} protected: impl_type impl; @@ -87,6 +89,15 @@ namespace sqlite_orm { template friend struct serializator_context_builder; + template + friend storage_t storage_cat(const storage_t& storage, + CTETables&&... cteTables) { + using storage_type = storage_t; + return storage_type(storage, + std::forward(cteTables)..., + storage.get_impl().table...); + } + template void create_table(sqlite3* db, const std::string& tableName, const I& tableImpl) { using table_type = typename std::decay::type; @@ -436,7 +447,7 @@ namespace sqlite_orm { class O, class... Args, class Tuple = std::tuple, - typename sfinae = typename std::enable_if::value >= 1>::type> + typename SFINAE = typename std::enable_if::value >= 1>::type> std::string group_concat(F O::*m, Args&&... args) { return this->group_concat_internal(m, {}, std::forward(args)...); } @@ -904,6 +915,11 @@ namespace sqlite_orm { } } + template + friend prepared_statement_t> prepare_with_cte(self&& storage, with_t ast) { + return storage.prepare_impl>(std::move(ast)); + } + public: /** * This is a cute function used to replace migration up/down functionality. @@ -969,6 +985,13 @@ namespace sqlite_orm { return this->impl.table_exists(tableName, con.get()); } + template + typename std::enable_if::value, prepared_statement_t>>::type + prepare(with_t ast) { + auto cte = make_cte_storage(*this, ast); + return prepare_with_cte(std::move(cte), std::move(ast)); + } + template prepared_statement_t> prepare(select_t sel) { sel.highest_level = true; @@ -1478,8 +1501,8 @@ namespace sqlite_orm { perform_step(db, stmt); } - template::type> - std::vector execute(const prepared_statement_t>& statement) { + template + std::vector _execute_select(const S& statement) { auto con = this->get_connection(); auto db = con.get(); auto stmt = statement.stmt; @@ -1518,6 +1541,16 @@ namespace sqlite_orm { return res; } + template::type> + std::vector execute(const prepared_statement_t>>& statement) { + return _execute_select(statement); + } + + template::type> + std::vector execute(const prepared_statement_t>& statement) { + return _execute_select(statement); + } + template R execute(const prepared_statement_t>& statement) { auto& tImpl = this->get_impl(); diff --git a/dev/storage_base.h b/dev/storage_base.h index 3c676dbd3..fc140c176 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -459,8 +459,8 @@ namespace sqlite_orm { storage_base(const std::string& filename_, int foreignKeysCount) : pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), - inMemory(filename_.empty() || filename_ == ":memory:"), - connection(std::make_unique(filename_)), cachedForeignKeysCount(foreignKeysCount) { + inMemory(filename_.empty() || filename_ == ":memory:"), connection(new connection_holder(filename_)), + cachedForeignKeysCount(foreignKeysCount) { if(this->inMemory) { this->connection->retain(); this->on_open_internal(this->connection->get()); @@ -470,7 +470,7 @@ namespace sqlite_orm { storage_base(const storage_base& other) : on_open(other.on_open), pragma(std::bind(&storage_base::get_connection, this)), limit(std::bind(&storage_base::get_connection, this)), inMemory(other.inMemory), - connection(std::make_unique(other.connection->filename)), + connection(new connection_holder(other.connection->filename)), cachedForeignKeysCount(other.cachedForeignKeysCount) { if(this->inMemory) { this->connection->retain(); @@ -478,6 +478,12 @@ namespace sqlite_orm { } } + storage_base(const storage_base& other, bool /*shareConnection*/) : + on_open{other.on_open}, pragma{std::bind(&storage_base::get_connection, this)}, + limit{std::bind(&storage_base::get_connection, this)}, inMemory{other.inMemory}, + connection{other.connection.get(), connection_deleter{false /*no ownership*/}}, + cachedForeignKeysCount{other.cachedForeignKeysCount} {} + ~storage_base() { if(this->isOpenedForever) { this->connection->release(); @@ -727,9 +733,20 @@ namespace sqlite_orm { return res; } + // enables turning a unique_ptr into an observing smart pointer + struct connection_deleter { + const bool ownership = true; + + void operator()(connection_holder* holder) const { + if(this->ownership) { + delete holder; + } + } + }; + const bool inMemory; bool isOpenedForever = false; - std::unique_ptr connection; + std::unique_ptr connection; std::map collatingFunctions; const int cachedForeignKeysCount; std::function _busy_handler; diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 49ed71566..873a2d7b1 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -198,49 +198,114 @@ namespace sqlite_orm { return this->super::column_name(m); } - template - const std::string* column_name(const column_pointer& c, - typename std::enable_if::value>::type* = nullptr) const { + template + const std::string* column_name(const column_pointer& c, + typename std::enable_if::value>::type* = nullptr) const { return this->column_name_simple(c.field); } - template + template const std::string* - column_name(const column_pointer& c, - typename std::enable_if::value>::type* = nullptr) const { + column_name(const column_pointer& c, + typename std::enable_if::value>::type* = nullptr) const { return this->super::column_name(c); } - template + template::value, int> = 0> const auto& get_impl(typename std::enable_if::value>::type* = nullptr) const { return *this; } - template + template::value, int> = 0> const auto& get_impl(typename std::enable_if::value>::type* = nullptr) const { return this->super::template get_impl(); } - template + template::value, int> = 0> auto& get_impl(typename std::enable_if::value>::type* = nullptr) { return *this; } - template + template::value, int> = 0> auto& get_impl(typename std::enable_if::value>::type* = nullptr) { return this->super::template get_impl(); } - template + template::value, int> = 0> + const auto& get_impl(polyfill::enable_if_t::value>* = nullptr) const { + return *this; + } + + template::value, int> = 0> + const auto& get_impl(polyfill::enable_if_t::value>* = nullptr) const { + return this->super::template get_impl