summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pgjson.py16
-rw-r--r--test/dialect/postgresql/test_types.py40
2 files changed, 51 insertions, 5 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/pgjson.py b/lib/sqlalchemy/dialects/postgresql/pgjson.py
index 161fe83fa..b41446dae 100644
--- a/lib/sqlalchemy/dialects/postgresql/pgjson.py
+++ b/lib/sqlalchemy/dialects/postgresql/pgjson.py
@@ -39,7 +39,7 @@ class JSON(sqltypes.TypeEngine):
* Path Index operations::
- data_table.c.data.get_path('{key_1, key_2, ..., key_n}']
+ data_table.c.data.get_path("'{key_1, key_2, ..., key_n}'"]
Please be aware that when used with the SQLAlchemy ORM, you will need to
replace the JSON object present on an attribute with a new object in order
@@ -71,11 +71,23 @@ class JSON(sqltypes.TypeEngine):
# The only downside to this is that you cannot dereference more
# than one level deep in json structures, though comparator
# support for multi-level dereference is lacking anyhow.
+ return self.expr.op('->', precedence=5)(other)
+
+ def get_item_as_text(self, other):
+ """Text expression. Get the value at the given key as text. Use
+ this when you need to cast the type of the returned value."""
return self.expr.op('->>', precedence=5)(other)
def get_path(self, other):
- """Text expression. Get the value at a given path. Paths are of
+ """Text expression. Get the value at a given path. Paths are of
the form {key_1, key_2, ..., key_n}."""
+ return self.expr.op('#>', precedence=5)(other)
+
+ def get_path_as_text(self, other):
+ """Text expression. Get the value at a given path, as text.
+ Paths are of the form '{key_1, key_2, ..., key_n}' (quotes are
+ required). Use this when you need to cast the type of the
+ returned value."""
return self.expr.op('#>>', precedence=5)(other)
def _adapt_expression(self, op, other_comparator):
diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py
index 114b36f8f..c7a973e4e 100644
--- a/test/dialect/postgresql/test_types.py
+++ b/test/dialect/postgresql/test_types.py
@@ -1713,19 +1713,31 @@ class JSONTest(fixtures.TestBase):
def test_where_getitem(self):
self._test_where(
self.jsoncol['bar'] == None,
- "(test_table.test_column ->> %(test_column_1)s) IS NULL"
+ "(test_table.test_column -> %(test_column_1)s) IS NULL"
)
def test_where_path(self):
self._test_where(
self.jsoncol.get_path('{"foo", 1}') == None,
+ "(test_table.test_column #> %(test_column_1)s) IS NULL"
+ )
+
+ def test_where_getitem_as_text(self):
+ self._test_where(
+ self.jsoncol.get_item_as_text('bar') == None,
+ "(test_table.test_column ->> %(test_column_1)s) IS NULL"
+ )
+
+ def test_where_path_as_text(self):
+ self._test_where(
+ self.jsoncol.get_path_as_text('{"foo", 1}') == None,
"(test_table.test_column #>> %(test_column_1)s) IS NULL"
)
def test_cols_get(self):
self._test_cols(
self.jsoncol['foo'],
- "test_table.test_column ->> %(test_column_1)s AS anon_1",
+ "test_table.test_column -> %(test_column_1)s AS anon_1",
True
)
@@ -1800,9 +1812,31 @@ class JSONRoundTripTest(fixtures.TablesTest):
self._fixture_data(engine)
self._test_criterion(engine)
+ def test_path_query(self):
+ engine = testing.db
+ self._fixture_data(engine)
+ data_table = self.tables.data_table
+ result = engine.execute(
+ select([data_table.c.data]).where(
+ data_table.c.data.get_path_as_text('{k1}') == 'r3v1'
+ )
+ ).first()
+ eq_(result, ({'k1': 'r3v1', 'k2': 'r3v2'},))
+
+ def test_query_returned_as_text(self):
+ engine = testing.db
+ self._fixture_data(engine)
+ data_table = self.tables.data_table
+ result = engine.execute(
+ select([data_table.c.data.get_item_as_text('k1')])
+ ).first()
+ assert isinstance(result[0], basestring)
+
def _test_criterion(self, engine):
data_table = self.tables.data_table
result = engine.execute(
- select([data_table.c.data]).where(data_table.c.data['k1'] == 'r3v1')
+ select([data_table.c.data]).where(
+ data_table.c.data.get_item_as_text('k1') == 'r3v1'
+ )
).first()
eq_(result, ({'k1': 'r3v1', 'k2': 'r3v2'},)) \ No newline at end of file