Skip to content

Commit bb780ab

Browse files
elpransJames William Pye
authored andcommitted
Make Cursor.seek() return the number of rows it navigated over
Cursor.seek() now returns the number of rows seek navigated over to reach the specified offset. For this to become useful, this commit also adds support for relative 'FORWARD'/'BACKWARD' modes. While currently implemented "RELATIVE" mode makes relative seeks possible, it does not support exhaustive seek mode, i. e., "FORWARD ALL" and "BACKWARD ALL". It is important to distinguish those modes from ABSOLUTE seeks since ABSOLUTE seek always returns a single row, while relative seeks return total number of rows. Thus, seek('ALL', 'FORWARD') is the only way to obtain the total number of rows returned by the query without actually iterating over it.
1 parent 55c5b09 commit bb780ab

3 files changed

Lines changed: 61 additions & 12 deletions

File tree

postgresql/api.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ class Cursor(
228228
0 : 'ABSOLUTE',
229229
1 : 'RELATIVE',
230230
2 : 'FROM_END',
231+
3 : 'FORWARD',
232+
4 : 'BACKWARD'
231233
}
232234
_direction_map = {
233235
True : 'FORWARD',
@@ -293,6 +295,10 @@ def seek(self, offset, whence = 'ABSOLUTE'):
293295
Relative.
294296
``2`` or ``"FROM_END"``
295297
Absolute from end.
298+
``3`` or ``"FORWARD"``
299+
Relative forward.
300+
``4`` or ``"BACKWARD"``
301+
Relative backward.
296302
297303
Direction effects whence. If direction is BACKWARD, ABSOLUTE positioning
298304
will effectively be FROM_END, RELATIVE's position will be negated, and

postgresql/driver/pq3.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,23 +1092,40 @@ def seek(self, offset, whence = 'ABSOLUTE'):
10921092
"unknown whence parameter, %r" %(whence,)
10931093
)
10941094
rwhence = rwhence.upper()
1095-
if self.direction is False:
1096-
if rwhence == 'RELATIVE':
1095+
1096+
if offset == 'ALL':
1097+
if rwhence not in ('BACKWARD', 'FORWARD'):
1098+
rwhence = 'BACKWARD' if self.direction is False else 'FORWARD'
1099+
else:
1100+
if offset < 0 and rwhence == 'BACKWARD':
10971101
offset = -offset
1098-
elif rwhence == 'ABSOLUTE':
1099-
rwhence = 'FROM_END'
1100-
else:
1101-
rwhence = 'ABSOLUTE'
1102+
rwhence = 'FORWARD'
11021103

1103-
if rwhence == 'RELATIVE':
1104-
if offset < 0:
1104+
if self.direction is False:
1105+
if offset == 'ALL' and rwhence != 'FORWARD':
1106+
rwhence = 'BACKWARD'
1107+
else:
1108+
if rwhence == 'RELATIVE':
1109+
offset = -offset
1110+
elif rwhence == 'ABSOLUTE':
1111+
rwhence = 'FROM_END'
1112+
else:
1113+
rwhence = 'ABSOLUTE'
1114+
1115+
if rwhence in ('RELATIVE', 'BACKWARD', 'FORWARD'):
1116+
if offset == 'ALL':
11051117
cmd = self._pq_xp_move(
1106-
str(-offset).encode('ascii'), b'BACKWARD'
1118+
str(offset).encode('ascii'), str(rwhence).encode('ascii')
11071119
)
11081120
else:
1109-
cmd = self._pq_xp_move(
1110-
str(offset).encode('ascii'), b'RELATIVE'
1111-
)
1121+
if offset < 0:
1122+
cmd = self._pq_xp_move(
1123+
str(-offset).encode('ascii'), b'BACKWARD'
1124+
)
1125+
else:
1126+
cmd = self._pq_xp_move(
1127+
str(offset).encode('ascii'), str(rwhence).encode('ascii')
1128+
)
11121129
elif rwhence == 'ABSOLUTE':
11131130
cmd = self._pq_xp_move(str(offset).encode('ascii'), b'ABSOLUTE')
11141131
else:
@@ -1122,6 +1139,16 @@ def seek(self, offset, whence = 'ABSOLUTE'):
11221139
self.database._pq_push(x, self)
11231140
self.database._pq_complete()
11241141

1142+
count = None
1143+
complete = element.Complete.type
1144+
for cm in x.messages_received():
1145+
if getattr(cm, 'type', None) == complete:
1146+
count = cm.extract_count()
1147+
break
1148+
1149+
# XXX: Raise if count is None?
1150+
return count
1151+
11251152
class Statement(pg_api.Statement):
11261153
string = None
11271154
database = None

postgresql/test/test_driver.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,22 @@ def testScroll(self, direction = True):
879879
c.seek(10, 2)
880880
self.failUnlessEqual(r1, c.read(10))
881881

882+
@pg_tmp
883+
def testSeek(self):
884+
ps = db.prepare("SELECT i FROM generate_series(0, (2^6)::int - 1) AS g(i)")
885+
c = ps.declare()
886+
887+
self.failUnlessEqual(c.seek(4, 'FORWARD'), 4)
888+
self.failUnlessEqual([x for x, in c.read(10)], list(range(4, 14)))
889+
890+
self.failUnlessEqual(c.seek(2, 'BACKWARD'), 2)
891+
self.failUnlessEqual([x for x, in c.read(10)], list(range(12, 22)))
892+
893+
self.failUnlessEqual(c.seek(-5, 'BACKWARD'), 5)
894+
self.failUnlessEqual([x for x, in c.read(10)], list(range(27, 37)))
895+
896+
self.failUnlessEqual(c.seek('ALL'), 27)
897+
882898
def testScrollBackwards(self):
883899
# testScroll again, but backwards this time.
884900
self.testScroll(direction = False)

0 commit comments

Comments
 (0)