Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/dml.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

97 statements  

1# dialects/postgresql/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 List 

11from typing import Optional 

12from typing import Tuple 

13from typing import Union 

14 

15from . import ext 

16from .._typing import _OnConflictConstraintT 

17from .._typing import _OnConflictIndexElementsT 

18from .._typing import _OnConflictIndexWhereT 

19from .._typing import _OnConflictSetT 

20from .._typing import _OnConflictWhereT 

21from ... import util 

22from ...sql import coercions 

23from ...sql import roles 

24from ...sql import schema 

25from ...sql._typing import _DMLTableArgument 

26from ...sql.base import _exclusive_against 

27from ...sql.base import _generative 

28from ...sql.base import ColumnCollection 

29from ...sql.base import ReadOnlyColumnCollection 

30from ...sql.dml import Insert as StandardInsert 

31from ...sql.elements import ClauseElement 

32from ...sql.elements import ColumnElement 

33from ...sql.elements import KeyedColumnElement 

34from ...sql.elements import TextClause 

35from ...sql.expression import alias 

36from ...util.typing import Self 

37 

38 

39__all__ = ("Insert", "insert") 

40 

41 

42def insert(table: _DMLTableArgument) -> Insert: 

43 """Construct a PostgreSQL-specific variant :class:`_postgresql.Insert` 

44 construct. 

45 

46 .. container:: inherited_member 

47 

48 The :func:`sqlalchemy.dialects.postgresql.insert` function creates 

49 a :class:`sqlalchemy.dialects.postgresql.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:`_postgresql.Insert` construct includes additional methods 

55 :meth:`_postgresql.Insert.on_conflict_do_update`, 

56 :meth:`_postgresql.Insert.on_conflict_do_nothing`. 

57 

58 """ 

59 return Insert(table) 

60 

61 

62class Insert(StandardInsert): 

63 """PostgreSQL-specific implementation of INSERT. 

64 

65 Adds methods for PG-specific syntaxes such as ON CONFLICT. 

66 

67 The :class:`_postgresql.Insert` object is created using the 

68 :func:`sqlalchemy.dialects.postgresql.insert` function. 

69 

70 """ 

71 

72 stringify_dialect = "postgresql" 

73 inherit_cache = False 

74 

75 @util.memoized_property 

76 def excluded( 

77 self, 

78 ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: 

79 """Provide the ``excluded`` namespace for an ON CONFLICT statement 

80 

81 PG's ON CONFLICT clause allows reference to the row that would 

82 be inserted, known as ``excluded``. This attribute provides 

83 all columns in this row to be referenceable. 

84 

85 .. tip:: The :attr:`_postgresql.Insert.excluded` attribute is an 

86 instance of :class:`_expression.ColumnCollection`, which provides 

87 an interface the same as that of the :attr:`_schema.Table.c` 

88 collection described at :ref:`metadata_tables_and_columns`. 

89 With this collection, ordinary names are accessible like attributes 

90 (e.g. ``stmt.excluded.some_column``), but special names and 

91 dictionary method names should be accessed using indexed access, 

92 such as ``stmt.excluded["column name"]`` or 

93 ``stmt.excluded["values"]``. See the docstring for 

94 :class:`_expression.ColumnCollection` for further examples. 

95 

96 .. seealso:: 

97 

98 :ref:`postgresql_insert_on_conflict` - example of how 

99 to use :attr:`_expression.Insert.excluded` 

100 

101 """ 

102 return alias(self.table, name="excluded").columns 

103 

104 _on_conflict_exclusive = _exclusive_against( 

105 "_post_values_clause", 

106 msgs={ 

107 "_post_values_clause": "This Insert construct already has " 

108 "an ON CONFLICT clause established" 

109 }, 

110 ) 

111 

112 @_generative 

113 @_on_conflict_exclusive 

114 def on_conflict_do_update( 

115 self, 

116 constraint: _OnConflictConstraintT = None, 

117 index_elements: _OnConflictIndexElementsT = None, 

118 index_where: _OnConflictIndexWhereT = None, 

119 set_: _OnConflictSetT = None, 

120 where: _OnConflictWhereT = None, 

121 ) -> Self: 

122 r""" 

123 Specifies a DO UPDATE SET action for ON CONFLICT clause. 

124 

125 Either the ``constraint`` or ``index_elements`` argument is 

126 required, but only one of these can be specified. 

127 

128 :param constraint: 

129 The name of a unique or exclusion constraint on the table, 

130 or the constraint object itself if it has a .name attribute. 

131 

132 :param index_elements: 

133 A sequence consisting of string column names, :class:`_schema.Column` 

134 objects, or other column expression objects that will be used 

135 to infer a target index. 

136 

137 :param index_where: 

138 Additional WHERE criterion that can be used to infer a 

139 conditional target index. 

140 

141 :param set\_: 

142 A dictionary or other mapping object 

143 where the keys are either names of columns in the target table, 

144 or :class:`_schema.Column` objects or other ORM-mapped columns 

145 matching that of the target table, and expressions or literals 

146 as values, specifying the ``SET`` actions to take. 

147 

148 .. versionadded:: 1.4 The 

149 :paramref:`_postgresql.Insert.on_conflict_do_update.set_` 

150 parameter supports :class:`_schema.Column` objects from the target 

151 :class:`_schema.Table` as keys. 

152 

153 .. warning:: This dictionary does **not** take into account 

154 Python-specified default UPDATE values or generation functions, 

155 e.g. those specified using :paramref:`_schema.Column.onupdate`. 

156 These values will not be exercised for an ON CONFLICT style of 

157 UPDATE, unless they are manually specified in the 

158 :paramref:`.Insert.on_conflict_do_update.set_` dictionary. 

159 

160 :param where: 

161 Optional argument. An expression object representing a ``WHERE`` 

162 clause that restricts the rows affected by ``DO UPDATE SET``. Rows not 

163 meeting the ``WHERE`` condition will not be updated (effectively a 

164 ``DO NOTHING`` for those rows). 

165 

166 

167 .. seealso:: 

168 

169 :ref:`postgresql_insert_on_conflict` 

170 

171 """ 

172 self._post_values_clause = OnConflictDoUpdate( 

173 constraint, index_elements, index_where, set_, where 

174 ) 

175 return self 

176 

177 @_generative 

178 @_on_conflict_exclusive 

179 def on_conflict_do_nothing( 

180 self, 

181 constraint: _OnConflictConstraintT = None, 

182 index_elements: _OnConflictIndexElementsT = None, 

183 index_where: _OnConflictIndexWhereT = None, 

184 ) -> Self: 

185 """ 

186 Specifies a DO NOTHING action for ON CONFLICT clause. 

187 

188 The ``constraint`` and ``index_elements`` arguments 

189 are optional, but only one of these can be specified. 

190 

191 :param constraint: 

192 The name of a unique or exclusion constraint on the table, 

193 or the constraint object itself if it has a .name attribute. 

194 

195 :param index_elements: 

196 A sequence consisting of string column names, :class:`_schema.Column` 

197 objects, or other column expression objects that will be used 

198 to infer a target index. 

199 

200 :param index_where: 

201 Additional WHERE criterion that can be used to infer a 

202 conditional target index. 

203 

204 .. seealso:: 

205 

206 :ref:`postgresql_insert_on_conflict` 

207 

208 """ 

209 self._post_values_clause = OnConflictDoNothing( 

210 constraint, index_elements, index_where 

211 ) 

212 return self 

213 

214 

215class OnConflictClause(ClauseElement): 

216 stringify_dialect = "postgresql" 

217 

218 constraint_target: Optional[str] 

219 inferred_target_elements: Optional[List[Union[str, schema.Column[Any]]]] 

220 inferred_target_whereclause: Optional[ 

221 Union[ColumnElement[Any], TextClause] 

222 ] 

223 

224 def __init__( 

225 self, 

226 constraint: _OnConflictConstraintT = None, 

227 index_elements: _OnConflictIndexElementsT = None, 

228 index_where: _OnConflictIndexWhereT = None, 

229 ): 

230 if constraint is not None: 

231 if not isinstance(constraint, str) and isinstance( 

232 constraint, 

233 (schema.Constraint, ext.ExcludeConstraint), 

234 ): 

235 constraint = getattr(constraint, "name") or constraint 

236 

237 if constraint is not None: 

238 if index_elements is not None: 

239 raise ValueError( 

240 "'constraint' and 'index_elements' are mutually exclusive" 

241 ) 

242 

243 if isinstance(constraint, str): 

244 self.constraint_target = constraint 

245 self.inferred_target_elements = None 

246 self.inferred_target_whereclause = None 

247 elif isinstance(constraint, schema.Index): 

248 index_elements = constraint.expressions 

249 index_where = constraint.dialect_options["postgresql"].get( 

250 "where" 

251 ) 

252 elif isinstance(constraint, ext.ExcludeConstraint): 

253 index_elements = constraint.columns 

254 index_where = constraint.where 

255 else: 

256 index_elements = constraint.columns 

257 index_where = constraint.dialect_options["postgresql"].get( 

258 "where" 

259 ) 

260 

261 if index_elements is not None: 

262 self.constraint_target = None 

263 self.inferred_target_elements = [ 

264 coercions.expect(roles.DDLConstraintColumnRole, column) 

265 for column in index_elements 

266 ] 

267 

268 self.inferred_target_whereclause = ( 

269 coercions.expect( 

270 ( 

271 roles.StatementOptionRole 

272 if isinstance(constraint, ext.ExcludeConstraint) 

273 else roles.WhereHavingRole 

274 ), 

275 index_where, 

276 ) 

277 if index_where is not None 

278 else None 

279 ) 

280 

281 elif constraint is None: 

282 self.constraint_target = self.inferred_target_elements = ( 

283 self.inferred_target_whereclause 

284 ) = None 

285 

286 

287class OnConflictDoNothing(OnConflictClause): 

288 __visit_name__ = "on_conflict_do_nothing" 

289 

290 

291class OnConflictDoUpdate(OnConflictClause): 

292 __visit_name__ = "on_conflict_do_update" 

293 

294 update_values_to_set: List[Tuple[Union[schema.Column[Any], str], Any]] 

295 update_whereclause: Optional[ColumnElement[Any]] 

296 

297 def __init__( 

298 self, 

299 constraint: _OnConflictConstraintT = None, 

300 index_elements: _OnConflictIndexElementsT = None, 

301 index_where: _OnConflictIndexWhereT = None, 

302 set_: _OnConflictSetT = None, 

303 where: _OnConflictWhereT = None, 

304 ): 

305 super().__init__( 

306 constraint=constraint, 

307 index_elements=index_elements, 

308 index_where=index_where, 

309 ) 

310 

311 if ( 

312 self.inferred_target_elements is None 

313 and self.constraint_target is None 

314 ): 

315 raise ValueError( 

316 "Either constraint or index_elements, " 

317 "but not both, must be specified unless DO NOTHING" 

318 ) 

319 

320 if isinstance(set_, dict): 

321 if not set_: 

322 raise ValueError("set parameter dictionary must not be empty") 

323 elif isinstance(set_, ColumnCollection): 

324 set_ = dict(set_) 

325 else: 

326 raise ValueError( 

327 "set parameter must be a non-empty dictionary " 

328 "or a ColumnCollection such as the `.c.` collection " 

329 "of a Table object" 

330 ) 

331 self.update_values_to_set = [ 

332 (coercions.expect(roles.DMLColumnRole, key), value) 

333 for key, value in set_.items() 

334 ] 

335 self.update_whereclause = ( 

336 coercions.expect(roles.WhereHavingRole, where) 

337 if where is not None 

338 else None 

339 )