Skip to content

Triple nested custom types cause binary dumper to use wrong dumper for nested objects #547

@exrok

Description

@exrok

Hello, again :). With binary loading fixed in #545 🎉, next up is... binary dumping.
(aside: I suspect since the binary protocol isn't default, it just doesn't get tested as much.)

Observations

I have only observed this issue with the binary protocol, text protocol seems fine.

When serializing something of the form: Composite( Array[ Composite( Enum ) ] ),
The stack trace shows

  • Composite Dumper abc.CollectionBinaryDumper
  • Array Dumper abc.ItemListBinaryDumper
  • Composite Dumper abc.ItemBinaryDumper
  • Array Dumper abc.ItemListBinaryDumper (Expected Enum Dumper)

And the following error is thrown due to the enum, Kind, being passed to the array dumper.

File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 270, in dump_list
if len(L) != dims[dim]:
TypeError: object of type 'Kind' has no len()

Note: this can occur without an array.

In a custom BinaryDumper, adding self._tx.set_dumper_types(self.info.field_types, self.format) to
the top of the dump method seemed to workaround the problem.
However, the fix was only intermittent, sometimes still failing.

Env

  • Python: v3.10
  • OS: Arch Linux
  • DB: Postgres 15.2
  • psycopg 3.1.8 both with and without binary extra
  • Don't know if it is used but I have libpq.so.5.15 on my system.

Reproduction

SQL to initialize the state.

DROP SCHEMA IF EXISTS test CASCADE;
CREATE SCHEMA IF NOT EXISTS test;

CREATE TYPE test.kind AS ENUM('A', 'B');

CREATE TYPE test.item AS (kind test.Kind, time timestamptz);

CREATE TYPE test.collection AS (items test.item[]);

Python to trigger the problem.

import psycopg
from enum import Enum, auto
from datetime import datetime, timezone
from psycopg.types.enum import EnumInfo, register_enum
from psycopg.types.composite import CompositeInfo, register_composite


class Kind(Enum):
    A = auto()
    B = auto()


with psycopg.connect("host=localhost user=postgres password=postgres") as conn:
    info = CompositeInfo.fetch(conn, "test.collection")
    register_composite(info, conn)
    Collection = info.python_type

    info = CompositeInfo.fetch(conn, "test.item")
    register_composite(info, conn)
    Item = info.python_type

    info = EnumInfo.fetch(conn, "test.kind")
    register_enum(info, conn, Kind)

    # Successful: Array of Items
    print(
        conn.execute(
            "SELECT %b",
            [[Item(kind=Kind.A, time=datetime.now(tz=timezone.utc))]],
        ).fetchall()
    )

    # Successful: Collection with empty array
    print(
        conn.execute(
            "SELECT %b",
            [Collection(items=[])],
        ).fetchall()
    )

    # Failure: Collection with non-empty array
    print(
        conn.execute(
            "SELECT %b",
            [Collection(items=[Item(kind=Kind.A, time=datetime.now(tz=timezone.utc))])],
        ).fetchall()
    )

Error Traceback Logs

Traceback when using psycopg[binary,pool] version 3.1.8
Traceback (most recent call last):
  File "/home/user/bugs/python/src/repro.py", line 43, in <module>
    conn.execute(
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/connection.py", line 876, in execute
    return cur.execute(query, params, prepare=prepare)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 719, in execute
    self._conn.wait(
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/connection.py", line 957, in wait
    return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
  File "psycopg_binary/_psycopg/waiting.pyx", line 172, in psycopg_binary._psycopg.wait_c
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 195, in _execute_gen
    pgq = self._convert_query(query, params)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 468, in _convert_query
    pgq.convert(query, params)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_queries.py", line 80, in convert
    self.dump(vars)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_queries.py", line 91, in dump
    self.params = self._tx.dump_sequence(params, self._want_formats)
  File "psycopg_binary/_psycopg/transform.pyx", line 353, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "psycopg_binary/_psycopg/transform.pyx", line 404, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/composite.py", line 85, in dump
    adapted = self._tx.dump_sequence(obj, self._formats)
  File "psycopg_binary/_psycopg/transform.pyx", line 353, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "psycopg_binary/_psycopg/transform.pyx", line 379, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 289, in dump
    dump_list(obj, 0)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 277, in dump_list
    ad = self.sub_dumper.dump(item)  # type: ignore[union-attr]
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/composite.py", line 85, in dump
    adapted = self._tx.dump_sequence(obj, self._formats)
  File "psycopg_binary/_psycopg/transform.pyx", line 353, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "psycopg_binary/_psycopg/transform.pyx", line 379, in psycopg_binary._psycopg.Transformer.dump_sequence
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 289, in dump
    dump_list(obj, 0)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 270, in dump_list
    if len(L) != dims[dim]:
TypeError: object of type 'Kind' has no len()
Traceback when using only psycopg[pool] version 3.1.8
Traceback (most recent call last):
  File "/home/user/bugs/python/src/repro.py", line 43, in <module>
    conn.execute(
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/connection.py", line 876, in execute
    return cur.execute(query, params, prepare=prepare)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 719, in execute
    self._conn.wait(
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/connection.py", line 957, in wait
    return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/waiting.py", line 214, in wait_select
    s = next(gen)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 195, in _execute_gen
    pgq = self._convert_query(query, params)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/cursor.py", line 468, in _convert_query
    pgq.convert(query, params)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_queries.py", line 80, in convert
    self.dump(vars)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_queries.py", line 91, in dump
    self.params = self._tx.dump_sequence(params, self._want_formats)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_transform.py", line 188, in dump_sequence
    out[i] = dumper.dump(param)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/composite.py", line 85, in dump
    adapted = self._tx.dump_sequence(obj, self._formats)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_transform.py", line 177, in dump_sequence
    out[i] = self._row_dumpers[i].dump(param)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 289, in dump
    dump_list(obj, 0)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 277, in dump_list
    ad = self.sub_dumper.dump(item)  # type: ignore[union-attr]
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/composite.py", line 85, in dump
    adapted = self._tx.dump_sequence(obj, self._formats)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/_transform.py", line 177, in dump_sequence
    out[i] = self._row_dumpers[i].dump(param)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 289, in dump
    dump_list(obj, 0)
  File "/home/user/bugs/python/.venv/lib/python3.10/site-packages/psycopg/types/array.py", line 270, in dump_list
    if len(L) != dims[dim]:
TypeError: object of type 'Kind' has no len()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions