summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2020-06-26 00:13:25 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2020-06-26 00:13:25 +0000
commit2d9387354f11da322c516412eb5dfe937163c90b (patch)
tree13a054d4f6de3088da9aedc5aa22f8fce32654e5 /lib/sqlalchemy/engine
parent3138201a82d4e62e56e44ca9c8914c20dd46d1b4 (diff)
parentf1a3038f480ee1965928cdcd1dc0c47347f270bc (diff)
downloadsqlalchemy-2d9387354f11da322c516412eb5dfe937163c90b.tar.gz
Merge "Default psycopg2 executemany mode to "values_only""
Diffstat (limited to 'lib/sqlalchemy/engine')
-rw-r--r--lib/sqlalchemy/engine/base.py6
-rw-r--r--lib/sqlalchemy/engine/cursor.py82
-rw-r--r--lib/sqlalchemy/engine/default.py83
3 files changed, 98 insertions, 73 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 81c0c9f58..c73f89a2b 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1163,10 +1163,10 @@ class Connection(Connectable):
# ensure we don't retain a link to the view object for keys()
# which links to the values, which we don't want to cache
keys = sorted(distilled_params[0])
- inline = len(distilled_params) > 1
+ for_executemany = len(distilled_params) > 1
else:
keys = []
- inline = False
+ for_executemany = False
dialect = self.dialect
@@ -1182,7 +1182,7 @@ class Connection(Connectable):
dialect=dialect,
compiled_cache=compiled_cache,
column_keys=keys,
- inline=inline,
+ for_executemany=for_executemany,
schema_translate_map=schema_translate_map,
linting=self.dialect.compiler_linting | compiler.WARN_LINTING,
)
diff --git a/lib/sqlalchemy/engine/cursor.py b/lib/sqlalchemy/engine/cursor.py
index abffe0d1f..65cd92e6f 100644
--- a/lib/sqlalchemy/engine/cursor.py
+++ b/lib/sqlalchemy/engine/cursor.py
@@ -1077,7 +1077,7 @@ class FullyBufferedCursorFetchStrategy(CursorFetchStrategy):
__slots__ = ("_rowbuffer", "alternate_cursor_description")
def __init__(
- self, dbapi_cursor, alternate_description, initial_buffer=None
+ self, dbapi_cursor, alternate_description=None, initial_buffer=None
):
self.alternate_cursor_description = alternate_description
if initial_buffer is not None:
@@ -1304,7 +1304,37 @@ class BaseCursorResult(object):
self.connection._safe_close_cursor(cursor)
self._soft_closed = True
- @util.memoized_property
+ @property
+ def inserted_primary_key_rows(self):
+ """Return a list of tuples, each containing the primary key for each row
+ just inserted.
+
+ Usually, this method will return at most a list with a single
+ entry which is the same row one would get back from
+ :attr:`_engine.CursorResult.inserted_primary_key`. To support
+ "executemany with INSERT" mode, multiple rows can be part of the
+ list returned.
+
+ .. versionadded:: 1.4
+
+ """
+ if not self.context.compiled:
+ raise exc.InvalidRequestError(
+ "Statement is not a compiled " "expression construct."
+ )
+ elif not self.context.isinsert:
+ raise exc.InvalidRequestError(
+ "Statement is not an insert() " "expression construct."
+ )
+ elif self.context._is_explicit_returning:
+ raise exc.InvalidRequestError(
+ "Can't call inserted_primary_key "
+ "when returning() "
+ "is used."
+ )
+ return self.context.inserted_primary_key_rows
+
+ @property
def inserted_primary_key(self):
"""Return the primary key for the row just inserted.
@@ -1331,22 +1361,18 @@ class BaseCursorResult(object):
"""
- if not self.context.compiled:
+ if self.context.executemany:
raise exc.InvalidRequestError(
- "Statement is not a compiled " "expression construct."
- )
- elif not self.context.isinsert:
- raise exc.InvalidRequestError(
- "Statement is not an insert() " "expression construct."
- )
- elif self.context._is_explicit_returning:
- raise exc.InvalidRequestError(
- "Can't call inserted_primary_key "
- "when returning() "
- "is used."
+ "This statement was an executemany call; if primary key "
+ "returning is supported, please "
+ "use .inserted_primary_key_rows."
)
- return self.context.inserted_primary_key
+ ikp = self.inserted_primary_key_rows
+ if ikp:
+ return ikp[0]
+ else:
+ return None
def last_updated_params(self):
"""Return the collection of updated parameters from this
@@ -1393,6 +1419,19 @@ class BaseCursorResult(object):
return self.context.compiled_parameters[0]
@property
+ def returned_defaults_rows(self):
+ """Return a list of rows each containing the values of default
+ columns that were fetched using
+ the :meth:`.ValuesBase.return_defaults` feature.
+
+ The return value is a list of :class:`.Row` objects.
+
+ .. versionadded:: 1.4
+
+ """
+ return self.context.returned_default_rows
+
+ @property
def returned_defaults(self):
"""Return the values of default columns that were fetched using
the :meth:`.ValuesBase.return_defaults` feature.
@@ -1408,7 +1447,18 @@ class BaseCursorResult(object):
:meth:`.ValuesBase.return_defaults`
"""
- return self.context.returned_defaults
+
+ if self.context.executemany:
+ raise exc.InvalidRequestError(
+ "This statement was an executemany call; if return defaults "
+ "is supported, please use .returned_defaults_rows."
+ )
+
+ rows = self.context.returned_default_rows
+ if rows:
+ return rows[0]
+ else:
+ return None
def lastrow_has_defaults(self):
"""Return ``lastrow_has_defaults()`` from the underlying
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index cef719498..790f68de7 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -67,6 +67,7 @@ class DefaultDialect(interfaces.Dialect):
postfetch_lastrowid = True
implicit_returning = False
full_returning = False
+ insert_executemany_returning = False
cte_follows_insert = False
@@ -704,7 +705,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
compiled = None
statement = None
result_column_struct = None
- returned_defaults = None
+ returned_default_rows = None
execution_options = util.immutabledict()
cursor_fetch_strategy = _cursor._DEFAULT_FETCH
@@ -1322,12 +1323,14 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
if self.isinsert:
if self._is_implicit_returning:
- row = result.fetchone()
- self.returned_defaults = row
- self._setup_ins_pk_from_implicit_returning(row)
+ rows = result.all()
- # test that it has a cursor metadata that is accurate.
- # the first row will have been fetched and current assumptions
+ self.returned_default_rows = rows
+
+ self._setup_ins_pk_from_implicit_returning(result, rows)
+
+ # test that it has a cursor metadata that is accurate. the
+ # first row will have been fetched and current assumptions
# are that the result has only one row, until executemany()
# support is added here.
assert result._metadata.returns_rows
@@ -1343,7 +1346,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
elif self.isupdate and self._is_implicit_returning:
row = result.fetchone()
- self.returned_defaults = row
+ self.returned_default_rows = [row]
result._soft_close()
# test that it has a cursor metadata that is accurate.
@@ -1359,61 +1362,33 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
return result
def _setup_ins_pk_from_lastrowid(self):
- key_getter = self.compiled._key_getters_for_crud_column[2]
- table = self.compiled.statement.table
- compiled_params = self.compiled_parameters[0]
+
+ getter = self.compiled._inserted_primary_key_from_lastrowid_getter
lastrowid = self.get_lastrowid()
- if lastrowid is not None:
- autoinc_col = table._autoincrement_column
- if autoinc_col is not None:
- # apply type post processors to the lastrowid
- proc = autoinc_col.type._cached_result_processor(
- self.dialect, None
- )
- if proc is not None:
- lastrowid = proc(lastrowid)
- self.inserted_primary_key = [
- lastrowid
- if c is autoinc_col
- else compiled_params.get(key_getter(c), None)
- for c in table.primary_key
- ]
- else:
- # don't have a usable lastrowid, so
- # do the same as _setup_ins_pk_from_empty
- self.inserted_primary_key = [
- compiled_params.get(key_getter(c), None)
- for c in table.primary_key
- ]
+ self.inserted_primary_key_rows = [
+ getter(lastrowid, self.compiled_parameters[0])
+ ]
def _setup_ins_pk_from_empty(self):
- key_getter = self.compiled._key_getters_for_crud_column[2]
- table = self.compiled.statement.table
- compiled_params = self.compiled_parameters[0]
- self.inserted_primary_key = [
- compiled_params.get(key_getter(c), None) for c in table.primary_key
+
+ getter = self.compiled._inserted_primary_key_from_lastrowid_getter
+
+ self.inserted_primary_key_rows = [
+ getter(None, self.compiled_parameters[0])
]
- def _setup_ins_pk_from_implicit_returning(self, row):
- if row is None:
- self.inserted_primary_key = None
+ def _setup_ins_pk_from_implicit_returning(self, result, rows):
+
+ if not rows:
+ self.inserted_primary_key_rows = []
return
- key_getter = self.compiled._key_getters_for_crud_column[2]
- table = self.compiled.statement.table
- compiled_params = self.compiled_parameters[0]
-
- # TODO: why are we using keyed index here? can't we get the ints?
- # can compiler build up the structure here as far as what was
- # explicit and what comes back in returning?
- row_mapping = row._mapping
- self.inserted_primary_key = [
- row_mapping[col] if value is None else value
- for col, value in [
- (col, compiled_params.get(key_getter(col), None))
- for col in table.primary_key
- ]
+ getter = self.compiled._inserted_primary_key_from_returning_getter
+ compiled_params = self.compiled_parameters
+
+ self.inserted_primary_key_rows = [
+ getter(row, param) for row, param in zip(rows, compiled_params)
]
def lastrow_has_defaults(self):