You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
10KB

  1. # postgresql/json.py
  2. # Copyright (C) 2005-2021 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
  7. from __future__ import absolute_import
  8. from ... import types as sqltypes
  9. from ... import util
  10. from ...sql import operators
  11. __all__ = ("JSON", "JSONB")
  12. idx_precedence = operators._PRECEDENCE[operators.json_getitem_op]
  13. ASTEXT = operators.custom_op(
  14. "->>",
  15. precedence=idx_precedence,
  16. natural_self_precedent=True,
  17. eager_grouping=True,
  18. )
  19. JSONPATH_ASTEXT = operators.custom_op(
  20. "#>>",
  21. precedence=idx_precedence,
  22. natural_self_precedent=True,
  23. eager_grouping=True,
  24. )
  25. HAS_KEY = operators.custom_op(
  26. "?",
  27. precedence=idx_precedence,
  28. natural_self_precedent=True,
  29. eager_grouping=True,
  30. )
  31. HAS_ALL = operators.custom_op(
  32. "?&",
  33. precedence=idx_precedence,
  34. natural_self_precedent=True,
  35. eager_grouping=True,
  36. )
  37. HAS_ANY = operators.custom_op(
  38. "?|",
  39. precedence=idx_precedence,
  40. natural_self_precedent=True,
  41. eager_grouping=True,
  42. )
  43. CONTAINS = operators.custom_op(
  44. "@>",
  45. precedence=idx_precedence,
  46. natural_self_precedent=True,
  47. eager_grouping=True,
  48. )
  49. CONTAINED_BY = operators.custom_op(
  50. "<@",
  51. precedence=idx_precedence,
  52. natural_self_precedent=True,
  53. eager_grouping=True,
  54. )
  55. class JSONPathType(sqltypes.JSON.JSONPathType):
  56. def bind_processor(self, dialect):
  57. super_proc = self.string_bind_processor(dialect)
  58. def process(value):
  59. assert isinstance(value, util.collections_abc.Sequence)
  60. tokens = [util.text_type(elem) for elem in value]
  61. value = "{%s}" % (", ".join(tokens))
  62. if super_proc:
  63. value = super_proc(value)
  64. return value
  65. return process
  66. def literal_processor(self, dialect):
  67. super_proc = self.string_literal_processor(dialect)
  68. def process(value):
  69. assert isinstance(value, util.collections_abc.Sequence)
  70. tokens = [util.text_type(elem) for elem in value]
  71. value = "{%s}" % (", ".join(tokens))
  72. if super_proc:
  73. value = super_proc(value)
  74. return value
  75. return process
  76. class JSON(sqltypes.JSON):
  77. """Represent the PostgreSQL JSON type.
  78. :class:`_postgresql.JSON` is used automatically whenever the base
  79. :class:`_types.JSON` datatype is used against a PostgreSQL backend,
  80. however base :class:`_types.JSON` datatype does not provide Python
  81. accessors for PostgreSQL-specific comparison methods such as
  82. :meth:`_postgresql.JSON.Comparator.astext`; additionally, to use
  83. PostgreSQL ``JSONB``, the :class:`_postgresql.JSONB` datatype should
  84. be used explicitly.
  85. .. seealso::
  86. :class:`_types.JSON` - main documentation for the generic
  87. cross-platform JSON datatype.
  88. The operators provided by the PostgreSQL version of :class:`_types.JSON`
  89. include:
  90. * Index operations (the ``->`` operator)::
  91. data_table.c.data['some key']
  92. data_table.c.data[5]
  93. * Index operations returning text (the ``->>`` operator)::
  94. data_table.c.data['some key'].astext == 'some value'
  95. Note that equivalent functionality is available via the
  96. :attr:`.JSON.Comparator.as_string` accessor.
  97. * Index operations with CAST
  98. (equivalent to ``CAST(col ->> ['some key'] AS <type>)``)::
  99. data_table.c.data['some key'].astext.cast(Integer) == 5
  100. Note that equivalent functionality is available via the
  101. :attr:`.JSON.Comparator.as_integer` and similar accessors.
  102. * Path index operations (the ``#>`` operator)::
  103. data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')]
  104. * Path index operations returning text (the ``#>>`` operator)::
  105. data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')].astext == 'some value'
  106. .. versionchanged:: 1.1 The :meth:`_expression.ColumnElement.cast`
  107. operator on
  108. JSON objects now requires that the :attr:`.JSON.Comparator.astext`
  109. modifier be called explicitly, if the cast works only from a textual
  110. string.
  111. Index operations return an expression object whose type defaults to
  112. :class:`_types.JSON` by default,
  113. so that further JSON-oriented instructions
  114. may be called upon the result type.
  115. Custom serializers and deserializers are specified at the dialect level,
  116. that is using :func:`_sa.create_engine`. The reason for this is that when
  117. using psycopg2, the DBAPI only allows serializers at the per-cursor
  118. or per-connection level. E.g.::
  119. engine = create_engine("postgresql://scott:tiger@localhost/test",
  120. json_serializer=my_serialize_fn,
  121. json_deserializer=my_deserialize_fn
  122. )
  123. When using the psycopg2 dialect, the json_deserializer is registered
  124. against the database using ``psycopg2.extras.register_default_json``.
  125. .. seealso::
  126. :class:`_types.JSON` - Core level JSON type
  127. :class:`_postgresql.JSONB`
  128. .. versionchanged:: 1.1 :class:`_postgresql.JSON` is now a PostgreSQL-
  129. specific specialization of the new :class:`_types.JSON` type.
  130. """ # noqa
  131. astext_type = sqltypes.Text()
  132. def __init__(self, none_as_null=False, astext_type=None):
  133. """Construct a :class:`_types.JSON` type.
  134. :param none_as_null: if True, persist the value ``None`` as a
  135. SQL NULL value, not the JSON encoding of ``null``. Note that
  136. when this flag is False, the :func:`.null` construct can still
  137. be used to persist a NULL value::
  138. from sqlalchemy import null
  139. conn.execute(table.insert(), data=null())
  140. .. versionchanged:: 0.9.8 - Added ``none_as_null``, and :func:`.null`
  141. is now supported in order to persist a NULL value.
  142. .. seealso::
  143. :attr:`_types.JSON.NULL`
  144. :param astext_type: the type to use for the
  145. :attr:`.JSON.Comparator.astext`
  146. accessor on indexed attributes. Defaults to :class:`_types.Text`.
  147. .. versionadded:: 1.1
  148. """
  149. super(JSON, self).__init__(none_as_null=none_as_null)
  150. if astext_type is not None:
  151. self.astext_type = astext_type
  152. class Comparator(sqltypes.JSON.Comparator):
  153. """Define comparison operations for :class:`_types.JSON`."""
  154. @property
  155. def astext(self):
  156. """On an indexed expression, use the "astext" (e.g. "->>")
  157. conversion when rendered in SQL.
  158. E.g.::
  159. select(data_table.c.data['some key'].astext)
  160. .. seealso::
  161. :meth:`_expression.ColumnElement.cast`
  162. """
  163. if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType):
  164. return self.expr.left.operate(
  165. JSONPATH_ASTEXT,
  166. self.expr.right,
  167. result_type=self.type.astext_type,
  168. )
  169. else:
  170. return self.expr.left.operate(
  171. ASTEXT, self.expr.right, result_type=self.type.astext_type
  172. )
  173. comparator_factory = Comparator
  174. class JSONB(JSON):
  175. """Represent the PostgreSQL JSONB type.
  176. The :class:`_postgresql.JSONB` type stores arbitrary JSONB format data,
  177. e.g.::
  178. data_table = Table('data_table', metadata,
  179. Column('id', Integer, primary_key=True),
  180. Column('data', JSONB)
  181. )
  182. with engine.connect() as conn:
  183. conn.execute(
  184. data_table.insert(),
  185. data = {"key1": "value1", "key2": "value2"}
  186. )
  187. The :class:`_postgresql.JSONB` type includes all operations provided by
  188. :class:`_types.JSON`, including the same behaviors for indexing
  189. operations.
  190. It also adds additional operators specific to JSONB, including
  191. :meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`,
  192. :meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`,
  193. and :meth:`.JSONB.Comparator.contained_by`.
  194. Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB`
  195. type does not detect
  196. in-place changes when used with the ORM, unless the
  197. :mod:`sqlalchemy.ext.mutable` extension is used.
  198. Custom serializers and deserializers
  199. are shared with the :class:`_types.JSON` class,
  200. using the ``json_serializer``
  201. and ``json_deserializer`` keyword arguments. These must be specified
  202. at the dialect level using :func:`_sa.create_engine`. When using
  203. psycopg2, the serializers are associated with the jsonb type using
  204. ``psycopg2.extras.register_default_jsonb`` on a per-connection basis,
  205. in the same way that ``psycopg2.extras.register_default_json`` is used
  206. to register these handlers with the json type.
  207. .. versionadded:: 0.9.7
  208. .. seealso::
  209. :class:`_types.JSON`
  210. """
  211. __visit_name__ = "JSONB"
  212. class Comparator(JSON.Comparator):
  213. """Define comparison operations for :class:`_types.JSON`."""
  214. def has_key(self, other):
  215. """Boolean expression. Test for presence of a key. Note that the
  216. key may be a SQLA expression.
  217. """
  218. return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean)
  219. def has_all(self, other):
  220. """Boolean expression. Test for presence of all keys in jsonb"""
  221. return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean)
  222. def has_any(self, other):
  223. """Boolean expression. Test for presence of any key in jsonb"""
  224. return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean)
  225. def contains(self, other, **kwargs):
  226. """Boolean expression. Test if keys (or array) are a superset
  227. of/contained the keys of the argument jsonb expression.
  228. """
  229. return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
  230. def contained_by(self, other):
  231. """Boolean expression. Test if keys are a proper subset of the
  232. keys of the argument jsonb expression.
  233. """
  234. return self.operate(
  235. CONTAINED_BY, other, result_type=sqltypes.Boolean
  236. )
  237. comparator_factory = Comparator