Skip to content

Commit

Permalink
Merge branch 'Chain-4.3' into IAsyncDisposable-488
Browse files Browse the repository at this point in the history
  • Loading branch information
Grauenwolf committed Jul 14, 2022
2 parents e081249 + 5d35755 commit 5a3f138
Show file tree
Hide file tree
Showing 69 changed files with 6,461 additions and 3,942 deletions.
58 changes: 58 additions & 0 deletions Tortuga.Chain/Changelog.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,69 @@
## Version 4.3

Updated dependency to Anchor 4.1.

### Features

[#88 Simple Aggregators](https://github.com/TortugaResearch/Tortuga.Chain/issues/88)

The "simple aggregators" agument the `AsCount` method. Each returns a single value for the desired column.

* `AsAverage(columnName)`
* `AsMax(columnName)`
* `AsMin(columnName)`
* `AsSum(columnName, distinct)`

These all return a `ScalarDbCommandBuilder` with which the caller can specify the return type. They are built on the `AggregateColumn` model, which overrides the usual column selection process.

For more complex aggregation, use the `AsAggregate` method. This accepts a collection of `AggregateColumn` objects, which can be used for both aggregegate functions and grouping.

The original `AsCount` methods were reworked to fit into this model.

[#89 Declarative Aggregators](https://github.com/TortugaResearch/Tortuga.Chain/issues/89)

Attributes can now be used to declare aggregations directly in a model.

```csharp
[Table("Sales.EmployeeSalesView"]
public class SalesFigures
{
[AggregateColumn(AggregateType.Min, "TotalPrice")]
public decimal SmallestSale { get; set; }

[AggregateColumn(AggregateType.Max, "TotalPrice")]
public decimal LargestSale { get; set; }

[AggregateColumn(AggregateType.Average, "TotalPrice")]
public decimal AverageSale { get; set; }

[CustomAggregateColumn("Max(TotalPrice) - Min(TotalPrice)")]
public decimal Range { get; set; }

[GroupByColumn]
public int EmployeeKey { get; set; }

[GroupByColumn]
public string EmployeeName { get; set; }
}

```

To use this feature, you need use either of these patterns:

```csharp
datasource.FromTable(tableName, filter).ToAggregate<TObject>().ToCollection().Execute();
datasource.FromTable<TObject>(filter).ToAggregate().ToCollection().Execute();
```

In the second version, the table or view name is extracted from the class.

### Technical Debt

[#488 Add IAsyncDisposable support](https://github.com/TortugaResearch/Tortuga.Chain/issues/488)
Added support for `IAsyncDisposable` to transactional data sources.


## Version 4.2

### Features
Expand Down
7 changes: 7 additions & 0 deletions Tortuga.Chain/Engineering Notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ When used...
<AbstractConnection, AbstractTransaction, AbstractCommand, AbstractParameter, AbstractObjectName, AbstractDbType>
```

## Aggregates

Standard aggregate functions are listed in `AggregateType`.

To convert the enum to a database specific function, use `DatabaseMetadataCache.GetAggregateFunction`.

If the database doesn't support a given aggregation, override `GetAggregateFunction` and throw a `NotSupportedException`.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace Traits;

[Trait]
class SupportsUpdateSet<TCommand, TParameter, TObjectName, TDbType> : ISupportsUpdateSet
class SupportsUpdateSetTrait<TCommand, TParameter, TObjectName, TDbType> : ISupportsUpdateSet
where TCommand : DbCommand
where TParameter : DbParameter
where TObjectName : struct
Expand All @@ -16,7 +16,6 @@ class SupportsUpdateSet<TCommand, TParameter, TObjectName, TDbType> : ISupportsU
[Container(RegisterInterface = true)]
internal IUpdateDeleteSetHelper<TCommand, TParameter, TObjectName, TDbType> DataSource { get; set; } = null!;


IUpdateSetDbCommandBuilder ISupportsUpdateSet.UpdateSet(string tableName, string updateExpression, UpdateOptions options)
{
return DataSource.OnUpdateSet(DataSource.DatabaseMetadata.ParseObjectName(tableName), updateExpression, options);
Expand Down Expand Up @@ -76,8 +75,6 @@ public IUpdateSetDbCommandBuilder<TCommand, TParameter> UpdateSet(TObjectName ta
return DataSource.OnUpdateSet(tableName, updateExpression, null, options);
}



/// <summary>
/// Update multiple records using an update expression.
/// </summary>
Expand Down Expand Up @@ -119,10 +116,4 @@ public IUpdateSetDbCommandBuilder<TCommand, TParameter> UpdateSet<TObject>(strin
{
return DataSource.OnUpdateSet(DataSource.DatabaseMetadata.GetTableOrViewFromClass<TObject>().Name, updateExpression, null, options);
}



}



142 changes: 142 additions & 0 deletions Tortuga.Chain/Shared/Tests/Aggregation/ComplexAggregateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Tests.Models;
using Tortuga.Chain.Aggregates;

namespace Tests.Aggregate;

[TestClass]
public class ComplexAggregateTests : TestBase
{
const string Filter = "EmployeeKey < 100"; //So we don't overlfow on Sum/Avg

[DataTestMethod, TablesAndViewData(DataSourceGroup.All)]
public void MinMaxAvg(string dataSourceName, DataSourceType mode, string tableName)
{
var dataSource = DataSource(dataSourceName, mode);

try
{
//PostgreSQL is case sensitive, so we need to ensure we're using the correct name.
var table = dataSource.DatabaseMetadata.GetTableOrViewFromClass<Employee>();
var columnName = table.Columns["EmployeeKey"].SqlName;

var result = dataSource.From<Employee>(Filter).AsAggregate(
new AggregateColumn(AggregateType.Min, columnName, "MinEmployeeKey"),
new AggregateColumn(AggregateType.Max, columnName, "MaxEmployeeKey"),
new AggregateColumn(AggregateType.Count, columnName, "CountEmployeeKey")
).ToRow().Execute();

Assert.IsTrue(result.ContainsKey("MinEmployeeKey"));
Assert.IsTrue(result.ContainsKey("MaxEmployeeKey"));
Assert.IsTrue(result.ContainsKey("CountEmployeeKey"));
}
finally
{
Release(dataSource);
}
}

[DataTestMethod, TablesAndViewData(DataSourceGroup.All)]
public void MinMaxAvg_WithGroup(string dataSourceName, DataSourceType mode, string tableName)
{
var dataSource = DataSource(dataSourceName, mode);
try
{
//PostgreSQL is case sensitive, so we need to ensure we're using the correct name.
var table = dataSource.DatabaseMetadata.GetTableOrViewFromClass<Employee>();
var ekColumnName = table.Columns["EmployeeKey"].SqlName;
var gColumnName = table.Columns["Gender"].SqlName;

var result = dataSource.From<Employee>(Filter).AsAggregate(
new AggregateColumn(AggregateType.Min, ekColumnName, "MinEmployeeKey"),
new AggregateColumn(AggregateType.Max, ekColumnName, "MaxEmployeeKey"),
new AggregateColumn(AggregateType.Count, ekColumnName, "CountEmployeeKey"),
new GroupByColumn(gColumnName, "Gender"),
new CustomAggregateColumn($"Max({ekColumnName}) - Min({ekColumnName})", "Range")
).ToTable().Execute();

Assert.IsTrue(result.ColumnNames.Contains("MinEmployeeKey"));
Assert.IsTrue(result.ColumnNames.Contains("MaxEmployeeKey"));
Assert.IsTrue(result.ColumnNames.Contains("CountEmployeeKey"));
Assert.IsTrue(result.ColumnNames.Contains("Gender"));
Assert.IsTrue(result.ColumnNames.Contains("Range"));
}
finally
{
Release(dataSource);
}
}

[DataTestMethod, TablesAndViewData(DataSourceGroup.All)]
public void AggregateObject(string dataSourceName, DataSourceType mode, string tableName)
{
var dataSource = DataSource(dataSourceName, mode);
try
{
//PostgreSQL is case sensitive, so we need to ensure we're using the correct name.
var table = dataSource.DatabaseMetadata.GetTableOrViewFromClass<Employee>();
var ekColumnName = table.Columns["EmployeeKey"].SqlName;
var gColumnName = table.Columns["Gender"].SqlName;

var result = dataSource.From<Employee>(Filter).AsAggregate<EmployeeReport>().ToObject().Execute();
}
finally
{
Release(dataSource);
}
}

[DataTestMethod, TablesAndViewData(DataSourceGroup.All)]
public void AggregateObject_WithGroup(string dataSourceName, DataSourceType mode, string tableName)
{
var dataSource = DataSource(dataSourceName, mode);
try
{
//PostgreSQL is case sensitive, so we need to ensure we're using the correct name.
var table = dataSource.DatabaseMetadata.GetTableOrViewFromClass<Employee>();
var ekColumnName = table.Columns["EmployeeKey"].SqlName;
var gColumnName = table.Columns["Gender"].SqlName;

var result = dataSource.From<Employee>(Filter).AsAggregate<GroupedEmployeeReport>().ToCollection().Execute();
}
finally
{
Release(dataSource);
}
}

public class GroupedEmployeeReport
{
#if POSTGRESQL
const string ekColumnName = "employeekey";
#else
const string ekColumnName = "EmployeeKey";
#endif

[AggregateColumn(AggregateType.Min, "EmployeeKey")]
public int MinEmployeeKey { get; set; }

[AggregateColumn(AggregateType.Max, "EmployeeKey")]
public int MaxEmployeeKey { get; set; }

[AggregateColumn(AggregateType.Count, "EmployeeKey")]
public int CountEmployeeKey { get; set; }

[GroupByColumn]
public string Gender { get; set; }

[CustomAggregateColumn($"Max({ekColumnName}) - Min({ekColumnName})")]
public int Range { get; set; }
}

public class EmployeeReport
{
[AggregateColumn(AggregateType.Min, "EmployeeKey")]
public int MinEmployeeKey { get; set; }

[AggregateColumn(AggregateType.Max, "EmployeeKey")]
public int MaxEmployeeKey { get; set; }

[AggregateColumn(AggregateType.Count, "EmployeeKey")]
public int CountEmployeeKey { get; set; }
}
}
Loading

0 comments on commit 5a3f138

Please sign in to comment.