This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8baa9c1f2e3 fix(db2): accept sqlglot's parse_mod in the DB2 term 
parser (#44708)
8baa9c1f2e3 is described below

commit 8baa9c1f2e3cd0187cc1ab2922f5568118348efe
Author: Amin Ghadersohi <[email protected]>
AuthorDate: Thu Oct 1 12:50:17 2026 +1000

    fix(db2): accept sqlglot's parse_mod in the DB2 term parser (#44708)
---
 superset/sql/dialects/db2.py               | 81 ++++++++++++++----------------
 tests/unit_tests/sql/dialects/db2_tests.py | 20 ++++++++
 2 files changed, 58 insertions(+), 43 deletions(-)

diff --git a/superset/sql/dialects/db2.py b/superset/sql/dialects/db2.py
index b10fd925567..6211511c6b1 100644
--- a/superset/sql/dialects/db2.py
+++ b/superset/sql/dialects/db2.py
@@ -28,6 +28,23 @@ from sqlglot import exp, tokens
 from sqlglot.dialects.dialect import rename_func
 from sqlglot.dialects.postgres import Postgres
 
+LABELED_DURATION_UNITS = {
+    "MICROSECOND",
+    "MICROSECONDS",
+    "SECOND",
+    "SECONDS",
+    "MINUTE",
+    "MINUTES",
+    "HOUR",
+    "HOURS",
+    "DAY",
+    "DAYS",
+    "MONTH",
+    "MONTHS",
+    "YEAR",
+    "YEARS",
+}
+
 
 class DB2Interval(exp.Expression):
     """DB2 labeled duration expression (e.g., '1 DAYS', '2 MONTHS')."""
@@ -67,68 +84,46 @@ class DB2(Postgres):
     class Parser(Postgres.Parser):
         """DB2 SQL parser with support for labeled durations."""
 
-        def _parse_term(self) -> exp.Expression | None:
+        def _parse_term(self, parse_mod: bool = True) -> exp.Expression | None:
             """
             Override term parsing to support DB2 labeled durations.
 
             This is called during expression parsing for addition/subtraction
             operations. We intercept patterns like `expr + 1 DAYS` and parse 
them
-            specially.
+            specially. Everything else follows sqlglot's own implementation,
+            including the ``parse_mod`` flag it passes while parsing LIMIT and
+            OFFSET, and the other term operators (e.g. COLLATE).
             """
-            this = self._parse_factor()
-            if not this:
-                return None
-
-            while self._match_set((tokens.TokenType.PLUS, 
tokens.TokenType.DASH)):
-                op = self._prev.token_type
+            this = self._parse_factor(parse_mod=parse_mod)
 
-                # Parse the right side of the + or -
-                rhs = self._parse_factor()
-                if not rhs:  # pragma: no cover
-                    break
+            while self._match_set(self.TERM):
+                token_type = self._prev.token_type
+                klass = self.TERM[token_type]
+                comments = self._prev_comments
+                expression = self._parse_factor(parse_mod=parse_mod)
 
                 # Check if there's a time unit after the right side
                 # This handles patterns like: expr + 1 DAYS, expr + (func()) 
DAYS
                 if (
-                    self._curr
+                    token_type in (tokens.TokenType.PLUS, 
tokens.TokenType.DASH)
+                    and expression is not None
+                    and self._curr
                     and self._curr.token_type == tokens.TokenType.VAR
-                    and self._curr.text.upper()
-                    in {
-                        "MICROSECOND",
-                        "MICROSECONDS",
-                        "SECOND",
-                        "SECONDS",
-                        "MINUTE",
-                        "MINUTES",
-                        "HOUR",
-                        "HOURS",
-                        "DAY",
-                        "DAYS",
-                        "MONTH",
-                        "MONTHS",
-                        "YEAR",
-                        "YEARS",
-                    }
+                    and self._curr.text.upper() in LABELED_DURATION_UNITS
                 ):
                     # Found a DB2 labeled duration
                     unit_token = self._curr
                     self._advance()
-
-                    duration = DB2Interval(
-                        this=rhs,
+                    expression = DB2Interval(
+                        this=expression,
                         unit=exp.Literal.string(unit_token.text.upper()),
                     )
 
-                    if op == tokens.TokenType.PLUS:
-                        this = exp.Add(this=this, expression=duration)
-                    else:
-                        this = exp.Sub(this=this, expression=duration)
-                else:
-                    # Not a labeled duration - use normal Add/Sub
-                    if op == tokens.TokenType.PLUS:
-                        this = exp.Add(this=this, expression=rhs)
-                    else:
-                        this = exp.Sub(this=this, expression=rhs)
+                this = self.expression(
+                    klass(this=this, expression=expression), comments=comments
+                )
+                if isinstance(this, exp.Collate):
+                    self._normalize_collate(this)
 
             return this
 
diff --git a/tests/unit_tests/sql/dialects/db2_tests.py 
b/tests/unit_tests/sql/dialects/db2_tests.py
index b8add1d65b1..a6a4c7e9704 100644
--- a/tests/unit_tests/sql/dialects/db2_tests.py
+++ b/tests/unit_tests/sql/dialects/db2_tests.py
@@ -244,3 +244,23 @@ def test_column_plus_literal_duration() -> None:
 
     # Should parse as (col + 1 DAYS), not (col + 1) AS DAYS
     assert regenerated == "SELECT col + 1 DAYS FROM t"
+
+
[email protected](
+    ("sql", "expected"),
+    [
+        ("SELECT * FROM t LIMIT 10 OFFSET 2", "SELECT * FROM t LIMIT 10 OFFSET 
2"),
+        (
+            "SELECT * FROM (SELECT 1 AS a FROM t) AS x LIMIT 5",
+            "SELECT * FROM (SELECT 1 AS a FROM t) AS x LIMIT 5",
+        ),
+        ("SELECT a % 3 FROM t LIMIT 4", "SELECT a % 3 FROM t LIMIT 4"),
+        ("SELECT * FROM t LIMIT 10 %", "SELECT * FROM t LIMIT 10 PERCENT"),
+        ("SELECT a COLLATE x FROM t", "SELECT a COLLATE x FROM t"),
+    ],
+)
+def test_limit_offset_and_other_term_operators(sql: str, expected: str) -> 
None:
+    """
+    sqlglot passes ``parse_mod`` to ``_parse_term`` while parsing LIMIT/OFFSET.
+    """
+    assert parse_one(sql, dialect=DB2).sql(dialect=DB2) == expected

Reply via email to