dml.py 6.7 KB

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