Repository: sqlfluff/sqlfluff
Pull request: Initial support for Transact-SQL (T-SQL) dialect
### Brief summary of the change made
Idea of this PR is to get basic support for TSQL. Such that most of the TSQL code is parsable.

Fixes #660 
Fixes: #1282 

### Are there any other side effects of this change that we should be aware of?
Should not be any side effects.

### Pull Request checklist

Still in draft!

- [x] Please confirm you have completed any of the necessary steps below.

- Included test cases to demonstrate any code changes, which may be one or more of the following:
  - `.yml` rule test cases in `test/fixtures/rules/std_rule_cases`.
  - `.sql`/`.yml` parser test cases in `test/fixtures/parser` (note YML files can be auto generated with `python test/generate_parse_fixture_yml.py` or by running `tox` locally).
  - Full autofix test cases in `test/fixtures/linter/autofix`.
  - Other.
- Added appropriate documentation for the change.
- Created GitHub issues for any relevant followup/future enhancements if appropriate.


Problem statement:
T-SQL dialect (Microsoft SQL Server Transact-SQL)
T-SQL support would be great for those of us stuck in MS Land! 😄


Create dialect for MS SQL DB
For parsing MS SQL DB sql scripts it seems to be useful to add special dialect.

For example, for table creation we have the following structure, which cannot be parsed with ANSI dialect (due to square brackets around schema, index, table, fields names and "GO", "CLUSTERED","UNCLUSTERED" keywords).
Similar issue with square brackets we have for scripts creation for views and indexes.

table1.sql:
"
CREATE TABLE [schema1].[table1] (
    [field1]                            DATE           NOT NULL,
    [field2]                              BIGINT         NOT NULL,
    [field3]                          BIGINT         NULL
);


GO
CREATE CLUSTERED INDEX [Cix_U_XID_YID]
    ON [schema1].[table1]([field1] ASC, [field2] ASC);
"

Hints from issue discussion:
+1 for me.

Is there a guide on how this could be implemented? 
I don't know about a guide, but if you look at existing files like dialect_bigquery.py or dialect_postgres.py, it'd be pretty similar. Essentially, each dialect is a Python module that specializes an existing"base" dialect module. In most cases, the base dialect is dialect_ansi.py, which is sort of the ANSI SQL standard with a few db-specific bits that may someday be moved to other dialects if needed.

Commit message: commit intial function fix tsql

Patch to review:
diff --git a/src/sqlfluff/core/dialects/__init__.py b/src/sqlfluff/core/dialects/__init__.py
index 3d8895bd452..2835c6a310a 100644
--- a/src/sqlfluff/core/dialects/__init__.py
+++ b/src/sqlfluff/core/dialects/__init__.py
@@ -26,6 +26,7 @@
     "snowflake": ("dialect_snowflake", "snowflake_dialect"),
     "exasol": ("dialect_exasol", "exasol_dialect"),
     "exasol_fs": ("dialect_exasol_fs", "exasol_fs_dialect"),
+    "tsql": ("dialect_tsql", "tsql_dialect"),
 }
 
 
diff --git a/src/sqlfluff/dialects/dialect_tsql.py b/src/sqlfluff/dialects/dialect_tsql.py
new file mode 100644
index 00000000000..8c135716193
--- /dev/null
+++ b/src/sqlfluff/dialects/dialect_tsql.py
@@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
+        ("curly", "StartCurlyBracketSegment", "EndCurlyBracketSegment", False),
+    ]
+)
+
+
+# Update only RESERVED Keywords
+# tsql_dialect.sets("reserved_keywords").clear()
+# tsql_dialect.sets("reserved_keywords").update(RESERVED_KEYWORDS)
+
+tsql_dialect.add(
+    AtSignLiteralSegment=NamedParser(
+        "atsign",
+        CodeSegment,
+        name="atsign_literal",
+        type="literal",
+        trim_chars=("@",),
+    ),
+)
+
+tsql_dialect.add(    
+    AtSignSignSegment=StringParser(
+        "@", SymbolSegment, name="atsign", type="user_designator"
+    ),   
+    SessionVariableNameSegment=RegexParser(
+        r"[@][a-zA-Z0-9_]*",
+        CodeSegment,
+        name="declared_variable",
+        type="variable",
+    ),
+)
+
+tsql_dialect.replace(
+    ParameterNameSegment=RegexParser(
+        r"[@][A-Za-z0-9_]+", CodeSegment, name="parameter", type="parameter"
+    )
+)
+
+tsql_dialect.insert_lexer_matchers(
+    [
+        RegexLexer(
+            "atsign",
+            r"[@][a-zA-Z0-9_]+",
+            CodeSegment,
+        ),
+    ],
+    before="code",
+)
+
+
+@tsql_dialect.segment(replace=True)
+class CreateFunctionStatementSegment(BaseSegment):
+    """A `CREATE FUNCTION` statement.
+
+    This version in the ANSI dialect should be a "common subset" of the
+    structure of the code for those dialects.
+    postgres: https://www.postgresql.org/docs/9.1/sql-createfunction.html
+    snowflake: https://docs.snowflake.com/en/sql-reference/sql/create-function.html
+    bigquery: https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions
+    """
+
+    type = "create_function_statement"
+
+    match_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",
+        Anything(),
+    )
+    parse_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",       
+        Sequence(
+            Ref("StartSquareBracketSegment", optional=True),
+            Ref("FunctionNameSegment"),
+            Ref("EndSquareBracketSegment", optional=True),
+            Ref("DotSegment"), optional=True),               
+        Sequence(
+            Ref("StartSquareBracketSegment", optional=True),
+            Ref("FunctionNameSegment"),
+            Ref("EndSquareBracketSegment", optional=True)),
+        Ref("FunctionParameterListGrammar"),
+        Sequence(  # Optional function return type
+            "RETURNS",
+            Ref("DatatypeSegment"),
+            optional=True,
+        ),
+        Sequence("AS"),
+        Sequence("BEGIN"),
+        Ref("FunctionDefinitionGrammar"),
+        Sequence("END"),
+        Sequence("GO", optional=True),
+    )
+
+
+@tsql_dialect.segment(replace=True)
+class FunctionDefinitionGrammar(BaseSegment):
+    """This is the body of a `CREATE FUNCTION AS` statement."""
+
+    match_grammar = Sequence(        
+        GreedyUntil("RETURN", optional=True),
+        Sequence("RETURN"),
+        GreedyUntil("END"),
+    )
\ No newline at end of file
diff --git a/src/sqlfluff/dialects/tsql_keywords.py b/src/sqlfluff/dialects/tsql_keywords.py
new file mode 100644
index 00000000000..a864b351386
--- /dev/null
+++ b/src/sqlfluff/dialects/tsql_keywords.py
@@ -0,0 +1,189 @@
+"""A list of all SQL key words."""
+
+RESERVED_KEYWORDS = [
+    "ADD",
+    "ALL",
+    "ALTER",
+    "AND",
+    "ANY",
+    "AS",
+    "ASC",
+    "AUTHORIZATION",
+    "BACKUP",
+    "BEGIN",
+    "BETWEEN",
+    "BREAK",
+    "BROWSE",
+    "BULK",
+    "BY",
+    "CASCADE",
+    "CASE",
+    "CHECK",
+    "CHECKPOINT",
+    "CLOSE",
+    "CLUSTERED",
+    "COALESCE",
+    "COLLATE",
+    "COLUMN",
+    "COMMIT",
+    "COMPUTE",
+    "CONSTRAINT",
+    "CONTAINS",
+    "CONTAINSTABLE",
+    "CONTINUE",
+    "CONVERT",
+    "CREATE",
+    "CROSS",
+    "CURRENT",
+    "CURRENT_DATE",
+    "CURRENT_TIME",
+    "CURRENT_TIMESTAMP",
+    "CURRENT_USER",
+    "CURSOR",
+    "DATABASE",
+    "DBCC",
+    "DEALLOCATE",
+    "DECLARE",
+    "DEFAULT",
+    "DELETE",
+    "DENY",
+    "DESC",
+    "DISK",
+    "DISTINCT",
+    "DISTRIBUTED",
+    "DOUBLE",
+    "DROP",
+    "DUMP",
+    "ELSE",
+    "END",
+    "ERRLVL",
+    "ESCAPE",
+    "EXCEPT",
+    "EXEC",
+    "EXECUTE",
+    "EXISTS",
+    "EXIT",
+    "EXTERNAL",
+    "FETCH",
+    "FILE",
+    "FILLFACTOR",
+    "FOR",
+    "FOREIGN",
+    "FREETEXT",
+    "FREETEXTTABLE",
+    "FROM",
+    "FULL",
+    "FUNCTION",
+    "GOTO",
+    "GRANT",
+    "GROUP",
+    "HAVING",
+    "HOLDLOCK",
+    "IDENTITY",
+    "IDENTITY_INSERT",
+    "IDENTITYCOL",
+    "IF",
+    "IN",
+    "INDEX",
+    "INNER",
+    "INSERT",
+    "INTERSECT",
+    "INTO",
+    "IS",
+    "JOIN",
+    "KEY",
+    "KILL",
+    "LEFT",
+    "LIKE",
+    "LINENO",
+    "LOAD",
+    "MERGE",
+    "NATIONAL",
+    "NOCHECK",
+    "NONCLUSTERED",
+    "NOT",
+    "NULL",
+    "NULLIF",
+    "OF",
+    "OFF",
+    "OFFSETS",
+    "ON",
+    "OPEN",
+    "OPENDATASOURCE",
+    "OPENQUERY",
+    "OPENROWSET",
+    "OPENXML",
+    "OPTION",
+    "OR",
+    "ORDER",
+    "OUTER",
+    "OVER",
+    "PERCENT",
+    "PIVOT",
+    "PLAN",
+    "PRECISION",
+    "PRIMARY",
+    "PRINT",
+    "PROC",
+    "PROCEDURE",
+    "PUBLIC",
+    "RAISERROR",
+    "READ",
+    "READTEXT",
+    "RECONFIGURE",
+    "REFERENCES",
+    "REPLICATION",
+    "RESTORE",
+    "RESTRICT",
+    "RETURN",
+    "REVERT",
+    "REVOKE",
+    "RIGHT",
+    "ROLLBACK",
+    "ROWCOUNT",
+    "ROWGUIDCOL",
+    "RULE",
+    "SAVE",
+    "SCHEMA",
+    "SECURITYAUDIT",
+    "SELECT",
+    "SEMANTICKEYPHRASETABLE",
+    "SEMANTICSIMILARITYDETAILSTABLE",
+    "SEMANTICSIMILARITYTABLE",
+    "SESSION_USER",
+    "SET",
+    "SETUSER",
+    "SHUTDOWN",
+    "SOME",
+    "STATISTICS",
+    "SYSTEM_USER",
+    "TABLE",
+    "TABLESAMPLE",
+    "TEXTSIZE",
+    "THEN",
+    "TO",
+    "TOP",
+    "TRAN",
+    "TRANSACTION",
+    "TRIGGER",
+    "TRUNCATE",
+    "TRY_CONVERT",
+    "TSEQUAL",
+    "UNION",
+    "UNIQUE",
+    "UNPIVOT",
+    "UPDATE",
+    "UPDATETEXT",
+    "USE",
+    "USER",
+    "VALUES",
+    "VARYING",
+    "VIEW",
+    "WAITFOR",
+    "WHEN",
+    "WHERE",
+    "WHILE",
+    "WITH",
+    "WITHIN GROUP",
+    "WRITETEXT"
+]
\ No newline at end of file
diff --git a/test/dialects/tsql_test.py b/test/dialects/tsql_test.py
new file mode 100644
index 00000000000..f582843cfec
--- /dev/null
+++ b/test/dialects/tsql_test.py
@@ -0,0 +1,16 @@
+"""Tests specific to the postgres dialect."""
+
+import pytest
+
+from sqlfluff.core import FluffConfig, Linter
+
+
+
+def test_space_is_not_reserved(raw):
+    """Ensure that SPACE is not treated as reserved."""
+    cfg = FluffConfig(
+        configs={"core": {"exclude_rules": "L009,L016,L031", "dialect": "tsql"}}
+    )
+    lnt = Linter(config=cfg)
+    result = lnt.lint_string(raw)
+    assert result.num_violations() == 0
diff --git a/test/fixtures/parser/tsql/minimal_function.sql b/test/fixtures/parser/tsql/minimal_function.sql
new file mode 100644
index 00000000000..540f4cc1a3a
--- /dev/null
+++ b/test/fixtures/parser/tsql/minimal_function.sql
@@ -0,0 +1,8 @@
+CREATE OR ALTER FUNCTION [dbo].[add] (@add_1 int, @add_2 int) RETURNS integer
+AS 
+BEGIN
+	DECLARE @time int = 1
+	SELECT * FROM Table
+	RETURN @add_1 + @add_2
+END
+	
diff --git a/test/fixtures/parser/tsql/minimal_function.yml b/test/fixtures/parser/tsql/minimal_function.yml
new file mode 100644
index 00000000000..5b909e7bad6
--- /dev/null
+++ b/test/fixtures/parser/tsql/minimal_function.yml
@@ -0,0 +1,47 @@
+file:
+  statement:
+    create_function_statement:
+    - keyword: CREATE
+    - binary_operator: OR
+    - keyword: ALTER
+    - keyword: FUNCTION
+    - start_square_bracket: '['
+    - function_name:
+        function_name_identifier: dbo
+    - end_square_bracket: ']'
+    - dot: .
+    - start_square_bracket: '['
+    - function_name:
+        function_name_identifier: add
+    - end_square_bracket: ']'
+    - base:
+        bracketed:
+        - start_bracket: (
+        - parameter: '@add_1'
+        - data_type:
+            data_type_identifier: int
+        - comma: ','
+        - parameter: '@add_2'
+        - data_type:
+            data_type_identifier: int
+        - end_bracket: )
+    - keyword: RETURNS
+    - data_type:
+        data_type_identifier: integer
+    - keyword: AS
+    - keyword: BEGIN
+    - base:
+      - raw: DECLARE
+      - raw: '@time'
+      - raw: int
+      - raw: '='
+      - raw: '1'
+      - raw: SELECT
+      - raw: '*'
+      - raw: FROM
+      - raw: Table
+      - keyword: RETURN
+      - raw: '@add_1'
+      - raw: +
+      - raw: '@add_2'
+    - keyword: END


Code review comments:
diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
Careful you don't accidentally update the ansi dialect!

```suggestion
tsql_dialect.sets("bracket_pairs").update(
```

diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
Any reason you need this set to `TRUE`? If this is not set then no need to update this set at all as same as ANSI.

@author:
Removed

diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
+        ("curly", "StartCurlyBracketSegment", "EndCurlyBracketSegment", False),
+    ]
+)
+
+
+# Update only RESERVED Keywords
+# tsql_dialect.sets("reserved_keywords").clear()
+# tsql_dialect.sets("reserved_keywords").update(RESERVED_KEYWORDS)
+
+tsql_dialect.add(
+    AtSignLiteralSegment=NamedParser(
+        "atsign",
+        CodeSegment,
+        name="atsign_literal",
+        type="literal",
+        trim_chars=("@",),
+    ),
+)
+
+tsql_dialect.add(    
+    AtSignSignSegment=StringParser(
+        "@", SymbolSegment, name="atsign", type="user_designator"
+    ),   
+    SessionVariableNameSegment=RegexParser(
+        r"[@][a-zA-Z0-9_]*",
+        CodeSegment,
+        name="declared_variable",
+        type="variable",
+    ),
+)
+
+tsql_dialect.replace(
+    ParameterNameSegment=RegexParser(
+        r"[@][A-Za-z0-9_]+", CodeSegment, name="parameter", type="parameter"
+    )
+)
+
+tsql_dialect.insert_lexer_matchers(
+    [
+        RegexLexer(
+            "atsign",
+            r"[@][a-zA-Z0-9_]+",
+            CodeSegment,
+        ),
+    ],
+    before="code",
+)
+
+
+@tsql_dialect.segment(replace=True)
+class CreateFunctionStatementSegment(BaseSegment):
+    """A `CREATE FUNCTION` statement.
+
+    This version in the ANSI dialect should be a "common subset" of the
+    structure of the code for those dialects.
+    postgres: https://www.postgresql.org/docs/9.1/sql-createfunction.html
+    snowflake: https://docs.snowflake.com/en/sql-reference/sql/create-function.html
+    bigquery: https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions
+    """
+
+    type = "create_function_statement"
+
+    match_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",
+        Anything(),
+    )
+    parse_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",       
+        Sequence(
+            Ref("StartSquareBracketSegment", optional=True),
+            Ref("FunctionNameSegment"),
+            Ref("EndSquareBracketSegment", optional=True),
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
IMHO, this should be changed to `Bracketed` syntax and if brackets are optional then use `OneOf` to indicate either unbracketed, or bracket version:

```suggestion
        Sequence(
            OneOf(
                Ref("FunctionNameSegment"),
                Bracketed(
                    Ref("FunctionNameSegment"),
                    bracket_type="square",
                ),
            ),
```

@user2:
Even better, use `OptionallyBracketed`. As currently written, the following function names would be legal:
* `[abc`
* `abc]`

Which could cause some confusing parser behavior on someone's `.sql` file.

diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
+        ("curly", "StartCurlyBracketSegment", "EndCurlyBracketSegment", False),
+    ]
+)
+
+
+# Update only RESERVED Keywords
+# tsql_dialect.sets("reserved_keywords").clear()
+# tsql_dialect.sets("reserved_keywords").update(RESERVED_KEYWORDS)
+
+tsql_dialect.add(
+    AtSignLiteralSegment=NamedParser(
+        "atsign",
+        CodeSegment,
+        name="atsign_literal",
+        type="literal",
+        trim_chars=("@",),
+    ),
+)
+
+tsql_dialect.add(    
+    AtSignSignSegment=StringParser(
+        "@", SymbolSegment, name="atsign", type="user_designator"
+    ),   
+    SessionVariableNameSegment=RegexParser(
+        r"[@][a-zA-Z0-9_]*",
+        CodeSegment,
+        name="declared_variable",
+        type="variable",
+    ),
+)
+
+tsql_dialect.replace(
+    ParameterNameSegment=RegexParser(
+        r"[@][A-Za-z0-9_]+", CodeSegment, name="parameter", type="parameter"
+    )
+)
+
+tsql_dialect.insert_lexer_matchers(
+    [
+        RegexLexer(
+            "atsign",
+            r"[@][a-zA-Z0-9_]+",
+            CodeSegment,
+        ),
+    ],
+    before="code",
+)
+
+
+@tsql_dialect.segment(replace=True)
+class CreateFunctionStatementSegment(BaseSegment):
+    """A `CREATE FUNCTION` statement.
+
+    This version in the ANSI dialect should be a "common subset" of the
+    structure of the code for those dialects.
+    postgres: https://www.postgresql.org/docs/9.1/sql-createfunction.html
+    snowflake: https://docs.snowflake.com/en/sql-reference/sql/create-function.html
+    bigquery: https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
Change this to T-SQL specific info

@author:
Yes, it was just a draft to see part of the code

diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
+        ("curly", "StartCurlyBracketSegment", "EndCurlyBracketSegment", False),
+    ]
+)
+
+
+# Update only RESERVED Keywords
+# tsql_dialect.sets("reserved_keywords").clear()
+# tsql_dialect.sets("reserved_keywords").update(RESERVED_KEYWORDS)
+
+tsql_dialect.add(
+    AtSignLiteralSegment=NamedParser(
+        "atsign",
+        CodeSegment,
+        name="atsign_literal",
+        type="literal",
+        trim_chars=("@",),
+    ),
+)
+
+tsql_dialect.add(    
+    AtSignSignSegment=StringParser(
+        "@", SymbolSegment, name="atsign", type="user_designator"
+    ),   
+    SessionVariableNameSegment=RegexParser(
+        r"[@][a-zA-Z0-9_]*",
+        CodeSegment,
+        name="declared_variable",
+        type="variable",
+    ),
+)
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
Similar here - do you need this?

@author:
Problably not, will test without.

diff_hunk: @@ -0,0 +1,177 @@
+""" The MSSQL T-SQL dialect.
+
+https://docs.microsoft.com/en-us/sql/t-sql/language-elements/language-elements-transact-sql
+
+
+"""
+from enum import Enum
+from typing import Generator, List, Tuple, NamedTuple, Optional, Union
+
+from sqlfluff.core.parser import (
+    Matchable,
+    BaseSegment,
+    KeywordSegment,
+    SymbolSegment,
+    Sequence,
+    GreedyUntil,
+    StartsWith,
+    OneOf,
+    Delimited,
+    Bracketed,
+    AnyNumberOf,
+    Ref,
+    SegmentGenerator,
+    Anything,
+    Indent,
+    Dedent,
+    Nothing,
+    OptionallyBracketed,
+    StringLexer,
+    RegexLexer,
+    CodeSegment,
+    CommentSegment,
+    WhitespaceSegment,
+    NewlineSegment,
+    StringParser,
+    NamedParser,
+    RegexParser,
+    Conditional,
+)
+
+from sqlfluff.core.dialects.base import Dialect
+from sqlfluff.core.dialects.common import AliasInfo
+from sqlfluff.core.parser.segments.base import BracketedSegment
+
+from sqlfluff.dialects.ansi_keywords import (
+    ansi_reserved_keywords,
+    ansi_unreserved_keywords,
+)
+from sqlfluff.dialects.tsql_keywords import (
+    # BARE_FUNCTIONS,
+    RESERVED_KEYWORDS,
+    # UNRESERVED_KEYWORDS,
+)
+
+from sqlfluff.core.dialects import load_raw_dialect
+
+
+ansi_dialect = load_raw_dialect("ansi")
+tsql_dialect = ansi_dialect.copy_as("tsql")
+
+
+# Bracket pairs (a set of tuples).
+# (name, startref, endref, persists)
+# NOTE: The `persists` value controls whether this type
+# of bracket is persisted during matching to speed up other
+# parts of the matching process. Round brackets are the most
+# common and match the largest areas and so are sufficient.
+ansi_dialect.sets("bracket_pairs").update(
+    [
+        ("round", "StartBracketSegment", "EndBracketSegment", True),
+        ("square", "StartSquareBracketSegment", "EndSquareBracketSegment", True),
+        ("curly", "StartCurlyBracketSegment", "EndCurlyBracketSegment", False),
+    ]
+)
+
+
+# Update only RESERVED Keywords
+# tsql_dialect.sets("reserved_keywords").clear()
+# tsql_dialect.sets("reserved_keywords").update(RESERVED_KEYWORDS)
+
+tsql_dialect.add(
+    AtSignLiteralSegment=NamedParser(
+        "atsign",
+        CodeSegment,
+        name="atsign_literal",
+        type="literal",
+        trim_chars=("@",),
+    ),
+)
+
+tsql_dialect.add(    
+    AtSignSignSegment=StringParser(
+        "@", SymbolSegment, name="atsign", type="user_designator"
+    ),   
+    SessionVariableNameSegment=RegexParser(
+        r"[@][a-zA-Z0-9_]*",
+        CodeSegment,
+        name="declared_variable",
+        type="variable",
+    ),
+)
+
+tsql_dialect.replace(
+    ParameterNameSegment=RegexParser(
+        r"[@][A-Za-z0-9_]+", CodeSegment, name="parameter", type="parameter"
+    )
+)
+
+tsql_dialect.insert_lexer_matchers(
+    [
+        RegexLexer(
+            "atsign",
+            r"[@][a-zA-Z0-9_]+",
+            CodeSegment,
+        ),
+    ],
+    before="code",
+)
+
+
+@tsql_dialect.segment(replace=True)
+class CreateFunctionStatementSegment(BaseSegment):
+    """A `CREATE FUNCTION` statement.
+
+    This version in the ANSI dialect should be a "common subset" of the
+    structure of the code for those dialects.
+    postgres: https://www.postgresql.org/docs/9.1/sql-createfunction.html
+    snowflake: https://docs.snowflake.com/en/sql-reference/sql/create-function.html
+    bigquery: https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions
+    """
+
+    type = "create_function_statement"
+
+    match_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",
+        Anything(),
+    )
+    parse_grammar = Sequence(
+        "CREATE",
+        Sequence("OR", "ALTER", optional=True),
+        Ref("TemporaryGrammar", optional=True),
+        "FUNCTION",       
+        Sequence(
+            Ref("StartSquareBracketSegment", optional=True),
+            Ref("FunctionNameSegment"),
+            Ref("EndSquareBracketSegment", optional=True),
+            Ref("DotSegment"), optional=True),               
+        Sequence(
+            Ref("StartSquareBracketSegment", optional=True),
+            Ref("FunctionNameSegment"),
+            Ref("EndSquareBracketSegment", optional=True)),
path: src/sqlfluff/dialects/dialect_tsql.py
text: @user1:
Similar comment here, but as it's no longer a Sequence if you remove the brackets as only one Item remaining, so can jump straight into the `OneOf` instead of using `Sequence` at all:

```suggestion
        OneOf(
            Ref("FunctionNameSegment"),
            Brackets(
                Ref("FunctionNameSegment"),
                bracket_type="square",
        ),
```


@user2:
Another opportunity to use `OptionallyBracketed` 🙌

diff_hunk: @@ -0,0 +1,189 @@
+"""A list of all SQL key words."""
+
+RESERVED_KEYWORDS = [
+    "ADD",
+    "ALL",
+    "ALTER",
+    "AND",
+    "ANY",
+    "AS",
+    "ASC",
+    "AUTHORIZATION",
+    "BACKUP",
+    "BEGIN",
+    "BETWEEN",
+    "BREAK",
+    "BROWSE",
+    "BULK",
+    "BY",
+    "CASCADE",
+    "CASE",
+    "CHECK",
+    "CHECKPOINT",
+    "CLOSE",
+    "CLUSTERED",
+    "COALESCE",
+    "COLLATE",
+    "COLUMN",
+    "COMMIT",
+    "COMPUTE",
+    "CONSTRAINT",
+    "CONTAINS",
+    "CONTAINSTABLE",
+    "CONTINUE",
+    "CONVERT",
+    "CREATE",
+    "CROSS",
+    "CURRENT",
+    "CURRENT_DATE",
+    "CURRENT_TIME",
+    "CURRENT_TIMESTAMP",
+    "CURRENT_USER",
+    "CURSOR",
+    "DATABASE",
+    "DBCC",
+    "DEALLOCATE",
+    "DECLARE",
+    "DEFAULT",
+    "DELETE",
+    "DENY",
+    "DESC",
+    "DISK",
+    "DISTINCT",
+    "DISTRIBUTED",
+    "DOUBLE",
+    "DROP",
+    "DUMP",
+    "ELSE",
+    "END",
+    "ERRLVL",
+    "ESCAPE",
+    "EXCEPT",
+    "EXEC",
+    "EXECUTE",
+    "EXISTS",
+    "EXIT",
+    "EXTERNAL",
+    "FETCH",
+    "FILE",
+    "FILLFACTOR",
+    "FOR",
+    "FOREIGN",
+    "FREETEXT",
+    "FREETEXTTABLE",
+    "FROM",
+    "FULL",
+    "FUNCTION",
+    "GOTO",
+    "GRANT",
+    "GROUP",
+    "HAVING",
+    "HOLDLOCK",
+    "IDENTITY",
+    "IDENTITY_INSERT",
+    "IDENTITYCOL",
+    "IF",
+    "IN",
+    "INDEX",
+    "INNER",
+    "INSERT",
+    "INTERSECT",
+    "INTO",
+    "IS",
+    "JOIN",
+    "KEY",
+    "KILL",
+    "LEFT",
+    "LIKE",
+    "LINENO",
+    "LOAD",
+    "MERGE",
+    "NATIONAL",
+    "NOCHECK",
+    "NONCLUSTERED",
+    "NOT",
+    "NULL",
+    "NULLIF",
+    "OF",
+    "OFF",
+    "OFFSETS",
+    "ON",
+    "OPEN",
+    "OPENDATASOURCE",
+    "OPENQUERY",
+    "OPENROWSET",
+    "OPENXML",
+    "OPTION",
+    "OR",
+    "ORDER",
+    "OUTER",
+    "OVER",
+    "PERCENT",
+    "PIVOT",
+    "PLAN",
+    "PRECISION",
+    "PRIMARY",
+    "PRINT",
+    "PROC",
+    "PROCEDURE",
+    "PUBLIC",
+    "RAISERROR",
+    "READ",
+    "READTEXT",
+    "RECONFIGURE",
+    "REFERENCES",
+    "REPLICATION",
+    "RESTORE",
+    "RESTRICT",
+    "RETURN",
+    "REVERT",
+    "REVOKE",
+    "RIGHT",
+    "ROLLBACK",
+    "ROWCOUNT",
+    "ROWGUIDCOL",
+    "RULE",
+    "SAVE",
+    "SCHEMA",
+    "SECURITYAUDIT",
+    "SELECT",
+    "SEMANTICKEYPHRASETABLE",
+    "SEMANTICSIMILARITYDETAILSTABLE",
+    "SEMANTICSIMILARITYTABLE",
+    "SESSION_USER",
+    "SET",
+    "SETUSER",
+    "SHUTDOWN",
+    "SOME",
+    "STATISTICS",
+    "SYSTEM_USER",
+    "TABLE",
+    "TABLESAMPLE",
+    "TEXTSIZE",
+    "THEN",
+    "TO",
+    "TOP",
+    "TRAN",
+    "TRANSACTION",
+    "TRIGGER",
+    "TRUNCATE",
+    "TRY_CONVERT",
+    "TSEQUAL",
+    "UNION",
+    "UNIQUE",
+    "UNPIVOT",
+    "UPDATE",
+    "UPDATETEXT",
+    "USE",
+    "USER",
+    "VALUES",
+    "VARYING",
+    "VIEW",
+    "WAITFOR",
+    "WHEN",
+    "WHERE",
+    "WHILE",
+    "WITH",
+    "WITHIN GROUP",
path: src/sqlfluff/dialects/tsql_keywords.py
text: @user1:
Watch if this one causes problems when you add it in and don't see how you can have a keyword with a space itn it - isn't that _words_ (plural) not _word_? 😀

@author:
Words were copied from the unfinished branch of TSQL.

@user2:
IIUC (but kind of guessing), this will basically be ignored (kind of like unreachable code). I think reserved keywords are used to prevent the lexer from matching these things, but if there's a space in the input code, that would be two separate keywords.

@user1:
See comment here: https://github.com/sqlfluff/sqlfluff/pull/960#discussion_r691163297

@author:
Removed keywords all together as I'm somehow this error when adding the reserved keywords: RuntimeError: Grammar refers to 'UsingKeywordSegment' which was not found in the tsql dialect

diff_hunk: @@ -0,0 +1,16 @@
+"""Tests specific to the postgres dialect."""
+
+import pytest
+
+from sqlfluff.core import FluffConfig, Linter
+
+
+
+def test_space_is_not_reserved(raw):
+    """Ensure that SPACE is not treated as reserved."""
path: test/dialects/tsql_test.py
text: @user1:
This currently fails. You need to add this to `dialect_tsql` if you want to run this test:

```
tsql_dialect.sets("unreserved_keywords").remove("SPACE")
```

Maybe this test will pass when you actually load the TSQL Keywords?

To be honest I'd just delete this file for now, and add it later if needed.

diff_hunk: @@ -0,0 +1,16 @@
+"""Tests specific to the postgres dialect."""
path: test/dialects/tsql_test.py
text: @user1:
```suggestion
"""Tests specific to the T-SQL dialect."""
```