Skip to content

Commit a5a6de4

Browse files
committed
Corrector and Rewriter of aggregate decimal columns
which try to add information about decimal paramters of aggregate expression.
1 parent 55fc504 commit a5a6de4

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 Xtensive.Orm.Model;
6+
using Xtensive.Orm.Rse.Compilation;
7+
using Xtensive.Orm.Rse.Providers;
8+
9+
namespace Xtensive.Orm.Rse.Transformation
10+
{
11+
/// <summary>
12+
/// Corrects <see cref="AggregateProvider"/>'s columns of <see cref="decimal"/> type
13+
/// by adding information about desirable precision and scale (if such info successfully gathered).
14+
/// </summary>
15+
public sealed class AggregateOverDecimalColumnCorrector : IPreCompiler
16+
{
17+
private readonly DomainModel domainModel;
18+
19+
CompilableProvider IPreCompiler.Process(CompilableProvider rootProvider)
20+
{
21+
return new DecimalAggregateColumnRewriter(domainModel, rootProvider).Rewrite();
22+
}
23+
24+
public AggregateOverDecimalColumnCorrector(DomainModel domainModel)
25+
{
26+
this.domainModel = domainModel;
27+
}
28+
}
29+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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

Comments
 (0)