Repository: tobymao/sqlglot

Issue:
INCLUDE keyword for indexes in PostgreSQL
As I understood, the INCLUDE keyword for indexes isn't supported. Not a big problem, but would be better if it worked.

```text
Python 3.8.10 (default, Nov 22 2023, 10:22:35) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlglot
>>> sqlglot.parse_one("CREATE INDEX et_vid_idx ON et (vid) INCLUDE (fid)", read="postgres")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/ngw/env/lib/python3.8/site-packages/sqlglot/__init__.py", line 125, in parse_one
    result = dialect.parse(sql, **opts)
  File "/opt/ngw/env/lib/python3.8/site-packages/sqlglot/dialects/dialect.py", line 442, in parse
    return self.parser(**opts).parse(self.tokenize(sql), sql)
  File "/opt/ngw/env/lib/python3.8/site-packages/sqlglot/parser.py", line 1034, in parse
    return self._parse(
  File "/opt/ngw/env/lib/python3.8/site-packages/sqlglot/parser.py", line 1103, in _parse
    self.raise_error("Invalid expression / Unexpected token")
  File "/opt/ngw/env/lib/python3.8/site-packages/sqlglot/parser.py", line 1144, in raise_error
    raise error
sqlglot.errors.ParseError: Invalid expression / Unexpected token. Line 1, Col: 43.
  CREATE INDEX et_vid_idx ON et (vid) INCLUDE (fid)
>>> sqlglot.__version__
'20.9.0'
```

Without ``INCLUDE`` everything works:

```text
>>> sqlglot.parse_one("CREATE INDEX et_vid_idx ON et (vid)", read="postgres")
Create(
  this=Index(
    this=Identifier(this=et_vid_idx, quoted=False),
    table=Table(
      this=Identifier(this=et, quoted=False)),
    columns=[
      Ordered(
        this=Column(
          this=Identifier(this=vid, quoted=False)),
        nulls_first=False)]),
  kind=INDEX,
  exists=False)
  ```


Comments:

Patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index ddad8f83..2286d682 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -1667,6 +1667,7 @@ class Index(Expression):
         "unique": False,
         "primary": False,
         "amp": False,  # teradata
+        "include": False,
         "partition_by": False,  # teradata
         "where": False,  # postgres partial indexes
     }
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 977185ff..79e4b898 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1111,7 +1111,10 @@ class Generator:
         partition_by = self.expressions(expression, key="partition_by", flat=True)
         partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
         where = self.sql(expression, "where")
-        return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{partition_by}{where}"
+        include = self.expressions(expression, key="include", flat=True)
+        if include:
+            include = f" INCLUDE ({include})"
+        return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{include}{partition_by}{where}"
 
     def identifier_sql(self, expression: exp.Identifier) -> str:
         text = expression.name
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 5fb40b95..54d09715 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2681,6 +2681,8 @@ class Parser(metaclass=_Parser):
         else:
             columns = None
 
+        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
+
         return self.expression(
             exp.Index,
             this=index,
@@ -2690,6 +2692,7 @@ class Parser(metaclass=_Parser):
             unique=unique,
             primary=primary,
             amp=amp,
+            include=include,
             partition_by=self._parse_partition_by(),
             where=self._parse_where(),
         )


Test patch:
diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py
index ed25315b..1c2a2286 100644
--- a/tests/dialects/test_postgres.py
+++ b/tests/dialects/test_postgres.py
@@ -582,6 +582,7 @@ class TestPostgres(Validator):
         cdef.args["kind"].assert_is(exp.DataType)
         self.assertEqual(expr.sql(dialect="postgres"), "CREATE TABLE t (x INTERVAL DAY)")
 
+        self.validate_identity("CREATE INDEX et_vid_idx ON et(vid) INCLUDE (fid)")
         self.validate_identity("CREATE INDEX idx_x ON x USING BTREE(x, y) WHERE (NOT y IS NULL)")
         self.validate_identity("CREATE TABLE test (elems JSONB[])")
         self.validate_identity("CREATE TABLE public.y (x TSTZRANGE NOT NULL)")