dml.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. # dialects/mysql/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 exc
  8. from ... import util
  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. """MySQL-specific implementation of INSERT.
  19. Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE.
  20. The :class:`~.mysql.Insert` object is created using the
  21. :func:`sqlalchemy.dialects.mysql.insert` function.
  22. .. versionadded:: 1.2
  23. """
  24. stringify_dialect = "mysql"
  25. inherit_cache = False
  26. @property
  27. def inserted(self):
  28. """Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE
  29. statement
  30. MySQL's ON DUPLICATE KEY UPDATE clause allows reference to the row
  31. that would be inserted, via a special function called ``VALUES()``.
  32. This attribute provides all columns in this row to be referenceable
  33. such that they will render within a ``VALUES()`` function inside the
  34. ON DUPLICATE KEY UPDATE clause. The attribute is named ``.inserted``
  35. so as not to conflict with the existing
  36. :meth:`_expression.Insert.values` method.
  37. .. tip:: The :attr:`_mysql.Insert.inserted` attribute is an instance
  38. of :class:`_expression.ColumnCollection`, which provides an
  39. interface the same as that of the :attr:`_schema.Table.c`
  40. collection described at :ref:`metadata_tables_and_columns`.
  41. With this collection, ordinary names are accessible like attributes
  42. (e.g. ``stmt.inserted.some_column``), but special names and
  43. dictionary method names should be accessed using indexed access,
  44. such as ``stmt.inserted["column name"]`` or
  45. ``stmt.inserted["values"]``. See the docstring for
  46. :class:`_expression.ColumnCollection` for further examples.
  47. .. seealso::
  48. :ref:`mysql_insert_on_duplicate_key_update` - example of how
  49. to use :attr:`_expression.Insert.inserted`
  50. """
  51. return self.inserted_alias.columns
  52. @util.memoized_property
  53. def inserted_alias(self):
  54. return alias(self.table, name="inserted")
  55. @_generative
  56. @_exclusive_against(
  57. "_post_values_clause",
  58. msgs={
  59. "_post_values_clause": "This Insert construct already "
  60. "has an ON DUPLICATE KEY clause present"
  61. },
  62. )
  63. def on_duplicate_key_update(self, *args, **kw):
  64. r"""
  65. Specifies the ON DUPLICATE KEY UPDATE clause.
  66. :param \**kw: Column keys linked to UPDATE values. The
  67. values may be any SQL expression or supported literal Python
  68. values.
  69. .. warning:: This dictionary does **not** take into account
  70. Python-specified default UPDATE values or generation functions,
  71. e.g. those specified using :paramref:`_schema.Column.onupdate`.
  72. These values will not be exercised for an ON DUPLICATE KEY UPDATE
  73. style of UPDATE, unless values are manually specified here.
  74. :param \*args: As an alternative to passing key/value parameters,
  75. a dictionary or list of 2-tuples can be passed as a single positional
  76. argument.
  77. Passing a single dictionary is equivalent to the keyword argument
  78. form::
  79. insert().on_duplicate_key_update({"name": "some name"})
  80. Passing a list of 2-tuples indicates that the parameter assignments
  81. in the UPDATE clause should be ordered as sent, in a manner similar
  82. to that described for the :class:`_expression.Update`
  83. construct overall
  84. in :ref:`tutorial_parameter_ordered_updates`::
  85. insert().on_duplicate_key_update(
  86. [("name", "some name"), ("value", "some value")])
  87. .. versionchanged:: 1.3 parameters can be specified as a dictionary
  88. or list of 2-tuples; the latter form provides for parameter
  89. ordering.
  90. .. versionadded:: 1.2
  91. .. seealso::
  92. :ref:`mysql_insert_on_duplicate_key_update`
  93. """
  94. if args and kw:
  95. raise exc.ArgumentError(
  96. "Can't pass kwargs and positional arguments simultaneously"
  97. )
  98. if args:
  99. if len(args) > 1:
  100. raise exc.ArgumentError(
  101. "Only a single dictionary or list of tuples "
  102. "is accepted positionally."
  103. )
  104. values = args[0]
  105. else:
  106. values = kw
  107. inserted_alias = getattr(self, "inserted_alias", None)
  108. self._post_values_clause = OnDuplicateClause(inserted_alias, values)
  109. insert = public_factory(
  110. Insert, ".dialects.mysql.insert", ".dialects.mysql.Insert"
  111. )
  112. class OnDuplicateClause(ClauseElement):
  113. __visit_name__ = "on_duplicate_key_update"
  114. _parameter_ordering = None
  115. stringify_dialect = "mysql"
  116. def __init__(self, inserted_alias, update):
  117. self.inserted_alias = inserted_alias
  118. # auto-detect that parameters should be ordered. This is copied from
  119. # Update._proces_colparams(), however we don't look for a special flag
  120. # in this case since we are not disambiguating from other use cases as
  121. # we are in Update.values().
  122. if isinstance(update, list) and (
  123. update and isinstance(update[0], tuple)
  124. ):
  125. self._parameter_ordering = [key for key, value in update]
  126. update = dict(update)
  127. if isinstance(update, dict):
  128. if not update:
  129. raise ValueError(
  130. "update parameter dictionary must not be empty"
  131. )
  132. elif isinstance(update, ColumnCollection):
  133. update = dict(update)
  134. else:
  135. raise ValueError(
  136. "update parameter must be a non-empty dictionary "
  137. "or a ColumnCollection such as the `.c.` collection "
  138. "of a Table object"
  139. )
  140. self.update = update