// // 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.Linq; using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Scripting { /// /// Implements matching logic to filter scripting objects based on an /// include/exclude criteria. /// /// /// First, objects are included by the include filter. Then, objects are removed by /// the exclude filter. Matches are made by comparing case insensitive strings for the /// ScriptingObject Type, Schema, and Name properties. Wildcards '*' are supported for /// the ScriptingObject Schema and Name properties. Matching on ScriptingObject Type /// property must be an exact match. /// /// Examples: /// /// Include ScriptingObject { Type = null, Schema = "dbo", Name = null } /// -> matches all objects in the dbo schema. /// /// Include ScriptingObject { Type = "Table", Schema = "dbo", Name = null } /// -> matches all tables in the dbo schema. /// /// Include ScriptingObject { Type = "Table", Schema = null, Name = "Emp*" } /// -> matches all table names that start with "Emp" /// /// Include ScriptingObject { Type = "View", Schema = null, Name = "Emp*" } /// Include ScriptingObject { Type = "Table", Schema = null, Name = "Emp*" } /// -> matches all table and views with names that start with "Emp" /// /// Include ScriptingObject { Type = "Table", Schema = null, Name = null } /// Exclude ScriptingObject { Type = null, Schema = "HumanResources", Name = null } /// -> matches all tables except tables in the "HumanResources" schema /// /// public static class ScriptingObjectMatcher { private const string Wildcard = "*"; /// /// Given a collection of candidate scripting objects, filters the items that match /// based on the passed include and exclude criteria. /// /// The include object criteria. /// The exclude object criteria. /// The include schema filter. /// The exclude schema filter. /// The include type filter. /// The exclude type filter. /// The candidate object to filter. /// The matching scripting objects. public static IEnumerable Match( ScriptingObject includeCriteria, ScriptingObject excludeCriteria, string includeSchemas, string excludeSchemas, string includeTypes, string excludeTypes, IEnumerable candidates) { return Match( includeCriteria == null ? new ScriptingObject[0] : new[] { includeCriteria }, excludeCriteria == null ? new ScriptingObject[0] : new[] { excludeCriteria }, includeSchemas == null ? new List(): new List { includeSchemas }, excludeSchemas == null ? new List(): new List { excludeSchemas }, includeTypes == null ? new List(): new List { includeTypes }, excludeTypes == null ? new List(): new List { excludeTypes }, candidates); } /// /// Given a collection of candidate scripting objects, filters the items that match /// based on the passed include and exclude criteria. /// /// The collection of include object criteria items. /// The collection of exclude object criteria items. /// The collection of include schema items. /// The collection of exclude schema items. /// The collection of include type items. /// The collection of exclude type items. /// The candidate object to filter. /// The matching scripting objects. public static IEnumerable Match( IEnumerable includeCriteria, IEnumerable excludeCriteria, IEnumerable includeSchemas, IEnumerable excludeSchemas, IEnumerable includeTypes, IEnumerable excludeTypes, IEnumerable candidates) { Validate.IsNotNull("candidates", candidates); IEnumerable matchedObjects = new List(); if (includeCriteria != null && includeCriteria.Any()) { foreach (ScriptingObject scriptingObjectCriteria in includeCriteria) { IEnumerable matches = MatchCriteria(scriptingObjectCriteria, candidates); matchedObjects = matchedObjects.Union(matches); } } else { matchedObjects = candidates; } if (excludeCriteria != null && excludeCriteria.Any()) { foreach (ScriptingObject scriptingObjectCriteria in excludeCriteria) { IEnumerable matches = MatchCriteria(scriptingObjectCriteria, matchedObjects); matchedObjects = matchedObjects.Except(matches); } } // Apply additional filters if included. matchedObjects = ExcludeSchemaAndOrType(excludeSchemas, excludeTypes, matchedObjects); matchedObjects = IncludeSchemaAndOrType(includeSchemas, includeTypes, matchedObjects); return matchedObjects; } private static IEnumerable ExcludeSchemaAndOrType(IEnumerable excludeSchemas, IEnumerable excludeTypes, IEnumerable candidates) { // Given a list of candidates, we remove any objects that match the excluded schema and/or type. IEnumerable remainingObjects = candidates; IEnumerable matches = null; if (excludeSchemas != null && excludeSchemas.Any()) { foreach (string exclude_schema in excludeSchemas) { matches = MatchCriteria(exclude_schema, (candidate) => { return candidate.Schema; }, candidates); remainingObjects = remainingObjects.Except(matches); } } if (excludeTypes != null && excludeTypes.Any()) { foreach (string exclude_type in excludeTypes) { matches = remainingObjects.Where(o => string.Equals(exclude_type, o.Type, StringComparison.OrdinalIgnoreCase)); remainingObjects = remainingObjects.Except(matches); } } return remainingObjects; } private static IEnumerable IncludeSchemaAndOrType(IEnumerable includeSchemas, IEnumerable includeTypes, IEnumerable candidates) { // Given a list of candidates, we return a new list of scripting objects that match // the schema and/or type filter. IEnumerable matchedSchema = new List(); IEnumerable matchedType = new List(); IEnumerable matchedObjects = new List(); IEnumerable matches = null; if (includeSchemas != null && includeSchemas.Any()) { foreach (string include_schema in includeSchemas) { matches = MatchCriteria(include_schema, (candidate) => { return candidate.Schema; }, candidates); matchedSchema = matchedSchema.Union(matches); } matchedObjects = matchedSchema; } else { matchedObjects = candidates; } if (includeTypes != null && includeTypes.Any()) { foreach (string include_type in includeTypes) { matches = matchedObjects.Where(o => string.Equals(include_type, o.Type, StringComparison.OrdinalIgnoreCase)); matchedType = matchedType.Union(matches); } matchedObjects = matchedType; } return matchedObjects; } private static IEnumerable MatchCriteria(ScriptingObject criteria, IEnumerable candidates) { Validate.IsNotNull("criteria", criteria); Validate.IsNotNull("candidates", candidates); IEnumerable matchedObjects = candidates; if (!string.IsNullOrWhiteSpace(criteria.Type)) { matchedObjects = matchedObjects.Where(o => string.Equals(criteria.Type, o.Type, StringComparison.OrdinalIgnoreCase)); } matchedObjects = MatchCriteria(criteria.Schema, (candidate) => { return candidate.Schema; }, matchedObjects); matchedObjects = MatchCriteria(criteria.Name, (candidate) => { return candidate.Name; }, matchedObjects); return matchedObjects; } private static IEnumerable MatchCriteria(string property, Func propertySelector, IEnumerable candidates) { IEnumerable matchedObjects = candidates; if (!string.IsNullOrWhiteSpace(property)) { if (property.Equals(Wildcard, StringComparison.OrdinalIgnoreCase)) { // Don't filter any objects } if (property.EndsWith(Wildcard, StringComparison.OrdinalIgnoreCase)) { matchedObjects = candidates.Where( o => propertySelector(o) != null && propertySelector(o).StartsWith( propertySelector(o).Substring(0, propertySelector(o).Length - 1), StringComparison.OrdinalIgnoreCase)); } else { matchedObjects = matchedObjects.Where(o => string.Equals(property, propertySelector(o), StringComparison.OrdinalIgnoreCase)); } } return matchedObjects; } } }