Skip to content

feat: Support limiting row count and offsets in Oracle queries #754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
39 changes: 26 additions & 13 deletions pypika/dialects.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import itertools
import warnings
from copy import copy
from typing import Any, Optional, Union, Tuple as TypedTuple

Expand Down Expand Up @@ -347,6 +348,19 @@ def __str__(self) -> str:
return self.get_sql()


class FetchNextAndOffsetRowsQueryBuilder(QueryBuilder):
def _limit_sql(self) -> str:
return " FETCH NEXT {limit} ROWS ONLY".format(limit=self._limit)

def _offset_sql(self) -> str:
return " OFFSET {offset} ROWS".format(offset=self._offset or 0)

@builder
def fetch_next(self, limit: int):
warnings.warn("`fetch_next` is deprecated - please use the `limit` method", DeprecationWarning)
self._limit = limit


class OracleQuery(Query):
"""
Defines a query class for use with Oracle.
Expand All @@ -357,7 +371,7 @@ def _builder(cls, **kwargs: Any) -> "OracleQueryBuilder":
return OracleQueryBuilder(**kwargs)


class OracleQueryBuilder(QueryBuilder):
class OracleQueryBuilder(FetchNextAndOffsetRowsQueryBuilder):
QUOTE_CHAR = None
QUERY_CLS = OracleQuery

Expand All @@ -370,6 +384,16 @@ def get_sql(self, *args: Any, **kwargs: Any) -> str:
kwargs['groupby_alias'] = False
return super().get_sql(*args, **kwargs)

def _apply_pagination(self, querystring: str) -> str:
# Note: Overridden as Oracle specifies offset before the fetch next limit
if self._offset:
querystring += self._offset_sql()

if self._limit is not None:
querystring += self._limit_sql()

return querystring


class PostgreSQLQuery(Query):
"""
Expand Down Expand Up @@ -670,7 +694,7 @@ def _builder(cls, **kwargs: Any) -> "MSSQLQueryBuilder":
return MSSQLQueryBuilder(**kwargs)


class MSSQLQueryBuilder(QueryBuilder):
class MSSQLQueryBuilder(FetchNextAndOffsetRowsQueryBuilder):
QUERY_CLS = MSSQLQuery

def __init__(self, **kwargs: Any) -> None:
Expand All @@ -695,17 +719,6 @@ def top(self, value: Union[str, int], percent: bool = False, with_ties: bool = F
self._top_percent: bool = percent
self._top_with_ties: bool = with_ties

@builder
def fetch_next(self, limit: int) -> "MSSQLQueryBuilder":
# Overridden to provide a more domain-specific API for T-SQL users
self._limit = limit

def _offset_sql(self) -> str:
return " OFFSET {offset} ROWS".format(offset=self._offset or 0)

def _limit_sql(self) -> str:
return " FETCH NEXT {limit} ROWS ONLY".format(limit=self._limit)

def _apply_pagination(self, querystring: str) -> str:
# Note: Overridden as MSSQL specifies offset before the fetch next limit
if self._limit is not None or self._offset:
Expand Down
9 changes: 2 additions & 7 deletions pypika/tests/dialects/test_mssql.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,13 @@ def test_limit(self):

self.assertEqual('SELECT "def" FROM "abc" ORDER BY "def" OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', str(q))

def test_fetch_next(self):
q = MSSQLQuery.from_("abc").select("def").orderby("def").fetch_next(10)

self.assertEqual('SELECT "def" FROM "abc" ORDER BY "def" OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', str(q))

def test_offset(self):
q = MSSQLQuery.from_("abc").select("def").orderby("def").offset(10)

self.assertEqual('SELECT "def" FROM "abc" ORDER BY "def" OFFSET 10 ROWS', str(q))

def test_fetch_next_with_offset(self):
q = MSSQLQuery.from_("abc").select("def").orderby("def").fetch_next(10).offset(10)
def test_limit_with_offset(self):
q = MSSQLQuery.from_("abc").select("def").orderby("def").limit(10).offset(10)

self.assertEqual('SELECT "def" FROM "abc" ORDER BY "def" OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY', str(q))

Expand Down
30 changes: 30 additions & 0 deletions pypika/tests/dialects/test_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,33 @@ def test_groupby_alias_False_does_not_group_by_alias_when_subqueries_are_present
q = OracleQuery.from_(subquery).select(col, Count('*')).groupby(col)

self.assertEqual('SELECT sq0.abc a,COUNT(\'*\') FROM (SELECT abc FROM table1) sq0 GROUP BY sq0.abc', str(q))

def test_limit_query(self):
t = Table('table1')
limit = 5
q = OracleQuery.from_(t).select(t.test).limit(limit)

self.assertEqual(f'SELECT test FROM table1 FETCH NEXT {limit} ROWS ONLY', str(q))

def test_offset_query(self):
t = Table('table1')
offset = 5
q = OracleQuery.from_(t).select(t.test).offset(offset)

self.assertEqual(f'SELECT test FROM table1 OFFSET {offset} ROWS', str(q))

def test_limit_offset_query(self):
t = Table('table1')
limit = 5
offset = 5
q = OracleQuery.from_(t).select(t.test).limit(limit).offset(offset)

self.assertEqual(f'SELECT test FROM table1 OFFSET {offset} ROWS FETCH NEXT {limit} ROWS ONLY', str(q))

def test_fetch_next_method_deprecated(self):
with self.assertWarns(DeprecationWarning):
t = Table('table1')
limit = 5
q = OracleQuery.from_(t).select(t.test).fetch_next(limit)

self.assertEqual(f'SELECT test FROM table1 FETCH NEXT {limit} ROWS ONLY', str(q))
Loading