mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-26 09:35:38 -05:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user