Repository: tobymao/sqlglot

Issue:
args with value of None are not ignored when perform semantic comparison
**Before you file an issue**
- Make sure you specify the "read" dialect eg. `parse_one(sql, read="spark")`
- Make sure you specify the "write" dialect eg. `ast.sql(dialect="duckdb")`
- Check if the issue still exists on main

**Fully reproducible code snippet**
Please include a fully reproducible code snippet or the input sql, dialect, and expected output.

**Official Documentation**
Please include links to official SQL documentation related to your issue.

When compare with `diff` module, some expressions have args with value of None, so even this has not impact on semantic output, it still output `Update` objects.

```python
from sqlglot import parse_one
from sqlglot.optimizer import optimize
from sqlglot.diff import diff, Keep

query1 = "SELECT a FROM b"
query2 = "SELECT b.a FROM b"

meta = {"b": {"a": "STRING"}}

ast1 = parse_one(query1, dialect="bigquery")
ast2 = parse_one(query2, dialect="bigquery")

delta = [d for d in diff(optimize(ast1, schema=meta, dialect="bigquery"), optimize(ast2, schema=meta, dialect="bigquery")) if not isinstance(d, Keep)]

```

In the above example, there is one `Update`, the differences between source and target is: source does not have `db` and `catalog` keys, target has these keys, but both values are None. 

Maybe values with None can be ignored? Like add ` or value is None` in the if condition in below code? 

https://github.com/tobymao/sqlglot/blob/980f53852444ec38c45483cbbf63a286244262a6/sqlglot/diff.py#L396-L400

Discussion:

Fix (patch):
diff --git a/sqlglot/diff.py b/sqlglot/diff.py
index f7cdebd82c..fabac9b4c2 100644
--- a/sqlglot/diff.py
+++ b/sqlglot/diff.py
@@ -393,8 +393,10 @@ def _get_expression_leaves(expression: exp.Expression) -> t.Iterator[exp.Express
 
 def _get_non_expression_leaves(expression: exp.Expression) -> t.Iterator[t.Tuple[str, t.Any]]:
     for arg, value in expression.args.items():
-        if isinstance(value, exp.Expression) or (
-            isinstance(value, list) and isinstance(seq_get(value, 0), exp.Expression)
+        if (
+            value is None
+            or isinstance(value, exp.Expression)
+            or (isinstance(value, list) and isinstance(seq_get(value, 0), exp.Expression))
         ):
             continue
 


Test changes:
diff --git a/tests/test_diff.py b/tests/test_diff.py
index d238dc0650..621ca76709 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -345,5 +345,14 @@ def test_non_expression_leaf_delta(self):
             ],
         )
 
+    def test_none_args_are_not_treated_as_leaves(self):
+        expr_src = parse_one("a.b")
+        expr_tgt = exp.Column(this=exp.to_identifier("b"), table=exp.to_identifier("a"))
+
+        self.assertEqual(set(expr_src.args), {"this", "table", "db", "catalog"})
+        self.assertEqual(set(expr_tgt.args), {"this", "table"})
+
+        self._validate_delta_only(diff_delta_only(expr_src, expr_tgt), [])
+
     def _validate_delta_only(self, actual_delta, expected_delta):
         self.assertEqual(set(actual_delta), set(expected_delta))