left-hand side type to be transferred directly to the right hand side
so that bind-level rules can be applied to the expression's argument.
+ .. change:: 3955
+ :tags: bug, sql, postgresql
+ :versions: 1.2.0b1
+ :tickets: 3955
+
+ Changed the mechanics of :class:`.ResultProxy` to unconditionally
+ delay the "autoclose" step until the :class:`.Connection` is done
+ with the object; in the case where Postgresql ON CONFLICT with
+ RETURNING returns no rows, autoclose was occurring in this previously
+ non-existent use case, causing the usual autocommit behavior that
+ occurs unconditionally upon INSERT/UPDATE/DELETE to fail.
.. changelog::
:version: 1.1.8
else:
result = context.get_result_proxy()
if result._metadata is None:
- result._soft_close(_autoclose_connection=False)
+ result._soft_close()
if context.should_autocommit and self._root.__transaction is None:
self._root._commit_impl(autocommit=True)
- if result._soft_closed and self.should_close_with_result:
- self.close()
-
+ # for "connectionless" execution, we have to close this
+ # Connection after the statement is complete.
+ if self.should_close_with_result:
+ # ResultProxy already exhausted rows / has no rows.
+ # close us now
+ if result._soft_closed:
+ self.close()
+ else:
+ # ResultProxy will close this Connection when no more
+ # rows to fetch.
+ result._autoclose_connection = True
return result
def _cursor_execute(self, cursor, statement, parameters, context=None):
row = result.fetchone()
self.returned_defaults = row
self._setup_ins_pk_from_implicit_returning(row)
- result._soft_close(_autoclose_connection=False)
+ result._soft_close()
result._metadata = None
elif not self._is_explicit_returning:
- result._soft_close(_autoclose_connection=False)
+ result._soft_close()
result._metadata = None
elif self.isupdate and self._is_implicit_returning:
row = result.fetchone()
self.returned_defaults = row
- result._soft_close(_autoclose_connection=False)
+ result._soft_close()
result._metadata = None
elif result._metadata is None:
# (which requires open cursor on some drivers
# such as kintersbasdb, mxodbc)
result.rowcount
- result._soft_close(_autoclose_connection=False)
+ result._soft_close()
return result
def _setup_ins_pk_from_lastrowid(self):
_process_row = RowProxy
out_parameters = None
- _can_close_connection = False
+ _autoclose_connection = False
_metadata = None
_soft_closed = False
closed = False
return self._saved_cursor.description
- def _soft_close(self, _autoclose_connection=True):
+ def _soft_close(self):
"""Soft close this :class:`.ResultProxy`.
This releases all DBAPI cursor resources, but leaves the
self._soft_closed = True
cursor = self.cursor
self.connection._safe_close_cursor(cursor)
- if _autoclose_connection and \
- self.connection.should_close_with_result:
+ if self._autoclose_connection:
self.connection.close()
self.cursor = None
__only_on__ = 'postgresql >= 9.5',
__backend__ = True
+ run_define_tables = 'each'
@classmethod
def define_tables(cls, metadata):
with testing.db.connect() as conn:
result = conn.execute(
insert(users).on_conflict_do_nothing(),
+
dict(id=1, name='name1')
)
eq_(result.inserted_primary_key, [1])
[(1, 'name1')]
)
+ def test_on_conflict_do_nothing_connectionless(self):
+ users = self.tables.users_xtra
+
+ with testing.db.connect() as conn:
+ result = conn.execute(
+ insert(users).on_conflict_do_nothing(
+ constraint='uq_login_email'),
+
+ dict(name='name1', login_email='email1')
+ )
+ eq_(result.inserted_primary_key, [1])
+ eq_(result.returned_defaults, (1,))
+
+ result = testing.db.execute(
+ insert(users).on_conflict_do_nothing(
+ constraint='uq_login_email'
+ ),
+ dict(name='name2', login_email='email1')
+ )
+ eq_(result.inserted_primary_key, None)
+ eq_(result.returned_defaults, None)
+
+ eq_(
+ testing.db.execute(users.select().where(users.c.id == 1)).fetchall(),
+ [(1, 'name1', 'email1', None)]
+ )
+
@testing.provide_metadata
def test_on_conflict_do_nothing_target(self):
users = self.tables.users
result.fetchone
)
+ def test_connectionless_autoclose_rows_exhausted(self):
+ users = self.tables.users
+ users.insert().execute(
+ dict(user_id=1, user_name='john'),
+ )
+
+ result = testing.db.execute("select * from users")
+ connection = result.connection
+ assert not connection.closed
+ eq_(result.fetchone(), (1, 'john'))
+ assert not connection.closed
+ eq_(result.fetchone(), None)
+ assert connection.closed
+
+ @testing.requires.returning
+ def test_connectionless_autoclose_crud_rows_exhausted(self):
+ users = self.tables.users
+ stmt = users.insert().values(user_id=1, user_name='john').\
+ returning(users.c.user_id)
+ result = testing.db.execute(stmt)
+ connection = result.connection
+ assert not connection.closed
+ eq_(result.fetchone(), (1, ))
+ assert not connection.closed
+ eq_(result.fetchone(), None)
+ assert connection.closed
+
+ def test_connectionless_autoclose_no_rows(self):
+ result = testing.db.execute("select * from users")
+ connection = result.connection
+ assert not connection.closed
+ eq_(result.fetchone(), None)
+ assert connection.closed
+
+ def test_connectionless_autoclose_no_metadata(self):
+ result = testing.db.execute("update users set user_id=5")
+ connection = result.connection
+ assert connection.closed
+ assert_raises_message(
+ exc.ResourceClosedError,
+ "This result object does not return rows.",
+ result.fetchone
+ )
+
def test_row_case_sensitive(self):
row = testing.db.execute(
select([