From 5d5debbad6d88cc1820fcab976c80b1edb429e24 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Thu, 26 Aug 2021 13:06:15 -0700 Subject: [PATCH] Enable scripting for subobjects such as triggers (#1237) * WIP 1 * Cleanups * Change how schema is appended in to urn * Add comment and fix incorrect condition * Add parent type name to support Views, etc. --- .../Metadata/Contracts/ObjectMetadata.cs | 4 ++ .../ObjectExplorer/SmoModel/SmoTreeNode.cs | 22 ++++++++--- .../Scripting/Contracts/ScriptingObject.cs | 31 +++++++++++++--- .../Scripting/ScriptingExtensionMethods.cs | 37 +++++++++++++++---- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Metadata/Contracts/ObjectMetadata.cs b/src/Microsoft.SqlTools.ServiceLayer/Metadata/Contracts/ObjectMetadata.cs index f5d9e55f..8d837b3b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Metadata/Contracts/ObjectMetadata.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Metadata/Contracts/ObjectMetadata.cs @@ -30,6 +30,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts public string Schema { get; set; } public string Name { get; set; } + + public string ParentName { get; set; } + + public string ParentTypeName { get; set; } public string Urn { get; set; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs index 9f71e840..7c2e076c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs @@ -4,6 +4,7 @@ // using System.Globalization; +using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; @@ -56,13 +57,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel { SmoObject = smoObject; NodeValue = smoObject.Name; - ScriptSchemaObjectBase schemaBasecObject = smoObject as ScriptSchemaObjectBase; + ScriptSchemaObjectBase schemaBaseObject = smoObject as ScriptSchemaObjectBase; ObjectMetadata = new Metadata.Contracts.ObjectMetadata(); ObjectMetadata.Name = smoObject.Name; try { - if(smoObject.Urn != null) + if (smoObject.Urn != null) { ObjectMetadata.MetadataTypeName = smoObject.Urn.Type; } @@ -72,16 +73,27 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel //Ignore the exception, sometimes the urn returns exception and I' not sure why } - if (schemaBasecObject != null) + if (schemaBaseObject != null) { - ObjectMetadata.Schema = schemaBasecObject.Schema; + ObjectMetadata.Schema = schemaBaseObject.Schema; if (!string.IsNullOrEmpty(ObjectMetadata.Schema)) { NodeValue = $"{ObjectMetadata.Schema}.{smoObject.Name}"; } } + else + { + // Try to read the schema from the parent object + var parent = smoObject?.ParentCollection?.ParentInstance as ScriptSchemaObjectBase; + if (parent != null) + { + ObjectMetadata.Schema = parent.Schema; + ObjectMetadata.ParentName = parent.Name; + ObjectMetadata.ParentTypeName = parent.Urn.Type; + } + } } - + public virtual NamedSmoObject GetParentSmoObject() { if (SmoObject != null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Scripting/Contracts/ScriptingObject.cs b/src/Microsoft.SqlTools.ServiceLayer/Scripting/Contracts/ScriptingObject.cs index 6ecea44c..49dad72c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Scripting/Contracts/ScriptingObject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Scripting/Contracts/ScriptingObject.cs @@ -4,7 +4,6 @@ // using System; -using System.Collections.Generic; namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts { @@ -52,13 +51,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts /// public string Name { get; set; } + /// + /// Gets or sets the parent object name + /// + public string ParentName { get; set; } + + /// + /// Gets or sets the parent object type name, such as Table, View, etc. + /// + public string ParentTypeName { get; set; } + public override string ToString() { - string objectName = string.IsNullOrEmpty(this.Schema) - ? this.Name - : this.Schema + "." + this.Name; - - return objectName; + string objectName = string.Empty; + if (!string.IsNullOrEmpty(this.Schema)) + { + objectName += this.Schema + "."; + } + if (!string.IsNullOrEmpty(this.ParentName)) + { + objectName += this.ParentName + "."; + } + return objectName + this.Name; } public override int GetHashCode() @@ -66,6 +80,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts return StringComparer.OrdinalIgnoreCase.GetHashCode(this.Type ?? string.Empty) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(this.Schema ?? string.Empty) ^ + StringComparer.OrdinalIgnoreCase.GetHashCode(this.ParentName ?? string.Empty) ^ + StringComparer.OrdinalIgnoreCase.GetHashCode(this.ParentTypeName ?? string.Empty) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name ?? string.Empty); } @@ -87,6 +103,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts return string.Equals(this.Type, other.Type, StringComparison.OrdinalIgnoreCase) && string.Equals(this.Schema, other.Schema, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.ParentName, other.ParentName, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.ParentTypeName, other.ParentTypeName, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.ParentTypeName, other.ParentTypeName, StringComparison.OrdinalIgnoreCase) && string.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptingExtensionMethods.cs b/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptingExtensionMethods.cs index df13e3e1..7e4f6197 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptingExtensionMethods.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Scripting/ScriptingExtensionMethods.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.SqlScriptPublish; @@ -100,15 +101,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting Validate.IsNotNullOrWhitespaceString("scriptingObject.Type", scriptingObject.Type); // Leaving the server name blank will automatically match whatever the server SMO is running against. - string urn = string.Format( - "Server[@Name='{0}']/Database[@Name='{1}']/{2}[@Name='{3}' {4}]", - server.ToUpper(), - Urn.EscapeString(database), - scriptingObject.Type, - Urn.EscapeString(scriptingObject.Name), - scriptingObject.Schema != null ? string.Format("and @Schema = '{0}'", Urn.EscapeString(scriptingObject.Schema)) : string.Empty); + StringBuilder urnBuilder = new StringBuilder(); + urnBuilder.AppendFormat("Server[@Name='{0}']/", server.ToUpper()); + urnBuilder.AppendFormat("Database[@Name='{0}']/", Urn.EscapeString(database)); - return new Urn(urn); + bool hasParentObject = !string.IsNullOrWhiteSpace(scriptingObject.ParentName) + && !string.IsNullOrWhiteSpace(scriptingObject.ParentTypeName); + if (hasParentObject) + { + urnBuilder.AppendFormat("{0}[@Name='{1}'", scriptingObject.ParentTypeName, Urn.EscapeString(scriptingObject.ParentName)); + if (!string.IsNullOrWhiteSpace(scriptingObject.Schema)) + { + urnBuilder.AppendFormat(" and @Schema = '{0}'", Urn.EscapeString(scriptingObject.Schema)); + } + urnBuilder.Append("]/"); + } + + urnBuilder.AppendFormat("{0}[@Name='{1}'", scriptingObject.Type, Urn.EscapeString(scriptingObject.Name)); + + // add schema to object only if there is no parent object specified + // the parent object field is only set for objects that don't have schema themselves + // so if parent is not null then the schema filter will already be set that part of the urn above + if (!string.IsNullOrWhiteSpace(scriptingObject.Schema) && !hasParentObject) + { + urnBuilder.AppendFormat(" and @Schema = '{0}'", Urn.EscapeString(scriptingObject.Schema)); + } + + urnBuilder.Append("]"); + + return new Urn(urnBuilder.ToString()); } ///