dml.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. # postgresql/on_conflict.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 ...sql.elements import ClauseElement, _literal_as_binds
  8. from ...sql.dml import Insert as StandardInsert
  9. from ...sql.expression import alias
  10. from ...sql import schema
  11. from ...util.langhelpers import public_factory
  12. from ...sql.base import _generative
  13. from ... import util
  14. from . import ext
  15. __all__ = ('Insert', 'insert')
  16. class Insert(StandardInsert):
  17. """PostgreSQL-specific implementation of INSERT.
  18. Adds methods for PG-specific syntaxes such as ON CONFLICT.
  19. .. versionadded:: 1.1
  20. """
  21. @util.memoized_property
  22. def excluded(self):
  23. """Provide the ``excluded`` namespace for an ON CONFLICT statement
  24. PG's ON CONFLICT clause allows reference to the row that would
  25. be inserted, known as ``excluded``. This attribute provides
  26. all columns in this row to be referenaceable.
  27. .. seealso::
  28. :ref:`postgresql_insert_on_conflict` - example of how
  29. to use :attr:`.Insert.excluded`
  30. """
  31. return alias(self.table, name='excluded').columns
  32. @_generative
  33. def on_conflict_do_update(
  34. self,
  35. constraint=None, index_elements=None,
  36. index_where=None, set_=None, where=None):
  37. """
  38. Specifies a DO UPDATE SET action for ON CONFLICT clause.
  39. Either the ``constraint`` or ``index_elements`` argument is
  40. required, but only one of these can be specified.
  41. :param constraint:
  42. The name of a unique or exclusion constraint on the table,
  43. or the constraint object itself if it has a .name attribute.
  44. :param index_elements:
  45. A sequence consisting of string column names, :class:`.Column`
  46. objects, or other column expression objects that will be used
  47. to infer a target index.
  48. :param index_where:
  49. Additional WHERE criterion that can be used to infer a
  50. conditional target index.
  51. :param set_:
  52. Required argument. A dictionary or other mapping object
  53. with column names as keys and expressions or literals as values,
  54. specifying the ``SET`` actions to take.
  55. If the target :class:`.Column` specifies a ".key" attribute distinct
  56. from the column name, that key should be used.
  57. .. warning:: This dictionary does **not** take into account
  58. Python-specified default UPDATE values or generation functions,
  59. e.g. those specified using :paramref:`.Column.onupdate`.
  60. These values will not be exercised for an ON CONFLICT style of
  61. UPDATE, unless they are manually specified in the
  62. :paramref:`.Insert.on_conflict_do_update.set_` dictionary.
  63. :param where:
  64. Optional argument. If present, can be a literal SQL
  65. string or an acceptable expression for a ``WHERE`` clause
  66. that restricts the rows affected by ``DO UPDATE SET``. Rows
  67. not meeting the ``WHERE`` condition will not be updated
  68. (effectively a ``DO NOTHING`` for those rows).
  69. .. versionadded:: 1.1
  70. .. seealso::
  71. :ref:`postgresql_insert_on_conflict`
  72. """
  73. self._post_values_clause = OnConflictDoUpdate(
  74. constraint, index_elements, index_where, set_, where)
  75. return self
  76. @_generative
  77. def on_conflict_do_nothing(
  78. self,
  79. constraint=None, index_elements=None, index_where=None):
  80. """
  81. Specifies a DO NOTHING action for ON CONFLICT clause.
  82. The ``constraint`` and ``index_elements`` arguments
  83. are optional, but only one of these can be specified.
  84. :param constraint:
  85. The name of a unique or exclusion constraint on the table,
  86. or the constraint object itself if it has a .name attribute.
  87. :param index_elements:
  88. A sequence consisting of string column names, :class:`.Column`
  89. objects, or other column expression objects that will be used
  90. to infer a target index.
  91. :param index_where:
  92. Additional WHERE criterion that can be used to infer a
  93. conditional target index.
  94. .. versionadded:: 1.1
  95. .. seealso::
  96. :ref:`postgresql_insert_on_conflict`
  97. """
  98. self._post_values_clause = OnConflictDoNothing(
  99. constraint, index_elements, index_where)
  100. return self
  101. insert = public_factory(Insert, '.dialects.postgresql.insert')
  102. class OnConflictClause(ClauseElement):
  103. def __init__(
  104. self,
  105. constraint=None,
  106. index_elements=None,
  107. index_where=None):
  108. if constraint is not None:
  109. if not isinstance(constraint, util.string_types) and \
  110. isinstance(constraint, (
  111. schema.Index, schema.Constraint,
  112. ext.ExcludeConstraint)):
  113. constraint = getattr(constraint, 'name') or constraint
  114. if constraint is not None:
  115. if index_elements is not None:
  116. raise ValueError(
  117. "'constraint' and 'index_elements' are mutually exclusive")
  118. if isinstance(constraint, util.string_types):
  119. self.constraint_target = constraint
  120. self.inferred_target_elements = None
  121. self.inferred_target_whereclause = None
  122. elif isinstance(constraint, schema.Index):
  123. index_elements = constraint.expressions
  124. index_where = \
  125. constraint.dialect_options['postgresql'].get("where")
  126. elif isinstance(constraint, ext.ExcludeConstraint):
  127. index_elements = constraint.columns
  128. index_where = constraint.where
  129. else:
  130. index_elements = constraint.columns
  131. index_where = \
  132. constraint.dialect_options['postgresql'].get("where")
  133. if index_elements is not None:
  134. self.constraint_target = None
  135. self.inferred_target_elements = index_elements
  136. self.inferred_target_whereclause = index_where
  137. elif constraint is None:
  138. self.constraint_target = self.inferred_target_elements = \
  139. self.inferred_target_whereclause = None
  140. class OnConflictDoNothing(OnConflictClause):
  141. __visit_name__ = 'on_conflict_do_nothing'
  142. class OnConflictDoUpdate(OnConflictClause):
  143. __visit_name__ = 'on_conflict_do_update'
  144. def __init__(
  145. self,
  146. constraint=None,
  147. index_elements=None,
  148. index_where=None,
  149. set_=None,
  150. where=None):
  151. super(OnConflictDoUpdate, self).__init__(
  152. constraint=constraint,
  153. index_elements=index_elements,
  154. index_where=index_where)
  155. if self.inferred_target_elements is None and \
  156. self.constraint_target is None:
  157. raise ValueError(
  158. "Either constraint or index_elements, "
  159. "but not both, must be specified unless DO NOTHING")
  160. if (not isinstance(set_, dict) or not set_):
  161. raise ValueError("set parameter must be a non-empty dictionary")
  162. self.update_values_to_set = [
  163. (key, value)
  164. for key, value in set_.items()
  165. ]
  166. self.update_whereclause = where