Repository: sqlfluff/sqlfluff

Issue:
Postgres psql \gset meta-command is recognized as comment, causes unparsable section
### Search before asking

- [X] I searched the [issues](https://github.com/sqlfluff/sqlfluff/issues) and found no similar issues.


### What Happened

First off, thanks for building and maintaining sqlfluff! It's great to have a tool like this so writing SQL can be a bit more consistent and maintainable. :heart: 

It looks like there have been some efforts to support psql meta-commands and variables:
- https://github.com/sqlfluff/sqlfluff/issues/1327
- https://github.com/sqlfluff/sqlfluff/issues/1534
- https://github.com/sqlfluff/sqlfluff/issues/3343

But the `\gset` meta-command is causing some problems. Here's some context on `\gset`:
- https://www.postgresql.org/docs/current/app-psql.html (and search in the page for gset)
- https://www.depesz.com/2013/02/03/waiting-for-9-3-create-a-psql-command-gset-to-store-query-results-into-psql-variables/

Notably, the `\gset` command often appears at the end of a query and in place of a semi-colon. Example:
```sql
select 'hello' as my_psql_var \gset
select :'my_psql_var';

-- Running the above in psql gives the below output
 ?column? 
----------
 hello
(1 row)
```

If I put the above sql in a file named `test.sql` and lint it with sqlfluff:
```
$ sqlfluff lint --ignore-local-config --dialect postgres test.sql
== [test.sql] FAIL                                                                                                                                                                                                                                                                
L:   2 | P:   1 |  PRS | Line 2, Position 1: Found unparsable section: "select
                       | :'my_psql_var'"
L:   2 | P:  22 | L003 | Expected 1 indentation, found 0 [compared to line 01]
WARNING: Parsing errors found and dialect is set to 'postgres'. Have you configured your dialect correctly?
All Finished 📜 🎉!
```

The parse output shows that `\gset` is being treated as a "comment":
```
$ sqlfluff parse --ignore-local-config --dialect postgres test.sql
[L:  1, P:  1]      |file:
[L:  1, P:  1]      |    statement:
[L:  1, P:  1]      |        select_statement:
[L:  1, P:  1]      |            select_clause:
[L:  1, P:  1]      |                keyword:                                      'select'
[L:  1, P:  7]      |                [META] indent:
[L:  1, P:  7]      |                whitespace:                                   ' '
[L:  1, P:  8]      |                select_clause_element:
[L:  1, P:  8]      |                    quoted_literal:                           "'hello'"
[L:  1, P: 15]      |                    whitespace:                               ' '
[L:  1, P: 16]      |                    alias_expression:
[L:  1, P: 16]      |                        keyword:                              'as'
[L:  1, P: 18]      |                        whitespace:                           ' '
[L:  1, P: 19]      |                        naked_identifier:                     'my_psql_var'
[L:  1, P: 30]      |                whitespace:                                   ' '
[L:  1, P: 31]      |                comment:                                      '\\gset'
[L:  1, P: 36]      |                newline:                                      '\n'
[L:  2, P:  1]      |                unparsable:                                   !! Expected: 'Nothing...'
[L:  2, P:  1]      |                    raw:                                      'select'
[L:  2, P:  7]      |                    whitespace:                               ' '
[L:  2, P:  8]      |                    raw:                                      ':'
[L:  2, P:  9]      |                    single_quote:                             "'my_psql_var'"
[L:  2, P: 22]      |            [META] dedent:
[L:  2, P: 22]      |    statement_terminator:                                     ';'
[L:  2, P: 23]      |    newline:                                                  '\n'
[L:  3, P:  1]      |    [META] end_of_file:

==== parsing violations ====
L:   2 | P:   1 |  PRS | Line 2, Position 1: Found unparsable section: "select
                       | :'my_psql_var'"
WARNING: Parsing errors found and dialect is set to 'postgres'. Have you configured your dialect correctly?
```

### Expected Behaviour

I would expect most psql meta-commands that involve the current query buffer (`\g`, `\gdesc`, `\gexec`, `\gset`, `\gx`, `\w`, `\write`, `\watch`, `\crosstabview`) to act as a semi-colon rather than a comment. The [postgres docs](https://www.postgresql.org/docs/current/app-psql.html) even mention that:

> `\g` without any arguments is essentially equivalent to a semicolon

One note about these meta-commands is running them on an empty query buffer will re-execute the most recent query instead. So, the sql commands above could be written like this (note that line 1 now has a semi-colon):

```
select 'hello' as my_psql_var;
\gset
select :'my_psql_var';
```

and that passes sqlfluff linting without the "unparsable section" failure. But, that runs the first select twice, which can be undesirable -- I'm often trying to store variables on data-mutating statements like `insert ... returning field1 as varname \gset` or `select field1 as varname from function_call() \gset` where I wouldn't want to run the statement again.

### Observed Behaviour

As noted in the "What Happened" section, psql meta-commands like `\gset` act as comments instead of ending the current query buffer / statement. This behavior causes sqlfluff linting errors for most code online (in postgres docs, stackoverflow answers, blogposts, etc.) that involves these psql meta-commands.

I'm not sure why `\` is recognized as a comment. According to https://www.postgresql.org/docs/8.0/sql-syntax.html#SQL-SYNTAX-COMMENTS only `--` and `/* ... */` should be, but I'm probably missing some historical context or something.

### How to reproduce

Contents of `test.sql` file:
```
select 'hello' as my_psql_var \gset
select :'my_psql_var';
```

Commands run:
```
sqlfluff lint --ignore-local-config --dialect postgres test.sql
sqlfluff parse --ignore-local-config --dialect postgres test.sql
```

If you want to play with psql meta-commands and the above script:
```
docker run --rm --detach --name=test-psql-gset-unparsable --env=POSTGRES_PASSWORD=foo --volume=$(pwd):/curdir:ro postgres:15
docker exec -it --workdir=/curdir test-psql-gset-unparsable psql -U postgres

# This will give you the psql prompt, you can run test.sql with `\i` like below:
postgres=# \i test.sql
# To exit, do `\q`:
postgres=# \q

# To stop the postgres container
docker stop test-psql-gset-unparsable
```

### Dialect

Postgres

### Version

```
$ sqlfluff --version
sqlfluff, version 1.4.5

$ python --version
Python 3.11.1
```

### Configuration

default

### Are you willing to work on and submit a PR to address the issue?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/sqlfluff/sqlfluff/blob/main/CODE_OF_CONDUCT.md)

Discussion:
@WittierDinosaur: The historical context is essentially that we shoved them in as comments until we could decide how we wanted to handle them. Moving the query buffer metas out of that seems like a fairly sensible first step

Solution patch:
diff --git a/src/sqlfluff/core/templaters/builtins/dbt.py b/src/sqlfluff/core/templaters/builtins/dbt.py
index df8cc2aa9..e41d0a293 100644
--- a/src/sqlfluff/core/templaters/builtins/dbt.py
+++ b/src/sqlfluff/core/templaters/builtins/dbt.py
@@ -57,4 +57,11 @@ DBT_BUILTINS = {
     # to be parsed TWICE if it uses this variable.
     "is_incremental": FunctionWrapper("is_incremental", lambda: True),
     "this": RelationEmulator(),
+    "zip_strict": FunctionWrapper("zip_strict", zip),
+    "zip": FunctionWrapper(
+        "zip",
+        lambda *args, default=None: (
+            zip(*args) if all(hasattr(arg, "__iter__") for arg in args) else default
+        ),
+    ),
 }
diff --git a/src/sqlfluff/dialects/dialect_postgres.py b/src/sqlfluff/dialects/dialect_postgres.py
index b84d401dd..49b2307ef 100644
--- a/src/sqlfluff/dialects/dialect_postgres.py
+++ b/src/sqlfluff/dialects/dialect_postgres.py
@@ -171,7 +171,7 @@ postgres_dialect.insert_lexer_matchers(
             # them. In future we may want to enhance this to actually parse them to
             # ensure they are valid meta commands.
             "meta_command",
-            r"\\([^\\\r\n])+((\\\\)|(?=\n)|(?=\r\n))?",
+            r"\\(?!gset|gexec)([^\\\r\n])+((\\\\)|(?=\n)|(?=\r\n))?",
             CommentSegment,
         ),
         RegexLexer(
@@ -184,6 +184,14 @@ postgres_dialect.insert_lexer_matchers(
             r"\$\d+",
             LiteralSegment,
         ),
+        RegexLexer(
+            # For now we'll just treat meta syntax like comments and so just ignore
+            # them. In future we may want to enhance this to actually parse them to
+            # ensure they are valid meta commands.
+            "meta_command_query_buffer",
+            r"\\([^\\\r\n])+((\\g(set|exec))|(?=\n)|(?=\r\n))?",
+            SymbolSegment,
+        ),
     ],
     before="word",  # Final thing to search for - as psql specific
 )
@@ -402,6 +410,9 @@ postgres_dialect.add(
     CreateForeignTableGrammar=Sequence("CREATE", "FOREIGN", "TABLE"),
     IntervalUnitsGrammar=OneOf("YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND"),
     WalrusOperatorSegment=StringParser(":=", SymbolSegment, type="assignment_operator"),
+    MetaCommandQueryBufferSegment=TypedParser(
+        "meta_command_query_buffer", SymbolSegment, type="meta_command"
+    ),
     FullTextSearchOperatorSegment=TypedParser(
         "full_text_search_operator", LiteralSegment, type="full_text_search_operator"
     ),
@@ -650,6 +661,7 @@ postgres_dialect.replace(
         "LIMIT",
         Ref("CommaSegment"),
         Ref("SetOperatorSegment"),
+        Ref("MetaCommandQueryBufferSegment"),
     ),
     LiteralGrammar=ansi_dialect.get_grammar("LiteralGrammar").copy(
         insert=[
@@ -1731,6 +1743,7 @@ class UnorderedSelectStatementSegment(ansi.UnorderedSelectStatementSegment):
             Sequence("ON", "CONFLICT"),
             Ref.keyword("RETURNING"),
             Ref("WithCheckOptionSegment"),
+            Ref("MetaCommandQueryBufferSegment"),
         ],
     )
 
@@ -1758,6 +1771,7 @@ class SelectStatementSegment(ansi.SelectStatementSegment):
             Sequence("ON", "CONFLICT"),
             Ref.keyword("RETURNING"),
             Ref("WithCheckOptionSegment"),
+            Ref("MetaCommandQueryBufferSegment"),
         ],
     )
 
@@ -1786,6 +1800,7 @@ class SelectClauseSegment(ansi.SelectClauseSegment):
             Ref("SetOperatorSegment"),
             Sequence("WITH", Ref.keyword("NO", optional=True), "DATA"),
             Ref("WithCheckOptionSegment"),
+            Ref("MetaCommandQueryBufferSegment"),
         ],
         parse_mode=ParseMode.GREEDY_ONCE_STARTED,
     )
@@ -4788,8 +4803,10 @@ class StatementSegment(ansi.StatementSegment):
             Ref("ShowStatementSegment"),
             Ref("SetConstraintsStatementSegment"),
             Ref("CreateForeignDataWrapperStatementSegment"),
+            Ref("MetaCommandQueryBufferStatement"),
             Ref("DropForeignTableStatement"),
             Ref("CreateOperatorStatementSegment"),
+            Ref("AlterForeignTableStatementSegment"),
         ],
     )
 
@@ -6423,6 +6440,24 @@ class ShowStatementSegment(BaseSegment):
     )
 
 
+class MetaCommandQueryBufferStatement(BaseSegment):
+    """A statement that uses meta-commands to change query buffer (e.g. gset and gexec).
+
+    https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-GEXEC
+    """
+
+    type = "meta_command_statement"
+
+    match_grammar = Sequence(
+        AnyNumberOf(
+            Sequence(
+                Ref("SelectStatementSegment"),
+                Ref("MetaCommandQueryBufferSegment", optional=True),
+            )
+        )
+    )
+
+
 class DropForeignTableStatement(BaseSegment):
     """A `DROP FOREIGN TABLE` Statement.
 
@@ -6509,3 +6544,66 @@ class CreateOperatorStatementSegment(BaseSegment):
             )
         ),
     )
+
+
+class AlterForeignTableStatementSegment(BaseSegment):
+    """An `ALTER TABLE` statement.
+
+    https://www.postgresql.org/docs/17/sql-alterforeigntable.html
+    """
+
+    type = "alter_foreign_table_statement"
+
+    match_grammar = Sequence(
+        "ALTER",
+        "FOREIGN",
+        "TABLE",
+        Sequence(
+            Ref("IfExistsGrammar", optional=True),
+            Ref.keyword("ONLY", optional=True),
+            Ref("TableReferenceSegment"),
+            Ref("StarSegment", optional=True),
+            OneOf(
+                Delimited(Ref("AlterForeignTableActionSegment")),
+                Sequence(
+                    "RENAME",
+                    Ref.keyword("COLUMN", optional=True),
+                    Ref("ColumnReferenceSegment"),
+                    "TO",
+                    Ref("ColumnReferenceSegment"),
+                ),
+            ),
+        ),
+    )
+
+
+class AlterForeignTableActionSegment(AlterTableActionSegment):
+    """Alter Foreign Table Action Segment.
+
+    https://www.postgresql.org/docs/17/sql-alterforeigntable.html
+    """
+
+    type = "alter_foreign_table_action_segment"
+
+    match_grammar = AlterTableActionSegment.match_grammar.copy(
+        insert=[
+            Sequence(
+                Sequence(
+                    "ALTER",
+                    Ref("COLUMN", optional=True),
+                    Ref("ColumnReferenceSegment"),
+                    optional=True,
+                ),
+                "OPTIONS",
+                Bracketed(
+                    Delimited(
+                        Sequence(
+                            OneOf("ADD", "SET", "DROP", optional=True),
+                            Ref("SingleIdentifierGrammar"),
+                            Ref("QuotedLiteralSegment", optional=True),
+                        )
+                    )
+                ),
+            )
+        ]
+    )
diff --git a/src/sqlfluff/dialects/dialect_tsql.py b/src/sqlfluff/dialects/dialect_tsql.py
index 57b7cf674..c6a541acc 100644
--- a/src/sqlfluff/dialects/dialect_tsql.py
+++ b/src/sqlfluff/dialects/dialect_tsql.py
@@ -2517,6 +2517,7 @@ class ColumnConstraintSegment(BaseSegment):
                             Ref("BareFunctionSegment"),
                             Ref("FunctionSegment"),
                             Ref("NextValueSequenceSegment"),
+                            Ref("HexadecimalLiteralSegment"),
                         ),
                     ),
                 ),
@@ -6308,15 +6309,90 @@ class CreateUserStatementSegment(ansi.CreateUserStatementSegment):
     https://learn.microsoft.com/en-us/sql/t-sql/statements/create-user-transact-sql#syntax
     """
 
+    _allow_encrypted_value = Sequence(
+        "ALLOW_ENCRYPTED_VALUE_MODIFICATIONS",
+        Ref("EqualsSegment"),
+        OneOf("ON", "OFF"),
+    )
+
+    _default_schema = Sequence(
+        "DEFAULT_SCHEMA",
+        Ref("EqualsSegment"),
+        Ref("ObjectReferenceSegment"),
+    )
+
+    _default_language = Sequence(
+        "DEFAULT_LANGUAGE",
+        Ref("EqualsSegment"),
+        Ref("ObjectReferenceSegment"),
+    )
+
+    _external_provider = Sequence(
+        "FROM",
+        "EXTERNAL",
+        "PROVIDER",
+        Sequence(
+            "WITH",
+            "OBJECT_ID",
+            Ref("EqualsSegment"),
+            Ref("QuotedLiteralSegment"),
+            optional=True,
+        ),
+        optional=True,
+    )
+
+    _limited_option_list = Sequence(
+        "WITH",
+        Delimited(
+            _default_schema,
+            _default_language,
+            _allow_encrypted_value,
+        ),
+        optional=True,
+    )
+
+    _options_list = Delimited(
+        _default_schema,
+        _default_language,
+        Sequence(
+            "SID",
+            Ref("EqualsSegment"),
+            Ref("HexadecimalLiteralSegment"),
+        ),
+        _allow_encrypted_value,
+        Sequence(
+            "PASSWORD",
+            Ref("EqualsSegment"),
+            Ref("QuotedLiteralSegment"),
+        ),
+    )
+
     match_grammar = Sequence(
         "CREATE",
         "USER",
         Ref("RoleReferenceSegment"),
-        Sequence(
-            OneOf("FROM", "FOR"),
-            "LOGIN",
-            Ref("ObjectReferenceSegment"),
-            optional=True,
+        AnyNumberOf(
+            Sequence("WITH", _options_list),
+            Sequence(
+                OneOf("FROM", "FOR"),
+                "LOGIN",
+                Ref("ObjectReferenceSegment"),
+                _limited_option_list,
+            ),
+            Sequence(
+                OneOf("FROM", "FOR"),
+                OneOf(
+                    "CERTIFICATE",
+                    Sequence("ASYMMETRIC", "KEY"),
+                ),
+                Ref("ObjectReferenceSegment"),
+            ),
+            Sequence(
+                "WITHOUT",
+                "LOGIN",
+                _limited_option_list,
+            ),
+            _external_provider,
         ),
     )
 
diff --git a/src/sqlfluff/dialects/dialect_tsql_keywords.py b/src/sqlfluff/dialects/dialect_tsql_keywords.py
index b6e59bd99..3fb017ffa 100644
--- a/src/sqlfluff/dialects/dialect_tsql_keywords.py
+++ b/src/sqlfluff/dialects/dialect_tsql_keywords.py
@@ -254,6 +254,7 @@ UNRESERVED_KEYWORDS = [
     "ACTION",
     "AFTER",
     "ALGORITHM",
+    "ALLOW_ENCRYPTED_VALUE_MODIFICATIONS",
     "ALLOW_PAGE_LOCKS",
     "ALLOW_ROW_LOCKS",
     "ALLOWED",
@@ -321,6 +322,7 @@ UNRESERVED_KEYWORDS = [
     "DEADLOCK_PRIORITY",
     "DEFAULT_DATABASE",
     "DEFAULT_LANGUAGE",
+    "DEFAULT_SCHEMA",
     "DELAY",
     "DELAYED_DURABILITY",
     "DELIMITEDTEXT",
@@ -642,6 +644,7 @@ UNRESERVED_KEYWORDS = [
     "WHILE",
     "WINDOWS",
     "WITHIN",
+    "WITHOUT",
     "WITHOUT_ARRAY_WRAPPER",
     "WORK",
     "XACT_ABORT",


Test patch:
diff --git a/test/core/templaters/jinja_test.py b/test/core/templaters/jinja_test.py
index 10fd21613..5711a4d37 100644
--- a/test/core/templaters/jinja_test.py
+++ b/test/core/templaters/jinja_test.py
@@ -706,6 +706,8 @@ def assert_structure(yaml_loader, path, code_only=True, include_meta=False):
         ("jinja_c_dbt/dbt_builtins_this_callable", True, False),
         ("jinja_c_dbt/dbt_builtins_var_default", True, False),
         ("jinja_c_dbt/dbt_builtins_test", True, False),
+        ("jinja_c_dbt/dbt_builtins_zip", True, False),
+        ("jinja_c_dbt/dbt_builtins_zip_strict", True, False),
         # do directive
         ("jinja_e/jinja", True, False),
         # case sensitivity and python literals
diff --git a/test/fixtures/dialects/postgres/alter_foreign_table.sql b/test/fixtures/dialects/postgres/alter_foreign_table.sql
new file mode 100644
index 000000000..8fa9313a5
--- /dev/null
+++ b/test/fixtures/dialects/postgres/alter_foreign_table.sql
@@ -0,0 +1,11 @@
+ALTER FOREIGN TABLE distributors ALTER COLUMN street SET NOT NULL;
+
+ALTER FOREIGN TABLE t_user ADD COLUMN my_column text;
+
+ALTER TABLE bar_fdw.foo ADD test varchar NULL;
+
+ALTER FOREIGN TABLE myschema.distributors OPTIONS (ADD opt1 'value', SET opt2 'value2', DROP opt3);
+
+ALTER FOREIGN TABLE test OPTIONS (SET table $$(select my_column from my_table)$$);
+
+ALTER FOREIGN TABLE test ADD COLUMN new_column int8, OPTIONS (SET table $$(select my_column from my_table)$$);
diff --git a/test/fixtures/dialects/postgres/alter_foreign_table.yml b/test/fixtures/dialects/postgres/alter_foreign_table.yml
new file mode 100644
index 000000000..3afad314c
--- /dev/null
+++ b/test/fixtures/dialects/postgres/alter_foreign_table.yml
@@ -0,0 +1,120 @@
+# YML test files are auto-generated from SQL files and should not be edited by
+# hand. To help enforce this, the "hash" field in the file must match a hash
+# computed by SQLFluff when running the tests. Please run
+# `python test/generate_parse_fixture_yml.py`  to generate them after adding or
+# altering SQL files.
+_hash: fb422bdb80c2368243abf4fbb79459993e69af838ddeb700d926c046c16da370
+file:
+- statement:
+    alter_foreign_table_statement:
+    - keyword: ALTER
+    - keyword: FOREIGN
+    - keyword: TABLE
+    - table_reference:
+        naked_identifier: distributors
+    - alter_foreign_table_action_segment:
+      - keyword: ALTER
+      - keyword: COLUMN
+      - column_reference:
+          naked_identifier: street
+      - keyword: SET
+      - keyword: NOT
+      - keyword: 'NULL'
+- statement_terminator: ;
+- statement:
+    alter_foreign_table_statement:
+    - keyword: ALTER
+    - keyword: FOREIGN
+    - keyword: TABLE
+    - table_reference:
+        naked_identifier: t_user
+    - alter_foreign_table_action_segment:
+      - keyword: ADD
+      - keyword: COLUMN
+      - column_reference:
+          naked_identifier: my_column
+      - data_type:
+          keyword: text
+- statement_terminator: ;
+- statement:
+    alter_table_statement:
+    - keyword: ALTER
+    - keyword: TABLE
+    - table_reference:
+      - naked_identifier: bar_fdw
+      - dot: .
+      - naked_identifier: foo
+    - alter_table_action_segment:
+        keyword: ADD
+        column_reference:
+          naked_identifier: test
+        data_type:
+          keyword: varchar
+        column_constraint_segment:
+          keyword: 'NULL'
+- statement_terminator: ;
+- statement:
+    alter_foreign_table_statement:
+    - keyword: ALTER
+    - keyword: FOREIGN
+    - keyword: TABLE
+    - table_reference:
+      - naked_identifier: myschema
+      - dot: .
+      - naked_identifier: distributors
+    - alter_foreign_table_action_segment:
+        keyword: OPTIONS
+        bracketed:
+        - start_bracket: (
+        - keyword: ADD
+        - naked_identifier: opt1
+        - quoted_literal: "'value'"
+        - comma: ','
+        - keyword: SET
+        - naked_identifier: opt2
+        - quoted_literal: "'value2'"
+        - comma: ','
+        - keyword: DROP
+        - naked_identifier: opt3
+        - end_bracket: )
+- statement_terminator: ;
+- statement:
+    alter_foreign_table_statement:
+    - keyword: ALTER
+    - keyword: FOREIGN
+    - keyword: TABLE
+    - table_reference:
+        naked_identifier: test
+    - alter_foreign_table_action_segment:
+        keyword: OPTIONS
+        bracketed:
+          start_bracket: (
+          keyword: SET
+          naked_identifier: table
+          quoted_literal: $$(select my_column from my_table)$$
+          end_bracket: )
+- statement_terminator: ;
+- statement:
+    alter_foreign_table_statement:
+    - keyword: ALTER
+    - keyword: FOREIGN
+    - keyword: TABLE
+    - table_reference:
+        naked_identifier: test
+    - alter_foreign_table_action_segment:
+      - keyword: ADD
+      - keyword: COLUMN
+      - column_reference:
+          naked_identifier: new_column
+      - data_type:
+          keyword: int8
+    - comma: ','
+    - alter_foreign_table_action_segment:
+        keyword: OPTIONS
+        bracketed:
+          start_bracket: (
+          keyword: SET
+          naked_identifier: table
+          quoted_literal: $$(select my_column from my_table)$$
+          end_bracket: )
+- statement_terminator: ;
diff --git a/test/fixtures/dialects/postgres/meta_commands_query_buffer.sql b/test/fixtures/dialects/postgres/meta_commands_query_buffer.sql
new file mode 100644
index 000000000..555263462
--- /dev/null
+++ b/test/fixtures/dialects/postgres/meta_commands_query_buffer.sql
@@ -0,0 +1,23 @@
+SELECT format('create index on my_table(%I)', attname)
+FROM pg_attribute
+WHERE attrelid = 'my_table'::regclass AND attnum > 0
+ORDER BY attnum
+\gexec
+
+SELECT 'hello' AS var1, 10 AS var2
+\gset
+
+SELECT 'hello' AS var1, 10 AS var2
+\gset result_
+
+SELECT
+    EXISTS(SELECT 1 FROM customer WHERE customer_id = 123) as is_customer,
+    EXISTS(SELECT 1 FROM employee WHERE employee_id = 456) as is_employee
+\gset
+
+SELECT 'hello' AS my_psql_var \gset
+SELECT :'my_psql_var';
+
+SELECT relname, relkind FROM pg_class LIMIT 1 \gset
+
+SELECT i FROM generate_series(1,2) i \gset prefix
diff --git a/test/fixtures/dialects/postgres/meta_commands_query_buffer.yml b/test/fixtures/dialects/postgres/meta_commands_query_buffer.yml
new file mode 100644
index 000000000..4c5aa71bc
--- /dev/null
+++ b/test/fixtures/dialects/postgres/meta_commands_query_buffer.yml
@@ -0,0 +1,222 @@
+# YML test files are auto-generated from SQL files and should not be edited by
+# hand. To help enforce this, the "hash" field in the file must match a hash
+# computed by SQLFluff when running the tests. Please run
+# `python test/generate_parse_fixture_yml.py`  to generate them after adding or
+# altering SQL files.
+_hash: 2bb502774fdc8feebe5b1079893948028e617d599b976164d1c594928eb13ec9
+file:
+- statement:
+    meta_command_statement:
+    - select_statement:
+        select_clause:
+          keyword: SELECT
+          select_clause_element:
+            function:
+              function_name:
+                function_name_identifier: format
+              function_contents:
+                bracketed:
+                - start_bracket: (
+                - expression:
+                    quoted_literal: "'create index on my_table(%I)'"
+                - comma: ','
+                - expression:
+                    column_reference:
+                      naked_identifier: attname
+                - end_bracket: )
+        from_clause:
+          keyword: FROM
+          from_expression:
+            from_expression_element:
+              table_expression:
+                table_reference:
+                  naked_identifier: pg_attribute
+        where_clause:
+          keyword: WHERE
+          expression:
+          - column_reference:
+              naked_identifier: attrelid
+          - comparison_operator:
+              raw_comparison_operator: '='
+          - cast_expression:
+              quoted_literal: "'my_table'"
+              casting_operator: '::'
+              data_type:
+                data_type_identifier: regclass
+          - binary_operator: AND
+          - column_reference:
+              naked_identifier: attnum
+          - comparison_operator:
+              raw_comparison_operator: '>'
+          - numeric_literal: '0'
+        orderby_clause:
+        - keyword: ORDER
+        - keyword: BY
+        - column_reference:
+            naked_identifier: attnum
+    - meta_command: \gexec
+    - select_statement:
+        select_clause:
+        - keyword: SELECT
+        - select_clause_element:
+            quoted_literal: "'hello'"
+            alias_expression:
+              keyword: AS
+              naked_identifier: var1
+        - comma: ','
+        - select_clause_element:
+            numeric_literal: '10'
+            alias_expression:
+              keyword: AS
+              naked_identifier: var2
+    - meta_command: \gset
+    - select_statement:
+        select_clause:
+        - keyword: SELECT
+        - select_clause_element:
+            quoted_literal: "'hello'"
+            alias_expression:
+              keyword: AS
+              naked_identifier: var1
+        - comma: ','
+        - select_clause_element:
+            numeric_literal: '10'
+            alias_expression:
+              keyword: AS
+              naked_identifier: var2
+    - meta_command: \gset result_
+    - select_statement:
+        select_clause:
+        - keyword: SELECT
+        - select_clause_element:
+            function:
+              function_name:
+                function_name_identifier: EXISTS
+              function_contents:
+                bracketed:
+                  start_bracket: (
+                  expression:
+                    select_statement:
+                      select_clause:
+                        keyword: SELECT
+                        select_clause_element:
+                          numeric_literal: '1'
+                      from_clause:
+                        keyword: FROM
+                        from_expression:
+                          from_expression_element:
+                            table_expression:
+                              table_reference:
+                                naked_identifier: customer
+                      where_clause:
+                        keyword: WHERE
+                        expression:
+                          column_reference:
+                            naked_identifier: customer_id
+                          comparison_operator:
+                            raw_comparison_operator: '='
+                          numeric_literal: '123'
+                  end_bracket: )
+            alias_expression:
+              keyword: as
+              naked_identifier: is_customer
+        - comma: ','
+        - select_clause_element:
+            function:
+              function_name:
+                function_name_identifier: EXISTS
+              function_contents:
+                bracketed:
+                  start_bracket: (
+                  expression:
+                    select_statement:
+                      select_clause:
+                        keyword: SELECT
+                        select_clause_element:
+                          numeric_literal: '1'
+                      from_clause:
+                        keyword: FROM
+                        from_expression:
+                          from_expression_element:
+                            table_expression:
+                              table_reference:
+                                naked_identifier: employee
+                      where_clause:
+                        keyword: WHERE
+                        expression:
+                          column_reference:
+                            naked_identifier: employee_id
+                          comparison_operator:
+                            raw_comparison_operator: '='
+                          numeric_literal: '456'
+                  end_bracket: )
+            alias_expression:
+              keyword: as
+              naked_identifier: is_employee
+    - meta_command: \gset
+    - select_statement:
+        select_clause:
+          keyword: SELECT
+          select_clause_element:
+            quoted_literal: "'hello'"
+            alias_expression:
+              keyword: AS
+              naked_identifier: my_psql_var
+    - meta_command: \gset
+    - select_statement:
+        select_clause:
+          keyword: SELECT
+          select_clause_element:
+            psql_variable:
+              colon: ':'
+              quoted_literal: "'my_psql_var'"
+- statement_terminator: ;
+- statement:
+    meta_command_statement:
+    - select_statement:
+        select_clause:
+        - keyword: SELECT
+        - select_clause_element:
+            column_reference:
+              naked_identifier: relname
+        - comma: ','
+        - select_clause_element:
+            column_reference:
+              naked_identifier: relkind
+        from_clause:
+          keyword: FROM
+          from_expression:
+            from_expression_element:
+              table_expression:
+                table_reference:
+                  naked_identifier: pg_class
+        limit_clause:
+          keyword: LIMIT
+          numeric_literal: '1'
+    - meta_command: \gset
+    - select_statement:
+        select_clause:
+          keyword: SELECT
+          select_clause_element:
+            column_reference:
+              naked_identifier: i
+        from_clause:
+          keyword: FROM
+          from_expression:
+            from_expression_element:
+              table_expression:
+                function:
+                  function_name:
+                    function_name_identifier: generate_series
+                  function_contents:
+                    bracketed:
+                    - start_bracket: (
+                    - expression:
+                        numeric_literal: '1'
+                    - comma: ','
+                    - expression:
+                        numeric_literal: '2'
+                    - end_bracket: )
+              alias_expression:
+                naked_identifier: i
+    - meta_command: \gset prefix
diff --git a/test/fixtures/dialects/tsql/alter_table.sql b/test/fixtures/dialects/tsql/alter_table.sql
index f77dd87ea..8b8bd3d25 100644
--- a/test/fixtures/dialects/tsql/alter_table.sql
+++ b/test/fixtures/dialects/tsql/alter_table.sql
@@ -118,5 +118,21 @@ REFERENCES [HangFire].[Job] ([Id])
 ON UPDATE CASCADE
 ON DELETE CASCADE; GO
 
+-- Drop multiple columns in one statement
+ALTER TABLE UserData DROP COLUMN [StrSkill], [StrItem], [StrSerial];
+ALTER TABLE UserData DROP COLUMN IF EXISTS StrSkill, StrItem, StrSerial;
+
+-- Check hexadecimal defaults in constraints
+CREATE TABLE [dbo].[UserData] (
+    [strUserId] [char](21) NOT NULL,
+    [strItem] [binary](400) NULL,
+    [strSkill] [binary](400) NULL,
+    CONSTRAINT PK_UserData PRIMARY KEY CLUSTERED ([strUserId] ASC)
+);
+
+ALTER TABLE [dbo].[UserData]
+ADD CONSTRAINT [DF_UserData_strSkill] DEFAULT (0x00) FOR [strSkill];
+GO
+
 ALTER TABLE [TestTable] DROP PERIOD FOR SYSTEM_TIME;
 ALTER TABLE [TestTable] ADD PERIOD FOR SYSTEM_TIME (StartDate, EndDate);
diff --git a/test/fixtures/dialects/tsql/alter_table.yml b/test/fixtures/dialects/tsql/alter_table.yml
index ff6aa391f..7532d4c0d 100644
--- a/test/fixtures/dialects/tsql/alter_table.yml
+++ b/test/fixtures/dialects/tsql/alter_table.yml
@@ -3,7 +3,7 @@
 # computed by SQLFluff when running the tests. Please run
 # `python test/generate_parse_fixture_yml.py`  to generate them after adding or
 # altering SQL files.
-_hash: 35a8de48837d0ff07dad6ea1a570638073a47a6d0fa6b3ab1102bd7ee0b0c407
+_hash: 2a0d700bd5dc5418ed964e623a378ff5053d0abcadd537d2e9fb58905c6321c0
 file:
 - batch:
     statement:
@@ -943,6 +943,133 @@ file:
     statement_terminator: ;
 - go_statement:
     keyword: GO
+- batch:
+  - statement:
+      alter_table_statement:
+      - keyword: ALTER
+      - keyword: TABLE
+      - table_reference:
+          naked_identifier: UserData
+      - keyword: DROP
+      - keyword: COLUMN
+      - column_reference:
+          quoted_identifier: '[StrSkill]'
+      - comma: ','
+      - column_reference:
+          quoted_identifier: '[StrItem]'
+      - comma: ','
+      - column_reference:
+          quoted_identifier: '[StrSerial]'
+  - statement_terminator: ;
+  - statement:
+      alter_table_statement:
+      - keyword: ALTER
+      - keyword: TABLE
+      - table_reference:
+          naked_identifier: UserData
+      - keyword: DROP
+      - keyword: COLUMN
+      - keyword: IF
+      - keyword: EXISTS
+      - column_reference:
+          naked_identifier: StrSkill
+      - comma: ','
+      - column_reference:
+          naked_identifier: StrItem
+      - comma: ','
+      - column_reference:
+          naked_identifier: StrSerial
+  - statement_terminator: ;
+  - statement:
+      create_table_statement:
+      - keyword: CREATE
+      - keyword: TABLE
+      - table_reference:
+        - quoted_identifier: '[dbo]'
+        - dot: .
+        - quoted_identifier: '[UserData]'
+      - bracketed:
+        - start_bracket: (
+        - column_definition:
+            quoted_identifier: '[strUserId]'
+            data_type:
+              data_type_identifier: '[char]'
+              bracketed_arguments:
+                bracketed:
+                  start_bracket: (
+                  expression:
+                    numeric_literal: '21'
+                  end_bracket: )
+            column_constraint_segment:
+            - keyword: NOT
+            - keyword: 'NULL'
+        - comma: ','
+        - column_definition:
+            quoted_identifier: '[strItem]'
+            data_type:
+              data_type_identifier: '[binary]'
+              bracketed_arguments:
+                bracketed:
+                  start_bracket: (
+                  expression:
+                    numeric_literal: '400'
+                  end_bracket: )
+            column_constraint_segment:
+              keyword: 'NULL'
+        - comma: ','
+        - column_definition:
+            quoted_identifier: '[strSkill]'
+            data_type:
+              data_type_identifier: '[binary]'
+              bracketed_arguments:
+                bracketed:
+                  start_bracket: (
+                  expression:
+                    numeric_literal: '400'
+                  end_bracket: )
+            column_constraint_segment:
+              keyword: 'NULL'
+        - comma: ','
+        - table_constraint:
+          - keyword: CONSTRAINT
+          - object_reference:
+              naked_identifier: PK_UserData
+          - keyword: PRIMARY
+          - keyword: KEY
+          - keyword: CLUSTERED
+          - bracketed_index_column_list_grammar:
+              bracketed:
+                start_bracket: (
+                index_column_definition:
+                  quoted_identifier: '[strUserId]'
+                  keyword: ASC
+                end_bracket: )
+        - end_bracket: )
+      - statement_terminator: ;
+  - statement:
+      alter_table_statement:
+      - keyword: ALTER
+      - keyword: TABLE
+      - table_reference:
+        - quoted_identifier: '[dbo]'
+        - dot: .
+        - quoted_identifier: '[UserData]'
+      - keyword: ADD
+      - column_constraint_segment:
+        - keyword: CONSTRAINT
+        - object_reference:
+            quoted_identifier: '[DF_UserData_strSkill]'
+        - keyword: DEFAULT
+        - bracketed:
+            start_bracket: (
+            numeric_literal: '0x00'
+            end_bracket: )
+      - keyword: FOR
+      - column_reference:
+          quoted_identifier: '[strSkill]'
+  - statement_terminator: ;
+- go_statement:
+    keyword: GO
 - batch:
   - statement:
       alter_table_statement:
diff --git a/test/fixtures/dialects/tsql/create_user.sql b/test/fixtures/dialects/tsql/create_user.sql
index 35a58f060..7736577fd 100644
--- a/test/fixtures/dialects/tsql/create_user.sql
+++ b/test/fixtures/dialects/tsql/create_user.sql
@@ -1,3 +1,28 @@
 CREATE USER user_name FOR LOGIN login_name;
 
 CREATE USER user_name FROM LOGIN login_name;
+
+CREATE USER [TestUser];
+
+CREATE USER Barry WITH PASSWORD = 'sdjklalie8rew8337!$d';
+
+CREATE USER [TestUser] FROM EXTERNAL PROVIDER;
+
+CREATE USER RIGHTSHOLDER WITHOUT LOGIN;
+
+CREATE USER RIGHTSHOLDER WITHOUT LOGIN
+WITH DEFAULT_SCHEMA = Other;
+
+CREATE USER [Barry]
+WITH ALLOW_ENCRYPTED_VALUE_MODIFICATIONS = ON;
+
+CREATE USER Wanida FOR LOGIN WanidaBenshoof
+WITH DEFAULT_SCHEMA = Marketing;
+
+CREATE USER Wanida FOR LOGIN WanidaSmith
+WITH DEFAULT_SCHEMA = Tech,
+ALLOW_ENCRYPTED_VALUE_MODIFICATIONS = ON;
+
+CREATE USER JinghaoLiu FOR CERTIFICATE CarnationProduction50;
+
+CREATE USER JinghaoLiu FOR ASYMMETRIC KEY CarnationProduction;
diff --git a/test/fixtures/dialects/tsql/create_user.yml b/test/fixtures/dialects/tsql/create_user.yml
index e702f1e42..530739105 100644
--- a/test/fixtures/dialects/tsql/create_user.yml
+++ b/test/fixtures/dialects/tsql/create_user.yml
@@ -3,7 +3,7 @@
 # computed by SQLFluff when running the tests. Please run
 # `python test/generate_parse_fixture_yml.py`  to generate them after adding or
 # altering SQL files.
-_hash: 2b4a6e7caaa3fefb8ef6ede5afddef853bd50bb50400557ed71264a39df1f4e0
+_hash: 6ad02fea04cd5bcde25f874297d50f480ddede6878e37c38dd6ebf53f59a08bd
 file:
   batch:
   - statement:
@@ -28,3 +28,130 @@ file:
       - object_reference:
           naked_identifier: login_name
   - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          quoted_identifier: '[TestUser]'
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: Barry
+      - keyword: WITH
+      - keyword: PASSWORD
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - quoted_literal: "'sdjklalie8rew8337!$d'"
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          quoted_identifier: '[TestUser]'
+      - keyword: FROM
+      - keyword: EXTERNAL
+      - keyword: PROVIDER
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: RIGHTSHOLDER
+      - keyword: WITHOUT
+      - keyword: LOGIN
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: RIGHTSHOLDER
+      - keyword: WITHOUT
+      - keyword: LOGIN
+      - keyword: WITH
+      - keyword: DEFAULT_SCHEMA
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - object_reference:
+          naked_identifier: Other
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          quoted_identifier: '[Barry]'
+      - keyword: WITH
+      - keyword: ALLOW_ENCRYPTED_VALUE_MODIFICATIONS
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - keyword: 'ON'
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: Wanida
+      - keyword: FOR
+      - keyword: LOGIN
+      - object_reference:
+          naked_identifier: WanidaBenshoof
+      - keyword: WITH
+      - keyword: DEFAULT_SCHEMA
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - object_reference:
+          naked_identifier: Marketing
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: Wanida
+      - keyword: FOR
+      - keyword: LOGIN
+      - object_reference:
+          naked_identifier: WanidaSmith
+      - keyword: WITH
+      - keyword: DEFAULT_SCHEMA
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - object_reference:
+          naked_identifier: Tech
+      - comma: ','
+      - keyword: ALLOW_ENCRYPTED_VALUE_MODIFICATIONS
+      - comparison_operator:
+          raw_comparison_operator: '='
+      - keyword: 'ON'
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: JinghaoLiu
+      - keyword: FOR
+      - keyword: CERTIFICATE
+      - object_reference:
+          naked_identifier: CarnationProduction50
+  - statement_terminator: ;
+  - statement:
+      create_user_statement:
+      - keyword: CREATE
+      - keyword: USER
+      - role_reference:
+          naked_identifier: JinghaoLiu
+      - keyword: FOR
+      - keyword: ASYMMETRIC
+      - keyword: KEY
+      - object_reference:
+          naked_identifier: CarnationProduction
+  - statement_terminator: ;
diff --git a/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.sql b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.sql
new file mode 100644
index 000000000..7c597bf9d
--- /dev/null
+++ b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.sql
@@ -0,0 +1,6 @@
+{% set not_iterable = 1 %}
+{% set iterable = ['a1', 'a2'] %}
+{% set result = zip(not_iterable, iterable) %}
+
+select
+  {{ result }}
diff --git a/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.yml b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.yml
new file mode 100644
index 000000000..bf2c038f5
--- /dev/null
+++ b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip.yml
@@ -0,0 +1,8 @@
+file:
+  - statement:
+      select_statement:
+        select_clause:
+          keyword: select
+          select_clause_element:
+            column_reference:
+              naked_identifier: None
diff --git a/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.sql b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.sql
new file mode 100644
index 000000000..3d6a2497e
--- /dev/null
+++ b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.sql
@@ -0,0 +1,7 @@
+{% set cols = ['c1', 'c2'] %}
+{% set aliases = ['a1', 'a2'] %}
+
+select
+{% for (col, alias) in zip_strict(cols, aliases) %}
+  {{ col }} as {{ alias }}{% if not loop.last %},{% endif %}
+{% endfor %}
diff --git a/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.yml b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.yml
new file mode 100644
index 000000000..e38041702
--- /dev/null
+++ b/test/fixtures/templater/jinja_c_dbt/dbt_builtins_zip_strict.yml
@@ -0,0 +1,18 @@
+file:
+  - statement:
+      select_statement:
+        select_clause:
+        - keyword: select
+        - select_clause_element:
+            column_reference:
+              naked_identifier: c1
+            alias_expression:
+              keyword: as
+              naked_identifier: a1
+        - comma: ','
+        - select_clause_element:
+            column_reference:
+              naked_identifier: c2
+            alias_expression:
+              keyword: as
+              naked_identifier: a2