Moving out legacy schemas into their own folder (#1866)

This commit is contained in:
Aasim Khan
2023-02-17 14:31:25 -08:00
committed by GitHub
parent b3ae394fa6
commit e8d24f8e47
10 changed files with 342 additions and 3 deletions

View File

@@ -1269,6 +1269,14 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string SchemaHierarchy_BuiltInSchema
{
get
{
return Keys.GetString(Keys.SchemaHierarchy_BuiltInSchema);
}
}
public static string SchemaHierarchy_Security
{
get
@@ -10674,6 +10682,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string SchemaHierarchy_Schemas = "SchemaHierarchy_Schemas";
public const string SchemaHierarchy_BuiltInSchema = "SchemaHierarchy_BuiltInSchema";
public const string SchemaHierarchy_Security = "SchemaHierarchy_Security";

View File

@@ -897,6 +897,10 @@
<value>Schemas</value>
<comment></comment>
</data>
<data name="SchemaHierarchy_BuiltInSchema" xml:space="preserve">
<value>Built-in Schemas</value>
<comment></comment>
</data>
<data name="SchemaHierarchy_Security" xml:space="preserve">
<value>Security</value>
<comment></comment>

View File

@@ -440,6 +440,8 @@ SchemaHierarchy_Rules = Rules
SchemaHierarchy_Schemas = Schemas
SchemaHierarchy_BuiltInSchema = Built-in Schemas
SchemaHierarchy_Security = Security
SchemaHierarchy_ServerObjects = Server Objects

View File

@@ -6545,6 +6545,11 @@ The Query Processor estimates that implementing the following index could improv
<target state="new">Sequence Number End</target>
<note></note>
</trans-unit>
<trans-unit id="SchemaHierarchy_BuiltInSchema">
<source>Built-in Schemas</source>
<target state="new">Built-in Schemas</target>
<note></note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -41,6 +41,18 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
/// </summary>
public Type TypeToReverse { get; set; } = default!;
/// <summary>
/// Indicates if the filter is a "not" filter. Eg (not(@IsSystemObject = 0))
/// </summary>
public bool IsNotFilter { get; set; } = false;
/// <summary>
/// Indicates the type of the filter. It can be EQUALS, DATETIME, FALSE or CONTAINS
/// More information can be found here:
/// https://learn.microsoft.com/en-us/sql/powershell/query-expressions-and-uniform-resource-names?view=sql-server-ver16#examples
/// </summary>
public FilterType FilterType { get; set; } = FilterType.EQUALS;
/// <summary>
/// Returns true if the filter can be apply to the given type and Server type
/// </summary>
@@ -82,8 +94,35 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
propertyValue = (int)Convert.ChangeType(value, Type);
}
string filterText = string.Empty;
switch (FilterType)
{
case FilterType.EQUALS:
filterText = $"@{Property} = {propertyValue}";
break;
case FilterType.DATETIME:
filterText = $"@{Property} = datetime({propertyValue})";
break;
case FilterType.CONTAINS:
filterText = $"contains(@{Property}, {propertyValue})";
break;
case FilterType.FALSE:
filterText = $"@{Property} = false()";
break;
case FilterType.ISNULL:
filterText = $"isnull(@{Property})";
break;
}
string orPrefix = filter.Length == 0 ? string.Empty : " or ";
filter.Append($"{orPrefix}@{Property} = {propertyValue}");
if (IsNotFilter)
{
filter.Append($"{orPrefix}not({filterText})");
}
else
{
filter.Append($"{orPrefix}{filterText}");
}
}
if (filter.Length != 0)
@@ -93,4 +132,13 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
return string.Empty;
}
}
public enum FilterType
{
EQUALS,
DATETIME,
CONTAINS,
FALSE,
ISNULL
}
}

View File

@@ -22,6 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
Assemblies,
AsymmetricKeys,
BrokerPriorities,
BuiltInSchemas,
Certificates,
ColumnEncryptionKeys,
ColumnMasterKeys,

View File

@@ -680,6 +680,78 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
public override bool PutFoldersAfterNodes => true;
public override IEnumerable<string> ApplicableParents() { return new[] { nameof(NodeTypes.Database) }; }
public override IEnumerable<INodeFilter> Filters
{
get
{
var filters = new List<INodeFilter>();
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_accessadmin" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_backupoperator" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_datareader" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_datawriter" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_ddladmin" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_denydatareader" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_denydatawriter" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_owner" },
});
filters.Add(new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
IsNotFilter = true,
Values = new List<object> { "db_securityadmin" },
});
return filters;
}
}
protected override void OnExpandPopulateFolders(IList<TreeNode> currentChildren, TreeNode parent)
{
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.ObjectExplorer.GroupBySchema)
@@ -708,6 +780,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
IsSystemObject = false,
ValidFor = ValidForFlag.AllOnPrem|ValidForFlag.AzureV12,
SortPriority = SmoTreeNode.NextSortPriority,
});
}
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.ObjectExplorer.GroupBySchema)
{
currentChildren.Add(new FolderNode {
NodeValue = SR.SchemaHierarchy_BuiltInSchema,
NodeTypeId = NodeTypes.BuiltInSchemas,
IsSystemObject = false,
SortPriority = SmoTreeNode.NextSortPriority,
});
}
currentChildren.Add(new FolderNode {
@@ -767,6 +848,96 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
}
}
[Export(typeof(ChildFactory))]
[Shared]
internal partial class BuiltInSchemasChildFactory : SmoChildFactoryBase
{
public override IEnumerable<string> ApplicableParents() { return new[] { nameof(NodeTypes.BuiltInSchemas) }; }
public override IEnumerable<INodeFilter> Filters
{
get
{
var filters = new List<INodeFilter>();
filters.Add(new NodeOrFilter
{
FilterList = new List<NodePropertyFilter> {
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_accessadmin" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_backupoperator" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_datareader" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_datawriter" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_ddladmin" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_denydatareader" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_denydatawriter" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_owner" },
},
new NodePropertyFilter
{
Property = "Name",
Type = typeof(string),
Values = new List<object> { "db_securityadmin" },
},
}
});
return filters;
}
}
internal override Type[] ChildQuerierTypes
{
get
{
return new [] { typeof(SqlSchemaQuerier), };
}
}
public override TreeNode CreateChild(TreeNode parent, object context)
{
var child = new ExpandableSchemaTreeNode();
InitializeChild(parent, child, context);
return child;
}
}
[Export(typeof(ChildFactory))]
[Shared]
internal partial class ExpandableSchemaChildFactory : SmoChildFactoryBase

View File

@@ -621,6 +621,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
var propertyType = filter.GetAttribute("Type");
var propertyValue = filter.GetAttribute("Value");
var validFor = filter.GetAttribute("ValidFor");
var filterType = filter.GetAttribute("FilterType");
var isNotFiler = filter.GetAttribute("IsNotFilter");
var typeToReverse = filter.GetAttribute("TypeToReverse");
List<XmlElement> filterValues = GetNodeFilterValues(xmlFile, parentName, propertyName, orFilter);
@@ -637,9 +639,24 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
WriteLine(indent + " ValidFor = {0},", GetValidForFlags(validFor));
}
if (!string.IsNullOrWhiteSpace(filterType))
{
WriteLine(indent + " FilterType = FilterType.{0},", filterType.ToUpper());
}
if (!string.IsNullOrWhiteSpace(isNotFiler))
{
WriteLine(indent + " IsNotFilter = {0},", isNotFiler);
}
if (propertyValue != null && (filterValues == null || filterValues.Count == 0))
{
WriteLine(indent + " Values = new List<object> {{ {0} }},", propertyValue);
if (propertyType.Equals("string"))
{
WriteLine(indent + " Values = new List<object> {{ \"{0}\" }},", propertyValue);
}
else
{
WriteLine(indent + " Values = new List<object> {{ {0} }},", propertyValue);
}
}
if (filterValues != null && filterValues.Count > 0)
{
@@ -650,7 +667,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
string separator = (i != filterValues.Count - 1) ? "," : "";
var filterValue = filterValues[i];
WriteLine(indent + " {{ {0} }}{1}", filterValue.InnerText, separator);
if(propertyType.Equals("string"))
{
WriteLine(indent + " {{ \"{0}\" }}{1}", filterValue.InnerText, separator);
}
else
{
WriteLine(indent + " {{ {0} }}{1}", filterValue.InnerText, separator);
}
}
WriteLine(indent + " }");

View File

@@ -64,9 +64,26 @@
<Node Name="Database" LocLabel="string.Empty" Image="Database" BaseClass="ModelBased" NodeType="Database" IsAsyncLoad="" Strategy="CreateModel" TreeNode="ExpandableSchemaTreeNode" PutFoldersAfterNodes="true">
<ConditionalChildQuerierType Name="SqlSchema" SettingsFlag="GroupBySchema"/>
<Filters>
<!--
Excluding built-in schemas for backward compatibility from the database node and moving them to legacy schema folder.
The list of schemas was obtained from:
https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/ownership-and-user-schema-separation?view=sql-server-ver16#built-in-schemas-for-backward-compatibility
-->
<Filter Property="Name" Value="db_accessadmin" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_backupoperator" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_datareader" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_datawriter" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_ddladmin" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_denydatareader" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_denydatawriter" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_owner" Type="string" IsNotFilter="true"/>
<Filter Property="Name" Value="db_securityadmin" Type="string" IsNotFilter="true"/>
</Filters>
<Child Name="Tables" SettingsFlag="!GroupBySchema"/>
<Child Name="Views" SettingsFlag="!GroupBySchema"/>
<Child Name="Synonyms" SettingsFlag="!GroupBySchema"/>
<Child Name="BuiltInSchemas" SettingsFlag="GroupBySchema"/>
<Child Name="Programmability"/>
<Child Name="ExternalResources"/>
<Child Name="ServiceBroker"/>
@@ -74,6 +91,22 @@
<Child Name="Security"/>
</Node>
<Node Name="BuiltInSchemas" LocLabel="SR.SchemaHierarchy_BuiltInSchema" BaseClass="ModelBased" Strategy="MultipleElementsOfType" ChildQuerierTypes="SqlSchema" TreeNode="ExpandableSchemaTreeNode">
<Filters>
<Or>
<Filter Property="Name" Value="db_accessadmin" Type="string" />
<Filter Property="Name" Value="db_backupoperator" Type="string" />
<Filter Property="Name" Value="db_datareader" Type="string"/>
<Filter Property="Name" Value="db_datawriter" Type="string"/>
<Filter Property="Name" Value="db_ddladmin" Type="string"/>
<Filter Property="Name" Value="db_denydatareader" Type="string"/>
<Filter Property="Name" Value="db_denydatawriter" Type="string"/>
<Filter Property="Name" Value="db_owner" Type="string"/>
<Filter Property="Name" Value="db_securityadmin" Type="string"/>
</Or>
</Filters>
</Node>
<Node Name="ExpandableSchema" LocLabel="string.empty" BaseClass="ModelBased" Strategy="CreateModel">
<Child Name="Tables"/>
<Child Name="Views"/>

View File

@@ -255,6 +255,46 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectExplorer
});
}
[Test]
public async Task GroupBySchemaHidesLegacySchemas()
{
string query = @"Create schema t1
GO
Create schema t2
GO";
string databaseName = "#testDb#";
await RunTest(databaseName, query, "TestDb", async (testDbName, session) =>
{
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.ObjectExplorer = new ObjectExplorerSettings() { GroupBySchema = true };
var databaseNode = session.Root.ToNodeInfo();
var databaseChildren = await _service.ExpandNode(session, databaseNode.NodePath);
Assert.True(databaseChildren.Nodes.Any(t => t.Label == "t1"), "Non legacy schema node t1 should be found in database node when group by schema is enabled");
Assert.True(databaseChildren.Nodes.Any(t => t.Label == "t2"), "Non legacy schema node t2 should be found in database node when group by schema is enabled");
string[] legacySchemas = new string[]
{
"db_accessadmin",
"db_backupoperator",
"db_datareader",
"db_datawriter",
"db_ddladmin",
"db_denydatareader",
"db_denydatawriter",
"db_owner",
"db_securityadmin"
};
foreach(var nodes in databaseChildren.Nodes)
{
Assert.That(legacySchemas, Does.Not.Contain(nodes.Label), "Legacy schema node should not be found in database node when group by schema is enabled");
}
var legacySchemasNode = databaseChildren.Nodes.First(t => t.Label == SR.SchemaHierarchy_BuiltInSchema);
var legacySchemasChildren = await _service.ExpandNode(session, legacySchemasNode.NodePath);
foreach(var nodes in legacySchemasChildren.Nodes)
{
Assert.That(legacySchemas, Does.Contain(nodes.Label), "Legacy schema nodes should be found in legacy schemas folder when group by schema is enabled");
}
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.ObjectExplorer = new ObjectExplorerSettings() { GroupBySchema = false };
});
}
private async Task VerifyRefresh(ObjectExplorerSession session, string tablePath, string tableName, bool deleted = true)
{