Repository: tobymao/sqlglot

Issue:
Parser support for `ON` clause in column def foreign key constraint
It appears that the parser does not support the `ON` clause of a column def foreign key constraint.

```python
sql = "CREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE CASCADE);"
sqlglot.parse_one(sql)
```

```shell
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/anon/sqlglot/sqlglot/__init__.py", line 139, in parse_one
    result = dialect.parse(sql, **opts)
  File "/Users/anon/sqlglot/sqlglot/dialects/dialect.py", line 157, in parse
    return self.parser(**opts).parse(self.tokenizer.tokenize(sql), sql)
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 702, in parse
    return self._parse(
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 765, in _parse
    expressions.append(parse_method(self))
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 895, in _parse_statement
    return self.STATEMENT_PARSERS[self._prev.token_type](self)
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 427, in <lambda>
    TokenType.CREATE: lambda self: self._parse_create(),
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 992, in _parse_create
    this = self._parse_schema(this=table_parts)
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 2705, in _parse_schema
    self._match_r_paren()
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 3608, in _match_r_paren
    self.raise_error("Expecting )")
  File "/Users/anon/sqlglot/sqlglot/parser.py", line 811, in raise_error
    raise error
sqlglot.errors.ParseError: Expecting ). Line 1, Col: 51.
  CREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE CASCADE);
```


Discussion:

Resolving patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2804,7 +2804,23 @@ def _parse_key_constraint_options(self) -> t.List[str]:
             if not self._curr:
                 break
 
-            if self._match_text_seq("NOT", "ENFORCED"):
+            if self._match(TokenType.ON):
+                action = None
+                on = self._advance_any() and self._prev.text
+
+                if self._match(TokenType.NO_ACTION):
+                    action = "NO ACTION"
+                elif self._match(TokenType.CASCADE):
+                    action = "CASCADE"
+                elif self._match_pair(TokenType.SET, TokenType.NULL):
+                    action = "SET NULL"
+                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
+                    action = "SET DEFAULT"
+                else:
+                    self.raise_error("Invalid key constraint")
+
+                options.append(f"ON {on} {action}")
+            elif self._match_text_seq("NOT", "ENFORCED"):
                 options.append("NOT ENFORCED")
             elif self._match_text_seq("DEFERRABLE"):
                 options.append("DEFERRABLE")
@@ -2814,10 +2830,6 @@ def _parse_key_constraint_options(self) -> t.List[str]:
                 options.append("NORELY")
             elif self._match_text_seq("MATCH", "FULL"):
                 options.append("MATCH FULL")
-            elif self._match_text_seq("ON", "UPDATE", "NO ACTION"):
-                options.append("ON UPDATE NO ACTION")
-            elif self._match_text_seq("ON", "DELETE", "NO ACTION"):
-                options.append("ON DELETE NO ACTION")
             else:
                 break
 


Test patch:
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -512,6 +512,14 @@ CREATE TABLE z (a INT UNIQUE AUTO_INCREMENT)
 CREATE TABLE z (a INT REFERENCES parent(b, c))
 CREATE TABLE z (a INT PRIMARY KEY, b INT REFERENCES foo(id))
 CREATE TABLE z (a INT, FOREIGN KEY (a) REFERENCES parent(b, c))
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON DELETE NO ACTION)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON DELETE CASCADE)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON DELETE SET NULL)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON DELETE SET DEFAULT)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON UPDATE NO ACTION)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON UPDATE CASCADE)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON UPDATE SET NULL)
+CREATE TABLE foo (bar INT REFERENCES baz(baz_id) ON UPDATE SET DEFAULT)
 CREATE TABLE asd AS SELECT asd FROM asd WITH NO DATA
 CREATE TABLE asd AS SELECT asd FROM asd WITH DATA
 CREATE TABLE products (x INT GENERATED BY DEFAULT AS IDENTITY)