Implements Contextualization API into Azure Data Studio to get better query recommendations from extensions like Copilot (#2159)

* Add contract to get all metadata request

* Add new metadata service request endpoint

* Adds factory to make database server scripts

* Minor clean up

* Corrects filename typo

* Cleans up SmoScripterFactory

* Stubs out metadata cacher

* Method clean up

* Add writing and reading to script cache

* Cleans up request endpoint flow

* Add missing edge case when cache isn't empty

* Remove unused code

* Remove unneeded null check

* Read to end of stream

* Passes correct parameter to write cache

* Adds integration test to get all scripts

* Renames new request endpoint

* Rename request class to AllServerMetadataRequest

* Renames server metadata request endpoints

* Refresh cache and adjusts return obj type

* Clean up

* Assert table script generation

* Minor cache refresh adjustment

* Ensure test create table script is accurate

* Code review changes

* Additional code review changes

* Swap logger write for logger warning

* Renames generate request endpoint methods

* Remove unused using statement

* Remove unnecessary create table check

* Check if previous script file is valid for reuse

* Pascal case for method name

* Code review changes

* Fix PR issues

* Update doc comment

* Fixes tests after code review changes

* Fix failing int. test due to 30 day temp file expiry

* Generalize type names and update request endpoint

* Updates doc comment.

* Remove 'database' from type and method names

* Code review changes

* Code review changes

* Issues with background thread.

* Remove thread sleep for test reliability

* Remove reflection from int. tests
This commit is contained in:
Lewis Sanchez
2023-08-22 12:28:32 -07:00
committed by GitHub
parent ac8e4d6803
commit 08e855aa1d
6 changed files with 627 additions and 23 deletions

View File

@@ -0,0 +1,26 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
{
public class GenerateServerContextualizationParams
{
/// <summary>
/// The URI of the connection to generate context for.
/// </summary>
public string OwnerUri { get; set; }
}
/// <summary>
/// Event set after a connection to a server is completed.
/// </summary>
public class GenerateServerContextualizationNotification
{
public static readonly EventType<GenerateServerContextualizationParams> Type =
EventType<GenerateServerContextualizationParams>.Create("metadata/generateServerContext");
}
}

View File

@@ -0,0 +1,31 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
{
public class GetServerContextualizationParams
{
/// <summary>
/// The URI of the connection to generate scripts for.
/// </summary>
public string OwnerUri { get; set; }
}
public class GetServerContextualizationResult
{
/// <summary>
/// An array containing the generated server context.
/// </summary>
public string[] Context { get; set; }
}
public class GetServerContextualizationRequest
{
public static readonly RequestType<GetServerContextualizationParams, GetServerContextualizationResult> Type =
RequestType<GetServerContextualizationParams, GetServerContextualizationResult>.Create("metadata/getServerContext");
}
}

View File

@@ -0,0 +1,140 @@
//
// 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.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
/// <summary>
/// This class is responsible for reading, writing, and checking the validity of script files.
/// </summary>
public static class MetadataScriptTempFileStream
{
private const short ScriptFileExpirationInDays = 30;
/// <summary>
/// This method writes the passed in scripts to a temporary file.
/// </summary>
/// <param name="serverName">The name of the server which will go on to become the name of the file.</param>
/// <param name="scripts">The generated scripts that will be written to the temporary file.</param>
public static void Write(string serverName, IEnumerable<string> scripts)
{
var encodedServerName = Base64Encode(serverName);
var tempFileName = $"{encodedServerName}.tmp";
var generatedScripts = scripts.ToList();
try
{
var tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName);
using (StreamWriter sw = new StreamWriter(tempFilePath, false))
{
foreach (var script in generatedScripts)
{
sw.WriteLine(script);
}
}
}
catch (Exception ex)
{
Logger.Warning($"Failed to write scripts to temporary file. Error: {ex.Message}");
throw;
}
}
/// <summary>
/// Reads the scripts associated with the provided server name.
/// </summary>
/// <param name="serverName">The name of the server to retrieve the scripts for.</param>
/// <returns>List containing all the scripts in the file.</returns>
public static IEnumerable<string> Read(string serverName)
{
var encodedServerName = Base64Encode(serverName);
var tempFileName = $"{encodedServerName}.tmp";
var scripts = new List<string>();
try
{
var tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName);
if (!File.Exists(tempFilePath))
{
return scripts;
}
using (StreamReader sr = new StreamReader(tempFilePath))
{
while (!sr.EndOfStream)
{
var line = sr.ReadLine();
if (!String.IsNullOrWhiteSpace(line))
{
scripts.Add(line);
}
}
}
}
catch (Exception ex)
{
Logger.Warning($"Failed to read scripts from temporary file. Error: {ex.Message}");
throw;
}
return scripts;
}
/// <summary>
/// Determines if the script file for a server is too old and needs to be updated
/// </summary>
/// <param name="serverName">The name of the file associated with the given server name.</param>
/// <returns>True: The file was created within the expiration period; False: The script file needs to be created
/// or updated because it is too old.</returns>
public static bool IsScriptTempFileUpdateNeeded(string serverName)
{
var encodedServerName = Base64Encode(serverName);
var tempFileName = $"{encodedServerName}.tmp";
try
{
var tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName);
if (!File.Exists(tempFilePath))
{
return true;
}
else
{
/**
* Generated scripts don't need to be super up to date, so 30 days was chosen as the amount of time
* before the scripts are re-generated. This expiration date may change in the future,
* but for now this is what we're going with.
*/
var lastWriteTime = File.GetLastWriteTime(tempFilePath);
var isUpdateNeeded = (DateTime.Now - lastWriteTime).TotalDays < ScriptFileExpirationInDays ? false : true;
return isUpdateNeeded;
}
}
catch (Exception ex)
{
Logger.Warning($"Unable to determine if the script file is older than {ScriptFileExpirationInDays} days. Error: {ex.Message}");
throw;
}
}
/// <summary>
/// Encodes a string to it's base 64 string representation.
/// </summary>
/// <param name="str">The string to base64 encode.</param>
/// <returns>Base64 encoded string.</returns>
private static string Base64Encode(string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
return Convert.ToBase64String(bytes);
}
}
}

View File

@@ -7,14 +7,17 @@
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.SqlCore.Metadata;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
@@ -56,6 +59,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest, true);
serviceHost.SetRequestHandler(TableMetadataRequest.Type, HandleGetTableRequest, true);
serviceHost.SetRequestHandler(ViewMetadataRequest.Type, HandleGetViewRequest, true);
serviceHost.SetEventHandler(GenerateServerContextualizationNotification.Type, HandleGenerateServerContextualizationNotification, true);
serviceHost.SetRequestHandler(GetServerContextualizationRequest.Type, HandleGetServerContextualizationRequest, true);
}
/// <summary>
@@ -116,6 +121,113 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
await HandleGetTableOrViewRequest(metadataParams, "view", requestContext);
}
/// <summary>
/// Handles the event for generating server contextualization scripts.
/// </summary>
internal static Task HandleGenerateServerContextualizationNotification(GenerateServerContextualizationParams contextualizationParams,
EventContext eventContext)
{
_ = Task.Factory.StartNew(() =>
{
GenerateServerContextualization(contextualizationParams);
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
return Task.CompletedTask;
}
/// <summary>
/// Generates the contextualization scripts for a server. The generated context is in the form of create scripts for
/// database objects like tables and views.
/// </summary>
/// <param name="contextualizationParams">The contextualization parameters.</param>
internal static void GenerateServerContextualization(GenerateServerContextualizationParams contextualizationParams)
{
MetadataService.ConnectionServiceInstance.TryFindConnection(contextualizationParams.OwnerUri, out ConnectionInfo connectionInfo);
if (connectionInfo != null)
{
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connectionInfo, "metadata"))
{
// If scripts have been generated within the last 30 days then there isn't a need to go through the process
// of generating scripts again.
if (!MetadataScriptTempFileStream.IsScriptTempFileUpdateNeeded(connectionInfo.ConnectionDetails.ServerName))
{
return;
}
var scripts = SmoScripterHelpers.GenerateAllServerTableScripts(sqlConn);
if (scripts != null)
{
try
{
MetadataScriptTempFileStream.Write(connectionInfo.ConnectionDetails.ServerName, scripts);
}
catch (Exception ex)
{
Logger.Error($"An error was encountered while writing to the cache. Error: {ex.Message}");
}
}
else
{
Logger.Error("Failed to generate server scripts");
}
}
}
}
/// <summary>
/// Handles the request for getting database server contextualization scripts.
/// </summary>
internal static Task HandleGetServerContextualizationRequest(GetServerContextualizationParams contextualizationParams,
RequestContext<GetServerContextualizationResult> requestContext)
{
_ = Task.Factory.StartNew(async () =>
{
await GetServerContextualization(contextualizationParams, requestContext);
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
return Task.CompletedTask;
}
/// <summary>
/// Gets server contextualization scripts. The retrieved scripts are create scripts for database objects like tables and views.
/// </summary>
/// <param name="contextualizationParams">The contextualization parameters to get context.</param>
/// <param name="requestContext">The request context for the request.</param>
/// <returns></returns>
internal static async Task GetServerContextualization(GetServerContextualizationParams contextualizationParams, RequestContext<GetServerContextualizationResult> requestContext)
{
MetadataService.ConnectionServiceInstance.TryFindConnection(contextualizationParams.OwnerUri, out ConnectionInfo connectionInfo);
if (connectionInfo != null)
{
try
{
var scripts = MetadataScriptTempFileStream.Read(connectionInfo.ConnectionDetails.ServerName);
await requestContext.SendResult(new GetServerContextualizationResult
{
Context = scripts.ToArray()
});
}
catch (Exception ex)
{
Logger.Error("Failed to read scripts from the script cache");
await requestContext.SendError(ex);
}
}
else
{
Logger.Error("Failed to find connection info about the server.");
await requestContext.SendError("Failed to find connection info about the server.");
}
}
/// <summary>
/// Handle a table pr view metadata query request
/// </summary>

View File

@@ -0,0 +1,208 @@
//
// 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.Data.Common;
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.SqlCore.Connection;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Metadata
{
internal static class SmoScripterHelpers
{
public static IEnumerable<string>? GenerateAllServerTableScripts(DbConnection connection)
{
var serverConnection = SmoScripterHelpers.GetServerConnection(connection);
if (serverConnection == null)
{
return null;
}
Server server = new Server(serverConnection);
var scripts = SmoScripterHelpers.GenerateTableScripts(server);
return scripts;
}
private static ServerConnection? GetServerConnection(DbConnection connection)
{
// Get a connection to the database for SMO purposes
var sqlConnection = connection as SqlConnection ?? SmoScripterHelpers.TryFindingReliableSqlConnection(connection as ReliableSqlConnection);
if (sqlConnection == null)
{
return null;
}
var serverConnection = SmoScripterHelpers.ConnectToServerWithSmo(sqlConnection);
return serverConnection;
}
private static SqlConnection? TryFindingReliableSqlConnection(ReliableSqlConnection reliableSqlConnection)
{
// It's not actually a SqlConnection, so let's try a reliable SQL connection
if (reliableSqlConnection == null)
{
// If we don't have connection we can use with SMO, just give up on using SMO
return null;
}
// We have a reliable connection, use the underlying connection
return reliableSqlConnection.GetUnderlyingConnection();
}
private static ServerConnection ConnectToServerWithSmo(SqlConnection connection)
{
// Connect with SMO and get the metadata for the table
var serverConnection = (connection.AccessToken == null)
? new ServerConnection(connection)
: new ServerConnection(connection, new AzureAccessToken(connection.AccessToken));
return serverConnection;
}
private static IEnumerable<string> GenerateTableScripts(Server server)
{
var urns = SmoScripterHelpers.GetAllServerTableAndViewUrns(server);
var scriptingOptions = new ScriptingOptions
{
AgentAlertJob = false,
AgentJobId = false,
AgentNotify = false,
AllowSystemObjects = false,
AnsiFile = false,
AnsiPadding = false,
AppendToFile = false,
Bindings = false,
ChangeTracking = false,
ClusteredIndexes = false,
ColumnStoreIndexes = false,
ContinueScriptingOnError = true,
ConvertUserDefinedDataTypesToBaseType = false,
DdlBodyOnly = false,
DdlHeaderOnly = true,
DriAll = false,
DriAllConstraints = false,
DriAllKeys = false,
DriChecks = false,
DriClustered = false,
DriDefaults = false,
DriForeignKeys = false,
DriIncludeSystemNames = false,
DriIndexes = false,
DriNonClustered = false,
DriPrimaryKey = false,
DriUniqueKeys = false,
DriWithNoCheck = false,
EnforceScriptingOptions = true,
ExtendedProperties = false,
FullTextCatalogs = false,
FullTextIndexes = false,
FullTextStopLists = false,
IncludeDatabaseContext = false,
IncludeDatabaseRoleMemberships = false,
IncludeFullTextCatalogRootPath = false,
IncludeHeaders = false,
IncludeIfNotExists = false,
IncludeScriptingParametersHeader = false,
Indexes = false,
LoginSid = false,
NoAssemblies = true,
NoCollation = true,
NoCommandTerminator = true,
NoExecuteAs = true,
NoFileGroup = true,
NoFileStream = true,
NoFileStreamColumn = true,
NoIdentities = true,
NoIndexPartitioningSchemes = true,
NoMailProfileAccounts = true,
NoMailProfilePrincipals = true,
NonClusteredIndexes = false,
NoTablePartitioningSchemes = true,
NoVardecimal = false,
NoViewColumns = false,
NoXmlNamespaces = false,
OptimizerData = false,
Permissions = false,
PrimaryObject = true,
SchemaQualify = true,
SchemaQualifyForeignKeysReferences = true,
ScriptBatchTerminator = false,
ScriptData = false,
ScriptDataCompression = false,
ScriptDrops = false,
ScriptForAlter = false,
ScriptForCreateDrop = false,
ScriptForCreateOrAlter = true,
ScriptOwner = false,
ScriptSchema = true,
ScriptXmlCompression = false,
SpatialIndexes = false,
Statistics = false,
TimestampToBinary = false,
ToFileOnly = false,
Triggers = false,
WithDependencies = false,
XmlIndexes = false
};
var scripter = new Scripter(server);
scripter.Options = scriptingOptions;
var generatedScripts = scripter.Script(urns);
var scripts = new List<string>();
foreach (var s in generatedScripts)
{
// Needed to remove '\r' and '\n' characters from script, so that an entire create script
// can be written and read as a single line to and from a temp file. Since scripts aren't
// going to be read by people, and mainly sent to Copilot to generate accurate suggestions,
// a lack of formatting is fine.
var script = s.Replace("\r", string.Empty).Replace("\n", string.Empty);
scripts.Add(script);
}
return scripts;
}
private static UrnCollection GetAllServerTableAndViewUrns(Server server)
{
UrnCollection urnCollection = new UrnCollection();
foreach (Database db in server.Databases)
{
try
{
foreach (SqlServer.Management.Smo.Table t in db.Tables)
{
urnCollection.Add(t.Urn);
}
}
catch (Exception ex)
{
Logger.Warning($"Unable to get table URNs. Error: {ex.Message}");
}
try
{
foreach (SqlServer.Management.Smo.View v in db.Views)
{
urnCollection.Add(v.Urn);
}
}
catch (Exception ex)
{
Logger.Warning($"Unable to get view URNs. Error: {ex.Message}");
}
}
return urnCollection;
}
}
}

View File

@@ -5,6 +5,14 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
@@ -12,16 +20,10 @@ using Microsoft.SqlTools.ServiceLayer.Metadata;
using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts;
using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.SqlCore.Metadata;
using Moq;
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using static Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility.LiveConnectionHelper;
using Microsoft.SqlTools.SqlCore.Metadata;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
{
@@ -32,6 +34,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
{
private string testTableSchema = "dbo";
private string testTableName = "MetadataTestTable";
private string testTableName2 = "SecondMetadataTestTable";
private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects()
{
@@ -50,20 +53,20 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
return result;
}
private void CreateTestTable(SqlConnection sqlConn)
private void CreateTestTable(SqlConnection sqlConn, string testTableSchema, string testTableName)
{
string sql = string.Format("IF OBJECT_ID('{0}.{1}', 'U') IS NULL CREATE TABLE {0}.{1}(id int)",
this.testTableSchema, this.testTableName);
testTableSchema, testTableName);
using (var sqlCommand = new SqlCommand(sql, sqlConn))
{
sqlCommand.ExecuteNonQuery();
}
sqlCommand.ExecuteNonQuery();
}
}
private void DeleteTestTable(SqlConnection sqlConn)
private void DeleteTestTable(SqlConnection sqlConn, string testTableSchema, string testTableName)
{
string sql = string.Format("IF OBJECT_ID('{0}.{1}', 'U') IS NOT NULL DROP TABLE {0}.{1}",
this.testTableSchema, this.testTableName);
testTableSchema, testTableName);
using (var sqlCommand = new SqlCommand(sql, sqlConn))
{
sqlCommand.ExecuteNonQuery();
@@ -82,7 +85,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
var sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo);
Assert.NotNull(sqlConn);
CreateTestTable(sqlConn);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName);
var metadata = new List<ObjectMetadata>();
MetadataService.ReadMetadata(sqlConn, metadata);
@@ -100,18 +103,18 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
}
Assert.True(foundTestTable);
DeleteTestTable(sqlConn);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName);
}
[Test]
public async Task GetTableInfoReturnsValidResults()
{
this.testTableName += new Random().Next(1000000, 9999999).ToString();
var result = GetLiveAutoCompleteTestObjects();
var sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo);
CreateTestTable(sqlConn);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName);
var requestContext = new Mock<RequestContext<TableMetadataResult>>();
requestContext.Setup(x => x.SendResult(It.IsAny<TableMetadataResult>())).Returns(Task.FromResult(new object()));
@@ -125,15 +128,99 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
await MetadataService.HandleGetTableRequest(metadataParmas, requestContext.Object);
DeleteTestTable(sqlConn);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName);
requestContext.VerifyAll();
}
[Test]
public async Task VerifyGenerateServerContextualizationNotification()
{
this.testTableName += new Random().Next(1000000, 9999999).ToString();
this.testTableName2 += new Random().Next(0, 999999).ToString();
var connectionResult = LiveConnectionHelper.InitLiveConnectionInfo(null);
var sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName2);
var generateServerContextualizationParams = new GenerateServerContextualizationParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri
};
MetadataService.GenerateServerContextualization(generateServerContextualizationParams);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName2);
DeleteServerContextualizationTempFile(sqlConn.DataSource);
}
[Test]
public async Task VerifyGetServerContextualizationRequest()
{
this.testTableName += new Random().Next(1000000, 9999999).ToString();
this.testTableName2 += new Random().Next(1000000, 9999999).ToString();
var connectionResult = LiveConnectionHelper.InitLiveConnectionInfo(null);
var sqlConn = ConnectionService.OpenSqlConnection(connectionResult.ConnectionInfo);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName);
CreateTestTable(sqlConn, this.testTableSchema, this.testTableName2);
var generateServerContextualizationParams = new GenerateServerContextualizationParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri
};
MetadataService.GenerateServerContextualization(generateServerContextualizationParams);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName);
DeleteTestTable(sqlConn, this.testTableSchema, this.testTableName2);
var firstCreateTableScript = $"CREATE TABLE [{this.testTableSchema}].[{this.testTableName}](\t[id] [int] NULL)";
var secondCreateTableScript = $"CREATE TABLE [{this.testTableSchema}].[{this.testTableName2}](\t[id] [int] NULL)";
var mockGetServerContextualizationRequestContext = new Mock<RequestContext<GetServerContextualizationResult>>();
var actualGetServerContextualizationResponse = new GetServerContextualizationResult();
mockGetServerContextualizationRequestContext.Setup(x => x.SendResult(It.IsAny<GetServerContextualizationResult>()))
.Callback<GetServerContextualizationResult>(actual => actualGetServerContextualizationResponse = actual)
.Returns(Task.CompletedTask);
var getServerContextualizationParams = new GetServerContextualizationParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri
};
await MetadataService.GetServerContextualization(getServerContextualizationParams, mockGetServerContextualizationRequestContext.Object);
Assert.IsTrue(actualGetServerContextualizationResponse.Context.Contains(firstCreateTableScript));
Assert.IsTrue(actualGetServerContextualizationResponse.Context.Contains(secondCreateTableScript));
DeleteServerContextualizationTempFile(sqlConn.DataSource);
mockGetServerContextualizationRequestContext.VerifyAll();
}
private void DeleteServerContextualizationTempFile(string serverName)
{
var bytes = Encoding.UTF8.GetBytes(serverName);
var encodedServerName = Convert.ToBase64String(bytes);
var tempFileName = $"{encodedServerName}.tmp";
var tempFilePath = Path.Combine(Path.GetTempPath(), tempFileName);
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
}
}
[Test]
public async Task GetViewInfoReturnsValidResults()
{
var result = GetLiveAutoCompleteTestObjects();
{
var result = GetLiveAutoCompleteTestObjects();
var requestContext = new Mock<RequestContext<TableMetadataResult>>();
requestContext.Setup(x => x.SendResult(It.IsAny<TableMetadataResult>())).Returns(Task.FromResult(new object()));
@@ -166,7 +253,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
AS
RETURN SELECT 1 AS AccessResult
GO";
List<ObjectMetadata> expectedMetadataList = new List<ObjectMetadata>
{
new ObjectMetadata
@@ -254,7 +341,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Metadata
return result.Metadata == null;
}
if(expectedMetadataList.Count != result.Metadata.Length)
if (expectedMetadataList.Count != result.Metadata.Length)
{
return false;
}