Skip to content

Commit f1552da

Browse files
feat: Support limiting row count and offsets in Oracle queries (#754)
1 parent 8841520 commit f1552da

File tree

3 files changed

+58
-20
lines changed

3 files changed

+58
-20
lines changed

pypika/dialects.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import itertools
2+
import warnings
23
from copy import copy
34
from typing import Any, Optional, Union, Tuple as TypedTuple
45

@@ -347,6 +348,19 @@ def __str__(self) -> str:
347348
return self.get_sql()
348349

349350

351+
class FetchNextAndOffsetRowsQueryBuilder(QueryBuilder):
352+
def _limit_sql(self) -> str:
353+
return " FETCH NEXT {limit} ROWS ONLY".format(limit=self._limit)
354+
355+
def _offset_sql(self) -> str:
356+
return " OFFSET {offset} ROWS".format(offset=self._offset or 0)
357+
358+
@builder
359+
def fetch_next(self, limit: int):
360+
warnings.warn("`fetch_next` is deprecated - please use the `limit` method", DeprecationWarning)
361+
self._limit = limit
362+
363+
350364
class OracleQuery(Query):
351365
"""
352366
Defines a query class for use with Oracle.
@@ -357,7 +371,7 @@ def _builder(cls, **kwargs: Any) -> "OracleQueryBuilder":
357371
return OracleQueryBuilder(**kwargs)
358372

359373

360-
class OracleQueryBuilder(QueryBuilder):
374+
class OracleQueryBuilder(FetchNextAndOffsetRowsQueryBuilder):
361375
QUOTE_CHAR = None
362376
QUERY_CLS = OracleQuery
363377

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

387+
def _apply_pagination(self, querystring: str) -> str:
388+
# Note: Overridden as Oracle specifies offset before the fetch next limit
389+
if self._offset:
390+
querystring += self._offset_sql()
391+
392+
if self._limit is not None:
393+
querystring += self._limit_sql()
394+
395+
return querystring
396+
373397

374398
class PostgreSQLQuery(Query):
375399
"""
@@ -670,7 +694,7 @@ def _builder(cls, **kwargs: Any) -> "MSSQLQueryBuilder":
670694
return MSSQLQueryBuilder(**kwargs)
671695

672696

673-
class MSSQLQueryBuilder(QueryBuilder):
697+
class MSSQLQueryBuilder(FetchNextAndOffsetRowsQueryBuilder):
674698
QUERY_CLS = MSSQLQuery
675699

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

698-
@builder
699-
def fetch_next(self, limit: int) -> "MSSQLQueryBuilder":
700-
# Overridden to provide a more domain-specific API for T-SQL users
701-
self._limit = limit
702-
703-
def _offset_sql(self) -> str:
704-
return " OFFSET {offset} ROWS".format(offset=self._offset or 0)
705-
706-
def _limit_sql(self) -> str:
707-
return " FETCH NEXT {limit} ROWS ONLY".format(limit=self._limit)
708-
709722
def _apply_pagination(self, querystring: str) -> str:
710723
# Note: Overridden as MSSQL specifies offset before the fetch next limit
711724
if self._limit is not None or self._offset:

pypika/tests/dialects/test_mssql.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,13 @@ def test_limit(self):
5353

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

56-
def test_fetch_next(self):
57-
q = MSSQLQuery.from_("abc").select("def").orderby("def").fetch_next(10)
58-
59-
self.assertEqual('SELECT "def" FROM "abc" ORDER BY "def" OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', str(q))
60-
6156
def test_offset(self):
6257
q = MSSQLQuery.from_("abc").select("def").orderby("def").offset(10)
6358

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

66-
def test_fetch_next_with_offset(self):
67-
q = MSSQLQuery.from_("abc").select("def").orderby("def").fetch_next(10).offset(10)
61+
def test_limit_with_offset(self):
62+
q = MSSQLQuery.from_("abc").select("def").orderby("def").limit(10).offset(10)
6863

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

pypika/tests/dialects/test_oracle.py

+30
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,33 @@ def test_groupby_alias_False_does_not_group_by_alias_when_subqueries_are_present
1919
q = OracleQuery.from_(subquery).select(col, Count('*')).groupby(col)
2020

2121
self.assertEqual('SELECT sq0.abc a,COUNT(\'*\') FROM (SELECT abc FROM table1) sq0 GROUP BY sq0.abc', str(q))
22+
23+
def test_limit_query(self):
24+
t = Table('table1')
25+
limit = 5
26+
q = OracleQuery.from_(t).select(t.test).limit(limit)
27+
28+
self.assertEqual(f'SELECT test FROM table1 FETCH NEXT {limit} ROWS ONLY', str(q))
29+
30+
def test_offset_query(self):
31+
t = Table('table1')
32+
offset = 5
33+
q = OracleQuery.from_(t).select(t.test).offset(offset)
34+
35+
self.assertEqual(f'SELECT test FROM table1 OFFSET {offset} ROWS', str(q))
36+
37+
def test_limit_offset_query(self):
38+
t = Table('table1')
39+
limit = 5
40+
offset = 5
41+
q = OracleQuery.from_(t).select(t.test).limit(limit).offset(offset)
42+
43+
self.assertEqual(f'SELECT test FROM table1 OFFSET {offset} ROWS FETCH NEXT {limit} ROWS ONLY', str(q))
44+
45+
def test_fetch_next_method_deprecated(self):
46+
with self.assertWarns(DeprecationWarning):
47+
t = Table('table1')
48+
limit = 5
49+
q = OracleQuery.from_(t).select(t.test).fetch_next(limit)
50+
51+
self.assertEqual(f'SELECT test FROM table1 FETCH NEXT {limit} ROWS ONLY', str(q))

0 commit comments

Comments
 (0)