MagicStack/asyncpg

`copy_records_to_table` uses named prepared statement even when statement_cache_size is 0

Closed this issue · 0 comments

We are using asyncpg with pgbouncer statement pooling mode. According to the FAQ https://magicstack.github.io/asyncpg/current/faq.html#why-am-i-getting-prepared-statement-errors , setting statement_cache_size=0 will be enough to disable the usage of named prepared statement. But we are still getting the error prepared statement “__asyncpg_stmt_xx__” already exists.

After some exploration, we find that copy_records_to_table is calling self._prepare with name=None, which eventually instructs self._get_statement to create a named prepared statement. As a result, a named prepared statement will be created with name __asyncpg_stmt_xx__, and other client application using asyncpg could try to create something with the same name, leading to the error.

The fix would be straightforward if I am not missing anything: #1218

Test set up:

  • asyncpg 0.30.0
  • pgbouncer 1.22.0
  • postgres 16.6

Simple script to reproduce:

import asyncpg
import asyncio
from asyncpg.connection import Connection

async def main():
    conn: Connection = await asyncpg.connect(
        host='localhost',
        port=6432,
        user='postgres',
        database='postgres',
        statement_cache_size=0,
        server_settings={'application_name': 'reproduce'}
    )

    try:
        # Create a sample table
        await conn.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER,
                name TEXT,
                email TEXT
            )
        ''')

        # Sample records to copy
        records = [
            (1, 'Alice', 'alice@example.com'),
            (2, 'Bob', 'bob@example.com'),
            (3, 'Charlie', 'charlie@example.com')
        ]

        # Copy records to the table
        result = await conn.copy_records_to_table(
            'users',
            records=records,
            columns=('id', 'name', 'email')
        )

        print(f"Copy operation completed: {result}")

    finally:
        await conn.close()

asyncio.run(main())