Skip to content

Commit

Permalink
- Added support for the :class:postgresql.JSONB datatype when
Browse files Browse the repository at this point in the history
using psycopg2 2.5.4 or greater, which features native conversion
of JSONB data so that SQLAlchemy's converters must be disabled;
additionally, the newly added psycopg2 extension
``extras.register_default_jsonb`` is used to establish a JSON
deserializer passed to the dialect via the ``json_deserializer``
argument.  Also repaired the Postgresql integration tests which
weren't actually round-tripping the JSONB type as opposed to the
JSON type.  Pull request courtesy Mateusz Susik.

- Repaired the use of the "array_oid" flag when registering the
HSTORE type with older psycopg2 versions < 2.4.3, which does not
support this flag, as well as use of the native json serializer
hook "register_default_json" with user-defined ``json_deserializer``
on psycopg2 versions < 2.5, which does not include native json.
  • Loading branch information
zzzeek committed Feb 1, 2015
1 parent a523163 commit bf70f55
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 17 deletions.
12 changes: 12 additions & 0 deletions README.unittests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,18 @@ expect them to be present will fail.

Additional steps specific to individual databases are as follows::

POSTGRESQL: To enable unicode testing with JSONB, create the
database with UTF8 encoding::

postgres=# create database test with owner=scott encoding='utf8' template=template0;

To include tests for HSTORE, create the HSTORE type engine::

postgres=# \c test;
You are now connected to database "test" as user "postgresql".
test=# create extension hstore;
CREATE EXTENSION

MYSQL: Default storage engine should be "MyISAM". Tests that require
"InnoDB" as the engine will specify this explicitly.

Expand Down
23 changes: 23 additions & 0 deletions doc/build/changelog/changelog_09.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@
.. changelog::
:version: 0.9.9

.. change::
:tags: bug, postgresql
:pullreq: github:145

Added support for the :class:`postgresql.JSONB` datatype when
using psycopg2 2.5.4 or greater, which features native conversion
of JSONB data so that SQLAlchemy's converters must be disabled;
additionally, the newly added psycopg2 extension
``extras.register_default_jsonb`` is used to establish a JSON
deserializer passed to the dialect via the ``json_deserializer``
argument. Also repaired the Postgresql integration tests which
weren't actually round-tripping the JSONB type as opposed to the
JSON type. Pull request courtesy Mateusz Susik.

.. change::
:tags: bug, postgresql

Repaired the use of the "array_oid" flag when registering the
HSTORE type with older psycopg2 versions < 2.4.3, which does not
support this flag, as well as use of the native json serializer
hook "register_default_json" with user-defined ``json_deserializer``
on psycopg2 versions < 2.5, which does not include native json.

.. change::
:tags: bug, schema
:tickets: 3298, 1765
Expand Down
26 changes: 16 additions & 10 deletions lib/sqlalchemy/dialects/postgresql/psycopg2.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,17 @@
The ``psycopg2`` DBAPI includes an extension to natively handle marshalling of
the HSTORE type. The SQLAlchemy psycopg2 dialect will enable this extension
by default when it is detected that the target database has the HSTORE
type set up for use. In other words, when the dialect makes the first
by default when psycopg2 version 2.4 or greater is used, and
it is detected that the target database has the HSTORE type set up for use.
In other words, when the dialect makes the first
connection, a sequence like the following is performed:
1. Request the available HSTORE oids using
``psycopg2.extras.HstoreAdapter.get_oids()``.
If this function returns a list of HSTORE identifiers, we then determine
that the ``HSTORE`` extension is present.
This function is **skipped** if the version of psycopg2 installed is
less than version 2.4.
2. If the ``use_native_hstore`` flag is at its default of ``True``, and
we've detected that ``HSTORE`` oids are available, the
Expand Down Expand Up @@ -583,19 +586,22 @@ def on_connect(conn):
hstore_oids = self._hstore_oids(conn)
if hstore_oids is not None:
oid, array_oid = hstore_oids
kw = {'oid': oid}
if util.py2k:
extras.register_hstore(conn, oid=oid,
array_oid=array_oid,
unicode=True)
else:
extras.register_hstore(conn, oid=oid,
array_oid=array_oid)
kw['unicode'] = True
if self.psycopg2_version >= (2, 4, 3):
kw['array_oid'] = array_oid
extras.register_hstore(conn, **kw)
fns.append(on_connect)

if self.dbapi and self._json_deserializer:
def on_connect(conn):
extras.register_default_json(
conn, loads=self._json_deserializer)
if self._has_native_json:
extras.register_default_json(
conn, loads=self._json_deserializer)
if self._has_native_jsonb:
extras.register_default_jsonb(
conn, loads=self._json_deserializer)
fns.append(on_connect)

if fns:
Expand Down
27 changes: 20 additions & 7 deletions test/dialect/postgresql/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1948,13 +1948,15 @@ class JSONRoundTripTest(fixtures.TablesTest):
__only_on__ = ('postgresql >= 9.3',)
__backend__ = True

test_type = JSON

@classmethod
def define_tables(cls, metadata):
Table('data_table', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(30), nullable=False),
Column('data', JSON),
Column('nulldata', JSON(none_as_null=True))
Column('data', cls.test_type),
Column('nulldata', cls.test_type(none_as_null=True))
)

def _fixture_data(self, engine):
Expand Down Expand Up @@ -2016,7 +2018,8 @@ def _non_native_engine(self, json_serializer=None, json_deserializer=None):
else:
options = {}

if testing.against("postgresql+psycopg2"):
if testing.against("postgresql+psycopg2") and \
testing.db.dialect.psycopg2_version >= (2, 5):
from psycopg2.extras import register_default_json
engine = engines.testing_engine(options=options)

Expand All @@ -2037,7 +2040,7 @@ def pass_(value):
def test_reflect(self):
insp = inspect(testing.db)
cols = insp.get_columns('data_table')
assert isinstance(cols[2]['type'], JSON)
assert isinstance(cols[2]['type'], self.test_type)

@testing.only_on("postgresql+psycopg2")
def test_insert_native(self):
Expand Down Expand Up @@ -2096,7 +2099,7 @@ def dumps(value):
"key": "value",
"x": "q"
},
JSON
self.test_type
)
])
eq_(
Expand Down Expand Up @@ -2172,7 +2175,7 @@ def _test_fixed_round_trip(self, engine):
"key": "value",
"key2": {"k1": "v1", "k2": "v2"}
},
JSON
self.test_type
)
])
eq_(
Expand All @@ -2199,7 +2202,7 @@ def _test_unicode_round_trip(self, engine):
util.u('réveillé'): util.u('réveillé'),
"data": {"k1": util.u('drôle')}
},
JSON
self.test_type
)
])
eq_(
Expand Down Expand Up @@ -2266,3 +2269,13 @@ def test_where_contained_by(self):

class JSONBRoundTripTest(JSONRoundTripTest):
__only_on__ = ('postgresql >= 9.4',)

test_type = JSONB

@testing.requires.postgresql_utf8_server_encoding
def test_unicode_round_trip_python(self):
super(JSONBRoundTripTest, self).test_unicode_round_trip_python()

@testing.requires.postgresql_utf8_server_encoding
def test_unicode_round_trip_native(self):
super(JSONBRoundTripTest, self).test_unicode_round_trip_native()
6 changes: 6 additions & 0 deletions test/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,9 @@ def _has_mysql_fully_case_sensitive(self, config):
return against(config, 'mysql') and \
config.db.dialect._detect_casing(config.db) == 0

@property
def postgresql_utf8_server_encoding(self):
return only_if(
lambda config: against(config, 'postgresql') and
config.db.scalar("show server_encoding").lower() == "utf8"
)

0 comments on commit bf70f55

Please sign in to comment.