Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sqlglot/dialects/clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class Tokenizer(tokens.Tokenizer):
"ENUM8": TokenType.ENUM8,
"ENUM16": TokenType.ENUM16,
"EXCHANGE": TokenType.COMMAND,
"EXPLAIN": TokenType.DESCRIBE,
"FINAL": TokenType.FINAL,
"FIXEDSTRING": TokenType.FIXEDSTRING,
"FLOAT32": TokenType.FLOAT,
Expand Down Expand Up @@ -121,6 +122,8 @@ class Tokenizer(tokens.Tokenizer):

KEYWORDS.pop("/*+")

COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}

SINGLE_TOKENS = {
**tokens.Tokenizer.SINGLE_TOKENS,
"$": TokenType.HEREDOC_STRING,
Expand Down
20 changes: 20 additions & 0 deletions sqlglot/generators/clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,26 @@ def trycast_sql(self, expression: exp.TryCast) -> str:

return super().cast_sql(expression)

def describe_sql(self, expression: exp.Describe) -> str:
keyword = "EXPLAIN" if expression.text("kind").upper() == "EXPLAIN" else "DESCRIBE"
style = expression.args.get("style")
style = f" {style}" if style else ""
format = self.sql(expression, "format")
format = f" {format}" if format else ""
partition = self.sql(expression, "partition")
partition = f" {partition}" if partition else ""
as_json = " AS JSON" if expression.args.get("as_json") else ""

return f"{keyword}{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"

def show_sql(self, expression: exp.Show) -> str:
target = self.sql(expression, "target")
target = f" {target}" if target else ""
from_ = self.sql(expression, "from_")
from_ = f" FROM {from_}" if from_ else ""

return f"SHOW {expression.name}{target}{from_}"

def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:
this = self.json_path_part(expression.this)
return str(int(this) + 1) if is_int(this) else this
Expand Down
39 changes: 39 additions & 0 deletions sqlglot/parsers/clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from sqlglot.helper import seq_get
from sqlglot.tokens import Token, TokenType
from sqlglot.trie import new_trie
from builtins import type as Type

if t.TYPE_CHECKING:
Expand Down Expand Up @@ -72,6 +73,13 @@ def _build_split(exp_class: Type[E]) -> t.Callable[[list], E]:
)


def _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[ClickHouseParser], exp.Show]:
def _parse(self: ClickHouseParser) -> exp.Show:
return self._parse_show_clickhouse(*args, **kwargs)

return _parse


# Skip the 'week' unit since ClickHouse's toStartOfWeek
# uses an extra mode argument to specify the first day of the week
TIMESTAMP_TRUNC_UNITS = {
Expand Down Expand Up @@ -456,11 +464,42 @@ def _resolve_clickhouse_agg(cls, name: str) -> tuple[str, Sequence[str]] | None:
TokenType.L_BRACE: lambda self: self._parse_query_parameter(),
}

DESCRIBE_STYLES = {
*parser.Parser.DESCRIBE_STYLES,
"ESTIMATE",
}

SHOW_PARSERS = {
"TABLES": _show_parser("TABLES"),
"CREATE TABLE": _show_parser("CREATE TABLE", target=True),
}

STATEMENT_PARSERS = {
**parser.Parser.STATEMENT_PARSERS,
TokenType.DETACH: lambda self: self._parse_detach(),
TokenType.SHOW: lambda self: self._parse_show(),
}

SHOW_TRIE = new_trie(key.split(" ") for key in SHOW_PARSERS)

def _parse_show_clickhouse(self, this: str, target: bool = False) -> exp.Show:
target_table = self._parse_table(schema=True) if target else None
from_ = (
self._parse_table(schema=True)
if not target and self._match_set((TokenType.FROM, TokenType.IN))
else None
)
return self.expression(exp.Show(this=this, target=target_table, from_=from_))

def _parse_describe(self) -> exp.Describe:
kind = self._prev.text.upper()
describe = super()._parse_describe()

if kind == "EXPLAIN":
describe.set("kind", kind)

return describe

def _parse_wrapped_select_or_assignment(self) -> exp.Expr | None:
return self._parse_wrapped(
lambda: self._parse_select() or self._parse_assignment(), optional=True
Expand Down
20 changes: 20 additions & 0 deletions tests/dialects/test_clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,26 @@ def test_clickhouse(self):

self.validate_identity("SELECT []")

def test_show_and_explain(self):
show_tables = self.parse_one("SHOW TABLES")
self.assertIsInstance(show_tables, exp.Show)
self.assertEqual(show_tables.sql(dialect="clickhouse"), "SHOW TABLES")

show_create = self.parse_one("SHOW CREATE TABLE t")
self.assertIsInstance(show_create, exp.Show)
self.assertEqual(show_create.sql(dialect="clickhouse"), "SHOW CREATE TABLE t")

explain = self.parse_one("EXPLAIN SELECT 1")
self.assertIsInstance(explain, exp.Describe)
self.assertEqual(explain.text("kind"), "EXPLAIN")
self.assertEqual(explain.sql(dialect="clickhouse"), "EXPLAIN SELECT 1")

explain_estimate = self.parse_one("EXPLAIN ESTIMATE SELECT 1")
self.assertIsInstance(explain_estimate, exp.Describe)
self.assertEqual(explain_estimate.text("kind"), "EXPLAIN")
self.assertEqual(explain_estimate.text("style"), "ESTIMATE")
self.assertEqual(explain_estimate.sql(dialect="clickhouse"), "EXPLAIN ESTIMATE SELECT 1")

def test_clickhouse_values(self):
ast = self.parse_one("SELECT * FROM VALUES (1, 2, 3)")
self.assertEqual(len(list(ast.find_all(exp.Tuple))), 4)
Expand Down
Loading