4
4
// Created by: Denis Krjuchkov
5
5
// Created: 2009.04.27
6
6
7
+ using System ;
7
8
using System . Collections . Generic ;
8
9
using System . Linq ;
9
10
using Xtensive . Core ;
@@ -18,6 +19,8 @@ namespace Xtensive.Orm.Providers.PostgreSql
18
19
{
19
20
internal class SqlCompiler : Providers . SqlCompiler
20
21
{
22
+ private const int MaxDotnetDecimalPrecision = 28 ;
23
+
21
24
protected override SqlProvider VisitFreeText ( FreeTextProvider provider )
22
25
{
23
26
var rankColumnName = provider . Header . Columns [ provider . Header . Columns . Count - 1 ] . Name ;
@@ -55,13 +58,83 @@ protected override SqlProvider VisitFreeText(FreeTextProvider provider)
55
58
protected override SqlExpression ProcessAggregate ( SqlProvider source , IReadOnlyList < SqlExpression > sourceColumns , AggregateColumn aggregateColumn )
56
59
{
57
60
var result = base . ProcessAggregate ( source , sourceColumns , aggregateColumn ) ;
58
- if ( aggregateColumn . AggregateType == AggregateType . Sum || aggregateColumn . AggregateType == AggregateType . Avg ) {
61
+ var aggregateType = aggregateColumn . AggregateType ;
62
+ var originCalculateColumn = source . Origin . Header . Columns [ aggregateColumn . SourceIndex ] ;
63
+ if ( AggregateRequiresDecimalAdjustments ( aggregateColumn ) ) {
64
+ if ( ! IsCalculatedColumn ( originCalculateColumn ) ) {
65
+ // this is aggregate by one column, result will be defined by the precision and scale of the column
66
+ return result ;
67
+ }
68
+
69
+ // For expressions we had to try to guess result type parameters to avoid overflow exception
70
+ // on reading something like 12.000000000000000000000000000000 (30 zeros) which in practice can be reduced
71
+ // to 12.0 on reading but Npgsql does not allow us neither to turn on such conversion inside Npgsql (as it was in v3.x.x) nor
72
+ // to get raw data and make conversion by ourselves (because nothing similar to SqlDecimal has provided by the library).
73
+
74
+ // Official answer of the Npgsql team is to either cast to DECIMAL with proper parameters or read all parameters as
75
+ // strings and then convert :-)
76
+ // Reading strings is not an option so we try to tell fortunes in a teacup :-(
77
+ var resultType = ( ! TryAdjustPrecisionScale ( aggregateColumn . Descriptor . DecimalParametersHint , aggregateType , out var newPrecision , out var newScale ) )
78
+ ? Driver . MapValueType ( aggregateColumn . Type )
79
+ : Driver . MapValueType ( aggregateColumn . Type , null , newPrecision , newScale ) ;
80
+ return SqlDml . Cast ( result , resultType ) ;
81
+ }
82
+ else if ( aggregateType != AggregateType . Count ) {
59
83
result = SqlDml . Cast ( result , Driver . MapValueType ( aggregateColumn . Type ) ) ;
60
84
}
61
-
62
85
return result ;
63
86
}
64
87
88
+ private bool AggregateRequiresDecimalAdjustments ( AggregateColumn aggregateColumn )
89
+ {
90
+ var aggregateType = aggregateColumn . AggregateType ;
91
+ return ( aggregateType is AggregateType . Sum or AggregateType . Avg
92
+ or AggregateType . Min or AggregateType . Min ) && aggregateColumn . Type == WellKnownTypes . DecimalType ;
93
+ }
94
+
95
+ private bool TryAdjustPrecisionScale (
96
+ in ( int precision , int scale ) ? typeHint ,
97
+ in AggregateType aggregateType ,
98
+ out int precision , out int scale )
99
+ {
100
+ if ( ! typeHint . HasValue ) {
101
+ precision = - 1 ;
102
+ scale = - 1 ;
103
+ return false ;
104
+ }
105
+ var typeHintValue = typeHint . Value ;
106
+
107
+ if ( typeHintValue . precision == MaxDotnetDecimalPrecision ) {
108
+ // No room for adjust, otherwise we'll lose floor part data
109
+ precision = typeHintValue . precision ;
110
+ scale = typeHintValue . scale ;
111
+ return true ;
112
+ }
113
+
114
+ // choose max available precision for .net or let it be the one user declared
115
+ precision = ( typeHintValue . precision < 28 ) ? 28 : typeHintValue . precision ;
116
+
117
+ // It is benefitial to increase scale but for how much? It is open question,
118
+ // sometimes we need bigger floor part, and sometimes bigger fractional part.
119
+ // This algorithm is a trade-off.
120
+ scale = aggregateType switch {
121
+ AggregateType . Avg =>
122
+ ( typeHintValue . precision < MaxDotnetDecimalPrecision )
123
+ ? typeHintValue . scale + Math . Max ( ( precision - typeHintValue . precision ) / 2 , 1 )
124
+ : typeHintValue . scale + 1 ,
125
+ AggregateType . Sum or
126
+ AggregateType . Min or
127
+ AggregateType . Max =>
128
+ ( typeHintValue . precision < MaxDotnetDecimalPrecision - 1 )
129
+ ? typeHintValue . scale + 2
130
+ : typeHintValue . scale + 1 ,
131
+ _ => typeHintValue . scale ,
132
+ } ;
133
+ return true ;
134
+ }
135
+
136
+ private bool IsCalculatedColumn ( Column column ) => column is CalculatedColumn ;
137
+
65
138
public SqlCompiler ( HandlerAccessor handlers , in CompilerConfiguration configuration )
66
139
: base ( handlers , configuration )
67
140
{
0 commit comments