mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-01-31 09:24:07 +03:00
Added support for arrays of composite types
This commit is contained in:
parent
2f9ceeac64
commit
8963b8adcb
2
NEWS
2
NEWS
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user