Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/dialects/postgresql/json.py : 52%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# postgresql/json.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7from __future__ import absolute_import
9from ... import types as sqltypes
10from ... import util
11from ...sql import operators
14__all__ = ("JSON", "JSONB")
16idx_precedence = operators._PRECEDENCE[operators.json_getitem_op]
18ASTEXT = operators.custom_op(
19 "->>",
20 precedence=idx_precedence,
21 natural_self_precedent=True,
22 eager_grouping=True,
23)
25JSONPATH_ASTEXT = operators.custom_op(
26 "#>>",
27 precedence=idx_precedence,
28 natural_self_precedent=True,
29 eager_grouping=True,
30)
33HAS_KEY = operators.custom_op(
34 "?",
35 precedence=idx_precedence,
36 natural_self_precedent=True,
37 eager_grouping=True,
38)
40HAS_ALL = operators.custom_op(
41 "?&",
42 precedence=idx_precedence,
43 natural_self_precedent=True,
44 eager_grouping=True,
45)
47HAS_ANY = operators.custom_op(
48 "?|",
49 precedence=idx_precedence,
50 natural_self_precedent=True,
51 eager_grouping=True,
52)
54CONTAINS = operators.custom_op(
55 "@>",
56 precedence=idx_precedence,
57 natural_self_precedent=True,
58 eager_grouping=True,
59)
61CONTAINED_BY = operators.custom_op(
62 "<@",
63 precedence=idx_precedence,
64 natural_self_precedent=True,
65 eager_grouping=True,
66)
69class JSONPathType(sqltypes.JSON.JSONPathType):
70 def bind_processor(self, dialect):
71 super_proc = self.string_bind_processor(dialect)
73 def process(value):
74 assert isinstance(value, util.collections_abc.Sequence)
75 tokens = [util.text_type(elem) for elem in value]
76 value = "{%s}" % (", ".join(tokens))
77 if super_proc:
78 value = super_proc(value)
79 return value
81 return process
83 def literal_processor(self, dialect):
84 super_proc = self.string_literal_processor(dialect)
86 def process(value):
87 assert isinstance(value, util.collections_abc.Sequence)
88 tokens = [util.text_type(elem) for elem in value]
89 value = "{%s}" % (", ".join(tokens))
90 if super_proc:
91 value = super_proc(value)
92 return value
94 return process
97class JSON(sqltypes.JSON):
98 """Represent the PostgreSQL JSON type.
100 This type is a specialization of the Core-level :class:`_types.JSON`
101 type. Be sure to read the documentation for :class:`_types.JSON` for
102 important tips regarding treatment of NULL values and ORM use.
104 .. versionchanged:: 1.1 :class:`_postgresql.JSON` is now a PostgreSQL-
105 specific specialization of the new :class:`_types.JSON` type.
107 The operators provided by the PostgreSQL version of :class:`_types.JSON`
108 include:
110 * Index operations (the ``->`` operator)::
112 data_table.c.data['some key']
114 data_table.c.data[5]
117 * Index operations returning text (the ``->>`` operator)::
119 data_table.c.data['some key'].astext == 'some value'
121 Note that equivalent functionality is available via the
122 :attr:`.JSON.Comparator.as_string` accessor.
124 * Index operations with CAST
125 (equivalent to ``CAST(col ->> ['some key'] AS <type>)``)::
127 data_table.c.data['some key'].astext.cast(Integer) == 5
129 Note that equivalent functionality is available via the
130 :attr:`.JSON.Comparator.as_integer` and similar accessors.
132 * Path index operations (the ``#>`` operator)::
134 data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')]
136 * Path index operations returning text (the ``#>>`` operator)::
138 data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')].astext == 'some value'
140 .. versionchanged:: 1.1 The :meth:`_expression.ColumnElement.cast`
141 operator on
142 JSON objects now requires that the :attr:`.JSON.Comparator.astext`
143 modifier be called explicitly, if the cast works only from a textual
144 string.
146 Index operations return an expression object whose type defaults to
147 :class:`_types.JSON` by default,
148 so that further JSON-oriented instructions
149 may be called upon the result type.
151 Custom serializers and deserializers are specified at the dialect level,
152 that is using :func:`_sa.create_engine`. The reason for this is that when
153 using psycopg2, the DBAPI only allows serializers at the per-cursor
154 or per-connection level. E.g.::
156 engine = create_engine("postgresql://scott:tiger@localhost/test",
157 json_serializer=my_serialize_fn,
158 json_deserializer=my_deserialize_fn
159 )
161 When using the psycopg2 dialect, the json_deserializer is registered
162 against the database using ``psycopg2.extras.register_default_json``.
164 .. seealso::
166 :class:`_types.JSON` - Core level JSON type
168 :class:`_postgresql.JSONB`
170 """ # noqa
172 astext_type = sqltypes.Text()
174 def __init__(self, none_as_null=False, astext_type=None):
175 """Construct a :class:`_types.JSON` type.
177 :param none_as_null: if True, persist the value ``None`` as a
178 SQL NULL value, not the JSON encoding of ``null``. Note that
179 when this flag is False, the :func:`.null` construct can still
180 be used to persist a NULL value::
182 from sqlalchemy import null
183 conn.execute(table.insert(), data=null())
185 .. versionchanged:: 0.9.8 - Added ``none_as_null``, and :func:`.null`
186 is now supported in order to persist a NULL value.
188 .. seealso::
190 :attr:`_types.JSON.NULL`
192 :param astext_type: the type to use for the
193 :attr:`.JSON.Comparator.astext`
194 accessor on indexed attributes. Defaults to :class:`_types.Text`.
196 .. versionadded:: 1.1
198 """
199 super(JSON, self).__init__(none_as_null=none_as_null)
200 if astext_type is not None:
201 self.astext_type = astext_type
203 class Comparator(sqltypes.JSON.Comparator):
204 """Define comparison operations for :class:`_types.JSON`."""
206 @property
207 def astext(self):
208 """On an indexed expression, use the "astext" (e.g. "->>")
209 conversion when rendered in SQL.
211 E.g.::
213 select([data_table.c.data['some key'].astext])
215 .. seealso::
217 :meth:`_expression.ColumnElement.cast`
219 """
220 if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType):
221 return self.expr.left.operate(
222 JSONPATH_ASTEXT,
223 self.expr.right,
224 result_type=self.type.astext_type,
225 )
226 else:
227 return self.expr.left.operate(
228 ASTEXT, self.expr.right, result_type=self.type.astext_type
229 )
231 comparator_factory = Comparator
234class JSONB(JSON):
235 """Represent the PostgreSQL JSONB type.
237 The :class:`_postgresql.JSONB` type stores arbitrary JSONB format data, e.
238 g.::
240 data_table = Table('data_table', metadata,
241 Column('id', Integer, primary_key=True),
242 Column('data', JSONB)
243 )
245 with engine.connect() as conn:
246 conn.execute(
247 data_table.insert(),
248 data = {"key1": "value1", "key2": "value2"}
249 )
251 The :class:`_postgresql.JSONB` type includes all operations provided by
252 :class:`_types.JSON`, including the same behaviors for indexing operations
253 .
254 It also adds additional operators specific to JSONB, including
255 :meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`,
256 :meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`,
257 and :meth:`.JSONB.Comparator.contained_by`.
259 Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB`
260 type does not detect
261 in-place changes when used with the ORM, unless the
262 :mod:`sqlalchemy.ext.mutable` extension is used.
264 Custom serializers and deserializers
265 are shared with the :class:`_types.JSON` class,
266 using the ``json_serializer``
267 and ``json_deserializer`` keyword arguments. These must be specified
268 at the dialect level using :func:`_sa.create_engine`. When using
269 psycopg2, the serializers are associated with the jsonb type using
270 ``psycopg2.extras.register_default_jsonb`` on a per-connection basis,
271 in the same way that ``psycopg2.extras.register_default_json`` is used
272 to register these handlers with the json type.
274 .. versionadded:: 0.9.7
276 .. seealso::
278 :class:`_types.JSON`
280 """
282 __visit_name__ = "JSONB"
284 class Comparator(JSON.Comparator):
285 """Define comparison operations for :class:`_types.JSON`."""
287 def has_key(self, other):
288 """Boolean expression. Test for presence of a key. Note that the
289 key may be a SQLA expression.
290 """
291 return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean)
293 def has_all(self, other):
294 """Boolean expression. Test for presence of all keys in jsonb
295 """
296 return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean)
298 def has_any(self, other):
299 """Boolean expression. Test for presence of any key in jsonb
300 """
301 return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean)
303 def contains(self, other, **kwargs):
304 """Boolean expression. Test if keys (or array) are a superset
305 of/contained the keys of the argument jsonb expression.
306 """
307 return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
309 def contained_by(self, other):
310 """Boolean expression. Test if keys are a proper subset of the
311 keys of the argument jsonb expression.
312 """
313 return self.operate(
314 CONTAINED_BY, other, result_type=sqltypes.Boolean
315 )
317 comparator_factory = Comparator