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