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:

Solution patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
index e0d392b0cd..aaa1e376d6 100644
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -107,6 +107,13 @@ def _builder(args: 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 Parser(parser.Parser):
             ),
             "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 @@ def _parse_lambda_arg(self) -> t.Optional[exp.Expression]:
 
             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 Generator(generator.Generator):
                 "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 cce3a17a7b..b804212fc7 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 9aee03c1ca..94fe91708d 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2829,13 +2829,14 @@ def check_sql(self, expression: exp.Check) -> str:
 
     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 4eb97235da..a11c21a456 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -976,6 +976,12 @@ def test_snowflake(self):
                 "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 @@ def test_ddl(self):
         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\\' '",