Repository: tobymao/sqlglot (2024-10-01 ~ 2024-10-03)

problem_statement: Incorrect Handling of the `~~~` Operator in DuckDB
When testing SQLGlot with DuckDB, I discovered that the `~~~` operator is incorrectly handled. This operator is equivalent to the `GLOB` keyword in DuckDB, but SQLGlot incorrectly translates it.

#### Original Query:

```sql
SELECT ?::VARCHAR ~~~ ?::VARCHAR LIMIT 1
```

#### SQLGlot Output:

```sql
SELECT REGEXP_MATCHES(CAST(? AS TEXT), ~~CAST(? AS TEXT)) LIMIT 1
```

#### Expected Behavior:
SQLGlot should produce either the `~~~` operator or the `GLOB` function, as defined by DuckDB for pattern matching. The correct translation would be:

```sql
SELECT ?::VARCHAR ~~~ ?::VARCHAR LIMIT 1
```

#### Reference:
The `~~~` operator is documented to function the same as `GLOB` in DuckDB:
- [DuckDB Pattern Matching Documentation](https://duckdb.org/docs/sql/functions/pattern_matching.html)


date: 2024-10-01
patch: diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
index 29aa5c42..fd084d86 100644
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -649,6 +649,7 @@ class Tokenizer(metaclass=_Tokenizer):
         "<->": TokenType.LR_ARROW,
         "&&": TokenType.DAMP,
         "??": TokenType.DQMARK,
+        "~~~": TokenType.GLOB,
         "ALL": TokenType.ALL,
         "ALWAYS": TokenType.ALWAYS,
         "AND": TokenType.AND,

test_patch: diff --git a/tests/dialects/test_duckdb.py b/tests/dialects/test_duckdb.py
index 5925789e..c9f4d62a 100644
--- a/tests/dialects/test_duckdb.py
+++ b/tests/dialects/test_duckdb.py
@@ -866,6 +866,7 @@ class TestDuckDB(Validator):
         )
         self.validate_identity("a ^ b", "POWER(a, b)")
         self.validate_identity("a ** b", "POWER(a, b)")
+        self.validate_identity("a ~~~ b", "a GLOB b")
 
     def test_array_index(self):
         with self.assertLogs(helper_logger) as cm:


----

problem_statement: sqlglot.diff Bug for exp.Window Differences
The following statements are considered equal by `sqlglot.diff`.

However, one is `OVER`, while the other is `KEEP DENSE_RANK`, and thus these should not be considered semantically identical.

```
import sqlglot


parsed = sqlglot.parse_one(
    '''
        SELECT MAX(foo) OVER (
            ORDER BY bar
        )
        FROM baz
    ''',
    dialect='oracle'
)

parsed2 = sqlglot.parse_one(
    '''
        SELECT MAX(foo) KEEP (
            DENSE_RANK LAST
            ORDER BY bar
        )
        FROM baz
    ''',
    dialect='oracle'
)

diffs = sqlglot.diff(parsed, parsed2)

from sqlglot.diff import Keep

# This should fail but does not.
assert all(isinstance(diff, Keep) for diff in diffs)
```
date: 2024-10-01
patch: diff --git a/sqlglot/diff.py b/sqlglot/diff.py
index 5ebc682e..eaca8b8a 100644
--- a/sqlglot/diff.py
+++ b/sqlglot/diff.py
@@ -129,12 +129,14 @@ def diff(
 
 # The expression types for which Update edits are allowed.
 UPDATABLE_EXPRESSION_TYPES = (
+    exp.Alias,
     exp.Boolean,
+    exp.Column,
     exp.DataType,
+    exp.Lambda,
     exp.Literal,
     exp.Table,
-    exp.Column,
-    exp.Lambda,
+    exp.Window,
 )
 
 IGNORED_LEAF_EXPRESSION_TYPES = (exp.Identifier,)

test_patch: diff --git a/tests/test_diff.py b/tests/test_diff.py
index f83c805a..a8345232 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -157,11 +157,20 @@ class TestDiff(unittest.TestCase):
         self._validate_delta_only(
             diff_delta_only(expr_src, expr_tgt),
             [
-                Remove(parse_one("ROW_NUMBER()")),  # the Anonymous node
-                Insert(parse_one("RANK()")),  # the Anonymous node
+                Remove(parse_one("ROW_NUMBER()")),
+                Insert(parse_one("RANK()")),
+                Update(source=expr_src.selects[0], target=expr_tgt.selects[0]),
             ],
         )
 
+        expr_src = parse_one("SELECT MAX(x) OVER (ORDER BY y) FROM z", "oracle")
+        expr_tgt = parse_one("SELECT MAX(x) KEEP (DENSE_RANK LAST ORDER BY y) FROM z", "oracle")
+
+        self._validate_delta_only(
+            diff_delta_only(expr_src, expr_tgt),
+            [Update(source=expr_src.selects[0], target=expr_tgt.selects[0])],
+        )
+
     def test_pre_matchings(self):
         expr_src = parse_one("SELECT 1")
         expr_tgt = parse_one("SELECT 1, 2, 3, 4")
@@ -202,5 +211,19 @@ class TestDiff(unittest.TestCase):
             ],
         )
 
+        expr_src = parse_one("SELECT 1 AS c1, 2 AS c2")
+        expr_tgt = parse_one("SELECT 2 AS c1, 3 AS c2")
+
+        self._validate_delta_only(
+            diff_delta_only(expr_src, expr_tgt),
+            [
+                Remove(expression=exp.alias_(1, "c1")),
+                Remove(expression=exp.Literal.number(1)),
+                Insert(expression=exp.alias_(3, "c2")),
+                Insert(expression=exp.Literal.number(3)),
+                Update(source=exp.alias_(2, "c2"), target=exp.alias_(2, "c1")),
+            ],
+        )
+
     def _validate_delta_only(self, actual_delta, expected_delta):
         self.assertEqual(set(actual_delta), set(expected_delta))


----

problem_statement: sqlglot.diff False positive warning "Locking reads using 'FOR UPDATE/SHARE' are not supported"
```
import sqlglot

parsed = sqlglot.parse_one(
    '''
        SELECT foo
        FROM bar
        FOR UPDATE
    ''',
    dialect='oracle'
)

parsed2 = parsed.copy()

sqlglot.diff(parsed, parsed2)
```

This emits the following warning, which is a false positive in my case.

    Locking reads using 'FOR UPDATE/SHARE' are not supported

The root cause of this is that the `ChangeDistiller` uses a default `Dialect`:

    `self._sql_generator = Dialect().generator()

I'm not sure what a good solution to this issue is.

sqlglot.diff could take in a `dialect` parameter, which could be used to to set the `_sql_generator`. This is fine in my case because I am only diffing two parsed statements from Oracle. But this won't help solve the case where you are diffing two statements from different databases.

date: 2024-10-03
patch: diff --git a/sqlglot/diff.py b/sqlglot/diff.py
index eaca8b8a..023139ce 100644
--- a/sqlglot/diff.py
+++ b/sqlglot/diff.py
@@ -14,6 +14,9 @@ from heapq import heappop, heappush
 from sqlglot import Dialect, expressions as exp
 from sqlglot.helper import ensure_list
 
+if t.TYPE_CHECKING:
+    from sqlglot.dialects.dialect import DialectType
+
 
 @dataclass(frozen=True)
 class Insert:
@@ -63,6 +66,7 @@ def diff(
     target: exp.Expression,
     matchings: t.List[t.Tuple[exp.Expression, exp.Expression]] | None = None,
     delta_only: bool = False,
+    copy: bool = True,
     **kwargs: t.Any,
 ) -> t.List[Edit]:
     """
@@ -91,6 +95,9 @@ def diff(
             Note: expression references in this list must refer to the same node objects that are
             referenced in source / target trees.
         delta_only: excludes all `Keep` nodes from the diff.
+        copy: whether to copy the input expressions.
+            Note: if this is set to false, the caller must ensure that there are no shared references
+            in the two ASTs, otherwise the diffing algorithm may produce unexpected behavior.
         kwargs: additional arguments to pass to the ChangeDistiller instance.
 
     Returns:
@@ -110,8 +117,8 @@ def diff(
             if id(old_node) in matching_ids
         }
 
-    source_copy = source.copy()
-    target_copy = target.copy()
+    source_copy = source.copy() if copy else source
+    target_copy = target.copy() if copy else target
 
     node_mappings = {
         **compute_node_mappings(source, source_copy),
@@ -149,10 +156,10 @@ class ChangeDistiller:
     Chawathe et al. described in http://ilpubs.stanford.edu:8090/115/1/1995-46.pdf.
     """
 
-    def __init__(self, f: float = 0.6, t: float = 0.6) -> None:
+    def __init__(self, f: float = 0.6, t: float = 0.6, dialect: DialectType = None) -> None:
         self.f = f
         self.t = t
-        self._sql_generator = Dialect().generator()
+        self._sql_generator = Dialect.get_or_raise(dialect).generator()
 
     def diff(
         self,

test_patch: diff --git a/tests/test_diff.py b/tests/test_diff.py
index a8345232..edd3b267 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -225,5 +225,20 @@ class TestDiff(unittest.TestCase):
             ],
         )
 
+    def test_dialect_aware_diff(self):
+        from sqlglot.generator import logger
+
+        with self.assertLogs(logger) as cm:
+            # We want to assert there are no warnings, but the 'assertLogs' method does not support that.
+            # Therefore, we are adding a dummy warning, and then we will assert it is the only warning.
+            logger.warning("Dummy warning")
+
+            expression = parse_one("SELECT foo FROM bar FOR UPDATE", dialect="oracle")
+            self._validate_delta_only(
+                diff_delta_only(expression, expression.copy(), dialect="oracle"), []
+            )
+
+        self.assertEqual(["WARNING:sqlglot:Dummy warning"], cm.output)
+
     def _validate_delta_only(self, actual_delta, expected_delta):
         self.assertEqual(set(actual_delta), set(expected_delta))