diff --git a/CHANGELOG.md b/CHANGELOG.md index 9025349..14bd059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Fixed + +- model: escape single quotes in `Edm.String` literals, so filters on values containing an apostrophe produce a valid OData query - Francois Pilet + ## [1.12.0] diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 7312b01..0470ed3 100644 --- a/pyodata/v2/model.py +++ b/pyodata/v2/model.py @@ -570,7 +570,7 @@ class EdmStringTypTraits(TypTraits): # pylint: disable=no-self-use def to_literal(self, value): - return '\'%s\'' % (value) + return '\'%s\'' % (str(value).replace("'", "''")) # pylint: disable=no-self-use def from_json(self, value): @@ -578,7 +578,7 @@ def from_json(self, value): def from_literal(self, value): if len(value) >= 2 and value[0] == "'" and value[-1] == "'": - return value[1:-1] + return value[1:-1].replace("''", "'") return value diff --git a/tests/test_model_v2.py b/tests/test_model_v2.py index 32aa207..87c669b 100644 --- a/tests/test_model_v2.py +++ b/tests/test_model_v2.py @@ -430,6 +430,17 @@ def test_traits(): assert typ.traits.from_literal("only right'") == "only right'" assert typ.traits.from_literal("'") == "'" + # EdmStringTypTraits.to_literal — a single quote inside the value must be doubled, + # otherwise the resulting literal terminates early and the server rejects the query + assert typ.traits.to_literal("O'Brien") == "'O''Brien'" + assert typ.traits.to_literal("l'Etat") == "'l''Etat'" + assert typ.traits.to_literal("''") == "''''''" + assert typ.traits.to_literal('no quotes') == "'no quotes'" + + # EdmStringTypTraits — to_literal/from_literal must round-trip + for value in ["O'Brien", "l'Etat", "a''b", "'wrapped'", 'no quotes', '']: + assert typ.traits.from_literal(typ.traits.to_literal(value)) == value + # bool typ = Types.from_name('Edm.Boolean') assert repr(typ.traits) == 'EdmBooleanTypTraits'