Skip to content

Commit

Permalink
Adds SchemaManager (#21)
Browse files Browse the repository at this point in the history
* Adds SchemaManager

* Fixes status name consistency

* Addresses feedback

* Refactors and adds transaction

* Minor refactoring

* Adds short version

* Adds SqlHandler

* Adds logic to retrieve diff sql content

* Addresses feedback

* Refactoring

* Adds diff script changes

* Minor refactoring
  • Loading branch information
rbans96 authored May 18, 2020
1 parent f620ec2 commit b21ac31
Show file tree
Hide file tree
Showing 26 changed files with 1,482 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Microsoft.Health.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.SqlServer.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Development.IdentityProvider", "src\Microsoft.Health.Development.IdentityProvider\Microsoft.Health.Development.IdentityProvider.csproj", "{8DFE583A-60D6-4014-A95A-01AAE8D35A0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchemaManager", "tools\SchemaManager\SchemaManager.csproj", "{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -111,6 +113,10 @@ Global
{8DFE583A-60D6-4014-A95A-01AAE8D35A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DFE583A-60D6-4014-A95A-01AAE8D35A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DFE583A-60D6-4014-A95A-01AAE8D35A0E}.Release|Any CPU.Build.0 = Release|Any CPU
{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -131,6 +137,7 @@ Global
{C74D5E00-8BE4-4C99-8A59-9D58D255C140} = {CCD9FF99-E177-446E-B9E5-9F570FD96A34}
{85781F6A-28D8-4850-A991-51157E5DF46E} = {CCD9FF99-E177-446E-B9E5-9F570FD96A34}
{8DFE583A-60D6-4014-A95A-01AAE8D35A0E} = {8AD2A324-DAB5-4380-94A5-31F7D817C384}
{4AB4CF95-DAE6-4B64-9DC5-5F0633FABD56} = {B70945F4-01A6-4351-955B-C4A2943B5E3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_SortFileContentOnSave = True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,25 @@ BEGIN
GROUP BY Version, Status

END
GO

--
-- STORED PROCEDURE
-- SelectCurrentSchemaVersion
--
-- DESCRIPTION
-- Selects the current completed schema version
--
-- RETURNS
-- The current version as a result set
--
CREATE PROCEDURE dbo.SelectCurrentSchemaVersion
AS
BEGIN
SET NOCOUNT ON

SELECT MAX(Version)
FROM SchemaVersion
WHERE Status = 'completed'
END
GO
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,25 @@ BEGIN
GROUP BY Version, Status

END
GO

--
-- STORED PROCEDURE
-- SelectCurrentSchemaVersion
--
-- DESCRIPTION
-- Selects the current completed schema version
--
-- RETURNS
-- The current version as a result set
--
CREATE PROCEDURE dbo.SelectCurrentSchemaVersion
AS
BEGIN
SET NOCOUNT ON

SELECT MAX(Version)
FROM SchemaVersion
WHERE Status = 'completed'
END
GO
8 changes: 8 additions & 0 deletions tools/SchemaManager/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Resources;

[assembly: NeutralResourcesLanguage("en-us")]
14 changes: 14 additions & 0 deletions tools/SchemaManager/CommandNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

namespace SchemaManager
{
public static class CommandNames
{
public const string Apply = "apply";
public const string Available = "available";
public const string Current = "current";
}
}
164 changes: 164 additions & 0 deletions tools/SchemaManager/Commands/ApplyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common;
using SchemaManager.Exceptions;
using SchemaManager.Model;
using SchemaManager.Utils;

namespace SchemaManager.Commands
{
public static class ApplyCommand
{
private static readonly TimeSpan MaxWaitTime = TimeSpan.FromMinutes(1);

public static async Task HandlerAsync(string connectionString, Uri server, MutuallyExclusiveType exclusiveType, bool force)
{
ISchemaClient schemaClient = new SchemaClient(server);

if (force && !EnsureForce())
{
return;
}

try
{
var availableVersions = await schemaClient.GetAvailability();

if (availableVersions.Count <= 1)
{
CommandUtils.PrintError(Resources.AvailableVersionsDefaultErrorMessage);
return;
}

availableVersions.Sort((x, y) => x.Id.CompareTo(y.Id));

// Removing the current version
if (availableVersions.First().Id == SchemaDataStore.GetCurrentSchemaVersion(connectionString))
{
availableVersions.RemoveAt(0);
}

var targetVersion = exclusiveType.Next == true ? availableVersions.First().Id :
exclusiveType.Latest == true ? availableVersions.Last().Id :
exclusiveType.Version;

availableVersions = availableVersions.Where(availableVersion => availableVersion.Id <= targetVersion)
.ToList();

// Checking the specified version is not out of range of available versions
if (availableVersions.Count < 1 || targetVersion < availableVersions.First().Id || targetVersion > availableVersions.Last().Id)
{
throw new SchemaManagerException(string.Format(Resources.SpecifiedVersionNotAvailable, targetVersion));
}

// to ensure server side polling is completed
Console.WriteLine(Resources.WaitMessage);
await Task.Delay(MaxWaitTime);

if (!force)
{
await ValidateCompatibleVersion(schemaClient, availableVersions.First().Id, availableVersions.Last().Id);
}
else if (availableVersions.First().Id == 1)
{
string script = await GetScript(schemaClient, 1, availableVersions.Last().Script);
UpgradeSchema(connectionString, availableVersions.Last().Id, script);
return;
}

foreach (AvailableVersion availableVersion in availableVersions)
{
int executingVersion = availableVersion.Id;
if (!force)
{
await ValidateInstancesVersion(schemaClient, executingVersion);
}

string script = await GetScript(schemaClient, executingVersion, availableVersion.Script, availableVersion.Diff);

UpgradeSchema(connectionString, executingVersion, script);

// to ensure server side polling is completed after each version migration
if (executingVersion != availableVersions.Last().Id)
{
Console.WriteLine(Resources.WaitMessage);
await Task.Delay(MaxWaitTime);
}
}
}
catch (SchemaManagerException ex)
{
CommandUtils.PrintError(ex.Message);
return;
}
catch (HttpRequestException)
{
CommandUtils.PrintError(string.Format(Resources.RequestFailedMessage, server));
return;
}
catch (Exception ex) when (ex is SqlException || ex is ExecutionFailureException)
{
CommandUtils.PrintError(string.Format(Resources.QueryExecutionErrorMessage, ex.Message));
return;
}
}

private static void UpgradeSchema(string connectionString, int version, string script)
{
// check if the record for given version exists in failed status
SchemaDataStore.DeleteSchemaVersion(connectionString, version, SchemaDataStore.Failed);

SchemaDataStore.ExecuteScriptAndCompleteSchemaVersion(connectionString, script, version);

Console.WriteLine(string.Format(Resources.SchemaMigrationSuccessMessage, version));
}

private static async Task<string> GetScript(ISchemaClient schemaClient, int version, Uri scriptUri, Uri diffUri = null)
{
if (version == 1)
{
return await schemaClient.GetScript(scriptUri);
}

return await schemaClient.GetDiffScript(diffUri);
}

private static async Task ValidateCompatibleVersion(ISchemaClient schemaClient, int minAvailableVersion, int maxAvailableVersion)
{
CompatibleVersion compatibleVersion = await schemaClient.GetCompatibility();

// check if min and max available versions are not in compatibile range
if (minAvailableVersion < compatibleVersion.Min || maxAvailableVersion > compatibleVersion.Max)
{
throw new SchemaManagerException(string.Format(Resources.VersionIncompatibilityMessage, maxAvailableVersion));
}
}

private static async Task ValidateInstancesVersion(ISchemaClient schemaClient, int version)
{
List<CurrentVersion> currentVersions = await schemaClient.GetCurrentVersionInformation();

// check if any instance is not running on the previous version
if (currentVersions.Any(currentVersion => currentVersion.Id != (version - 1) && currentVersion.Servers.Count > 0))
{
throw new SchemaManagerException(string.Format(Resources.InvalidVersionMessage, version));
}
}

private static bool EnsureForce()
{
Console.WriteLine(Resources.ForceWarning);
return string.Equals(Console.ReadLine(), "yes", StringComparison.OrdinalIgnoreCase);
}
}
}
77 changes: 77 additions & 0 deletions tools/SchemaManager/Commands/AvailableCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.CommandLine.Invocation;
using System.CommandLine.Rendering;
using System.CommandLine.Rendering.Views;
using System.Net.Http;
using System.Threading.Tasks;
using SchemaManager.Exceptions;
using SchemaManager.Model;
using SchemaManager.Utils;

namespace SchemaManager.Commands
{
public static class AvailableCommand
{
public static async Task Handler(InvocationContext invocationContext, Uri server)
{
var region = new Region(
0,
0,
Console.WindowWidth,
Console.WindowHeight,
true);

List<AvailableVersion> availableVersions = null;
ISchemaClient schemaClient = new SchemaClient(server);

try
{
availableVersions = await schemaClient.GetAvailability();
}
catch (SchemaManagerException ex)
{
CommandUtils.PrintError(ex.Message);
return;
}
catch (HttpRequestException)
{
CommandUtils.PrintError(string.Format(Resources.RequestFailedMessage, server));
return;
}

var tableView = new TableView<AvailableVersion>
{
Items = availableVersions,
};

tableView.AddColumn(
cellValue: availableVersion => availableVersion.Id,
header: new ContentView("Version"));

tableView.AddColumn(
cellValue: availableVersion => availableVersion.Script,
header: new ContentView("Script"));

tableView.AddColumn(
cellValue: availableVersion => availableVersion.Diff,
header: new ContentView("Diff"));

var consoleRenderer = new ConsoleRenderer(
invocationContext.Console,
mode: invocationContext.BindingContext.OutputMode(),
resetAfterRender: true);

using (var screen = new ScreenView(renderer: consoleRenderer))
{
screen.Child = tableView;
screen.Render(region);
}
}
}
}
Loading

0 comments on commit b21ac31

Please sign in to comment.