1#include "duckdb/function/scalar/operators.hpp"
2#include "duckdb/optimizer/statistics_propagator.hpp"
3#include "duckdb/planner/bound_result_modifier.hpp"
4#include "duckdb/planner/expression/bound_cast_expression.hpp"
5#include "duckdb/planner/expression/bound_constant_expression.hpp"
6#include "duckdb/planner/expression/bound_function_expression.hpp"
7#include "duckdb/storage/statistics/base_statistics.hpp"
8#include "duckdb/common/operator/subtract.hpp"
9
10namespace duckdb {
11
12template <class T>
13bool GetCastType(T signed_range, LogicalType &cast_type) {
14 auto range = static_cast<typename std::make_unsigned<decltype(signed_range)>::type>(signed_range);
15
16 // Check if this range fits in a smaller type
17 if (range < NumericLimits<uint8_t>::Maximum()) {
18 cast_type = LogicalType::UTINYINT;
19 } else if (sizeof(T) > sizeof(uint16_t) && range < NumericLimits<uint16_t>::Maximum()) {
20 cast_type = LogicalType::USMALLINT;
21 } else if (sizeof(T) > sizeof(uint32_t) && range < NumericLimits<uint32_t>::Maximum()) {
22 cast_type = LogicalType::UINTEGER;
23 } else {
24 return false;
25 }
26 return true;
27}
28
29template <>
30bool GetCastType(hugeint_t range, LogicalType &cast_type) {
31 if (range < NumericLimits<uint8_t>().Maximum()) {
32 cast_type = LogicalType::UTINYINT;
33 } else if (range < NumericLimits<uint16_t>().Maximum()) {
34 cast_type = LogicalType::USMALLINT;
35 } else if (range < NumericLimits<uint32_t>().Maximum()) {
36 cast_type = LogicalType::UINTEGER;
37 } else if (range < NumericLimits<uint64_t>().Maximum()) {
38 cast_type = LogicalType::UBIGINT;
39 } else {
40 return false;
41 }
42 return true;
43}
44
45template <class T>
46unique_ptr<Expression> TemplatedCastToSmallestType(unique_ptr<Expression> expr, BaseStatistics &stats) {
47 // Compute range
48 if (!NumericStats::HasMinMax(stats)) {
49 return expr;
50 }
51
52 auto signed_min_val = NumericStats::Min(stats).GetValue<T>();
53 auto signed_max_val = NumericStats::Max(stats).GetValue<T>();
54 if (signed_max_val < signed_min_val) {
55 return expr;
56 }
57
58 // Compute range, cast to unsigned to prevent comparing signed with unsigned
59 T signed_range;
60 if (!TrySubtractOperator::Operation(signed_max_val, signed_min_val, signed_range)) {
61 // overflow in subtraction: cannot do any simplification
62 return expr;
63 }
64
65 // Check if this range fits in a smaller type
66 LogicalType cast_type;
67 if (!GetCastType(signed_range, cast_type)) {
68 return expr;
69 }
70
71 // Create expression to map to a smaller range
72 auto input_type = expr->return_type;
73 auto minimum_expr = make_uniq<BoundConstantExpression>(Value::CreateValue(signed_min_val));
74 vector<unique_ptr<Expression>> arguments;
75 arguments.push_back(x: std::move(expr));
76 arguments.push_back(std::move(minimum_expr));
77 auto minus_expr = make_uniq<BoundFunctionExpression>(args&: input_type, args: SubtractFun::GetFunction(left_type: input_type, right_type: input_type),
78 args: std::move(arguments), args: nullptr, args: true);
79
80 // Cast to smaller type
81 return BoundCastExpression::AddDefaultCastToType(expr: std::move(minus_expr), target_type: cast_type);
82}
83
84unique_ptr<Expression> CastToSmallestType(unique_ptr<Expression> expr, BaseStatistics &num_stats) {
85 auto physical_type = expr->return_type.InternalType();
86 switch (physical_type) {
87 case PhysicalType::UINT8:
88 case PhysicalType::INT8:
89 return expr;
90 case PhysicalType::UINT16:
91 return TemplatedCastToSmallestType<uint16_t>(expr: std::move(expr), stats&: num_stats);
92 case PhysicalType::INT16:
93 return TemplatedCastToSmallestType<int16_t>(expr: std::move(expr), stats&: num_stats);
94 case PhysicalType::UINT32:
95 return TemplatedCastToSmallestType<uint32_t>(expr: std::move(expr), stats&: num_stats);
96 case PhysicalType::INT32:
97 return TemplatedCastToSmallestType<int32_t>(expr: std::move(expr), stats&: num_stats);
98 case PhysicalType::UINT64:
99 return TemplatedCastToSmallestType<uint64_t>(expr: std::move(expr), stats&: num_stats);
100 case PhysicalType::INT64:
101 return TemplatedCastToSmallestType<int64_t>(expr: std::move(expr), stats&: num_stats);
102 case PhysicalType::INT128:
103 return TemplatedCastToSmallestType<hugeint_t>(expr: std::move(expr), stats&: num_stats);
104 default:
105 throw NotImplementedException("Unknown integer type!");
106 }
107}
108
109void StatisticsPropagator::PropagateAndCompress(unique_ptr<Expression> &expr, unique_ptr<BaseStatistics> &stats) {
110 stats = PropagateExpression(expr);
111 if (stats) {
112 if (expr->return_type.IsIntegral()) {
113 expr = CastToSmallestType(expr: std::move(expr), num_stats&: *stats);
114 }
115 }
116}
117
118} // namespace duckdb
119