From 8ac4c2e29093cb9c1382905c05400339a6094bee Mon Sep 17 00:00:00 2001 From: Martijn The Date: Tue, 14 May 2024 15:20:39 +0200 Subject: [PATCH] Add support of SETTINGS ClickHouse clause ### Summary Clickhouse SQL has an optional [`SETTINGS` clause](settings-clause) with `SELECT`s which can be used to configure [all kinds of options](settings). This PR adds a `settings` method to `ClickHouseQueryBuilder` to add these options to a `SELECT` query. All current (Clickhouse 24.4) settings keys are snake-case. Therefore I opted to use the settings keys directly as kwargs to the `settings` method, allowing passing multiple settings pairs in one call in a Pythonic/ideomatic way. [settings-clause]: https://clickhouse.com/docs/en/sql-reference/statements/select#settings-in-select-query [settings]: https://clickhouse.com/docs/en/operations/settings/settings ### Test Plan Added a unit test to exercise the new function. --- pypika/dialects.py | 8 ++++++++ pypika/tests/dialects/test_clickhouse.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/pypika/dialects.py b/pypika/dialects.py index dab72e07..b91df3c3 100644 --- a/pypika/dialects.py +++ b/pypika/dialects.py @@ -798,12 +798,18 @@ def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self._sample = None self._sample_offset = None + self._settings = None @builder def sample(self, sample: int, offset: Optional[int] = None) -> "ClickHouseQueryBuilder": self._sample = sample self._sample_offset = offset + @builder + def settings(self, **kwargs: Any) -> "ClickHouseQueryBuilder": + self._settings = self._settings.copy() if self._settings else {} + self._settings.update(kwargs) + @staticmethod def _delete_sql(**kwargs: Any) -> str: return 'ALTER TABLE' @@ -820,6 +826,8 @@ def _from_sql(self, with_namespace: bool = False, **kwargs: Any) -> str: clauses.append(f"SAMPLE {self._sample}") if self._sample_offset is not None: clauses.append(f"OFFSET {self._sample_offset}") + if self._settings: + clauses.append(f"SETTINGS {', '.join(f'{k}={v}' for k, v in sorted(self._settings.items()))}") return " FROM {clauses}".format(clauses=" ".join(clauses)) def _set_sql(self, **kwargs: Any) -> str: diff --git a/pypika/tests/dialects/test_clickhouse.py b/pypika/tests/dialects/test_clickhouse.py index 8da62701..93c43cd7 100644 --- a/pypika/tests/dialects/test_clickhouse.py +++ b/pypika/tests/dialects/test_clickhouse.py @@ -23,6 +23,14 @@ def test_use_SAMPLE_with_offset_keyword(self): query = ClickHouseQuery.from_(t).select(t.foo).sample(10, 5) self.assertEqual(str(query), 'SELECT "foo" FROM "abc" SAMPLE 10 OFFSET 5') + def test_settings(self) -> None: + t = Table('abc') + query1 = ClickHouseQuery.from_(t).select(t.foo).settings(foo="bar") + query2 = query1.settings(baz="qux") + # Settings get deep-copied: + self.assertEqual(str(query1), 'SELECT "foo" FROM "abc" SETTINGS foo=bar') + # Settings are ordered alphabetically in the query string: + self.assertEqual(str(query2), 'SELECT "foo" FROM "abc" SETTINGS baz=qux, foo=bar') class ClickHouseDeleteTests(TestCase): table_abc = Table("abc")