]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fixed bug in HSTORE type where keys/values that contained
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 28 Jun 2013 15:35:57 +0000 (11:35 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 28 Jun 2013 15:38:28 +0000 (11:38 -0400)
backslashed quotes would not be escaped correctly when
using the "non native" (i.e. non-psycopg2) means
of translating HSTORE data.  Patch courtesy Ryan Kelly.
[ticket:2766]

Conflicts:
doc/build/changelog/changelog_09.rst
lib/sqlalchemy/dialects/postgresql/hstore.py

doc/build/changelog/changelog_08.rst
lib/sqlalchemy/dialects/postgresql/hstore.py
test/dialect/test_postgresql.py

index ef87fea453c8d6165540d8b6d9ab8d3b6049918f..625b72c0b7841345e05551fcd71d5b995efab1b3 100644 (file)
@@ -6,6 +6,15 @@
 .. changelog::
     :version: 0.8.2
 
+    .. change::
+        :tags: bug, postgresql
+        :tickets: 2766
+
+        Fixed bug in HSTORE type where keys/values that contained
+        backslashed quotes would not be escaped correctly when
+        using the "non native" (i.e. non-psycopg2) means
+        of translating HSTORE data.  Patch courtesy Ryan Kelly.
+
     .. change::
         :tags: bug, postgresql
         :tickets: 2767
index 4daf54bab6208a83b5803b3486cba2a3ed33446d..dc9f05a6045ee6988dd002ac8884e0467ecb1f8f 100644 (file)
@@ -68,11 +68,11 @@ def _parse_hstore(hstore_str):
     pair_match = HSTORE_PAIR_RE.match(hstore_str)
 
     while pair_match is not None:
-        key = pair_match.group('key')
+        key = pair_match.group('key').replace(r'\"', '"').replace("\\\\", "\\")
         if pair_match.group('value_null'):
             value = None
         else:
-            value = pair_match.group('value').replace(r'\"', '"')
+            value = pair_match.group('value').replace(r'\"', '"').replace("\\\\", "\\")
         result[key] = value
 
         pos += pair_match.end()
@@ -98,7 +98,7 @@ def _serialize_hstore(val):
         if position == 'value' and s is None:
             return 'NULL'
         elif isinstance(s, basestring):
-            return '"%s"' % s.replace('"', r'\"')
+            return '"%s"' % s.replace("\\", "\\\\").replace('"', r'\"')
         else:
             raise ValueError("%r in %s position is not a string." %
                              (s, position))
index 1389fe5f877cd40abc59739e169a538a3e8da0f7..986e57516442b8ee0033b467742b78eafd9d8fdf 100644 (file)
@@ -2951,6 +2951,16 @@ class HStoreTest(fixtures.TestBase):
             '"key1"=>"value1", "key2"=>"value2"'
         )
 
+    def test_bind_serialize_with_slashes_and_quotes(self):
+        from sqlalchemy.engine import default
+
+        dialect = default.DefaultDialect()
+        proc = self.test_table.c.hash.type._cached_bind_processor(dialect)
+        eq_(
+            proc({'\\"a': '\\"1'}),
+            '"\\\\\\"a"=>"\\\\\\"1"'
+        )
+
     def test_parse_error(self):
         from sqlalchemy.engine import default
 
@@ -2977,6 +2987,17 @@ class HStoreTest(fixtures.TestBase):
             {"key1": "value1", "key2": "value2"}
         )
 
+    def test_result_deserialize_with_slashes_and_quotes(self):
+        from sqlalchemy.engine import default
+
+        dialect = default.DefaultDialect()
+        proc = self.test_table.c.hash.type._cached_result_processor(
+                    dialect, None)
+        eq_(
+            proc('"\\\\\\"a"=>"\\\\\\"1"'),
+            {'\\"a': '\\"1'}
+        )
+
     def test_bind_serialize_psycopg2(self):
         from sqlalchemy.dialects.postgresql import psycopg2
 
@@ -3291,6 +3312,22 @@ class HStoreRoundTripTest(fixtures.TablesTest):
         engine = testing.db
         self._test_unicode_round_trip(engine)
 
+    def test_escaped_quotes_round_trip_python(self):
+        engine = self._non_native_engine()
+        self._test_escaped_quotes_round_trip(engine)
+
+    @testing.only_on("postgresql+psycopg2")
+    def test_escaped_quotes_round_trip_native(self):
+        engine = testing.db
+        self._test_escaped_quotes_round_trip(engine)
+
+    def _test_escaped_quotes_round_trip(self, engine):
+        engine.execute(
+            self.tables.data_table.insert(),
+            {'name': 'r1', 'data': {r'key \"foo\"': r'value \"bar"\ xyz'}}
+        )
+        self._assert_data([{r'key \"foo\"': r'value \"bar"\ xyz'}])
+
 class _RangeTypeMixin(object):
     __requires__ = 'range_types',
     __dialect__ = 'postgresql+psycopg2'