Repository: tobymao/sqlglot

Issue:
Parsing Fails on Snowflake Foreign Keys Without Explicit Names
When parsing a Snowflake `CREATE TABLE` statement that creates an inline foreign key constraint with no explicit name, `sqlglot` fails with the following error:

```
sqlglot.errors.ParseError: Expecting (. Line 4, Col: 48.
```

It seems that it is expecting an explicit column name for the foreign key constraint.

From what I can tell from [Snowflake's documentation](https://docs.snowflake.com/en/sql-reference/sql/create-table-constraint#:~:text=do%20not%20need%20to%20be%20specified%20if%20the%20signature), this is valid syntax:
> When defining foreign keys, either inline or out-of-line, column name(s) for the referenced table do not need to be specified if the signature (name and data type) of the foreign key column(s) and the referenced table’s primary key column(s) exactly match

I am also able to successfully run these statements in Snowflake.

**Fully reproducible code snippet**
```python

from sqlglot import parse_one

sql = '''
CREATE OR REPLACE TABLE TEST (
	ID NUMBER NOT NULL IDENTITY PRIMARY KEY,
	SOME_REF NUMBER NOT NULL FOREIGN KEY REFERENCES SOME_OTHER_TABLE (ID)
);
'''
parsed = parse_one(sql, read='snowflake')

```

However, this does work:
```python
from sqlglot import parse_one

sql = '''
CREATE OR REPLACE TABLE TEST (
	ID NUMBER NOT NULL IDENTITY PRIMARY KEY,
	SOME_REF NUMBER NOT NULL FOREIGN KEY (SOME_NAME) REFERENCES SOME_OTHER_TABLE (ID)
);
'''
parsed = parse_one(sql, read='snowflake')
```

**Official Documentation**
https://docs.snowflake.com/en/sql-reference/sql/create-table-constraint#:~:text=do%20not%20need%20to%20be%20specified%20if%20the%20signature


Discussion:

Patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
index e0d392b0..aaa1e376 100644
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -107,6 +107,13 @@ def _build_date_time_add(expr_type: t.Type[E]) -> t.Callable[[t.List], E]:
     return _builder
 
 
+def _build_bitor(args: t.List) -> exp.BitwiseOr | exp.Anonymous:
+    if len(args) == 3:
+        return exp.Anonymous(this="BITOR", expressions=args)
+
+    return binary_from_function(exp.BitwiseOr)(args)
+
+
 # https://docs.snowflake.com/en/sql-reference/functions/div0
 def _build_if_from_div0(args: t.List) -> exp.If:
     lhs = exp._wrap(seq_get(args, 0), exp.Binary)
@@ -393,6 +400,8 @@ class Snowflake(Dialect):
             ),
             "BITXOR": binary_from_function(exp.BitwiseXor),
             "BIT_XOR": binary_from_function(exp.BitwiseXor),
+            "BITOR": _build_bitor,
+            "BIT_OR": _build_bitor,
             "BOOLXOR": binary_from_function(exp.Xor),
             "DATE": _build_datetime("DATE", exp.DataType.Type.DATE),
             "DATE_TRUNC": _date_trunc_to_time,
@@ -790,6 +799,14 @@ class Snowflake(Dialect):
 
             return this
 
+        def _parse_foreign_key(self) -> exp.ForeignKey:
+            # inlineFK, the REFERENCES columns are implied
+            if self._match(TokenType.REFERENCES, advance=False):
+                return self.expression(exp.ForeignKey)
+
+            # outoflineFK, explicitly names the columns
+            return super()._parse_foreign_key()
+
     class Tokenizer(tokens.Tokenizer):
         STRING_ESCAPES = ["\\", "'"]
         HEX_STRINGS = [("x'", "'"), ("X'", "'")]
@@ -869,6 +886,7 @@ class Snowflake(Dialect):
                 "CONVERT_TIMEZONE", e.args.get("zone"), e.this
             ),
             exp.BitwiseXor: rename_func("BITXOR"),
+            exp.BitwiseOr: rename_func("BITOR"),
             exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]),
             exp.DateAdd: date_delta_sql("DATEADD"),
             exp.DateDiff: date_delta_sql("DATEDIFF"),
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index cce3a17a..b804212f 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2113,7 +2113,7 @@ class Directory(Expression):
 
 class ForeignKey(Expression):
     arg_types = {
-        "expressions": True,
+        "expressions": False,
         "reference": False,
         "delete": False,
         "update": False,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 9aee03c1..94fe9170 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2829,13 +2829,14 @@ class Generator(metaclass=_Generator):
 
     def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
         expressions = self.expressions(expression, flat=True)
+        expressions = f" ({expressions})" if expressions else ""
         reference = self.sql(expression, "reference")
         reference = f" {reference}" if reference else ""
         delete = self.sql(expression, "delete")
         delete = f" ON DELETE {delete}" if delete else ""
         update = self.sql(expression, "update")
         update = f" ON UPDATE {update}" if update else ""
-        return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
+        return f"FOREIGN KEY{expressions}{reference}{delete}{update}"
 
     def primarykey_sql(self, expression: exp.ForeignKey) -> str:
         expressions = self.expressions(expression, flat=True)


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
index 4eb97235..a11c21a4 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -976,6 +976,12 @@ WHERE
                 "snowflake": "EDITDISTANCE(col1, col2, 3)",
             },
         )
+        self.validate_identity("SELECT BITOR(a, b) FROM table")
+
+        self.validate_identity("SELECT BIT_OR(a, b) FROM table", "SELECT BITOR(a, b) FROM table")
+
+        # Test BITOR with three arguments, padding on the left
+        self.validate_identity("SELECT BITOR(a, b, 'LEFT') FROM table_name")
 
     def test_null_treatment(self):
         self.validate_all(
@@ -1450,6 +1456,9 @@ WHERE
         self.validate_identity(
             """CREATE OR REPLACE FUNCTION ibis_udfs.public.object_values("obj" OBJECT) RETURNS ARRAY LANGUAGE JAVASCRIPT STRICT AS ' return Object.values(obj) '"""
         )
+        self.validate_identity(
+            "CREATE OR REPLACE TABLE TEST (SOME_REF DECIMAL(38, 0) NOT NULL FOREIGN KEY REFERENCES SOME_OTHER_TABLE (ID))"
+        )
         self.validate_identity(
             "CREATE OR REPLACE FUNCTION my_udf(location OBJECT(city VARCHAR, zipcode DECIMAL(38, 0), val ARRAY(BOOLEAN))) RETURNS VARCHAR AS $$ SELECT 'foo' $$",
             "CREATE OR REPLACE FUNCTION my_udf(location OBJECT(city VARCHAR, zipcode DECIMAL(38, 0), val ARRAY(BOOLEAN))) RETURNS VARCHAR AS ' SELECT \\'foo\\' '",