|
| 1 | +from types import SimpleNamespace |
| 2 | + |
| 3 | +from clickhouse_sqlalchemy.sql import Select as _BaseSelect |
| 4 | +from sqlalchemy.sql.base import _generative |
| 5 | +from sqlalchemy.sql.elements import ClauseList, _literal_as_label_reference |
| 6 | +from sqlalchemy.sql.selectable import _offset_or_limit_clause |
| 7 | + |
| 8 | + |
| 9 | +class LimitByClause: |
| 10 | + |
| 11 | + def __init__(self, clauses, offset, limit): |
| 12 | + self.clauses = ClauseList( |
| 13 | + *clauses, _literal_as_text=_literal_as_label_reference, |
| 14 | + ) |
| 15 | + self.offset = _offset_or_limit_clause(offset) |
| 16 | + self.limit = _offset_or_limit_clause(limit) |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | +# Minimal value to trick SQLAlchemy into believing that there is a ORDER BY |
| 21 | +# clause |
| 22 | +_ORDER_BY_SENTINEL = SimpleNamespace(clauses=True) |
| 23 | + |
| 24 | + |
| 25 | +class Select(_BaseSelect): |
| 26 | + |
| 27 | + _limit_by_clause = None |
| 28 | + |
| 29 | + @_generative |
| 30 | + def limit_by(self, *clauses, offset=None, limit): |
| 31 | + """return a new selectable with the given |
| 32 | + `LIMIT [offset,] limit BY clauses` criterion applied. |
| 33 | +
|
| 34 | + e.g.:: |
| 35 | +
|
| 36 | + stmt = ( |
| 37 | + select([table.c.name, table.c.amount]) |
| 38 | + .order_by(table.c.amount.desc()) |
| 39 | + .limit_by(table.c.name, limit=1) |
| 40 | + ) |
| 41 | + """ |
| 42 | + # LIMIT BY clause comes just after ORDER BY, so we hack the later's |
| 43 | + # machinery to add former too. Alternative would be redefining |
| 44 | + # whole `_compose_select_body` method just to insert 2 lines of code. |
| 45 | + # The worst here is that this method is redefined in |
| 46 | + # clickhouse_sqlalchemy and still is under active development. |
| 47 | + if not self._order_by_clause.clauses: |
| 48 | + self._order_by_clause = _ORDER_BY_SENTINEL |
| 49 | + |
| 50 | + self._limit_by_clause = LimitByClause(clauses, offset, limit) |
| 51 | + |
| 52 | + def append_order_by(self, *clauses): |
| 53 | + if self._order_by_clause is _ORDER_BY_SENTINEL: |
| 54 | + self._order_by_clause = ClauseList() |
| 55 | + super().append_order_by(*clauses) |
| 56 | + if ( |
| 57 | + self._limit_by_clause is not None and |
| 58 | + not self._order_by_clause.clauses |
| 59 | + ): |
| 60 | + self._order_by_clause = _ORDER_BY_SENTINEL |
| 61 | + |
| 62 | + |
| 63 | +select = Select |
0 commit comments