]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added support for the :class:`postgresql.JSONB` datatype when
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 1 Feb 2015 00:04:54 +0000 (19:04 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 1 Feb 2015 00:04:54 +0000 (19:04 -0500)
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.

README.unittests.rst
doc/build/changelog/changelog_09.rst
lib/sqlalchemy/dialects/postgresql/psycopg2.py
test/dialect/postgresql/test_types.py
test/requirements.py

index 7ca229df60957a9877782d77965518a2f2e72286..5aeba51f6cc94d322eabd659a0b90d462f6b6b47 100644 (file)
@@ -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.
 
index 8d4c8d7f67849090985fcccf159aa0eefd401a3b..2af1cd35f0fa7c81f4f24f8be72d2dac0591d485 100644 (file)
 .. 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
index 21011b68bcc6bf31f4e24745db1717120760027d..26e45fed2eb27d88f350dfb323ed154cbc925b9c 100644 (file)
@@ -263,14 +263,17 @@ HSTORE type
 
 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
@@ -583,19 +586,22 @@ class PGDialect_psycopg2(PGDialect):
                 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:
index 1f572c9a1e17469581ba0d35eb303313d086d120..5e00fd6054fbc45022b35464b08f38299368d081 100644 (file)
@@ -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):
@@ -2016,7 +2018,8 @@ class JSONRoundTripTest(fixtures.TablesTest):
         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)
 
@@ -2037,7 +2040,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
     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):
@@ -2096,7 +2099,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     "key": "value",
                     "x": "q"
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2172,7 +2175,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     "key": "value",
                     "key2": {"k1": "v1", "k2": "v2"}
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2199,7 +2202,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     util.u('réveillé'): util.u('réveillé'),
                     "data": {"k1": util.u('drôle')}
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2266,3 +2269,13 @@ class JSONBTest(JSONTest):
 
 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()
index 89fc108b9611a74117761b712783ba7f719f4ab1..4d5869226ff00889886091f2a52c6bfde228889a 100644 (file)
@@ -816,3 +816,9 @@ class DefaultRequirements(SuiteRequirements):
         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"
+        )