if self.prepare_threshold is None:
return None
+ # Check if we need to discard our entire state: it should happen on
+ # rollback or on dropping objects, because the same object may get
+ # recreated and postgres would fail internal lookups.
+ if self._prepared or prep == Prepare.SHOULD:
+ for result in results:
+ if result.status != ExecStatus.COMMAND_OK:
+ continue
+ cmdstat = result.command_status
+ if cmdstat and (
+ cmdstat.startswith(b"DROP ") or cmdstat == b"ROLLBACK"
+ ):
+ self._prepared.clear()
+ return b"DEALLOCATE ALL"
+
key = (query.query, query.types)
# If we know the query already the cache size won't change
# We cannot prepare a multiple statement
return None
- result = results[0]
- if (
- result.status != ExecStatus.TUPLES_OK
- and result.status != ExecStatus.COMMAND_OK
- ):
+ status = results[0].status
+ if ExecStatus.COMMAND_OK != status != ExecStatus.TUPLES_OK:
# We don't prepare failed queries or other weird results
return None
return b"DEALLOCATE " + old_val
else:
return None
+
+ def clear(self) -> Optional[bytes]:
+ if self._prepared_idx:
+ self._prepared.clear()
+ self._prepared_idx = 0
+ return b"DEALLOCATE ALL"
+ else:
+ return None
results = yield from execute(self._conn.pgconn)
# Update the prepare state of the query
- if prepare is not False:
- cmd = self._conn._prepared.maintain(pgq, results, prep, name)
- if cmd:
- yield from self._conn._exec_command(cmd)
+ # If an operation requires to flush our prepared statements cache,
+ # do it. Note that there is an off-by-one error because
+ cmd = self._conn._prepared.maintain(pgq, results, prep, name)
+ if cmd:
+ yield from self._conn._exec_command(cmd)
return results
cur = conn.execute("select parameter_types from pg_prepared_statements")
assert cur.fetchall() == [(["jsonb"],)]
+
+
+def test_change_type_execute(conn):
+ conn.prepare_threshold = 0
+ for i in range(3):
+ conn.execute("CREATE TYPE prepenum AS ENUM ('foo', 'bar', 'baz')")
+ conn.execute("CREATE TABLE preptable(id integer, bar prepenum[])")
+ conn.cursor().execute(
+ "INSERT INTO preptable (bar) VALUES (%(enum_col)s::prepenum[])",
+ {"enum_col": ["foo"]},
+ )
+ conn.rollback()
+
+
+def test_change_type_executemany(conn):
+ for i in range(3):
+ conn.execute("CREATE TYPE prepenum AS ENUM ('foo', 'bar', 'baz')")
+ conn.execute("CREATE TABLE preptable(id integer, bar prepenum[])")
+ conn.cursor().executemany(
+ "INSERT INTO preptable (bar) VALUES (%(enum_col)s::prepenum[])",
+ [{"enum_col": ["foo"]}, {"enum_col": ["foo", "bar"]}],
+ )
+ conn.rollback()
+
+
+def test_change_type(conn):
+ conn.prepare_threshold = 0
+ conn.execute("CREATE TYPE prepenum AS ENUM ('foo', 'bar', 'baz')")
+ conn.execute("CREATE TABLE preptable(id integer, bar prepenum[])")
+ conn.cursor().execute(
+ "INSERT INTO preptable (bar) VALUES (%(enum_col)s::prepenum[])",
+ {"enum_col": ["foo"]},
+ )
+ conn.execute("DROP TABLE preptable")
+ conn.execute("DROP TYPE prepenum")
+ conn.execute("CREATE TYPE prepenum AS ENUM ('foo', 'bar', 'baz')")
+ conn.execute("CREATE TABLE preptable(id integer, bar prepenum[])")
+ conn.cursor().execute(
+ "INSERT INTO preptable (bar) VALUES (%(enum_col)s::prepenum[])",
+ {"enum_col": ["foo"]},
+ )
+
+ cur = conn.execute(
+ "select count(*) from pg_prepared_statements", prepare=False
+ )
+ assert cur.fetchone()[0] == 3
+
+
+def test_change_type_savepoint(conn):
+ conn.prepare_threshold = 0
+ with conn.transaction():
+ for i in range(3):
+ with pytest.raises(ZeroDivisionError):
+ with conn.transaction():
+ conn.execute(
+ "CREATE TYPE prepenum AS ENUM ('foo', 'bar', 'baz')"
+ )
+ conn.execute(
+ "CREATE TABLE preptable(id integer, bar prepenum[])"
+ )
+ conn.cursor().execute(
+ "INSERT INTO preptable (bar) "
+ "VALUES (%(enum_col)s::prepenum[])",
+ {"enum_col": ["foo"]},
+ )
+ raise ZeroDivisionError()