]> 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:06:04 +0000 (19:06 -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.

(cherry picked from commit bf70f556b382dc376783efbcb598e0fab71ee235)

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 99ee1d9977658f2f700167ff31fc479ce0e08ea1..73a9c7ed88bcf113065752bbc07a71a3302be1e2 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 034e02251d1a75339e3389407db80ccd94c0da6b..17e56db4d5d50dc01da5f0b8216f01b879b322a4 100644 (file)
@@ -250,14 +250,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
@@ -559,19 +562,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 42a04035a5d8fa42a5599282a59e73eb3ee75b51..4f825c1779b15f2f7af207438ce771d1032696ab 100644 (file)
@@ -1947,13 +1947,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):
@@ -2015,7 +2017,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)
 
@@ -2036,7 +2039,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):
@@ -2095,7 +2098,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     "key": "value",
                     "x": "q"
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2171,7 +2174,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     "key": "value",
                     "key2": {"k1": "v1", "k2": "v2"}
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2198,7 +2201,7 @@ class JSONRoundTripTest(fixtures.TablesTest):
                     util.u('réveillé'): util.u('réveillé'),
                     "data": {"k1": util.u('drôle')}
                 },
-                JSON
+                self.test_type
             )
         ])
         eq_(
@@ -2265,3 +2268,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 2953c2167a53704a625a288f4e49c3f98b0f4728..9f7fbab1c41e8fa0572310ab708079b81ebd1364 100644 (file)
@@ -781,3 +781,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"
+        )