Scaled back a fix made for :ticket:`6581` where "executemany values" mode
for psycopg2 were disabled for all "ON CONFLICT" styles of INSERT, to
not apply to the "ON CONFLICT DO NOTHING" clause, which does not include
any parameters and is safe for "executemany values" mode. "ON CONFLICT
DO UPDATE" is still blocked from "executemany values" as there may
be additional parameters in the DO UPDATE clause that cannot be batched
(which is the original issue fixed by :ticket:`6581`).
Fixes: #7880
Change-Id: Id3e23a0c6699333409a50148fa8923cb8e564bdc
(cherry picked from commit
cc8c5835a92b0035530b541c81b0c714b570b095)
--- /dev/null
+.. change::
+ :tags: bug, postgresql
+ :tickets: 7880
+
+ Scaled back a fix made for :ticket:`6581` where "executemany values" mode
+ for psycopg2 were disabled for all "ON CONFLICT" styles of INSERT, to
+ not apply to the "ON CONFLICT DO NOTHING" clause, which does not include
+ any parameters and is safe for "executemany values" mode. "ON CONFLICT
+ DO UPDATE" is still blocked from "executemany values" as there may
+ be additional parameters in the DO UPDATE clause that cannot be batched
+ (which is the original issue fixed by :ticket:`6581`).
from uuid import UUID as _python_UUID
from . import array as _array
+from . import dml
from . import hstore as _hstore
from . import json as _json
from . import ranges as _ranges
return target_text
+ @util.memoized_property
+ def _is_safe_for_fast_insert_values_helper(self):
+ # don't allow fast executemany if _post_values_clause is
+ # present and is not an OnConflictDoNothing. what this means
+ # concretely is that the
+ # "fast insert executemany helper" won't be used, in other
+ # words we won't convert "executemany()" of many parameter
+ # sets into a single INSERT with many elements in VALUES.
+ # We can't apply that optimization safely if for example the
+ # statement includes a clause like "ON CONFLICT DO UPDATE"
+
+ return self.insert_single_values_expr is not None and (
+ self.statement._post_values_clause is None
+ or isinstance(
+ self.statement._post_values_clause, dml.OnConflictDoNothing
+ )
+ )
+
def visit_on_conflict_do_nothing(self, on_conflict, **kw):
target_text = self._on_conflict_target(on_conflict, **kw)
self.executemany_mode & EXECUTEMANY_VALUES
and context
and context.isinsert
- and context.compiled.insert_single_values_expr
+ and context.compiled._is_safe_for_fast_insert_values_helper
):
executemany_values = (
"(%s)" % context.compiled.insert_single_values_expr
[value for c, expr, value in crud_params]
)
text += " VALUES (%s)" % insert_single_values_expr
- if toplevel and insert_stmt._post_values_clause is None:
- # don't assign insert_single_values_expr if _post_values_clause
- # is present. what this means concretely is that the
- # "fast insert executemany helper" won't be used, in other
- # words we won't convert "executemany()" of many parameter
- # sets into a single INSERT with many elements in VALUES.
- # We can't apply that optimization safely if for example the
- # statement includes a clause like "ON CONFLICT DO UPDATE"
+ if toplevel:
self.insert_single_values_expr = insert_single_values_expr
if insert_stmt._post_values_clause is not None:
from sqlalchemy import TypeDecorator
from sqlalchemy import util
from sqlalchemy.dialects.postgresql import base as postgresql
+from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.dialects.postgresql import psycopg2 as psycopg2_dialect
from sqlalchemy.dialects.postgresql.psycopg2 import EXECUTEMANY_BATCH
from sqlalchemy.dialects.postgresql.psycopg2 import EXECUTEMANY_PLAIN
Column(ue("\u6e2c\u8a66"), Integer),
)
- def test_insert(self, connection):
+ @testing.combinations(
+ "insert", "pg_insert", "pg_insert_on_conflict", argnames="insert_type"
+ )
+ def test_insert(self, connection, insert_type):
from psycopg2 import extras
values_page_size = connection.dialect.executemany_values_page_size
else:
assert False
+ if insert_type == "pg_insert_on_conflict":
+ stmt += " ON CONFLICT DO NOTHING"
+
with mock.patch.object(
extras, meth.__name__, side_effect=meth
) as mock_exec:
+ if insert_type == "insert":
+ ins_stmt = self.tables.data.insert()
+ elif insert_type == "pg_insert":
+ ins_stmt = pg_insert(self.tables.data)
+ elif insert_type == "pg_insert_on_conflict":
+ ins_stmt = pg_insert(self.tables.data).on_conflict_do_nothing()
+ else:
+ assert False
+
connection.execute(
- self.tables.data.insert(),
+ ins_stmt,
[
{"x": "x1", "y": "y1"},
{"x": "x2", "y": "y2"},