diff --git a/README.md b/README.md index 17677ac693..388155884d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ DevToys helps in daily tasks like formatting JSON, comparing text, testing RegEx Many tools are available. - Converters - - Json <> Yaml + - JSON <> YAML - Number Base - Encoders / Decoders - HTML @@ -36,7 +36,8 @@ Many tools are available. - Base64 - JWT Decoder - Formatters - - Json + - JSON + - SQL - Generators - Hash (MD5, SHA1, SHA256, SHA512) - UUID 1 and 4 @@ -100,6 +101,7 @@ Here is the list of tool name you can use: - `loremipsum` - Lorem Ipsum Generator - `checksum` - CheckSum File - `jsonformat` Json Formatter +- `sqlformat` - SQL Formatter - `jsonyaml` - Json <> Yaml - `jwt` - JWT Decoder - `colorblind` - Color Blindness Simulator diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index e0e3f0f314..679cdd59e8 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -15,6 +15,7 @@ This project incorporates components from the projects listed below. The origina 11. github-markdown-css (https://github.com/sindresorhus/github-markdown-css) 12. Efficient Compression Tool (https://github.com/fhanau/Efficient-Compression-Tool) 13. NLipsum (https://github.com/alexcpendleton/NLipsum) +14. SQL Formatter (https://github.com/zeroturnaround/sql-formatter) Newtonsoft.Json NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -458,4 +459,29 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +SQL Formatter NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2016-2020 ZeroTurnaround LLC +Copyright (c) 2020-present George Leslie-Waksman and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/dev/impl/DevToys/Assets/Icons/SqlFormatter.svg b/src/dev/impl/DevToys/Assets/Icons/SqlFormatter.svg new file mode 100644 index 0000000000..61af51b3ab --- /dev/null +++ b/src/dev/impl/DevToys/Assets/Icons/SqlFormatter.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/dev/impl/DevToys/DevToys.csproj b/src/dev/impl/DevToys/DevToys.csproj index 90c5b5ea11..aabef43fb5 100644 --- a/src/dev/impl/DevToys/DevToys.csproj +++ b/src/dev/impl/DevToys/DevToys.csproj @@ -33,12 +33,35 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -57,6 +80,8 @@ + + @@ -85,6 +110,9 @@ GZipEncoderDecoderToolPage.xaml + + SqlFormatterToolPage.xaml + CheckSumGeneratorToolPage.xaml @@ -274,6 +302,7 @@ PreserveNewest + PreserveNewest @@ -538,6 +567,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Formatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Formatter.cs new file mode 100644 index 0000000000..5dde9b9b89 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Formatter.cs @@ -0,0 +1,347 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DevToys.Shared.Core; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal abstract class Formatter + { + private static readonly Regex SpacesEndRegex = new Regex(@"[ \t]+$", RegexOptions.Compiled); + private static readonly Regex WhitespacesRegex = new Regex(@"\s+$", RegexOptions.Compiled); + private static readonly Regex CommentWhitespacesRegex = new Regex(@"\n[ \t]*", RegexOptions.Compiled); + + private readonly InlineBlock _inlineBlock = new(); + + private Indentation? _indentation = null; + private SqlFormatterOptions _options; + private Params? _params = null; + private IReadOnlyList? _tokens = null; + protected Token? _previousReservedToken = null; + private int _index; + + /// + /// Formats whitespace in a SQL string to make it easier to read. + /// + internal string Format(string query) + { + return Format(query, new SqlFormatterOptions(indentationSize: 2, uppercase: false)); + } + + /// + /// Formats whitespace in a SQL string to make it easier to read. + /// + internal string Format(string query, SqlFormatterOptions options) + { + _options = options; + _indentation = new Indentation(options.IndentationSize); + _params = new Params(options.PlaceholderParameters); + + _tokens = GetTokenizer().Tokenize(query); + string? formattedQuery = GetFormattedQueryFromTokens(); + return formattedQuery.Trim(); + } + + /// + /// SQL Tokenizer for this formatter, provided by subclasses. + /// + protected abstract Tokenizer GetTokenizer(); + + /// + /// Reprocess and modify a token based on parsed context. + /// + protected virtual Token TokenOverride(Token token) + { + // subclasses can override this to modify tokens during formatting + return token; + } + + protected Token? TokenLookBehind(int n = 1) + { + if (_tokens is null || _tokens!.Count <= _index - n || _index - n < 0) + { + return null; + } + + return _tokens![_index - n]; + } + + protected Token? TokenLookAhead(int n = 1) + { + if (_tokens is null || _tokens!.Count <= _index + n) + { + return null; + } + return _tokens![_index + n]; + } + + private string GetFormattedQueryFromTokens() + { + string formattedQuery = string.Empty; + + Assumes.NotNull(_tokens, nameof(_tokens)); + for (int i = 0; i < _tokens!.Count; i++) + { + _index = i; + + Token token = TokenOverride(_tokens[i]); + if (token.Type == TokenType.LineComment) + { + formattedQuery = FormatLineComment(token, formattedQuery); + } + else if (token.Type == TokenType.BlockComment) + { + formattedQuery = FormatBlockComment(token, formattedQuery); + } + else if (token.Type == TokenType.ReservedTopLevel) + { + formattedQuery = FormatTopLevelReservedWord(token, formattedQuery); + _previousReservedToken = token; + } + else if (token.Type == TokenType.ReservedTopLevelNoIndent) + { + formattedQuery = FormatTopLevelReservedWordNoIndent(token, formattedQuery); + _previousReservedToken = token; + } + else if (token.Type == TokenType.ReservedNewLine) + { + formattedQuery = FormatNewlineReservedWord(token, formattedQuery); + _previousReservedToken = token; + } + else if (token.Type == TokenType.Reserved) + { + formattedQuery = FormatWithSpaces(token, formattedQuery); + _previousReservedToken = token; + } + else if (token.Type == TokenType.OpenParen) + { + formattedQuery = FormatOpeningParentheses(token, formattedQuery); + } + else if (token.Type == TokenType.CloseParen) + { + formattedQuery = FormatClosingParentheses(token, formattedQuery); + } + else if (token.Type == TokenType.PlaceHolder) + { + formattedQuery = FormatPlaceholder(token, formattedQuery); + } + else if (string.Equals(token.Value, ",", System.StringComparison.Ordinal)) + { + formattedQuery = FormatComma(token, formattedQuery); + } + else if (string.Equals(token.Value, ":", System.StringComparison.Ordinal)) + { + formattedQuery = FormatWithSpaceAfter(token, formattedQuery); + } + else if (string.Equals(token.Value, ".", System.StringComparison.Ordinal)) + { + formattedQuery = FormatWithoutSpaces(token, formattedQuery); + } + else if (string.Equals(token.Value, ";", System.StringComparison.Ordinal)) + { + formattedQuery = FormatQuerySeparator(token, formattedQuery); + } + else + { + formattedQuery = FormatWithSpaces(token, formattedQuery); + } + } + + return formattedQuery; + } + + private string FormatLineComment(Token token, string query) + { + return AddNewLine(query + Show(token)); + } + + private string FormatBlockComment(Token token, string query) + { + return AddNewLine(AddNewLine(query) + IndentComment(token.Value)); + } + + private string IndentComment(string comment) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + return CommentWhitespacesRegex.Replace(comment, "\n" + _indentation!.GetIndent() + " "); + } + + private string FormatTopLevelReservedWordNoIndent(Token token, string query) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + _indentation!.DecreaseTopLevel(); + + query = AddNewLine(query) + EqualizeWhitespace(Show(token)); + return AddNewLine(query); + } + + private string FormatTopLevelReservedWord(Token token, string query) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + _indentation!.DecreaseTopLevel(); + + query = AddNewLine(query); + + _indentation.IncreaseTopLevel(); + + query += EqualizeWhitespace(Show(token)); + return AddNewLine(query); + } + + private string FormatNewlineReservedWord(Token token, string query) + { + if (TokenHelper.IsAnd(token) && TokenHelper.isBetween(TokenLookBehind(2))) + { + return FormatWithSpaces(token, query); + } + + return AddNewLine(query) + EqualizeWhitespace(Show(token)) + " "; + } + + /// + /// Replace any sequence of whitespace characters with single space + /// + private string EqualizeWhitespace(string input) + { + return WhitespacesRegex.Replace(input, " "); + } + + /// + /// Opening parentheses increase the block indent level and start a new line + /// + private string FormatOpeningParentheses(Token token, string query) + { + // Take out the preceding space unless there was whitespace there in the original query + // or another opening parens or line comment + if (token.WhitespaceBefore?.Length == 0) + { + TokenType? type = TokenLookBehind()?.Type; + if (type.HasValue && type != TokenType.OpenParen && type != TokenType.LineComment && type != TokenType.Operator) + { + query = TrimSpacesEnd(query); + } + } + + query += Show(token); + + Assumes.NotNull(_tokens, nameof(token)); + _inlineBlock.BeginIfPossible(_tokens!, _index); + + if (!_inlineBlock.IsActive()) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + _indentation!.IncreaseBlockLevel(); + query = AddNewLine(query); + } + + return query; + } + + /// + /// Closing parentheses decrease the block indent level + /// + private string FormatClosingParentheses(Token token, string query) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + if (_inlineBlock.IsActive()) + { + _inlineBlock.End(); + return FormatWithSpaceAfter(token, query); + } + else + { + _indentation!.DecreaseBlockLevel(); + return FormatWithSpaces(token, AddNewLine(query)); + } + } + + private string FormatPlaceholder(Token token, string query) + { + Assumes.NotNull(_params, nameof(_params)); + return query + _params!.Get(token) + ' '; + } + + /// + /// Commas start a new line (unless within inline parentheses or SQL "LIMIT" clause) + /// + private string FormatComma(Token token, string query) + { + query = FormatWithSpaceAfter(token, query); + + if (_inlineBlock.IsActive()) + { + return query; + } + else if (TokenHelper.isLimit(_previousReservedToken)) + { + return query; + } + else + { + return AddNewLine(query); + } + } + + private string FormatWithSpaceAfter(Token token, string query) + { + return TrimSpacesEnd(query) + Show(token) + " "; + } + + private string FormatWithoutSpaces(Token token, string query) + { + return TrimSpacesEnd(query) + Show(token); + } + + private string FormatWithSpaces(Token token, string query) + { + return query + Show(token) + " "; + } + + private string FormatQuerySeparator(Token token, string query) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + _indentation!.ResetIndentation(); + return TrimSpacesEnd(query) + Show(token) + string.Concat(Enumerable.Repeat("\r\n", _options.LinesBetweenQueries)); + } + + /// + /// Converts token to string (uppercasing it if needed) + /// + private string Show(Token token) + { + if (_options.Uppercase + && (token.Type == TokenType.Reserved + || token.Type == TokenType.ReservedTopLevel + || token.Type == TokenType.ReservedTopLevelNoIndent + || token.Type == TokenType.ReservedNewLine + || token.Type == TokenType.OpenParen + || token.Type == TokenType.CloseParen)) + { + return token.Value.ToUpperInvariant(); + } + else + { + return token.Value; + } + } + + private string AddNewLine(string query) + { + Assumes.NotNull(_indentation, nameof(_indentation)); + query = TrimSpacesEnd(query); + if (!query.EndsWith('\n')) + { + query += Environment.NewLine; + } + return query + _indentation!.GetIndent(); + } + + private string TrimSpacesEnd(string input) + { + return SpacesEndRegex.Replace(input, string.Empty); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Indentation.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Indentation.cs new file mode 100644 index 0000000000..33bc4eb365 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Indentation.cs @@ -0,0 +1,90 @@ +#nullable enable + +using System.Collections.Generic; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + /// + /// Manages indentation levels. + /// + internal sealed class Indentation + { + private enum IndentationType + { + /// + /// increased by ReservedTopLevel words + /// + TopLevel, + + /// + /// increased by open-parenthesis + /// + BlockLevel + } + + private readonly Stack _indentationTypes = new(); + private readonly int _indentationSize; + + public Indentation(int indentationSize) + { + _indentationSize = indentationSize; + } + + /// + /// Returns current indentation string. + /// + internal string GetIndent() + { + return new string(' ', _indentationSize * _indentationTypes.Count); + } + + /// + /// Increases indentation by one top-level indent. + /// + internal void IncreaseTopLevel() + { + _indentationTypes.Push(IndentationType.TopLevel); + } + + /// + /// Increases indentation by one block-level indent. + /// + internal void IncreaseBlockLevel() + { + _indentationTypes.Push(IndentationType.BlockLevel); + } + + /// + /// Decreases indentation by one top-level indent. + /// Does nothing when the previous indent is not top-level. + /// + internal void DecreaseTopLevel() + { + if (_indentationTypes.TryPeek(out IndentationType type) && type == IndentationType.TopLevel) + { + _indentationTypes.Pop(); + } + } + + /// + /// Decreases indentation by one block-level indent. + /// If there are top-level indents within the block-level indent, throws away these as well. + /// + internal void DecreaseBlockLevel() + { + while (_indentationTypes.Count > 0) + { + IndentationType type = _indentationTypes.Pop(); + if (type != IndentationType.TopLevel) + { + break; + } + } + } + + internal void ResetIndentation() + { + _indentationTypes.Clear(); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/InlineBlock.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/InlineBlock.cs new file mode 100644 index 0000000000..16678d83eb --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/InlineBlock.cs @@ -0,0 +1,112 @@ +#nullable enable + +using System; +using System.Collections.Generic; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + /// + /// Bookkeeper for inline blocks. + /// Inline blocks are parenthized expressions that are shorter than INLINE_MAX_LENGTH. + /// These blocks are formatted on a single line, unlike longer parenthized expressions + /// where open-parenthesis causes newline and increase of indentation. + /// + internal sealed class InlineBlock + { + private const int InlineMaxLength = 50; + + private int _level = 0; + + /// + /// Begins inline block when lookahead through upcoming tokens determines that the + /// block would be smaller than INLINE_MAX_LENGTH. + /// + /// Array of all tokens + /// Current token position + internal void BeginIfPossible(IReadOnlyList tokens, int index) + { + if (_level == 0 && IsInlineBlock(tokens, index)) + { + _level = 1; + } + else if (_level > 0) + { + _level++; + } + else + { + _level = 0; + } + } + + /// + /// Finishes current inline block. There might be several nested ones. + /// + internal void End() + { + _level--; + } + + /// + /// True when inside an inline block + /// + internal bool IsActive() + { + return _level > 0; + } + + /// + /// Check if this should be an inline parentheses block. + /// Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2) + /// + private bool IsInlineBlock(IReadOnlyList tokens, int index) + { + int length = 0; + int level = 0; + + for (int i = index; i < tokens.Count; i++) + { + Token token = tokens[i]; + length += token.Value.Length; + + // Overran max length + if (length > InlineMaxLength) + { + return false; + } + + if (token.Type == TokenType.OpenParen) + { + level++; + } + else if (token.Type == TokenType.CloseParen) + { + level--; + if (level == 0) + { + return true; + } + } + + if (IsForbiddenToken(token)) + { + return false; + } + } + return false; + } + + /// + /// Reserved words that cause newlines, comments and semicolons are not allowed inside inline parentheses block + /// + private bool IsForbiddenToken(Token token) + { + return + token.Type == TokenType.ReservedTopLevel + || token.Type == TokenType.ReservedNewLine + // || token.Type == TokenType.LineComment + || token.Type == TokenType.BlockComment + || string.Equals(token.Value, ";", StringComparison.Ordinal); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Params.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Params.cs new file mode 100644 index 0000000000..768442fe50 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Params.cs @@ -0,0 +1,36 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + /// + /// Handles placeholder replacement with given params. + /// + internal sealed class Params + { + private readonly IReadOnlyDictionary? _params; + private int _index; + + public Params(IReadOnlyDictionary? parameters) + { + _params = parameters; + } + + internal string? Get(Token token) + { + if (_params is null) + { + return token.Value; + } + + if (!string.IsNullOrEmpty(token.Key)) + { + return _params[token.Key!]; + } + + return _params.Values.ToArray()[_index++]; + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/RegexFactory.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/RegexFactory.cs new file mode 100644 index 0000000000..83961000c7 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/RegexFactory.cs @@ -0,0 +1,118 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DevToys.Shared.Core; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal static class RegexFactory + { + private static readonly Regex SpecialCharacterRegex = new(@"[.*+?^${}()|[\]\\]", RegexOptions.Compiled); + private static readonly Dictionary Patterns = new Dictionary() + { + { "``", "((`[^`]*($|`))+)" }, + { "{}", "((\\{[^\\}]*($|\\}))+)" }, + { "[]", "((\\[[^\\]]*($|\\]))(\\][^\\]]*($|\\]))*)" }, + { "\"\"", "((\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*(\"|$))+)" }, + { "''", "(('[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)" }, + { "N''", "((N'[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)" }, + { "U&''", "((U&'[^'\\\\]*(?:\\\\.[^'\\\\]*)*('|$))+)" }, + { "U&\"\"", "((U&\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*(\"|$))+)" }, + { "$$", "((?\\$\\w*\\$)[\\s\\S]*?(?:\\k|$))" } + }; + + internal static Regex CreateOperatorRegex(IEnumerable multiLetterOperators) + { + IOrderedEnumerable sortedOperators = SortByLengthDesc(multiLetterOperators); + IEnumerable escapedOperators = sortedOperators.Select(item => EscapeSpecialCharacters(item)); + string operators = string.Join("|", escapedOperators); + return new Regex(@$"^({operators}|.)", RegexOptions.Compiled); + } + + internal static Regex CreateLineCommentRegex(string[] lineCommentTypes) + { + return new Regex("^((?:" + string.Join('|', lineCommentTypes.Select(item => EscapeSpecialCharacters(item))) + ").*?)(?:\\r\\n|\\r|\\n|$)", RegexOptions.Compiled | RegexOptions.Singleline); + } + + internal static Regex CreateReservedWordRegex(string[] reservedWords) + { + if (reservedWords.Length == 0) + { + return new Regex(@"^\b$", RegexOptions.Compiled); + } + + string reservedWordsPattern = string.Join('|', SortByLengthDesc(reservedWords)).Replace(" ", "\\s+"); + return new Regex(@$"^({reservedWordsPattern})\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + + internal static Regex CreateWordRegex(string[] specialCharacters) + { + return new Regex(@"^([\p{L}\p{M}\p{Nd}\p{Pc}\p{Cf}\p{Cs}\p{Co}" + string.Join(string.Empty, specialCharacters) + "]+)", RegexOptions.Compiled); + } + + internal static Regex CreateStringRegex(string[] stringTypes) + { + return new Regex("^(" + CreateStringPattern(stringTypes) + ")", RegexOptions.Compiled); + } + + /// + /// This enables the following string patterns: + /// 1. backtick quoted string using `` to escape + /// 2. square bracket quoted string (SQL Server) using ]] to escape + /// 3. double quoted string using "" or \" to escape + /// 4. single quoted string using '' or \' to escape + /// 5. national character quoted string using N'' or N\' to escape + /// 6. Unicode single-quoted string using \' to escape + /// 7. Unicode double-quoted string using \" to escape + /// 8. PostgreSQL dollar-quoted strings + /// + internal static string CreateStringPattern(string[] stringTypes) + { + return string.Join('|', stringTypes.Select(item => Patterns[item])); + } + + internal static Regex? CreatePlaceholderRegex(string[] types, string pattern) + { + if (types is null || types.Length == 0) + { + return null; + } + + string typesRegex = string.Join('|', types.Select(item => EscapeSpecialCharacters(item))); + + return new Regex("^((?:" + typesRegex + ")(?:" + pattern + "))", RegexOptions.Compiled); + } + + internal static Regex CreateParenRegex(string[] parens) + { + return new Regex("^(" + string.Join('|', parens.Select(item => EscapeParen(item))) + ")", RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + + private static string EscapeParen(string paren) + { + if (paren.Length == 1) + { + // A single punctuation character + return EscapeSpecialCharacters(paren); + } + else + { + // longer word + return "\\b" + paren + "\\b"; + } + } + + private static IOrderedEnumerable SortByLengthDesc(IEnumerable strings) + { + return strings.OrderByDescending(s => s.Length); + } + + private static string EscapeSpecialCharacters(string input) + { + return SpecialCharacterRegex.Replace(input, "\\$&"); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Token.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Token.cs new file mode 100644 index 0000000000..0dc3b1cdaa --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Token.cs @@ -0,0 +1,21 @@ +#nullable enable + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal sealed class Token + { + internal string Value { get; } + + internal TokenType Type { get; } + + internal string? WhitespaceBefore { get; set; } + + internal string? Key { get; set; } + + public Token(string value, TokenType type) + { + Value = value; + Type = type; + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenHelper.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenHelper.cs new file mode 100644 index 0000000000..6bef799323 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenHelper.cs @@ -0,0 +1,59 @@ +#nullable enable + +using System; +using System.Text.RegularExpressions; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal static class TokenHelper + { + private static readonly TimeSpan TimeOut = TimeSpan.FromMilliseconds(500); + private static readonly Regex AndRegex = new("^AND$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex BetweenRegex = new("^BETWEEN$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex LimitRegex = new("^LIMIT$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex SetRegex = new("^SET$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex ByRegex = new("^BY$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex WindowRegex = new("^WINDOW$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + private static readonly Regex EndRegex = new("^END$", RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeOut); + + internal static bool IsAnd(Token? token) + { + return IsToken(token, TokenType.ReservedNewLine, AndRegex); + } + + internal static bool isBetween(Token? token) + { + return IsToken(token, TokenType.Reserved, BetweenRegex); + } + + internal static bool isLimit(Token? token) + { + return IsToken(token, TokenType.ReservedTopLevel, LimitRegex); + } + + internal static bool isSet(Token? token) + { + return IsToken(token, TokenType.ReservedTopLevel, SetRegex); + } + + internal static bool isBy(Token? token) + { + return IsToken(token, TokenType.Reserved, ByRegex); + } + + internal static bool isWindow(Token? token) + { + return IsToken(token, TokenType.ReservedTopLevel, WindowRegex); + } + + internal static bool isEnd(Token? token) + { + return IsToken(token, TokenType.CloseParen, EndRegex); + } + + private static bool IsToken(Token? token, TokenType type, Regex regex) + { + return token?.Type == type && regex.IsMatch(token?.Value); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenType.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenType.cs new file mode 100644 index 0000000000..d31c6cd16d --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/TokenType.cs @@ -0,0 +1,21 @@ +#nullable enable + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal enum TokenType + { + Word, + String, + Reserved, + ReservedTopLevel, + ReservedTopLevelNoIndent, + ReservedNewLine, + Operator, + OpenParen, + CloseParen, + LineComment, + BlockComment, + Number, + PlaceHolder + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Tokenizer.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Tokenizer.cs new file mode 100644 index 0000000000..db5a539030 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Core/Tokenizer.cs @@ -0,0 +1,283 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace DevToys.Helpers.SqlFormatter.Core +{ + internal class Tokenizer + { + private static readonly Regex WhitespaceRegex = new Regex(@"^(\s+)", RegexOptions.Compiled); + private static readonly Regex NumberRegex = new Regex(@"^((-\s*)?[0-9]+(\.[0-9]+)?([eE]-?[0-9]+(\.[0-9]+)?)?|0x[0-9a-fA-F]+|0b[01]+)\b", RegexOptions.Compiled); + private static readonly Regex BlockCommentRegex = new Regex(@"^(\/\*(.*?)*?(?:\*\/|$))", RegexOptions.Compiled | RegexOptions.Singleline); + + private readonly Regex _operatorRegex; + private readonly Regex _lineCommentRegex; + private readonly Regex _reservedTopLevelRegex; + private readonly Regex _reservedTopLevelNoIndentRegex; + private readonly Regex _reservedNewLineRegex; + private readonly Regex _reservedPlainRegex; + private readonly Regex _wordRegex; + private readonly Regex _stringRegex; + private readonly Regex _openParenRegex; + private readonly Regex _closeParenRegex; + private readonly Regex? _indexedPlaceholderRegex; + private readonly Regex? _indentNamedPlaceholderRegex; + private readonly Regex? _stringNamedPlaceholderRegex; + + /// + /// Initializes a new instance of class. + /// + /// Reserved words in SQL + /// Words that are set to new line separately + /// Words that are set to newline + /// Words that are top level but have no indentation + /// String types to enable: "", '', ``, [], N'' + /// Opening parentheses to enable, like (, [ + /// Closing parentheses to enable, like ), ] + /// Prefixes for indexed placeholders, like ? + /// Prefixes for named placeholders, like @ and : + /// Line comments to enable, like # and -- + /// Special chars that can be found inside of words, like @ and # + /// Additional operators to recognize + public Tokenizer( + string[] reservedWords, + string[] reservedTopLevelWords, + string[] reservedNewlineWords, + string[] reservedTopLevelWordsNoIndent, + string[] stringTypes, + string[] openParens, + string[] closeParens, + string[] indexedPlaceholderTypes, + string[] namedPlaceholderTypes, + string[] lineCommentTypes, + string[] specialWordChars, + string[]? operators = null) + { + var operatorsParam = new List { "<>", "<=", ">=" }; + if (operators is not null) + { + operatorsParam.AddRange(operators); + } + _operatorRegex = RegexFactory.CreateOperatorRegex(operatorsParam); + + _lineCommentRegex = RegexFactory.CreateLineCommentRegex(lineCommentTypes); + _reservedTopLevelRegex = RegexFactory.CreateReservedWordRegex(reservedTopLevelWords); + _reservedTopLevelNoIndentRegex = RegexFactory.CreateReservedWordRegex(reservedTopLevelWordsNoIndent); + _reservedNewLineRegex = RegexFactory.CreateReservedWordRegex(reservedNewlineWords); + _reservedPlainRegex = RegexFactory.CreateReservedWordRegex(reservedWords); + _wordRegex = RegexFactory.CreateWordRegex(specialWordChars); + _stringRegex = RegexFactory.CreateStringRegex(stringTypes); + _openParenRegex = RegexFactory.CreateParenRegex(openParens); + _closeParenRegex = RegexFactory.CreateParenRegex(closeParens); + _indexedPlaceholderRegex = RegexFactory.CreatePlaceholderRegex(indexedPlaceholderTypes, "[0-9]*"); + _indentNamedPlaceholderRegex = RegexFactory.CreatePlaceholderRegex(namedPlaceholderTypes, "[a-zA-Z0-9._$]+"); + _stringNamedPlaceholderRegex = RegexFactory.CreatePlaceholderRegex(namedPlaceholderTypes, RegexFactory.CreateStringPattern(stringTypes)); + } + + /// + /// Takes a SQL string and breaks it into tokens. + /// + /// The SQL string + /// + internal IReadOnlyList Tokenize(string input) + { + var tokens = new List(); + Token? token = null; + + // Keep processing the string until it is empty + while (input.Length > 0) + { + // TODO: This is a direct translation from https://github.com/zeroturnaround/sql-formatter + // The current algorithm allocates a lot of strings, which is terrible from memory consumption perspective. + + // grab any preceding whitespace + string whitespaceBefore = GetWhitespace(input); + if (whitespaceBefore.Length > 0) + { + input = input.Substring(whitespaceBefore.Length); + } + + if (input.Length > 0) + { + // Get the next token and the token type + token = GetNextToken(input, token); + + if (token is not null) + { + // Advance the string + input = input.Substring(token.Value.Length); + token.WhitespaceBefore = whitespaceBefore; + tokens.Add(token); + } + } + } + + return tokens; + } + + private string GetWhitespace(string input) + { + MatchCollection? matches = WhitespaceRegex.Matches(input); + return matches is null || matches.Count == 0 ? string.Empty : matches[0].Value; + } + + private Token? GetNextToken(string input, Token? previousToken) + { + return GetCommentToken(input) + ?? GetStringToken(input) + ?? GetOpenParenToken(input) + ?? GetCloseParenToken(input) + ?? GetPlaceholderToken(input) + ?? GetNumberToken(input) + ?? GetReservedWordToken(input, previousToken) + ?? GetWordToken(input) + ?? GetOperatorToken(input); + } + + private Token? GetCommentToken(string input) + { + return GetLineCommentToken(input) + ?? GetBlockCommentToken(input); + } + + private Token? GetLineCommentToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.LineComment, _lineCommentRegex); + } + + private Token? GetBlockCommentToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.BlockComment, BlockCommentRegex); + } + + private Token? GetStringToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.String, _stringRegex); + } + + private Token? GetOpenParenToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.OpenParen, _openParenRegex); + } + + private Token? GetCloseParenToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.CloseParen, _closeParenRegex); + } + + private Token? GetPlaceholderToken(string input) + { + return GetIdentNamedPlaceholderToken(input) + ?? GetStringNamedPlaceholderToken(input) + ?? GetIndexedPlaceholderToken(input); + } + + private Token? GetIdentNamedPlaceholderToken(string input) + { + return GetPlaceholderTokenWithKey(input, _indentNamedPlaceholderRegex, (v) => v.Substring(1)); + } + + private Token? GetStringNamedPlaceholderToken(string input) + { + return GetPlaceholderTokenWithKey( + input, + _stringNamedPlaceholderRegex, + (v) => GetEscapedPlaceholderKey( + key: v.Substring(1, 1), + quoteChar: v.Substring(v.Length - 2))); + } + + private Token? GetIndexedPlaceholderToken(string input) + { + return GetPlaceholderTokenWithKey(input, _indexedPlaceholderRegex, (v) => v.Substring(1)); + } + + private Token? GetPlaceholderTokenWithKey(string input, Regex? regex, Func parseKey) + { + Token? token = GetTokenOnFirstMatch(input, TokenType.PlaceHolder, regex); + if (token is not null) + { + token.Key = parseKey(token.Value); + } + + return token; + } + + private string GetEscapedPlaceholderKey(string key, string quoteChar) + { + string? escapeRegexPattern = new Regex(@"[.*+?^${}()|[\]\\]").Replace("\\" + quoteChar, "\\$&"); + var escapeRegex = new Regex(escapeRegexPattern); + return escapeRegex.Replace(key, quoteChar); + } + + private Token? GetNumberToken(string input) + { + // Decimal, binary, or hex numbers + return GetTokenOnFirstMatch(input, TokenType.Number, NumberRegex); + } + + private Token? GetWordToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.Word, _wordRegex); + } + + private Token? GetOperatorToken(string input) + { + // Punctuation and symbols + return GetTokenOnFirstMatch(input, TokenType.Operator, _operatorRegex); + } + + private Token? GetReservedWordToken(string input, Token? previousToken) + { + // A reserved word cannot be preceded by a "." + // this makes it so in "mytable.from", "from" is not considered a reserved word + if (previousToken is not null && string.Equals(".", previousToken.Value)) + { + return null; + } + + return GetTopLevelReservedToken(input) + ?? GetNewlineReservedToken(input) + ?? GetTopLevelReservedTokenNoIndent(input) + ?? GetPlainReservedToken(input); + } + + private Token? GetTopLevelReservedToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.ReservedTopLevel, _reservedTopLevelRegex); + } + + private Token? GetNewlineReservedToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.ReservedNewLine, _reservedNewLineRegex); + } + + private Token? GetTopLevelReservedTokenNoIndent(string input) + { + return GetTokenOnFirstMatch(input, TokenType.ReservedTopLevelNoIndent, _reservedTopLevelNoIndentRegex); + } + + private Token? GetPlainReservedToken(string input) + { + return GetTokenOnFirstMatch(input, TokenType.Reserved, _reservedPlainRegex); + } + + private Token? GetTokenOnFirstMatch(string input, TokenType type, Regex? regex) + { + if (regex is null) + { + return null; + } + + MatchCollection? matches = regex.Matches(input); + if (matches is null || matches.Count == 0) + { + return null; + } + + return new Token(matches[0].Value, type); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/Db2Formatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/Db2Formatter.cs new file mode 100644 index 0000000000..5a63ad9118 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/Db2Formatter.cs @@ -0,0 +1,582 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class Db2Formatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ABS", + "ACTIVATE", + "ALIAS", + "ALL", + "ALLOCATE", + "ALLOW", + "ALTER", + "ANY", + "ARE", + "ARRAY", + "AS", + "ASC", + "ASENSITIVE", + "ASSOCIATE", + "ASUTIME", + "ASYMMETRIC", + "AT", + "ATOMIC", + "ATTRIBUTES", + "AUDIT", + "AUTHORIZATION", + "AUX", + "AUXILIARY", + "AVG", + "BEFORE", + "BEGIN", + "BETWEEN", + "BIGINT", + "BINARY", + "BLOB", + "BOOLEAN", + "BOTH", + "BUFFERPOOL", + "BY", + "CACHE", + "CALL", + "CALLED", + "CAPTURE", + "CARDINALITY", + "CASCADED", + "CASE", + "CAST", + "CCSID", + "CEIL", + "CEILING", + "CHAR", + "CHARACTER", + "CHARACTER_LENGTH", + "CHAR_LENGTH", + "CHECK", + "CLOB", + "CLONE", + "CLOSE", + "CLUSTER", + "COALESCE", + "COLLATE", + "COLLECT", + "COLLECTION", + "COLLID", + "COLUMN", + "COMMENT", + "COMMIT", + "CONCAT", + "CONDITION", + "CONNECT", + "CONNECTION", + "CONSTRAINT", + "CONTAINS", + "CONTINUE", + "CONVERT", + "CORR", + "CORRESPONDING", + "COUNT", + "COUNT_BIG", + "COVAR_POP", + "COVAR_SAMP", + "CREATE", + "CROSS", + "CUBE", + "CUME_DIST", + "CURRENT", + "CURRENT_DATE", + "CURRENT_DEFAULT_TRANSFORM_GROUP", + "CURRENT_LC_CTYPE", + "CURRENT_PATH", + "CURRENT_ROLE", + "CURRENT_SCHEMA", + "CURRENT_SERVER", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_TIMEZONE", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", + "CURRENT_USER", + "CURSOR", + "CYCLE", + "DATA", + "DATABASE", + "DATAPARTITIONNAME", + "DATAPARTITIONNUM", + "DATE", + "DAY", + "DAYS", + "DB2GENERAL", + "DB2GENRL", + "DB2SQL", + "DBINFO", + "DBPARTITIONNAME", + "DBPARTITIONNUM", + "DEALLOCATE", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULT", + "DEFAULTS", + "DEFINITION", + "DELETE", + "DENSERANK", + "DENSE_RANK", + "DEREF", + "DESCRIBE", + "DESCRIPTOR", + "DETERMINISTIC", + "DIAGNOSTICS", + "DISABLE", + "DISALLOW", + "DISCONNECT", + "DISTINCT", + "DO", + "DOCUMENT", + "DOUBLE", + "DROP", + "DSSIZE", + "DYNAMIC", + "EACH", + "EDITPROC", + "ELEMENT", + "ELSE", + "ELSEIF", + "ENABLE", + "ENCODING", + "ENCRYPTION", + "END", + "END-EXEC", + "ENDING", + "ERASE", + "ESCAPE", + "EVERY", + "EXCEPTION", + "EXCLUDING", + "EXCLUSIVE", + "EXEC", + "EXECUTE", + "EXISTS", + "EXIT", + "EXP", + "EXPLAIN", + "EXTENDED", + "EXTERNAL", + "EXTRACT", + "FALSE", + "FENCED", + "FETCH", + "FIELDPROC", + "FILE", + "FILTER", + "FINAL", + "FIRST", + "FLOAT", + "FLOOR", + "FOR", + "FOREIGN", + "FREE", + "FULL", + "FUNCTION", + "FUSION", + "GENERAL", + "GENERATED", + "GET", + "GLOBAL", + "GOTO", + "GRANT", + "GRAPHIC", + "GROUP", + "GROUPING", + "HANDLER", + "HASH", + "HASHED_VALUE", + "HINT", + "HOLD", + "HOUR", + "HOURS", + "IDENTITY", + "IF", + "IMMEDIATE", + "IN", + "INCLUDING", + "INCLUSIVE", + "INCREMENT", + "INDEX", + "INDICATOR", + "INDICATORS", + "INF", + "INFINITY", + "INHERIT", + "INNER", + "INOUT", + "INSENSITIVE", + "INSERT", + "INT", + "INTEGER", + "INTEGRITY", + "INTERSECTION", + "INTERVAL", + "INTO", + "IS", + "ISOBID", + "ISOLATION", + "ITERATE", + "JAR", + "JAVA", + "KEEP", + "KEY", + "LABEL", + "LANGUAGE", + "LARGE", + "LATERAL", + "LC_CTYPE", + "LEADING", + "LEAVE", + "LEFT", + "LIKE", + "LINKTYPE", + "LN", + "LOCAL", + "LOCALDATE", + "LOCALE", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCATOR", + "LOCATORS", + "LOCK", + "LOCKMAX", + "LOCKSIZE", + "LONG", + "LOOP", + "LOWER", + "MAINTAINED", + "MATCH", + "MATERIALIZED", + "MAX", + "MAXVALUE", + "MEMBER", + "MERGE", + "METHOD", + "MICROSECOND", + "MICROSECONDS", + "MIN", + "MINUTE", + "MINUTES", + "MINVALUE", + "MOD", + "MODE", + "MODIFIES", + "MODULE", + "MONTH", + "MONTHS", + "MULTISET", + "NAN", + "NATIONAL", + "NATURAL", + "NCHAR", + "NCLOB", + "NEW", + "NEW_TABLE", + "NEXTVAL", + "NO", + "NOCACHE", + "NOCYCLE", + "NODENAME", + "NODENUMBER", + "NOMAXVALUE", + "NOMINVALUE", + "NONE", + "NOORDER", + "NORMALIZE", + "NORMALIZED", + "NOT", + "NULL", + "NULLIF", + "NULLS", + "NUMERIC", + "NUMPARTS", + "OBID", + "OCTET_LENGTH", + "OF", + "OFFSET", + "OLD", + "OLD_TABLE", + "ON", + "ONLY", + "OPEN", + "OPTIMIZATION", + "OPTIMIZE", + "OPTION", + "ORDER", + "OUT", + "OUTER", + "OVER", + "OVERLAPS", + "OVERLAY", + "OVERRIDING", + "PACKAGE", + "PADDED", + "PAGESIZE", + "PARAMETER", + "PART", + "PARTITION", + "PARTITIONED", + "PARTITIONING", + "PARTITIONS", + "PASSWORD", + "PATH", + "PERCENTILE_CONT", + "PERCENTILE_DISC", + "PERCENT_RANK", + "PIECESIZE", + "PLAN", + "POSITION", + "POWER", + "PRECISION", + "PREPARE", + "PREVVAL", + "PRIMARY", + "PRIQTY", + "PRIVILEGES", + "PROCEDURE", + "PROGRAM", + "PSID", + "PUBLIC", + "QUERY", + "QUERYNO", + "RANGE", + "RANK", + "READ", + "READS", + "REAL", + "RECOVERY", + "RECURSIVE", + "REF", + "REFERENCES", + "REFERENCING", + "REFRESH", + "REGR_AVGX", + "REGR_AVGY", + "REGR_COUNT", + "REGR_INTERCEPT", + "REGR_R2", + "REGR_SLOPE", + "REGR_SXX", + "REGR_SXY", + "REGR_SYY", + "RELEASE", + "RENAME", + "REPEAT", + "RESET", + "RESIGNAL", + "RESTART", + "RESTRICT", + "RESULT", + "RESULT_SET_LOCATOR", + "RETURN", + "RETURNS", + "REVOKE", + "RIGHT", + "ROLE", + "ROLLBACK", + "ROLLUP", + "ROUND_CEILING", + "ROUND_DOWN", + "ROUND_FLOOR", + "ROUND_HALF_DOWN", + "ROUND_HALF_EVEN", + "ROUND_HALF_UP", + "ROUND_UP", + "ROUTINE", + "ROW", + "ROWNUMBER", + "ROWS", + "ROWSET", + "ROW_NUMBER", + "RRN", + "RUN", + "SAVEPOINT", + "SCHEMA", + "SCOPE", + "SCRATCHPAD", + "SCROLL", + "SEARCH", + "SECOND", + "SECONDS", + "SECQTY", + "SECURITY", + "SENSITIVE", + "SEQUENCE", + "SESSION", + "SESSION_USER", + "SIGNAL", + "SIMILAR", + "SIMPLE", + "SMALLINT", + "SNAN", + "SOME", + "SOURCE", + "SPECIFIC", + "SPECIFICTYPE", + "SQL", + "SQLEXCEPTION", + "SQLID", + "SQLSTATE", + "SQLWARNING", + "SQRT", + "STACKED", + "STANDARD", + "START", + "STARTING", + "STATEMENT", + "STATIC", + "STATMENT", + "STAY", + "STDDEV_POP", + "STDDEV_SAMP", + "STOGROUP", + "STORES", + "STYLE", + "SUBMULTISET", + "SUBSTRING", + "SUM", + "SUMMARY", + "SYMMETRIC", + "SYNONYM", + "SYSFUN", + "SYSIBM", + "SYSPROC", + "SYSTEM", + "SYSTEM_USER", + "TABLE", + "TABLESAMPLE", + "TABLESPACE", + "THEN", + "TIME", + "TIMESTAMP", + "TIMEZONE_HOUR", + "TIMEZONE_MINUTE", + "TO", + "TRAILING", + "TRANSACTION", + "TRANSLATE", + "TRANSLATION", + "TREAT", + "TRIGGER", + "TRIM", + "TRUE", + "TRUNCATE", + "TYPE", + "UESCAPE", + "UNDO", + "UNIQUE", + "UNKNOWN", + "UNNEST", + "UNTIL", + "UPPER", + "USAGE", + "USER", + "USING", + "VALIDPROC", + "VALUE", + "VARCHAR", + "VARIABLE", + "VARIANT", + "VARYING", + "VAR_POP", + "VAR_SAMP", + "VCAT", + "VERSION", + "VIEW", + "VOLATILE", + "VOLUMES", + "WHEN", + "WHENEVER", + "WHILE", + "WIDTH_BUCKET", + "WINDOW", + "WITH", + "WITHIN", + "WITHOUT", + "WLM", + "WRITE", + "XMLELEMENT", + "XMLEXISTS", + "XMLNAMESPACES", + "YEAR", + "YEARS", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "AFTER", + "ALTER COLUMN", + "ALTER TABLE", + "DELETE FROM", + "EXCEPT", + "FETCH FIRST", + "FROM", + "GROUP BY", + "GO", + "HAVING", + "INSERT INTO", + "INTERSECT", + "LIMIT", + "ORDER BY", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "OR", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN" + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``", "[]" }, + openParens: new[] { "(" }, + closeParens: new[] { ")" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: new[] { ":" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: new[] { "#" ,"@" }, + operators: new[] { "**", "!=", "!>", "!>", "||" }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MariaDbFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MariaDbFormatter.cs new file mode 100644 index 0000000000..f5ce0e8be0 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MariaDbFormatter.cs @@ -0,0 +1,329 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class MariaDbFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ACCESSIBLE", + "ADD", + "ALL", + "ALTER", + "ANALYZE", + "AND", + "AS", + "ASC", + "ASENSITIVE", + "BEFORE", + "BETWEEN", + "BIGINT", + "BINARY", + "BLOB", + "BOTH", + "BY", + "CALL", + "CASCADE", + "CASE", + "CHANGE", + "CHAR", + "CHARACTER", + "CHECK", + "COLLATE", + "COLUMN", + "CONDITION", + "CONSTRAINT", + "CONTINUE", + "CONVERT", + "CREATE", + "CROSS", + "CURRENT_DATE", + "CURRENT_ROLE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "CURSOR", + "DATABASE", + "DATABASES", + "DAY_HOUR", + "DAY_MICROSECOND", + "DAY_MINUTE", + "DAY_SECOND", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULT", + "DELAYED", + "DELETE", + "DESC", + "DESCRIBE", + "DETERMINISTIC", + "DISTINCT", + "DISTINCTROW", + "DIV", + "DO_DOMAIN_IDS", + "DOUBLE", + "DROP", + "DUAL", + "EACH", + "ELSE", + "ELSEIF", + "ENCLOSED", + "ESCAPED", + "EXCEPT", + "EXISTS", + "EXIT", + "EXPLAIN", + "FALSE", + "FETCH", + "FLOAT", + "FLOAT4", + "FLOAT8", + "FOR", + "FORCE", + "FOREIGN", + "FROM", + "FULLTEXT", + "GENERAL", + "GRANT", + "GROUP", + "HAVING", + "HIGH_PRIORITY", + "HOUR_MICROSECOND", + "HOUR_MINUTE", + "HOUR_SECOND", + "IF", + "IGNORE", + "IGNORE_DOMAIN_IDS", + "IGNORE_SERVER_IDS", + "IN", + "INDEX", + "INFILE", + "INNER", + "INOUT", + "INSENSITIVE", + "INSERT", + "INT", + "INT1", + "INT2", + "INT3", + "INT4", + "INT8", + "INTEGER", + "INTERSECT", + "INTERVAL", + "INTO", + "IS", + "ITERATE", + "JOIN", + "KEY", + "KEYS", + "KILL", + "LEADING", + "LEAVE", + "LEFT", + "LIKE", + "LIMIT", + "LINEAR", + "LINES", + "LOAD", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCK", + "LONG", + "LONGBLOB", + "LONGTEXT", + "LOOP", + "LOW_PRIORITY", + "MASTER_HEARTBEAT_PERIOD", + "MASTER_SSL_VERIFY_SERVER_CERT", + "MATCH", + "MAXVALUE", + "MEDIUMBLOB", + "MEDIUMINT", + "MEDIUMTEXT", + "MIDDLEINT", + "MINUTE_MICROSECOND", + "MINUTE_SECOND", + "MOD", + "MODIFIES", + "NATURAL", + "NOT", + "NO_WRITE_TO_BINLOG", + "NULL", + "NUMERIC", + "ON", + "OPTIMIZE", + "OPTION", + "OPTIONALLY", + "OR", + "ORDER", + "OUT", + "OUTER", + "OUTFILE", + "OVER", + "PAGE_CHECKSUM", + "PARSE_VCOL_EXPR", + "PARTITION", + "POSITION", + "PRECISION", + "PRIMARY", + "PROCEDURE", + "PURGE", + "RANGE", + "READ", + "READS", + "READ_WRITE", + "REAL", + "RECURSIVE", + "REF_SYSTEM_ID", + "REFERENCES", + "REGEXP", + "RELEASE", + "RENAME", + "REPEAT", + "REPLACE", + "REQUIRE", + "RESIGNAL", + "RESTRICT", + "RETURN", + "RETURNING", + "REVOKE", + "RIGHT", + "RLIKE", + "ROWS", + "SCHEMA", + "SCHEMAS", + "SECOND_MICROSECOND", + "SELECT", + "SENSITIVE", + "SEPARATOR", + "SET", + "SHOW", + "SIGNAL", + "SLOW", + "SMALLINT", + "SPATIAL", + "SPECIFIC", + "SQL", + "SQLEXCEPTION", + "SQLSTATE", + "SQLWARNING", + "SQL_BIG_RESULT", + "SQL_CALC_FOUND_ROWS", + "SQL_SMALL_RESULT", + "SSL", + "STARTING", + "STATS_AUTO_RECALC", + "STATS_PERSISTENT", + "STATS_SAMPLE_PAGES", + "STRAIGHT_JOIN", + "TABLE", + "TERMINATED", + "THEN", + "TINYBLOB", + "TINYINT", + "TINYTEXT", + "TO", + "TRAILING", + "TRIGGER", + "TRUE", + "UNDO", + "UNION", + "UNIQUE", + "UNLOCK", + "UNSIGNED", + "UPDATE", + "USAGE", + "USE", + "USING", + "UTC_DATE", + "UTC_TIME", + "UTC_TIMESTAMP", + "VALUES", + "VARBINARY", + "VARCHAR", + "VARCHARACTER", + "VARYING", + "WHEN", + "WHERE", + "WHILE", + "WINDOW", + "WITH", + "WRITE", + "XOR", + "YEAR_MONTH", + "ZEROFILL", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "ALTER COLUMN", + "ALTER TABLE", + "DELETE FROM", + "EXCEPT", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "ORDER BY", + "SELECT", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "WHEN", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + // non-standard joins + "STRAIGHT_JOIN", + "NATURAL LEFT JOIN", + "NATURAL LEFT OUTER JOIN", + "NATURAL RIGHT JOIN", + "NATURAL RIGHT OUTER JOIN" + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: Array.Empty(), + lineCommentTypes: new[] { "#", "--" }, + specialWordChars: new[] { "@" }, + operators: new[] { ":=", "<<", ">>", "!=", "<>", "<=>", "&&", "||" }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MySqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MySqlFormatter.cs new file mode 100644 index 0000000000..2a1fd648fe --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/MySqlFormatter.cs @@ -0,0 +1,342 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class MySqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ACCESSIBLE", + "ADD", + "ALL", + "ALTER", + "ANALYZE", + "AND", + "AS", + "ASC", + "ASENSITIVE", + "BEFORE", + "BETWEEN", + "BIGINT", + "BINARY", + "BLOB", + "BOTH", + "BY", + "CALL", + "CASCADE", + "CASE", + "CHANGE", + "CHAR", + "CHARACTER", + "CHECK", + "COLLATE", + "COLUMN", + "CONDITION", + "CONSTRAINT", + "CONTINUE", + "CONVERT", + "CREATE", + "CROSS", + "CUBE", + "CUME_DIST", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "CURSOR", + "DATABASE", + "DATABASES", + "DAY_HOUR", + "DAY_MICROSECOND", + "DAY_MINUTE", + "DAY_SECOND", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULT", + "DELAYED", + "DELETE", + "DENSE_RANK", + "DESC", + "DESCRIBE", + "DETERMINISTIC", + "DISTINCT", + "DISTINCTROW", + "DIV", + "DOUBLE", + "DROP", + "DUAL", + "EACH", + "ELSE", + "ELSEIF", + "EMPTY", + "ENCLOSED", + "ESCAPED", + "EXCEPT", + "EXISTS", + "EXIT", + "EXPLAIN", + "FALSE", + "FETCH", + "FIRST_VALUE", + "FLOAT", + "FLOAT4", + "FLOAT8", + "FOR", + "FORCE", + "FOREIGN", + "FROM", + "FULLTEXT", + "FUNCTION", + "GENERATED", + "GET", + "GRANT", + "GROUP", + "GROUPING", + "GROUPS", + "HAVING", + "HIGH_PRIORITY", + "HOUR_MICROSECOND", + "HOUR_MINUTE", + "HOUR_SECOND", + "IF", + "IGNORE", + "IN", + "INDEX", + "INFILE", + "INNER", + "INOUT", + "INSENSITIVE", + "INSERT", + "INT", + "INT1", + "INT2", + "INT3", + "INT4", + "INT8", + "INTEGER", + "INTERVAL", + "INTO", + "IO_AFTER_GTIDS", + "IO_BEFORE_GTIDS", + "IS", + "ITERATE", + "JOIN", + "JSON_TABLE", + "KEY", + "KEYS", + "KILL", + "LAG", + "LAST_VALUE", + "LATERAL", + "LEAD", + "LEADING", + "LEAVE", + "LEFT", + "LIKE", + "LIMIT", + "LINEAR", + "LINES", + "LOAD", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCK", + "LONG", + "LONGBLOB", + "LONGTEXT", + "LOOP", + "LOW_PRIORITY", + "MASTER_BIND", + "MASTER_SSL_VERIFY_SERVER_CERT", + "MATCH", + "MAXVALUE", + "MEDIUMBLOB", + "MEDIUMINT", + "MEDIUMTEXT", + "MIDDLEINT", + "MINUTE_MICROSECOND", + "MINUTE_SECOND", + "MOD", + "MODIFIES", + "NATURAL", + "NOT", + "NO_WRITE_TO_BINLOG", + "NTH_VALUE", + "NTILE", + "NULL", + "NUMERIC", + "OF", + "ON", + "OPTIMIZE", + "OPTIMIZER_COSTS", + "OPTION", + "OPTIONALLY", + "OR", + "ORDER", + "OUT", + "OUTER", + "OUTFILE", + "OVER", + "PARTITION", + "PERCENT_RANK", + "PRECISION", + "PRIMARY", + "PROCEDURE", + "PURGE", + "RANGE", + "RANK", + "READ", + "READS", + "READ_WRITE", + "REAL", + "RECURSIVE", + "REFERENCES", + "REGEXP", + "RELEASE", + "RENAME", + "REPEAT", + "REPLACE", + "REQUIRE", + "RESIGNAL", + "RESTRICT", + "RETURN", + "REVOKE", + "RIGHT", + "RLIKE", + "ROW", + "ROWS", + "ROW_NUMBER", + "SCHEMA", + "SCHEMAS", + "SECOND_MICROSECOND", + "SELECT", + "SENSITIVE", + "SEPARATOR", + "SET", + "SHOW", + "SIGNAL", + "SMALLINT", + "SPATIAL", + "SPECIFIC", + "SQL", + "SQLEXCEPTION", + "SQLSTATE", + "SQLWARNING", + "SQL_BIG_RESULT", + "SQL_CALC_FOUND_ROWS", + "SQL_SMALL_RESULT", + "SSL", + "STARTING", + "STORED", + "STRAIGHT_JOIN", + "SYSTEM", + "TABLE", + "TERMINATED", + "THEN", + "TINYBLOB", + "TINYINT", + "TINYTEXT", + "TO", + "TRAILING", + "TRIGGER", + "TRUE", + "UNDO", + "UNION", + "UNIQUE", + "UNLOCK", + "UNSIGNED", + "UPDATE", + "USAGE", + "USE", + "USING", + "UTC_DATE", + "UTC_TIME", + "UTC_TIMESTAMP", + "VALUES", + "VARBINARY", + "VARCHAR", + "VARCHARACTER", + "VARYING", + "VIRTUAL", + "WHEN", + "WHERE", + "WHILE", + "WINDOW", + "WITH", + "WRITE", + "XOR", + "YEAR_MONTH", + "ZEROFILL", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "ALTER COLUMN", + "ALTER TABLE", + "DELETE FROM", + "EXCEPT", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "ORDER BY", + "SELECT", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "WHEN", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + // non-standard joins + "STRAIGHT_JOIN", + "NATURAL LEFT JOIN", + "NATURAL LEFT OUTER JOIN", + "NATURAL RIGHT JOIN", + "NATURAL RIGHT OUTER JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: Array.Empty(), + lineCommentTypes: new[] { "#", "--" }, + specialWordChars: new[] { "@" }, + operators: new[] { ":=", "<<", ">>", "!=", "<>", "<=>", "&&", "||", "->", "->>" }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/N1qlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/N1qlFormatter.cs new file mode 100644 index 0000000000..67f992ca87 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/N1qlFormatter.cs @@ -0,0 +1,253 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + // For reference: http://docs.couchbase.com.s3-website-us-west-1.amazonaws.com/server/6.0/n1ql/n1ql-language-reference/index.html + internal sealed class N1qlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ALL", + "ALTER", + "ANALYZE", + "AND", + "ANY", + "ARRAY", + "AS", + "ASC", + "BEGIN", + "BETWEEN", + "BINARY", + "BOOLEAN", + "BREAK", + "BUCKET", + "BUILD", + "BY", + "CALL", + "CASE", + "CAST", + "CLUSTER", + "COLLATE", + "COLLECTION", + "COMMIT", + "CONNECT", + "CONTINUE", + "CORRELATE", + "COVER", + "CREATE", + "DATABASE", + "DATASET", + "DATASTORE", + "DECLARE", + "DECREMENT", + "DELETE", + "DERIVED", + "DESC", + "DESCRIBE", + "DISTINCT", + "DO", + "DROP", + "EACH", + "ELEMENT", + "ELSE", + "END", + "EVERY", + "EXCEPT", + "EXCLUDE", + "EXECUTE", + "EXISTS", + "EXPLAIN", + "FALSE", + "FETCH", + "FIRST", + "FLATTEN", + "FOR", + "FORCE", + "FROM", + "FUNCTION", + "GRANT", + "GROUP", + "GSI", + "HAVING", + "IF", + "IGNORE", + "ILIKE", + "IN", + "INCLUDE", + "INCREMENT", + "INDEX", + "INFER", + "INLINE", + "INNER", + "INSERT", + "INTERSECT", + "INTO", + "IS", + "JOIN", + "KEY", + "KEYS", + "KEYSPACE", + "KNOWN", + "LAST", + "LEFT", + "LET", + "LETTING", + "LIKE", + "LIMIT", + "LSM", + "MAP", + "MAPPING", + "MATCHED", + "MATERIALIZED", + "MERGE", + "MISSING", + "NAMESPACE", + "NEST", + "NOT", + "NULL", + "NUMBER", + "OBJECT", + "OFFSET", + "ON", + "OPTION", + "OR", + "ORDER", + "OUTER", + "OVER", + "PARSE", + "PARTITION", + "PASSWORD", + "PATH", + "POOL", + "PREPARE", + "PRIMARY", + "PRIVATE", + "PRIVILEGE", + "PROCEDURE", + "PUBLIC", + "RAW", + "REALM", + "REDUCE", + "RENAME", + "RETURN", + "RETURNING", + "REVOKE", + "RIGHT", + "ROLE", + "ROLLBACK", + "SATISFIES", + "SCHEMA", + "SELECT", + "SELF", + "SEMI", + "SET", + "SHOW", + "SOME", + "START", + "STATISTICS", + "STRING", + "SYSTEM", + "THEN", + "TO", + "TRANSACTION", + "TRIGGER", + "TRUE", + "TRUNCATE", + "UNDER", + "UNION", + "UNIQUE", + "UNKNOWN", + "UNNEST", + "UNSET", + "UPDATE", + "UPSERT", + "USE", + "USER", + "USING", + "VALIDATE", + "VALUE", + "VALUED", + "VALUES", + "VIA", + "VIEW", + "WHEN", + "WHERE", + "WHILE", + "WITH", + "WITHIN", + "WORK", + "XOR", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "DELETE FROM", + "EXCEPT ALL", + "EXCEPT", + "EXPLAIN DELETE FROM", + "EXPLAIN UPDATE", + "EXPLAIN UPSERT", + "FROM", + "GROUP BY", + "HAVING", + "INFER", + "INSERT INTO", + "LET", + "LIMIT", + "MERGE", + "NEST", + "ORDER BY", + "PREPARE", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "UNNEST", + "UPDATE", + "UPSERT", + "USE KEYS", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "OR", + "XOR", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``" }, + openParens: new[] { "(", "[", "{" }, + closeParens: new[] { ")", "]", "}" }, + indexedPlaceholderTypes: Array.Empty(), + namedPlaceholderTypes: new[] { "$" }, + lineCommentTypes: new[] { "#", "--" }, + specialWordChars: Array.Empty(), + operators: new[] { "==", "!=" }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PlSqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PlSqlFormatter.cs new file mode 100644 index 0000000000..bc3298774e --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PlSqlFormatter.cs @@ -0,0 +1,455 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class PlSqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "A", + "ACCESSIBLE", + "AGENT", + "AGGREGATE", + "ALL", + "ALTER", + "ANY", + "ARRAY", + "AS", + "ASC", + "AT", + "ATTRIBUTE", + "AUTHID", + "AVG", + "BETWEEN", + "BFILE_BASE", + "BINARY_INTEGER", + "BINARY", + "BLOB_BASE", + "BLOCK", + "BODY", + "BOOLEAN", + "BOTH", + "BOUND", + "BREADTH", + "BULK", + "BY", + "BYTE", + "C", + "CALL", + "CALLING", + "CASCADE", + "CASE", + "CHAR_BASE", + "CHAR", + "CHARACTER", + "CHARSET", + "CHARSETFORM", + "CHARSETID", + "CHECK", + "CLOB_BASE", + "CLONE", + "CLOSE", + "CLUSTER", + "CLUSTERS", + "COALESCE", + "COLAUTH", + "COLLECT", + "COLUMNS", + "COMMENT", + "COMMIT", + "COMMITTED", + "COMPILED", + "COMPRESS", + "CONNECT", + "CONSTANT", + "CONSTRUCTOR", + "CONTEXT", + "CONTINUE", + "CONVERT", + "COUNT", + "CRASH", + "CREATE", + "CREDENTIAL", + "CURRENT", + "CURRVAL", + "CURSOR", + "CUSTOMDATUM", + "DANGLING", + "DATA", + "DATE_BASE", + "DATE", + "DAY", + "DECIMAL", + "DEFAULT", + "DEFINE", + "DELETE", + "DEPTH", + "DESC", + "DETERMINISTIC", + "DIRECTORY", + "DISTINCT", + "DO", + "DOUBLE", + "DROP", + "DURATION", + "ELEMENT", + "ELSIF", + "EMPTY", + "END", + "ESCAPE", + "EXCEPTIONS", + "EXCLUSIVE", + "EXECUTE", + "EXISTS", + "EXIT", + "EXTENDS", + "EXTERNAL", + "EXTRACT", + "FALSE", + "FETCH", + "FINAL", + "FIRST", + "FIXED", + "FLOAT", + "FOR", + "FORALL", + "FORCE", + "FROM", + "FUNCTION", + "GENERAL", + "GOTO", + "GRANT", + "GROUP", + "HASH", + "HEAP", + "HIDDEN", + "HOUR", + "IDENTIFIED", + "IF", + "IMMEDIATE", + "IN", + "INCLUDING", + "INDEX", + "INDEXES", + "INDICATOR", + "INDICES", + "INFINITE", + "INSTANTIABLE", + "INT", + "INTEGER", + "INTERFACE", + "INTERVAL", + "INTO", + "INVALIDATE", + "IS", + "ISOLATION", + "JAVA", + "LANGUAGE", + "LARGE", + "LEADING", + "LENGTH", + "LEVEL", + "LIBRARY", + "LIKE", + "LIKE2", + "LIKE4", + "LIKEC", + "LIMITED", + "LOCAL", + "LOCK", + "LONG", + "MAP", + "MAX", + "MAXLEN", + "MEMBER", + "MERGE", + "MIN", + "MINUTE", + "MLSLABEL", + "MOD", + "MODE", + "MONTH", + "MULTISET", + "NAME", + "NAN", + "NATIONAL", + "NATIVE", + "NATURAL", + "NATURALN", + "NCHAR", + "NEW", + "NEXTVAL", + "NOCOMPRESS", + "NOCOPY", + "NOT", + "NOWAIT", + "NULL", + "NULLIF", + "NUMBER_BASE", + "NUMBER", + "OBJECT", + "OCICOLL", + "OCIDATE", + "OCIDATETIME", + "OCIDURATION", + "OCIINTERVAL", + "OCILOBLOCATOR", + "OCINUMBER", + "OCIRAW", + "OCIREF", + "OCIREFCURSOR", + "OCIROWID", + "OCISTRING", + "OCITYPE", + "OF", + "OLD", + "ON", + "ONLY", + "OPAQUE", + "OPEN", + "OPERATOR", + "OPTION", + "ORACLE", + "ORADATA", + "ORDER", + "ORGANIZATION", + "ORLANY", + "ORLVARY", + "OTHERS", + "OUT", + "OVERLAPS", + "OVERRIDING", + "PACKAGE", + "PARALLEL_ENABLE", + "PARAMETER", + "PARAMETERS", + "PARENT", + "PARTITION", + "PASCAL", + "PCTFREE", + "PIPE", + "PIPELINED", + "PLS_INTEGER", + "PLUGGABLE", + "POSITIVE", + "POSITIVEN", + "PRAGMA", + "PRECISION", + "PRIOR", + "PRIVATE", + "PROCEDURE", + "PUBLIC", + "RAISE", + "RANGE", + "RAW", + "READ", + "REAL", + "RECORD", + "REF", + "REFERENCE", + "RELEASE", + "RELIES_ON", + "REM", + "REMAINDER", + "RENAME", + "RESOURCE", + "RESULT_CACHE", + "RESULT", + "RETURN", + "RETURNING", + "REVERSE", + "REVOKE", + "ROLLBACK", + "ROW", + "ROWID", + "ROWNUM", + "ROWTYPE", + "SAMPLE", + "SAVE", + "SAVEPOINT", + "SB1", + "SB2", + "SB4", + "SEARCH", + "SECOND", + "SEGMENT", + "SELF", + "SEPARATE", + "SEQUENCE", + "SERIALIZABLE", + "SHARE", + "SHORT", + "SIZE_T", + "SIZE", + "SMALLINT", + "SOME", + "SPACE", + "SPARSE", + "SQL", + "SQLCODE", + "SQLDATA", + "SQLERRM", + "SQLNAME", + "SQLSTATE", + "STANDARD", + "START", + "STATIC", + "STDDEV", + "STORED", + "STRING", + "STRUCT", + "STYLE", + "SUBMULTISET", + "SUBPARTITION", + "SUBSTITUTABLE", + "SUBTYPE", + "SUCCESSFUL", + "SUM", + "SYNONYM", + "SYSDATE", + "TABAUTH", + "TABLE", + "TDO", + "THE", + "THEN", + "TIME", + "TIMESTAMP", + "TIMEZONE_ABBR", + "TIMEZONE_HOUR", + "TIMEZONE_MINUTE", + "TIMEZONE_REGION", + "TO", + "TRAILING", + "TRANSACTION", + "TRANSACTIONAL", + "TRIGGER", + "TRUE", + "TRUSTED", + "TYPE", + "UB1", + "UB2", + "UB4", + "UID", + "UNDER", + "UNIQUE", + "UNPLUG", + "UNSIGNED", + "UNTRUSTED", + "USE", + "USER", + "USING", + "VALIDATE", + "VALIST", + "VALUE", + "VARCHAR", + "VARCHAR2", + "VARIABLE", + "VARIANCE", + "VARRAY", + "VARYING", + "VIEW", + "VIEWS", + "VOID", + "WHENEVER", + "WHILE", + "WITH", + "WORK", + "WRAPPED", + "WRITE", + "YEAR", + "ZONE", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "ALTER COLUMN", + "ALTER TABLE", + "BEGIN", + "CONNECT BY", + "DECLARE", + "DELETE FROM", + "DELETE", + "END", + "EXCEPT", + "EXCEPTION", + "FETCH FIRST", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "LOOP", + "MODIFY", + "ORDER BY", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "START WITH", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "MINUS", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "CROSS APPLY", + "ELSE", + "END", + "OR", + "OUTER APPLY", + "WHEN", + "XOR", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "N''", "''", "``" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: new[] { ":" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: new[] { "_", "$", "#", ".", "@" }, + operators: new[] { "||", "**", "!=", ":=" }); + } + + protected override Token TokenOverride(Token token) + { + if (TokenHelper.isSet(token) && TokenHelper.isBy(base._previousReservedToken)) + { + return new Token(token.Value, TokenType.Reserved); + } + + return base.TokenOverride(token); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PostgreSqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PostgreSqlFormatter.cs new file mode 100644 index 0000000000..c5f44a4011 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/PostgreSqlFormatter.cs @@ -0,0 +1,551 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class PostgreSqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ABORT", + "ABSOLUTE", + "ACCESS", + "ACTION", + "ADD", + "ADMIN", + "AFTER", + "AGGREGATE", + "ALL", + "ALSO", + "ALTER", + "ALWAYS", + "ANALYSE", + "ANALYZE", + "AND", + "ANY", + "ARRAY", + "AS", + "ASC", + "ASSERTION", + "ASSIGNMENT", + "ASYMMETRIC", + "AT", + "ATTACH", + "ATTRIBUTE", + "AUTHORIZATION", + "BACKWARD", + "BEFORE", + "BEGIN", + "BETWEEN", + "BIGINT", + "BINARY", + "BIT", + "BOOLEAN", + "BOTH", + "BY", + "CACHE", + "CALL", + "CALLED", + "CASCADE", + "CASCADED", + "CASE", + "CAST", + "CATALOG", + "CHAIN", + "CHAR", + "CHARACTER", + "CHARACTERISTICS", + "CHECK", + "CHECKPOINT", + "CLASS", + "CLOSE", + "CLUSTER", + "COALESCE", + "COLLATE", + "COLLATION", + "COLUMN", + "COLUMNS", + "COMMENT", + "COMMENTS", + "COMMIT", + "COMMITTED", + "CONCURRENTLY", + "CONFIGURATION", + "CONFLICT", + "CONNECTION", + "CONSTRAINT", + "CONSTRAINTS", + "CONTENT", + "CONTINUE", + "CONVERSION", + "COPY", + "COST", + "CREATE", + "CROSS", + "CSV", + "CUBE", + "CURRENT", + "CURRENT_CATALOG", + "CURRENT_DATE", + "CURRENT_ROLE", + "CURRENT_SCHEMA", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "CURSOR", + "CYCLE", + "DATA", + "DATABASE", + "DAY", + "DEALLOCATE", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULT", + "DEFAULTS", + "DEFERRABLE", + "DEFERRED", + "DEFINER", + "DELETE", + "DELIMITER", + "DELIMITERS", + "DEPENDS", + "DESC", + "DETACH", + "DICTIONARY", + "DISABLE", + "DISCARD", + "DISTINCT", + "DO", + "DOCUMENT", + "DOMAIN", + "DOUBLE", + "DROP", + "EACH", + "ELSE", + "ENABLE", + "ENCODING", + "ENCRYPTED", + "END", + "ENUM", + "ESCAPE", + "EVENT", + "EXCEPT", + "EXCLUDE", + "EXCLUDING", + "EXCLUSIVE", + "EXECUTE", + "EXISTS", + "EXPLAIN", + "EXPRESSION", + "EXTENSION", + "EXTERNAL", + "EXTRACT", + "FALSE", + "FAMILY", + "FETCH", + "FILTER", + "FIRST", + "FLOAT", + "FOLLOWING", + "FOR", + "FORCE", + "FOREIGN", + "FORWARD", + "FREEZE", + "FROM", + "FULL", + "FUNCTION", + "FUNCTIONS", + "GENERATED", + "GLOBAL", + "GRANT", + "GRANTED", + "GREATEST", + "GROUP", + "GROUPING", + "GROUPS", + "HANDLER", + "HAVING", + "HEADER", + "HOLD", + "HOUR", + "IDENTITY", + "IF", + "ILIKE", + "IMMEDIATE", + "IMMUTABLE", + "IMPLICIT", + "IMPORT", + "IN", + "INCLUDE", + "INCLUDING", + "INCREMENT", + "INDEX", + "INDEXES", + "INHERIT", + "INHERITS", + "INITIALLY", + "INLINE", + "INNER", + "INOUT", + "INPUT", + "INSENSITIVE", + "INSERT", + "INSTEAD", + "INT", + "INTEGER", + "INTERSECT", + "INTERVAL", + "INTO", + "INVOKER", + "IS", + "ISNULL", + "ISOLATION", + "JOIN", + "KEY", + "LABEL", + "LANGUAGE", + "LARGE", + "LAST", + "LATERAL", + "LEADING", + "LEAKPROOF", + "LEAST", + "LEFT", + "LEVEL", + "LIKE", + "LIMIT", + "LISTEN", + "LOAD", + "LOCAL", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOCATION", + "LOCK", + "LOCKED", + "LOGGED", + "MAPPING", + "MATCH", + "MATERIALIZED", + "MAXVALUE", + "METHOD", + "MINUTE", + "MINVALUE", + "MODE", + "MONTH", + "MOVE", + "NAME", + "NAMES", + "NATIONAL", + "NATURAL", + "NCHAR", + "NEW", + "NEXT", + "NFC", + "NFD", + "NFKC", + "NFKD", + "NO", + "NONE", + "NORMALIZE", + "NORMALIZED", + "NOT", + "NOTHING", + "NOTIFY", + "NOTNULL", + "NOWAIT", + "NULL", + "NULLIF", + "NULLS", + "NUMERIC", + "OBJECT", + "OF", + "OFF", + "OFFSET", + "OIDS", + "OLD", + "ON", + "ONLY", + "OPERATOR", + "OPTION", + "OPTIONS", + "OR", + "ORDER", + "ORDINALITY", + "OTHERS", + "OUT", + "OUTER", + "OVER", + "OVERLAPS", + "OVERLAY", + "OVERRIDING", + "OWNED", + "OWNER", + "PARALLEL", + "PARSER", + "PARTIAL", + "PARTITION", + "PASSING", + "PASSWORD", + "PLACING", + "PLANS", + "POLICY", + "POSITION", + "PRECEDING", + "PRECISION", + "PREPARE", + "PREPARED", + "PRESERVE", + "PRIMARY", + "PRIOR", + "PRIVILEGES", + "PROCEDURAL", + "PROCEDURE", + "PROCEDURES", + "PROGRAM", + "PUBLICATION", + "QUOTE", + "RANGE", + "READ", + "REAL", + "REASSIGN", + "RECHECK", + "RECURSIVE", + "REF", + "REFERENCES", + "REFERENCING", + "REFRESH", + "REINDEX", + "RELATIVE", + "RELEASE", + "RENAME", + "REPEATABLE", + "REPLACE", + "REPLICA", + "RESET", + "RESTART", + "RESTRICT", + "RETURNING", + "RETURNS", + "REVOKE", + "RIGHT", + "ROLE", + "ROLLBACK", + "ROLLUP", + "ROUTINE", + "ROUTINES", + "ROW", + "ROWS", + "RULE", + "SAVEPOINT", + "SCHEMA", + "SCHEMAS", + "SCROLL", + "SEARCH", + "SECOND", + "SECURITY", + "SELECT", + "SEQUENCE", + "SEQUENCES", + "SERIALIZABLE", + "SERVER", + "SESSION", + "SESSION_USER", + "SET", + "SETOF", + "SETS", + "SHARE", + "SHOW", + "SIMILAR", + "SIMPLE", + "SKIP", + "SMALLINT", + "SNAPSHOT", + "SOME", + "SQL", + "STABLE", + "STANDALONE", + "START", + "STATEMENT", + "STATISTICS", + "STDIN", + "STDOUT", + "STORAGE", + "STORED", + "STRICT", + "STRIP", + "SUBSCRIPTION", + "SUBSTRING", + "SUPPORT", + "SYMMETRIC", + "SYSID", + "SYSTEM", + "TABLE", + "TABLES", + "TABLESAMPLE", + "TABLESPACE", + "TEMP", + "TEMPLATE", + "TEMPORARY", + "TEXT", + "THEN", + "TIES", + "TIME", + "TIMESTAMP", + "TO", + "TRAILING", + "TRANSACTION", + "TRANSFORM", + "TREAT", + "TRIGGER", + "TRIM", + "TRUE", + "TRUNCATE", + "TRUSTED", + "TYPE", + "TYPES", + "UESCAPE", + "UNBOUNDED", + "UNCOMMITTED", + "UNENCRYPTED", + "UNION", + "UNIQUE", + "UNKNOWN", + "UNLISTEN", + "UNLOGGED", + "UNTIL", + "UPDATE", + "USER", + "USING", + "VACUUM", + "VALID", + "VALIDATE", + "VALIDATOR", + "VALUE", + "VALUES", + "VARCHAR", + "VARIADIC", + "VARYING", + "VERBOSE", + "VERSION", + "VIEW", + "VIEWS", + "VOLATILE", + "WHEN", + "WHERE", + "WHITESPACE", + "WINDOW", + "WITH", + "WITHIN", + "WITHOUT", + "WORK", + "WRAPPER", + "WRITE", + "XML", + "XMLATTRIBUTES", + "XMLCONCAT", + "XMLELEMENT", + "XMLEXISTS", + "XMLFOREST", + "XMLNAMESPACES", + "XMLPARSE", + "XMLPI", + "XMLROOT", + "XMLSERIALIZE", + "XMLTABLE", + "YEAR", + "YES", + "ZONE", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "AFTER", + "ALTER COLUMN", + "ALTER TABLE", + "CASE", + "DELETE FROM", + "END", + "EXCEPT", + "FETCH FIRST", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "ORDER BY", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = new[] { "INTERSECT", "INTERSECT ALL", "UNION", "UNION ALL" }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "WHEN", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "U&''", "U&\"\"", "$$" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "$" }, + namedPlaceholderTypes: new[] { ":" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: Array.Empty(), + operators: new[] + { + "!=", + "<<", + ">>", + "||/", + "|/", + "::", + "->>", + "->", + "~~*", + "~~", + "!~~*", + "!~~", + "~*", + "!~*", + "!~", + "!!" + }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/RedshiftFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/RedshiftFormatter.cs new file mode 100644 index 0000000000..92be9dbb80 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/RedshiftFormatter.cs @@ -0,0 +1,406 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class RedshiftFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "AES128", + "AES256", + "ALLOWOVERWRITE", + "ANALYSE", + "ARRAY", + "AS", + "ASC", + "AUTHORIZATION", + "BACKUP", + "BINARY", + "BLANKSASNULL", + "BOTH", + "BYTEDICT", + "BZIP2", + "CAST", + "CHECK", + "COLLATE", + "COLUMN", + "CONSTRAINT", + "CREATE", + "CREDENTIALS", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_USER", + "CURRENT_USER_ID", + "DEFAULT", + "DEFERRABLE", + "DEFLATE", + "DEFRAG", + "DELTA", + "DELTA32K", + "DESC", + "DISABLE", + "DISTINCT", + "DO", + "ELSE", + "EMPTYASNULL", + "ENABLE", + "ENCODE", + "ENCRYPT", + "ENCRYPTION", + "END", + "EXPLICIT", + "FALSE", + "FOR", + "FOREIGN", + "FREEZE", + "FULL", + "GLOBALDICT256", + "GLOBALDICT64K", + "GRANT", + "GZIP", + "IDENTITY", + "IGNORE", + "ILIKE", + "INITIALLY", + "INTO", + "LEADING", + "LOCALTIME", + "LOCALTIMESTAMP", + "LUN", + "LUNS", + "LZO", + "LZOP", + "MINUS", + "MOSTLY13", + "MOSTLY32", + "MOSTLY8", + "NATURAL", + "NEW", + "NULLS", + "OFF", + "OFFLINE", + "OFFSET", + "OLD", + "ON", + "ONLY", + "OPEN", + "ORDER", + "OVERLAPS", + "PARALLEL", + "PARTITION", + "PERCENT", + "PERMISSIONS", + "PLACING", + "PRIMARY", + "RAW", + "READRATIO", + "RECOVER", + "REFERENCES", + "REJECTLOG", + "RESORT", + "RESTORE", + "SESSION_USER", + "SIMILAR", + "SYSDATE", + "SYSTEM", + "TABLE", + "TAG", + "TDES", + "TEXT255", + "TEXT32K", + "THEN", + "TIMESTAMP", + "TO", + "TOP", + "TRAILING", + "TRUE", + "TRUNCATECOLUMNS", + "UNIQUE", + "USER", + "USING", + "VERBOSE", + "WALLET", + "WHEN", + "WITH", + "WITHOUT", + "PREDICATE", + "COLUMNS", + "COMPROWS", + "COMPRESSION", + "COPY", + "FORMAT", + "DELIMITER", + "FIXEDWIDTH", + "AVRO", + "JSON", + "ENCRYPTED", + "BZIP2", + "GZIP", + "LZOP", + "PARQUET", + "ORC", + "ACCEPTANYDATE", + "ACCEPTINVCHARS", + "BLANKSASNULL", + "DATEFORMAT", + "EMPTYASNULL", + "ENCODING", + "ESCAPE", + "EXPLICIT_IDS", + "FILLRECORD", + "IGNOREBLANKLINES", + "IGNOREHEADER", + "NULL AS", + "REMOVEQUOTES", + "ROUNDEC", + "TIMEFORMAT", + "TRIMBLANKS", + "TRUNCATECOLUMNS", + "COMPROWS", + "COMPUPDATE", + "MAXERROR", + "NOLOAD", + "STATUPDATE", + "MANIFEST", + "REGION", + "IAM_ROLE", + "MASTER_SYMMETRIC_KEY", + "SSH", + "ACCEPTANYDATE", + "ACCEPTINVCHARS", + "ACCESS_KEY_ID", + "SECRET_ACCESS_KEY", + "AVRO", + "BLANKSASNULL", + "BZIP2", + "COMPROWS", + "COMPUPDATE", + "CREDENTIALS", + "DATEFORMAT", + "DELIMITER", + "EMPTYASNULL", + "ENCODING", + "ENCRYPTED", + "ESCAPE", + "EXPLICIT_IDS", + "FILLRECORD", + "FIXEDWIDTH", + "FORMAT", + "IAM_ROLE", + "GZIP", + "IGNOREBLANKLINES", + "IGNOREHEADER", + "JSON", + "LZOP", + "MANIFEST", + "MASTER_SYMMETRIC_KEY", + "MAXERROR", + "NOLOAD", + "NULL AS", + "READRATIO", + "REGION", + "REMOVEQUOTES", + "ROUNDEC", + "SSH", + "STATUPDATE", + "TIMEFORMAT", + "SESSION_TOKEN", + "TRIMBLANKS", + "TRUNCATECOLUMNS", + "EXTERNAL", + "DATA CATALOG", + "HIVE METASTORE", + "CATALOG_ROLE", + "VACUUM", + "COPY", + "UNLOAD", + "EVEN", + "ALL", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "AFTER", + "ALTER COLUMN", + "ALTER TABLE", + "DELETE FROM", + "EXCEPT", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "INTERSECT", + "TOP", + "LIMIT", + "MODIFY", + "ORDER BY", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "UNION ALL", + "UNION", + "UPDATE", + "VALUES", + "WHERE", + "VACUUM", + "COPY", + "UNLOAD", + "ANALYZE", + "ANALYSE", + "DISTKEY", + "SORTKEY", + "COMPOUND", + "INTERLEAVED", + "FORMAT", + "DELIMITER", + "FIXEDWIDTH", + "AVRO", + "JSON", + "ENCRYPTED", + "BZIP2", + "GZIP", + "LZOP", + "PARQUET", + "ORC", + "ACCEPTANYDATE", + "ACCEPTINVCHARS", + "BLANKSASNULL", + "DATEFORMAT", + "EMPTYASNULL", + "ENCODING", + "ESCAPE", + "EXPLICIT_IDS", + "FILLRECORD", + "IGNOREBLANKLINES", + "IGNOREHEADER", + "NULL AS", + "REMOVEQUOTES", + "ROUNDEC", + "TIMEFORMAT", + "TRIMBLANKS", + "TRUNCATECOLUMNS", + "COMPROWS", + "COMPUPDATE", + "MAXERROR", + "NOLOAD", + "STATUPDATE", + "MANIFEST", + "REGION", + "IAM_ROLE", + "MASTER_SYMMETRIC_KEY", + "SSH", + "ACCEPTANYDATE", + "ACCEPTINVCHARS", + "ACCESS_KEY_ID", + "SECRET_ACCESS_KEY", + "AVRO", + "BLANKSASNULL", + "BZIP2", + "COMPROWS", + "COMPUPDATE", + "CREDENTIALS", + "DATEFORMAT", + "DELIMITER", + "EMPTYASNULL", + "ENCODING", + "ENCRYPTED", + "ESCAPE", + "EXPLICIT_IDS", + "FILLRECORD", + "FIXEDWIDTH", + "FORMAT", + "IAM_ROLE", + "GZIP", + "IGNOREBLANKLINES", + "IGNOREHEADER", + "JSON", + "LZOP", + "MANIFEST", + "MASTER_SYMMETRIC_KEY", + "MAXERROR", + "NOLOAD", + "NULL AS", + "READRATIO", + "REGION", + "REMOVEQUOTES", + "ROUNDEC", + "SSH", + "STATUPDATE", + "TIMEFORMAT", + "SESSION_TOKEN", + "TRIMBLANKS", + "TRUNCATECOLUMNS", + "EXTERNAL", + "DATA CATALOG", + "HIVE METASTORE", + "CATALOG_ROLE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent = Array.Empty(); + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "OUTER APPLY", + "WHEN", + "VACUUM", + "COPY", + "UNLOAD", + "ANALYZE", + "ANALYSE", + "DISTKEY", + "SORTKEY", + "COMPOUND", + "INTERLEAVED", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``" }, + openParens: new[] { "(" }, + closeParens: new[] { ")" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: new[] { "@", "#", "$" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: Array.Empty(), + operators: new[] + { + "|/", + "||/", + "<<", + ">>", + "!=", + "||" + }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/SparkSqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/SparkSqlFormatter.cs new file mode 100644 index 0000000000..08680849c7 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/SparkSqlFormatter.cs @@ -0,0 +1,299 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class SparkSqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ALL", + "ALTER", + "ANALYSE", + "ANALYZE", + "ARRAY_ZIP", + "ARRAY", + "AS", + "ASC", + "AVG", + "BETWEEN", + "CASCADE", + "CASE", + "CAST", + "COALESCE", + "COLLECT_LIST", + "COLLECT_SET", + "COLUMN", + "COLUMNS", + "COMMENT", + "CONSTRAINT", + "CONTAINS", + "CONVERT", + "COUNT", + "CUME_DIST", + "CURRENT ROW", + "CURRENT_DATE", + "CURRENT_TIMESTAMP", + "DATABASE", + "DATABASES", + "DATE_ADD", + "DATE_SUB", + "DATE_TRUNC", + "DAY_HOUR", + "DAY_MINUTE", + "DAY_SECOND", + "DAY", + "DAYS", + "DECODE", + "DEFAULT", + "DELETE", + "DENSE_RANK", + "DESC", + "DESCRIBE", + "DISTINCT", + "DISTINCTROW", + "DIV", + "DROP", + "ELSE", + "ENCODE", + "END", + "EXISTS", + "EXPLAIN", + "EXPLODE_OUTER", + "EXPLODE", + "FILTER", + "FIRST_VALUE", + "FIRST", + "FIXED", + "FLATTEN", + "FOLLOWING", + "FROM_UNIXTIME", + "FULL", + "GREATEST", + "GROUP_CONCAT", + "HOUR_MINUTE", + "HOUR_SECOND", + "HOUR", + "HOURS", + "IF", + "IFNULL", + "IN", + "INSERT", + "INTERVAL", + "INTO", + "IS", + "LAG", + "LAST_VALUE", + "LAST", + "LEAD", + "LEADING", + "LEAST", + "LEVEL", + "LIKE", + "MAX", + "MERGE", + "MIN", + "MINUTE_SECOND", + "MINUTE", + "MONTH", + "NATURAL", + "NOT", + "NOW()", + "NTILE", + "NULL", + "NULLIF", + "OFFSET", + "ON DELETE", + "ON UPDATE", + "ON", + "ONLY", + "OPTIMIZE", + "OVER", + "PERCENT_RANK", + "PRECEDING", + "RANGE", + "RANK", + "REGEXP", + "RENAME", + "RLIKE", + "ROW", + "ROWS", + "SECOND", + "SEPARATOR", + "SEQUENCE", + "SIZE", + "STRING", + "STRUCT", + "SUM", + "TABLE", + "TABLES", + "TEMPORARY", + "THEN", + "TO_DATE", + "TO_JSON", + "TO", + "TRAILING", + "TRANSFORM", + "TRUE", + "TRUNCATE", + "TYPE", + "TYPES", + "UNBOUNDED", + "UNIQUE", + "UNIX_TIMESTAMP", + "UNLOCK", + "UNSIGNED", + "USING", + "VARIABLES", + "VIEW", + "WHEN", + "WITH", + "YEAR_MONTH", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "AFTER", + "ALTER COLUMN", + "ALTER DATABASE", + "ALTER SCHEMA", + "ALTER TABLE", + "CLUSTER BY", + "CLUSTERED BY", + "DELETE FROM", + "DISTRIBUTE BY", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "OPTIONS", + "ORDER BY", + "PARTITION BY", + "PARTITIONED BY", + "RANGE", + "ROWS", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "TBLPROPERTIES", + "UPDATE", + "USING", + "VALUES", + "WHERE", + "WINDOW", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent + = + { + "EXCEPT ALL", + "EXCEPT", + "INTERSECT ALL", + "INTERSECT", + "UNION ALL", + "UNION" + }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "CREATE OR", + "CREATE", + "ELSE", + "LATERAL VIEW", + "OR", + "OUTER APPLY", + "WHEN", + "XOR", + // joins + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + // non-standard-joins + "ANTI JOIN", + "SEMI JOIN", + "LEFT ANTI JOIN", + "LEFT SEMI JOIN", + "RIGHT OUTER JOIN", + "RIGHT SEMI JOIN", + "NATURAL ANTI JOIN", + "NATURAL FULL OUTER JOIN", + "NATURAL INNER JOIN", + "NATURAL LEFT ANTI JOIN", + "NATURAL LEFT OUTER JOIN", + "NATURAL LEFT SEMI JOIN", + "NATURAL OUTER JOIN", + "NATURAL RIGHT OUTER JOIN", + "NATURAL RIGHT SEMI JOIN", + "NATURAL SEMI JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''", "``", "{}" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: new[] { "$" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: Array.Empty(), + operators: new[] + { + "!=", + "<=>", + "&&", + "||", + "==" + }); + } + + protected override Token TokenOverride(Token token) + { + // Fix cases where names are ambiguously keywords or functions + if (TokenHelper.isWindow(token)) + { + Token? aheadToken = TokenLookAhead(); + if (aheadToken is not null && aheadToken.Type == TokenType.OpenParen) + { + // This is a function call, treat it as a reserved word + return new Token(token.Value, TokenType.Reserved); + } + } + + // Fix cases where names are ambiguously keywords or properties + if (TokenHelper.isEnd(token)) + { + Token? backToken = TokenLookBehind(); + if (backToken is not null && backToken.Type == TokenType.Operator && string.Equals(backToken.Value, ".", StringComparison.Ordinal)) + { + // This is window().end (or similar) not CASE ... END + return new Token(token.Value, TokenType.Word); + } + } + + return token; + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/StandardSqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/StandardSqlFormatter.cs new file mode 100644 index 0000000000..f859b2df6d --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/StandardSqlFormatter.cs @@ -0,0 +1,390 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class StandardSqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ABS", + "ALL", + "ALLOCATE", + "ALTER", + "AND", + "ANY", + "ARE", + "ARRAY", + "AS", + "ASENSITIVE", + "ASYMMETRIC", + "AT", + "ATOMIC", + "AUTHORIZATION", + "AVG", + "BEGIN", + "BETWEEN", + "BIGINT", + "BINARY", + "BLOB", + "BOOLEAN", + "BOTH", + "BY", + "CALL", + "CALLED", + "CARDINALITY", + "CASCADED", + "CASE", + "CAST", + "CEIL", + "CEILING", + "CHAR", + "CHAR_LENGTH", + "CHARACTER", + "CHARACTER_LENGTH", + "CHECK", + "CLOB", + "CLOSE", + "COALESCE", + "COLLATE", + "COLLECT", + "COLUMN", + "COMMIT", + "CONDITION", + "CONNECT", + "CONSTRAINT", + "CONVERT", + "CORR", + "CORRESPONDING", + "COUNT", + "COVAR_POP", + "COVAR_SAMP", + "CREATE", + "CROSS", + "CUBE", + "CUME_DIST", + "CURRENT", + "CURRENT_CATALOG", + "CURRENT_DATE", + "CURRENT_DEFAULT_TRANSFORM_GROUP", + "CURRENT_PATH", + "CURRENT_ROLE", + "CURRENT_SCHEMA", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", + "CURRENT_USER", + "CURSOR", + "CYCLE", + "DATE", + "DAY", + "DEALLOCATE", + "DEC", + "DECIMAL", + "DECLARE", + "DEFAULT", + "DELETE", + "DENSE_RANK", + "DEREF", + "DESCRIBE", + "DETERMINISTIC", + "DISCONNECT", + "DISTINCT", + "DOUBLE", + "DROP", + "DYNAMIC", + "EACH", + "ELEMENT", + "ELSE", + "END", + "END-EXEC", + "ESCAPE", + "EVERY", + "EXCEPT", + "EXEC", + "EXECUTE", + "EXISTS", + "EXP", + "EXTERNAL", + "EXTRACT", + "FALSE", + "FETCH", + "FILTER", + "FLOAT", + "FLOOR", + "FOR", + "FOREIGN", + "FREE", + "FROM", + "FULL", + "FUNCTION", + "FUSION", + "GET", + "GLOBAL", + "GRANT", + "GROUP", + "GROUPING", + "HAVING", + "HOLD", + "HOUR", + "IDENTITY", + "IN", + "INDICATOR", + "INNER", + "INOUT", + "INSENSITIVE", + "INSERT", + "INT", + "INTEGER", + "INTERSECT", + "INTERSECTION", + "INTERVAL", + "INTO", + "IS", + "JOIN", + "LANGUAGE", + "LARGE", + "LATERAL", + "LEADING", + "LEFT", + "LIKE", + "LIKE_REGEX", + "LN", + "LOCAL", + "LOCALTIME", + "LOCALTIMESTAMP", + "LOWER", + "MATCH", + "MAX", + "MEMBER", + "MERGE", + "METHOD", + "MIN", + "MINUTE", + "MOD", + "MODIFIES", + "MODULE", + "MONTH", + "MULTISET", + "NATIONAL", + "NATURAL", + "NCHAR", + "NCLOB", + "NEW", + "NO", + "NONE", + "NORMALIZE", + "NOT", + "NULL", + "NULLIF", + "NUMERIC", + "OCTET_LENGTH", + "OCCURRENCES_REGEX", + "OF", + "OLD", + "ON", + "ONLY", + "OPEN", + "OR", + "ORDER", + "OUT", + "OUTER", + "OVER", + "OVERLAPS", + "OVERLAY", + "PARAMETER", + "PARTITION", + "PERCENT_RANK", + "PERCENTILE_CONT", + "PERCENTILE_DISC", + "POSITION", + "POSITION_REGEX", + "POWER", + "PRECISION", + "PREPARE", + "PRIMARY", + "PROCEDURE", + "RANGE", + "RANK", + "READS", + "REAL", + "RECURSIVE", + "REF", + "REFERENCES", + "REFERENCING", + "REGR_AVGX", + "REGR_AVGY", + "REGR_COUNT", + "REGR_INTERCEPT", + "REGR_R2", + "REGR_SLOPE", + "REGR_SXX", + "REGR_SXY", + "REGR_SYY", + "RELEASE", + "RESULT", + "RETURN", + "RETURNS", + "REVOKE", + "RIGHT", + "ROLLBACK", + "ROLLUP", + "ROW", + "ROW_NUMBER", + "ROWS", + "SAVEPOINT", + "SCOPE", + "SCROLL", + "SEARCH", + "SECOND", + "SELECT", + "SENSITIVE", + "SESSION_USER", + "SET", + "SIMILAR", + "SMALLINT", + "SOME", + "SPECIFIC", + "SPECIFICTYPE", + "SQL", + "SQLEXCEPTION", + "SQLSTATE", + "SQLWARNING", + "SQRT", + "START", + "STATIC", + "STDDEV_POP", + "STDDEV_SAMP", + "SUBMULTISET", + "SUBSTRING", + "SUBSTRING_REGEX", + "SUM", + "SYMMETRIC", + "SYSTEM", + "SYSTEM_USER", + "TABLE", + "TABLESAMPLE", + "THEN", + "TIME", + "TIMESTAMP", + "TIMEZONE_HOUR", + "TIMEZONE_MINUTE", + "TO", + "TRAILING", + "TRANSLATE", + "TRANSLATE_REGEX", + "TRANSLATION", + "TREAT", + "TRIGGER", + "TRIM", + "TRUE", + "UESCAPE", + "UNION", + "UNIQUE", + "UNKNOWN", + "UNNEST", + "UPDATE", + "UPPER", + "USER", + "USING", + "VALUE", + "VALUES", + "VAR_POP", + "VAR_SAMP", + "VARBINARY", + "VARCHAR", + "VARYING", + "WHEN", + "WHENEVER", + "WHERE", + "WIDTH_BUCKET", + "WINDOW", + "WITH", + "WITHIN", + "WITHOUT", + "YEAR", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "ALTER COLUMN", + "ALTER TABLE", + "CASE", + "DELETE FROM", + "END", + "FETCH FIRST", + "FETCH NEXT", + "FETCH PRIOR", + "FETCH LAST", + "FETCH ABSOLUTE", + "FETCH RELATIVE", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "LIMIT", + "ORDER BY", + "SELECT", + "SET SCHEMA", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent + = + { + "INTERSECT", + "INTERSECT ALL", + "INTERSECT DISTINCT", + "UNION", + "UNION ALL", + "UNION DISTINCT", + "EXCEPT", + "EXCEPT ALL", + "EXCEPT DISTINCT", + }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "WHEN", + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "''" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: new[] { "?" }, + namedPlaceholderTypes: Array.Empty(), + lineCommentTypes: new[] { "--" }, + specialWordChars: Array.Empty()); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/TSqlFormatter.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/TSqlFormatter.cs new file mode 100644 index 0000000000..034e4c8395 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/Languages/TSqlFormatter.cs @@ -0,0 +1,290 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; + +namespace DevToys.Helpers.SqlFormatter.Languages +{ + internal sealed class TSqlFormatter : Formatter + { + private static readonly string[] ReservedWords + = + { + "ADD", + "EXTERNAL", + "PROCEDURE", + "ALL", + "FETCH", + "PUBLIC", + "ALTER", + "FILE", + "RAISERROR", + "AND", + "FILLFACTOR", + "READ", + "ANY", + "FOR", + "READTEXT", + "AS", + "FOREIGN", + "RECONFIGURE", + "ASC", + "FREETEXT", + "REFERENCES", + "AUTHORIZATION", + "FREETEXTTABLE", + "REPLICATION", + "BACKUP", + "FROM", + "RESTORE", + "BEGIN", + "FULL", + "RESTRICT", + "BETWEEN", + "FUNCTION", + "RETURN", + "BREAK", + "GOTO", + "REVERT", + "BROWSE", + "GRANT", + "REVOKE", + "BULK", + "GROUP", + "RIGHT", + "BY", + "HAVING", + "ROLLBACK", + "CASCADE", + "HOLDLOCK", + "ROWCOUNT", + "CASE", + "IDENTITY", + "ROWGUIDCOL", + "CHECK", + "IDENTITY_INSERT", + "RULE", + "CHECKPOINT", + "IDENTITYCOL", + "SAVE", + "CLOSE", + "IF", + "SCHEMA", + "CLUSTERED", + "IN", + "SECURITYAUDIT", + "COALESCE", + "INDEX", + "SELECT", + "COLLATE", + "INNER", + "SEMANTICKEYPHRASETABLE", + "COLUMN", + "INSERT", + "SEMANTICSIMILARITYDETAILSTABLE", + "COMMIT", + "INTERSECT", + "SEMANTICSIMILARITYTABLE", + "COMPUTE", + "INTO", + "SESSION_USER", + "CONSTRAINT", + "IS", + "SET", + "CONTAINS", + "JOIN", + "SETUSER", + "CONTAINSTABLE", + "KEY", + "SHUTDOWN", + "CONTINUE", + "KILL", + "SOME", + "CONVERT", + "LEFT", + "STATISTICS", + "CREATE", + "LIKE", + "SYSTEM_USER", + "CROSS", + "LINENO", + "TABLE", + "CURRENT", + "LOAD", + "TABLESAMPLE", + "CURRENT_DATE", + "MERGE", + "TEXTSIZE", + "CURRENT_TIME", + "NATIONAL", + "THEN", + "CURRENT_TIMESTAMP", + "NOCHECK", + "TO", + "CURRENT_USER", + "NONCLUSTERED", + "TOP", + "CURSOR", + "NOT", + "TRAN", + "DATABASE", + "NULL", + "TRANSACTION", + "DBCC", + "NULLIF", + "TRIGGER", + "DEALLOCATE", + "OF", + "TRUNCATE", + "DECLARE", + "OFF", + "TRY_CONVERT", + "DEFAULT", + "OFFSETS", + "TSEQUAL", + "DELETE", + "ON", + "UNION", + "DENY", + "OPEN", + "UNIQUE", + "DESC", + "OPENDATASOURCE", + "UNPIVOT", + "DISK", + "OPENQUERY", + "UPDATE", + "DISTINCT", + "OPENROWSET", + "UPDATETEXT", + "DISTRIBUTED", + "OPENXML", + "USE", + "DOUBLE", + "OPTION", + "USER", + "DROP", + "OR", + "VALUES", + "DUMP", + "ORDER", + "VARYING", + "ELSE", + "OUTER", + "VIEW", + "END", + "OVER", + "WAITFOR", + "ERRLVL", + "PERCENT", + "WHEN", + "ESCAPE", + "PIVOT", + "WHERE", + "EXCEPT", + "PLAN", + "WHILE", + "EXEC", + "PRECISION", + "WITH", + "EXECUTE", + "PRIMARY", + "WITHIN GROUP", + "EXISTS", + "PRINT", + "WRITETEXT", + "EXIT", + "PROC", + }; + + private static readonly string[] ReservedTopLevelWords + = + { + "ADD", + "ALTER COLUMN", + "ALTER TABLE", + "CASE", + "DELETE FROM", + "END", + "EXCEPT", + "FROM", + "GROUP BY", + "HAVING", + "INSERT INTO", + "INSERT", + "LIMIT", + "ORDER BY", + "SELECT", + "SET CURRENT SCHEMA", + "SET SCHEMA", + "SET", + "UPDATE", + "VALUES", + "WHERE", + }; + + private static readonly string[] ReservedTopLevelWordsNoIndent + = + { + "INTERSECT", + "INTERSECT ALL", + "MINUS", + "UNION", + "UNION ALL" + }; + + private static readonly string[] ReservedNewlineWords + = + { + "AND", + "ELSE", + "OR", + "WHEN", + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN", + "CROSS JOIN", + "NATURAL JOIN", + }; + + protected override Tokenizer GetTokenizer() + { + return + new Tokenizer( + ReservedWords, + ReservedTopLevelWords, + ReservedNewlineWords, + ReservedTopLevelWordsNoIndent, + stringTypes: new[] { "\"\"", "N''", "''", "[]" }, + openParens: new[] { "(", "CASE" }, + closeParens: new[] { ")", "END" }, + indexedPlaceholderTypes: Array.Empty(), + namedPlaceholderTypes: new[] { "@" }, + lineCommentTypes: new[] { "--" }, + specialWordChars: new[] { "#", "@" }, + operators: new[] + { + ">=", + "<=", + "<>", + "!=", + "!<", + "!>", + "+=", + "-=", + "*=", + "/=", + "%=", + "|=", + "&=", + "^=", + "::" + }); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterHelper.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterHelper.cs new file mode 100644 index 0000000000..7571505659 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterHelper.cs @@ -0,0 +1,36 @@ +#nullable enable + +using System; +using DevToys.Helpers.SqlFormatter.Core; +using DevToys.Helpers.SqlFormatter.Languages; + +namespace DevToys.Helpers.SqlFormatter +{ + internal static class SqlFormatterHelper + { + internal static string Format(string sql, SqlLanguage language, SqlFormatterOptions options) + { + if (string.IsNullOrEmpty(sql)) + { + return string.Empty; + } + + Formatter formatter = language switch + { + SqlLanguage.Sql => new StandardSqlFormatter(), + SqlLanguage.Tsql => new TSqlFormatter(), + SqlLanguage.Spark => new SparkSqlFormatter(), + SqlLanguage.RedShift => new RedshiftFormatter(), + SqlLanguage.PostgreSql => new PostgreSqlFormatter(), + SqlLanguage.PlSql => new PlSqlFormatter(), + SqlLanguage.N1ql => new N1qlFormatter(), + SqlLanguage.MySql => new MySqlFormatter(), + SqlLanguage.MariaDb => new MariaDbFormatter(), + SqlLanguage.Db2 => new Db2Formatter(), + _ => throw new NotSupportedException(), + }; + + return formatter.Format(sql, options); + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterOptions.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterOptions.cs new file mode 100644 index 0000000000..c7778ab267 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlFormatterOptions.cs @@ -0,0 +1,29 @@ +#nullable enable + +using System.Collections.Generic; + +namespace DevToys.Helpers.SqlFormatter +{ + internal struct SqlFormatterOptions + { + public int IndentationSize { get; } + + public bool Uppercase { get; } + + public int LinesBetweenQueries { get; } + + public IReadOnlyDictionary? PlaceholderParameters { get; } + + public SqlFormatterOptions( + int indentationSize, + bool uppercase, + int linesBetweenQueries = 1, + IReadOnlyDictionary? placeholderParameters = null) + { + IndentationSize = indentationSize; + Uppercase = uppercase; + LinesBetweenQueries = linesBetweenQueries; + PlaceholderParameters = placeholderParameters; + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlLanguage.cs b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlLanguage.cs new file mode 100644 index 0000000000..5ab8bcf887 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/SqlFormatter/SqlLanguage.cs @@ -0,0 +1,18 @@ +#nullable enable + +namespace DevToys.Helpers.SqlFormatter +{ + public enum SqlLanguage + { + Sql, + Tsql, + Spark, + RedShift, + PostgreSql, + PlSql, + N1ql, + MySql, + MariaDb, + Db2 + } +} diff --git a/src/dev/impl/DevToys/LanguageManager.cs b/src/dev/impl/DevToys/LanguageManager.cs index 917607a162..fdc78bc0c0 100644 --- a/src/dev/impl/DevToys/LanguageManager.cs +++ b/src/dev/impl/DevToys/LanguageManager.cs @@ -45,6 +45,7 @@ public partial class LanguageManager : ObservableObject private readonly RegExStrings _regex = new RegExStrings(); private readonly SearchResultStrings _searchresult = new SearchResultStrings(); private readonly SettingsStrings _settings = new SettingsStrings(); + private readonly SqlFormatterStrings _sqlformatter = new SqlFormatterStrings(); private readonly StringUtilitiesStrings _stringutilities = new StringUtilitiesStrings(); private readonly TextDiffStrings _textdiff = new TextDiffStrings(); private readonly ToolGroupsStrings _toolgroups = new ToolGroupsStrings(); @@ -165,6 +166,11 @@ public partial class LanguageManager : ObservableObject /// public SettingsStrings Settings => _settings; + /// + /// Gets the . + /// + public SqlFormatterStrings SqlFormatter => _sqlformatter; + /// /// Gets the . /// @@ -2063,6 +2069,121 @@ public string GetFormattedVersion(string? param0) public string SearchDisplayName => _resources.GetString("SearchDisplayName"); } + public class SqlFormatterStrings : ObservableObject + { + private readonly ResourceLoader _resources = ResourceLoader.GetForViewIndependentUse("SqlFormatter"); + + /// + /// Gets the resource AccessibleName. + /// + public string AccessibleName => _resources.GetString("AccessibleName"); + + /// + /// Gets the resource Configuration. + /// + public string Configuration => _resources.GetString("Configuration"); + + /// + /// Gets the resource MenuDisplayName. + /// + public string MenuDisplayName => _resources.GetString("MenuDisplayName"); + + /// + /// Gets the resource FourSpaces. + /// + public string FourSpaces => _resources.GetString("FourSpaces"); + + /// + /// Gets the resource Indentation. + /// + public string Indentation => _resources.GetString("Indentation"); + + /// + /// Gets the resource Input. + /// + public string Input => _resources.GetString("Input"); + + /// + /// Gets the resource OneTab. + /// + public string OneTab => _resources.GetString("OneTab"); + + /// + /// Gets the resource Output. + /// + public string Output => _resources.GetString("Output"); + + /// + /// Gets the resource TwoSpaces. + /// + public string TwoSpaces => _resources.GetString("TwoSpaces"); + + /// + /// Gets the resource Description. + /// + public string Description => _resources.GetString("Description"); + + /// + /// Gets the resource SearchDisplayName. + /// + public string SearchDisplayName => _resources.GetString("SearchDisplayName"); + + /// + /// Gets the resource SqlLanguage. + /// + public string SqlLanguage => _resources.GetString("SqlLanguage"); + + /// + /// Gets the resource SqlLanguageDb2. + /// + public string SqlLanguageDb2 => _resources.GetString("SqlLanguageDb2"); + + /// + /// Gets the resource SqlLanguageMariaDb. + /// + public string SqlLanguageMariaDb => _resources.GetString("SqlLanguageMariaDb"); + + /// + /// Gets the resource SqlLanguageMySql. + /// + public string SqlLanguageMySql => _resources.GetString("SqlLanguageMySql"); + + /// + /// Gets the resource SqlLanguageN1ql. + /// + public string SqlLanguageN1ql => _resources.GetString("SqlLanguageN1ql"); + + /// + /// Gets the resource SqlLanguagePlSql. + /// + public string SqlLanguagePlSql => _resources.GetString("SqlLanguagePlSql"); + + /// + /// Gets the resource SqlLanguagePostgreSql. + /// + public string SqlLanguagePostgreSql => _resources.GetString("SqlLanguagePostgreSql"); + + /// + /// Gets the resource SqlLanguageRedShift. + /// + public string SqlLanguageRedShift => _resources.GetString("SqlLanguageRedShift"); + + /// + /// Gets the resource SqlLanguageSpark. + /// + public string SqlLanguageSpark => _resources.GetString("SqlLanguageSpark"); + + /// + /// Gets the resource SqlLanguageSql. + /// + public string SqlLanguageSql => _resources.GetString("SqlLanguageSql"); + + /// + /// Gets the resource SqlLanguageTsql. + /// + public string SqlLanguageTsql => _resources.GetString("SqlLanguageTsql"); + } + public class StringUtilitiesStrings : ObservableObject { private readonly ResourceLoader _resources = ResourceLoader.GetForViewIndependentUse("StringUtilities"); diff --git a/src/dev/impl/DevToys/Models/SqlLanguageDisplayPair.cs b/src/dev/impl/DevToys/Models/SqlLanguageDisplayPair.cs new file mode 100644 index 0000000000..e8bf386130 --- /dev/null +++ b/src/dev/impl/DevToys/Models/SqlLanguageDisplayPair.cs @@ -0,0 +1,45 @@ +using System; +using DevToys.Helpers.SqlFormatter; + +namespace DevToys.Models +{ + public class SqlLanguageDisplayPair : IEquatable + { + private static SqlFormatterStrings Strings => LanguageManager.Instance.SqlFormatter; + + public static readonly SqlLanguageDisplayPair Db2 = new(Strings.SqlLanguageDb2, SqlLanguage.Db2); + + public static readonly SqlLanguageDisplayPair MariaDb = new(Strings.SqlLanguageMariaDb, SqlLanguage.MariaDb); + + public static readonly SqlLanguageDisplayPair MySql = new(Strings.SqlLanguageMySql, SqlLanguage.MySql); + + public static readonly SqlLanguageDisplayPair N1ql = new(Strings.SqlLanguageN1ql, SqlLanguage.N1ql); + + public static readonly SqlLanguageDisplayPair PlSql = new(Strings.SqlLanguagePlSql, SqlLanguage.PlSql); + + public static readonly SqlLanguageDisplayPair PostgreSql = new(Strings.SqlLanguagePostgreSql, SqlLanguage.PostgreSql); + + public static readonly SqlLanguageDisplayPair RedShift = new(Strings.SqlLanguageRedShift, SqlLanguage.RedShift); + + public static readonly SqlLanguageDisplayPair Spark = new(Strings.SqlLanguageSpark, SqlLanguage.Spark); + + public static readonly SqlLanguageDisplayPair Sql = new(Strings.SqlLanguageSql, SqlLanguage.Sql); + + public static readonly SqlLanguageDisplayPair Tsql = new(Strings.SqlLanguageTsql, SqlLanguage.Tsql); + + public string DisplayName { get; } + + public SqlLanguage Value { get; } + + private SqlLanguageDisplayPair(string displayName, SqlLanguage value) + { + DisplayName = displayName; + Value = value; + } + + public bool Equals(SqlLanguageDisplayPair other) + { + return other.Value == Value; + } + } +} diff --git a/src/dev/impl/DevToys/Strings/cs-CZ/JsonFormatter.resw b/src/dev/impl/DevToys/Strings/cs-CZ/JsonFormatter.resw index 75d70bbed8..f73e39cce1 100644 --- a/src/dev/impl/DevToys/Strings/cs-CZ/JsonFormatter.resw +++ b/src/dev/impl/DevToys/Strings/cs-CZ/JsonFormatter.resw @@ -148,9 +148,9 @@ Odsazení nebo zmenšení dat Json - Json + JSON - Formátování Json + Formátování JSON \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/cs-CZ/JsonYaml.resw b/src/dev/impl/DevToys/Strings/cs-CZ/JsonYaml.resw index a748d58509..6d33684805 100644 --- a/src/dev/impl/DevToys/Strings/cs-CZ/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/cs-CZ/JsonYaml.resw @@ -142,7 +142,7 @@ Vložený YAML je neplatný. - Json na Yaml + JSON na YAML Výstup @@ -151,15 +151,15 @@ 2 místa - Yaml na Json + YAML na JSON - Převod Json na Yaml a obráceně + Převod JSON na YAML a obráceně - Json <> Yaml + JSON <> YAML - Převodník Json <> Yaml + PřevodníkJSON <> YAML \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/cs-CZ/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/cs-CZ/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/cs-CZ/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/de-DE/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/de-DE/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/de-DE/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/en-US/JsonFormatter.resw b/src/dev/impl/DevToys/Strings/en-US/JsonFormatter.resw index 26e3f21920..05460107f1 100644 --- a/src/dev/impl/DevToys/Strings/en-US/JsonFormatter.resw +++ b/src/dev/impl/DevToys/Strings/en-US/JsonFormatter.resw @@ -124,7 +124,7 @@ Configuration - Json + JSON 4 spaces @@ -148,9 +148,9 @@ 2 spaces - Indent or minify Json data + Indent or minify JSON data - Json Formatter + JSON Formatter \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/en-US/JsonYaml.resw b/src/dev/impl/DevToys/Strings/en-US/JsonYaml.resw index 5b6c72786c..93d7321129 100644 --- a/src/dev/impl/DevToys/Strings/en-US/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/en-US/JsonYaml.resw @@ -130,7 +130,7 @@ Conversion - Json <> Yaml + JSON <> YAML 4 spaces @@ -145,7 +145,7 @@ The entered YAML is invalid. - Json to Yaml + JSON to YAML Output @@ -154,12 +154,12 @@ 2 spaces - Yaml to Json + YAML to JSON - Convert Json data to Yaml and vice versa + Convert JSON data to YAML and vice versa - Json <> Yaml Converter + JSON <> YAML Converter \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/en-US/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/en-US/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/en-US/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/es-AR/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/es-AR/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/es-AR/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/es-ES/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/es-ES/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/es-ES/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/fr-FR/JsonFormatter.resw b/src/dev/impl/DevToys/Strings/fr-FR/JsonFormatter.resw index 521806bc69..d1106ac773 100644 --- a/src/dev/impl/DevToys/Strings/fr-FR/JsonFormatter.resw +++ b/src/dev/impl/DevToys/Strings/fr-FR/JsonFormatter.resw @@ -145,12 +145,12 @@ 2 espaces - Indente ou minifie du Json + Indente ou minifie du JSON - Json + JSON - Formatteur de Json + Formatteur de JSON \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/fr-FR/JsonYaml.resw b/src/dev/impl/DevToys/Strings/fr-FR/JsonYaml.resw index 03f65457a3..12b6a3cc88 100644 --- a/src/dev/impl/DevToys/Strings/fr-FR/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/fr-FR/JsonYaml.resw @@ -142,7 +142,7 @@ Le YAML saisie est invalide. - Json vers Yaml + JSON vers YAML Sortie @@ -151,15 +151,15 @@ 2 espaces - Yaml vers Json + YAML vers JSON - Convertir les données Json en Yaml et vice versa + Convertir les données JSON en YAML et vice versa - Json <> Yaml + JSON <> YAML - Convertisseur Json <> Yaml + Convertisseur JSON <> YAML \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/fr-FR/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/fr-FR/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/fr-FR/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/hu-HU/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/hu-HU/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/hu-HU/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/id-ID/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/id-ID/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/id-ID/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/ja-JP/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/ja-JP/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/ja-JP/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/pl-PL/JsonYaml.resw b/src/dev/impl/DevToys/Strings/pl-PL/JsonYaml.resw index 070fed5f3d..dab8430d27 100644 --- a/src/dev/impl/DevToys/Strings/pl-PL/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/pl-PL/JsonYaml.resw @@ -157,7 +157,7 @@ Konwertuj dane JSON do YAML oraz vice-versa - JSON <> Yaml + JSON <> YAML Konwerter JSON <> YAML Converter diff --git a/src/dev/impl/DevToys/Strings/pl-PL/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/pl-PL/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/pl-PL/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/pt-PT/JsonFormatter.resw b/src/dev/impl/DevToys/Strings/pt-PT/JsonFormatter.resw index 032d49d5dc..3fe1c738f5 100644 --- a/src/dev/impl/DevToys/Strings/pt-PT/JsonFormatter.resw +++ b/src/dev/impl/DevToys/Strings/pt-PT/JsonFormatter.resw @@ -124,7 +124,7 @@ Configuração - Json + JSON 4 espaços @@ -148,9 +148,9 @@ 2 espaços - Indentar ou minificar dados Json + Indentar ou minificar dados JSON - Formatador Json + Formatador JSON \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/pt-PT/JsonYaml.resw b/src/dev/impl/DevToys/Strings/pt-PT/JsonYaml.resw index cc13e8b9d1..b4934864f1 100644 --- a/src/dev/impl/DevToys/Strings/pt-PT/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/pt-PT/JsonYaml.resw @@ -130,7 +130,7 @@ Conversão - Json <> Yaml + JSON <> YAML 4 espaços @@ -145,7 +145,7 @@ O YAML introduzido é inválido. - Json para Yaml + JSON para YAML Saída @@ -154,12 +154,12 @@ 2 espaços - Yaml para Json + YAML para JSON - Converter os dados da Json em Yaml e vice-versa + Converter os dados da JSON em YAML e vice-versa - Conversor Json <> Yaml + Conversor JSON <> YAML \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/pt-PT/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/pt-PT/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/pt-PT/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/ru-RU/JsonFormatter.resw b/src/dev/impl/DevToys/Strings/ru-RU/JsonFormatter.resw index ada771364e..5b5e4110c4 100644 --- a/src/dev/impl/DevToys/Strings/ru-RU/JsonFormatter.resw +++ b/src/dev/impl/DevToys/Strings/ru-RU/JsonFormatter.resw @@ -124,7 +124,7 @@ Установки - Json + JSON 4 пробела @@ -148,9 +148,9 @@ 2 пробела - Переопределение отступов или минимизация данных Json + Переопределение отступов или минимизация данных JSON - Форматирование Json + Форматирование JSON \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/ru-RU/JsonYaml.resw b/src/dev/impl/DevToys/Strings/ru-RU/JsonYaml.resw index 12726c612d..dbd33cb9ef 100644 --- a/src/dev/impl/DevToys/Strings/ru-RU/JsonYaml.resw +++ b/src/dev/impl/DevToys/Strings/ru-RU/JsonYaml.resw @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Инструмент конвертирования Json <> Yaml + Инструмент конвертирования JSON <> YAML Установки @@ -130,7 +130,7 @@ Конвертирование - Json <> Yaml + JSON <> YAML 4 пробела @@ -145,7 +145,7 @@ Введенный Yaml недопустим. - Json в Yaml + JSON в YAML Вывод @@ -154,12 +154,12 @@ 2 пробела - Yaml в Json + YAML в JSON - Конвертирование данных Json в Yaml и наоборот + Конвертирование данных JSON в YAML и наоборот - Конвертирование Json <> Yaml + Конвертирование JSON <> YAML \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/ru-RU/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/ru-RU/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/ru-RU/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/zh-Hans/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/zh-Hans/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/zh-Hans/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/Strings/zh-Hant/SqlFormatter.resw b/src/dev/impl/DevToys/Strings/zh-Hant/SqlFormatter.resw new file mode 100644 index 0000000000..b0b765e99c --- /dev/null +++ b/src/dev/impl/DevToys/Strings/zh-Hant/SqlFormatter.resw @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + SQL Formatter tool + + + Configuration + + + SQL + + + 4 spaces + + + Indentation + + + Input + + + 1 tab + + + Output + + + 2 spaces + + + Indent SQL queries + + + SQL Formatter + + + Language + + + Db2 + + + MariaDB + + + MySQL + + + N1QL + + + PL/SQL + + + PostgreSQL + + + Amazon Redshift + + + Spark SQL + + + Standard SQL + + + Transact-SQL + + \ No newline at end of file diff --git a/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolProvider.cs b/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolProvider.cs new file mode 100644 index 0000000000..3859fc082f --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolProvider.cs @@ -0,0 +1,47 @@ +#nullable enable + +using System.Composition; +using DevToys.Api.Tools; +using DevToys.Core.Threading; +using DevToys.Shared.Api.Core; +using Windows.UI.Xaml.Controls; + +namespace DevToys.ViewModels.Tools.SqlFormatter +{ + [Export(typeof(IToolProvider))] + [Name("SQL Formatter")] + [Parent(FormattersGroupToolProvider.InternalName)] + [ProtocolName("sqlformat")] + [Order(0)] + [NotScrollable] + internal sealed class SqlFormatterToolProvider : ToolProviderBase, IToolProvider + { + private readonly IMefProvider _mefProvider; + + public string MenuDisplayName => LanguageManager.Instance.SqlFormatter.MenuDisplayName; + + public string? SearchDisplayName => LanguageManager.Instance.SqlFormatter.SearchDisplayName; + + public string? Description => LanguageManager.Instance.SqlFormatter.Description; + + public string AccessibleName => LanguageManager.Instance.SqlFormatter.AccessibleName; + + public TaskCompletionNotifier IconSource => CreateSvgIcon("SqlFormatter.svg"); + + [ImportingConstructor] + public SqlFormatterToolProvider(IMefProvider mefProvider) + { + _mefProvider = mefProvider; + } + + public bool CanBeTreatedByTool(string data) + { + return false; + } + + public IToolViewModel CreateTool() + { + return _mefProvider.Import(); + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolViewModel.cs new file mode 100644 index 0000000000..82e032b050 --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/Formatters/SqlFormatter/SqlFormatterToolViewModel.cs @@ -0,0 +1,203 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using DevToys.Api.Core; +using DevToys.Api.Core.Settings; +using DevToys.Api.Tools; +using DevToys.Core.Threading; +using DevToys.Helpers.SqlFormatter; +using DevToys.Models; +using DevToys.Shared.Core.Threading; +using DevToys.Views.Tools.SqlFormatter; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace DevToys.ViewModels.Tools.SqlFormatter +{ + [Export(typeof(SqlFormatterToolViewModel))] + public sealed class SqlFormatterToolViewModel : ObservableRecipient, IToolViewModel + { + /// + /// The indentation to apply while formatting. + /// + private static readonly SettingDefinition Indentation + = new( + name: $"{nameof(SqlFormatterToolViewModel)}.{nameof(Indentation)}", + isRoaming: true, + defaultValue: Models.Indentation.TwoSpaces); + + /// + /// The SQL language to consider when formatting. + /// + private static readonly SettingDefinition SqlLanguage + = new( + name: $"{nameof(SqlFormatterToolViewModel)}.{nameof(SqlLanguage)}", + isRoaming: true, + defaultValue: Helpers.SqlFormatter.SqlLanguage.Sql); + + private readonly IMarketingService _marketingService; + private readonly Queue _formattingQueue = new(); + + private bool _toolSuccessfullyWorked; + private bool _formattingInProgress; + private string? _inputValue; + private string? _outputValue; + + public Type View { get; } = typeof(SqlFormatterToolPage); + + internal SqlFormatterStrings Strings => LanguageManager.Instance.SqlFormatter; + + /// + /// Gets or sets the desired indentation. + /// + internal IndentationDisplayPair IndentationMode + { + get + { + Indentation settingsValue = SettingsProvider.GetSetting(Indentation); + IndentationDisplayPair? indentation = Indentations.FirstOrDefault(x => x.Value == settingsValue); + return indentation ?? IndentationDisplayPair.TwoSpaces; + } + set + { + if (IndentationMode != value) + { + SettingsProvider.SetSetting(Indentation, value.Value); + OnPropertyChanged(); + QueueFormatting(); + } + } + } + + /// + /// Gets or sets the desired SQL language to use. + /// + internal SqlLanguageDisplayPair SqlLanguageMode + { + get + { + SqlLanguage settingsValue = SettingsProvider.GetSetting(SqlLanguage); + SqlLanguageDisplayPair? indentation = SqlLanguages.FirstOrDefault(x => x.Value == settingsValue); + return indentation ?? SqlLanguageDisplayPair.Sql; + } + set + { + if (SqlLanguageMode != value) + { + SettingsProvider.SetSetting(SqlLanguage, value.Value); + OnPropertyChanged(); + QueueFormatting(); + } + } + } + + /// + /// Get a list of supported Indentation + /// + internal IReadOnlyList Indentations = new ObservableCollection { + Models.IndentationDisplayPair.TwoSpaces, + Models.IndentationDisplayPair.FourSpaces + }; + + /// + /// Get a list of supported SQL Languages + /// + internal IReadOnlyList SqlLanguages = new ObservableCollection { + Models.SqlLanguageDisplayPair.Db2, + Models.SqlLanguageDisplayPair.MariaDb, + Models.SqlLanguageDisplayPair.MySql, + Models.SqlLanguageDisplayPair.N1ql, + Models.SqlLanguageDisplayPair.PlSql, + Models.SqlLanguageDisplayPair.PostgreSql, + Models.SqlLanguageDisplayPair.RedShift, + Models.SqlLanguageDisplayPair.Spark, + Models.SqlLanguageDisplayPair.Sql, + Models.SqlLanguageDisplayPair.Tsql + }; + + /// + /// Gets or sets the input text. + /// + internal string? InputValue + { + get => _inputValue; + set + { + SetProperty(ref _inputValue, value); + QueueFormatting(); + } + } + + internal string? OutputValue + { + get => _outputValue; + set => SetProperty(ref _outputValue, value); + } + + internal ISettingsProvider SettingsProvider { get; } + + [ImportingConstructor] + public SqlFormatterToolViewModel(ISettingsProvider settingsProvider, IMarketingService marketingService) + { + SettingsProvider = settingsProvider; + _marketingService = marketingService; + } + + private void QueueFormatting() + { + _formattingQueue.Enqueue(InputValue ?? string.Empty); + TreatQueueAsync().Forget(); + } + + private async Task TreatQueueAsync() + { + if (_formattingInProgress) + { + return; + } + + _formattingInProgress = true; + + await TaskScheduler.Default; + + while (_formattingQueue.TryDequeue(out string? text)) + { + int indentationSize = IndentationMode.Value switch + { + Models.Indentation.TwoSpaces => 2, + Models.Indentation.FourSpaces => 4, + _ => throw new NotSupportedException(), + }; + + string? result + = SqlFormatterHelper.Format( + text, + SqlLanguageMode.Value, + new SqlFormatterOptions( + indentationSize, + uppercase: true, + linesBetweenQueries: 2)); + + if (result != null) + { + ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => + { + OutputValue = result; + + if (!_toolSuccessfullyWorked) + { + _toolSuccessfullyWorked = true; + _marketingService.NotifyToolSuccessfullyWorked(); + } + }).ForgetSafely(); + } + } + + _formattingInProgress = false; + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/Graphic/ColorBlindnessSimulator/ColorBlindnessSimulatorToolViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/Graphic/ColorBlindnessSimulator/ColorBlindnessSimulatorToolViewModel.cs index e562628490..0cde068371 100644 --- a/src/dev/impl/DevToys/ViewModels/Tools/Graphic/ColorBlindnessSimulator/ColorBlindnessSimulatorToolViewModel.cs +++ b/src/dev/impl/DevToys/ViewModels/Tools/Graphic/ColorBlindnessSimulator/ColorBlindnessSimulatorToolViewModel.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using DevToys.Api.Core; using DevToys.Api.Tools; using DevToys.Core; using DevToys.Core.Threading; @@ -32,6 +33,7 @@ public sealed class ColorBlindnessSimulatorToolViewModel : ObservableRecipient, private readonly object _lockObject = new(); private readonly List _tempFileNames = new(); + private readonly IMarketingService _marketingService; private CancellationTokenSource? _cancellationTokenSource; private DateTime _timeSinceLastprogressUpdate; @@ -107,8 +109,10 @@ internal Uri? DeuteranopiaOutput } [ImportingConstructor] - public ColorBlindnessSimulatorToolViewModel() + public ColorBlindnessSimulatorToolViewModel(IMarketingService marketingService) { + _marketingService = marketingService; + SelectFilesAreaDragOverCommand = new RelayCommand(ExecuteSelectFilesAreaDragOverCommand); SelectFilesAreaDragLeaveCommand = new RelayCommand(ExecuteSelectFilesAreaDragLeaveCommand); SelectFilesAreaDragDropCommand = new AsyncRelayCommand(ExecuteSelectFilesAreaDragDropCommandAsync); @@ -390,6 +394,8 @@ await ThreadHelper.RunOnUIThreadAsync(() => IsProgressGridVisible = false; IsResultGridVisible = true; + + _marketingService.NotifyToolSuccessfullyWorked(); }); } diff --git a/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml b/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml new file mode 100644 index 0000000000..d8f78b6bd5 --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml.cs b/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml.cs new file mode 100644 index 0000000000..1819cef516 --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/Formatters/SqlFormatter/SqlFormatterToolPage.xaml.cs @@ -0,0 +1,55 @@ +#nullable enable + +using DevToys.Api.Core.Navigation; +using DevToys.Shared.Core; +using DevToys.ViewModels.Tools.SqlFormatter; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace DevToys.Views.Tools.SqlFormatter +{ + public sealed partial class SqlFormatterToolPage : Page + { + public static readonly DependencyProperty ViewModelProperty + = DependencyProperty.Register( + nameof(ViewModel), + typeof(SqlFormatterToolViewModel), + typeof(SqlFormatterToolPage), + new PropertyMetadata(null)); + + /// + /// Gets the page's view model. + /// + public SqlFormatterToolViewModel ViewModel + { + get => (SqlFormatterToolViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + public SqlFormatterToolPage() + { + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + var parameters = (NavigationParameter)e.Parameter; + + if (ViewModel is null) + { + // Set the view model + Assumes.NotNull(parameters.ViewModel, nameof(parameters.ViewModel)); + ViewModel = (SqlFormatterToolViewModel)parameters.ViewModel!; + DataContext = ViewModel; + } + + if (!string.IsNullOrWhiteSpace(parameters.ClipBoardContent)) + { + ViewModel.InputValue = parameters.ClipBoardContent; + } + + base.OnNavigatedTo(e); + } + } +} diff --git a/src/tests/DevToys.Tests/DevToys.Tests.csproj b/src/tests/DevToys.Tests/DevToys.Tests.csproj index 43a2a1ab3e..d604387478 100644 --- a/src/tests/DevToys.Tests/DevToys.Tests.csproj +++ b/src/tests/DevToys.Tests/DevToys.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/src/tests/DevToys.Tests/Providers/Tools/SqlFormatterTests.cs b/src/tests/DevToys.Tests/Providers/Tools/SqlFormatterTests.cs new file mode 100644 index 0000000000..0488a8baa1 --- /dev/null +++ b/src/tests/DevToys.Tests/Providers/Tools/SqlFormatterTests.cs @@ -0,0 +1,1653 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using DevToys.Helpers.SqlFormatter; +using DevToys.Helpers.SqlFormatter.Core; +using DevToys.Helpers.SqlFormatter.Languages; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DevToys.Tests.Providers.Tools +{ + [TestClass] + public class SqlFormatterTests : MefBaseTest + { + [TestMethod] + public void StandardSqlFormatterTest() + { + var formatter = new StandardSqlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsJoin(formatter); + + // formats FETCH FIRST like LIMIT + string input = "SELECT * FETCH FIRST 2 ROWS ONLY;"; + string expectedResult = +@"SELECT + * +FETCH FIRST + 2 ROWS ONLY;"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void TSqlFormatterTest() + { + var formatter = new TSqlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''", "N''", "[]"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators(formatter, "%", "&", "|", "^", "~", "!=", "!<", "!>", "+=", "-=", "*=", "/=", "%=", "|=", "&=", "^=", "::"); + SupportsJoin(formatter, without: new[] { "NATURAL" }); + + // formats INSERT without INTO + string input = "INSERT Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');"; + string expectedResult = +@"INSERT + Customers (ID, MoneyBalance, Address, City) +VALUES + (12, -123.4, 'Skagen 2111', 'Stv');"; + AssertFormat(formatter, input, expectedResult); + + // recognizes @variables + input = "SELECT @variable, @\"var name\", @[var name];"; + expectedResult = "SELECT\r\n @variable,\r\n @\"var name\",\r\n @[var name];"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with CROSS JOIN + input = "SELECT a, b FROM t CROSS JOIN t2 on t.id = t2.id_t"; + expectedResult = +@"SELECT + a, + b +FROM + t + CROSS JOIN t2 on t.id = t2.id_t"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void SparkSqlFormatterTest() + { + var formatter = new SparkSqlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators(formatter, "!=", "%", "|", "&", "^", "~", "!", "<=>", "%", "&&", "||", "=="); + SupportsJoin( + formatter, + additionally: new[] + { + "ANTI JOIN", + "SEMI JOIN", + "LEFT ANTI JOIN", + "LEFT SEMI JOIN", + "RIGHT OUTER JOIN", + "RIGHT SEMI JOIN", + "NATURAL ANTI JOIN", + "NATURAL FULL OUTER JOIN", + "NATURAL INNER JOIN", + "NATURAL LEFT ANTI JOIN", + "NATURAL LEFT OUTER JOIN", + "NATURAL LEFT SEMI JOIN", + "NATURAL OUTER JOIN", + "NATURAL RIGHT OUTER JOIN", + "NATURAL RIGHT SEMI JOIN", + "NATURAL SEMI JOIN" + }); + + // formats WINDOW specification as top level + string input = "SELECT *, LAG(value) OVER wnd AS next_value FROM tbl WINDOW wnd as (PARTITION BY id ORDER BY time);"; + string expectedResult = +@"SELECT + *, + LAG(value) OVER wnd AS next_value +FROM + tbl +WINDOW + wnd as ( + PARTITION BY + id + ORDER BY + time + );"; + AssertFormat(formatter, input, expectedResult); + + // formats window function and end as inline + input = "SELECT window(time, \"1 hour\").start AS window_start, window(time, \"1 hour\").end AS window_end FROM tbl;"; + expectedResult = "SELECT\r\n window(time, \"1 hour\").start AS window_start,\r\n window(time, \"1 hour\").end AS window_end\r\nFROM\r\n tbl;"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void RedshiftFormatterTest() + { + var formatter = new RedshiftFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsAlterTableModify(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsSchema(formatter); + SupportsOperators(formatter, "%", "^", "|/", "||/", "<<", ">>", "&", "|", "~", "!", "!=", "||"); + SupportsJoin(formatter); + + // formats LIMIT + string input = "SELECT col1 FROM tbl ORDER BY col2 DESC LIMIT 10;"; + string expectedResult = +@"SELECT + col1 +FROM + tbl +ORDER BY + col2 DESC +LIMIT + 10;"; + AssertFormat(formatter, input, expectedResult); + + // formats only -- as a line comment + input = +@"SELECT col FROM +-- This is a comment +MyTable;"; + expectedResult = +@"SELECT + col +FROM + -- This is a comment + MyTable;"; + AssertFormat(formatter, input, expectedResult); + + // recognizes @ as part of identifiers + input = @"SELECT @col1 FROM tbl"; + expectedResult = +@"SELECT + @col1 +FROM + tbl"; + AssertFormat(formatter, input, expectedResult); + + // formats DISTKEY and SORTKEY after CREATE TABLE + input = @"CREATE TABLE items (a INT PRIMARY KEY, b TEXT, c INT NOT NULL, d INT NOT NULL) DISTKEY(created_at) SORTKEY(created_at);"; + expectedResult = +@"CREATE TABLE items (a INT PRIMARY KEY, b TEXT, c INT NOT NULL, d INT NOT NULL) +DISTKEY +(created_at) +SORTKEY +(created_at);"; + AssertFormat(formatter, input, expectedResult); + + // formats COPY + input = +@"COPY schema.table +FROM 's3://bucket/file.csv' +IAM_ROLE 'arn:aws:iam::123456789:role/rolename' +FORMAT AS CSV DELIMITER ',' QUOTE '' +REGION AS 'us-east-1'"; + expectedResult = +@"COPY + schema.table +FROM + 's3://bucket/file.csv' +IAM_ROLE + 'arn:aws:iam::123456789:role/rolename' +FORMAT + AS CSV +DELIMITER + ',' QUOTE '' +REGION + AS 'us-east-1'"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void PostgreSqlFormatterTest() + { + var formatter = new PostgreSqlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''", "U&\"\"", "U&''", "$$"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators( + formatter, + "%", + "^", + "!", + "!!", + "@", + "!=", + "&", + "|", + "~", + "#", + "<<", + ">>", + "||/", + "|/", + "::", + "->>", + "->", + "~~*", + "~~", + "!~~*", + "!~~", + "~*", + "!~*", + "!~"); + SupportsJoin(formatter); + + // supports $n placeholders + string input = "SELECT $1, $2 FROM tbl"; + string expectedResult = +@"SELECT + $1, + $2 +FROM + tbl"; + AssertFormat(formatter, input, expectedResult); + + // supports :name placeholders + input = "foo = :bar"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void PlSqlFormatterTest() + { + var formatter = new PlSqlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsAlterTableModify(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators(formatter, "||", "**", "!=", ":="); + SupportsJoin(formatter); + + // formats FETCH FIRST like LIMIT + string input = "SELECT col1 FROM tbl ORDER BY col2 DESC FETCH FIRST 20 ROWS ONLY;"; + string expectedResult = +@"SELECT + col1 +FROM + tbl +ORDER BY + col2 DESC +FETCH FIRST + 20 ROWS ONLY;"; + AssertFormat(formatter, input, expectedResult); + + // formats only -- as a line comment + input = "SELECT col FROM\r\n-- This is a comment\r\nMyTable;\r\n"; + expectedResult = +@"SELECT + col +FROM + -- This is a comment + MyTable;"; + AssertFormat(formatter, input, expectedResult); + + // recognizes _, $, #, . and @ as part of identifiers + input = "SELECT my_col$1#, col.2@ FROM tbl\r\n"; + expectedResult = +@"SELECT + my_col$1#, + col.2@ +FROM + tbl"; + AssertFormat(formatter, input, expectedResult); + + // formats INSERT without INTO + input = "INSERT Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');"; + expectedResult = +@"INSERT + Customers (ID, MoneyBalance, Address, City) +VALUES + (12, -123.4, 'Skagen 2111', 'Stv');"; + AssertFormat(formatter, input, expectedResult); + + // recognizes ?[0-9]* placeholders + input = "SELECT ?1, ?25, ?;"; + expectedResult = +@"SELECT + ?1, + ?25, + ?;"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with CROSS APPLY + input = "SELECT a, b FROM t CROSS APPLY fn(t.id)"; + expectedResult = +@"SELECT + a, + b +FROM + t + CROSS APPLY fn(t.id)"; + AssertFormat(formatter, input, expectedResult); + + // formats simple SELECT + input = "SELECT N, M FROM t"; + expectedResult = +@"SELECT + N, + M +FROM + t"; + AssertFormat(formatter, input, expectedResult); + + // formats simple SELECT with national characters + input = "SELECT N'value'"; + expectedResult = +@"SELECT + N'value'"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with OUTER APPLY + input = "SELECT a, b FROM t OUTER APPLY fn(t.id)"; + expectedResult = +@"SELECT + a, + b +FROM + t + OUTER APPLY fn(t.id)"; + AssertFormat(formatter, input, expectedResult); + + // formats Oracle recursive sub queries + input = +@"WITH t1(id, parent_id) AS ( + -- Anchor member. + SELECT + id, + parent_id + FROM + tab1 + WHERE + parent_id IS NULL + MINUS + -- Recursive member. + SELECT + t2.id, + t2.parent_id + FROM + tab1 t2, + t1 + WHERE + t2.parent_id = t1.id +) SEARCH BREADTH FIRST BY id SET order1, +another AS (SELECT * FROM dual) +SELECT id, parent_id FROM t1 ORDER BY order1;"; + expectedResult = +@"WITH t1(id, parent_id) AS ( + -- Anchor member. + SELECT + id, + parent_id + FROM + tab1 + WHERE + parent_id IS NULL + MINUS + -- Recursive member. + SELECT + t2.id, + t2.parent_id + FROM + tab1 t2, + t1 + WHERE + t2.parent_id = t1.id +) SEARCH BREADTH FIRST BY id SET order1, +another AS ( + SELECT + * + FROM + dual +) +SELECT + id, + parent_id +FROM + t1 +ORDER BY + order1;"; + AssertFormat(formatter, input, expectedResult); + + // formats Oracle recursive sub queries regardless of capitalization + input = +@"WITH t1(id, parent_id) AS ( + -- Anchor member. + SELECT + id, + parent_id + FROM + tab1 + WHERE + parent_id IS NULL + MINUS + -- Recursive member. + SELECT + t2.id, + t2.parent_id + FROM + tab1 t2, + t1 + WHERE + t2.parent_id = t1.id +) SEARCH BREADTH FIRST by id set order1, +another AS (SELECT * FROM dual) +SELECT id, parent_id FROM t1 ORDER BY order1;"; + expectedResult = +@"WITH t1(id, parent_id) AS ( + -- Anchor member. + SELECT + id, + parent_id + FROM + tab1 + WHERE + parent_id IS NULL + MINUS + -- Recursive member. + SELECT + t2.id, + t2.parent_id + FROM + tab1 t2, + t1 + WHERE + t2.parent_id = t1.id +) SEARCH BREADTH FIRST by id set order1, +another AS ( + SELECT + * + FROM + dual +) +SELECT + id, + parent_id +FROM + t1 +ORDER BY + order1;"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void N1qlFormatterTest() + { + var formatter = new N1qlFormatter(); + BehavesLikeSqlFormatter(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators(formatter, "%", "==", "!="); + SupportsJoin(formatter, without: new[] { "FULL", "CROSS", "NATURAL" }); + + // formats SELECT query with element selection expression + string input = "SELECT order_lines[0].productId FROM orders;"; + string expectedResult = +@"SELECT + order_lines[0].productId +FROM + orders;"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with primary key querying + input = "SELECT fname, email FROM tutorial USE KEYS ['dave', 'ian'];"; + expectedResult = +@"SELECT + fname, + email +FROM + tutorial +USE KEYS + ['dave', 'ian'];"; + AssertFormat(formatter, input, expectedResult); + + // formats INSERT with {} object literal + input = "INSERT INTO heroes (KEY, VALUE) VALUES ('123', {'id':1,'type':'Tarzan'});"; + expectedResult = +@"INSERT INTO + heroes (KEY, VALUE) +VALUES + ('123', {'id': 1, 'type': 'Tarzan'});"; + AssertFormat(formatter, input, expectedResult); + + // formats INSERT with large object and array literals + input = "INSERT INTO heroes (KEY, VALUE) VALUES ('123', {'id': 1, 'type': 'Tarzan', 'array': [123456789, 123456789, 123456789, 123456789, 123456789], 'hello': 'world'});"; + expectedResult = +@"INSERT INTO + heroes (KEY, VALUE) +VALUES + ( + '123', + { + 'id': 1, + 'type': 'Tarzan', + 'array': [ + 123456789, + 123456789, + 123456789, + 123456789, + 123456789 + ], + 'hello': 'world' + } + );"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with UNNEST top level reserver word + input = "SELECT * FROM tutorial UNNEST tutorial.children c;"; + expectedResult = +@"SELECT + * +FROM + tutorial +UNNEST + tutorial.children c;"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with NEST and USE KEYS + input = +@"SELECT * FROM usr +USE KEYS 'Elinor_33313792' NEST orders_with_users orders +ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END;"; + expectedResult = +@"SELECT + * +FROM + usr +USE KEYS + 'Elinor_33313792' +NEST + orders_with_users orders ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END;"; + AssertFormat(formatter, input, expectedResult); + + // formats explained DELETE query with USE KEYS and RETURNING + input = "EXPLAIN DELETE FROM tutorial t USE KEYS 'baldwin' RETURNING t"; + expectedResult = +@"EXPLAIN DELETE FROM + tutorial t +USE KEYS + 'baldwin' RETURNING t"; + AssertFormat(formatter, input, expectedResult); + + // formats UPDATE query with USE KEYS and RETURNING + input = "UPDATE tutorial USE KEYS 'baldwin' SET type = 'actor' RETURNING tutorial.type"; + expectedResult = +@"UPDATE + tutorial +USE KEYS + 'baldwin' +SET + type = 'actor' RETURNING tutorial.type"; + AssertFormat(formatter, input, expectedResult); + + // recognizes $variables + input = "SELECT $variable, $\'var name\', $\"var name\", $`var name`;"; + expectedResult = "SELECT\r\n $variable,\r\n $'var name',\r\n $\"var name\",\r\n $`var name`;"; + AssertFormat(formatter, input, expectedResult); + } + + [TestMethod] + public void MySqlFormatterTest() + { + var formatter = new MySqlFormatter(); + BehavesLikeMariaDbFormatter(formatter); + SupportsOperators(formatter, "->", "->>"); + } + + [TestMethod] + public void MariaDbFormatterTest() + { + var formatter = new MariaDbFormatter(); + BehavesLikeMariaDbFormatter(formatter); + } + + [TestMethod] + public void Db2FormatterTest() + { + var formatter = new Db2Formatter(); + BehavesLikeSqlFormatter(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsBetween(formatter); + SupportsSchema(formatter); + SupportsOperators(formatter, "%", "**", "!=", "!>", "!>", "||"); + + // formats FETCH FIRST like LIMIT + string input = "SELECT col1 FROM tbl ORDER BY col2 DESC FETCH FIRST 20 ROWS ONLY;"; + string expectedResult = +@"SELECT + col1 +FROM + tbl +ORDER BY + col2 DESC +FETCH FIRST + 20 ROWS ONLY;"; + AssertFormat(formatter, input, expectedResult); + + // formats only -- as a line comment + input = +@"SELECT col FROM +-- This is a comment +MyTable;"; + expectedResult = +@"SELECT + col +FROM + -- This is a comment + MyTable;"; + AssertFormat(formatter, input, expectedResult); + + // recognizes @ and # as part of identifiers + input = "SELECT col#1, @col2 FROM tbl"; + expectedResult = +@"SELECT + col#1, + @col2 +FROM + tbl"; + AssertFormat(formatter, input, expectedResult); + + // recognizes :variables + input = "SELECT :variable;"; + expectedResult = +@"SELECT + :variable;"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Core tests for all SQL formatters + /// + private void BehavesLikeSqlFormatter(Formatter formatter) + { + SupportsComments(formatter); + SupportsConfigOptions(formatter); + SupportsOperators(formatter, "=", "+", "-", "*", "/", "<>", ">", "<", ">=", "<="); + + // does nothing with empty input + string input = string.Empty; + string expectedResult = string.Empty; + string output = formatter.Format(input); + Assert.AreEqual(expectedResult, output); + + // formats lonely semicolon + input = ";"; + expectedResult = ";"; + AssertFormat(formatter, input, expectedResult); + + // formats simple SELECT query + input = "SELECT count(*),Column1 FROM Table1;"; + expectedResult = +@"SELECT + count(*), + Column1 +FROM + Table1;"; + AssertFormat(formatter, input, expectedResult); + + // formats complex SELECT + input = "SELECT DISTINCT name, ROUND(age/7) field1, 18 + 20 AS field2, 'some string' FROM foo;"; + expectedResult = +@"SELECT + DISTINCT name, + ROUND(age / 7) field1, + 18 + 20 AS field2, + 'some string' +FROM + foo;"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT with complex WHERE + input = +@"SELECT * FROM foo WHERE Column1 = 'testing' +AND ( (Column2 = Column3 OR Column4 >= NOW()) );"; + expectedResult = +@"SELECT + * +FROM + foo +WHERE + Column1 = 'testing' + AND ( + ( + Column2 = Column3 + OR Column4 >= NOW() + ) + );"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT with top level reserved words + input = +@"SELECT * FROM foo WHERE name = 'John' GROUP BY some_column +HAVING column > 10 ORDER BY other_column LIMIT 5;"; + expectedResult = +@"SELECT + * +FROM + foo +WHERE + name = 'John' +GROUP BY + some_column +HAVING + column > 10 +ORDER BY + other_column +LIMIT + 5;"; + AssertFormat(formatter, input, expectedResult); + + // formats LIMIT with two comma-separated values on single line + input = @"LIMIT 5, 10;"; + expectedResult = +@"LIMIT + 5, 10;"; + AssertFormat(formatter, input, expectedResult); + + // formats LIMIT of single value followed by another SELECT using commas + input = @"LIMIT 5; SELECT foo, bar;"; + expectedResult = +@"LIMIT + 5; +SELECT + foo, + bar;"; + AssertFormat(formatter, input, expectedResult); + + // formats LIMIT of single value and OFFSET + input = @"LIMIT 5 OFFSET 8;"; + expectedResult = +@"LIMIT + 5 OFFSET 8;"; + AssertFormat(formatter, input, expectedResult); + + // recognizes LIMIT in lowercase + input = @"limit 5, 10;"; + expectedResult = +@"limit + 5, 10;"; + AssertFormat(formatter, input, expectedResult); + + // preserves case of keywords + input = @"select distinct * frOM foo WHERe a > 1 and b = 3"; + expectedResult = +@"select + distinct * +frOM + foo +WHERe + a > 1 + and b = 3"; + AssertFormat(formatter, input, expectedResult); + + // formats SELECT query with SELECT query inside it + input = @"SELECT *, SUM(*) AS sum FROM (SELECT * FROM Posts LIMIT 30) WHERE a > b"; + expectedResult = +@"SELECT + *, + SUM(*) AS sum +FROM + ( + SELECT + * + FROM + Posts + LIMIT + 30 + ) +WHERE + a > b"; + AssertFormat(formatter, input, expectedResult); + + // formats simple INSERT query + input = @"INSERT INTO Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');"; + expectedResult = +@"INSERT INTO + Customers (ID, MoneyBalance, Address, City) +VALUES + (12, -123.4, 'Skagen 2111', 'Stv');"; + AssertFormat(formatter, input, expectedResult); + + // formats open paren after comma + input = @"WITH TestIds AS (VALUES (4),(5), (6),(7),(9),(10),(11)) SELECT * FROM TestIds;"; + expectedResult = +@"WITH TestIds AS ( + VALUES + (4), + (5), + (6), + (7), + (9), + (10), + (11) +) +SELECT + * +FROM + TestIds;"; + AssertFormat(formatter, input, expectedResult); + + // keeps short parenthesized list with nested parenthesis on single line + input = @"SELECT (a + b * (c - NOW()));"; + expectedResult = +@"SELECT + (a + b * (c - NOW()));"; + AssertFormat(formatter, input, expectedResult); + + // breaks long parenthesized lists to multiple lines + input = +@"INSERT INTO some_table (id_product, id_shop, id_currency, id_country, id_registration) ( +SELECT IF(dq.id_discounter_shopping = 2, dq.value, dq.value / 100), +IF (dq.id_discounter_shopping = 2, 'amount', 'percentage') FROM foo);"; + expectedResult = +@"INSERT INTO + some_table ( + id_product, + id_shop, + id_currency, + id_country, + id_registration + ) ( + SELECT + IF( + dq.id_discounter_shopping = 2, + dq.value, + dq.value / 100 + ), + IF ( + dq.id_discounter_shopping = 2, + 'amount', + 'percentage' + ) + FROM + foo + );"; + AssertFormat(formatter, input, expectedResult); + + // formats simple UPDATE query + input = @"UPDATE Customers SET ContactName='Alfred Schmidt', City='Hamburg' WHERE CustomerName='Alfreds Futterkiste';"; + expectedResult = +@"UPDATE + Customers +SET + ContactName = 'Alfred Schmidt', + City = 'Hamburg' +WHERE + CustomerName = 'Alfreds Futterkiste';"; + AssertFormat(formatter, input, expectedResult); + + // formats simple DELETE query + input = @"DELETE FROM Customers WHERE CustomerName='Alfred' AND Phone=5002132;"; + expectedResult = +@"DELETE FROM + Customers +WHERE + CustomerName = 'Alfred' + AND Phone = 5002132;"; + AssertFormat(formatter, input, expectedResult); + + // formats simple DROP query + input = @"DROP TABLE IF EXISTS admin_role;"; + expectedResult = @"DROP TABLE IF EXISTS admin_role;"; + AssertFormat(formatter, input, expectedResult); + + // formats incomplete query + input = @"SELECT count("; + expectedResult = +@"SELECT + count("; + AssertFormat(formatter, input, expectedResult); + + // formats UPDATE query with AS part + input = @"UPDATE customers SET total_orders = order_summary.total FROM ( SELECT * FROM bank) AS order_summary"; + expectedResult = +@"UPDATE + customers +SET + total_orders = order_summary.total +FROM + ( + SELECT + * + FROM + bank + ) AS order_summary"; + AssertFormat(formatter, input, expectedResult); + + // formats top-level and newline multi-word reserved words with inconsistent spacing + input = "SELECT * FROM foo LEFT \t \r\n JOIN bar ORDER \r\n BY blah"; + expectedResult = "SELECT\r\n *\r\nFROM\r\n foo\r\n LEFT \t \r\n JOIN bar\r\nORDER \r\n BY\r\n blah"; + AssertFormat(formatter, input, expectedResult); + + // formats long double parenthized queries to multiple lines + input = @"((foo = '0123456789-0123456789-0123456789-0123456789'))"; + expectedResult = +@"( + ( + foo = '0123456789-0123456789-0123456789-0123456789' + ) +)"; + AssertFormat(formatter, input, expectedResult); + + // formats short double parenthized queries to one line + input = @"((foo = 'bar'))"; + expectedResult = @"((foo = 'bar'))"; + AssertFormat(formatter, input, expectedResult); + + // formats logical operators + AssertFormat(formatter, @"foo ALL bar", @"foo ALL bar"); + AssertFormat(formatter, @"foo = ANY (1, 2, 3)", @"foo = ANY (1, 2, 3)"); + AssertFormat(formatter, @"EXISTS bar", @"EXISTS bar"); + AssertFormat(formatter, @"foo IN (1, 2, 3)", @"foo IN (1, 2, 3)"); + AssertFormat(formatter, @"foo LIKE 'hello%'", @"foo LIKE 'hello%'"); + AssertFormat(formatter, @"foo IS NULL", @"foo IS NULL"); + AssertFormat(formatter, @"UNIQUE foo", @"UNIQUE foo"); + + // formats AND/OR operators + AssertFormat(formatter, "foo AND bar", "foo\r\nAND bar"); + AssertFormat(formatter, "foo OR bar", "foo\r\nOR bar"); + + // keeps separation between multiple statements + AssertFormat(formatter, "foo;bar;", "foo;\r\nbar;"); + AssertFormat(formatter, "foo\r\n;bar;", "foo;\r\nbar;"); + AssertFormat(formatter, "foo\n\n\n;bar;\n\n", "foo;\r\nbar;"); + input = +@"SELECT count(*),Column1 FROM Table1; +SELECT count(*),Column1 FROM Table2;"; + expectedResult = +@"SELECT + count(*), + Column1 +FROM + Table1; +SELECT + count(*), + Column1 +FROM + Table2;"; + AssertFormat(formatter, input, expectedResult); + + // formats unicode correctly + input = @"SELECT 结合使用, тест FROM table;"; + expectedResult = +@"SELECT + 结合使用, + тест +FROM + table;"; + AssertFormat(formatter, input, expectedResult); + + // correctly indents create statement after select + input = +@"SELECT * FROM test; +CREATE TABLE TEST(id NUMBER NOT NULL, col1 VARCHAR2(20), col2 VARCHAR2(20));"; + expectedResult = +@"SELECT + * +FROM + test; +CREATE TABLE TEST( + id NUMBER NOT NULL, + col1 VARCHAR2(20), + col2 VARCHAR2(20) +);"; + AssertFormat(formatter, input, expectedResult); + + // correctly handles floats as single tokens + input = @"SELECT 1e-9 AS a, 1.5e-10 AS b, 3.5E12 AS c, 3.5e12 AS d;"; + expectedResult = +@"SELECT + 1e-9 AS a, + 1.5e-10 AS b, + 3.5E12 AS c, + 3.5e12 AS d;"; + AssertFormat(formatter, input, expectedResult); + + // does not split UNION ALL in half + input = +@"SELECT * FROM tbl1 +UNION ALL +SELECT * FROM tbl2;"; + expectedResult = +@"SELECT + * +FROM + tbl1 +UNION ALL +SELECT + * +FROM + tbl2;"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Shared tests for MySQL and MariaDB + /// + private void BehavesLikeMariaDbFormatter(Formatter formatter) + { + BehavesLikeSqlFormatter(formatter); + SupportsCase(formatter); + SupportsCreateTable(formatter); + SupportsAlterTable(formatter); + SupportsStrings(formatter, "\"\"", "''", "``"); + SupportsBetween(formatter); + SupportsComments(formatter); + SupportsConfigOptions(formatter); + SupportsOperators( + formatter, + "%", + "&", + "|", + "^", + "~", + "!=", + "!", + "<=>", + "<<", + ">>", + "&&", + "||", + ":="); + SupportsJoin( + formatter, + without: new[] { "FULL" }, + additionally: new[] + { + "STRAIGHT_JOIN", + "NATURAL LEFT JOIN", + "NATURAL LEFT OUTER JOIN", + "NATURAL RIGHT JOIN", + "NATURAL RIGHT OUTER JOIN" + }); + + // supports # comments + string input = "SELECT a # comment\r\nFROM b # comment"; + string expectedResult = +@"SELECT + a # comment +FROM + b # comment"; + AssertFormat(formatter, input, expectedResult); + + // supports @variables + input = "SELECT @foo, @bar"; + expectedResult = +@"SELECT + @foo, + @bar"; + AssertFormat(formatter, input, expectedResult); + + // supports setting variables: @var := + input = "SET @foo := (SELECT * FROM tbl);"; + expectedResult = +@"SET + @foo := ( + SELECT + * + FROM + tbl + );"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests for standard -- and /* *\/ comments + /// + private void SupportsComments(Formatter formatter) + { + // formats SELECT query with different comments + string input = @" +SELECT +/* + * This is a block comment + */ +* FROM +-- This is another comment +MyTable -- One final comment +WHERE 1 = 2;"; + + string expectedResult = +@"SELECT + /* + * This is a block comment + */ + * +FROM + -- This is another comment + MyTable -- One final comment +WHERE + 1 = 2;"; + AssertFormat(formatter, input, expectedResult); + + // maintains block comment indentation + input = +@"SELECT + /* + * This is a block comment + */ + * +FROM + MyTable +WHERE + 1 = 2"; + + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + // formats tricky line comments + input = "SELECT a--comment, here\r\nFROM b--comment"; + + expectedResult = +@"SELECT + a --comment, here +FROM + b --comment"; + AssertFormat(formatter, input, expectedResult); + + // formats line comments followed by semicolon + input = @" +SELECT a FROM b +--comment +;"; + + expectedResult = +@"SELECT + a +FROM + b --comment +;"; + AssertFormat(formatter, input, expectedResult); + + // formats line comments followed by comma + input = @" +SELECT a --comment +, b"; + + expectedResult = +@"SELECT + a --comment +, + b"; + AssertFormat(formatter, input, expectedResult); + + // formats line comments followed by close-paren + input = "SELECT ( a --comment\r\n )"; + + expectedResult = +@"SELECT + (a --comment +)"; + AssertFormat(formatter, input, expectedResult); + + // formats line comments followed by open-paren + input = "SELECT a --comment\r\n()"; + + expectedResult = +@"SELECT + a --comment + ()"; + AssertFormat(formatter, input, expectedResult); + + // recognizes line-comments with Windows line-endings (converts them to UNIX) + input = "SELECT * FROM\r\n-- line comment 1\r\nMyTable -- line comment 2\r\n"; + expectedResult = "SELECT\r\n *\r\nFROM\r\n -- line comment 1\r\n MyTable -- line comment 2"; + AssertFormat(formatter, input, expectedResult); + + // formats query that ends with open comment + input = @" +SELECT count(*) +/*Comment"; + + expectedResult = +@"SELECT + count(*) + /*Comment"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests for all the config options + /// + private void SupportsConfigOptions(Formatter formatter) + { + // supports indent option + string input = @"SELECT count(*),Column1 FROM Table1;"; + + string expectedResult = +@"SELECT + count(*), + Column1 +FROM + Table1;"; + + string output = formatter.Format(input, new SqlFormatterOptions(indentationSize: 4, uppercase: false)); + Assert.AreEqual(expectedResult, output); + + // supports linesBetweenQueries option + input = @"SELECT * FROM foo; SELECT * FROM bar;"; + + expectedResult = +@"SELECT + * +FROM + foo; + +SELECT + * +FROM + bar;"; + + output = formatter.Format(input, new SqlFormatterOptions(indentationSize: 2, uppercase: false, linesBetweenQueries: 2)); + Assert.AreEqual(expectedResult, output); + + // supports uppercase option + input = @"select distinct * frOM foo left join bar WHERe cola > 1 and colb = 3"; + + expectedResult = +@"SELECT + DISTINCT * +FROM + foo + LEFT JOIN bar +WHERE + cola > 1 + AND colb = 3"; + + output = formatter.Format(input, new SqlFormatterOptions(indentationSize: 2, uppercase: true)); + Assert.AreEqual(expectedResult, output); + } + + /// + /// Tests support for various operators + /// + private void SupportsOperators(Formatter formatter, params string[] operators) + { + foreach (string op in operators) + { + string input = $"foo{op}bar"; + string expectedResult = $"foo {op} bar"; + AssertFormat(formatter, input, expectedResult); + } + } + + /// + /// Tests support for CASE [WHEN...] END syntax + /// + private void SupportsCase(Formatter formatter) + { + // formats CASE ... WHEN with a blank expression + string input = "CASE WHEN option = 'foo' THEN 1 WHEN option = 'bar' THEN 2 WHEN option = 'baz' THEN 3 ELSE 4 END;"; + + string expectedResult = +@"CASE + WHEN option = 'foo' THEN 1 + WHEN option = 'bar' THEN 2 + WHEN option = 'baz' THEN 3 + ELSE 4 +END;"; + AssertFormat(formatter, input, expectedResult); + + // formats CASE ... WHEN with an expression + input = "CASE toString(getNumber()) WHEN 'one' THEN 1 WHEN 'two' THEN 2 WHEN 'three' THEN 3 ELSE 4 END;"; + + expectedResult = +@"CASE + toString(getNumber()) + WHEN 'one' THEN 1 + WHEN 'two' THEN 2 + WHEN 'three' THEN 3 + ELSE 4 +END;"; + AssertFormat(formatter, input, expectedResult); + + // formats CASE ... WHEN inside SELECT + input = "SELECT foo, bar, CASE baz WHEN 'one' THEN 1 WHEN 'two' THEN 2 ELSE 3 END FROM table"; + + expectedResult = +@"SELECT + foo, + bar, + CASE + baz + WHEN 'one' THEN 1 + WHEN 'two' THEN 2 + ELSE 3 + END +FROM + table"; + AssertFormat(formatter, input, expectedResult); + + // recognizes lowercase CASE ... END + input = "case when option = 'foo' then 1 else 2 end;"; + + expectedResult = +@"case + when option = 'foo' then 1 + else 2 +end;"; + AssertFormat(formatter, input, expectedResult); + + // ignores words CASE and END inside other strings + input = "SELECT CASEDATE, ENDDATE FROM table1;"; + + expectedResult = +@"SELECT + CASEDATE, + ENDDATE +FROM + table1;"; + AssertFormat(formatter, input, expectedResult); + + // properly converts to uppercase in case statements + input = "case toString(getNumber()) when 'one' then 1 when 'two' then 2 when 'three' then 3 else 4 end;"; + + expectedResult = +@"CASE + toString(getNumber()) + WHEN 'one' THEN 1 + WHEN 'two' THEN 2 + WHEN 'three' THEN 3 + ELSE 4 +END;"; + string output = formatter.Format(input, new SqlFormatterOptions(indentationSize: 2, uppercase: true)); + Assert.AreEqual(expectedResult, output); + } + + /// + /// Tests support for CREATE TABLE syntax + /// + private void SupportsCreateTable(Formatter formatter) + { + // formats short CREATE TABLE + string input = "CREATE TABLE items (a INT PRIMARY KEY, b TEXT);"; + + string expectedResult = + @"CREATE TABLE items (a INT PRIMARY KEY, b TEXT);"; + AssertFormat(formatter, input, expectedResult); + + // formats long CREATE TABLE + input = "CREATE TABLE items (a INT PRIMARY KEY, b TEXT, c INT NOT NULL, doggie INT NOT NULL);"; + + expectedResult = +@"CREATE TABLE items ( + a INT PRIMARY KEY, + b TEXT, + c INT NOT NULL, + doggie INT NOT NULL +);"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests support for ALTER TABLE syntax + /// + private void SupportsAlterTable(Formatter formatter) + { + // formats ALTER TABLE ... ALTER COLUMN query + string input = "ALTER TABLE supplier ALTER COLUMN supplier_name VARCHAR(100) NOT NULL;"; + + string expectedResult = + @"ALTER TABLE + supplier +ALTER COLUMN + supplier_name VARCHAR(100) NOT NULL;"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests support for ALTER TABLE ... MODIFY syntax + /// + private void SupportsAlterTableModify(Formatter formatter) + { + // formats ALTER TABLE ... MODIFY statement + string input = "ALTER TABLE supplier MODIFY supplier_name char(100) NOT NULL;"; + + string expectedResult = +@"ALTER TABLE + supplier +MODIFY + supplier_name char(100) NOT NULL;"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests support for various string syntax + /// + private void SupportsStrings(Formatter formatter, params string[] stringTypes) + { + string input; + string expectedResult; + if (stringTypes.Contains("\"\"")) + { + // supports double-quoted strings + input = "\"foo JOIN bar\""; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "\"foo \\\" JOIN bar\""; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("''")) + { + // supports single-quoted strings + input = "'foo JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "'foo \\' JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("``")) + { + // supports backtick-quoted strings + input = "`foo JOIN bar`"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "`foo `` JOIN bar`"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("U&\"\"")) + { + // supports unicode double-quoted strings + input = "U&\"foo JOIN bar\""; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "U&\"foo \\\" JOIN bar\""; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("U&''")) + { + // supports single-quoted strings + input = "U&'foo JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "U&'foo \\' JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("$$")) + { + // supports dollar-quoted strings + input = "$xxx$foo $$ LEFT JOIN $yyy$ bar$xxx$"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "$$foo JOIN bar$$"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "$$foo $ JOIN bar$$"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "$$foo \r\n bar$$"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("[]")) + { + // supports [bracket-quoted identifiers] + input = "[foo JOIN bar]"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "[foo ]] JOIN bar]"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + if (stringTypes.Contains("N''")) + { + // supports T-SQL unicode strings + input = "N'foo JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + + input = "N'foo \\' JOIN bar'"; + expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + } + + /// + /// Tests support for BETWEEN _ AND _ syntax + /// + private void SupportsBetween(Formatter formatter) + { + // formats BETWEEN _ AND _ on single line + string input = "foo BETWEEN bar AND baz"; + string expectedResult = input; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests support for SET SCHEMA syntax + /// + private void SupportsSchema(Formatter formatter) + { + // formats simple SET SCHEMA statements + string input = "SET SCHEMA schema1;"; + string expectedResult = +@"SET SCHEMA + schema1;"; + AssertFormat(formatter, input, expectedResult); + } + + /// + /// Tests support for various joins + /// + private void SupportsJoin(Formatter formatter, string[] without = null, string[] additionally = null) + { + Regex unsupportedJoinRegex + = without != null + ? new Regex(string.Join("|", without), RegexOptions.Compiled) + : new Regex(@"^whateve_!%&$"); + + Func isSupportedJoin + = (string join) => !unsupportedJoinRegex.Match(join).Success; + + string[] joins = new[] { "CROSS JOIN", "NATURAL JOIN" }; + foreach (string join in joins) + { + if (isSupportedJoin(join)) + { + string input = $"SELECT * FROM tbl1 {join} tbl2"; + string expectedResult = +$@"SELECT + * +FROM + tbl1 + {join} tbl2"; + AssertFormat(formatter, input, expectedResult); + } + } + + // ::= [ ] JOIN + // + // ::= INNER | [ OUTER ] + // + // ::= LEFT | RIGHT | FULL + + joins = new[] + { + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "LEFT OUTER JOIN", + "RIGHT JOIN", + "RIGHT OUTER JOIN", + "FULL JOIN", + "FULL OUTER JOIN" + }; + + if (additionally != null) + { + joins = joins.Concat(additionally).ToArray(); + } + + foreach (string join in joins) + { + if (isSupportedJoin(join)) + { + string input = +$@"SELECT customer_id.from, COUNT(order_id) AS total FROM customers +{join} orders ON customers.customer_id = orders.customer_id;"; + string expectedResult = +$@"SELECT + customer_id.from, + COUNT(order_id) AS total +FROM + customers + {join} orders ON customers.customer_id = orders.customer_id;"; + AssertFormat(formatter, input, expectedResult); + } + } + } + + private void AssertFormat(Formatter formatter, string input, string expectedOutput) + { + string output = formatter.Format(input); + Assert.AreEqual(expectedOutput, output); + } + } +}