json.py 11 KB

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