Added support for arrays of composite types

This commit is contained in:
Daniele Varrazzo 2011-09-22 19:55:33 +02:00
parent 2f9ceeac64
commit 8963b8adcb
3 changed files with 55 additions and 7 deletions

2
NEWS
View File

@ -3,7 +3,7 @@ What's new in psycopg 2.4.3
- Added 'new_array_type()' function for easy creation of array - Added 'new_array_type()' function for easy creation of array
typecasters. typecasters.
- Added support for arrays of hstores (ticket #66). - Added support for arrays of hstores and composite types (ticket #66).
- Fixed segfault in case of transaction started with connection lost - Fixed segfault in case of transaction started with connection lost
(and possibly other events). (and possibly other events).
- Rollback connections in transaction or in error before putting them - Rollback connections in transaction or in error before putting them

View File

@ -778,6 +778,9 @@ class CompositeCaster(object):
"""Helps conversion of a PostgreSQL composite type into a Python object. """Helps conversion of a PostgreSQL composite type into a Python object.
The class is usually created by the `register_composite()` function. The class is usually created by the `register_composite()` function.
You may want to create and register manually instances of the class if
querying the database at registration time is not desirable (such as when
using an :ref:`asynchronous connections <async-support>`).
.. attribute:: name .. attribute:: name
@ -787,6 +790,10 @@ class CompositeCaster(object):
The oid of the PostgreSQL type. The oid of the PostgreSQL type.
.. attribute:: array_oid
The oid of the PostgreSQL array type, if available.
.. attribute:: type .. attribute:: type
The type of the Python objects returned. If :py:func:`collections.namedtuple()` The type of the Python objects returned. If :py:func:`collections.namedtuple()`
@ -802,14 +809,20 @@ class CompositeCaster(object):
List of component type oids of the type to be casted. List of component type oids of the type to be casted.
""" """
def __init__(self, name, oid, attrs): def __init__(self, name, oid, attrs, array_oid=None):
self.name = name self.name = name
self.oid = oid self.oid = oid
self.array_oid = array_oid
self.attnames = [ a[0] for a in attrs ] self.attnames = [ a[0] for a in attrs ]
self.atttypes = [ a[1] for a in attrs ] self.atttypes = [ a[1] for a in attrs ]
self._create_type(name, self.attnames) self._create_type(name, self.attnames)
self.typecaster = _ext.new_type((oid,), name, self.parse) self.typecaster = _ext.new_type((oid,), name, self.parse)
if array_oid:
self.array_typecaster = _ext.new_array_type(
(array_oid,), "%sARRAY" % name, self.typecaster)
else:
self.array_typecaster = None
def parse(self, s, curs): def parse(self, s, curs):
if s is None: if s is None:
@ -881,15 +894,18 @@ class CompositeCaster(object):
tname = name tname = name
schema = 'public' schema = 'public'
# column typarray not available before PG 8.3
typarray = conn.server_version >= 80300 and "typarray" or "NULL"
# get the type oid and attributes # get the type oid and attributes
curs.execute("""\ curs.execute("""\
SELECT t.oid, attname, atttypid SELECT t.oid, %s, attname, atttypid
FROM pg_type t FROM pg_type t
JOIN pg_namespace ns ON typnamespace = ns.oid JOIN pg_namespace ns ON typnamespace = ns.oid
JOIN pg_attribute a ON attrelid = typrelid JOIN pg_attribute a ON attrelid = typrelid
WHERE typname = %s and nspname = %s WHERE typname = %%s and nspname = %%s
ORDER BY attnum; ORDER BY attnum;
""", (tname, schema)) """ % typarray, (tname, schema))
recs = curs.fetchall() recs = curs.fetchall()
@ -903,9 +919,11 @@ ORDER BY attnum;
"PostgreSQL type '%s' not found" % name) "PostgreSQL type '%s' not found" % name)
type_oid = recs[0][0] type_oid = recs[0][0]
type_attrs = [ (r[1], r[2]) for r in recs ] array_oid = recs[0][1]
type_attrs = [ (r[2], r[3]) for r in recs ]
return CompositeCaster(tname, type_oid, type_attrs) return CompositeCaster(tname, type_oid, type_attrs,
array_oid=array_oid)
def register_composite(name, conn_or_curs, globally=False): def register_composite(name, conn_or_curs, globally=False):
"""Register a typecaster to convert a composite type into a tuple. """Register a typecaster to convert a composite type into a tuple.
@ -919,10 +937,17 @@ def register_composite(name, conn_or_curs, globally=False):
*conn_or_curs*, otherwise register it globally *conn_or_curs*, otherwise register it globally
:return: the registered `CompositeCaster` instance responsible for the :return: the registered `CompositeCaster` instance responsible for the
conversion conversion
.. versionchanged:: 2.4.3
added support for array of composite types
""" """
caster = CompositeCaster._from_db(name, conn_or_curs) caster = CompositeCaster._from_db(name, conn_or_curs)
_ext.register_type(caster.typecaster, not globally and conn_or_curs or None) _ext.register_type(caster.typecaster, not globally and conn_or_curs or None)
if caster.array_typecaster is not None:
_ext.register_type(caster.array_typecaster, not globally and conn_or_curs or None)
return caster return caster

View File

@ -620,6 +620,29 @@ class AdaptTypeTestCase(unittest.TestCase):
curs.execute("select (4,8)::typens.typens_ii") curs.execute("select (4,8)::typens.typens_ii")
self.assertEqual(curs.fetchone()[0], (4,8)) self.assertEqual(curs.fetchone()[0], (4,8))
@skip_if_no_composite
@skip_before_postgres(8, 3)
def test_composite_array(self):
oid = self._create_type("type_isd",
[('anint', 'integer'), ('astring', 'text'), ('adate', 'date')])
t = psycopg2.extras.register_composite("type_isd", self.conn)
curs = self.conn.cursor()
r1 = (10, 'hello', date(2011,1,2))
r2 = (20, 'world', date(2011,1,3))
curs.execute("select %s::type_isd[];", ([r1, r2],))
v = curs.fetchone()[0]
self.assertEqual(len(v), 2)
self.assert_(isinstance(v[0], t.type))
self.assertEqual(v[0][0], 10)
self.assertEqual(v[0][1], "hello")
self.assertEqual(v[0][2], date(2011,1,2))
self.assert_(isinstance(v[1], t.type))
self.assertEqual(v[1][0], 20)
self.assertEqual(v[1][1], "world")
self.assertEqual(v[1][2], date(2011,1,3))
def _create_type(self, name, fields): def _create_type(self, name, fields):
curs = self.conn.cursor() curs = self.conn.cursor()
try: try: