Merging upstream version 18.4.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b982664fe2
commit
d90681de49
92 changed files with 43076 additions and 40554 deletions
59
CHANGELOG.md
59
CHANGELOG.md
|
@ -1,6 +1,63 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
## [v18.4.0] - 2023-09-12
|
||||||
|
### :sparkles: New Features
|
||||||
|
- [`5e2042a`](https://github.com/tobymao/sqlglot/commit/5e2042aaa0e4be08d02c369a660d3b37ce78b567) - add TINYTEXT and TINYBLOB types *(PR [#2182](https://github.com/tobymao/sqlglot/pull/2182) by [@Nitrino](https://github.com/Nitrino))*
|
||||||
|
- [`0c536bd`](https://github.com/tobymao/sqlglot/commit/0c536bd3ca0fc0bf0d9ba649281530faf53304dd) - **oracle**: add support for JSON_ARRAYAGG *(PR [#2189](https://github.com/tobymao/sqlglot/pull/2189) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- [`f4e3e09`](https://github.com/tobymao/sqlglot/commit/f4e3e095c5eebc347f5d95e41fd68252af9b13bc) - **oracle**: add support for JSON_TABLE *(PR [#2191](https://github.com/tobymao/sqlglot/pull/2191) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *addresses issue [#2187](undefined) opened by [@sashindeitidata](https://github.com/sashindeitidata)*
|
||||||
|
- [`11d95ff`](https://github.com/tobymao/sqlglot/commit/11d95ff3ece4691aa4d766c60c6765cd8a68589a) - add redshift concat_ws support *(PR [#2194](https://github.com/tobymao/sqlglot/pull/2194) by [@eakmanrq](https://github.com/eakmanrq))*
|
||||||
|
|
||||||
|
### :bug: Bug Fixes
|
||||||
|
- [`c7433bf`](https://github.com/tobymao/sqlglot/commit/c7433bfe5086eb66895b43514eb4edfa56eb1228) - join using with star *(commit by [@tobymao](https://github.com/tobymao))*
|
||||||
|
- [`451439c`](https://github.com/tobymao/sqlglot/commit/451439c84a8feda05d51c47180c9f69cc92f22d6) - **clickhouse**: add missing type mappings for string types *(PR [#2183](https://github.com/tobymao/sqlglot/pull/2183) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- [`5ba5165`](https://github.com/tobymao/sqlglot/commit/5ba51657bc810139a28603b1bb542d44173bdc55) - **duckdb**: rename VariancePop -> var_pop in DuckDB *(PR [#2184](https://github.com/tobymao/sqlglot/pull/2184) by [@gforsyth](https://github.com/gforsyth))*
|
||||||
|
- [`d192515`](https://github.com/tobymao/sqlglot/commit/d19251566424ba07efe46b3be4ac6bbe327e7821) - **optimizer**: merge subqueries should use alias from outer scope *(PR [#2185](https://github.com/tobymao/sqlglot/pull/2185) by [@barakalon](https://github.com/barakalon))*
|
||||||
|
- [`12db377`](https://github.com/tobymao/sqlglot/commit/12db377ea8b07b1ff418dc988ef1ea4c20288206) - **mysql**: multi table update closes [#2193](https://github.com/tobymao/sqlglot/pull/2193) *(commit by [@tobymao](https://github.com/tobymao))*
|
||||||
|
- [`b9f5ede`](https://github.com/tobymao/sqlglot/commit/b9f5edee02aed346ebaea767274cc08e3960419b) - **oracle**: make parentheses in JSON_TABLE's COLUMNS clause optional *(commit by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- [`8c51275`](https://github.com/tobymao/sqlglot/commit/8c512750044efa059adc3afee32517684dabfc12) - **mysql**: parse column prefix in index / pk defn. correctly *(PR [#2197](https://github.com/tobymao/sqlglot/pull/2197) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *fixes issue [#2195](undefined) opened by [@Nitrino](https://github.com/Nitrino)*
|
||||||
|
|
||||||
|
### :recycle: Refactors
|
||||||
|
- [`a81dd14`](https://github.com/tobymao/sqlglot/commit/a81dd14a6de1a50438eae64c2dd20e4841c29572) - override Bracket.output_name only when there's one bracket expression *(commit by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- [`7ae5a94`](https://github.com/tobymao/sqlglot/commit/7ae5a9463cd68371f6ed45b9e00582eb44cead3b) - fix mutation bug in Column.to_dot, simplify Dot.build *(PR [#2196](https://github.com/tobymao/sqlglot/pull/2196) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
|
||||||
|
### :wrench: Chores
|
||||||
|
- [`981ad23`](https://github.com/tobymao/sqlglot/commit/981ad23cd1bf2b95e121bb9a7f3b677d4a053be4) - **duckdb**: fix var_pop tests *(commit by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
|
||||||
|
|
||||||
|
## [v18.3.0] - 2023-09-07
|
||||||
|
### :boom: BREAKING CHANGES
|
||||||
|
- due to [`3fc2eb5`](https://github.com/tobymao/sqlglot/commit/3fc2eb581528504db4523c3e0a537000e026a4cc) - improve support for interval spans like HOUR TO SECOND *(PR [#2167](https://github.com/tobymao/sqlglot/pull/2167) by [@GeorgeSittas](https://github.com/GeorgeSittas))*:
|
||||||
|
|
||||||
|
improve support for interval spans like HOUR TO SECOND (#2167)
|
||||||
|
|
||||||
|
- due to [`93b7ba2`](https://github.com/tobymao/sqlglot/commit/93b7ba20640a880ceeb63660b796ab94579bb73a) - MySQL Timestamp Data Types *(PR [#2173](https://github.com/tobymao/sqlglot/pull/2173) by [@eakmanrq](https://github.com/eakmanrq))*:
|
||||||
|
|
||||||
|
MySQL Timestamp Data Types (#2173)
|
||||||
|
|
||||||
|
|
||||||
|
### :sparkles: New Features
|
||||||
|
- [`5dd0fda`](https://github.com/tobymao/sqlglot/commit/5dd0fdaaf9ec8bc5f9f0a2cd01395222eacf28a0) - **spark**: add support for raw strings *(PR [#2165](https://github.com/tobymao/sqlglot/pull/2165) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *addresses issue [#2162](undefined) opened by [@aersam](https://github.com/aersam)*
|
||||||
|
- [`d9f8910`](https://github.com/tobymao/sqlglot/commit/d9f89109e9795685392adb43bc2e87fbd346f263) - **teradata**: add support for the SAMPLE clause *(PR [#2169](https://github.com/tobymao/sqlglot/pull/2169) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- [`63ac621`](https://github.com/tobymao/sqlglot/commit/63ac621f7507d35ccdc32784ec0631437ddf0c1b) - **mysql**: improve support for unsigned int types *(PR [#2172](https://github.com/tobymao/sqlglot/pull/2172) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *addresses issue [#2166](undefined) opened by [@Nitrino](https://github.com/Nitrino)*
|
||||||
|
- [`cd301cc`](https://github.com/tobymao/sqlglot/commit/cd301cc9aa7a910fc6f7f0b9cc2dbba9a7d9ea24) - **postgres**: add support for ALTER TABLE ONLY ... *(PR [#2179](https://github.com/tobymao/sqlglot/pull/2179) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *addresses issue [#2178](undefined) opened by [@Nitrino](https://github.com/Nitrino)*
|
||||||
|
|
||||||
|
### :bug: Bug Fixes
|
||||||
|
- [`3fc2eb5`](https://github.com/tobymao/sqlglot/commit/3fc2eb581528504db4523c3e0a537000e026a4cc) - improve support for interval spans like HOUR TO SECOND *(PR [#2167](https://github.com/tobymao/sqlglot/pull/2167) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *fixes issue [#2163](undefined) opened by [@aersam](https://github.com/aersam)*
|
||||||
|
- [`93b7ba2`](https://github.com/tobymao/sqlglot/commit/93b7ba20640a880ceeb63660b796ab94579bb73a) - MySQL Timestamp Data Types *(PR [#2173](https://github.com/tobymao/sqlglot/pull/2173) by [@eakmanrq](https://github.com/eakmanrq))*
|
||||||
|
- [`6d761f9`](https://github.com/tobymao/sqlglot/commit/6d761f9934fcf57a06fb4645e43ce91dca6adc96) - filter_sql use strip closes [#2180](https://github.com/tobymao/sqlglot/pull/2180) *(commit by [@tobymao](https://github.com/tobymao))*
|
||||||
|
|
||||||
|
### :wrench: Chores
|
||||||
|
- [`5fbe303`](https://github.com/tobymao/sqlglot/commit/5fbe303504f19a1c949d0acf777c2bf2d3ecc1b6) - add minimum python version required to setup.py *(PR [#2170](https://github.com/tobymao/sqlglot/pull/2170) by [@GeorgeSittas](https://github.com/GeorgeSittas))*
|
||||||
|
- :arrow_lower_right: *addresses issue [#2168](undefined) opened by [@jlardieri5](https://github.com/jlardieri5)*
|
||||||
|
|
||||||
|
|
||||||
## [v18.2.0] - 2023-09-05
|
## [v18.2.0] - 2023-09-05
|
||||||
### :sparkles: New Features
|
### :sparkles: New Features
|
||||||
- [`5df9b5f`](https://github.com/tobymao/sqlglot/commit/5df9b5f658d24267e4f6b00bd89eb0b2f4dc5bfc) - **snowflake**: desc table type closes [#2145](https://github.com/tobymao/sqlglot/pull/2145) *(commit by [@tobymao](https://github.com/tobymao))*
|
- [`5df9b5f`](https://github.com/tobymao/sqlglot/commit/5df9b5f658d24267e4f6b00bd89eb0b2f4dc5bfc) - **snowflake**: desc table type closes [#2145](https://github.com/tobymao/sqlglot/pull/2145) *(commit by [@tobymao](https://github.com/tobymao))*
|
||||||
|
@ -1312,3 +1369,5 @@ Changelog
|
||||||
[v18.0.1]: https://github.com/tobymao/sqlglot/compare/v18.0.0...v18.0.1
|
[v18.0.1]: https://github.com/tobymao/sqlglot/compare/v18.0.0...v18.0.1
|
||||||
[v18.1.0]: https://github.com/tobymao/sqlglot/compare/v18.0.1...v18.1.0
|
[v18.1.0]: https://github.com/tobymao/sqlglot/compare/v18.0.1...v18.1.0
|
||||||
[v18.2.0]: https://github.com/tobymao/sqlglot/compare/v18.1.0...v18.2.0
|
[v18.2.0]: https://github.com/tobymao/sqlglot/compare/v18.1.0...v18.2.0
|
||||||
|
[v18.3.0]: https://github.com/tobymao/sqlglot/compare/v18.2.0...v18.3.0
|
||||||
|
[v18.4.0]: https://github.com/tobymao/sqlglot/compare/v18.3.0...v18.4.0
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
19551
docs/sqlglot/parser.html
19551
docs/sqlglot/parser.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -313,6 +313,8 @@ class ClickHouse(Dialect):
|
||||||
exp.DataType.Type.LONGTEXT: "String",
|
exp.DataType.Type.LONGTEXT: "String",
|
||||||
exp.DataType.Type.MEDIUMBLOB: "String",
|
exp.DataType.Type.MEDIUMBLOB: "String",
|
||||||
exp.DataType.Type.MEDIUMTEXT: "String",
|
exp.DataType.Type.MEDIUMTEXT: "String",
|
||||||
|
exp.DataType.Type.TINYBLOB: "String",
|
||||||
|
exp.DataType.Type.TINYTEXT: "String",
|
||||||
exp.DataType.Type.TEXT: "String",
|
exp.DataType.Type.TEXT: "String",
|
||||||
exp.DataType.Type.VARBINARY: "String",
|
exp.DataType.Type.VARBINARY: "String",
|
||||||
exp.DataType.Type.VARCHAR: "String",
|
exp.DataType.Type.VARCHAR: "String",
|
||||||
|
@ -331,6 +333,7 @@ class ClickHouse(Dialect):
|
||||||
exp.DataType.Type.FIXEDSTRING: "FixedString",
|
exp.DataType.Type.FIXEDSTRING: "FixedString",
|
||||||
exp.DataType.Type.FLOAT: "Float32",
|
exp.DataType.Type.FLOAT: "Float32",
|
||||||
exp.DataType.Type.INT: "Int32",
|
exp.DataType.Type.INT: "Int32",
|
||||||
|
exp.DataType.Type.MEDIUMINT: "Int32",
|
||||||
exp.DataType.Type.INT128: "Int128",
|
exp.DataType.Type.INT128: "Int128",
|
||||||
exp.DataType.Type.INT256: "Int256",
|
exp.DataType.Type.INT256: "Int256",
|
||||||
exp.DataType.Type.LOWCARDINALITY: "LowCardinality",
|
exp.DataType.Type.LOWCARDINALITY: "LowCardinality",
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import typing as t
|
import typing as t
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from sqlglot import exp
|
from sqlglot import exp
|
||||||
from sqlglot._typing import E
|
from sqlglot._typing import E
|
||||||
|
@ -656,11 +657,18 @@ def ts_or_ds_to_date_sql(dialect: str) -> t.Callable:
|
||||||
|
|
||||||
def concat_to_dpipe_sql(self: Generator, expression: exp.Concat | exp.SafeConcat) -> str:
|
def concat_to_dpipe_sql(self: Generator, expression: exp.Concat | exp.SafeConcat) -> str:
|
||||||
expression = expression.copy()
|
expression = expression.copy()
|
||||||
this, *rest_args = expression.expressions
|
return self.sql(reduce(lambda x, y: exp.DPipe(this=x, expression=y), expression.expressions))
|
||||||
for arg in rest_args:
|
|
||||||
this = exp.DPipe(this=this, expression=arg)
|
|
||||||
|
|
||||||
return self.sql(this)
|
|
||||||
|
def concat_ws_to_dpipe_sql(self: Generator, expression: exp.ConcatWs) -> str:
|
||||||
|
expression = expression.copy()
|
||||||
|
delim, *rest_args = expression.expressions
|
||||||
|
return self.sql(
|
||||||
|
reduce(
|
||||||
|
lambda x, y: exp.DPipe(this=x, expression=exp.DPipe(this=delim, expression=y)),
|
||||||
|
rest_args,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def regexp_extract_sql(self: Generator, expression: exp.RegexpExtract) -> str:
|
def regexp_extract_sql(self: Generator, expression: exp.RegexpExtract) -> str:
|
||||||
|
|
|
@ -291,6 +291,7 @@ class DuckDB(Dialect):
|
||||||
exp.UnixToStr: lambda self, e: f"STRFTIME(TO_TIMESTAMP({self.sql(e, 'this')}), {self.format_time(e)})",
|
exp.UnixToStr: lambda self, e: f"STRFTIME(TO_TIMESTAMP({self.sql(e, 'this')}), {self.format_time(e)})",
|
||||||
exp.UnixToTime: rename_func("TO_TIMESTAMP"),
|
exp.UnixToTime: rename_func("TO_TIMESTAMP"),
|
||||||
exp.UnixToTimeStr: lambda self, e: f"CAST(TO_TIMESTAMP({self.sql(e, 'this')}) AS TEXT)",
|
exp.UnixToTimeStr: lambda self, e: f"CAST(TO_TIMESTAMP({self.sql(e, 'this')}) AS TEXT)",
|
||||||
|
exp.VariancePop: rename_func("VAR_POP"),
|
||||||
exp.WeekOfYear: rename_func("WEEKOFYEAR"),
|
exp.WeekOfYear: rename_func("WEEKOFYEAR"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ class MySQL(Dialect):
|
||||||
QUOTES = ["'", '"']
|
QUOTES = ["'", '"']
|
||||||
COMMENTS = ["--", "#", ("/*", "*/")]
|
COMMENTS = ["--", "#", ("/*", "*/")]
|
||||||
IDENTIFIERS = ["`"]
|
IDENTIFIERS = ["`"]
|
||||||
STRING_ESCAPES = ["'", "\\"]
|
STRING_ESCAPES = ["'", '"', "\\"]
|
||||||
BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")]
|
BIT_STRINGS = [("b'", "'"), ("B'", "'"), ("0b", "")]
|
||||||
HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")]
|
HEX_STRINGS = [("x'", "'"), ("X'", "'"), ("0x", "")]
|
||||||
|
|
||||||
|
@ -132,6 +132,8 @@ class MySQL(Dialect):
|
||||||
"LONGBLOB": TokenType.LONGBLOB,
|
"LONGBLOB": TokenType.LONGBLOB,
|
||||||
"LONGTEXT": TokenType.LONGTEXT,
|
"LONGTEXT": TokenType.LONGTEXT,
|
||||||
"MEDIUMBLOB": TokenType.MEDIUMBLOB,
|
"MEDIUMBLOB": TokenType.MEDIUMBLOB,
|
||||||
|
"TINYBLOB": TokenType.TINYBLOB,
|
||||||
|
"TINYTEXT": TokenType.TINYTEXT,
|
||||||
"MEDIUMTEXT": TokenType.MEDIUMTEXT,
|
"MEDIUMTEXT": TokenType.MEDIUMTEXT,
|
||||||
"MEDIUMINT": TokenType.MEDIUMINT,
|
"MEDIUMINT": TokenType.MEDIUMINT,
|
||||||
"MEMBER OF": TokenType.MEMBER_OF,
|
"MEMBER OF": TokenType.MEMBER_OF,
|
||||||
|
@ -356,6 +358,15 @@ class MySQL(Dialect):
|
||||||
|
|
||||||
LOG_DEFAULTS_TO_LN = True
|
LOG_DEFAULTS_TO_LN = True
|
||||||
|
|
||||||
|
def _parse_primary_key_part(self) -> t.Optional[exp.Expression]:
|
||||||
|
this = self._parse_id_var()
|
||||||
|
if not self._match(TokenType.L_PAREN):
|
||||||
|
return this
|
||||||
|
|
||||||
|
expression = self._parse_number()
|
||||||
|
self._match_r_paren()
|
||||||
|
return self.expression(exp.ColumnPrefix, this=this, expression=expression)
|
||||||
|
|
||||||
def _parse_index_constraint(
|
def _parse_index_constraint(
|
||||||
self, kind: t.Optional[str] = None
|
self, kind: t.Optional[str] = None
|
||||||
) -> exp.IndexColumnConstraint:
|
) -> exp.IndexColumnConstraint:
|
||||||
|
@ -577,8 +588,10 @@ class MySQL(Dialect):
|
||||||
|
|
||||||
TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT)
|
TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMTEXT)
|
||||||
TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT)
|
TYPE_MAPPING.pop(exp.DataType.Type.LONGTEXT)
|
||||||
|
TYPE_MAPPING.pop(exp.DataType.Type.TINYTEXT)
|
||||||
TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB)
|
TYPE_MAPPING.pop(exp.DataType.Type.MEDIUMBLOB)
|
||||||
TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB)
|
TYPE_MAPPING.pop(exp.DataType.Type.LONGBLOB)
|
||||||
|
TYPE_MAPPING.pop(exp.DataType.Type.TINYBLOB)
|
||||||
|
|
||||||
PROPERTIES_LOCATION = {
|
PROPERTIES_LOCATION = {
|
||||||
**generator.Generator.PROPERTIES_LOCATION,
|
**generator.Generator.PROPERTIES_LOCATION,
|
||||||
|
|
|
@ -7,6 +7,9 @@ from sqlglot.dialects.dialect import Dialect, no_ilike_sql, rename_func, trim_sq
|
||||||
from sqlglot.helper import seq_get
|
from sqlglot.helper import seq_get
|
||||||
from sqlglot.tokens import TokenType
|
from sqlglot.tokens import TokenType
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from sqlglot._typing import E
|
||||||
|
|
||||||
|
|
||||||
def _parse_xml_table(self: Oracle.Parser) -> exp.XMLTable:
|
def _parse_xml_table(self: Oracle.Parser) -> exp.XMLTable:
|
||||||
this = self._parse_string()
|
this = self._parse_string()
|
||||||
|
@ -69,6 +72,16 @@ class Oracle(Dialect):
|
||||||
|
|
||||||
FUNCTION_PARSERS: t.Dict[str, t.Callable] = {
|
FUNCTION_PARSERS: t.Dict[str, t.Callable] = {
|
||||||
**parser.Parser.FUNCTION_PARSERS,
|
**parser.Parser.FUNCTION_PARSERS,
|
||||||
|
"JSON_ARRAY": lambda self: self._parse_json_array(
|
||||||
|
exp.JSONArray,
|
||||||
|
expressions=self._parse_csv(lambda: self._parse_format_json(self._parse_bitwise())),
|
||||||
|
),
|
||||||
|
"JSON_ARRAYAGG": lambda self: self._parse_json_array(
|
||||||
|
exp.JSONArrayAgg,
|
||||||
|
this=self._parse_format_json(self._parse_bitwise()),
|
||||||
|
order=self._parse_order(),
|
||||||
|
),
|
||||||
|
"JSON_TABLE": lambda self: self._parse_json_table(),
|
||||||
"XMLTABLE": _parse_xml_table,
|
"XMLTABLE": _parse_xml_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +95,38 @@ class Oracle(Dialect):
|
||||||
# Reference: https://stackoverflow.com/a/336455
|
# Reference: https://stackoverflow.com/a/336455
|
||||||
DISTINCT_TOKENS = {TokenType.DISTINCT, TokenType.UNIQUE}
|
DISTINCT_TOKENS = {TokenType.DISTINCT, TokenType.UNIQUE}
|
||||||
|
|
||||||
|
# Note: this is currently incomplete; it only implements the "JSON_value_column" part
|
||||||
|
def _parse_json_column_def(self) -> exp.JSONColumnDef:
|
||||||
|
this = self._parse_id_var()
|
||||||
|
kind = self._parse_types(allow_identifiers=False)
|
||||||
|
path = self._match_text_seq("PATH") and self._parse_string()
|
||||||
|
return self.expression(exp.JSONColumnDef, this=this, kind=kind, path=path)
|
||||||
|
|
||||||
|
def _parse_json_table(self) -> exp.JSONTable:
|
||||||
|
this = self._parse_format_json(self._parse_bitwise())
|
||||||
|
path = self._match(TokenType.COMMA) and self._parse_string()
|
||||||
|
error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
|
||||||
|
empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
|
||||||
|
self._match(TokenType.COLUMN)
|
||||||
|
expressions = self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
|
||||||
|
|
||||||
|
return exp.JSONTable(
|
||||||
|
this=this,
|
||||||
|
expressions=expressions,
|
||||||
|
path=path,
|
||||||
|
error_handling=error_handling,
|
||||||
|
empty_handling=empty_handling,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _parse_json_array(self, expr_type: t.Type[E], **kwargs) -> E:
|
||||||
|
return self.expression(
|
||||||
|
expr_type,
|
||||||
|
null_handling=self._parse_on_handling("NULL", "NULL", "ABSENT"),
|
||||||
|
return_type=self._match_text_seq("RETURNING") and self._parse_type(),
|
||||||
|
strict=self._match_text_seq("STRICT"),
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
def _parse_column(self) -> t.Optional[exp.Expression]:
|
def _parse_column(self) -> t.Optional[exp.Expression]:
|
||||||
column = super()._parse_column()
|
column = super()._parse_column()
|
||||||
if column:
|
if column:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import typing as t
|
||||||
from sqlglot import exp, transforms
|
from sqlglot import exp, transforms
|
||||||
from sqlglot.dialects.dialect import (
|
from sqlglot.dialects.dialect import (
|
||||||
concat_to_dpipe_sql,
|
concat_to_dpipe_sql,
|
||||||
|
concat_ws_to_dpipe_sql,
|
||||||
rename_func,
|
rename_func,
|
||||||
ts_or_ds_to_date_sql,
|
ts_or_ds_to_date_sql,
|
||||||
)
|
)
|
||||||
|
@ -123,6 +124,7 @@ class Redshift(Postgres):
|
||||||
TRANSFORMS = {
|
TRANSFORMS = {
|
||||||
**Postgres.Generator.TRANSFORMS,
|
**Postgres.Generator.TRANSFORMS,
|
||||||
exp.Concat: concat_to_dpipe_sql,
|
exp.Concat: concat_to_dpipe_sql,
|
||||||
|
exp.ConcatWs: concat_ws_to_dpipe_sql,
|
||||||
exp.CurrentTimestamp: lambda self, e: "SYSDATE",
|
exp.CurrentTimestamp: lambda self, e: "SYSDATE",
|
||||||
exp.DateAdd: lambda self, e: self.func(
|
exp.DateAdd: lambda self, e: self.func(
|
||||||
"DATEADD", exp.var(e.text("unit") or "day"), e.expression, e.this
|
"DATEADD", exp.var(e.text("unit") or "day"), e.expression, e.this
|
||||||
|
|
|
@ -20,6 +20,7 @@ import typing as t
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from enum import auto
|
from enum import auto
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from sqlglot._typing import E
|
from sqlglot._typing import E
|
||||||
from sqlglot.errors import ParseError
|
from sqlglot.errors import ParseError
|
||||||
|
@ -1170,7 +1171,7 @@ class Column(Condition):
|
||||||
parts.append(parent.expression)
|
parts.append(parent.expression)
|
||||||
parent = parent.parent
|
parent = parent.parent
|
||||||
|
|
||||||
return Dot.build(parts)
|
return Dot.build(deepcopy(parts))
|
||||||
|
|
||||||
|
|
||||||
class ColumnPosition(Expression):
|
class ColumnPosition(Expression):
|
||||||
|
@ -1537,6 +1538,10 @@ class ForeignKey(Expression):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ColumnPrefix(Expression):
|
||||||
|
arg_types = {"this": True, "expression": True}
|
||||||
|
|
||||||
|
|
||||||
class PrimaryKey(Expression):
|
class PrimaryKey(Expression):
|
||||||
arg_types = {"expressions": True, "options": False}
|
arg_types = {"expressions": True, "options": False}
|
||||||
|
|
||||||
|
@ -3529,6 +3534,8 @@ class DataType(Expression):
|
||||||
STRUCT = auto()
|
STRUCT = auto()
|
||||||
SUPER = auto()
|
SUPER = auto()
|
||||||
TEXT = auto()
|
TEXT = auto()
|
||||||
|
TINYBLOB = auto()
|
||||||
|
TINYTEXT = auto()
|
||||||
TIME = auto()
|
TIME = auto()
|
||||||
TIMETZ = auto()
|
TIMETZ = auto()
|
||||||
TIMESTAMP = auto()
|
TIMESTAMP = auto()
|
||||||
|
@ -3793,13 +3800,7 @@ class Dot(Binary):
|
||||||
if len(expressions) < 2:
|
if len(expressions) < 2:
|
||||||
raise ValueError(f"Dot requires >= 2 expressions.")
|
raise ValueError(f"Dot requires >= 2 expressions.")
|
||||||
|
|
||||||
a, b, *expressions = expressions
|
return t.cast(Dot, reduce(lambda x, y: Dot(this=x, expression=y), expressions))
|
||||||
dot = Dot(this=a, expression=b)
|
|
||||||
|
|
||||||
for expression in expressions:
|
|
||||||
dot = Dot(this=dot, expression=expression)
|
|
||||||
|
|
||||||
return dot
|
|
||||||
|
|
||||||
|
|
||||||
class DPipe(Binary):
|
class DPipe(Binary):
|
||||||
|
@ -3959,6 +3960,13 @@ class Between(Predicate):
|
||||||
class Bracket(Condition):
|
class Bracket(Condition):
|
||||||
arg_types = {"this": True, "expressions": True}
|
arg_types = {"this": True, "expressions": True}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_name(self) -> str:
|
||||||
|
if len(self.expressions) == 1:
|
||||||
|
return self.expressions[0].output_name
|
||||||
|
|
||||||
|
return super().output_name
|
||||||
|
|
||||||
|
|
||||||
class SafeBracket(Bracket):
|
class SafeBracket(Bracket):
|
||||||
"""Represents array lookup where OOB index yields NULL instead of causing a failure."""
|
"""Represents array lookup where OOB index yields NULL instead of causing a failure."""
|
||||||
|
@ -4477,6 +4485,10 @@ class IsNan(Func):
|
||||||
_sql_names = ["IS_NAN", "ISNAN"]
|
_sql_names = ["IS_NAN", "ISNAN"]
|
||||||
|
|
||||||
|
|
||||||
|
class FormatJson(Expression):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JSONKeyValue(Expression):
|
class JSONKeyValue(Expression):
|
||||||
arg_types = {"this": True, "expression": True}
|
arg_types = {"this": True, "expression": True}
|
||||||
|
|
||||||
|
@ -4487,11 +4499,48 @@ class JSONObject(Func):
|
||||||
"null_handling": False,
|
"null_handling": False,
|
||||||
"unique_keys": False,
|
"unique_keys": False,
|
||||||
"return_type": False,
|
"return_type": False,
|
||||||
"format_json": False,
|
|
||||||
"encoding": False,
|
"encoding": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAY.html
|
||||||
|
class JSONArray(Func):
|
||||||
|
arg_types = {
|
||||||
|
"expressions": True,
|
||||||
|
"null_handling": False,
|
||||||
|
"return_type": False,
|
||||||
|
"strict": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAYAGG.html
|
||||||
|
class JSONArrayAgg(Func):
|
||||||
|
arg_types = {
|
||||||
|
"this": True,
|
||||||
|
"order": False,
|
||||||
|
"null_handling": False,
|
||||||
|
"return_type": False,
|
||||||
|
"strict": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html
|
||||||
|
# Note: parsing of JSON column definitions is currently incomplete.
|
||||||
|
class JSONColumnDef(Expression):
|
||||||
|
arg_types = {"this": True, "kind": False, "path": False}
|
||||||
|
|
||||||
|
|
||||||
|
# # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html
|
||||||
|
class JSONTable(Func):
|
||||||
|
arg_types = {
|
||||||
|
"this": True,
|
||||||
|
"expressions": True,
|
||||||
|
"path": False,
|
||||||
|
"error_handling": False,
|
||||||
|
"empty_handling": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class OpenJSONColumnDef(Expression):
|
class OpenJSONColumnDef(Expression):
|
||||||
arg_types = {"this": True, "kind": True, "path": False, "as_json": False}
|
arg_types = {"this": True, "kind": True, "path": False, "as_json": False}
|
||||||
|
|
||||||
|
|
|
@ -193,8 +193,10 @@ class Generator:
|
||||||
exp.DataType.Type.NVARCHAR: "VARCHAR",
|
exp.DataType.Type.NVARCHAR: "VARCHAR",
|
||||||
exp.DataType.Type.MEDIUMTEXT: "TEXT",
|
exp.DataType.Type.MEDIUMTEXT: "TEXT",
|
||||||
exp.DataType.Type.LONGTEXT: "TEXT",
|
exp.DataType.Type.LONGTEXT: "TEXT",
|
||||||
|
exp.DataType.Type.TINYTEXT: "TEXT",
|
||||||
exp.DataType.Type.MEDIUMBLOB: "BLOB",
|
exp.DataType.Type.MEDIUMBLOB: "BLOB",
|
||||||
exp.DataType.Type.LONGBLOB: "BLOB",
|
exp.DataType.Type.LONGBLOB: "BLOB",
|
||||||
|
exp.DataType.Type.TINYBLOB: "BLOB",
|
||||||
exp.DataType.Type.INET: "INET",
|
exp.DataType.Type.INET: "INET",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2021,6 +2023,9 @@ class Generator:
|
||||||
def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
|
def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
|
||||||
return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}"
|
return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}"
|
||||||
|
|
||||||
|
def formatjson_sql(self, expression: exp.FormatJson) -> str:
|
||||||
|
return f"{self.sql(expression, 'this')} FORMAT JSON"
|
||||||
|
|
||||||
def jsonobject_sql(self, expression: exp.JSONObject) -> str:
|
def jsonobject_sql(self, expression: exp.JSONObject) -> str:
|
||||||
null_handling = expression.args.get("null_handling")
|
null_handling = expression.args.get("null_handling")
|
||||||
null_handling = f" {null_handling}" if null_handling else ""
|
null_handling = f" {null_handling}" if null_handling else ""
|
||||||
|
@ -2031,13 +2036,57 @@ class Generator:
|
||||||
unique_keys = ""
|
unique_keys = ""
|
||||||
return_type = self.sql(expression, "return_type")
|
return_type = self.sql(expression, "return_type")
|
||||||
return_type = f" RETURNING {return_type}" if return_type else ""
|
return_type = f" RETURNING {return_type}" if return_type else ""
|
||||||
format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
|
|
||||||
encoding = self.sql(expression, "encoding")
|
encoding = self.sql(expression, "encoding")
|
||||||
encoding = f" ENCODING {encoding}" if encoding else ""
|
encoding = f" ENCODING {encoding}" if encoding else ""
|
||||||
return self.func(
|
return self.func(
|
||||||
"JSON_OBJECT",
|
"JSON_OBJECT",
|
||||||
*expression.expressions,
|
*expression.expressions,
|
||||||
suffix=f"{null_handling}{unique_keys}{return_type}{format_json}{encoding})",
|
suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
|
||||||
|
)
|
||||||
|
|
||||||
|
def jsonarray_sql(self, expression: exp.JSONArray) -> str:
|
||||||
|
null_handling = expression.args.get("null_handling")
|
||||||
|
null_handling = f" {null_handling}" if null_handling else ""
|
||||||
|
return_type = self.sql(expression, "return_type")
|
||||||
|
return_type = f" RETURNING {return_type}" if return_type else ""
|
||||||
|
strict = " STRICT" if expression.args.get("strict") else ""
|
||||||
|
return self.func(
|
||||||
|
"JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
|
||||||
|
this = self.sql(expression, "this")
|
||||||
|
order = self.sql(expression, "order")
|
||||||
|
null_handling = expression.args.get("null_handling")
|
||||||
|
null_handling = f" {null_handling}" if null_handling else ""
|
||||||
|
return_type = self.sql(expression, "return_type")
|
||||||
|
return_type = f" RETURNING {return_type}" if return_type else ""
|
||||||
|
strict = " STRICT" if expression.args.get("strict") else ""
|
||||||
|
return self.func(
|
||||||
|
"JSON_ARRAYAGG",
|
||||||
|
this,
|
||||||
|
suffix=f"{order}{null_handling}{return_type}{strict})",
|
||||||
|
)
|
||||||
|
|
||||||
|
def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
|
||||||
|
this = self.sql(expression, "this")
|
||||||
|
kind = self.sql(expression, "kind")
|
||||||
|
kind = f" {kind}" if kind else ""
|
||||||
|
path = self.sql(expression, "path")
|
||||||
|
path = f" PATH {path}" if path else ""
|
||||||
|
return f"{this}{kind}{path}"
|
||||||
|
|
||||||
|
def jsontable_sql(self, expression: exp.JSONTable) -> str:
|
||||||
|
this = self.sql(expression, "this")
|
||||||
|
path = self.sql(expression, "path")
|
||||||
|
path = f", {path}" if path else ""
|
||||||
|
error_handling = expression.args.get("error_handling")
|
||||||
|
error_handling = f" {error_handling}" if error_handling else ""
|
||||||
|
empty_handling = expression.args.get("empty_handling")
|
||||||
|
empty_handling = f" {empty_handling}" if empty_handling else ""
|
||||||
|
columns = f" COLUMNS ({self.expressions(expression, skip_first=True)})"
|
||||||
|
return self.func(
|
||||||
|
"JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling}{columns})"
|
||||||
)
|
)
|
||||||
|
|
||||||
def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
|
def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
|
||||||
|
@ -2722,6 +2771,9 @@ class Generator:
|
||||||
condition = f" IF {condition}" if condition else ""
|
condition = f" IF {condition}" if condition else ""
|
||||||
return f"{this} FOR {expr} IN {iterator}{condition}"
|
return f"{this} FOR {expr} IN {iterator}{condition}"
|
||||||
|
|
||||||
|
def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
|
||||||
|
return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
|
||||||
|
|
||||||
|
|
||||||
def cached_generator(
|
def cached_generator(
|
||||||
cache: t.Optional[t.Dict[int, str]] = None
|
cache: t.Optional[t.Dict[int, str]] = None
|
||||||
|
|
|
@ -128,7 +128,7 @@ def _mergeable(outer_scope, inner_scope, leave_tables_isolated, from_or_join):
|
||||||
def _is_a_window_expression_in_unmergable_operation():
|
def _is_a_window_expression_in_unmergable_operation():
|
||||||
window_expressions = inner_select.find_all(exp.Window)
|
window_expressions = inner_select.find_all(exp.Window)
|
||||||
window_alias_names = {window.parent.alias_or_name for window in window_expressions}
|
window_alias_names = {window.parent.alias_or_name for window in window_expressions}
|
||||||
inner_select_name = inner_select.parent.alias_or_name
|
inner_select_name = from_or_join.alias_or_name
|
||||||
unmergable_window_columns = [
|
unmergable_window_columns = [
|
||||||
column
|
column
|
||||||
for column in outer_scope.columns
|
for column in outer_scope.columns
|
||||||
|
|
|
@ -129,7 +129,7 @@ def _expand_using(scope: Scope, resolver: Resolver) -> t.Dict[str, t.Any]:
|
||||||
table = columns.get(identifier)
|
table = columns.get(identifier)
|
||||||
|
|
||||||
if not table or identifier not in join_columns:
|
if not table or identifier not in join_columns:
|
||||||
if columns and join_columns:
|
if (columns and "*" not in columns) and join_columns:
|
||||||
raise OptimizeError(f"Cannot automatically join: {identifier}")
|
raise OptimizeError(f"Cannot automatically join: {identifier}")
|
||||||
|
|
||||||
table = table or source_table
|
table = table or source_table
|
||||||
|
|
|
@ -155,6 +155,8 @@ class Parser(metaclass=_Parser):
|
||||||
TokenType.JSON,
|
TokenType.JSON,
|
||||||
TokenType.JSONB,
|
TokenType.JSONB,
|
||||||
TokenType.INTERVAL,
|
TokenType.INTERVAL,
|
||||||
|
TokenType.TINYBLOB,
|
||||||
|
TokenType.TINYTEXT,
|
||||||
TokenType.TIME,
|
TokenType.TIME,
|
||||||
TokenType.TIMETZ,
|
TokenType.TIMETZ,
|
||||||
TokenType.TIMESTAMP,
|
TokenType.TIMESTAMP,
|
||||||
|
@ -764,6 +766,7 @@ class Parser(metaclass=_Parser):
|
||||||
"ANY_VALUE": lambda self: self._parse_any_value(),
|
"ANY_VALUE": lambda self: self._parse_any_value(),
|
||||||
"CAST": lambda self: self._parse_cast(self.STRICT_CAST),
|
"CAST": lambda self: self._parse_cast(self.STRICT_CAST),
|
||||||
"CONCAT": lambda self: self._parse_concat(),
|
"CONCAT": lambda self: self._parse_concat(),
|
||||||
|
"CONCAT_WS": lambda self: self._parse_concat_ws(),
|
||||||
"CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
|
"CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
|
||||||
"DECODE": lambda self: self._parse_decode(),
|
"DECODE": lambda self: self._parse_decode(),
|
||||||
"EXTRACT": lambda self: self._parse_extract(),
|
"EXTRACT": lambda self: self._parse_extract(),
|
||||||
|
@ -1942,7 +1945,7 @@ class Parser(metaclass=_Parser):
|
||||||
|
|
||||||
def _parse_update(self) -> exp.Update:
|
def _parse_update(self) -> exp.Update:
|
||||||
comments = self._prev_comments
|
comments = self._prev_comments
|
||||||
this = self._parse_table(alias_tokens=self.UPDATE_ALIAS_TOKENS)
|
this = self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS)
|
||||||
expressions = self._match(TokenType.SET) and self._parse_csv(self._parse_equality)
|
expressions = self._match(TokenType.SET) and self._parse_csv(self._parse_equality)
|
||||||
returning = self._parse_returning()
|
returning = self._parse_returning()
|
||||||
return self.expression(
|
return self.expression(
|
||||||
|
@ -3269,7 +3272,7 @@ class Parser(metaclass=_Parser):
|
||||||
if tokens[0].token_type in self.TYPE_TOKENS:
|
if tokens[0].token_type in self.TYPE_TOKENS:
|
||||||
self._prev = tokens[0]
|
self._prev = tokens[0]
|
||||||
elif self.SUPPORTS_USER_DEFINED_TYPES:
|
elif self.SUPPORTS_USER_DEFINED_TYPES:
|
||||||
return identifier
|
return exp.DataType.build(identifier.name, udt=True)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
@ -3888,6 +3891,9 @@ class Parser(metaclass=_Parser):
|
||||||
exp.ForeignKey, expressions=expressions, reference=reference, **options # type: ignore
|
exp.ForeignKey, expressions=expressions, reference=reference, **options # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _parse_primary_key_part(self) -> t.Optional[exp.Expression]:
|
||||||
|
return self._parse_field()
|
||||||
|
|
||||||
def _parse_primary_key(
|
def _parse_primary_key(
|
||||||
self, wrapped_optional: bool = False, in_props: bool = False
|
self, wrapped_optional: bool = False, in_props: bool = False
|
||||||
) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
|
) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
|
||||||
|
@ -3899,7 +3905,9 @@ class Parser(metaclass=_Parser):
|
||||||
if not in_props and not self._match(TokenType.L_PAREN, advance=False):
|
if not in_props and not self._match(TokenType.L_PAREN, advance=False):
|
||||||
return self.expression(exp.PrimaryKeyColumnConstraint, desc=desc)
|
return self.expression(exp.PrimaryKeyColumnConstraint, desc=desc)
|
||||||
|
|
||||||
expressions = self._parse_wrapped_csv(self._parse_field, optional=wrapped_optional)
|
expressions = self._parse_wrapped_csv(
|
||||||
|
self._parse_primary_key_part, optional=wrapped_optional
|
||||||
|
)
|
||||||
options = self._parse_key_constraint_options()
|
options = self._parse_key_constraint_options()
|
||||||
return self.expression(exp.PrimaryKey, expressions=expressions, options=options)
|
return self.expression(exp.PrimaryKey, expressions=expressions, options=options)
|
||||||
|
|
||||||
|
@ -4066,11 +4074,7 @@ class Parser(metaclass=_Parser):
|
||||||
def _parse_concat(self) -> t.Optional[exp.Expression]:
|
def _parse_concat(self) -> t.Optional[exp.Expression]:
|
||||||
args = self._parse_csv(self._parse_conjunction)
|
args = self._parse_csv(self._parse_conjunction)
|
||||||
if self.CONCAT_NULL_OUTPUTS_STRING:
|
if self.CONCAT_NULL_OUTPUTS_STRING:
|
||||||
args = [
|
args = self._ensure_string_if_null(args)
|
||||||
exp.func("COALESCE", exp.cast(arg, "text"), exp.Literal.string(""))
|
|
||||||
for arg in args
|
|
||||||
if arg
|
|
||||||
]
|
|
||||||
|
|
||||||
# Some dialects (e.g. Trino) don't allow a single-argument CONCAT call, so when
|
# Some dialects (e.g. Trino) don't allow a single-argument CONCAT call, so when
|
||||||
# we find such a call we replace it with its argument.
|
# we find such a call we replace it with its argument.
|
||||||
|
@ -4081,6 +4085,16 @@ class Parser(metaclass=_Parser):
|
||||||
exp.Concat if self.STRICT_STRING_CONCAT else exp.SafeConcat, expressions=args
|
exp.Concat if self.STRICT_STRING_CONCAT else exp.SafeConcat, expressions=args
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _parse_concat_ws(self) -> t.Optional[exp.Expression]:
|
||||||
|
args = self._parse_csv(self._parse_conjunction)
|
||||||
|
if len(args) < 2:
|
||||||
|
return self.expression(exp.ConcatWs, expressions=args)
|
||||||
|
delim, *values = args
|
||||||
|
if self.CONCAT_NULL_OUTPUTS_STRING:
|
||||||
|
values = self._ensure_string_if_null(values)
|
||||||
|
|
||||||
|
return self.expression(exp.ConcatWs, expressions=[delim] + values)
|
||||||
|
|
||||||
def _parse_string_agg(self) -> exp.Expression:
|
def _parse_string_agg(self) -> exp.Expression:
|
||||||
if self._match(TokenType.DISTINCT):
|
if self._match(TokenType.DISTINCT):
|
||||||
args: t.List[t.Optional[exp.Expression]] = [
|
args: t.List[t.Optional[exp.Expression]] = [
|
||||||
|
@ -4181,15 +4195,28 @@ class Parser(metaclass=_Parser):
|
||||||
return None
|
return None
|
||||||
return self.expression(exp.JSONKeyValue, this=key, expression=value)
|
return self.expression(exp.JSONKeyValue, this=key, expression=value)
|
||||||
|
|
||||||
|
def _parse_format_json(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]:
|
||||||
|
if not this or not self._match_text_seq("FORMAT", "JSON"):
|
||||||
|
return this
|
||||||
|
|
||||||
|
return self.expression(exp.FormatJson, this=this)
|
||||||
|
|
||||||
|
def _parse_on_handling(self, on: str, *values: str) -> t.Optional[str]:
|
||||||
|
# Parses the "X ON Y" syntax, i.e. NULL ON NULL (Oracle, T-SQL)
|
||||||
|
for value in values:
|
||||||
|
if self._match_text_seq(value, "ON", on):
|
||||||
|
return f"{value} ON {on}"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _parse_json_object(self) -> exp.JSONObject:
|
def _parse_json_object(self) -> exp.JSONObject:
|
||||||
star = self._parse_star()
|
star = self._parse_star()
|
||||||
expressions = [star] if star else self._parse_csv(self._parse_json_key_value)
|
expressions = (
|
||||||
|
[star]
|
||||||
null_handling = None
|
if star
|
||||||
if self._match_text_seq("NULL", "ON", "NULL"):
|
else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
|
||||||
null_handling = "NULL ON NULL"
|
)
|
||||||
elif self._match_text_seq("ABSENT", "ON", "NULL"):
|
null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
|
||||||
null_handling = "ABSENT ON NULL"
|
|
||||||
|
|
||||||
unique_keys = None
|
unique_keys = None
|
||||||
if self._match_text_seq("WITH", "UNIQUE"):
|
if self._match_text_seq("WITH", "UNIQUE"):
|
||||||
|
@ -4199,8 +4226,9 @@ class Parser(metaclass=_Parser):
|
||||||
|
|
||||||
self._match_text_seq("KEYS")
|
self._match_text_seq("KEYS")
|
||||||
|
|
||||||
return_type = self._match_text_seq("RETURNING") and self._parse_type()
|
return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
|
||||||
format_json = self._match_text_seq("FORMAT", "JSON")
|
self._parse_type()
|
||||||
|
)
|
||||||
encoding = self._match_text_seq("ENCODING") and self._parse_var()
|
encoding = self._match_text_seq("ENCODING") and self._parse_var()
|
||||||
|
|
||||||
return self.expression(
|
return self.expression(
|
||||||
|
@ -4209,7 +4237,6 @@ class Parser(metaclass=_Parser):
|
||||||
null_handling=null_handling,
|
null_handling=null_handling,
|
||||||
unique_keys=unique_keys,
|
unique_keys=unique_keys,
|
||||||
return_type=return_type,
|
return_type=return_type,
|
||||||
format_json=format_json,
|
|
||||||
encoding=encoding,
|
encoding=encoding,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4979,9 +5006,12 @@ class Parser(metaclass=_Parser):
|
||||||
self._match_r_paren()
|
self._match_r_paren()
|
||||||
return self.expression(exp.DictRange, this=this, min=min, max=max)
|
return self.expression(exp.DictRange, this=this, min=min, max=max)
|
||||||
|
|
||||||
def _parse_comprehension(self, this: exp.Expression) -> exp.Comprehension:
|
def _parse_comprehension(self, this: exp.Expression) -> t.Optional[exp.Comprehension]:
|
||||||
|
index = self._index
|
||||||
expression = self._parse_column()
|
expression = self._parse_column()
|
||||||
self._match(TokenType.IN)
|
if not self._match(TokenType.IN):
|
||||||
|
self._retreat(index - 1)
|
||||||
|
return None
|
||||||
iterator = self._parse_column()
|
iterator = self._parse_column()
|
||||||
condition = self._parse_conjunction() if self._match_text_seq("IF") else None
|
condition = self._parse_conjunction() if self._match_text_seq("IF") else None
|
||||||
return self.expression(
|
return self.expression(
|
||||||
|
@ -5125,3 +5155,10 @@ class Parser(metaclass=_Parser):
|
||||||
else:
|
else:
|
||||||
column.replace(dot_or_id)
|
column.replace(dot_or_id)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _ensure_string_if_null(self, values: t.List[exp.Expression]) -> t.List[exp.Expression]:
|
||||||
|
return [
|
||||||
|
exp.func("COALESCE", exp.cast(value, "text"), exp.Literal.string(""))
|
||||||
|
for value in values
|
||||||
|
if value
|
||||||
|
]
|
||||||
|
|
|
@ -108,6 +108,8 @@ class TokenType(AutoName):
|
||||||
LONGTEXT = auto()
|
LONGTEXT = auto()
|
||||||
MEDIUMBLOB = auto()
|
MEDIUMBLOB = auto()
|
||||||
LONGBLOB = auto()
|
LONGBLOB = auto()
|
||||||
|
TINYBLOB = auto()
|
||||||
|
TINYTEXT = auto()
|
||||||
BINARY = auto()
|
BINARY = auto()
|
||||||
VARBINARY = auto()
|
VARBINARY = auto()
|
||||||
JSON = auto()
|
JSON = auto()
|
||||||
|
@ -675,6 +677,7 @@ class Tokenizer(metaclass=_Tokenizer):
|
||||||
"BOOL": TokenType.BOOLEAN,
|
"BOOL": TokenType.BOOLEAN,
|
||||||
"BOOLEAN": TokenType.BOOLEAN,
|
"BOOLEAN": TokenType.BOOLEAN,
|
||||||
"BYTE": TokenType.TINYINT,
|
"BYTE": TokenType.TINYINT,
|
||||||
|
"MEDIUMINT": TokenType.MEDIUMINT,
|
||||||
"TINYINT": TokenType.TINYINT,
|
"TINYINT": TokenType.TINYINT,
|
||||||
"SHORT": TokenType.SMALLINT,
|
"SHORT": TokenType.SMALLINT,
|
||||||
"SMALLINT": TokenType.SMALLINT,
|
"SMALLINT": TokenType.SMALLINT,
|
||||||
|
@ -712,10 +715,16 @@ class Tokenizer(metaclass=_Tokenizer):
|
||||||
"STR": TokenType.TEXT,
|
"STR": TokenType.TEXT,
|
||||||
"STRING": TokenType.TEXT,
|
"STRING": TokenType.TEXT,
|
||||||
"TEXT": TokenType.TEXT,
|
"TEXT": TokenType.TEXT,
|
||||||
|
"LONGTEXT": TokenType.LONGTEXT,
|
||||||
|
"MEDIUMTEXT": TokenType.MEDIUMTEXT,
|
||||||
|
"TINYTEXT": TokenType.TINYTEXT,
|
||||||
"CLOB": TokenType.TEXT,
|
"CLOB": TokenType.TEXT,
|
||||||
"LONGVARCHAR": TokenType.TEXT,
|
"LONGVARCHAR": TokenType.TEXT,
|
||||||
"BINARY": TokenType.BINARY,
|
"BINARY": TokenType.BINARY,
|
||||||
"BLOB": TokenType.VARBINARY,
|
"BLOB": TokenType.VARBINARY,
|
||||||
|
"LONGBLOB": TokenType.LONGBLOB,
|
||||||
|
"MEDIUMBLOB": TokenType.MEDIUMBLOB,
|
||||||
|
"TINYBLOB": TokenType.TINYBLOB,
|
||||||
"BYTEA": TokenType.VARBINARY,
|
"BYTEA": TokenType.VARBINARY,
|
||||||
"VARBINARY": TokenType.VARBINARY,
|
"VARBINARY": TokenType.VARBINARY,
|
||||||
"TIME": TokenType.TIME,
|
"TIME": TokenType.TIME,
|
||||||
|
@ -1159,7 +1168,11 @@ class Tokenizer(metaclass=_Tokenizer):
|
||||||
escapes = self._STRING_ESCAPES if escapes is None else escapes
|
escapes = self._STRING_ESCAPES if escapes is None else escapes
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if self._char in escapes and (self._peek == delimiter or self._peek in escapes):
|
if (
|
||||||
|
self._char in escapes
|
||||||
|
and (self._peek == delimiter or self._peek in escapes)
|
||||||
|
and (self._char not in self._QUOTES or self._char == self._peek)
|
||||||
|
):
|
||||||
if self._peek == delimiter:
|
if self._peek == delimiter:
|
||||||
text += self._peek
|
text += self._peek
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -6,30 +6,19 @@ class TestClickhouse(Validator):
|
||||||
dialect = "clickhouse"
|
dialect = "clickhouse"
|
||||||
|
|
||||||
def test_clickhouse(self):
|
def test_clickhouse(self):
|
||||||
self.validate_all(
|
string_types = [
|
||||||
"DATE_ADD('day', 1, x)",
|
"BLOB",
|
||||||
read={
|
"LONGBLOB",
|
||||||
"clickhouse": "dateAdd(day, 1, x)",
|
"LONGTEXT",
|
||||||
"presto": "DATE_ADD('day', 1, x)",
|
"MEDIUMBLOB",
|
||||||
},
|
"MEDIUMTEXT",
|
||||||
write={
|
"TINYBLOB",
|
||||||
"clickhouse": "DATE_ADD('day', 1, x)",
|
"TINYTEXT",
|
||||||
"presto": "DATE_ADD('day', 1, x)",
|
"VARCHAR(255)",
|
||||||
"": "DATE_ADD(x, 1, 'day')",
|
]
|
||||||
},
|
|
||||||
)
|
for string_type in string_types:
|
||||||
self.validate_all(
|
self.validate_identity(f"CAST(x AS {string_type})", "CAST(x AS String)")
|
||||||
"DATE_DIFF('day', a, b)",
|
|
||||||
read={
|
|
||||||
"clickhouse": "dateDiff('day', a, b)",
|
|
||||||
"presto": "DATE_DIFF('day', a, b)",
|
|
||||||
},
|
|
||||||
write={
|
|
||||||
"clickhouse": "DATE_DIFF('day', a, b)",
|
|
||||||
"presto": "DATE_DIFF('day', a, b)",
|
|
||||||
"": "DATEDIFF(b, a, day)",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
expr = parse_one("count(x)")
|
expr = parse_one("count(x)")
|
||||||
self.assertEqual(expr.sql(dialect="clickhouse"), "COUNT(x)")
|
self.assertEqual(expr.sql(dialect="clickhouse"), "COUNT(x)")
|
||||||
|
@ -72,8 +61,8 @@ class TestClickhouse(Validator):
|
||||||
self.validate_identity("position(haystack, needle)")
|
self.validate_identity("position(haystack, needle)")
|
||||||
self.validate_identity("position(haystack, needle, position)")
|
self.validate_identity("position(haystack, needle, position)")
|
||||||
self.validate_identity("CAST(x AS DATETIME)")
|
self.validate_identity("CAST(x AS DATETIME)")
|
||||||
self.validate_identity("CAST(x AS VARCHAR(255))", "CAST(x AS String)")
|
self.validate_identity("CAST(x as MEDIUMINT)", "CAST(x AS Int32)")
|
||||||
self.validate_identity("CAST(x AS BLOB)", "CAST(x AS String)")
|
|
||||||
self.validate_identity(
|
self.validate_identity(
|
||||||
'SELECT CAST(tuple(1 AS "a", 2 AS "b", 3.0 AS "c").2 AS Nullable(String))'
|
'SELECT CAST(tuple(1 AS "a", 2 AS "b", 3.0 AS "c").2 AS Nullable(String))'
|
||||||
)
|
)
|
||||||
|
@ -93,6 +82,30 @@ class TestClickhouse(Validator):
|
||||||
"CREATE MATERIALIZED VIEW test_view (id UInt8) TO db.table1 AS SELECT * FROM test_data"
|
"CREATE MATERIALIZED VIEW test_view (id UInt8) TO db.table1 AS SELECT * FROM test_data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.validate_all(
|
||||||
|
"DATE_ADD('day', 1, x)",
|
||||||
|
read={
|
||||||
|
"clickhouse": "dateAdd(day, 1, x)",
|
||||||
|
"presto": "DATE_ADD('day', 1, x)",
|
||||||
|
},
|
||||||
|
write={
|
||||||
|
"clickhouse": "DATE_ADD('day', 1, x)",
|
||||||
|
"presto": "DATE_ADD('day', 1, x)",
|
||||||
|
"": "DATE_ADD(x, 1, 'day')",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.validate_all(
|
||||||
|
"DATE_DIFF('day', a, b)",
|
||||||
|
read={
|
||||||
|
"clickhouse": "dateDiff('day', a, b)",
|
||||||
|
"presto": "DATE_DIFF('day', a, b)",
|
||||||
|
},
|
||||||
|
write={
|
||||||
|
"clickhouse": "DATE_DIFF('day', a, b)",
|
||||||
|
"presto": "DATE_DIFF('day', a, b)",
|
||||||
|
"": "DATEDIFF(b, a, day)",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
"SELECT xor(1, 0)",
|
"SELECT xor(1, 0)",
|
||||||
read={
|
read={
|
||||||
|
|
|
@ -19,6 +19,7 @@ class TestDuckDB(Validator):
|
||||||
parse_one("a // b", read="duckdb").assert_is(exp.IntDiv).sql(dialect="duckdb"), "a // b"
|
parse_one("a // b", read="duckdb").assert_is(exp.IntDiv).sql(dialect="duckdb"), "a // b"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.validate_identity("VAR_POP(a)")
|
||||||
self.validate_identity("SELECT * FROM foo ASOF LEFT JOIN bar ON a = b")
|
self.validate_identity("SELECT * FROM foo ASOF LEFT JOIN bar ON a = b")
|
||||||
self.validate_identity("PIVOT Cities ON Year USING SUM(Population)")
|
self.validate_identity("PIVOT Cities ON Year USING SUM(Population)")
|
||||||
self.validate_identity("PIVOT Cities ON Year USING FIRST(Population)")
|
self.validate_identity("PIVOT Cities ON Year USING FIRST(Population)")
|
||||||
|
@ -34,6 +35,9 @@ class TestDuckDB(Validator):
|
||||||
self.validate_identity("SELECT (x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y)")
|
self.validate_identity("SELECT (x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y)")
|
||||||
self.validate_identity("SELECT a.x FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a)")
|
self.validate_identity("SELECT a.x FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a)")
|
||||||
self.validate_identity("ATTACH DATABASE ':memory:' AS new_database")
|
self.validate_identity("ATTACH DATABASE ':memory:' AS new_database")
|
||||||
|
self.validate_identity("FROM x SELECT x UNION SELECT 1", "SELECT x FROM x UNION SELECT 1")
|
||||||
|
self.validate_identity("FROM (FROM tbl)", "SELECT * FROM (SELECT * FROM tbl)")
|
||||||
|
self.validate_identity("FROM tbl", "SELECT * FROM tbl")
|
||||||
self.validate_identity(
|
self.validate_identity(
|
||||||
"SELECT {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'}"
|
"SELECT {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'}"
|
||||||
)
|
)
|
||||||
|
@ -53,13 +57,19 @@ class TestDuckDB(Validator):
|
||||||
"SELECT * FROM (PIVOT Cities ON Year USING SUM(Population) GROUP BY Country) AS pivot_alias"
|
"SELECT * FROM (PIVOT Cities ON Year USING SUM(Population) GROUP BY Country) AS pivot_alias"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.validate_identity("FROM x SELECT x UNION SELECT 1", "SELECT x FROM x UNION SELECT 1")
|
|
||||||
self.validate_all("FROM (FROM tbl)", write={"duckdb": "SELECT * FROM (SELECT * FROM tbl)"})
|
|
||||||
self.validate_all("FROM tbl", write={"duckdb": "SELECT * FROM tbl"})
|
|
||||||
self.validate_all("0b1010", write={"": "0 AS b1010"})
|
self.validate_all("0b1010", write={"": "0 AS b1010"})
|
||||||
self.validate_all("0x1010", write={"": "0 AS x1010"})
|
self.validate_all("0x1010", write={"": "0 AS x1010"})
|
||||||
self.validate_all("x ~ y", write={"duckdb": "REGEXP_MATCHES(x, y)"})
|
self.validate_all("x ~ y", write={"duckdb": "REGEXP_MATCHES(x, y)"})
|
||||||
self.validate_all("SELECT * FROM 'x.y'", write={"duckdb": 'SELECT * FROM "x.y"'})
|
self.validate_all("SELECT * FROM 'x.y'", write={"duckdb": 'SELECT * FROM "x.y"'})
|
||||||
|
self.validate_all(
|
||||||
|
"VAR_POP(x)",
|
||||||
|
read={
|
||||||
|
"": "VARIANCE_POP(x)",
|
||||||
|
},
|
||||||
|
write={
|
||||||
|
"": "VARIANCE_POP(x)",
|
||||||
|
},
|
||||||
|
)
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
"DATE_DIFF('day', CAST(b AS DATE), CAST(a AS DATE))",
|
"DATE_DIFF('day', CAST(b AS DATE), CAST(a AS DATE))",
|
||||||
read={
|
read={
|
||||||
|
|
|
@ -26,6 +26,9 @@ class TestMySQL(Validator):
|
||||||
self.validate_identity("CREATE TABLE foo (a BIGINT, INDEX USING BTREE (b))")
|
self.validate_identity("CREATE TABLE foo (a BIGINT, INDEX USING BTREE (b))")
|
||||||
self.validate_identity("CREATE TABLE foo (a BIGINT, FULLTEXT INDEX (b))")
|
self.validate_identity("CREATE TABLE foo (a BIGINT, FULLTEXT INDEX (b))")
|
||||||
self.validate_identity("CREATE TABLE foo (a BIGINT, SPATIAL INDEX (b))")
|
self.validate_identity("CREATE TABLE foo (a BIGINT, SPATIAL INDEX (b))")
|
||||||
|
self.validate_identity(
|
||||||
|
"CREATE TABLE `x` (`username` VARCHAR(200), PRIMARY KEY (`username`(16)))"
|
||||||
|
)
|
||||||
self.validate_identity(
|
self.validate_identity(
|
||||||
"UPDATE items SET items.price = 0 WHERE items.id >= 5 ORDER BY items.id LIMIT 10"
|
"UPDATE items SET items.price = 0 WHERE items.id >= 5 ORDER BY items.id LIMIT 10"
|
||||||
)
|
)
|
||||||
|
@ -204,21 +207,21 @@ class TestMySQL(Validator):
|
||||||
self.validate_identity("CAST(x AS MEDIUMINT) + CAST(y AS YEAR(4))")
|
self.validate_identity("CAST(x AS MEDIUMINT) + CAST(y AS YEAR(4))")
|
||||||
|
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
"CAST(x AS MEDIUMTEXT) + CAST(y AS LONGTEXT)",
|
"CAST(x AS MEDIUMTEXT) + CAST(y AS LONGTEXT) + CAST(z AS TINYTEXT)",
|
||||||
read={
|
read={
|
||||||
"mysql": "CAST(x AS MEDIUMTEXT) + CAST(y AS LONGTEXT)",
|
"mysql": "CAST(x AS MEDIUMTEXT) + CAST(y AS LONGTEXT) + CAST(z AS TINYTEXT)",
|
||||||
},
|
},
|
||||||
write={
|
write={
|
||||||
"spark": "CAST(x AS TEXT) + CAST(y AS TEXT)",
|
"spark": "CAST(x AS TEXT) + CAST(y AS TEXT) + CAST(z AS TEXT)",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
"CAST(x AS MEDIUMBLOB) + CAST(y AS LONGBLOB)",
|
"CAST(x AS MEDIUMBLOB) + CAST(y AS LONGBLOB) + CAST(z AS TINYBLOB)",
|
||||||
read={
|
read={
|
||||||
"mysql": "CAST(x AS MEDIUMBLOB) + CAST(y AS LONGBLOB)",
|
"mysql": "CAST(x AS MEDIUMBLOB) + CAST(y AS LONGBLOB) + CAST(z AS TINYBLOB)",
|
||||||
},
|
},
|
||||||
write={
|
write={
|
||||||
"spark": "CAST(x AS BLOB) + CAST(y AS BLOB)",
|
"spark": "CAST(x AS BLOB) + CAST(y AS BLOB) + CAST(z AS BLOB)",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.validate_all("CAST(x AS TIMESTAMP)", write={"mysql": "CAST(x AS DATETIME)"})
|
self.validate_all("CAST(x AS TIMESTAMP)", write={"mysql": "CAST(x AS DATETIME)"})
|
||||||
|
@ -240,6 +243,15 @@ class TestMySQL(Validator):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_escape(self):
|
def test_escape(self):
|
||||||
|
self.validate_identity("""'"abc"'""")
|
||||||
|
self.validate_identity(
|
||||||
|
r"'\'a'",
|
||||||
|
"'''a'",
|
||||||
|
)
|
||||||
|
self.validate_identity(
|
||||||
|
'''"'abc'"''',
|
||||||
|
"'''abc'''",
|
||||||
|
)
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
r"'a \' b '' '",
|
r"'a \' b '' '",
|
||||||
write={
|
write={
|
||||||
|
@ -525,6 +537,7 @@ class TestMySQL(Validator):
|
||||||
"mysql": "SELECT DATE(DATE_SUB(`dt`, INTERVAL (DAYOFMONTH(`dt`) - 1) DAY)) AS __timestamp FROM tableT",
|
"mysql": "SELECT DATE(DATE_SUB(`dt`, INTERVAL (DAYOFMONTH(`dt`) - 1) DAY)) AS __timestamp FROM tableT",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
self.validate_identity("SELECT name FROM temp WHERE name = ? FOR UPDATE")
|
||||||
self.validate_all(
|
self.validate_all(
|
||||||
"SELECT a FROM tbl FOR UPDATE",
|
"SELECT a FROM tbl FOR UPDATE",
|
||||||
write={
|
write={
|
||||||
|
|
|
@ -6,6 +6,7 @@ class TestOracle(Validator):
|
||||||
dialect = "oracle"
|
dialect = "oracle"
|
||||||
|
|
||||||
def test_oracle(self):
|
def test_oracle(self):
|
||||||
|
self.validate_identity("SELECT JSON_OBJECT(k1: v1 FORMAT JSON, k2: v2 FORMAT JSON)")
|
||||||
self.validate_identity("SELECT JSON_OBJECT('name': first_name || ' ' || last_name) FROM t")
|
self.validate_identity("SELECT JSON_OBJECT('name': first_name || ' ' || last_name) FROM t")
|
||||||
self.validate_identity("COALESCE(c1, c2, c3)")
|
self.validate_identity("COALESCE(c1, c2, c3)")
|
||||||
self.validate_identity("SELECT * FROM TABLE(foo)")
|
self.validate_identity("SELECT * FROM TABLE(foo)")
|
||||||
|
@ -25,6 +26,15 @@ class TestOracle(Validator):
|
||||||
self.validate_identity("SELECT * FROM table_name@dblink_name.database_link_domain")
|
self.validate_identity("SELECT * FROM table_name@dblink_name.database_link_domain")
|
||||||
self.validate_identity("SELECT * FROM table_name SAMPLE (25) s")
|
self.validate_identity("SELECT * FROM table_name SAMPLE (25) s")
|
||||||
self.validate_identity("SELECT * FROM V$SESSION")
|
self.validate_identity("SELECT * FROM V$SESSION")
|
||||||
|
self.validate_identity(
|
||||||
|
"SELECT JSON_ARRAYAGG(JSON_OBJECT('RNK': RNK, 'RATING_CODE': RATING_CODE, 'DATE_VALUE': DATE_VALUE, 'AGENT_ID': AGENT_ID RETURNING CLOB) RETURNING CLOB) AS JSON_DATA FROM tablename"
|
||||||
|
)
|
||||||
|
self.validate_identity(
|
||||||
|
"SELECT JSON_ARRAY(FOO() FORMAT JSON, BAR() NULL ON NULL RETURNING CLOB STRICT)"
|
||||||
|
)
|
||||||
|
self.validate_identity(
|
||||||
|
"SELECT JSON_ARRAYAGG(FOO() FORMAT JSON ORDER BY bar NULL ON NULL RETURNING CLOB STRICT)"
|
||||||
|
)
|
||||||
self.validate_identity(
|
self.validate_identity(
|
||||||
"SELECT COUNT(1) INTO V_Temp FROM TABLE(CAST(somelist AS data_list)) WHERE col LIKE '%contact'"
|
"SELECT COUNT(1) INTO V_Temp FROM TABLE(CAST(somelist AS data_list)) WHERE col LIKE '%contact'"
|
||||||
)
|
)
|
||||||
|
@ -190,3 +200,21 @@ MATCH_RECOGNIZE (
|
||||||
) MR""",
|
) MR""",
|
||||||
pretty=True,
|
pretty=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_json_table(self):
|
||||||
|
self.validate_identity(
|
||||||
|
"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS (foo PATH 'bar'))"
|
||||||
|
)
|
||||||
|
self.validate_identity(
|
||||||
|
"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS foo PATH 'bar')",
|
||||||
|
"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS (foo PATH 'bar'))",
|
||||||
|
)
|
||||||
|
self.validate_identity(
|
||||||
|
"""SELECT
|
||||||
|
CASE WHEN DBMS_LOB.GETLENGTH(info) < 32000 THEN DBMS_LOB.SUBSTR(info) END AS info_txt,
|
||||||
|
info AS info_clob
|
||||||
|
FROM schemaname.tablename ar
|
||||||
|
INNER JOIN JSON_TABLE(:emps, '$[*]' COLUMNS (empno NUMBER PATH '$')) jt
|
||||||
|
ON ar.empno = jt.empno""",
|
||||||
|
pretty=True,
|
||||||
|
)
|
||||||
|
|
|
@ -133,6 +133,9 @@ class TestPostgres(Validator):
|
||||||
alter_table_only = """ALTER TABLE ONLY "Album" ADD CONSTRAINT "FK_AlbumArtistId" FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION"""
|
alter_table_only = """ALTER TABLE ONLY "Album" ADD CONSTRAINT "FK_AlbumArtistId" FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION"""
|
||||||
expr = parse_one(alter_table_only)
|
expr = parse_one(alter_table_only)
|
||||||
|
|
||||||
|
# Checks that user-defined types are parsed into DataType instead of Identifier
|
||||||
|
parse_one("CREATE TABLE t (a udt)").this.expressions[0].args["kind"].assert_is(exp.DataType)
|
||||||
|
|
||||||
self.assertIsInstance(expr, exp.AlterTable)
|
self.assertIsInstance(expr, exp.AlterTable)
|
||||||
self.assertEqual(expr.sql(dialect="postgres"), alter_table_only)
|
self.assertEqual(expr.sql(dialect="postgres"), alter_table_only)
|
||||||
|
|
||||||
|
|
|
@ -360,3 +360,17 @@ class TestRedshift(Validator):
|
||||||
"redshift": "CREATE OR REPLACE VIEW v1 AS SELECT cola, colb FROM t1 WITH NO SCHEMA BINDING",
|
"redshift": "CREATE OR REPLACE VIEW v1 AS SELECT cola, colb FROM t1 WITH NO SCHEMA BINDING",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_concat(self):
|
||||||
|
self.validate_all(
|
||||||
|
"SELECT CONCAT('abc', 'def')",
|
||||||
|
write={
|
||||||
|
"redshift": "SELECT COALESCE(CAST('abc' AS VARCHAR(MAX)), '') || COALESCE(CAST('def' AS VARCHAR(MAX)), '')",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.validate_all(
|
||||||
|
"SELECT CONCAT_WS('DELIM', 'abc', 'def', 'ghi')",
|
||||||
|
write={
|
||||||
|
"redshift": "SELECT COALESCE(CAST('abc' AS VARCHAR(MAX)), '') || 'DELIM' || COALESCE(CAST('def' AS VARCHAR(MAX)), '') || 'DELIM' || COALESCE(CAST('ghi' AS VARCHAR(MAX)), '')",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
1
tests/fixtures/identity.sql
vendored
1
tests/fixtures/identity.sql
vendored
|
@ -717,6 +717,7 @@ UPDATE tbl_name SET foo = 123, bar = 345
|
||||||
UPDATE db.tbl_name SET foo = 123 WHERE tbl_name.bar = 234
|
UPDATE db.tbl_name SET foo = 123 WHERE tbl_name.bar = 234
|
||||||
UPDATE db.tbl_name SET foo = 123, foo_1 = 234 WHERE tbl_name.bar = 234
|
UPDATE db.tbl_name SET foo = 123, foo_1 = 234 WHERE tbl_name.bar = 234
|
||||||
UPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price
|
UPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price
|
||||||
|
UPDATE t1 AS a, t2 AS b, t3 AS c LEFT JOIN t4 AS d ON c.id = d.id SET a.id = 1
|
||||||
TRUNCATE TABLE x
|
TRUNCATE TABLE x
|
||||||
OPTIMIZE TABLE y
|
OPTIMIZE TABLE y
|
||||||
VACUUM FREEZE my_table
|
VACUUM FREEZE my_table
|
||||||
|
|
15
tests/fixtures/optimizer/merge_subqueries.sql
vendored
15
tests/fixtures/optimizer/merge_subqueries.sql
vendored
|
@ -310,6 +310,21 @@ FROM
|
||||||
t1;
|
t1;
|
||||||
SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x;
|
SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x;
|
||||||
|
|
||||||
|
# title: Don't merge window functions, inner table is aliased in outer query
|
||||||
|
with t1 as (
|
||||||
|
SELECT
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num
|
||||||
|
FROM
|
||||||
|
x
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
t2.row_num
|
||||||
|
FROM
|
||||||
|
t1 AS t2
|
||||||
|
WHERE
|
||||||
|
t2.row_num = 2;
|
||||||
|
WITH t1 AS (SELECT ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT t2.row_num AS row_num FROM t1 AS t2 WHERE t2.row_num = 2;
|
||||||
|
|
||||||
# title: Values Test
|
# title: Values Test
|
||||||
# dialect: spark
|
# dialect: spark
|
||||||
WITH t1 AS (
|
WITH t1 AS (
|
||||||
|
|
36
tests/fixtures/optimizer/optimizer.sql
vendored
36
tests/fixtures/optimizer/optimizer.sql
vendored
|
@ -987,3 +987,39 @@ SELECT
|
||||||
FROM "SALES" AS "SALES"
|
FROM "SALES" AS "SALES"
|
||||||
WHERE
|
WHERE
|
||||||
"SALES"."INSERT_TS" > '2023-08-07 21:03:35.590 -0700';
|
"SALES"."INSERT_TS" > '2023-08-07 21:03:35.590 -0700';
|
||||||
|
|
||||||
|
# title: using join without select *
|
||||||
|
# execute: false
|
||||||
|
with
|
||||||
|
alias1 as (select * from table1),
|
||||||
|
alias2 as (select * from table2),
|
||||||
|
alias3 as (
|
||||||
|
select
|
||||||
|
cid,
|
||||||
|
min(od) as m_od,
|
||||||
|
count(odi) as c_od,
|
||||||
|
from alias2
|
||||||
|
group by 1
|
||||||
|
)
|
||||||
|
select
|
||||||
|
alias1.cid,
|
||||||
|
alias3.m_od,
|
||||||
|
coalesce(alias3.c_od, 0) as c_od,
|
||||||
|
from alias1
|
||||||
|
left join alias3 using (cid);
|
||||||
|
WITH "alias3" AS (
|
||||||
|
SELECT
|
||||||
|
"table2"."cid" AS "cid",
|
||||||
|
MIN("table2"."od") AS "m_od",
|
||||||
|
COUNT("table2"."odi") AS "c_od"
|
||||||
|
FROM "table2" AS "table2"
|
||||||
|
GROUP BY
|
||||||
|
"table2"."cid"
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
"table1"."cid" AS "cid",
|
||||||
|
"alias3"."m_od" AS "m_od",
|
||||||
|
COALESCE("alias3"."c_od", 0) AS "c_od"
|
||||||
|
FROM "table1" AS "table1"
|
||||||
|
LEFT JOIN "alias3"
|
||||||
|
ON "table1"."cid" = "alias3"."cid";
|
||||||
|
|
|
@ -858,3 +858,8 @@ FROM READ_CSV('tests/fixtures/optimizer/tpc-h/nation.csv.gz', 'delimiter', '|')
|
||||||
),
|
),
|
||||||
parse_one('SELECT "a"."a" AS "a", "a"."b" AS "b" FROM "a" AS "a"'),
|
parse_one('SELECT "a"."a" AS "a", "a"."b" AS "b" FROM "a" AS "a"'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_semistructured(self):
|
||||||
|
query = parse_one("select a.b:c from d", read="snowflake")
|
||||||
|
qualified = optimizer.qualify.qualify(query)
|
||||||
|
self.assertEqual(qualified.expressions[0].alias, "c")
|
||||||
|
|
|
@ -719,3 +719,18 @@ class TestParser(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(ast.find(exp.Interval).this.sql(), "'71'")
|
self.assertEqual(ast.find(exp.Interval).this.sql(), "'71'")
|
||||||
self.assertEqual(ast.find(exp.Interval).unit.assert_is(exp.Var).sql(), "days")
|
self.assertEqual(ast.find(exp.Interval).unit.assert_is(exp.Var).sql(), "days")
|
||||||
|
|
||||||
|
def test_parse_concat_ws(self):
|
||||||
|
ast = parse_one("CONCAT_WS(' ', 'John', 'Doe')")
|
||||||
|
|
||||||
|
self.assertEqual(ast.sql(), "CONCAT_WS(' ', 'John', 'Doe')")
|
||||||
|
self.assertEqual(ast.expressions[0].sql(), "' '")
|
||||||
|
self.assertEqual(ast.expressions[1].sql(), "'John'")
|
||||||
|
self.assertEqual(ast.expressions[2].sql(), "'Doe'")
|
||||||
|
|
||||||
|
# Ensure we can parse without argument when error level is ignore
|
||||||
|
ast = parse(
|
||||||
|
"CONCAT_WS()",
|
||||||
|
error_level=ErrorLevel.IGNORE,
|
||||||
|
)
|
||||||
|
self.assertEqual(ast[0].sql(), "CONCAT_WS()")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue