/// <summary> /// Aplicação do template estendido para a sintaxe MANY MATCHES: /// - MANY [@parametro] [NOT] MATCHES [IF SET] ( ... ) /// </summary> /// <param name="text">O texto que sofrerá a substituição.</param> /// <param name="args">Argumentos disponíveis.</param> /// <param name="keyGen">Algoritmo de geração de nomes de parâmetros.</param> /// <returns></returns> private static string ReplaceManyMatches(string text, IDictionary <string, object> args, KeyGen keyGen) { // Aplicando a substituição da instrução: // many [target] [not] matches [if set] () var regex = new Regex(@"[(\s]many\s+(?:@([\w]+)\s+)?(?:(not)\s+)?matches(?:\s+(if\s+set))?\s*?(?:([a-zA-Z_.]+)|([a-zA-Z0-9_]*[(](?>[(](?<c>)|[^()]+|[)](?<-c>))*(?(c)(?!))[)]))"); var matches = regex.Matches(text); foreach (Match match in matches.Cast <Match>().Reverse()) { string replacement = ""; // Exemplos de textos extraidos // // " many matches ( ... ) " // [1] "" // [2] "" // [3] "" // -inutil- // [5] "( ... )" // // " many @parametro not matches if set ( ... ) " // [1] "parametro" // [2] "not" // [3] "if set" // -inutil- // [5] "( ... )" var bagName = match.Groups[1].Value; var isNot = (match.Groups[2].Length > 0); var isOptional = (match.Groups[3].Length > 0); var body = match.Groups[5].Value; body = body.Substring(1, body.Length - 2); var candidate = args.Get(bagName); var enumerable = (candidate as IEnumerable) ?? ((candidate as Var)?.Value as IEnumerable); var bags = enumerable?.OfType <IDictionary <string, object> >(); if (bags?.Any() != true) { replacement = isOptional ? "1=1" : "1=0"; } else { var sentences = new List <string>(); foreach (var bag in bags) { var sentence = body; var nestGen = keyGen.Derive(); foreach (var key in bag.Keys) { var matches2 = Regex.Matches(sentence, $@"@({key}\w?)"); foreach (Match match2 in matches2.Cast <Match>().Reverse()) { var matchKey = match2.Groups[1].Value; if (matchKey != key) { continue; } var index = match2.Groups[1].Index; var count = match2.Groups[1].Length; var keyName = nestGen.DeriveName(key); var keyValue = bag[key]; args[keyName] = keyValue; sentence = sentence.Stuff(index, count, keyName); } } sentence = ReplaceTemplates(sentence, args, nestGen); sentence = $"{(isNot ? " not " : "")}({sentence})"; sentences.Add(sentence); } replacement = $" ({string.Join(" or ", sentences.Distinct())})"; } text = text.Stuff(match.Index, match.Length, $" {replacement}"); } return(text); }
/// <summary> /// Constrói a condição que substituirá o template baseado na instrução: /// - target [always] matches @parametro /// /// Se o valor do parâmetro não tiver sido definido o critério retornado será nulo. /// /// O critério criado contém o termo "{0}" que deve ser substituído pela /// parte "target" à direta da instrução matches. /// /// Por exemplo, em: /// Campo matches @nome /// /// O critério produzir pode ser algo como: /// {0} like @nome /// /// Que deve então ser substituído com String.Format() pela parte à direta /// da instrução, neste caso "Campo": /// String.Format("{0} like @nome", "Campo"); /// /// Produzindo como resultado: /// Campo like @nome /// </summary> /// <param name="parameter">Nome do parâmetro que será substituído.</param> /// <param name="value">Valor atribuído ao parâmetro.</param> /// <param name="args">Argumentos disponíveis.</param> /// <param name="keyGen">Algoritmo de geração de nomes de parâmetros.</param> /// <returns>A condição que substituirá o template.</returns> private static string CreateCriteria(string parameter, object value, IDictionary <string, object> args, KeyGen keyGen) { value = (value as Var)?.Value ?? value; if (value == null) { return(null); } if (value is string text) { if (text.HasWildcardPattern()) { args[parameter] = value; return("{0} like @" + parameter); } else { args[parameter] = value; return("{0} = @" + parameter); } } if (value.GetType().IsValueType) { args[parameter] = value; return("{0} = @" + parameter); } if (value is Sql nestSql) { var nestGen = keyGen.Derive(); var nestArgs = nestSql.Parameters; var nestText = nestSql.Text + "\n"; // cada parametro recebera um sufixo para diferenciacao de parametros // que já existem na instância de Sql. foreach (var key in nestArgs.Keys) { var matches = Regex.Matches(nestText, $@"@({key}\w?)"); foreach (Match match in matches.Cast <Match>().Reverse()) { var matchKey = match.Groups[1].Value; if (matchKey != key) { continue; } var index = match.Groups[1].Index; var count = match.Groups[1].Length; var keyName = nestGen.DeriveName(key); var keyValue = nestArgs[key]; args[keyName] = keyValue; nestText = nestText.Stuff(index, count, keyName); } } return("{0} in (" + nestText + ")"); } if (value is IEnumerable list) { var items = Commander.CreateSqlCompatibleValue(list); var values = string.Join(",", (IEnumerable)items); return("{0} in (" + values + ")"); } if (value is Range range) { if (range.Min != null && range.Max != null) { var minArg = keyGen.DeriveName(parameter); var maxArg = keyGen.DeriveName(parameter); args[minArg] = range.Min; args[maxArg] = range.Max; return("{0} between @" + minArg + " and @" + maxArg); } if (range.Min != null) { var minArg = keyGen.DeriveName(parameter); args[minArg] = range.Min; return("{0} >= @" + minArg); } if (range.Max != null) { var name = keyGen.DeriveName(parameter); args[name] = range.Max; return("{0} <= @" + name); } args[parameter] = value; return("{0} = @" + parameter); } args[parameter] = value; return("{0} = @" + parameter); }
/// <summary> /// Aplica a substituição dos parâmetros para construção da instrução SQL definitiva. /// </summary> /// <param name="text">O texto que sofrerá a substituição.</param> /// <param name="args">Argumentos disponíveis.</param> /// <param name="keyGen">Algoritmo de geração de nomes de parâmetros.</param> /// <returns></returns> private static string ReplaceTemplates(string text, IDictionary <string, object> args, KeyGen keyGen) { text = ReplaceManyMatches(text, args, keyGen); var extendedParameters = ExtractKnownExtendedParameters(text).ToArray(); var names = from name in extendedParameters orderby name.Length descending, name select name; foreach (var name in names) { var value = args.Get(name); var criteria = CreateCriteria(name, value, args, keyGen); text = ReplaceMatches(text, name, criteria); } var patterns = new[] { new[] { @"1=1\sand\s1=1", "1=1" }, new[] { @"1=1\sand\s1=0", "1=0" }, new[] { @"1=0\sand\s1=1", "1=0" }, new[] { @"1=0\sand\s1=0", "1=0" }, new[] { @"1=1\sor\s1=1", "1=1" }, new[] { @"1=1\sor\s1=0", "1=1" }, new[] { @"1=0\sor\s1=1", "1=1" }, new[] { @"1=0\sor\s1=0", "1=0" }, new[] { @"1=1\sand\s", "" }, new[] { @"\sand\s1=1", "" }, new[] { @"1=0\sor\s", "" }, new[] { @"\sor\s1=0", "" }, new[] { @"\sand\s\(1=1\)", "" }, new[] { @"\(1=1\)\sand\s", "" }, new[] { @"\sor\s\(1=0\)", "" }, new[] { @"\(1=0\)\sor\s", "" } }; bool changed; do { changed = false; foreach (var pattern in patterns) { var oldText = pattern[0]; var newText = pattern[1]; var replacement = Regex.Replace(text, oldText, newText); if (replacement != text) { text = replacement; changed = true; } } } while (changed); return(text); }