Skip to content

Commit

Permalink
Implements diff script api (#24)
Browse files Browse the repository at this point in the history
* Code changes

* Minor refactor tests name

* Addresses feedback

* Adds unit test and refactoring

* Implements diff api

* Adds a unit test

* Addresses feedback

* Refactoring and updates the script uri

* Minor refactor

* Minor changes for complete status
  • Loading branch information
rbans96 authored May 15, 2020
1 parent 220693c commit f620ec2
Show file tree
Hide file tree
Showing 19 changed files with 184 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ public SchemaControllerTests()
_schemaController = new SchemaController(_schemaInformation, scriptProvider, urlHelperFactory, _mediator, NullLogger<SchemaController>.Instance);
}

[Fact]
public void GivenAScriptRequest_WhenSchemaIdFound_ThenReturnScriptSuccess()
{
ActionResult result = _schemaController.SqlScript(1);
string script = result.ToString();
Assert.NotNull(script);
}

[Fact]
public void GivenAnAvailableVersionsRequest_WhenCurrentVersionIsNull_ThenAllVersionsReturned()
{
Expand All @@ -59,6 +51,7 @@ public void GivenAnAvailableVersionsRequest_WhenCurrentVersionIsNull_ThenAllVers
JToken firstResult = jArrayResult.First;
Assert.Equal(1, firstResult["id"]);
Assert.Equal("https://localhost/script", firstResult["script"]);
Assert.Equal(string.Empty, firstResult["diff"]);

// Ensure available versions are in the ascending order
jArrayResult.RemoveAt(0);
Expand All @@ -85,6 +78,7 @@ public void GivenAnAvailableVersionsRequest_WhenCurrentVersionNotNull_ThenCorrec
JToken firstResult = jArrayResult.First;
Assert.Equal(2, firstResult["id"]);
Assert.Equal("https://localhost/script", firstResult["script"]);
Assert.Equal("https://localhost/script", firstResult["diff"]);
}
}
}
22 changes: 19 additions & 3 deletions src/Microsoft.Health.SqlServer.Api/Controllers/SchemaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ public ActionResult AvailableVersions()
{
var routeValues = new Dictionary<string, object> { { "id", version } };
string scriptUri = urlHelper.RouteUrl(RouteNames.Script, routeValues);
availableSchemas.Add(new { id = version, script = scriptUri });
string diffScriptUri = string.Empty;
if (version > 1)
{
diffScriptUri = urlHelper.RouteUrl(RouteNames.Diff, routeValues);
}

availableSchemas.Add(new { id = version, script = scriptUri, diff = diffScriptUri });
}

return new JsonResult(availableSchemas);
Expand All @@ -76,11 +82,21 @@ public async Task<ActionResult> CurrentVersionAsync()
[HttpGet]
[AllowAnonymous]
[Route(KnownRoutes.Script, Name = RouteNames.Script)]
public FileContentResult SqlScript(int id)
public async Task<FileContentResult> ScriptAsync(int id)
{
_logger.LogInformation($"Attempting to get script for schema version: {id}");
string fileName = $"{id}.sql";
return File(_scriptProvider.GetMigrationScriptAsBytes(id), "application/sql", fileName);
return File(await _scriptProvider.GetScriptAsBytesAsync(id, HttpContext.RequestAborted), "application/sql", fileName);
}

[HttpGet]
[AllowAnonymous]
[Route(KnownRoutes.Diff, Name = RouteNames.Diff)]
public async Task<FileContentResult> DiffScriptAsync(int id)
{
_logger.LogInformation($"Attempting to get diff script for schema version: {id}");
string fileName = $"{id}.diff.sql";
return File(await _scriptProvider.GetDiffScriptAsBytesAsync(id, HttpContext.RequestAborted), "application/sql", fileName);
}

[HttpGet]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ internal class KnownRoutes

public const string Current = Versions + "/current";
public const string Script = Versions + "/{id:int}/script";
public const string Diff = Script + "/diff";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace Microsoft.Health.SqlServer.Api.Features.Routing
public static class RouteNames
{
public const string Script = "Script";
public const string Diff = "Diff";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.IO;
using System.Threading.Tasks;
using Microsoft.Health.SqlServer.Features.Schema;
using Xunit;

namespace Microsoft.Health.SqlServer.UnitTests.Features.Schema
{
public class ScriptProviderTests
{
private readonly ScriptProvider<TestSchemaVersionEnum> _scriptProvider;

public ScriptProviderTests()
{
_scriptProvider = new ScriptProvider<TestSchemaVersionEnum>();
}

[Fact]
public async Task GivenASnapshotScript_WhenGetDiffScriptAsBytesAsync_ThenReturnsDiffScriptAsync()
{
Assert.NotNull(await _scriptProvider.GetDiffScriptAsBytesAsync(2, default));
}

[Fact]
public async Task GivenADiffScript_WhenGetSnapshotScriptAsBytesAsync_ThenReturnsSnapshotScriptAsync()
{
Assert.NotNull(await _scriptProvider.GetScriptAsBytesAsync(1, default));
}

[Fact]
public async Task GivenASnapshotScriptNotPresent_WhenGetSnapshotScriptAsBytesAsync_ThenReturnsFileNotFoundException()
{
FileNotFoundException ex = await Assert.ThrowsAsync<FileNotFoundException>(() => _scriptProvider.GetScriptAsBytesAsync(2, default));
Assert.Equal("The provided version is unknown.", ex.Message);
}

[Fact]
public async Task GivenADiffScriptNotPresent_WhenGetDiffScriptAsBytesAsync_ThenReturnsFileNotFoundException()
{
FileNotFoundException ex = await Assert.ThrowsAsync<FileNotFoundException>(() => _scriptProvider.GetDiffScriptAsBytesAsync(1, default));
Assert.Equal("The provided version is unknown.", ex.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

namespace Microsoft.Health.SqlServer.UnitTests.Features.Schema
{
public enum TestSchemaVersionEnum
{
Version1 = 1,
Version2 = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

<ItemGroup>
<None Remove="Features\Schema\Migrations\1.sql" />
<None Remove="Features\Schema\Migrations\2.diff.sql" />
<EmbeddedResource Include="Features\Schema\Migrations\2.diff.sql" />
<EmbeddedResource Include="Features\Schema\Migrations\1.sql" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ public class SqlServerDataStoreConfiguration
/// <summary>
/// Updates the schema migration options
/// </summary>
public SqlServerSchemaOptions SchemaOptions { get; } = new SqlServerSchemaOptions();
public SqlServerSchemaOptions SchemaOptions { get; set; } = new SqlServerSchemaOptions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Health.SqlServer.Features.Schema
{
public interface IScriptProvider
{
string GetMigrationScript(int version, bool applyFullSchemaSnapshot);

byte[] GetMigrationScriptAsBytes(int version);
Task<byte[]> GetScriptAsBytesAsync(int version, CancellationToken cancellationToken);

Task<byte[]> GetDiffScriptAsBytesAsync(int version, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ AS
DECLARE @timeout datetime2(0) = DATEADD(minute, @addMinutesOnTimeout, SYSUTCDATETIME())
DECLARE @currentVersion int = (SELECT COALESCE(MAX(Version), 0)
FROM dbo.SchemaVersion
WHERE Status = 'completed' AND Version <= @maxVersion)
WHERE Status = 'completed' OR Status = 'complete' AND Version <= @maxVersion)
IF EXISTS(SELECT * FROM dbo.InstanceSchema
WHERE Name = @name)
BEGIN
Expand Down
38 changes: 12 additions & 26 deletions src/Microsoft.Health.SqlServer/Features/Schema/SchemaInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// -------------------------------------------------------------------------------------------------

using System;
using System.Data;
using System.Data.SqlClient;
using EnsureThat;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -85,35 +84,22 @@ private void GetCurrentSchemaVersion()
{
connection.Open();

string procedureSchema = "dbo";
string procedureName = "SelectCurrentSchemaVersion";
string tableName = "dbo.SchemaVersion";

bool procedureExists;
using (var checkProcedureExistsCommand = connection.CreateCommand())
// since now the status is made consistent as 'completed', we might have to check for 'complete' as well for the previous version's status
using (var selectCommand = connection.CreateCommand())
{
checkProcedureExistsCommand.CommandText = @"
SELECT 1
FROM sys.procedures p
INNER JOIN sys.schemas s on p.schema_id = s.schema_id
WHERE s.name = @schemaName AND p.name = @procedureName";

checkProcedureExistsCommand.Parameters.AddWithValue("@schemaName", procedureSchema);
checkProcedureExistsCommand.Parameters.AddWithValue("@procedureName", procedureName);
procedureExists = checkProcedureExistsCommand.ExecuteScalar() != null;
}
selectCommand.CommandText = string.Format(
"SELECT MAX(Version) FROM {0} " +
"WHERE Status = 'complete' OR Status = 'completed'", tableName);

if (!procedureExists)
{
_logger.LogInformation("Procedure to select the schema version was not found. This must be a new database.");
}
else
{
using (var command = connection.CreateCommand())
try
{
command.CommandText = $"{procedureSchema}.{procedureName}";
command.CommandType = CommandType.StoredProcedure;

_schemaInformation.Current = (int?)command.ExecuteScalar();
_schemaInformation.Current = (int?)selectCommand.ExecuteScalar();
}
catch (SqlException e) when (e.Message is "Invalid object name 'dbo.SchemaVersion'.")
{
_logger.LogInformation($"The table {tableName} does not exists. It must be new database");
}
}
}
Expand Down
19 changes: 17 additions & 2 deletions src/Microsoft.Health.SqlServer/Features/Schema/ScriptProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Health.SqlServer.Features.Schema
{
Expand All @@ -31,9 +33,22 @@ public string GetMigrationScript(int version, bool applyFullSchemaSnapshot)
}
}

public byte[] GetMigrationScriptAsBytes(int version)
public async Task<byte[]> GetScriptAsBytesAsync(int version, CancellationToken cancellationToken)
{
string resourceName = $"{typeof(TSchemaVersionEnum).Namespace}.Migrations.{version}.sql";

return await ScriptAsBytesAsync(resourceName, cancellationToken);
}

public async Task<byte[]> GetDiffScriptAsBytesAsync(int version, CancellationToken cancellationToken)
{
string resourceName = $"{typeof(TSchemaVersionEnum).Namespace}.Migrations.{version}.diff.sql";

return await ScriptAsBytesAsync(resourceName, cancellationToken);
}

private async Task<byte[]> ScriptAsBytesAsync(string resourceName, CancellationToken cancellationToken)
{
using (Stream fileStream = Assembly.GetAssembly(typeof(TSchemaVersionEnum)).GetManifestResourceStream(resourceName))
{
if (fileStream == null)
Expand All @@ -42,7 +57,7 @@ public byte[] GetMigrationScriptAsBytes(int version)
}

var scriptBytes = new byte[fileStream.Length];
fileStream.Read(scriptBytes, 0, scriptBytes.Length);
await fileStream.ReadAsync(scriptBytes, 0, scriptBytes.Length, cancellationToken);
return scriptBytes;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -140,8 +139,12 @@ public async Task<List<CurrentVersionInformation>> GetCurrentVersionAsync(Cancel
instanceNames = names.Split(",").ToList();
}

var status = (SchemaVersionStatus)Enum.Parse(typeof(SchemaVersionStatus), (string)dataReader.GetValue(1), true);
var currentVersion = new CurrentVersionInformation((int)dataReader.GetValue(0), status, instanceNames);
var status = (string)dataReader.GetValue(1);

// To combine the complete and completed version since earlier status was marked in 'complete' status and now the fix has made to mark the status in completed state
status = string.Equals(status, "complete", StringComparison.OrdinalIgnoreCase) ? "completed" : status;
var schemaVersionStatus = (SchemaVersionStatus)Enum.Parse(typeof(SchemaVersionStatus), status, true);
var currentVersion = new CurrentVersionInformation((int)dataReader.GetValue(0), schemaVersionStatus, instanceNames);
currentVersions.Add(currentVersion);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Health.SqlServer.Api\Microsoft.Health.SqlServer.Api.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Health.SqlServer\Microsoft.Health.SqlServer.csproj" />
<ProjectReference Include="..\Microsoft.Health.SqlServer.Web\Microsoft.Health.SqlServer.Web.csproj" />
</ItemGroup>

Expand Down
Loading

0 comments on commit f620ec2

Please sign in to comment.