From e443cb56e07bc4b883a3db7ee32c5b5e7bf57b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Tue, 15 Jul 2025 22:43:00 +0200 Subject: [PATCH] Make sure we properly check the columns in the ORDER BY clause when there is a GROUP BY (#27) --- include/sqlgen/read.hpp | 2 +- include/sqlgen/select_from.hpp | 15 ++++++--- .../transpilation/check_aggregations.hpp | 32 +++++++++---------- include/sqlgen/transpilation/order_by_t.hpp | 18 ++++++++--- tests/postgres/test_joins_nested_grouped.cpp | 2 +- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/include/sqlgen/read.hpp b/include/sqlgen/read.hpp index ca8c9dd..c7c50eb 100644 --- a/include/sqlgen/read.hpp +++ b/include/sqlgen/read.hpp @@ -113,7 +113,7 @@ struct Read { "You must assign at least one column to order by(...)."); return Read, + transpilation::value_t, Nothing, typename std::remove_cvref_t::ColType...>, LimitType>{.where_ = _r.where_}; } diff --git a/include/sqlgen/select_from.hpp b/include/sqlgen/select_from.hpp index af4f1a6..1a26f21 100644 --- a/include/sqlgen/select_from.hpp +++ b/include/sqlgen/select_from.hpp @@ -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::ColType...>, - LimitType, ToType>{ + + using TableTupleType = + transpilation::table_tuple_t; + + using NewOrderByType = transpilation::order_by_t< + TableTupleType, GroupByType, + typename std::remove_cvref_t::ColType...>; + + return SelectFrom{ .fields_ = _s.fields_, .joins_ = _s.joins_, .where_ = _s.where_}; } diff --git a/include/sqlgen/transpilation/check_aggregations.hpp b/include/sqlgen/transpilation/check_aggregations.hpp index 05c7086..ffe588a 100644 --- a/include/sqlgen/transpilation/check_aggregations.hpp +++ b/include/sqlgen/transpilation/check_aggregations.hpp @@ -12,24 +12,24 @@ namespace sqlgen::transpilation { -template +template struct CheckAggregation; /// Case: No aggregation, no group by. -template - requires(true && ... && !MakeField::is_aggregation) -struct CheckAggregation, Nothing> { +template + requires(true && ... && !MakeField::is_aggregation) +struct CheckAggregation, Nothing> { static constexpr bool value = true; }; /// Case: At least one aggregation, no group by. -template - requires(false || ... || MakeField::is_aggregation) -struct CheckAggregation, Nothing> { +template + requires(false || ... || MakeField::is_aggregation) +struct CheckAggregation, Nothing> { static constexpr bool value = (true && ... && - (MakeField::is_aggregation || - !MakeField::is_column)); + (MakeField::is_aggregation || + !MakeField::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, Nothing> { }; /// Case: There is a group by. -template -struct CheckAggregation, +template +struct CheckAggregation, GroupBy> { template static constexpr bool included_in_group_by = @@ -48,9 +48,9 @@ struct CheckAggregation, static constexpr bool value = (true && ... && - (MakeField::is_aggregation || - (!MakeField::is_column && - !MakeField::is_operation) || + (MakeField::is_aggregation || + (!MakeField::is_column && + !MakeField::is_operation) || included_in_group_by)); static_assert(value, @@ -58,9 +58,9 @@ struct CheckAggregation, "must either be aggregated or included inside the GROUP BY."); }; -template +template consteval bool check_aggregations() { - return CheckAggregation, + return CheckAggregation, std::remove_cvref_t, std::remove_cvref_t>::value; } diff --git a/include/sqlgen/transpilation/order_by_t.hpp b/include/sqlgen/transpilation/order_by_t.hpp index 92709f3..4ceadab 100644 --- a/include/sqlgen/transpilation/order_by_t.hpp +++ b/include/sqlgen/transpilation/order_by_t.hpp @@ -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>> { template struct OrderBy {}; -template +template auto make_order_by() { static_assert( - all_columns_exist::ColType...>(), + all_columns_exist::ColType...>(), "A column in order_by does not exist."); + static_assert( + check_aggregations< + TablesType, rfl::Tuple::ColType...>, + GroupByType>(), + "The columns in the ORDER BY clause have not been properly " + "aggregated. Please refer to the stack trace for details."); + return OrderBy...>{}; } -template +template using order_by_t = std::invoke_result_t< - decltype(make_order_by)>; + decltype(make_order_by)>; } // namespace sqlgen::transpilation diff --git a/tests/postgres/test_joins_nested_grouped.cpp b/tests/postgres/test_joins_nested_grouped.cpp index 71355a8..003195d 100644 --- a/tests/postgres/test_joins_nested_grouped.cpp +++ b/tests/postgres/test_joins_nested_grouped.cpp @@ -23,7 +23,7 @@ struct Relationship { sqlgen::PrimaryKey child_id; }; -TEST(postgres, test_joins_nested) { +TEST(postgres, test_joins_nested_grouped) { const auto people1 = std::vector( {Person{ .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},