Make sure we properly check the columns in the ORDER BY clause when there is a GROUP BY (#27)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-07-15 22:43:00 +02:00
committed by GitHub
parent 1170f22d69
commit e443cb56e0
5 changed files with 42 additions and 27 deletions

View File

@@ -113,7 +113,7 @@ struct Read {
"You must assign at least one column to order by(...).");
return Read<Type, WhereType,
transpilation::order_by_t<
transpilation::value_t<Type>,
transpilation::value_t<Type>, Nothing,
typename std::remove_cvref_t<ColTypes>::ColType...>,
LimitType>{.where_ = _r.where_};
}

View File

@@ -206,11 +206,16 @@ struct SelectFrom {
"You cannot call to<...> before order_by(...).");
static_assert(sizeof...(ColTypes) != 0,
"You must assign at least one column to order_by.");
return SelectFrom<
StructType, AliasType, FieldsType, JoinsType, WhereType, GroupByType,
transpilation::order_by_t<
StructType, typename std::remove_cvref_t<ColTypes>::ColType...>,
LimitType, ToType>{
using TableTupleType =
transpilation::table_tuple_t<StructType, AliasType, JoinsType>;
using NewOrderByType = transpilation::order_by_t<
TableTupleType, GroupByType,
typename std::remove_cvref_t<ColTypes>::ColType...>;
return SelectFrom<StructType, AliasType, FieldsType, JoinsType, WhereType,
GroupByType, NewOrderByType, LimitType, ToType>{
.fields_ = _s.fields_, .joins_ = _s.joins_, .where_ = _s.where_};
}

View File

@@ -12,24 +12,24 @@
namespace sqlgen::transpilation {
template <class StructType, class FieldsType, class GroupByType>
template <class TablesType, class FieldsType, class GroupByType>
struct CheckAggregation;
/// Case: No aggregation, no group by.
template <class StructType, class... FieldTypes>
requires(true && ... && !MakeField<StructType, FieldTypes>::is_aggregation)
struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>, Nothing> {
template <class TablesType, class... FieldTypes>
requires(true && ... && !MakeField<TablesType, FieldTypes>::is_aggregation)
struct CheckAggregation<TablesType, rfl::Tuple<FieldTypes...>, Nothing> {
static constexpr bool value = true;
};
/// Case: At least one aggregation, no group by.
template <class StructType, class... FieldTypes>
requires(false || ... || MakeField<StructType, FieldTypes>::is_aggregation)
struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>, Nothing> {
template <class TablesType, class... FieldTypes>
requires(false || ... || MakeField<TablesType, FieldTypes>::is_aggregation)
struct CheckAggregation<TablesType, rfl::Tuple<FieldTypes...>, Nothing> {
static constexpr bool value =
(true && ... &&
(MakeField<StructType, FieldTypes>::is_aggregation ||
!MakeField<StructType, FieldTypes>::is_column));
(MakeField<TablesType, FieldTypes>::is_aggregation ||
!MakeField<TablesType, FieldTypes>::is_column));
static_assert(
value,
"If any column is aggregated and there is no GROUP BY, then all columns "
@@ -37,8 +37,8 @@ struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>, Nothing> {
};
/// Case: There is a group by.
template <class StructType, class... FieldTypes, class... ColTypes>
struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>,
template <class TablesType, class... FieldTypes, class... ColTypes>
struct CheckAggregation<TablesType, rfl::Tuple<FieldTypes...>,
GroupBy<ColTypes...>> {
template <class F>
static constexpr bool included_in_group_by =
@@ -48,9 +48,9 @@ struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>,
static constexpr bool value =
(true && ... &&
(MakeField<StructType, FieldTypes>::is_aggregation ||
(!MakeField<StructType, FieldTypes>::is_column &&
!MakeField<StructType, FieldTypes>::is_operation) ||
(MakeField<TablesType, FieldTypes>::is_aggregation ||
(!MakeField<TablesType, FieldTypes>::is_column &&
!MakeField<TablesType, FieldTypes>::is_operation) ||
included_in_group_by<FieldTypes>));
static_assert(value,
@@ -58,9 +58,9 @@ struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>,
"must either be aggregated or included inside the GROUP BY.");
};
template <class StructType, class FieldsType, class GroupByType>
template <class TablesType, class FieldsType, class GroupByType>
consteval bool check_aggregations() {
return CheckAggregation<std::remove_cvref_t<StructType>,
return CheckAggregation<std::remove_cvref_t<TablesType>,
std::remove_cvref_t<FieldsType>,
std::remove_cvref_t<GroupByType>>::value;
}

View File

@@ -8,6 +8,7 @@
#include "Col.hpp"
#include "Desc.hpp"
#include "all_columns_exist.hpp"
#include "check_aggregations.hpp"
namespace sqlgen::transpilation {
@@ -31,17 +32,26 @@ struct OrderByWrapper<Desc<transpilation::Col<_name, _alias>>> {
template <class... WrapperTypes>
struct OrderBy {};
template <class T, class... ColTypes>
template <class TablesType, class GroupByType, class... ColTypes>
auto make_order_by() {
static_assert(
all_columns_exist<T, typename OrderByWrapper<ColTypes>::ColType...>(),
all_columns_exist<TablesType,
typename OrderByWrapper<ColTypes>::ColType...>(),
"A column in order_by does not exist.");
static_assert(
check_aggregations<
TablesType, rfl::Tuple<typename OrderByWrapper<ColTypes>::ColType...>,
GroupByType>(),
"The columns in the ORDER BY clause have not been properly "
"aggregated. Please refer to the stack trace for details.");
return OrderBy<OrderByWrapper<ColTypes>...>{};
}
template <class T, class... ColTypes>
template <class TablesType, class GroupByType, class... ColTypes>
using order_by_t = std::invoke_result_t<
decltype(make_order_by<T, typename ColTypes::ColType...>)>;
decltype(make_order_by<TablesType, GroupByType,
typename ColTypes::ColType...>)>;
} // namespace sqlgen::transpilation

View File

@@ -23,7 +23,7 @@ struct Relationship {
sqlgen::PrimaryKey<uint32_t> child_id;
};
TEST(postgres, test_joins_nested) {
TEST(postgres, test_joins_nested_grouped) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},