1
+ // Copyright (C) 2025 Xtensive LLC.
2
+ // This code is distributed under MIT license terms.
3
+ // See the License.txt file in the project root for more information.
4
+
5
+ using System ;
6
+ using System . Collections . Generic ;
7
+ using System . Linq ;
8
+ using Xtensive . Orm . Model ;
9
+ using Xtensive . Orm . Rse . Providers ;
10
+ using Xtensive . Reflection ;
11
+
12
+
13
+ namespace Xtensive . Orm . Rse . Transformation
14
+ {
15
+ internal sealed class DecimalAggregateColumnRewriter : CompilableProviderVisitor
16
+ {
17
+ private readonly List < CalculateProvider > calculateProviders = new ( ) ;
18
+ private readonly DomainModel domainModel ;
19
+ private readonly CompilableProvider rootProvider ;
20
+
21
+ public CompilableProvider Rewrite ( )
22
+ {
23
+ return VisitCompilable ( rootProvider ) ;
24
+ }
25
+
26
+ protected override Provider VisitAggregate ( AggregateProvider provider )
27
+ {
28
+ OnRecursionEntrance ( provider ) ;
29
+ var source = VisitCompilable ( provider . Source ) ;
30
+ var resultParameters = OnRecursionExit ( provider ) ;
31
+ var shouldUseNewProvider = source != provider . Source && resultParameters == null ;
32
+
33
+ var aggregateColumns = provider . AggregateColumns ;
34
+ var headerColumns = source . Header . Columns ;
35
+ var newDescriptors = new AggregateColumnDescriptor [ aggregateColumns . Length ] ;
36
+
37
+ for ( int i = 0 , count = aggregateColumns . Length ; i < count ; i ++ ) {
38
+ var column = aggregateColumns [ i ] ;
39
+ if ( column . Type == WellKnownTypes . Decimal ) {
40
+ var originDescriptor = column . Descriptor ;
41
+ var aggregatedColumn = headerColumns [ originDescriptor . SourceIndex ] ;
42
+
43
+ var hints = TryGuessDecimalPrecisionAndSclale ( aggregatedColumn , source ) ;
44
+ if ( hints . HasValue ) {
45
+ newDescriptors [ i ] = new AggregateColumnDescriptor ( originDescriptor . Name , originDescriptor . SourceIndex , originDescriptor . AggregateType , hints . Value ) ;
46
+ shouldUseNewProvider = true ;
47
+ continue ;
48
+ }
49
+ }
50
+ newDescriptors [ i ] = column . Descriptor ;
51
+ }
52
+
53
+ if ( ! shouldUseNewProvider ) {
54
+ return provider ;
55
+ }
56
+
57
+ return source . Aggregate ( provider . GroupColumnIndexes , newDescriptors ) ;
58
+ }
59
+
60
+ protected override Provider VisitCalculate ( CalculateProvider provider )
61
+ {
62
+ var visitedProvider = base . VisitCalculate ( provider ) ;
63
+ calculateProviders . Add ( ( CalculateProvider ) visitedProvider ) ;
64
+ return visitedProvider ;
65
+ }
66
+
67
+
68
+ private ( int , int ) ? TryGuessDecimalPrecisionAndSclale (
69
+ Column aggregatedColumn , CompilableProvider originDataSource )
70
+ {
71
+ var headerColumns = originDataSource . Header . Columns ;
72
+
73
+ if ( aggregatedColumn is MappedColumn mColumn ) {
74
+ var resolvedColumn = mColumn . ColumnInfoRef . Resolve ( domainModel ) ;
75
+ if ( resolvedColumn . Precision . HasValue && resolvedColumn . Scale . HasValue )
76
+ return ( resolvedColumn . Precision . Value , resolvedColumn . Scale . Value ) ;
77
+ }
78
+ else if ( aggregatedColumn is CalculatedColumn cColumn ) {
79
+ if ( headerColumns . Count == 1 ) {
80
+ // If current source contains only calculated column which is aggregate,
81
+ // that means it uses indexes of its source in the calculated column
82
+ var ownerProvider = calculateProviders . FirstOrDefault ( cp => cp . CalculatedColumns . Contains ( cColumn ) ) ;
83
+ if ( ownerProvider == null )
84
+ return null ;
85
+ headerColumns = ownerProvider . Header . Columns ;
86
+ }
87
+ var expression = cColumn . Expression ;
88
+ var usedColumns = new TupleAccessGatherer ( ) . Gather ( expression ) ;
89
+
90
+ var maxFloorDigits = - 1 ;
91
+ var maxScaleDigits = - 1 ;
92
+ foreach ( var cIndex in usedColumns . Distinct ( ) ) {
93
+ var usedColumn = headerColumns [ cIndex ] ;
94
+ if ( usedColumn is MappedColumn mmColumn ) {
95
+ var resolvedColumn = mmColumn . ColumnInfoRef . Resolve ( domainModel ) ;
96
+
97
+ ( int ? p , int ? s ) @params = Type . GetTypeCode ( resolvedColumn . ValueType ) switch {
98
+ TypeCode . Decimal => ( resolvedColumn . Precision , resolvedColumn . Scale ) ,
99
+ TypeCode . Int32 or TypeCode . UInt32 => ( 19 , 8 ) ,
100
+ TypeCode . Int64 or TypeCode . UInt64 => ( 28 , 8 ) ,
101
+ TypeCode . Byte or TypeCode . SByte => ( 8 , 5 ) ,
102
+ TypeCode . Int16 or TypeCode . UInt16 => ( 10 , 5 ) ,
103
+ _ => ( null , null ) ,
104
+ } ;
105
+
106
+ if ( @params . p . HasValue && @params . s . HasValue ) {
107
+ if ( maxScaleDigits < @params . s . Value )
108
+ maxScaleDigits = @params . s . Value ;
109
+ var floorDigits = @params . p . Value - @params . s . Value ;
110
+ if ( maxFloorDigits < floorDigits )
111
+ maxFloorDigits = floorDigits ;
112
+ }
113
+ }
114
+ }
115
+
116
+ if ( maxFloorDigits == - 1 || maxScaleDigits == - 1 )
117
+ return null ;
118
+ if ( maxFloorDigits + maxScaleDigits <= 28 )
119
+ return ( maxFloorDigits + maxScaleDigits , maxScaleDigits ) ;
120
+ }
121
+
122
+ return null ;
123
+ }
124
+
125
+ public DecimalAggregateColumnRewriter ( DomainModel model , CompilableProvider rootProvider )
126
+ {
127
+ domainModel = model ;
128
+ this . rootProvider = rootProvider ;
129
+ }
130
+ }
131
+ }
0 commit comments