Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Commit

Permalink
Pimped tables and column aliases
Browse files Browse the repository at this point in the history
* Dedicated unit tests for aliases
* Type traits for aliases
* Syntactic sugar for easier creation and usage of aliased expressions (pointer-member-pointer operator, ad-hoc string aliases)
* Column aliases are usable on their own in expressions
  • Loading branch information
trueqbit committed Feb 15, 2023
1 parent 3d24f95 commit 858dc32
Show file tree
Hide file tree
Showing 29 changed files with 1,152 additions and 320 deletions.
183 changes: 157 additions & 26 deletions dev/alias.h
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
#pragma once

#include <type_traits> // std::enable_if, std::is_base_of, std::is_member_pointer
#include <sstream> // std::stringstream
#include <type_traits> // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const
#include <utility> // std::index_sequence, std::make_index_sequence
#include <string> // std::string
#include <sstream> // std::stringstream
#include <algorithm> // std::copy_n

#include "functional/cxx_universal.h"
#include "functional/cxx_type_traits_polyfill.h"
#include "type_traits.h"
#include "alias_traits.h"

namespace sqlite_orm {

/**
* This is base class for every class which is used as a custom table alias, column alias or expression alias.
* For more information please look through self_join.cpp example
*/
struct alias_tag {};

namespace internal {

#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
/*
* Helper class to facilitate user-defined string literal operator template
*/
template<size_t N>
struct string_identifier_template {
static constexpr size_t size() {
return N - 1;
}

constexpr string_identifier_template(const char (&id)[N]) {
std::copy_n(id, N, this->id);
}

char id[N];
};

template<template<char...> class Alias, string_identifier_template t, size_t... Idx>
consteval auto to_alias(std::index_sequence<Idx...>) {
return Alias<t.id[Idx]...>{};
}
#endif

/**
* This is a common built-in class used for custom single character table aliases.
* For convenience there exist type aliases `alias_a`, `alias_b`, ...
* For convenience there exist public type aliases `alias_a`, `alias_b`, ...
*/
template<class T, char A>
template<class T, char A, char... X>
struct table_alias : alias_tag {
using type = T;

static char get() {
return A;
static std::string get() {
return {A, X...};
}
};

Expand All @@ -40,20 +62,39 @@ namespace sqlite_orm {
column_type column;
};

/*
* Encapsulates extracting the alias identifier of a non-alias.
*/
template<class T, class SFINAE = void>
struct alias_extractor {
static std::string get() {
static std::string extract() {
return {};
}

static std::string as_alias() {
return {};
}
};

template<class T>
struct alias_extractor<T, std::enable_if_t<std::is_base_of<alias_tag, T>::value>> {
static std::string get() {
/*
* Encapsulates extracting the alias identifier of an alias.
*
* `extract()` always returns the alias identifier.
* `as_alias()` is used in contexts where a table is aliased.
*/
template<class A>
struct alias_extractor<A, match_if<is_alias, A>> {
static std::string extract() {
std::stringstream ss;
ss << T::get();
ss << A::get();
return ss.str();
}

// for column and regular table aliases -> alias identifier
template<class T = A, satisfies_not<std::is_same, polyfill::detected_t<type_t, T>, A> = true>
static std::string as_alias() {
return alias_extractor::extract();
}
};

/**
Expand All @@ -71,41 +112,105 @@ namespace sqlite_orm {
* This is a common built-in class used for custom single-character column aliases.
* For convenience there exist type aliases `colalias_a`, `colalias_b`, ...
*/
template<char A>
template<char A, char... X>
struct column_alias : alias_tag {
static std::string get() {
return std::string(1u, A);
return {A, X...};
}
};

template<class T>
struct alias_holder {
using type = T;

alias_holder() = default;
};

#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
template<char A, char... C>
struct table_alias_builder {
static_assert(sizeof...(C) == 0 && ((A >= 'A' && 'Z' <= A) || (A >= 'a' && 'z' <= A)),
"Table alias identifiers shall consist of a single alphabetic character, in order to evade "
"clashes with CTE aliases.");

template<auto t>
[[nodiscard]] consteval internal::table_alias<std::remove_const_t<decltype(t)>, A, C...> for_() const {
return {};
}

template<class T>
[[nodiscard]] consteval internal::table_alias<T, A, C...> for_() const {
return {};
}
};
#endif
}

/**
* @return column with table alias attached. Place it instead of a column statement in case you need to specify a
* column with table alias prefix like 'a.column'. For more information please look through self_join.cpp example
* column with table alias prefix like 'a.column'.
*/
template<class A, class C>
template<class A, class C, std::enable_if_t<internal::is_table_alias_v<A>, bool> = true>
internal::alias_column_t<A, C> alias_column(C c) {
using table_type = internal::type_t<A>;
static_assert(std::is_same<internal::table_type_of_t<C>, table_type>::value,
static_assert(std::is_same<polyfill::detected_t<internal::table_type_of_t, C>, table_type>::value,
"Column must be from aliased table");
return {c};
}

template<class T, class E>
internal::as_t<T, E> as(E expression) {
#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
template<auto als,
class C,
class A = std::remove_const_t<decltype(als)>,
std::enable_if_t<internal::is_table_alias_v<A>, bool> = true>
auto alias_column(C c) {
using table_type = internal::type_t<A>;
static_assert(std::is_same_v<polyfill::detected_t<internal::table_type_of_t, C>, table_type>,
"Column must be from aliased table");
return internal::alias_column_t<A, decltype(c)>{c};
}

template<class A, class F, std::enable_if_t<internal::is_table_alias_v<A>, bool> = true>
constexpr auto operator->*(const A& /*tableAlias*/, F field) {
return alias_column<A>(std::move(field));
}
#endif

/**
* Alias a column expression.
*/
template<class A, class E, internal::satisfies<internal::is_column_alias, A> = true>
internal::as_t<A, E> as(E expression) {
return {std::move(expression)};
}

template<class T>
internal::alias_holder<T> get() {
#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
template<auto als, class E, internal::satisfies<internal::is_column_alias, decltype(als)> = true>
auto as(E expression) {
return internal::as_t<std::remove_const_t<decltype(als)>, E>{std::move(expression)};
}

/**
* Alias a column expression.
*/
template<class A, class E, internal::satisfies<internal::is_column_alias, A> = true>
internal::as_t<A, E> operator>>=(E expression, const A&) {
return {std::move(expression)};
}
#endif

template<class A, internal::satisfies<internal::is_column_alias, A> = true>
internal::alias_holder<A> get() {
return {};
}

#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
template<auto als, internal::satisfies<internal::is_column_alias, decltype(als)> = true>
auto get() {
return internal::alias_holder<std::remove_const_t<decltype(als)>>{};
}
#endif

template<class T>
using alias_a = internal::table_alias<T, 'a'>;
template<class T>
Expand Down Expand Up @@ -168,4 +273,30 @@ namespace sqlite_orm {
using colalias_g = internal::column_alias<'g'>;
using colalias_h = internal::column_alias<'h'>;
using colalias_i = internal::column_alias<'i'>;

#ifdef SQLITE_ORM_CLASSTYPE_TEMPLATE_ARGS_SUPPORTED
/** @short Create aliased tables e.g. `constexpr auto z_alias = alias_<'z'>.for_<User>()`.
*/
template<char A>
inline constexpr internal::table_alias_builder<A> alias_{};

/** @short Create a table alias.
*
* Examples:
* constexpr auto z_alias = "z"_alias.for_<User>();
*/
template<internal::string_identifier_template t>
[[nodiscard]] consteval auto operator"" _alias() {
return internal::to_alias<internal::table_alias_builder, t>(std::make_index_sequence<t.size()>{});
}

/** @short Create a column alias.
* column_alias<'a'[, ...]> from a string literal.
* E.g. "a"_col, "b"_col
*/
template<internal::string_identifier_template t>
[[nodiscard]] consteval auto operator"" _col() {
return internal::to_alias<internal::column_alias, t>(std::make_index_sequence<t.size()>{});
}
#endif
}
64 changes: 64 additions & 0 deletions dev/alias_traits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

#include <type_traits> // std::remove_const, std::is_base_of, std::is_same

#include "functional/cxx_universal.h"
#include "functional/cxx_type_traits_polyfill.h"

namespace sqlite_orm {

/**
* Base class for a custom table alias, column alias or expression alias.
* For more information please look through self_join.cpp example
*/
struct alias_tag {};

namespace internal {

template<class A>
SQLITE_ORM_INLINE_VAR constexpr bool is_alias_v = std::is_base_of<alias_tag, A>::value;

template<class A>
using is_alias = polyfill::bool_constant<is_alias_v<A>>;

/** Alias for a column in a record set.
*
* A column alias has the following traits:
* - is derived from `alias_tag`
*
* @note: Currently, there is no distinguishing property of a column alias other than that it is derived from "alias_tag".
*/
template<class A>
SQLITE_ORM_INLINE_VAR constexpr bool is_column_alias_v = is_alias_v<A>;

template<class A>
using is_column_alias = is_alias<A>;

/** Alias for any type of record set.
*
* A record set alias has the following traits:
* - is derived from `alias_tag`.
* - has a `type` typename, which refers to a mapped object.
*/
template<class A>
SQLITE_ORM_INLINE_VAR constexpr bool is_recordset_alias_v =
polyfill::conjunction_v<is_alias<A>, polyfill::is_detected<type_t, A>>;

template<class A>
using is_recordset_alias = polyfill::bool_constant<is_recordset_alias_v<A>>;

/** Alias for a concrete table.
*
* A concrete table alias has the following traits:
* - is derived from `alias_tag`.
* - has a `type` typename, which refers to another mapped object (i.e. doesn't refer to itself).
*/
template<class A>
SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction_v<
std::is_base_of<alias_tag, A>,
polyfill::negation<std::is_same<polyfill::detected_t<type_t, A>, std::remove_const_t<A>>>>;

template<class A>
using is_table_alias = polyfill::bool_constant<is_table_alias_v<A>>;
}
}
15 changes: 14 additions & 1 deletion dev/ast_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <functional> // std::reference_wrapper

#include "tuple_helper/tuple_iteration.h"
#include "type_traits.h"
#include "conditions.h"
#include "alias.h"
#include "select_constraints.h"
#include "operators.h"
#include "core_functions.h"
Expand Down Expand Up @@ -673,7 +675,7 @@ namespace sqlite_orm {
};

/**
* Column alias or literal
* Column alias or literal: skipped
*/
template<class T>
struct ast_iterator<
Expand All @@ -686,6 +688,17 @@ namespace sqlite_orm {
void operator()(const node_type& /*node*/, L& /*lambda*/) const {}
};

/**
* Column alias: skipped
*/
template<char... C>
struct ast_iterator<column_alias<C...>, void> {
using node_type = column_alias<C...>;

template<class L>
void operator()(const node_type& /*node*/, L& /*lambda*/) const {}
};

template<class E>
struct ast_iterator<order_by_t<E>, void> {
using node_type = order_by_t<E>;
Expand Down
14 changes: 7 additions & 7 deletions dev/column_names_getter.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
#include <system_error>
#include <utility> // std::move

#include "functional/cxx_universal.h" // ::size_t
#include "functional/cxx_type_traits_polyfill.h" // polyfill::remove_cvref_t
#include "tuple_helper/tuple_traits.h"
#include "tuple_helper/tuple_iteration.h"
#include "error_code.h"
#include "mapped_type_proxy.h"
#include "alias_traits.h"
#include "select_constraints.h"
#include "alias.h"
#include "storage_lookup.h" // pick_table
#include "serializer_context.h"
#include "util.h"
Expand All @@ -34,8 +34,8 @@ namespace sqlite_orm {
table.for_each_column([qualified = !context.skip_table_name,
&tableName = table.name,
&collectedExpressions](const column_identifier& column) {
if(std::is_base_of<alias_tag, T>::value) {
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::get()) + "." +
if(is_alias_v<T>) {
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::extract()) + "." +
quote_identifier(column.name));
} else if(qualified) {
collectedExpressions.push_back(quote_identifier(tableName) + "." +
Expand All @@ -46,8 +46,8 @@ namespace sqlite_orm {
});
} else {
collectedExpressions.reserve(collectedExpressions.size() + 1);
if(std::is_base_of<alias_tag, T>::value) {
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::get()) + ".*");
if(is_alias_v<T>) {
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::extract()) + ".*");
} else if(!context.skip_table_name) {
const basic_table& table = pick_table<mapped_type_proxy_t<T>>(context.db_objects);
collectedExpressions.push_back(quote_identifier(table.name) + ".*");
Expand Down
Loading

0 comments on commit 858dc32

Please sign in to comment.