Skip to content

Commit bbd01d4

Browse files
author
James William Pye
committed
Add the optimized module providing a faster postgresql.protocol.element3.Tuple.parse.
1 parent d1b1f66 commit bbd01d4

3 files changed

Lines changed: 174 additions & 15 deletions

File tree

postgresql/protocol/element3.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
import pprint
99
from struct import pack, unpack, Struct
1010

11+
try:
12+
from .optimized import parse_tuple_message
13+
except ImportError:
14+
pass
15+
1116
StringFormat = b'\x00\x00'
1217
BinaryFormat = b'\x00\x01'
1318

@@ -69,32 +74,20 @@ def parse(typ, data):
6974
raise ValueError("string message not NUL-terminated")
7075
return typ(data[:-1])
7176

72-
class TupleMessage(Message):
77+
class TupleMessage(tuple, Message):
7378
"""
7479
A message who's data is based on a tuple structure.
7580
"""
7681
type = b''
77-
__slots__ = ('data',)
82+
__slots__ = ()
7883

7984
def __repr__(self):
8085
return '%s.%s(%s)' %(
8186
type(self).__module__,
8287
type(self).__name__,
83-
repr(self.data)
88+
tuple.__repr__(self)
8489
)
8590

86-
def __iter__(self):
87-
return self.data.__iter__()
88-
89-
def __len__(self):
90-
return self.data.__len__()
91-
92-
def __getitem__(self, i):
93-
return self.data.__getitem__(i)
94-
95-
def __init__(self, data):
96-
self.data = tuple(data)
97-
9891
class Void(Message):
9992
"""
10093
An absolutely empty message. When serialized, it always yields an empty
@@ -437,6 +430,10 @@ def parse(typ, data):
437430
atts.append(att)
438431
natts -= 1
439432
return typ(atts)
433+
try:
434+
parse = classmethod(parse_tuple_message)
435+
except NameError:
436+
pass
440437

441438
class KillInformation(Message):
442439
'Backend cancellation information'

postgresql/protocol/optimized.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* copyright 2009, James William Pye
3+
* http://python.projects.postgresql.org
4+
*
5+
*//*
6+
* Optimizations for protocol modules.
7+
*/
8+
#include <stdint.h>
9+
#ifdef WIN32
10+
#include <winsock.h>
11+
#else
12+
#include <sys/types.h>
13+
#include <netinet/in.h>
14+
#endif
15+
#include <Python.h>
16+
#include <structmember.h>
17+
18+
static PyObject *
19+
parse_tuple_message(PyObject *self, PyObject *args)
20+
{
21+
PyObject *typ;
22+
PyObject *ob;
23+
PyObject *rob;
24+
const char *data;
25+
uint16_t cnatt = 0, natts = 0;
26+
uint32_t attsize = 0;
27+
int position = 0;
28+
int dlen = 0;
29+
30+
if (PyArg_ParseTuple(args, "Oy#", &typ, &data, &dlen) < 0)
31+
return(NULL);
32+
33+
if (typ != Py_None)
34+
{
35+
if (!PyObject_IsSubclass(typ, (PyObject *) &PyTuple_Type))
36+
{
37+
const char *typname = "<not-a-type>";
38+
if (PyObject_IsInstance((PyObject *) typ->ob_type, (PyObject *) &PyType_Type))
39+
{
40+
typname = ((PyTypeObject *) typ)->tp_name;
41+
}
42+
43+
PyErr_Format(
44+
PyExc_TypeError,
45+
"cannot instantiate wire tuple into a non-tuple subtype: %s",
46+
typname
47+
);
48+
return(NULL);
49+
}
50+
}
51+
else
52+
{
53+
typ = (PyObject *) &PyTuple_Type;
54+
}
55+
56+
if (dlen < 2)
57+
{
58+
PyErr_Format(PyExc_ValueError,
59+
"invalid tuple message: %d bytes is too small", dlen);
60+
return(NULL);
61+
}
62+
natts = ntohs(*((uint16_t *) (data)));
63+
64+
rob = ((PyTypeObject *) typ)->tp_alloc((PyTypeObject *) typ, natts);
65+
if (rob == NULL)
66+
{
67+
return(NULL);
68+
}
69+
70+
position += 2;
71+
while (cnatt < natts)
72+
{
73+
/*
74+
* Need enough data for the attribute size.
75+
*/
76+
if (position + 4 > dlen)
77+
{
78+
PyErr_Format(PyExc_ValueError,
79+
"not enough data for attribute %d", cnatt);
80+
goto cleanup;
81+
}
82+
83+
attsize = ntohl(*((uint32_t *) (data + position)));
84+
position += 4;
85+
/*
86+
* NULL.
87+
*/
88+
if (attsize == 0xFFFFFFFFL)
89+
{
90+
Py_INCREF(Py_None);
91+
PyTuple_SET_ITEM(rob, cnatt, Py_None);
92+
}
93+
else
94+
{
95+
if (position + attsize > dlen)
96+
{
97+
PyErr_Format(PyExc_ValueError,
98+
"not enough data for attribute %d, size %d, "
99+
"but only %d remaining bytes in message",
100+
cnatt, attsize, dlen - position
101+
);
102+
goto cleanup;
103+
}
104+
ob = PyBytes_FromStringAndSize(data + position, attsize);
105+
if (ob == NULL)
106+
{
107+
/*
108+
* Probably a OOM error.
109+
*/
110+
goto cleanup;
111+
}
112+
PyTuple_SET_ITEM(rob, cnatt, ob);
113+
position += attsize;
114+
}
115+
116+
cnatt++;
117+
}
118+
119+
if (position != dlen)
120+
{
121+
PyErr_Format(PyExc_ValueError,
122+
"invalid tuple message, %d remaining "
123+
"bytes after processing %d attriutes",
124+
dlen - position, cnatt
125+
);
126+
goto cleanup;
127+
}
128+
129+
return(rob);
130+
131+
cleanup:
132+
Py_DECREF(rob);
133+
return(NULL);
134+
}
135+
136+
static PyMethodDef optimized_methods[] = {
137+
{"parse_tuple_message", (PyCFunction) parse_tuple_message, METH_VARARGS,
138+
PyDoc_STR("parse the given tuple data into a tuple of raw data"),},
139+
{NULL}
140+
};
141+
142+
static struct PyModuleDef optimized_module = {
143+
PyModuleDef_HEAD_INIT,
144+
"optimized",/* name of module */
145+
NULL, /* module documentation, may be NULL */
146+
-1, /* size of per-interpreter state of the module,
147+
or -1 if the module keeps state in global variables. */
148+
optimized_methods,
149+
};
150+
151+
PyMODINIT_FUNC
152+
PyInit_optimized(void)
153+
{
154+
return(PyModule_Create(&optimized_module));
155+
}
156+
/*
157+
* vim: ts=3:sw=3:noet:
158+
*/

postgresql/release/distutils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@
9696
'protocol.cbuffer' : {
9797
'sources' : [os.path.join('protocol', 'buffer.c')],
9898
'libraries' : (sys.platform == 'win32' and ['ws2_32'] or []),
99+
},
100+
'protocol.optimized' : {
101+
'sources' : [os.path.join('protocol', 'optimized.c')],
102+
'libraries' : (sys.platform == 'win32' and ['ws2_32'] or []),
99103
}
100104
}
101105

0 commit comments

Comments
 (0)