dml.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. # dialects/sqlite/dml.py
  2. # Copyright (C) 2005-2025 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 __future__ import annotations
  8. from typing import Any
  9. from typing import List
  10. from typing import Optional
  11. from typing import Tuple
  12. from typing import Union
  13. from .._typing import _OnConflictIndexElementsT
  14. from .._typing import _OnConflictIndexWhereT
  15. from .._typing import _OnConflictSetT
  16. from .._typing import _OnConflictWhereT
  17. from ... import util
  18. from ...sql import coercions
  19. from ...sql import roles
  20. from ...sql import schema
  21. from ...sql._typing import _DMLTableArgument
  22. from ...sql.base import _exclusive_against
  23. from ...sql.base import _generative
  24. from ...sql.base import ColumnCollection
  25. from ...sql.base import ReadOnlyColumnCollection
  26. from ...sql.dml import Insert as StandardInsert
  27. from ...sql.elements import ClauseElement
  28. from ...sql.elements import ColumnElement
  29. from ...sql.elements import KeyedColumnElement
  30. from ...sql.elements import TextClause
  31. from ...sql.expression import alias
  32. from ...util.typing import Self
  33. __all__ = ("Insert", "insert")
  34. def insert(table: _DMLTableArgument) -> Insert:
  35. """Construct a sqlite-specific variant :class:`_sqlite.Insert`
  36. construct.
  37. .. container:: inherited_member
  38. The :func:`sqlalchemy.dialects.sqlite.insert` function creates
  39. a :class:`sqlalchemy.dialects.sqlite.Insert`. This class is based
  40. on the dialect-agnostic :class:`_sql.Insert` construct which may
  41. be constructed using the :func:`_sql.insert` function in
  42. SQLAlchemy Core.
  43. The :class:`_sqlite.Insert` construct includes additional methods
  44. :meth:`_sqlite.Insert.on_conflict_do_update`,
  45. :meth:`_sqlite.Insert.on_conflict_do_nothing`.
  46. """
  47. return Insert(table)
  48. class Insert(StandardInsert):
  49. """SQLite-specific implementation of INSERT.
  50. Adds methods for SQLite-specific syntaxes such as ON CONFLICT.
  51. The :class:`_sqlite.Insert` object is created using the
  52. :func:`sqlalchemy.dialects.sqlite.insert` function.
  53. .. versionadded:: 1.4
  54. .. seealso::
  55. :ref:`sqlite_on_conflict_insert`
  56. """
  57. stringify_dialect = "sqlite"
  58. inherit_cache = False
  59. @util.memoized_property
  60. def excluded(
  61. self,
  62. ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
  63. """Provide the ``excluded`` namespace for an ON CONFLICT statement
  64. SQLite's ON CONFLICT clause allows reference to the row that would
  65. be inserted, known as ``excluded``. This attribute provides
  66. all columns in this row to be referenceable.
  67. .. tip:: The :attr:`_sqlite.Insert.excluded` attribute is an instance
  68. of :class:`_expression.ColumnCollection`, which provides an
  69. interface the same as that of the :attr:`_schema.Table.c`
  70. collection described at :ref:`metadata_tables_and_columns`.
  71. With this collection, ordinary names are accessible like attributes
  72. (e.g. ``stmt.excluded.some_column``), but special names and
  73. dictionary method names should be accessed using indexed access,
  74. such as ``stmt.excluded["column name"]`` or
  75. ``stmt.excluded["values"]``. See the docstring for
  76. :class:`_expression.ColumnCollection` for further examples.
  77. """
  78. return alias(self.table, name="excluded").columns
  79. _on_conflict_exclusive = _exclusive_against(
  80. "_post_values_clause",
  81. msgs={
  82. "_post_values_clause": "This Insert construct already has "
  83. "an ON CONFLICT clause established"
  84. },
  85. )
  86. @_generative
  87. @_on_conflict_exclusive
  88. def on_conflict_do_update(
  89. self,
  90. index_elements: _OnConflictIndexElementsT = None,
  91. index_where: _OnConflictIndexWhereT = None,
  92. set_: _OnConflictSetT = None,
  93. where: _OnConflictWhereT = None,
  94. ) -> Self:
  95. r"""
  96. Specifies a DO UPDATE SET action for ON CONFLICT clause.
  97. :param index_elements:
  98. A sequence consisting of string column names, :class:`_schema.Column`
  99. objects, or other column expression objects that will be used
  100. to infer a target index or unique constraint.
  101. :param index_where:
  102. Additional WHERE criterion that can be used to infer a
  103. conditional target index.
  104. :param set\_:
  105. A dictionary or other mapping object
  106. where the keys are either names of columns in the target table,
  107. or :class:`_schema.Column` objects or other ORM-mapped columns
  108. matching that of the target table, and expressions or literals
  109. as values, specifying the ``SET`` actions to take.
  110. .. versionadded:: 1.4 The
  111. :paramref:`_sqlite.Insert.on_conflict_do_update.set_`
  112. parameter supports :class:`_schema.Column` objects from the target
  113. :class:`_schema.Table` as keys.
  114. .. warning:: This dictionary does **not** take into account
  115. Python-specified default UPDATE values or generation functions,
  116. e.g. those specified using :paramref:`_schema.Column.onupdate`.
  117. These values will not be exercised for an ON CONFLICT style of
  118. UPDATE, unless they are manually specified in the
  119. :paramref:`.Insert.on_conflict_do_update.set_` dictionary.
  120. :param where:
  121. Optional argument. An expression object representing a ``WHERE``
  122. clause that restricts the rows affected by ``DO UPDATE SET``. Rows not
  123. meeting the ``WHERE`` condition will not be updated (effectively a
  124. ``DO NOTHING`` for those rows).
  125. """
  126. self._post_values_clause = OnConflictDoUpdate(
  127. index_elements, index_where, set_, where
  128. )
  129. return self
  130. @_generative
  131. @_on_conflict_exclusive
  132. def on_conflict_do_nothing(
  133. self,
  134. index_elements: _OnConflictIndexElementsT = None,
  135. index_where: _OnConflictIndexWhereT = None,
  136. ) -> Self:
  137. """
  138. Specifies a DO NOTHING action for ON CONFLICT clause.
  139. :param index_elements:
  140. A sequence consisting of string column names, :class:`_schema.Column`
  141. objects, or other column expression objects that will be used
  142. to infer a target index or unique constraint.
  143. :param index_where:
  144. Additional WHERE criterion that can be used to infer a
  145. conditional target index.
  146. """
  147. self._post_values_clause = OnConflictDoNothing(
  148. index_elements, index_where
  149. )
  150. return self
  151. class OnConflictClause(ClauseElement):
  152. stringify_dialect = "sqlite"
  153. inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]]
  154. inferred_target_whereclause: Optional[
  155. Union[ColumnElement[Any], TextClause]
  156. ]
  157. def __init__(
  158. self,
  159. index_elements: _OnConflictIndexElementsT = None,
  160. index_where: _OnConflictIndexWhereT = None,
  161. ):
  162. if index_elements is not None:
  163. self.inferred_target_elements = [
  164. coercions.expect(roles.DDLConstraintColumnRole, column)
  165. for column in index_elements
  166. ]
  167. self.inferred_target_whereclause = (
  168. coercions.expect(
  169. roles.WhereHavingRole,
  170. index_where,
  171. )
  172. if index_where is not None
  173. else None
  174. )
  175. else:
  176. self.inferred_target_elements = (
  177. self.inferred_target_whereclause
  178. ) = None
  179. class OnConflictDoNothing(OnConflictClause):
  180. __visit_name__ = "on_conflict_do_nothing"
  181. class OnConflictDoUpdate(OnConflictClause):
  182. __visit_name__ = "on_conflict_do_update"
  183. update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]]
  184. update_whereclause: Optional[ColumnElement[Any]]
  185. def __init__(
  186. self,
  187. index_elements: _OnConflictIndexElementsT = None,
  188. index_where: _OnConflictIndexWhereT = None,
  189. set_: _OnConflictSetT = None,
  190. where: _OnConflictWhereT = None,
  191. ):
  192. super().__init__(
  193. index_elements=index_elements,
  194. index_where=index_where,
  195. )
  196. if isinstance(set_, dict):
  197. if not set_:
  198. raise ValueError("set parameter dictionary must not be empty")
  199. elif isinstance(set_, ColumnCollection):
  200. set_ = dict(set_)
  201. else:
  202. raise ValueError(
  203. "set parameter must be a non-empty dictionary "
  204. "or a ColumnCollection such as the `.c.` collection "
  205. "of a Table object"
  206. )
  207. self.update_values_to_set = [
  208. (coercions.expect(roles.DMLColumnRole, key), value)
  209. for key, value in set_.items()
  210. ]
  211. self.update_whereclause = (
  212. coercions.expect(roles.WhereHavingRole, where)
  213. if where is not None
  214. else None
  215. )