|
1 | 1 | import itertools
|
2 | 2 | import warnings
|
3 | 3 | from copy import copy
|
4 |
| -from typing import Any, Optional, Union, Tuple as TypedTuple |
| 4 | +from typing import Any, Optional, Union, Tuple as TypedTuple, List |
5 | 5 |
|
6 | 6 | from pypika.enums import Dialects
|
7 | 7 | from pypika.queries import (
|
@@ -384,7 +384,7 @@ def get_sql(self, *args: Any, **kwargs: Any) -> str:
|
384 | 384 | kwargs['groupby_alias'] = False
|
385 | 385 | return super().get_sql(*args, **kwargs)
|
386 | 386 |
|
387 |
| - def _apply_pagination(self, querystring: str) -> str: |
| 387 | + def _apply_pagination(self, querystring: str, **kwargs) -> str: |
388 | 388 | # Note: Overridden as Oracle specifies offset before the fetch next limit
|
389 | 389 | if self._offset:
|
390 | 390 | querystring += self._offset_sql()
|
@@ -719,7 +719,7 @@ def top(self, value: Union[str, int], percent: bool = False, with_ties: bool = F
|
719 | 719 | self._top_percent: bool = percent
|
720 | 720 | self._top_with_ties: bool = with_ties
|
721 | 721 |
|
722 |
| - def _apply_pagination(self, querystring: str) -> str: |
| 722 | + def _apply_pagination(self, querystring: str, **kwargs) -> str: |
723 | 723 | # Note: Overridden as MSSQL specifies offset before the fetch next limit
|
724 | 724 | if self._limit is not None or self._offset:
|
725 | 725 | # Offset has to be present if fetch next is specified in a MSSQL query
|
@@ -794,11 +794,21 @@ def drop_view(self, view: str) -> "ClickHouseDropQueryBuilder":
|
794 | 794 | class ClickHouseQueryBuilder(QueryBuilder):
|
795 | 795 | QUERY_CLS = ClickHouseQuery
|
796 | 796 |
|
| 797 | + _distinct_on: List[Term] |
| 798 | + _limit_by: Optional[TypedTuple[int, int, List[Term]]] |
| 799 | + |
797 | 800 | def __init__(self, **kwargs) -> None:
|
798 | 801 | super().__init__(**kwargs)
|
799 | 802 | self._final = False
|
800 | 803 | self._sample = None
|
801 | 804 | self._sample_offset = None
|
| 805 | + self._distinct_on = [] |
| 806 | + self._limit_by = None |
| 807 | + |
| 808 | + def __copy__(self) -> "ClickHouseQueryBuilder": |
| 809 | + newone = super().__copy__() |
| 810 | + newone._limit_by = copy(self._limit_by) |
| 811 | + return newone |
802 | 812 |
|
803 | 813 | @builder
|
804 | 814 | def final(self) -> "ClickHouseQueryBuilder":
|
@@ -839,6 +849,55 @@ def _set_sql(self, **kwargs: Any) -> str:
|
839 | 849 | )
|
840 | 850 | )
|
841 | 851 |
|
| 852 | + @builder |
| 853 | + def distinct_on(self, *fields: Union[str, Term]) -> "ClickHouseQueryBuilder": |
| 854 | + for field in fields: |
| 855 | + if isinstance(field, str): |
| 856 | + self._distinct_on.append(Field(field)) |
| 857 | + elif isinstance(field, Term): |
| 858 | + self._distinct_on.append(field) |
| 859 | + |
| 860 | + def _distinct_sql(self, **kwargs: Any) -> str: |
| 861 | + if self._distinct_on: |
| 862 | + return "DISTINCT ON({distinct_on}) ".format( |
| 863 | + distinct_on=",".join(term.get_sql(with_alias=True, **kwargs) for term in self._distinct_on) |
| 864 | + ) |
| 865 | + return super()._distinct_sql(**kwargs) |
| 866 | + |
| 867 | + @builder |
| 868 | + def limit_by(self, n, *by: Union[str, Term]) -> "ClickHouseQueryBuilder": |
| 869 | + self._limit_by = (n, 0, [Field(field) if isinstance(field, str) else field for field in by]) |
| 870 | + |
| 871 | + @builder |
| 872 | + def limit_offset_by(self, n, offset, *by: Union[str, Term]) -> "ClickHouseQueryBuilder": |
| 873 | + self._limit_by = (n, offset, [Field(field) if isinstance(field, str) else field for field in by]) |
| 874 | + |
| 875 | + def _apply_pagination(self, querystring: str, **kwargs) -> str: |
| 876 | + # LIMIT BY isn't really a pagination per se but since we need |
| 877 | + # to add this to the query right before an actual LIMIT clause |
| 878 | + # this is good enough. |
| 879 | + if self._limit_by: |
| 880 | + querystring += self._limit_by_sql(**kwargs) |
| 881 | + return super()._apply_pagination(querystring, **kwargs) |
| 882 | + |
| 883 | + def _limit_by_sql(self, **kwargs: Any) -> str: |
| 884 | + (n, offset, by) = self._limit_by |
| 885 | + by = ",".join(term.get_sql(with_alias=True, **kwargs) for term in by) |
| 886 | + if offset != 0: |
| 887 | + return f" LIMIT {n} OFFSET {offset} BY ({by})" |
| 888 | + else: |
| 889 | + return f" LIMIT {n} BY ({by})" |
| 890 | + |
| 891 | + def replace_table(self, current_table: Optional[Table], new_table: Optional[Table]) -> "ClickHouseQueryBuilder": |
| 892 | + newone = super().replace_table(current_table, new_table) |
| 893 | + if self._limit_by: |
| 894 | + newone._limit_by = ( |
| 895 | + self._limit_by[0], |
| 896 | + self._limit_by[1], |
| 897 | + [column.replace_table(current_table, new_table) for column in self._limit_by[2]], |
| 898 | + ) |
| 899 | + return newone |
| 900 | + |
842 | 901 |
|
843 | 902 | class ClickHouseDropQueryBuilder(DropQueryBuilder):
|
844 | 903 | QUERY_CLS = ClickHouseQuery
|
|
0 commit comments