Skip to content

Commit 0f21994

Browse files
author
James William Pye
committed
Implement hstore support.
1 parent 21fe68a commit 0f21994

3 files changed

Lines changed: 98 additions & 4 deletions

File tree

postgresql/test/test_types.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
##
2-
# test.test_types
2+
# .test.test_types - test type representations and I/O
33
##
44
import unittest
5+
import struct
56
from ..python.functools import process_tuple
67
from .. import types as pg_types
78
from ..types.io import lib as typlib
89
from ..types.io import builtins
10+
from ..types.io.contrib_hstore import hstore_factory
911
from ..types import Array
1012

13+
class fake_typio(object):
14+
@staticmethod
15+
def encode(x):
16+
return x.encode('utf-8')
17+
@staticmethod
18+
def decode(x):
19+
return x.decode('utf-8')
20+
hstore_pack, hstore_unpack = hstore_factory(fake_typio)
21+
1122
# this must pack to that, and
1223
# that must unpack to this
1324
expectation_samples = {
@@ -128,6 +139,14 @@
128139
b'\x00\x00\x00\x00\xff\xff\xff\xff'
129140
)
130141
],
142+
143+
('hstore', hstore_pack, hstore_unpack) : [
144+
({}, b'\x00\x00\x00\x00'),
145+
({'b' : None}, b'\x00\x00\x00\x01\x00\x00\x00\x01b\xff\xff\xff\xff'),
146+
({'b' : 'k'}, b'\x00\x00\x00\x01\x00\x00\x00\x01b\x00\x00\x00\x01k'),
147+
({'foo' : 'bar'}, b'\x00\x00\x00\x01\x00\x00\x00\x03foo\x00\x00\x00\x03bar'),
148+
({'foo' : None}, b'\x00\x00\x00\x01\x00\x00\x00\x03foo\xff\xff\xff\xff'),
149+
],
131150
}
132151
expectation_samples[('box', typlib.box_pack, typlib.box_unpack)] = \
133152
expectation_samples[('lseg', typlib.lseg_pack, typlib.lseg_unpack)] = [
@@ -363,7 +382,31 @@ def testConsistency(self):
363382
)
364383
)
365384

366-
# Make some slices
385+
##
386+
# Further hstore tests.
387+
def test_hstore(self):
388+
# Can't do some tests with the consistency checks
389+
# because we are not using ordered dictionaries.
390+
self.failUnlessRaises((ValueError, struct.error), hstore_unpack, b'\x00\x00\x00\x00foo')
391+
self.failUnlessRaises(ValueError, hstore_unpack, b'\x00\x00\x00\x01')
392+
self.failUnlessRaises(ValueError, hstore_unpack, b'\x00\x00\x00\x02\x00\x00\x00\x01G\x00\x00\x00\x01G')
393+
sample = [
394+
([('foo','bar'),('k',None),('zero','heroes')],
395+
b'\x00\x00\x00\x03\x00\x00\x00\x03foo' + \
396+
b'\x00\x00\x00\x03bar\x00\x00\x00\x01k\xFF\xFF\xFF\xFF' + \
397+
b'\x00\x00\x00\x04zero\x00\x00\x00\x06heroes'),
398+
([('foo',None),('k',None),('zero',None)],
399+
b'\x00\x00\x00\x03\x00\x00\x00\x03foo' + \
400+
b'\xff\xff\xff\xff\x00\x00\x00\x01k\xFF\xFF\xFF\xFF' + \
401+
b'\x00\x00\x00\x04zero\xFF\xFF\xFF\xFF'),
402+
([], b'\x00\x00\x00\x00'),
403+
]
404+
for x in sample:
405+
src, serialized = x
406+
self.failUnlessEqual(hstore_pack(src), serialized)
407+
self.failUnlessEqual(hstore_unpack(serialized), dict(src))
408+
409+
# Make some slices; used by testSlicing
367410
slice_samples = [
368411
slice(0, None, x+1) for x in range(10)
369412
] + [

postgresql/types/io/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
##
2-
# copyright 2009, James William Pye
3-
# http://python.projects.postgresql.org
2+
# .types.io - I/O routines for packing and unpacking data
43
##
54
"""
65
PostgreSQL type I/O routines--packing and unpacking functions.
@@ -78,6 +77,10 @@
7877
'stdlib_xml_etree' : (
7978
pg_types.XMLOID,
8079
),
80+
81+
'contrib_hstore' : (
82+
'contrib_hstore',
83+
),
8184
}
8285

8386
# OID -> module name
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
##
2+
# .types.io.contrib_hstore - I/O routines for binary hstore
3+
##
4+
from ...python.structlib import split_sized_data, ulong_pack, ulong_unpack
5+
from ...python.itertools import chunk
6+
7+
##
8+
# Build the hstore I/O pair for a given typio.
9+
# It primarily need typio for decode and encode.
10+
def hstore_factory(typio,
11+
unpack_err = "expected {0} items in hstore, but found {1}".format
12+
):
13+
def pack_hstore(x,
14+
encode = typio.encode,
15+
len = len,
16+
):
17+
if hasattr(x, 'items'):
18+
x = x.items()
19+
encoded = [
20+
(encode(k), encode(v)) if v is not None else (encode(k), None)
21+
for k,v in x
22+
]
23+
return ulong_pack(len(encoded)) + b''.join(
24+
ulong_pack(len(k)) + k + b'\xFF\xFF\xFF\xFF'
25+
if v is None else ulong_pack(len(k)) + k + ulong_pack(len(v)) + v
26+
for k,v in encoded
27+
)
28+
29+
def unpack_hstore(x,
30+
decode = typio.decode,
31+
split = split_sized_data,
32+
len = len
33+
):
34+
view = memoryview(x)[4:]
35+
n = ulong_unpack(x)
36+
r = {
37+
decode(y[0]) : (decode(y[1]) if y[1] is not None else None)
38+
for y in chunk(split(view), 2) if y
39+
}
40+
if len(r) != n:
41+
raise ValueError(unpack_err(n, len(r)))
42+
return r
43+
44+
return (pack_hstore, unpack_hstore)
45+
46+
oid_to_io = {
47+
'contrib_hstore' : hstore_factory,
48+
}

0 commit comments

Comments
 (0)