json.py 9.9 KB

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