@@ -19,8 +19,6 @@ namespace Xtensive.Orm.Providers.PostgreSql
1919{
2020 internal class SqlCompiler : Providers . SqlCompiler
2121 {
22- private static readonly Type DecimalType = typeof ( decimal ) ;
23-
2422 protected override SqlProvider VisitFreeText ( FreeTextProvider provider )
2523 {
2624 var rankColumnName = provider . Header . Columns [ provider . Header . Columns . Count - 1 ] . Name ;
@@ -60,15 +58,73 @@ protected override SqlExpression ProcessAggregate(SqlProvider source, List<SqlEx
6058 var result = base . ProcessAggregate ( source , sourceColumns , aggregateColumn ) ;
6159 var aggregateType = aggregateColumn . AggregateType ;
6260 var originCalculateColumn = source . Origin . Header . Columns [ aggregateColumn . SourceIndex ] ;
63- if ( aggregateType == AggregateType . Sum || aggregateType == AggregateType . Avg ) {
64- if ( ! IsCalculatedColumn ( originCalculateColumn ) && aggregateColumn . Type == DecimalType ) {
65- return result ;
61+ if ( AggregateRequiresDecimalAdjustments ( aggregateColumn ) ) {
62+ if ( ! IsCalculatedColumn ( originCalculateColumn ) ) {
63+ // this is aggregate by one column, result will be defined by the precision and scale of the column
64+ return result ;
6665 }
66+
67+ // For expressions we had to try to guess result type parameters to avoid overflow exception
68+ // on reading something like 12.000000000000000000000000000000 (30 zeros) which in practice can be reduced
69+ // 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
70+ // to get raw data and make conversion by ourselves (because nothing similar to SqlDecimal has provided by the library).
71+
72+ // Official answer of the Npgsql team is to either cast to DECIMAL with proper parameters or read all parameters as
73+ // strings and then convert :-)
74+ // Reading strings is not an option so we try to tell fortunes in a teacup :-(
75+ var resultType = ( ! TryAdjustPrecisionScale ( aggregateColumn . Descriptor . DecimalParametersHint , aggregateType , out var newPrecision , out var newScale ) )
76+ ? Driver . MapValueType ( aggregateColumn . Type )
77+ : Driver . MapValueType ( aggregateColumn . Type , null , newPrecision , newScale ) ;
78+ return SqlDml . Cast ( result , resultType ) ;
79+ }
80+ else if ( aggregateType != AggregateType . Count ) {
6781 result = SqlDml . Cast ( result , Driver . MapValueType ( aggregateColumn . Type ) ) ;
6882 }
6983 return result ;
7084 }
7185
86+ private bool AggregateRequiresDecimalAdjustments ( AggregateColumn aggregateColumn )
87+ {
88+ var aggregateType = aggregateColumn . AggregateType ;
89+ return ( aggregateType is AggregateType . Sum or AggregateType . Avg
90+ or AggregateType . Min or AggregateType . Min ) && aggregateColumn . Type == WellKnownTypes . DecimalType ;
91+ }
92+
93+ private bool TryAdjustPrecisionScale (
94+ in ( int precision , int scale ) ? typeHint ,
95+ in AggregateType aggregateType ,
96+ out int precision , out int scale )
97+ {
98+ if ( ! typeHint . HasValue ) {
99+ precision = - 1 ;
100+ scale = - 1 ;
101+ return false ;
102+ }
103+ var typeHintValue = typeHint . Value ;
104+
105+ if ( typeHintValue . precision == 28 ) {
106+ // No room for adjust, otherwise we'll lose floor part data
107+ precision = typeHintValue . precision ;
108+ scale = typeHintValue . scale ;
109+ return true ;
110+ }
111+
112+ // choose max available precision for .net or let it be the one user declared
113+ precision = ( typeHintValue . precision < 28 ) ? 28 : typeHintValue . precision ;
114+
115+ // It is benefitial to increase scale but for how much? It is open question,
116+ // sometimes we need bigger floor part, and sometimes bigger fractional part.
117+ // This algorithm is a trade-off.
118+ scale = aggregateType switch {
119+ AggregateType . Avg => ( typeHintValue . precision < 28 ) ? typeHintValue . scale + Math . Max ( ( precision - typeHintValue . precision ) / 2 , 1 ) : typeHintValue . scale + 1 ,
120+ AggregateType . Sum => ( typeHintValue . precision < 27 ) ? typeHintValue . scale + 2 : typeHintValue . scale + 1 ,
121+ AggregateType . Min => ( typeHintValue . precision < 27 ) ? typeHintValue . scale + 2 : typeHintValue . scale + 1 ,
122+ AggregateType . Max => ( typeHintValue . precision < 27 ) ? typeHintValue . scale + 2 : typeHintValue . scale + 1 ,
123+ _ => typeHintValue . scale ,
124+ } ;
125+ return true ;
126+ }
127+
72128 private bool IsCalculatedColumn ( Column column ) => column is CalculatedColumn ;
73129
74130 public SqlCompiler ( HandlerAccessor handlers , CompilerConfiguration configuration )
0 commit comments