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.

200 lines
6.7KB

  1. # Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
  2. # <see AUTHORS file>
  3. #
  4. # This module is part of SQLAlchemy and is released under
  5. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  6. from ... import util
  7. from ...sql import coercions
  8. from ...sql import roles
  9. from ...sql.base import _exclusive_against
  10. from ...sql.base import _generative
  11. from ...sql.base import ColumnCollection
  12. from ...sql.dml import Insert as StandardInsert
  13. from ...sql.elements import ClauseElement
  14. from ...sql.expression import alias
  15. from ...util.langhelpers import public_factory
  16. __all__ = ("Insert", "insert")
  17. class Insert(StandardInsert):
  18. """SQLite-specific implementation of INSERT.
  19. Adds methods for SQLite-specific syntaxes such as ON CONFLICT.
  20. The :class:`_sqlite.Insert` object is created using the
  21. :func:`sqlalchemy.dialects.sqlite.insert` function.
  22. .. versionadded:: 1.4
  23. .. seealso::
  24. :ref:`sqlite_on_conflict_insert`
  25. """
  26. stringify_dialect = "sqlite"
  27. @util.memoized_property
  28. def excluded(self):
  29. """Provide the ``excluded`` namespace for an ON CONFLICT statement
  30. SQLite's ON CONFLICT clause allows reference to the row that would
  31. be inserted, known as ``excluded``. This attribute provides
  32. all columns in this row to be referenceable.
  33. .. tip:: The :attr:`_sqlite.Insert.excluded` attribute is an instance
  34. of :class:`_expression.ColumnCollection`, which provides an
  35. interface the same as that of the :attr:`_schema.Table.c`
  36. collection described at :ref:`metadata_tables_and_columns`.
  37. With this collection, ordinary names are accessible like attributes
  38. (e.g. ``stmt.excluded.some_column``), but special names and
  39. dictionary method names should be accessed using indexed access,
  40. such as ``stmt.excluded["column name"]`` or
  41. ``stmt.excluded["values"]``. See the docstring for
  42. :class:`_expression.ColumnCollection` for further examples.
  43. """
  44. return alias(self.table, name="excluded").columns
  45. _on_conflict_exclusive = _exclusive_against(
  46. "_post_values_clause",
  47. msgs={
  48. "_post_values_clause": "This Insert construct already has "
  49. "an ON CONFLICT clause established"
  50. },
  51. )
  52. @_generative
  53. @_on_conflict_exclusive
  54. def on_conflict_do_update(
  55. self,
  56. index_elements=None,
  57. index_where=None,
  58. set_=None,
  59. where=None,
  60. ):
  61. r"""
  62. Specifies a DO UPDATE SET action for ON CONFLICT clause.
  63. :param index_elements:
  64. A sequence consisting of string column names, :class:`_schema.Column`
  65. objects, or other column expression objects that will be used
  66. to infer a target index or unique constraint.
  67. :param index_where:
  68. Additional WHERE criterion that can be used to infer a
  69. conditional target index.
  70. :param set\_:
  71. A dictionary or other mapping object
  72. where the keys are either names of columns in the target table,
  73. or :class:`_schema.Column` objects or other ORM-mapped columns
  74. matching that of the target table, and expressions or literals
  75. as values, specifying the ``SET`` actions to take.
  76. .. versionadded:: 1.4 The
  77. :paramref:`_sqlite.Insert.on_conflict_do_update.set_`
  78. parameter supports :class:`_schema.Column` objects from the target
  79. :class:`_schema.Table` as keys.
  80. .. warning:: This dictionary does **not** take into account
  81. Python-specified default UPDATE values or generation functions,
  82. e.g. those specified using :paramref:`_schema.Column.onupdate`.
  83. These values will not be exercised for an ON CONFLICT style of
  84. UPDATE, unless they are manually specified in the
  85. :paramref:`.Insert.on_conflict_do_update.set_` dictionary.
  86. :param where:
  87. Optional argument. If present, can be a literal SQL
  88. string or an acceptable expression for a ``WHERE`` clause
  89. that restricts the rows affected by ``DO UPDATE SET``. Rows
  90. not meeting the ``WHERE`` condition will not be updated
  91. (effectively a ``DO NOTHING`` for those rows).
  92. """
  93. self._post_values_clause = OnConflictDoUpdate(
  94. index_elements, index_where, set_, where
  95. )
  96. @_generative
  97. @_on_conflict_exclusive
  98. def on_conflict_do_nothing(self, index_elements=None, index_where=None):
  99. """
  100. Specifies a DO NOTHING action for ON CONFLICT clause.
  101. :param index_elements:
  102. A sequence consisting of string column names, :class:`_schema.Column`
  103. objects, or other column expression objects that will be used
  104. to infer a target index or unique constraint.
  105. :param index_where:
  106. Additional WHERE criterion that can be used to infer a
  107. conditional target index.
  108. """
  109. self._post_values_clause = OnConflictDoNothing(
  110. index_elements, index_where
  111. )
  112. insert = public_factory(
  113. Insert, ".dialects.sqlite.insert", ".dialects.sqlite.Insert"
  114. )
  115. class OnConflictClause(ClauseElement):
  116. stringify_dialect = "sqlite"
  117. def __init__(self, index_elements=None, index_where=None):
  118. if index_elements is not None:
  119. self.constraint_target = None
  120. self.inferred_target_elements = index_elements
  121. self.inferred_target_whereclause = index_where
  122. else:
  123. self.constraint_target = (
  124. self.inferred_target_elements
  125. ) = self.inferred_target_whereclause = None
  126. class OnConflictDoNothing(OnConflictClause):
  127. __visit_name__ = "on_conflict_do_nothing"
  128. class OnConflictDoUpdate(OnConflictClause):
  129. __visit_name__ = "on_conflict_do_update"
  130. def __init__(
  131. self,
  132. index_elements=None,
  133. index_where=None,
  134. set_=None,
  135. where=None,
  136. ):
  137. super(OnConflictDoUpdate, self).__init__(
  138. index_elements=index_elements,
  139. index_where=index_where,
  140. )
  141. if isinstance(set_, dict):
  142. if not set_:
  143. raise ValueError("set parameter dictionary must not be empty")
  144. elif isinstance(set_, ColumnCollection):
  145. set_ = dict(set_)
  146. else:
  147. raise ValueError(
  148. "set parameter must be a non-empty dictionary "
  149. "or a ColumnCollection such as the `.c.` collection "
  150. "of a Table object"
  151. )
  152. self.update_values_to_set = [
  153. (coercions.expect(roles.DMLColumnRole, key), value)
  154. for key, value in set_.items()
  155. ]
  156. self.update_whereclause = where