array.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. # postgresql/array.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 .base import ischema_names
  8. from ...sql import expression, operators
  9. from ...sql.base import SchemaEventTarget
  10. from ... import types as sqltypes
  11. try:
  12. from uuid import UUID as _python_UUID
  13. except ImportError:
  14. _python_UUID = None
  15. def Any(other, arrexpr, operator=operators.eq):
  16. """A synonym for the :meth:`.ARRAY.Comparator.any` method.
  17. This method is legacy and is here for backwards-compatibility.
  18. .. seealso::
  19. :func:`.expression.any_`
  20. """
  21. return arrexpr.any(other, operator)
  22. def All(other, arrexpr, operator=operators.eq):
  23. """A synonym for the :meth:`.ARRAY.Comparator.all` method.
  24. This method is legacy and is here for backwards-compatibility.
  25. .. seealso::
  26. :func:`.expression.all_`
  27. """
  28. return arrexpr.all(other, operator)
  29. class array(expression.Tuple):
  30. """A PostgreSQL ARRAY literal.
  31. This is used to produce ARRAY literals in SQL expressions, e.g.::
  32. from sqlalchemy.dialects.postgresql import array
  33. from sqlalchemy.dialects import postgresql
  34. from sqlalchemy import select, func
  35. stmt = select([
  36. array([1,2]) + array([3,4,5])
  37. ])
  38. print stmt.compile(dialect=postgresql.dialect())
  39. Produces the SQL::
  40. SELECT ARRAY[%(param_1)s, %(param_2)s] ||
  41. ARRAY[%(param_3)s, %(param_4)s, %(param_5)s]) AS anon_1
  42. An instance of :class:`.array` will always have the datatype
  43. :class:`.ARRAY`. The "inner" type of the array is inferred from
  44. the values present, unless the ``type_`` keyword argument is passed::
  45. array(['foo', 'bar'], type_=CHAR)
  46. .. versionadded:: 0.8 Added the :class:`~.postgresql.array` literal type.
  47. See also:
  48. :class:`.postgresql.ARRAY`
  49. """
  50. __visit_name__ = 'array'
  51. def __init__(self, clauses, **kw):
  52. super(array, self).__init__(*clauses, **kw)
  53. self.type = ARRAY(self.type)
  54. def _bind_param(self, operator, obj, _assume_scalar=False, type_=None):
  55. if _assume_scalar or operator is operators.getitem:
  56. # if getitem->slice were called, Indexable produces
  57. # a Slice object from that
  58. assert isinstance(obj, int)
  59. return expression.BindParameter(
  60. None, obj, _compared_to_operator=operator,
  61. type_=type_,
  62. _compared_to_type=self.type, unique=True)
  63. else:
  64. return array([
  65. self._bind_param(operator, o, _assume_scalar=True, type_=type_)
  66. for o in obj])
  67. def self_group(self, against=None):
  68. if (against in (
  69. operators.any_op, operators.all_op, operators.getitem)):
  70. return expression.Grouping(self)
  71. else:
  72. return self
  73. CONTAINS = operators.custom_op("@>", precedence=5)
  74. CONTAINED_BY = operators.custom_op("<@", precedence=5)
  75. OVERLAP = operators.custom_op("&&", precedence=5)
  76. class ARRAY(SchemaEventTarget, sqltypes.ARRAY):
  77. """PostgreSQL ARRAY type.
  78. .. versionchanged:: 1.1 The :class:`.postgresql.ARRAY` type is now
  79. a subclass of the core :class:`.types.ARRAY` type.
  80. The :class:`.postgresql.ARRAY` type is constructed in the same way
  81. as the core :class:`.types.ARRAY` type; a member type is required, and a
  82. number of dimensions is recommended if the type is to be used for more
  83. than one dimension::
  84. from sqlalchemy.dialects import postgresql
  85. mytable = Table("mytable", metadata,
  86. Column("data", postgresql.ARRAY(Integer, dimensions=2))
  87. )
  88. The :class:`.postgresql.ARRAY` type provides all operations defined on the
  89. core :class:`.types.ARRAY` type, including support for "dimensions", indexed
  90. access, and simple matching such as :meth:`.types.ARRAY.Comparator.any`
  91. and :meth:`.types.ARRAY.Comparator.all`. :class:`.postgresql.ARRAY` class also
  92. provides PostgreSQL-specific methods for containment operations, including
  93. :meth:`.postgresql.ARRAY.Comparator.contains`
  94. :meth:`.postgresql.ARRAY.Comparator.contained_by`,
  95. and :meth:`.postgresql.ARRAY.Comparator.overlap`, e.g.::
  96. mytable.c.data.contains([1, 2])
  97. The :class:`.postgresql.ARRAY` type may not be supported on all
  98. PostgreSQL DBAPIs; it is currently known to work on psycopg2 only.
  99. Additionally, the :class:`.postgresql.ARRAY` type does not work directly in
  100. conjunction with the :class:`.ENUM` type. For a workaround, see the
  101. special type at :ref:`postgresql_array_of_enum`.
  102. .. seealso::
  103. :class:`.types.ARRAY` - base array type
  104. :class:`.postgresql.array` - produces a literal array value.
  105. """
  106. class Comparator(sqltypes.ARRAY.Comparator):
  107. """Define comparison operations for :class:`.ARRAY`.
  108. Note that these operations are in addition to those provided
  109. by the base :class:`.types.ARRAY.Comparator` class, including
  110. :meth:`.types.ARRAY.Comparator.any` and
  111. :meth:`.types.ARRAY.Comparator.all`.
  112. """
  113. def contains(self, other, **kwargs):
  114. """Boolean expression. Test if elements are a superset of the
  115. elements of the argument array expression.
  116. """
  117. return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
  118. def contained_by(self, other):
  119. """Boolean expression. Test if elements are a proper subset of the
  120. elements of the argument array expression.
  121. """
  122. return self.operate(
  123. CONTAINED_BY, other, result_type=sqltypes.Boolean)
  124. def overlap(self, other):
  125. """Boolean expression. Test if array has elements in common with
  126. an argument array expression.
  127. """
  128. return self.operate(OVERLAP, other, result_type=sqltypes.Boolean)
  129. comparator_factory = Comparator
  130. def __init__(self, item_type, as_tuple=False, dimensions=None,
  131. zero_indexes=False):
  132. """Construct an ARRAY.
  133. E.g.::
  134. Column('myarray', ARRAY(Integer))
  135. Arguments are:
  136. :param item_type: The data type of items of this array. Note that
  137. dimensionality is irrelevant here, so multi-dimensional arrays like
  138. ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
  139. ``ARRAY(ARRAY(Integer))`` or such.
  140. :param as_tuple=False: Specify whether return results
  141. should be converted to tuples from lists. DBAPIs such
  142. as psycopg2 return lists by default. When tuples are
  143. returned, the results are hashable.
  144. :param dimensions: if non-None, the ARRAY will assume a fixed
  145. number of dimensions. This will cause the DDL emitted for this
  146. ARRAY to include the exact number of bracket clauses ``[]``,
  147. and will also optimize the performance of the type overall.
  148. Note that PG arrays are always implicitly "non-dimensioned",
  149. meaning they can store any number of dimensions no matter how
  150. they were declared.
  151. :param zero_indexes=False: when True, index values will be converted
  152. between Python zero-based and PostgreSQL one-based indexes, e.g.
  153. a value of one will be added to all index values before passing
  154. to the database.
  155. .. versionadded:: 0.9.5
  156. """
  157. if isinstance(item_type, ARRAY):
  158. raise ValueError("Do not nest ARRAY types; ARRAY(basetype) "
  159. "handles multi-dimensional arrays of basetype")
  160. if isinstance(item_type, type):
  161. item_type = item_type()
  162. self.item_type = item_type
  163. self.as_tuple = as_tuple
  164. self.dimensions = dimensions
  165. self.zero_indexes = zero_indexes
  166. @property
  167. def hashable(self):
  168. return self.as_tuple
  169. @property
  170. def python_type(self):
  171. return list
  172. def compare_values(self, x, y):
  173. return x == y
  174. def _set_parent(self, column):
  175. """Support SchemaEventTarget"""
  176. if isinstance(self.item_type, SchemaEventTarget):
  177. self.item_type._set_parent(column)
  178. def _set_parent_with_dispatch(self, parent):
  179. """Support SchemaEventTarget"""
  180. if isinstance(self.item_type, SchemaEventTarget):
  181. self.item_type._set_parent_with_dispatch(parent)
  182. def _proc_array(self, arr, itemproc, dim, collection):
  183. if dim is None:
  184. arr = list(arr)
  185. if dim == 1 or dim is None and (
  186. # this has to be (list, tuple), or at least
  187. # not hasattr('__iter__'), since Py3K strings
  188. # etc. have __iter__
  189. not arr or not isinstance(arr[0], (list, tuple))):
  190. if itemproc:
  191. return collection(itemproc(x) for x in arr)
  192. else:
  193. return collection(arr)
  194. else:
  195. return collection(
  196. self._proc_array(
  197. x, itemproc,
  198. dim - 1 if dim is not None else None,
  199. collection)
  200. for x in arr
  201. )
  202. def bind_processor(self, dialect):
  203. item_proc = self.item_type.dialect_impl(dialect).\
  204. bind_processor(dialect)
  205. def process(value):
  206. if value is None:
  207. return value
  208. else:
  209. return self._proc_array(
  210. value,
  211. item_proc,
  212. self.dimensions,
  213. list)
  214. return process
  215. def result_processor(self, dialect, coltype):
  216. item_proc = self.item_type.dialect_impl(dialect).\
  217. result_processor(dialect, coltype)
  218. def process(value):
  219. if value is None:
  220. return value
  221. else:
  222. return self._proc_array(
  223. value,
  224. item_proc,
  225. self.dimensions,
  226. tuple if self.as_tuple else list)
  227. return process
  228. ischema_names['_array'] = ARRAY