summaryrefslogtreecommitdiffstats
path: root/lib/sqlalchemy/future/engine.py
blob: 3235529f7362f430e7a77b6aeeda5168cf474f05 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
from .. import util
from ..engine import Connection as _LegacyConnection
from ..engine import create_engine as _create_engine
from ..engine import Engine as _LegacyEngine
from ..engine.base import OptionEngineMixin

NO_OPTIONS = util.immutabledict()


def create_engine(*arg, **kw):
    """Create a new :class:`_future.Engine` instance.

    Arguments passed to :func:`_future.create_engine` are mostly identical
    to those passed to the 1.x :func:`_sa.create_engine` function.
    The difference is that the object returned is the :class:`._future.Engine`
    which has the 2.0 version of the API.

    """

    kw["_future_engine_class"] = Engine
    return _create_engine(*arg, **kw)


class Connection(_LegacyConnection):
    """Provides high-level functionality for a wrapped DB-API connection.

    The :class:`_future.Connection` object is procured by calling
    the :meth:`_future.Engine.connect` method of the :class:`_future.Engine`
    object, and provides services for execution of SQL statements as well
    as transaction control.

    **This is the SQLAlchemy 2.0 version** of the :class:`_engine.Connection`
    class.   The API and behavior of this object is largely the same, with the
    following differences in behavior:

    * The result object returned for results is the
      :class:`_engine.CursorResult`
      object, which is a subclass of the :class:`_engine.Result`.
      This object has a slightly different API and behavior than the
      :class:`_engine.LegacyCursorResult` returned for 1.x style usage.

    * The object has :meth:`_future.Connection.commit` and
      :meth:`_future.Connection.rollback` methods which commit or roll back
      the current transaction in progress, if any.

    * The object features "autobegin" behavior, such that any call to
      :meth:`_future.Connection.execute` will
      unconditionally start a
      transaction which can be controlled using the above mentioned
      :meth:`_future.Connection.commit` and
      :meth:`_future.Connection.rollback` methods.

    * The object does not have any "autocommit" functionality.  Any SQL
      statement or DDL statement will not be followed by any COMMIT until
      the transaction is explicitly committed, either via the
      :meth:`_future.Connection.commit` method, or if the connection is
      being used in a context manager that commits such as the one
      returned by :meth:`_future.Engine.begin`.

    * The SAVEPOINT method :meth:`_future.Connection.begin_nested` returns
      a :class:`_engine.NestedTransaction` as was always the case, and the
      savepoint can be controlled by invoking
      :meth:`_engine.NestedTransaction.commit` or
      :meth:`_engine.NestedTransaction.rollback` as was the case before.
      However, this savepoint "transaction" is not associated with the
      transaction that is controlled by the connection itself; the overall
      transaction can be committed or rolled back directly which will not emit
      any special instructions for the SAVEPOINT (this will typically have the
      effect that one desires).

    * The :class:`_future.Connection` object does not support "branching",
      which was a pattern by which a sub "connection" would be used that
      refers to this connection as a parent.



    """

    _is_future = True

    def _branch(self):
        raise NotImplementedError(
            "sqlalchemy.future.Connection does not support "
            "'branching' of new connections."
        )

    def begin(self):
        """Begin a transaction prior to autobegin occurring.

        The returned object is an instance of :class:`_engine.RootTransaction`.
        This object represents the "scope" of the transaction,
        which completes when either the :meth:`_engine.Transaction.rollback`
        or :meth:`_engine.Transaction.commit` method is called.

        The :meth:`_future.Connection.begin` method in SQLAlchemy 2.0 begins a
        transaction that normally will be begun in any case when the connection
        is first used to execute a statement.  The reason this method might be
        used would be to invoke the :meth:`_events.ConnectionEvents.begin`
        event at a specific time, or to organize code within the scope of a
        connection checkout in terms of context managed blocks, such as::

            with engine.connect() as conn:
                with conn.begin():
                    conn.execute(...)
                    conn.execute(...)

                with conn.begin():
                    conn.execute(...)
                    conn.execute(...)

        The above code is not  fundamentally any different in its behavior than
        the following code  which does not use
        :meth:`_future.Connection.begin`; the below style is referred towards
        as "commit as you go" style::

            with engine.connect() as conn:
                conn.execute(...)
                conn.execute(...)
                conn.commit()

                conn.execute(...)
                conn.execute(...)
                conn.commit()

        From a database point of view, the :meth:`_future.Connection.begin`
        method does not emit any SQL or change the state of the underlying
        DBAPI connection in any way; the Python DBAPI does not have any
        concept of explicit transaction begin.

        .. seealso::

            :ref:`tutorial_working_with_transactions` - in the
            :ref:`unified_tutorial`

            :meth:`_future.Connection.begin_nested` - use a SAVEPOINT

            :meth:`_engine.Connection.begin_twophase` -
            use a two phase /XID transaction

            :meth:`_future.Engine.begin` - context manager available from
            :class:`_future.Engine`

        """
        return super(Connection, self).begin()

    def begin_nested(self):
        """Begin a nested transaction (i.e. SAVEPOINT) and return a transaction
        handle.

        The returned object is an instance of
        :class:`_engine.NestedTransaction`.

        Nested transactions require SAVEPOINT support in the
        underlying database.  Any transaction in the hierarchy may
        ``commit`` and ``rollback``, however the outermost transaction
        still controls the overall ``commit`` or ``rollback`` of the
        transaction of a whole.

        If an outer :class:`.RootTransaction` is not present on this
        :class:`_future.Connection`, a new one is created using "autobegin".
        This outer transaction may be completed using "commit-as-you-go" style
        usage, by calling upon :meth:`_future.Connection.commit` or
        :meth:`_future.Connection.rollback`.

        .. tip::

            The "autobegin" behavior of :meth:`_future.Connection.begin_nested`
            is specific to :term:`2.0 style` use; for legacy behaviors, see
            :meth:`_engine.Connection.begin_nested`.

        The :class:`_engine.NestedTransaction` remains independent of the
        :class:`_future.Connection` object itself. Calling the
        :meth:`_future.Connection.commit` or
        :meth:`_future.Connection.rollback` will always affect the actual
        containing database transaction itself, and not the SAVEPOINT itself.
        When a database transaction is committed, any SAVEPOINTs that have been
        established are cleared and the data changes within their scope is also
        committed.

        .. seealso::

            :meth:`_future.Connection.begin`


        """
        return super(Connection, self).begin_nested()

    def commit(self):
        """Commit the transaction that is currently in progress.

        This method commits the current transaction if one has been started.
        If no transaction was started, the method has no effect, assuming
        the connection is in a non-invalidated state.

        A transaction is begun on a :class:`_future.Connection` automatically
        whenever a statement is first executed, or when the
        :meth:`_future.Connection.begin` method is called.

        .. note:: The :meth:`_future.Connection.commit` method only acts upon
          the primary database transaction that is linked to the
          :class:`_future.Connection` object.  It does not operate upon a
          SAVEPOINT that would have been invoked from the
          :meth:`_future.Connection.begin_nested` method; for control of a
          SAVEPOINT, call :meth:`_engine.NestedTransaction.commit` on the
          :class:`_engine.NestedTransaction` that is returned by the
          :meth:`_future.Connection.begin_nested` method itself.


        """
        if self._transaction:
            self._transaction.commit()

    def rollback(self):
        """Roll back the transaction that is currently in progress.

        This method rolls back the current transaction if one has been started.
        If no transaction was started, the method has no effect.  If a
        transaction was started and the connection is in an invalidated state,
        the transaction is cleared using this method.

        A transaction is begun on a :class:`_future.Connection` automatically
        whenever a statement is first executed, or when the
        :meth:`_future.Connection.begin` method is called.

        .. note:: The :meth:`_future.Connection.rollback` method only acts
          upon the primary database transaction that is linked to the
          :class:`_future.Connection` object.  It does not operate upon a
          SAVEPOINT that would have been invoked from the
          :meth:`_future.Connection.begin_nested` method; for control of a
          SAVEPOINT, call :meth:`_engine.NestedTransaction.rollback` on the
          :class:`_engine.NestedTransaction` that is returned by the
          :meth:`_future.Connection.begin_nested` method itself.


        """
        if self._transaction:
            self._transaction.rollback()

    def close(self):
        """Close this :class:`_future.Connection`.

        This has the effect of also calling :meth:`_future.Connection.rollback`
        if any transaction is in place.

        """
        super(Connection, self).close()

    def execute(self, statement, parameters=None, execution_options=None):
        r"""Executes a SQL statement construct and returns a
        :class:`_engine.Result`.

        :param statement: The statement to be executed.  This is always
         an object that is in both the :class:`_expression.ClauseElement` and
         :class:`_expression.Executable` hierarchies, including:

         * :class:`_expression.Select`
         * :class:`_expression.Insert`, :class:`_expression.Update`,
           :class:`_expression.Delete`
         * :class:`_expression.TextClause` and
           :class:`_expression.TextualSelect`
         * :class:`_schema.DDL` and objects which inherit from
           :class:`_schema.DDLElement`

        :param parameters: parameters which will be bound into the statement.
         This may be either a dictionary of parameter names to values,
         or a mutable sequence (e.g. a list) of dictionaries.  When a
         list of dictionaries is passed, the underlying statement execution
         will make use of the DBAPI ``cursor.executemany()`` method.
         When a single dictionary is passed, the DBAPI ``cursor.execute()``
         method will be used.

        :param execution_options: optional dictionary of execution options,
         which will be associated with the statement execution.  This
         dictionary can provide a subset of the options that are accepted
         by :meth:`_future.Connection.execution_options`.

        :return: a :class:`_engine.Result` object.

        """
        return self._execute_20(
            statement, parameters, execution_options or NO_OPTIONS
        )

    def scalar(self, statement, parameters=None, execution_options=None):
        r"""Executes a SQL statement construct and returns a scalar object.

        This method is shorthand for invoking the
        :meth:`_engine.Result.scalar` method after invoking the
        :meth:`_future.Connection.execute` method.  Parameters are equivalent.

        :return: a scalar Python value representing the first column of the
         first row returned.

        """
        return self.execute(statement, parameters, execution_options).scalar()


class Engine(_LegacyEngine):
    """Connects a :class:`_pool.Pool` and
    :class:`_engine.Dialect` together to provide a
    source of database connectivity and behavior.

    **This is the SQLAlchemy 2.0 version** of the :class:`~.engine.Engine`.

    An :class:`.future.Engine` object is instantiated publicly using the
    :func:`~sqlalchemy.future.create_engine` function.

    .. seealso::

        :doc:`/core/engines`

        :ref:`connections_toplevel`

    """

    _connection_cls = Connection
    _is_future = True

    def _not_implemented(self, *arg, **kw):
        raise NotImplementedError(
            "This method is not implemented for SQLAlchemy 2.0."
        )

    transaction = (
        run_callable
    ) = (
        execute
    ) = (
        scalar
    ) = (
        _execute_clauseelement
    ) = _execute_compiled = table_names = has_table = _not_implemented

    def _run_ddl_visitor(self, visitorcallable, element, **kwargs):
        # TODO: this is for create_all support etc.   not clear if we
        # want to provide this in 2.0, that is, a way to execute SQL where
        # they aren't calling "engine.begin()" explicitly, however, DDL
        # may be a special case for which we want to continue doing it this
        # way.  A big win here is that the full DDL sequence is inside of a
        # single transaction rather than COMMIT for each statement.
        with self.begin() as conn:
            conn._run_ddl_visitor(visitorcallable, element, **kwargs)

    @classmethod
    def _future_facade(self, legacy_engine):
        return Engine(
            legacy_engine.pool,
            legacy_engine.dialect,
            legacy_engine.url,
            logging_name=legacy_engine.logging_name,
            echo=legacy_engine.echo,
            hide_parameters=legacy_engine.hide_parameters,
            execution_options=legacy_engine._execution_options,
        )

    @util.contextmanager
    def begin(self):
        """Return a :class:`_future.Connection` object with a transaction
        begun.

        Use of this method is similar to that of
        :meth:`_future.Engine.connect`, typically as a context manager, which
        will automatically maintain the state of the transaction when the block
        ends, either by calling :meth:`_future.Connection.commit` when the
        block succeeds normally, or :meth:`_future.Connection.rollback` when an
        exception is raised, before propagating the exception outwards::

            with engine.begin() as connection:
                connection.execute(text("insert into table values ('foo')"))


        .. seealso::

            :meth:`_future.Engine.connect`

            :meth:`_future.Connection.begin`

        """
        with self.connect() as conn:
            with conn.begin():
                yield conn

    def connect(self):
        """Return a new :class:`_future.Connection` object.

        The :class:`_future.Connection` acts as a Python context manager, so
        the typical use of this method looks like::

            with engine.connect() as connection:
                connection.execute(text("insert into table values ('foo')"))
                connection.commit()

        Where above, after the block is completed, the connection is "closed"
        and its underlying DBAPI resources are returned to the connection pool.
        This also has the effect of rolling back any transaction that
        was explicitly begun or was begun via autobegin, and will
        emit the :meth:`_events.ConnectionEvents.rollback` event if one was
        started and is still in progress.

        .. seealso::

            :meth:`_future.Engine.begin`


        """
        return super(Engine, self).connect()


class OptionEngine(OptionEngineMixin, Engine):
    pass


Engine._option_cls = OptionEngine