mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Add scripting API implemented by the SqlScriptPublishModel (#316)
Update the ScriptingService to expose new scripting JSON-RPC APIs that use the SqlScriptPublishModel for script generation. The SqlScriptPublishModel is the model behind the SSMS scripting wizard. To enable scripting for CLI tools, we've ported SqlScriptPublishModel to .NET Core. The SqlScriptPublishModel wraps the SMO scripting APIs for .sql script generation. 1) Added three new requests to the ScriptingService: ScriptingRequest, ScriptingListObjectsRequest, ScriptingCancelRequest. 2) Generating scripts are long running operations, so the ScriptingRequest and ScriptingListObjectsRequest kick off a long running scripting task and return immediately. 3) Long running scripting task reports progress and completion, and can be cancelled by a ScriptingCancelRequest request. 4) Bumped the SMO nuget package to 140.17049.0. This new version contains a signed SSMS_Rel build of SMO with the SqlScriptPublishModel. 5) For testing, adding the Northwind database schema TODO (in later pull requests) 1) Integrate the new ScriptingService APIs with the ConnectionService 2) Integrate with the metadata support recently added
This commit is contained in:
@@ -50,18 +50,25 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(methodName))
|
||||
try
|
||||
{
|
||||
var methods = type.GetMethods().Where(x => x.CustomAttributes.Any(a => a.AttributeType == typeof(FactAttribute)));
|
||||
foreach (var method in methods)
|
||||
if (string.IsNullOrEmpty(methodName))
|
||||
{
|
||||
await RunTest(type, method, method.Name);
|
||||
var methods = type.GetMethods().Where(x => x.CustomAttributes.Any(a => a.AttributeType == typeof(FactAttribute)));
|
||||
foreach (var method in methods)
|
||||
{
|
||||
await RunTest(type, method, method.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo methodInfo = type.GetMethod(methodName);
|
||||
await RunTest(type, methodInfo, test);
|
||||
}
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
MethodInfo methodInfo = type.GetMethod(methodName);
|
||||
await RunTest(type, methodInfo, test);
|
||||
RunTestCleanup(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,5 +99,24 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunTestCleanup(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodInfo cleanupMethod = type.GetMethod("Cleanup");
|
||||
if (cleanupMethod != null)
|
||||
{
|
||||
cleanupMethod.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"An exception occurred running Cleanup for type {0}: {1}",
|
||||
type.FullName,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Scripting service end-to-end integration tests that use the SqlScriptPublishModel type to generate scripts.
|
||||
/// </summary>
|
||||
public class SqlScriptPublishModelTests : IClassFixture<SqlScriptPublishModelTests.ScriptingFixture>
|
||||
{
|
||||
public SqlScriptPublishModelTests(ScriptingFixture scriptingFixture)
|
||||
{
|
||||
this.Fixture = scriptingFixture;
|
||||
}
|
||||
|
||||
public ScriptingFixture Fixture { get; private set; }
|
||||
|
||||
public SqlTestDb Northwind { get { return this.Fixture.Database; } }
|
||||
|
||||
[Fact]
|
||||
public async Task ListSchemaObjects()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingListObjectsParams requestParams = new ScriptingListObjectsParams
|
||||
{
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
};
|
||||
|
||||
ScriptingListObjectsResult result = await testService.ListScriptingObjects(requestParams);
|
||||
ScriptingListObjectsCompleteParams completeParameters = await testService.Driver.WaitForEvent(ScriptingListObjectsCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.Equal<int>(ScriptingFixture.ObjectCountWithoutDatabase, completeParameters.DatabaseObjects.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptDatabaseSchema()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaOnly",
|
||||
},
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingPlanNotificationParams planEvent = await testService.Driver.WaitForEvent(ScriptingPlanNotificationEvent.Type, TimeSpan.FromSeconds(30));
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.True(parameters.Success);
|
||||
Assert.Equal<int>(ScriptingFixture.ObjectCountWithDatabase, planEvent.Count);
|
||||
Assert.True(File.Exists(tempFile.FilePath));
|
||||
Assert.True(new FileInfo(tempFile.FilePath).Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptDatabaseSchemaAndData()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaAndData",
|
||||
},
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingPlanNotificationParams planEvent = await testService.Driver.WaitForEvent(ScriptingPlanNotificationEvent.Type, TimeSpan.FromSeconds(30));
|
||||
ScriptingCompleteParams completeParameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.True(completeParameters.Success);
|
||||
Assert.Equal<int>(ScriptingFixture.ObjectCountWithDatabase, planEvent.Count);
|
||||
Assert.True(File.Exists(tempFile.FilePath));
|
||||
Assert.True(new FileInfo(tempFile.FilePath).Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptTable()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaOnly",
|
||||
},
|
||||
ScriptingObjects = new List<ScriptingObject>
|
||||
{
|
||||
new ScriptingObject
|
||||
{
|
||||
Type = "Table",
|
||||
Schema = "dbo",
|
||||
Name = "Customers",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingPlanNotificationParams planEvent = await testService.Driver.WaitForEvent(ScriptingPlanNotificationEvent.Type, TimeSpan.FromSeconds(30));
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.True(parameters.Success);
|
||||
Assert.Equal<int>(2, planEvent.Count);
|
||||
Assert.True(File.Exists(tempFile.FilePath));
|
||||
Assert.True(new FileInfo(tempFile.FilePath).Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptTableUsingIncludeFilter()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaOnly",
|
||||
},
|
||||
IncludeObjectCriteria = new List<ScriptingObject>
|
||||
{
|
||||
new ScriptingObject
|
||||
{
|
||||
Type = "Table",
|
||||
Schema = "dbo",
|
||||
Name = "Customers",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingPlanNotificationParams planEvent = await testService.Driver.WaitForEvent(ScriptingPlanNotificationEvent.Type, TimeSpan.FromSeconds(30));
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.True(parameters.Success);
|
||||
Assert.Equal<int>(2, planEvent.Count);
|
||||
Assert.True(File.Exists(tempFile.FilePath));
|
||||
Assert.True(new FileInfo(tempFile.FilePath).Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptTableAndData()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaAndData",
|
||||
},
|
||||
ScriptingObjects = new List<ScriptingObject>
|
||||
{
|
||||
new ScriptingObject
|
||||
{
|
||||
Type = "Table",
|
||||
Schema = "dbo",
|
||||
Name = "Customers",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingPlanNotificationParams planEvent = await testService.Driver.WaitForEvent(ScriptingPlanNotificationEvent.Type, TimeSpan.FromSeconds(30));
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(30));
|
||||
Assert.True(parameters.Success);
|
||||
Assert.Equal<int>(2, planEvent.Count);
|
||||
Assert.True(File.Exists(tempFile.FilePath));
|
||||
Assert.True(new FileInfo(tempFile.FilePath).Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptTableDoesNotExist()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaOnly",
|
||||
},
|
||||
ScriptingObjects = new List<ScriptingObject>
|
||||
{
|
||||
new ScriptingObject
|
||||
{
|
||||
Type = "Table",
|
||||
Schema = "dbo",
|
||||
Name = "TableDoesNotExist",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(15));
|
||||
Assert.True(parameters.HasError);
|
||||
Assert.Equal("An error occurred while scripting the objects.", parameters.ErrorMessage);
|
||||
Assert.Contains("The Table '[dbo].[TableDoesNotExist]' does not exist on the server.", parameters.ErrorDetails);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptSchemaCancel()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = this.Northwind.ConnectionString,
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaAndData",
|
||||
},
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingCancelResult cancelResult = await testService.CancelScript(result.OperationId);
|
||||
ScriptingCompleteParams cancelEvent = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(10));
|
||||
Assert.True(cancelEvent.Canceled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptSchemaInvalidConnectionString()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
using (SelfCleaningTempFile tempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = tempFile.FilePath,
|
||||
ConnectionString = "I'm an invalid connection string",
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaAndData",
|
||||
},
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(10));
|
||||
Assert.True(parameters.HasError);
|
||||
Assert.Equal("Error parsing ScriptingParams.ConnectionString property.", parameters.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScriptSchemaInvalidFilePath()
|
||||
{
|
||||
using (TestServiceDriverProvider testService = new TestServiceDriverProvider())
|
||||
{
|
||||
ScriptingParams requestParams = new ScriptingParams
|
||||
{
|
||||
FilePath = "This path doesn't event exist",
|
||||
ConnectionString = "Server=Temp;Database=Temp;User Id=Temp;Password=Temp",
|
||||
ScriptOptions = new ScriptOptions
|
||||
{
|
||||
TypeOfDataToScript = "SchemaAndData",
|
||||
},
|
||||
};
|
||||
|
||||
ScriptingResult result = await testService.Script(requestParams);
|
||||
ScriptingCompleteParams parameters = await testService.Driver.WaitForEvent(ScriptingCompleteEvent.Type, TimeSpan.FromSeconds(10));
|
||||
Assert.True(parameters.HasError);
|
||||
Assert.Equal("Invalid directory specified by the ScriptingParams.FilePath property.", parameters.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public class ScriptingFixture : IDisposable
|
||||
{
|
||||
public ScriptingFixture()
|
||||
{
|
||||
this.Database = SqlTestDb.CreateNew(TestServerType.OnPrem);
|
||||
this.Database.RunQuery(Scripts.CreateNorthwindSchema, throwOnError: true);
|
||||
Console.WriteLine("Northwind setup complete, database name: {0}", this.Database.DatabaseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The count of object when scripting the entire database including the database object.
|
||||
/// </summary>
|
||||
public const int ObjectCountWithDatabase = 46;
|
||||
|
||||
/// <summary>
|
||||
/// The count of objects when scripting the entire database excluding the database object.
|
||||
/// </summary>
|
||||
public const int ObjectCountWithoutDatabase = 45;
|
||||
|
||||
public SqlTestDb Database { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.Database != null)
|
||||
{
|
||||
Console.WriteLine("Northwind cleanup, deleting database name: {0}", this.Database.DatabaseName);
|
||||
this.Database.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user