Repository: sqlfluff/sqlfluff

Issue:
Clean up AliasedTableReferenceSegment grammar
This is a tech debt issue; nothing of concern to users.

In working on #865, Niall and I noticed something a bit odd about `AliasedTableReferenceSegment`. In order to add support for using table aliases in `UPDATE` statements, I had changed `UpdateStatementSegment` from:

```
Ref("TableReferenceSegment")
```

to:

```
Ref("AliasedTableReferenceSegment")
```

This worked, and all tests (new and old) passed because the match grammar for `AliasedTableReferenceSegment` specifies that the alias part is optional:

```
Ref("TableReferenceSegment"), Ref("AliasExpressionSegment", optional=True)
```

But... this resulted in the parse tree looking odd. (Note the nested `table_reference` nodes.)
```
    update_statement:
      keyword: UPDATE
      table_reference:
        table_reference:
          identifier: table_name
```

To avoid this, we updated my change to instead be:
```
OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceSegment"))
```

This avoids the nesting, but it'd be better if we didn't have to specify `OneOf()` to make the parse tree look reasonable.  `AliasedTableReferenceSegment` supports the exact SQL syntax we want. Can we refactor the ANSI dialect a bit so the parse tree looks okay if we do this?

Another (more minor) question: The name `AliasedTableReferenceSegment` suggests that there's an alias present, but it's actually optional. Should we consider renaming it to make this clearer?


Discussion:

Patch:
diff --git a/src/sqlfluff/dialects/dialect_ansi.py b/src/sqlfluff/dialects/dialect_ansi.py
index 4182116a3..e206ba18c 100644
--- a/src/sqlfluff/dialects/dialect_ansi.py
+++ b/src/sqlfluff/dialects/dialect_ansi.py
@@ -609,14 +609,12 @@ class AliasedObjectReferenceSegment(BaseSegment):
     )
 
 
-@ansi_dialect.segment()
-class AliasedTableReferenceSegment(BaseSegment):
-    """A reference to a table with an `AS` clause."""
-
-    type = "table_reference"
-    match_grammar = Sequence(
-        Ref("TableReferenceSegment"), Ref("AliasExpressionSegment", optional=True)
+ansi_dialect.add(
+    # This is a hook point to allow subclassing for other dialects
+    AliasedTableReferenceGrammar=Sequence(
+        Ref("TableReferenceSegment"), Ref("AliasExpressionSegment")
     )
+)
 
 
 @ansi_dialect.segment()
@@ -2220,7 +2218,7 @@ class UpdateStatementSegment(BaseSegment):
     match_grammar = StartsWith("UPDATE")
     parse_grammar = Sequence(
         "UPDATE",
-        OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceSegment")),
+        OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceGrammar")),
         Ref("SetClauseListSegment"),
         Ref("FromClauseSegment", optional=True),
         Ref("WhereClauseSegment", optional=True),
diff --git a/src/sqlfluff/dialects/dialect_exasol.py b/src/sqlfluff/dialects/dialect_exasol.py
index b5ec40462..d39fa1685 100644
--- a/src/sqlfluff/dialects/dialect_exasol.py
+++ b/src/sqlfluff/dialects/dialect_exasol.py
@@ -1322,7 +1322,7 @@ class UpdateStatementSegment(BaseSegment):
     match_grammar = StartsWith("UPDATE")
     parse_grammar = Sequence(
         "UPDATE",
-        Ref("AliasedTableReferenceSegment"),
+        OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceGrammar")),
         Ref("SetClauseListSegment"),
         Ref("UpdateFromClauseSegment", optional=True),
         Ref("WhereClauseSegment", optional=True),
@@ -1375,7 +1375,7 @@ class UpdateFromClauseSegment(BaseSegment):
     match_grammar = Sequence(
         "FROM",
         Delimited(
-            Ref("AliasedTableReferenceSegment"),
+            OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceGrammar")),
             terminator="WHERE",
         ),
     )
@@ -1404,7 +1404,7 @@ class MergeStatementSegment(BaseSegment):
     parse_grammar = Sequence(
         "MERGE",
         "INTO",
-        Ref("AliasedTableReferenceSegment"),
+        OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceGrammar")),
         "USING",
         OneOf(
             Ref("TableReferenceSegment"),  # tables/views
@@ -1527,7 +1527,7 @@ class DeleteStatementSegment(BaseSegment):
         "DELETE",
         Ref("StarSegment", optional=True),
         "FROM",
-        Ref("AliasedTableReferenceSegment"),
+        OneOf(Ref("TableReferenceSegment"), Ref("AliasedTableReferenceGrammar")),
         Ref("WhereClauseSegment", optional=True),
         Ref("PreferringClauseSegment", optional=True),
     )


Test patch:
diff --git a/test/fixtures/parser/ansi/describe_table.yml b/test/fixtures/parser/ansi/describe_table.yml
index c0c1a21df..1706eafd9 100644
--- a/test/fixtures/parser/ansi/describe_table.yml
+++ b/test/fixtures/parser/ansi/describe_table.yml
@@ -1,8 +1,8 @@
 file:
   statement:
     describe_statement:
-    - keyword: describe
-    - identifier: table
-    - object_reference:
+      keyword: describe
+      identifier: table
+      object_reference:
         identifier: '"my_table"'
   statement_terminator: ;
diff --git a/test/fixtures/parser/ansi/update_with_table_alias.yml b/test/fixtures/parser/ansi/update_with_table_alias.yml
index 6ed8de8be..1b17da063 100644
--- a/test/fixtures/parser/ansi/update_with_table_alias.yml
+++ b/test/fixtures/parser/ansi/update_with_table_alias.yml
@@ -3,11 +3,10 @@ file:
     update_statement:
       keyword: UPDATE
       table_reference:
-        table_reference:
-          identifier: my_table
-        alias_expression:
-          keyword: AS
-          identifier: tttd
+        identifier: my_table
+      alias_expression:
+        keyword: AS
+        identifier: tttd
       set_clause_list:
         keyword: SET
         set_clause:
diff --git a/test/fixtures/parser/mysql/create_table.yml b/test/fixtures/parser/mysql/create_table.yml
index 0e3a90302..04552fc89 100644
--- a/test/fixtures/parser/mysql/create_table.yml
+++ b/test/fixtures/parser/mysql/create_table.yml
@@ -1,43 +1,43 @@
-file:
-  statement:
-    create_table_statement:
-    - keyword: CREATE
-    - keyword: TABLE
-    - table_reference:
-        identifier: '`foo`'
-    - start_bracket: (
-    - column_definition:
-      - identifier: '`id`'
-      - data_type:
-          data_type_identifier: int
-          start_bracket: (
-          expression:
-            literal: '11'
-          end_bracket: )
-      - column_constraint:
-          keyword: unsigned
-      - column_constraint:
-        - keyword: NOT
-        - keyword: 'NULL'
-      - column_constraint:
-          keyword: AUTO_INCREMENT
-    - comma: ','
-    - table_constraint_definition:
-      - keyword: PRIMARY
-      - keyword: KEY
-      - start_bracket: (
-      - column_reference:
-          identifier: '`id`'
-      - end_bracket: )
-    - end_bracket: )
-    - parameter: ENGINE
-    - comparison_operator: '='
-    - parameter: InnoDB
-    - keyword: DEFAULT
-    - parameter: CHARSET
-    - comparison_operator: '='
-    - parameter: utf8mb4
-    - parameter: COLLATE
-    - comparison_operator: '='
-    - parameter: utf8mb4_unicode_ci
-  statement_terminator: ;
\ No newline at end of file
+file:
+  statement:
+    create_table_statement:
+    - keyword: CREATE
+    - keyword: TABLE
+    - table_reference:
+        identifier: '`foo`'
+    - start_bracket: (
+    - column_definition:
+      - identifier: '`id`'
+      - data_type:
+          data_type_identifier: int
+          start_bracket: (
+          expression:
+            literal: '11'
+          end_bracket: )
+      - column_constraint:
+          keyword: unsigned
+      - column_constraint:
+        - keyword: NOT
+        - keyword: 'NULL'
+      - column_constraint:
+          keyword: AUTO_INCREMENT
+    - comma: ','
+    - table_constraint_definition:
+      - keyword: PRIMARY
+      - keyword: KEY
+      - start_bracket: (
+      - column_reference:
+          identifier: '`id`'
+      - end_bracket: )
+    - end_bracket: )
+    - parameter: ENGINE
+    - comparison_operator: '='
+    - parameter: InnoDB
+    - keyword: DEFAULT
+    - parameter: CHARSET
+    - comparison_operator: '='
+    - parameter: utf8mb4
+    - parameter: COLLATE
+    - comparison_operator: '='
+    - parameter: utf8mb4_unicode_ci
+  statement_terminator: ;
diff --git a/test/fixtures/parser/snowflake/snowflake_describe_user.yml b/test/fixtures/parser/snowflake/snowflake_describe_user.yml
index c53754168..5cee8e8a4 100644
--- a/test/fixtures/parser/snowflake/snowflake_describe_user.yml
+++ b/test/fixtures/parser/snowflake/snowflake_describe_user.yml
@@ -1,8 +1,8 @@
 file:
   statement:
     describe_statement:
-    - keyword: describe
-    - identifier: user
-    - object_reference:
+      keyword: describe
+      identifier: user
+      object_reference:
         identifier: my_user
   statement_terminator: ;