From 654f08afa4894e2a1eca3eafe060201d4b73eb28 Mon Sep 17 00:00:00 2001 From: Abhishek Bhosale Date: Sun, 28 Dec 2025 09:56:01 +0530 Subject: [PATCH 1/3] fix: using in chr --- sqlglot/dialects/oracle.py | 8 ++++++++ tests/dialects/test_oracle.py | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/sqlglot/dialects/oracle.py b/sqlglot/dialects/oracle.py index f79496e2af..8771d369a4 100644 --- a/sqlglot/dialects/oracle.py +++ b/sqlglot/dialects/oracle.py @@ -151,6 +151,7 @@ class Parser(parser.Parser): order=self._parse_order(), ), "JSON_EXISTS": lambda self: self._parse_json_exists(), + "CHR": lambda self: self._parse_chr(), } FUNCTION_PARSERS.pop("CONVERT") @@ -254,6 +255,11 @@ def _parse_json_exists(self) -> exp.JSONExists: on_condition=self._parse_on_condition(), ) + def _parse_chr(self) -> exp.Chr: + args = [self._parse_bitwise()] + charset = self._match(TokenType.USING) and self._parse_var() + return self.expression(exp.Chr, expressions=args, charset=charset) + def _parse_into(self) -> t.Optional[exp.Into]: # https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/SELECT-INTO-statement.html bulk_collect = self._match(TokenType.BULK_COLLECT_INTO) @@ -334,6 +340,8 @@ class Generator(generator.Generator): exp.DateStrToDate: lambda self, e: self.func( "TO_DATE", e.this, exp.Literal.string("YYYY-MM-DD") ), + exp.Chr: lambda self, + e: f"CHR({self.sql(e.expressions[0])}{' USING ' + self.sql(e, 'charset') if e.args.get('charset') else ''})", exp.DateTrunc: lambda self, e: self.func("TRUNC", e.this, e.unit), exp.EuclideanDistance: rename_func("L2_DISTANCE"), exp.ILike: no_ilike_sql, diff --git a/tests/dialects/test_oracle.py b/tests/dialects/test_oracle.py index e832332abb..038ca8e566 100644 --- a/tests/dialects/test_oracle.py +++ b/tests/dialects/test_oracle.py @@ -806,3 +806,7 @@ def test_pseudocolumns(self): qualified.sql(dialect="oracle"), 'WITH "T" AS (SELECT 1 AS "COL") SELECT "T"."COL" AS "COL", ROWID AS "ROWID" FROM "T" "T" WHERE ROWNUM = 1', ) + + def test_chr(self): + self.validate_identity("SELECT CHR(187 USING NCHAR_CS)") + self.validate_identity("SELECT CHR(187)") From a13179f342a5bc08dfe928bb0b5d48b7f6173361 Mon Sep 17 00:00:00 2001 From: Abhishek Bhosale Date: Mon, 29 Dec 2025 19:19:03 +0530 Subject: [PATCH 2/3] moving chr to base --- sqlglot/dialects/mysql.py | 5 ----- sqlglot/dialects/oracle.py | 8 -------- sqlglot/generator.py | 6 ++++++ sqlglot/parser.py | 10 ++++++++++ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 6c070cbed1..9fdd7230c1 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -373,11 +373,6 @@ class Parser(parser.Parser): FUNCTION_PARSERS = { **parser.Parser.FUNCTION_PARSERS, - "CHAR": lambda self: self.expression( - exp.Chr, - expressions=self._parse_csv(self._parse_assignment), - charset=self._match(TokenType.USING) and self._parse_var(), - ), "GROUP_CONCAT": lambda self: self._parse_group_concat(), # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values "VALUES": lambda self: self.expression( diff --git a/sqlglot/dialects/oracle.py b/sqlglot/dialects/oracle.py index 8771d369a4..f79496e2af 100644 --- a/sqlglot/dialects/oracle.py +++ b/sqlglot/dialects/oracle.py @@ -151,7 +151,6 @@ class Parser(parser.Parser): order=self._parse_order(), ), "JSON_EXISTS": lambda self: self._parse_json_exists(), - "CHR": lambda self: self._parse_chr(), } FUNCTION_PARSERS.pop("CONVERT") @@ -255,11 +254,6 @@ def _parse_json_exists(self) -> exp.JSONExists: on_condition=self._parse_on_condition(), ) - def _parse_chr(self) -> exp.Chr: - args = [self._parse_bitwise()] - charset = self._match(TokenType.USING) and self._parse_var() - return self.expression(exp.Chr, expressions=args, charset=charset) - def _parse_into(self) -> t.Optional[exp.Into]: # https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/SELECT-INTO-statement.html bulk_collect = self._match(TokenType.BULK_COLLECT_INTO) @@ -340,8 +334,6 @@ class Generator(generator.Generator): exp.DateStrToDate: lambda self, e: self.func( "TO_DATE", e.this, exp.Literal.string("YYYY-MM-DD") ), - exp.Chr: lambda self, - e: f"CHR({self.sql(e.expressions[0])}{' USING ' + self.sql(e, 'charset') if e.args.get('charset') else ''})", exp.DateTrunc: lambda self, e: self.func("TRUNC", e.this, e.unit), exp.EuclideanDistance: rename_func("L2_DISTANCE"), exp.ILike: no_ilike_sql, diff --git a/sqlglot/generator.py b/sqlglot/generator.py index b5b6cd3cd7..ddc5c8c7e4 100644 --- a/sqlglot/generator.py +++ b/sqlglot/generator.py @@ -5472,3 +5472,9 @@ def weekstart_sql(self, expression: exp.WeekStart) -> str: return "WEEK" return self.func("WEEK", expression.this) + + def chr_sql(self, expression: exp.Chr) -> str: + this = self.expressions(sqls=[expression.this] + expression.expressions) + charset = expression.args.get("charset") + using = f" USING {self.sql(charset)}" if charset else "" + return f"{expression.sql_name()}({this}{using})" diff --git a/sqlglot/parser.py b/sqlglot/parser.py index 9ea0504170..0205dac069 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -1281,6 +1281,16 @@ def _parse_partitioned_by_bucket_or_truncate(self) -> t.Optional[exp.Expression] "CAST": lambda self: self._parse_cast(self.STRICT_CAST), "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), + "CHAR": lambda self: self.expression( + exp.Chr, + expressions=self._parse_csv(self._parse_assignment), + charset=self._match(TokenType.USING) and self._parse_var(), + ), + "CHR": lambda self: self.expression( + exp.Chr, + expressions=self._parse_csv(self._parse_assignment), + charset=self._match(TokenType.USING) and self._parse_var(), + ), "DECODE": lambda self: self._parse_decode(), "EXTRACT": lambda self: self._parse_extract(), "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), From 16ee0e29facc2e6eff3bf1523b277caae34a835e Mon Sep 17 00:00:00 2001 From: Abhishek Bhosale Date: Mon, 29 Dec 2025 19:56:34 +0530 Subject: [PATCH 3/3] optimized the chr and char --- sqlglot/dialects/mysql.py | 2 +- sqlglot/generator.py | 2 +- sqlglot/parser.py | 21 +++++++++------------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py index 9fdd7230c1..b584c351e9 100644 --- a/sqlglot/dialects/mysql.py +++ b/sqlglot/dialects/mysql.py @@ -1298,7 +1298,7 @@ def _oldstyle_limit_sql(self, expression: exp.Show) -> str: return "" def chr_sql(self, expression: exp.Chr) -> str: - this = self.expressions(sqls=[expression.this] + expression.expressions) + this = self.expressions(expression) charset = expression.args.get("charset") using = f" USING {self.sql(charset)}" if charset else "" return f"CHAR({this}{using})" diff --git a/sqlglot/generator.py b/sqlglot/generator.py index ddc5c8c7e4..2323f1406a 100644 --- a/sqlglot/generator.py +++ b/sqlglot/generator.py @@ -5474,7 +5474,7 @@ def weekstart_sql(self, expression: exp.WeekStart) -> str: return self.func("WEEK", expression.this) def chr_sql(self, expression: exp.Chr) -> str: - this = self.expressions(sqls=[expression.this] + expression.expressions) + this = self.expressions(expression) charset = expression.args.get("charset") using = f" USING {self.sql(charset)}" if charset else "" return f"{expression.sql_name()}({this}{using})" diff --git a/sqlglot/parser.py b/sqlglot/parser.py index 0205dac069..bf3ce07c9c 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -212,8 +212,6 @@ class Parser(metaclass=_Parser): "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None ), - "CHAR": lambda args: exp.Chr(expressions=args), - "CHR": lambda args: exp.Chr(expressions=args), "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), "CONCAT": lambda args, dialect: exp.Concat( expressions=args, @@ -1281,16 +1279,8 @@ def _parse_partitioned_by_bucket_or_truncate(self) -> t.Optional[exp.Expression] "CAST": lambda self: self._parse_cast(self.STRICT_CAST), "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), - "CHAR": lambda self: self.expression( - exp.Chr, - expressions=self._parse_csv(self._parse_assignment), - charset=self._match(TokenType.USING) and self._parse_var(), - ), - "CHR": lambda self: self.expression( - exp.Chr, - expressions=self._parse_csv(self._parse_assignment), - charset=self._match(TokenType.USING) and self._parse_var(), - ), + "CHAR": lambda self: self._parse_char(), + "CHR": lambda self: self._parse_char(), "DECODE": lambda self: self._parse_decode(), "EXTRACT": lambda self: self._parse_extract(), "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), @@ -6775,6 +6765,13 @@ def _parse_gap_fill(self) -> exp.GapFill: gap_fill = exp.GapFill.from_arg_list(args) return self.validate_expression(gap_fill, args) + def _parse_char(self) -> exp.Chr: + return self.expression( + exp.Chr, + expressions=self._parse_csv(self._parse_assignment), + charset=self._match(TokenType.USING) and self._parse_var(), + ) + def _parse_cast(self, strict: bool, safe: t.Optional[bool] = None) -> exp.Expression: this = self._parse_disjunction()