Skip to content

Commit

Permalink
add change table support for sql server
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Sep 11, 2023
1 parent 36fdb80 commit 5f8ced9
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 3 deletions.
60 changes: 60 additions & 0 deletions src/FluentCommand.SqlServer/Query/ChangeTableBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using FluentCommand.Extensions;
using FluentCommand.Query.Generators;
using FluentCommand.Reflection;

namespace FluentCommand.Query;

public class ChangeTableBuilder : StatementBuilder<ChangeTableBuilder>
{
private TableExpression _fromTable;
private QueryParameter _parameter;

public ChangeTableBuilder(IQueryGenerator queryGenerator, List<QueryParameter> parameters) : base(queryGenerator, parameters)
{
}

public ChangeTableBuilder From(
string tableName,
string tableSchema = null,
string tableAlias = null)
{
_fromTable = new TableExpression(tableName, tableSchema, tableAlias);

return this;
}

public ChangeTableBuilder From<TEntity>(
string tableAlias = null)
{
var typeAccessor = TypeAccessor.GetAccessor<TEntity>();

_fromTable = new TableExpression(typeAccessor.TableName, typeAccessor.TableSchema, tableAlias);

return this;
}

public ChangeTableBuilder LastVersion(long lastVersion)
{
var name = NextParameter();
_parameter = new QueryParameter(name, lastVersion, typeof(long));

Parameters.Add(_parameter);

return this;
}

public override QueryStatement BuildStatement()
{
if (_parameter == null)
return new QueryStatement(QueryGenerator.TableExpression(_fromTable), Parameters);

var table = QueryGenerator.TableExpression(new TableExpression(_fromTable.TableName, _fromTable.TableSchema));

var statement = $"CHANGETABLE (CHANGES {table}, {_parameter.Name})";

if (_fromTable.TableAlias.HasValue())
statement += $" AS [{_fromTable.TableAlias}]";

return new QueryStatement(statement, Parameters);
}
}
46 changes: 46 additions & 0 deletions src/FluentCommand.SqlServer/Query/ChangeTableBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace FluentCommand.Query;

public static class ChangeTableBuilderExtensions
{
public static SelectBuilder ChangeTable(this SelectBuilder selectBuilder, Action<ChangeTableBuilder> builder)
{
if (selectBuilder is null)
throw new ArgumentNullException(nameof(selectBuilder));
if (builder is null)
throw new ArgumentNullException(nameof(builder));

var queryBuilder = selectBuilder as IQueryBuilder;

var innerBuilder = new ChangeTableBuilder(queryBuilder.QueryGenerator, queryBuilder.Parameters);
builder(innerBuilder);

var statement = innerBuilder.BuildStatement();
selectBuilder.FromRaw(statement.Statement);

return selectBuilder;

}

public static SelectEntityBuilder<TEntity> ChangeTable<TEntity>(this SelectEntityBuilder<TEntity> selectBuilder, Action<ChangeTableBuilder> builder)
where TEntity : class
{
if (selectBuilder is null)
throw new ArgumentNullException(nameof(selectBuilder));
if (builder is null)
throw new ArgumentNullException(nameof(builder));

var queryBuilder = selectBuilder as IQueryBuilder;

var innerBuilder = new ChangeTableBuilder(queryBuilder.QueryGenerator, queryBuilder.Parameters);

// preset table and schema
innerBuilder.From<TEntity>();

builder(innerBuilder);

var statement = innerBuilder.BuildStatement();
selectBuilder.FromRaw(statement.Statement);

return selectBuilder;
}
}
39 changes: 39 additions & 0 deletions test/FluentCommand.SqlServer.Tests/DataQueryTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Transactions;

using FluentAssertions;

Expand All @@ -12,6 +13,8 @@
using Xunit;
using Xunit.Abstractions;

using IsolationLevel = System.Data.IsolationLevel;

namespace FluentCommand.SqlServer.Tests;

public class DataQueryTests : DatabaseTestBase
Expand Down Expand Up @@ -349,4 +352,40 @@ public async System.Threading.Tasks.Task SqlInsertUpdateDeleteEntityQuery()

}

[Fact]
public async System.Threading.Tasks.Task SqlQueryChangeTable()
{
var session = Services.GetRequiredService<IDataSession>();
session.Should().NotBeNull();

long previousVersion = 0;
long currentVersion = 0;

await using var transaction = await session.BeginTransactionAsync(IsolationLevel.ReadCommitted);

var changes = session
.Sql(builder =>
{
builder
.Statement()
.Query("SET @currentVersion = CHANGE_TRACKING_CURRENT_VERSION();");
builder
.Select<User>()
.Columns("t")
.ChangeTable(t => t
.From<User>("c")
.LastVersion(previousVersion)
)
.Join<User>(join => join
.Left(p => p.Id, "c")
.Right(p => p.Id, "t")
);
})
.ParameterOut<long>("@currentVersion", value => currentVersion = value)
.Query<User>();

await transaction.CommitAsync();
}

}
22 changes: 20 additions & 2 deletions test/FluentCommand.SqlServer.Tests/DatabaseInitializer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using DbUp;
using DbUp.Engine;
using DbUp.Engine.Output;

using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Expand All @@ -27,11 +30,26 @@ public Task StartAsync(CancellationToken cancellationToken)
{
var connectionString = _configuration.GetConnectionString("Tracker");

EnsureDatabase.For.SqlDatabase(connectionString, this);
// create database
EnsureDatabase.For
.SqlDatabase(connectionString, this);

// parse connection string
var builder = new SqlConnectionStringBuilder(connectionString);
var database = builder.InitialCatalog;

var upgradeEngine = DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.WithScript(
"enable-change-tracking",
$"ALTER DATABASE [{database}] SET CHANGE_TRACKING = ON ( CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON );",
new SqlScriptOptions { RunGroupOrder = 0 }
)
.WithScriptsEmbeddedInAssembly(
Assembly.GetExecutingAssembly(),
Encoding.Default,
new SqlScriptOptions { RunGroupOrder = 1 }
)
.LogTo(this)
.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,6 @@ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Us
CREATE INDEX [IX_UserLogin_UserId]
ON [dbo].[UserLogin] ([UserId]);


-- change tracking
ALTER TABLE [dbo].[User]
ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = OFF);
24 changes: 24 additions & 0 deletions test/FluentCommand.Tests/Query/SelectBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,30 @@ public async System.Threading.Tasks.Task SelectEntityTemporalBuilder()
await Verifier.Verify(sql).UseDirectory("Snapshots");
}

[Fact]
public async System.Threading.Tasks.Task SelectEntityChangeTableBuilder()
{
var sqlProvider = new SqlServerGenerator();
var parameters = new List<QueryParameter>();

var builder = new SelectEntityBuilder<User>(sqlProvider, parameters)
.Columns("t")
.ChangeTable(t => t
.From<User>("c")
.LastVersion(0)
)
.Join<User>(join => join
.Left(p => p.Id, "c")
.Right(p => p.Id, "t")
);

var queryStatement = builder.BuildStatement();

var sql = queryStatement.Statement;

await Verifier.Verify(sql).UseDirectory("Snapshots");
}

[Fact]
public async System.Threading.Tasks.Task SelectEntityWhereIn()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SELECT [t].[Id], [t].[EmailAddress], [t].[IsEmailAddressConfirmed], [t].[DisplayName], [t].[FirstName], [t].[LastName], [t].[PasswordHash], [t].[ResetHash], [t].[InviteHash], [t].[AccessFailedCount], [t].[LockoutEnabled], [t].[LockoutEnd], [t].[LastLogin], [t].[IsDeleted], [t].[Created], [t].[CreatedBy], [t].[Updated], [t].[UpdatedBy], [t].[RowVersion]
FROM CHANGETABLE (CHANGES [User], @p0000) AS [c]
INNER JOIN [User] AS [t] ON [c].[Id] = [t].[Id];

0 comments on commit 5f8ced9

Please sign in to comment.