mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Moving out legacy schemas into their own folder (#1866)
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -440,6 +440,8 @@ SchemaHierarchy_Rules = Rules
|
||||
|
||||
SchemaHierarchy_Schemas = Schemas
|
||||
|
||||
SchemaHierarchy_BuiltInSchema = Built-in Schemas
|
||||
|
||||
SchemaHierarchy_Security = Security
|
||||
|
||||
SchemaHierarchy_ServerObjects = Server Objects
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
|
||||
Assemblies,
|
||||
AsymmetricKeys,
|
||||
BrokerPriorities,
|
||||
BuiltInSchemas,
|
||||
Certificates,
|
||||
ColumnEncryptionKeys,
|
||||
ColumnMasterKeys,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 + " }");
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user