From: Mike Bayer Date: Fri, 28 Jun 2013 15:35:57 +0000 (-0400) Subject: Fixed bug in HSTORE type where keys/values that contained X-Git-Tag: rel_0_8_2~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=69179e009c2dcf71945415d455765b98ecd89fe4;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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. [ticket:2766] Conflicts: doc/build/changelog/changelog_09.rst lib/sqlalchemy/dialects/postgresql/hstore.py --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index ef87fea453..625b72c0b7 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -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 diff --git a/lib/sqlalchemy/dialects/postgresql/hstore.py b/lib/sqlalchemy/dialects/postgresql/hstore.py index 4daf54bab6..dc9f05a604 100644 --- a/lib/sqlalchemy/dialects/postgresql/hstore.py +++ b/lib/sqlalchemy/dialects/postgresql/hstore.py @@ -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)) diff --git a/test/dialect/test_postgresql.py b/test/dialect/test_postgresql.py index 1389fe5f87..986e575164 100644 --- a/test/dialect/test_postgresql.py +++ b/test/dialect/test_postgresql.py @@ -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'