From: Daniele Varrazzo Date: Fri, 8 Jan 2021 00:55:26 +0000 (+0100) Subject: Added Transformer.dump_sequence() X-Git-Tag: 3.0.dev0~198 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af092d2af6b1f678c7366551f514fe694bb9d3c5;p=thirdparty%2Fpsycopg.git Added Transformer.dump_sequence() --- diff --git a/psycopg3/psycopg3/_queries.py b/psycopg3/psycopg3/_queries.py index 55d234fac..76ccb1ac1 100644 --- a/psycopg3/psycopg3/_queries.py +++ b/psycopg3/psycopg3/_queries.py @@ -12,7 +12,6 @@ from functools import lru_cache from . import errors as e from .pq import Format from .sql import Composable -from .oids import TEXT_OID, INVALID_OID from .proto import Query, Params if TYPE_CHECKING: @@ -43,7 +42,6 @@ class PostgresQuery: self.types: Tuple[int, ...] = () self.formats: Optional[List[Format]] = None - self._unknown_oid = INVALID_OID self._parts: List[QueryPart] self.query = b"" self._encoding = "utf-8" @@ -52,8 +50,6 @@ class PostgresQuery: conn = transformer.connection if conn: self._encoding = conn.client_encoding - if conn.pgconn.server_version < 100000: - self._unknown_oid = TEXT_OID def convert(self, query: Query, vars: Optional[Params]) -> None: """ @@ -88,16 +84,9 @@ class PostgresQuery: self._parts, vars, self._order ) assert self.formats is not None - ps: List[Optional[bytes]] = [None] * len(params) - ts = [self._unknown_oid] * len(params) - for i in range(len(params)): - param = params[i] - if param is not None: - dumper = self._tx.get_dumper(param, self.formats[i]) - ps[i] = dumper.dump(param) - ts[i] = dumper.oid - self.params = ps - self.types = tuple(ts) + self.params, self.types = self._tx.dump_sequence( + params, self.formats + ) else: self.params = None self.types = () diff --git a/psycopg3/psycopg3/_transform.py b/psycopg3/psycopg3/_transform.py index 4926a0558..1cdea90d0 100644 --- a/psycopg3/psycopg3/_transform.py +++ b/psycopg3/psycopg3/_transform.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING from . import errors as e from .pq import Format -from .oids import INVALID_OID +from .oids import INVALID_OID, TEXT_OID from .proto import LoadFunc, AdaptContext if TYPE_CHECKING: @@ -32,11 +32,16 @@ class Transformer(AdaptContext): _pgresult: Optional["PGresult"] = None def __init__(self, context: Optional[AdaptContext] = None): + self._unknown_oid = INVALID_OID + # WARNING: don't store context, or you'll create a loop with the Cursor if context: self._adapters = context.adapters - self._connection = context.connection + conn = self._connection = context.connection + # PG 9.6 gives an error if an unknown oid is emitted as column + if conn and conn.pgconn.server_version < 100000: + self._unknown_oid = TEXT_OID else: from .adapt import global_adapters @@ -95,6 +100,20 @@ class Transformer(AdaptContext): self._row_loaders = rc + def dump_sequence( + self, params: Sequence[Any], formats: Sequence[Format] + ) -> Tuple[List[Any], Tuple[int, ...]]: + ps: List[Optional[bytes]] = [None] * len(params) + ts = [self._unknown_oid] * len(params) + for i in range(len(params)): + param = params[i] + if param is not None: + dumper = self.get_dumper(param, formats[i]) + ps[i] = dumper.dump(param) + ts[i] = dumper.oid + + return ps, tuple(ts) + def get_dumper(self, obj: Any, format: Format) -> "Dumper": # Fast path: return a Dumper class already instantiated from the same type cls = type(obj) diff --git a/psycopg3/psycopg3/proto.py b/psycopg3/psycopg3/proto.py index f7a717297..f0a5a4c3e 100644 --- a/psycopg3/psycopg3/proto.py +++ b/psycopg3/psycopg3/proto.py @@ -5,7 +5,7 @@ Protocol objects representing different implementations of the same classes. # Copyright (C) 2020 The Psycopg Team from typing import Any, Callable, Generator, Mapping -from typing import Optional, Sequence, Tuple, TypeVar, Union +from typing import List, Optional, Sequence, Tuple, TypeVar, Union from typing import TYPE_CHECKING from typing_extensions import Protocol @@ -91,6 +91,11 @@ class Transformer(Protocol): ) -> None: ... + def dump_sequence( + self, params: Sequence[Any], formats: Sequence[Format] + ) -> Tuple[List[Any], Tuple[int, ...]]: + ... + def get_dumper(self, obj: Any, format: Format) -> "Dumper": ... diff --git a/psycopg3_c/psycopg3_c/_psycopg3.pyi b/psycopg3_c/psycopg3_c/_psycopg3.pyi index 167d2560f..9e24e0b83 100644 --- a/psycopg3_c/psycopg3_c/_psycopg3.pyi +++ b/psycopg3_c/psycopg3_c/_psycopg3.pyi @@ -28,6 +28,9 @@ class Transformer(proto.AdaptContext): def set_row_types( self, types: Sequence[int], formats: Sequence[Format] ) -> None: ... + def dump_sequence( + self, params: Sequence[Any], formats: Sequence[Format] + ) -> Tuple[List[Any], Tuple[int, ...]]: ... def get_dumper(self, obj: Any, format: Format) -> Dumper: ... def load_rows(self, row0: int, row1: int) -> Sequence[Tuple[Any, ...]]: ... def load_row(self, row: int) -> Optional[Tuple[Any, ...]]: ... diff --git a/psycopg3_c/psycopg3_c/_psycopg3/transform.pyx b/psycopg3_c/psycopg3_c/_psycopg3/transform.pyx index 57e8dc4a6..4b9c62e43 100644 --- a/psycopg3_c/psycopg3_c/_psycopg3/transform.pyx +++ b/psycopg3_c/psycopg3_c/_psycopg3/transform.pyx @@ -58,11 +58,17 @@ cdef class Transformer: cdef pq.PGresult _pgresult cdef int _nfields, _ntuples cdef list _row_loaders + cdef int _unknown_oid def __cinit__(self, context: Optional["AdaptContext"] = None): + self._unknown_oid = oids.INVALID_OID if context is not None: self.adapters = context.adapters self.connection = context.connection + + # PG 9.6 gives an error if an unknown oid is emitted as column + if self.connection and self.connection.pgconn.server_version < 100000: + self._unknown_oid = oids.TEXT_OID else: from psycopg3.adapt import global_adapters self.adapters = global_adapters @@ -183,6 +189,39 @@ cdef class Transformer: f"cannot adapt type {cls.__name__} to format {Format(format).name}" ) + cpdef dump_sequence(self, object params, object formats): + # Verify that they are not none and that PyList_GET_ITEM won't blow up + cdef int nparams = len(params) + cdef list ps = PyList_New(nparams) + cdef tuple ts = PyTuple_New(nparams) + cdef object dumped, oid + cdef Py_ssize_t size + + cdef int i + for i in range(nparams): + param = params[i] + if param is not None: + format = formats[i] + dumper = self.get_dumper(param, format) + if isinstance(dumper, CDumper): + dumped = PyByteArray_FromStringAndSize("", 0) + size = (dumper).cdump(param, dumped, 0) + PyByteArray_Resize(dumped, size) + oid = (dumper).oid + else: + dumped = dumper.dump(param) + oid = dumper.oid + else: + dumped = None + oid = self._unknown_oid + + Py_INCREF(dumped) + PyList_SET_ITEM(ps, i, dumped) + Py_INCREF(oid) + PyTuple_SET_ITEM(ts, i, oid) + + return ps, ts + def load_rows(self, int row0, int row1) -> Sequence[Tuple[Any, ...]]: if self._pgresult is None: raise e.InterfaceError("result not set")