Skip to content

Commit 0239106

Browse files
author
James William Pye
committed
Numerous minor fixes and improvements.
CursorChunks Be sure to dispath for more before leaving __next__, but only after a non-zero _expand. postgresql.types.Row Don't overload __new__. It was a burden on performance for little gain. Use the class methods from_sequence and from_mapping instead. Tests * Make test_driver and test_dbapi20 use the same cluster. * Only test the crypt method if the server is less than 8.4. * Add test_python for functions and classes in the python package. Protocol Use chain, map, itemgetter for messages_received, reverse, and asyncs. .python Add rsetattr. This may take the place of .from_sequence for Row() construction. Change interlace to build the list of iterators rather than requiring them to be iterators. pq3.StoredProcedure Use '+ "regprocedure($1)"' for lookups. Earlier versions don't support casting from text.
1 parent d97072e commit 0239106

14 files changed

Lines changed: 232 additions & 90 deletions

File tree

postgresql/driver/pq3.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -188,24 +188,32 @@ def __iter__(self):
188188
return self
189189

190190
def __next__(self):
191+
self.cursor._expand()
192+
193+
# Grab the whole thing.
191194
if self.cursor._offset == 0:
192195
chunk = self.cursor._buffer
193196
else:
194197
chunk = self.cursor._buffer[self.cursor._offset:]
198+
199+
self.cursor.__dict__.update({
200+
'_offset': 0,
201+
'_buffer': [],
202+
})
203+
195204
if not chunk:
196-
self.cursor._buffer_more(self.cursor.chunksize or 64, self.cursor.direction)
205+
self.cursor._buffer_more(self.cursor.chunksize or 128, self.cursor.direction)
197206
if not self.cursor._buffer \
198207
and self.cursor._last_increase != self.cursor._last_reqsize:
199208
raise StopIteration
200209
# offset is expected to be zero.
201210
chunk = self.cursor._buffer
202-
203-
self.cursor._offset = 0
204-
self.cursor._buffer = []
205-
self.cursor.__dict__.update({
206-
'_offset': 0,
207-
'_buffer': [],
208-
})
211+
self.cursor.__dict__.update({
212+
'_offset': 0,
213+
'_buffer': [],
214+
})
215+
else:
216+
self.cursor._dispatch_for_more(self.cursor.direction)
209217

210218
return chunk
211219

@@ -689,16 +697,15 @@ def _buffer_more(self, quantity, direction):
689697
return self._last_increase
690698

691699
def _expansion(self):
692-
cte = self._raise_column_tuple_error
693700
return [
694-
pg_types.Row(
701+
pg_types.Row.from_sequence(
702+
self._output_attmap,
695703
pg_typio.process_tuple(
696-
self._output_io, y, cte
704+
self._output_io, y, self._raise_column_tuple_error
697705
),
698-
keymap = self._output_attmap
699706
)
700707
for y in self._xact.messages_received()
701-
if y.type == pq.element.Tuple.type
708+
if y.type is pq.element.Tuple.type
702709
]
703710

704711
def _pq_xp_move(self, position, whence):
@@ -1335,12 +1342,12 @@ def first(self, *parameters):
13351342
return None
13361343

13371344
if len(self._output_io) > 1:
1338-
return pg_types.Row(
1345+
return pg_types.Row.from_sequence(
1346+
self._output_attmap,
13391347
pg_typio.process_tuple(
13401348
self._output_io, xt,
13411349
self._raise_column_tuple_error
13421350
),
1343-
keymap = self._output_attmap
13441351
)
13451352
else:
13461353
if xt[0] is None:
@@ -1537,7 +1544,7 @@ def __init__(self, ident, database, description = ()):
15371544
).first(int(ident))
15381545
else:
15391546
proctup = database.prepare(
1540-
ProcedureLookup + ' WHERE pg_proc.oid = $1::text::regprocedure',
1547+
ProcedureLookup + ' WHERE pg_proc.oid = regprocedurein($1)',
15411548
title = 'func_lookup_by_name'
15421549
).first(ident)
15431550
if proctup is None:
@@ -2308,12 +2315,12 @@ def connect(self, timeout = None):
23082315
if sslmode == 'allow':
23092316
# first, without ssl, then with. :)
23102317
socket_makers = list(interlace(
2311-
iter(without_ssl), iter(with_ssl)
2318+
without_ssl, with_ssl
23122319
))
23132320
elif sslmode == 'prefer':
23142321
# first, with ssl, then without. :)
23152322
socket_makers = list(interlace(
2316-
iter(with_ssl), iter(without_ssl)
2323+
with_ssl, without_ssl
23172324
))
23182325
# prefer is special, because it *may* be possible to
23192326
# skip the subsequent "without" in situations SSL is off.

postgresql/protocol/typio.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,16 +579,15 @@ def raise_unpack_tuple_error(procs, tup, itemnum):
579579

580580
def unpack_a_record(data):
581581
data = tuple([x[1] for x in ts.record_unpack(data)])
582-
return pg_types.Row(
582+
return pg_types.Row.from_sequence(
583+
attmap,
583584
process_tuple(funpack, data, raise_unpack_tuple_error),
584-
keymap = attmap
585585
)
586586

587+
sorted_atts = sorted(attmap.items(), key = get1)
587588
def pack_a_record(data):
588589
if isinstance(data, dict):
589-
data = [
590-
data.get(k) for k,_ in sorted(attmap.items(), key = get1)
591-
]
590+
data = [data.get(k) for k,_ in sorted_atts]
592591
return ts.record_pack(
593592
tuple(zip(
594593
typids,
@@ -713,9 +712,13 @@ def __init__(self):
713712
pg_types.NAMEOID : (None, None),
714713
pg_types.BPCHAROID : (None, None),
715714
pg_types.VARCHAROID : (None, None),
715+
pg_types.CSTRINGOID : (None, None),
716716
pg_types.TEXTOID : (None, None),
717717
pg_types.CIDROID : (None, None),
718718
pg_types.INETOID : (None, None),
719+
pg_types.REGPROCEDUREOID : (None, None),
720+
pg_types.REGTYPEOID : (None, None),
721+
pg_types.REGPROCOID : (None, None),
719722

720723
pg_types.XMLOID : (
721724
self.xml_pack, self.xml_unpack

postgresql/protocol/xact3.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
import os
88
from abc import abstractmethod
99
from pprint import pformat
10+
from itertools import chain
11+
from operator import itemgetter
12+
get0 = itemgetter(0)
13+
get1 = itemgetter(1)
1014

15+
from ..python.functools import Composition as compose
1116
from .. import api as pg_api
1217
from .. import exceptions as pg_exc
1318
from . import element3 as element
1419

1520
from hashlib import md5
1621
from ..resolved.crypt import crypt
1722

23+
proctup = compose((get1, compose))
1824
Receiving = True
1925
Sending = False
2026
Complete = (None, None)
@@ -433,24 +439,23 @@ def reset(self):
433439

434440
def asyncs(self):
435441
"iterate over asynchronous messages received"
436-
for x in self._asyncs:
437-
for y in x[1]:
438-
yield y
442+
return chain.from_iterable(map(get1, self._asyncs))
439443

440444
def messages_received(self):
441-
for x in self.completed:
442-
for m in x[1]:
443-
yield m
445+
'Received and validate messages'
446+
return chain.from_iterable(map(get1, self.completed))
444447

445448
def reverse(self):
446449
"""
447-
A generator that producing an iterator of completed messages in reverse
450+
A iterator that producing the completed messages in reverse
448451
order. Last in, first out.
449452
"""
450-
for x in range(len(self.completed) - 1, -1, -1):
451-
c = self.completed[x][1]
452-
for y in range(len(c) - 1, -1, -1):
453-
yield c[y]
453+
return chain.from_iterable(
454+
map(
455+
compose((get1, reversed)),
456+
reversed(self.completed)
457+
)
458+
)
454459

455460
def standard_put(self, messages):
456461
"""
@@ -598,7 +603,7 @@ def put_copydata(self, messages):
598603
In the context of a copy, `put_copydata` is used as a fast path for
599604
storing `element.CopyData` messages. When a non-`element.CopyData.type`
600605
message is received, it reverts the ``state`` attribute back to
601-
`standard_put` to normally process the message..
606+
`standard_put` to process the message..
602607
"""
603608
# "Fail" quickly if the last message is not copy data.
604609
if messages[-1][0] is not element.CopyData.type:
@@ -634,12 +639,14 @@ def put_tupledata(self, messages):
634639
self.state = (Receiving, self.standard_put)
635640
return self.standard_put(messages)
636641

642+
p = element.Tuple.parse
637643
tuplemessages = []
638644
for x in messages:
639645
if x[0] is not element.Tuple.type:
640646
self.state = (Receiving, self.standard_put)
641647
return self.standard_put(messages)
642-
tuplemessages.append(element.Tuple.parse(x[1]))
648+
tuplemessages.append(p(x[1]))
649+
#tuplemessages = list(map(proctup, messages))
643650

644651
if not self.completed or self.completed[-1][0] != id(messages):
645652
self.completed.append(((id(messages), tuplemessages)))

postgresql/python/functools.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66
additional functools
77
"""
88
from .decorlib import method
9+
10+
def rsetattr(attr, val, ob):
11+
"""
12+
setattr() and return `ob`. Different order used to allow easier partial
13+
usage.
14+
"""
15+
setattr(ob, attr, val)
16+
return ob
17+
918
try:
10-
from .optimized import compose
19+
from .optimized import rsetattr
1120
except ImportError:
1221
pass
1322

@@ -17,7 +26,10 @@ def __call__(self, r):
1726
for x in self:
1827
r = x(r)
1928
return r
29+
2030
try:
31+
from .optimized import compose
2132
__call__ = method(compose)
22-
except NameError:
33+
del compose
34+
except ImportError:
2335
pass

postgresql/python/itertools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ def interlace(*iters) -> collections.Iterable:
1919
i1-n, i2-n, ..., in-n,
2020
)
2121
"""
22+
iters = [iter(x) for x in iters]
2223
for i in cycle(range(len(iters))):
2324
yield next(iters[i])

postgresql/python/optimized.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
#include <Python.h>
99
#include <structmember.h>
1010

11+
static PyObject *
12+
rsetattr(PyObject *self, PyObject *args)
13+
{
14+
PyObject *ob, *attr, *val;
15+
16+
if (!PyArg_ParseTuple(args, "OOO", &attr, &val, &ob))
17+
return(NULL);
18+
19+
if (PyObject_SetAttr(ob, attr, val) < 0)
20+
return(NULL);
21+
22+
Py_INCREF(ob);
23+
return(ob);
24+
}
25+
1126
/*
1227
* Override the functools.Composition __call__.
1328
*/
@@ -72,6 +87,11 @@ compose(PyObject *self, PyObject *args)
7287
}
7388

7489
static PyMethodDef optimized_methods[] = {
90+
{"rsetattr", rsetattr, METH_VARARGS,
91+
PyDoc_STR(
92+
"rsetattr(attr, val, ob) set the attribute to the value *and* return `ob`."
93+
),
94+
},
7595
{"compose", compose, METH_VARARGS,
7696
PyDoc_STR(
7797
"given a sequence of callables, "

postgresql/test/test_cluster.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
default_install = Installation.default()
1313
if default_install is None:
1414
sys.stderr.write("ERROR: cannot find 'default' pg_config\n")
15-
sys.stderr.write("HINT: set PGINSTALLATION to the `pg_config` path\n")
15+
sys.stderr.write("HINT: set the PGINSTALLATION environment variable to the `pg_config` path\n")
1616
sys.exit(1)
1717

1818
class test_cluster(unittest.TestCase):

0 commit comments

Comments
 (0)