Repository: tobymao/sqlglot

Issue:
Support postgres ALTER TABLE ONLY
Postgres supports parameter `ONLY` for `ALTER TABLE` expression. 
```sql
ALTER TABLE [ IF EXISTS ] [ ONLY ] name [ * ]
    action [, ... ]
```
The library parses this type of expressions incorrectly.

**Fully reproducible code snippet**
``` python
sql_with_only = """
    ALTER TABLE ONLY "Album" ADD CONSTRAINT "FK_AlbumArtistId"
        FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION;
"""

sql_without_only = """
    ALTER TABLE "Album" ADD CONSTRAINT "FK_AlbumArtistId"
        FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION;
"""

parsed_sql_exprs = sqlglot.parse(sql_with_only, read="postgres")
print(parsed_sql_exprs)

# [(COMMAND this: ALTER, expression:  TABLE ONLY "Album" ADD CONSTRAINT "FK_AlbumArtistId"
#        FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION)]

parsed_sql_exprs = sqlglot.parse(sql_without_only, read="postgres")
print(parsed_sql_exprs)

# [(ALTERTABLE this: 
#   (TABLE this: 
#     (IDENTIFIER this: Album, quoted: True)), exists: False, actions: 
#   (ADDCONSTRAINT this: 
#     (IDENTIFIER this: FK_AlbumArtistId, quoted: True), expression: 
#     (FOREIGNKEY expressions: 
#       (IDENTIFIER this: ArtistId, quoted: True), reference: 
#       (REFERENCE this: 
#         (SCHEMA this: 
#           (TABLE this: 
#             (IDENTIFIER this: Artist, quoted: True)), expressions: 
#           (IDENTIFIER this: ArtistId, quoted: True)), options: ON DELETE NO ACTION, ON UPDATE NO ACTION))))]
```


**Official Documentation**
https://www.postgresql.org/docs/current/sql-altertable.html



Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -3710,7 +3710,7 @@ class Rollback(Expression):
 
 
 class AlterTable(Expression):
-    arg_types = {"this": True, "actions": True, "exists": False}
+    arg_types = {"this": True, "actions": True, "exists": False, "only": False}
 
 
 class AddConstraint(Expression):
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2289,7 +2289,8 @@ def altertable_sql(self, expression: exp.AlterTable) -> str:
             actions = self.expressions(expression, key="actions")
 
         exists = " IF EXISTS" if expression.args.get("exists") else ""
-        return f"ALTER TABLE{exists} {self.sql(expression, 'this')} {actions}"
+        only = " ONLY" if expression.args.get("only") else ""
+        return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}"
 
     def droppartition_sql(self, expression: exp.DropPartition) -> str:
         expressions = self.expressions(expression)
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -4785,6 +4785,7 @@ def _parse_alter(self) -> exp.AlterTable | exp.Command:
             return self._parse_as_command(start)
 
         exists = self._parse_exists()
+        only = self._match_text_seq("ONLY")
         this = self._parse_table(schema=True)
 
         if self._next:
@@ -4800,7 +4801,9 @@ def _parse_alter(self) -> exp.AlterTable | exp.Command:
                     this=this,
                     exists=exists,
                     actions=actions,
+                    only=only,
                 )
+
         return self._parse_as_command(start)
 
     def _parse_merge(self) -> exp.Merge:


Test patch:
diff --git a/tests/dialects/test_postgres.py b/tests/dialects/test_postgres.py
--- a/tests/dialects/test_postgres.py
+++ b/tests/dialects/test_postgres.py
@@ -126,12 +126,17 @@ def test_array_offset(self, logger):
         )
 
     def test_postgres(self):
-        self.validate_identity("x @@ y")
-
         expr = parse_one("SELECT * FROM r CROSS JOIN LATERAL UNNEST(ARRAY[1]) AS s(location)")
         unnest = expr.args["joins"][0].this.this
         unnest.assert_is(exp.Unnest)
 
+        alter_table_only = """ALTER TABLE ONLY "Album" ADD CONSTRAINT "FK_AlbumArtistId" FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION"""
+        expr = parse_one(alter_table_only)
+
+        self.assertIsInstance(expr, exp.AlterTable)
+        self.assertEqual(expr.sql(dialect="postgres"), alter_table_only)
+
+        self.validate_identity("x @@ y")
         self.validate_identity("CAST(x AS MONEY)")
         self.validate_identity("CAST(x AS INT4RANGE)")
         self.validate_identity("CAST(x AS INT4MULTIRANGE)")