Repository: sqlfluff/sqlfluff
Pull request: Exclude EMIT clauses from rule L013
<!--Firstly, thanks for adding this feature! Secondly, please check the key steps against the checklist below to make your contribution easy to merge.-->

<!--Please give the Pull Request a meaningful title (including the dialect this PR is for if it is dialect specific), as this will automatically be added to the release notes, and then the Change Log.-->

### Brief summary of the change made
<!--If there is an open issue for this, then please include `fixes #XXXX` or `closes #XXXX` replacing `XXXX` with the issue number and it will automatically close the issue when the pull request is merged. Alternatively if not fully closed you can say `makes progress on #XXXX` to create a link on that issue without closing it.-->
Fixes #2361 

### Are there any other side effects of this change that we should be aware of?
No

### Pull Request checklist
- [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/dialects` (note YML files can be auto generated with `tox -e generate-fixture-yml`).
  - 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:
EXASOL: L013 wrong result when using json_extract
<!--If this is a parsing or linting issue, please include a minimal SQL example which reproduces the issue, along with the `sqlfluff parse` output, `sqlfluff lint` output and `sqlfluff fix` output when relevant.-->

In Exasol we could use the `json_extract()` function to extract multiple values out of a json string. The `emits` clause defines how the values are exposed. In such a case its not possible to use a alias.

## Expected Behaviour
L013 should not raise when using `json_extract()`

## Observed Behaviour
L013 raises.

## Steps to Reproduce
```sql
select
    somecol,
    json_extract(json_str, '$.AFIELD', '$.BFIELD') emits (cola char(1), colb char(1))
from abc;
```

## Dialect
Exasol

## Version
Include the output of `sqlfluff --version` along with your Python version.
If you are using dbt then please additionally include the dbt version.

sqlfluff v0.9.1

## Configuration
```
Include your SQLFluff configuration here
```

Hints from issue discussion:

Commit message: More typos

Patch to review:
diff --git a/src/sqlfluff/dialects/dialect_exasol.py b/src/sqlfluff/dialects/dialect_exasol.py
index 3c6c1e86c39..7cda7a1d41c 100644
--- a/src/sqlfluff/dialects/dialect_exasol.py
+++ b/src/sqlfluff/dialects/dialect_exasol.py
@@ -194,10 +194,6 @@
         Delimited(Ref("ColumnDatatypeSegment")),
         Ref("UDFParameterDotSyntaxSegment"),
     ),
-    EmitsGrammar=Sequence(
-        "EMITS",
-        Bracketed(Ref("UDFParameterGrammar")),
-    ),
     FunctionScriptTerminatorSegment=NamedParser(
         "function_script_terminator", CodeSegment, type="function_script_terminator"
     ),
@@ -3770,3 +3766,17 @@ class FileSegment(BaseFileSegment):
             allow_trailing=True,
         ),
     )
+
+
+@exasol_dialect.segment()
+class EmitsGrammar(BaseFileSegment):
+    """EMITS Grammar for JSON_EXTRACT for example.
+
+    In it's own class to give it a type to allow L013 to find it easily.
+    """
+
+    type = "emits_grammar"
+    match_grammar = Sequence(
+        "EMITS",
+        Bracketed(Ref("UDFParameterGrammar")),
+    )
diff --git a/src/sqlfluff/rules/L013.py b/src/sqlfluff/rules/L013.py
index 4a9779fbb24..ad8a8aa12d2 100644
--- a/src/sqlfluff/rules/L013.py
+++ b/src/sqlfluff/rules/L013.py
@@ -48,6 +48,14 @@ def _eval(self, context: RuleContext) -> Optional[LintResult]:
         if segment.all(sp.is_type("select_clause_element")) and not children.any(
             sp.is_type("alias_expression")
         ):
+            # Ignore if it's a function with an EMITS clause as EMITS is equivalent to AS
+            if (
+                children.select(sp.is_type("function"))
+                .children()
+                .select(sp.is_type("emits_grammar"))
+            ):
+                return None
+
             types = set(
                 children.select(sp.not_(sp.is_name("star"))).apply(sp.get_type())
             )
diff --git a/test/fixtures/dialects/exasol/CreatePythonScalarScript.yml b/test/fixtures/dialects/exasol/CreatePythonScalarScript.yml
index 93b406fb053..afa15ebf468 100644
--- a/test/fixtures/dialects/exasol/CreatePythonScalarScript.yml
+++ b/test/fixtures/dialects/exasol/CreatePythonScalarScript.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: 83ace62ee3511844b7a45f4449c5281ae5476a8226d730b44790b64edddd53f5
+_hash: cbb926d620eb9f98f0f2e35da808e828b8256c85d7d48e675b9660121781e829
 file:
   statement:
     create_udf_script:
@@ -46,18 +46,19 @@ file:
               literal: '50'
               end_bracket: )
       - end_bracket: )
-    - keyword: EMITS
-    - bracketed:
-        start_bracket: (
-        column_datatype_definition:
-          identifier: X
-          data_type:
-            keyword: VARCHAR
-            bracketed:
-              start_bracket: (
-              literal: '2000000'
-              end_bracket: )
-        end_bracket: )
+    - emits_grammar:
+        keyword: EMITS
+        bracketed:
+          start_bracket: (
+          column_datatype_definition:
+            identifier: X
+            data_type:
+              keyword: VARCHAR
+              bracketed:
+                start_bracket: (
+                literal: '2000000'
+                end_bracket: )
+          end_bracket: )
     - keyword: AS
     - script_content:
       - raw: "\"\"\"\n/*====================================================================\n\
diff --git a/test/fixtures/dialects/exasol/CreateUDFScriptDotSyntax.yml b/test/fixtures/dialects/exasol/CreateUDFScriptDotSyntax.yml
index 88eb2c78c3d..e38f7d17c42 100644
--- a/test/fixtures/dialects/exasol/CreateUDFScriptDotSyntax.yml
+++ b/test/fixtures/dialects/exasol/CreateUDFScriptDotSyntax.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: ceb461cff4af3a0c4752bcddad80552df62c258111b4112f46248e19eb01cc31
+_hash: 72127a4b9e2b4899bc92c774e9d53ac576935b9e32d2ef28872e143d9af52dcf
 file:
   statement:
     create_udf_script:
@@ -17,11 +17,12 @@ file:
         start_bracket: (
         identifier: '...'
         end_bracket: )
-    - keyword: EMITS
-    - bracketed:
-        start_bracket: (
-        identifier: '...'
-        end_bracket: )
+    - emits_grammar:
+        keyword: EMITS
+        bracketed:
+          start_bracket: (
+          identifier: '...'
+          end_bracket: )
     - keyword: AS
     - script_content:
       - raw: def
diff --git a/test/fixtures/dialects/exasol/CreateUDFScriptStatement2.yml b/test/fixtures/dialects/exasol/CreateUDFScriptStatement2.yml
index 9ceb36f89df..3c4be34a070 100644
--- a/test/fixtures/dialects/exasol/CreateUDFScriptStatement2.yml
+++ b/test/fixtures/dialects/exasol/CreateUDFScriptStatement2.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: d6a1260974bb84aebdafd6792a82821bc4da1425a6d50f1706f2fbb58a380030
+_hash: db234bcd3598a13cc87312b26f7b4a8e5c4e81d793298d36653eb2f46d58fd2a
 file:
   statement:
     create_udf_script:
@@ -24,18 +24,19 @@ file:
               literal: '10000'
               end_bracket: )
         end_bracket: )
-    - keyword: EMITS
-    - bracketed:
-        start_bracket: (
-        column_datatype_definition:
-          identifier: words
-          data_type:
-            keyword: varchar
-            bracketed:
-              start_bracket: (
-              literal: '100'
-              end_bracket: )
-        end_bracket: )
+    - emits_grammar:
+        keyword: EMITS
+        bracketed:
+          start_bracket: (
+          column_datatype_definition:
+            identifier: words
+            data_type:
+              keyword: varchar
+              bracketed:
+                start_bracket: (
+                literal: '100'
+                end_bracket: )
+          end_bracket: )
     - keyword: AS
     - script_content:
       - raw: function
diff --git a/test/fixtures/dialects/exasol/SelectStatement.yml b/test/fixtures/dialects/exasol/SelectStatement.yml
index e7444d2e65d..c5b3380d179 100644
--- a/test/fixtures/dialects/exasol/SelectStatement.yml
+++ b/test/fixtures/dialects/exasol/SelectStatement.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: db2441deb5cc2f6fcd962d60c912405664f860820b0a0a73ecc27e6755ab16e5
+_hash: 64d863b32f08816de682dfaffda156f7733aa44813ec51e32717a9989078e202
 file:
 - statement:
     select_statement:
@@ -952,9 +952,9 @@ file:
         keyword: SELECT
         select_clause_element:
           function:
-          - function_name:
+            function_name:
               function_name_identifier: JSON_EXTRACT
-          - bracketed:
+            bracketed:
             - start_bracket: (
             - expression:
                 column_reference:
@@ -966,27 +966,28 @@ file:
             - expression:
                 literal: "'$.error()'"
             - end_bracket: )
-          - keyword: EMITS
-          - bracketed:
-            - start_bracket: (
-            - column_datatype_definition:
-                identifier: id
-                data_type:
-                  keyword: VARCHAR
-                  bracketed:
-                    start_bracket: (
-                    literal: '2000'
-                    end_bracket: )
-            - comma: ','
-            - column_datatype_definition:
-                identifier: error_column
-                data_type:
-                  keyword: VARCHAR
-                  bracketed:
-                    start_bracket: (
-                    literal: '2000000'
-                    end_bracket: )
-            - end_bracket: )
+            emits_grammar:
+              keyword: EMITS
+              bracketed:
+              - start_bracket: (
+              - column_datatype_definition:
+                  identifier: id
+                  data_type:
+                    keyword: VARCHAR
+                    bracketed:
+                      start_bracket: (
+                      literal: '2000'
+                      end_bracket: )
+              - comma: ','
+              - column_datatype_definition:
+                  identifier: error_column
+                  data_type:
+                    keyword: VARCHAR
+                    bracketed:
+                      start_bracket: (
+                      literal: '2000000'
+                      end_bracket: )
+              - end_bracket: )
       from_clause:
         keyword: FROM
         from_expression:
diff --git a/test/fixtures/rules/std_rule_cases/L013.yml b/test/fixtures/rules/std_rule_cases/L013.yml
index 050fbf03246..452e4d2d151 100644
--- a/test/fixtures/rules/std_rule_cases/L013.yml
+++ b/test/fixtures/rules/std_rule_cases/L013.yml
@@ -23,3 +23,10 @@ test_fail_column_exp_without_alias_allow_scalar_false:
 
 test_pass_column_exp_with_alias:
   pass_str: SELECT upper(foo) as foo_up, bar from blah
+
+test_pass_function_emits:
+  # Don't expect alias if allow_scalar = True (default)
+  pass_str: SELECT json_extract(json_str, '$.AFIELD', '$.BFIELD') emits (cola char(1), colb char(1)) FROM table1
+  configs:
+    core:
+      dialect: exasol


Code review comments:
diff_hunk: @@ -3770,3 +3766,17 @@ class FileSegment(BaseFileSegment):
             allow_trailing=True,
         ),
     )
+
+
+@exasol_dialect.segment()
+class EmitsGrammar(BaseFileSegment):
+    """EMITS Grammar for JSON_EXTRACT for example.
path: src/sqlfluff/dialects/dialect_exasol.py
text: @author:
I've left this with the same name but what's the difference between a"Grammar" and a "Sequence" out of interest? Should this be a Sequence?

@user1:
`Sequence` is a type of grammar (as are `AnyNumberOf`, `AnySetOf`, `Delimited`, etc..).

So calling it `EmitsGrammar` made sense when it was written like:
```python
EmitsGrammar=Sequence(
        "EMITS",
        Bracketed(Ref("UDFParameterGrammar")),
    ),
```

This is now a `Segment` (inherits from `BaseSegment`) so we should change the name to `EmitsSegment` (or something similar).

diff_hunk: @@ -3770,3 +3766,17 @@ class FileSegment(BaseFileSegment):
             allow_trailing=True,
         ),
     )
+
+
+@exasol_dialect.segment()
+class EmitsGrammar(BaseFileSegment):
path: src/sqlfluff/dialects/dialect_exasol.py
text: @user1:
```suggestion
class EmitsGrammar(BaseSegment):
```
`BaseFileSegment` should only be used by `FileSegment`

@author:
Oops - good spot. Fixed