1# future/engine.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
7from .. import util
8from ..engine import Connection as _LegacyConnection
9from ..engine import create_engine as _create_engine
10from ..engine import Engine as _LegacyEngine
11from ..engine.base import OptionEngineMixin
12
13NO_OPTIONS = util.immutabledict()
14
15
16def create_engine(*arg, **kw):
17 """Create a new :class:`_future.Engine` instance.
18
19 Arguments passed to :func:`_future.create_engine` are mostly identical
20 to those passed to the 1.x :func:`_sa.create_engine` function.
21 The difference is that the object returned is the :class:`._future.Engine`
22 which has the 2.0 version of the API.
23
24 """
25
26 kw["_future_engine_class"] = Engine
27 return _create_engine(*arg, **kw)
28
29
30class Connection(_LegacyConnection):
31 """Provides high-level functionality for a wrapped DB-API connection.
32
33 The :class:`_future.Connection` object is procured by calling
34 the :meth:`_future.Engine.connect` method of the :class:`_future.Engine`
35 object, and provides services for execution of SQL statements as well
36 as transaction control.
37
38 **This is the SQLAlchemy 2.0 version** of the :class:`_engine.Connection`
39 class. The API and behavior of this object is largely the same, with the
40 following differences in behavior:
41
42 * The result object returned for results is the
43 :class:`_engine.CursorResult`
44 object, which is a subclass of the :class:`_engine.Result`.
45 This object has a slightly different API and behavior than the
46 :class:`_engine.LegacyCursorResult` returned for 1.x style usage.
47
48 * The object has :meth:`_future.Connection.commit` and
49 :meth:`_future.Connection.rollback` methods which commit or roll back
50 the current transaction in progress, if any.
51
52 * The object features "autobegin" behavior, such that any call to
53 :meth:`_future.Connection.execute` will
54 unconditionally start a
55 transaction which can be controlled using the above mentioned
56 :meth:`_future.Connection.commit` and
57 :meth:`_future.Connection.rollback` methods.
58
59 * The object does not have any "autocommit" functionality. Any SQL
60 statement or DDL statement will not be followed by any COMMIT until
61 the transaction is explicitly committed, either via the
62 :meth:`_future.Connection.commit` method, or if the connection is
63 being used in a context manager that commits such as the one
64 returned by :meth:`_future.Engine.begin`.
65
66 * The SAVEPOINT method :meth:`_future.Connection.begin_nested` returns
67 a :class:`_engine.NestedTransaction` as was always the case, and the
68 savepoint can be controlled by invoking
69 :meth:`_engine.NestedTransaction.commit` or
70 :meth:`_engine.NestedTransaction.rollback` as was the case before.
71 However, this savepoint "transaction" is not associated with the
72 transaction that is controlled by the connection itself; the overall
73 transaction can be committed or rolled back directly which will not emit
74 any special instructions for the SAVEPOINT (this will typically have the
75 effect that one desires).
76
77 * The :class:`_future.Connection` object does not support "branching",
78 which was a pattern by which a sub "connection" would be used that
79 refers to this connection as a parent.
80
81
82
83 """
84
85 _is_future = True
86
87 def _branch(self):
88 raise NotImplementedError(
89 "sqlalchemy.future.Connection does not support "
90 "'branching' of new connections."
91 )
92
93 def begin(self):
94 """Begin a transaction prior to autobegin occurring.
95
96 The returned object is an instance of :class:`_engine.RootTransaction`.
97 This object represents the "scope" of the transaction,
98 which completes when either the :meth:`_engine.Transaction.rollback`
99 or :meth:`_engine.Transaction.commit` method is called.
100
101 The :meth:`_future.Connection.begin` method in SQLAlchemy 2.0 begins a
102 transaction that normally will be begun in any case when the connection
103 is first used to execute a statement. The reason this method might be
104 used would be to invoke the :meth:`_events.ConnectionEvents.begin`
105 event at a specific time, or to organize code within the scope of a
106 connection checkout in terms of context managed blocks, such as::
107
108 with engine.connect() as conn:
109 with conn.begin():
110 conn.execute(...)
111 conn.execute(...)
112
113 with conn.begin():
114 conn.execute(...)
115 conn.execute(...)
116
117 The above code is not fundamentally any different in its behavior than
118 the following code which does not use
119 :meth:`_future.Connection.begin`; the below style is referred towards
120 as "commit as you go" style::
121
122 with engine.connect() as conn:
123 conn.execute(...)
124 conn.execute(...)
125 conn.commit()
126
127 conn.execute(...)
128 conn.execute(...)
129 conn.commit()
130
131 From a database point of view, the :meth:`_future.Connection.begin`
132 method does not emit any SQL or change the state of the underlying
133 DBAPI connection in any way; the Python DBAPI does not have any
134 concept of explicit transaction begin.
135
136 .. seealso::
137
138 :ref:`tutorial_working_with_transactions` - in the
139 :ref:`unified_tutorial`
140
141 :meth:`_future.Connection.begin_nested` - use a SAVEPOINT
142
143 :meth:`_engine.Connection.begin_twophase` -
144 use a two phase /XID transaction
145
146 :meth:`_future.Engine.begin` - context manager available from
147 :class:`_future.Engine`
148
149 """
150 return super(Connection, self).begin()
151
152 def begin_nested(self):
153 """Begin a nested transaction (i.e. SAVEPOINT) and return a transaction
154 handle.
155
156 The returned object is an instance of
157 :class:`_engine.NestedTransaction`.
158
159 Nested transactions require SAVEPOINT support in the
160 underlying database. Any transaction in the hierarchy may
161 ``commit`` and ``rollback``, however the outermost transaction
162 still controls the overall ``commit`` or ``rollback`` of the
163 transaction of a whole.
164
165 If an outer :class:`.RootTransaction` is not present on this
166 :class:`_future.Connection`, a new one is created using "autobegin".
167 This outer transaction may be completed using "commit-as-you-go" style
168 usage, by calling upon :meth:`_future.Connection.commit` or
169 :meth:`_future.Connection.rollback`.
170
171 .. tip::
172
173 The "autobegin" behavior of :meth:`_future.Connection.begin_nested`
174 is specific to :term:`2.0 style` use; for legacy behaviors, see
175 :meth:`_engine.Connection.begin_nested`.
176
177 The :class:`_engine.NestedTransaction` remains independent of the
178 :class:`_future.Connection` object itself. Calling the
179 :meth:`_future.Connection.commit` or
180 :meth:`_future.Connection.rollback` will always affect the actual
181 containing database transaction itself, and not the SAVEPOINT itself.
182 When a database transaction is committed, any SAVEPOINTs that have been
183 established are cleared and the data changes within their scope is also
184 committed.
185
186 .. seealso::
187
188 :meth:`_future.Connection.begin`
189
190
191 """
192 return super(Connection, self).begin_nested()
193
194 def commit(self):
195 """Commit the transaction that is currently in progress.
196
197 This method commits the current transaction if one has been started.
198 If no transaction was started, the method has no effect, assuming
199 the connection is in a non-invalidated state.
200
201 A transaction is begun on a :class:`_future.Connection` automatically
202 whenever a statement is first executed, or when the
203 :meth:`_future.Connection.begin` method is called.
204
205 .. note:: The :meth:`_future.Connection.commit` method only acts upon
206 the primary database transaction that is linked to the
207 :class:`_future.Connection` object. It does not operate upon a
208 SAVEPOINT that would have been invoked from the
209 :meth:`_future.Connection.begin_nested` method; for control of a
210 SAVEPOINT, call :meth:`_engine.NestedTransaction.commit` on the
211 :class:`_engine.NestedTransaction` that is returned by the
212 :meth:`_future.Connection.begin_nested` method itself.
213
214
215 """
216 if self._transaction:
217 self._transaction.commit()
218
219 def rollback(self):
220 """Roll back the transaction that is currently in progress.
221
222 This method rolls back the current transaction if one has been started.
223 If no transaction was started, the method has no effect. If a
224 transaction was started and the connection is in an invalidated state,
225 the transaction is cleared using this method.
226
227 A transaction is begun on a :class:`_future.Connection` automatically
228 whenever a statement is first executed, or when the
229 :meth:`_future.Connection.begin` method is called.
230
231 .. note:: The :meth:`_future.Connection.rollback` method only acts
232 upon the primary database transaction that is linked to the
233 :class:`_future.Connection` object. It does not operate upon a
234 SAVEPOINT that would have been invoked from the
235 :meth:`_future.Connection.begin_nested` method; for control of a
236 SAVEPOINT, call :meth:`_engine.NestedTransaction.rollback` on the
237 :class:`_engine.NestedTransaction` that is returned by the
238 :meth:`_future.Connection.begin_nested` method itself.
239
240
241 """
242 if self._transaction:
243 self._transaction.rollback()
244
245 def close(self):
246 """Close this :class:`_future.Connection`.
247
248 This has the effect of also calling :meth:`_future.Connection.rollback`
249 if any transaction is in place.
250
251 """
252 super(Connection, self).close()
253
254 def execute(self, statement, parameters=None, execution_options=None):
255 r"""Executes a SQL statement construct and returns a
256 :class:`_engine.Result`.
257
258 :param statement: The statement to be executed. This is always
259 an object that is in both the :class:`_expression.ClauseElement` and
260 :class:`_expression.Executable` hierarchies, including:
261
262 * :class:`_expression.Select`
263 * :class:`_expression.Insert`, :class:`_expression.Update`,
264 :class:`_expression.Delete`
265 * :class:`_expression.TextClause` and
266 :class:`_expression.TextualSelect`
267 * :class:`_schema.DDL` and objects which inherit from
268 :class:`_schema.DDLElement`
269
270 :param parameters: parameters which will be bound into the statement.
271 This may be either a dictionary of parameter names to values,
272 or a mutable sequence (e.g. a list) of dictionaries. When a
273 list of dictionaries is passed, the underlying statement execution
274 will make use of the DBAPI ``cursor.executemany()`` method.
275 When a single dictionary is passed, the DBAPI ``cursor.execute()``
276 method will be used.
277
278 :param execution_options: optional dictionary of execution options,
279 which will be associated with the statement execution. This
280 dictionary can provide a subset of the options that are accepted
281 by :meth:`_future.Connection.execution_options`.
282
283 :return: a :class:`_engine.Result` object.
284
285 """
286 return self._execute_20(
287 statement, parameters, execution_options or NO_OPTIONS
288 )
289
290 def scalar(self, statement, parameters=None, execution_options=None):
291 r"""Executes a SQL statement construct and returns a scalar object.
292
293 This method is shorthand for invoking the
294 :meth:`_engine.Result.scalar` method after invoking the
295 :meth:`_future.Connection.execute` method. Parameters are equivalent.
296
297 :return: a scalar Python value representing the first column of the
298 first row returned.
299
300 """
301 return self.execute(statement, parameters, execution_options).scalar()
302
303
304class Engine(_LegacyEngine):
305 """Connects a :class:`_pool.Pool` and
306 :class:`_engine.Dialect` together to provide a
307 source of database connectivity and behavior.
308
309 **This is the SQLAlchemy 2.0 version** of the :class:`~.engine.Engine`.
310
311 An :class:`.future.Engine` object is instantiated publicly using the
312 :func:`~sqlalchemy.future.create_engine` function.
313
314 .. seealso::
315
316 :doc:`/core/engines`
317
318 :ref:`connections_toplevel`
319
320 """
321
322 _connection_cls = Connection
323 _is_future = True
324
325 def _not_implemented(self, *arg, **kw):
326 raise NotImplementedError(
327 "This method is not implemented for SQLAlchemy 2.0."
328 )
329
330 transaction = (
331 run_callable
332 ) = (
333 execute
334 ) = (
335 scalar
336 ) = (
337 _execute_clauseelement
338 ) = _execute_compiled = table_names = has_table = _not_implemented
339
340 def _run_ddl_visitor(self, visitorcallable, element, **kwargs):
341 # TODO: this is for create_all support etc. not clear if we
342 # want to provide this in 2.0, that is, a way to execute SQL where
343 # they aren't calling "engine.begin()" explicitly, however, DDL
344 # may be a special case for which we want to continue doing it this
345 # way. A big win here is that the full DDL sequence is inside of a
346 # single transaction rather than COMMIT for each statement.
347 with self.begin() as conn:
348 conn._run_ddl_visitor(visitorcallable, element, **kwargs)
349
350 @classmethod
351 def _future_facade(self, legacy_engine):
352 return Engine(
353 legacy_engine.pool,
354 legacy_engine.dialect,
355 legacy_engine.url,
356 logging_name=legacy_engine.logging_name,
357 echo=legacy_engine.echo,
358 hide_parameters=legacy_engine.hide_parameters,
359 execution_options=legacy_engine._execution_options,
360 )
361
362 @util.contextmanager
363 def begin(self):
364 """Return a :class:`_future.Connection` object with a transaction
365 begun.
366
367 Use of this method is similar to that of
368 :meth:`_future.Engine.connect`, typically as a context manager, which
369 will automatically maintain the state of the transaction when the block
370 ends, either by calling :meth:`_future.Connection.commit` when the
371 block succeeds normally, or :meth:`_future.Connection.rollback` when an
372 exception is raised, before propagating the exception outwards::
373
374 with engine.begin() as connection:
375 connection.execute(text("insert into table values ('foo')"))
376
377
378 .. seealso::
379
380 :meth:`_future.Engine.connect`
381
382 :meth:`_future.Connection.begin`
383
384 """
385 with self.connect() as conn:
386 with conn.begin():
387 yield conn
388
389 def connect(self):
390 """Return a new :class:`_future.Connection` object.
391
392 The :class:`_future.Connection` acts as a Python context manager, so
393 the typical use of this method looks like::
394
395 with engine.connect() as connection:
396 connection.execute(text("insert into table values ('foo')"))
397 connection.commit()
398
399 Where above, after the block is completed, the connection is "closed"
400 and its underlying DBAPI resources are returned to the connection pool.
401 This also has the effect of rolling back any transaction that
402 was explicitly begun or was begun via autobegin, and will
403 emit the :meth:`_events.ConnectionEvents.rollback` event if one was
404 started and is still in progress.
405
406 .. seealso::
407
408 :meth:`_future.Engine.begin`
409
410
411 """
412 return super(Engine, self).connect()
413
414
415class OptionEngine(OptionEngineMixin, Engine):
416 pass
417
418
419Engine._option_cls = OptionEngine