Skip to content

Commit

Permalink
feat: Support limiting row count and offsets in Oracle queries (#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielenricocahall authored Apr 17, 2024
1 parent 8841520 commit f1552da
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 20 deletions.
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))

0 comments on commit f1552da

Please sign in to comment.