# Copyright (C) 2020 The Psycopg Team
-from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
+from typing import Any, Dict, List, Optional, Sequence, Tuple
from typing import TYPE_CHECKING
from . import errors as e
fmt = result.fformat(i)
rc.append(self.get_loader(oid, fmt).load)
- def set_row_types(self, types: Iterable[Tuple[int, Format]]) -> None:
- rc = self._row_loaders = []
- for oid, fmt in types:
- rc.append(self.get_loader(oid, fmt).load)
+ def set_row_types(
+ self, types: Sequence[int], formats: Sequence[Format]
+ ) -> None:
+ rc: List[LoadFunc] = [None] * len(types) # type: ignore[list-item]
+ for i in range(len(rc)):
+ rc[i] = self.get_loader(types[i], formats[i]).load
+
+ self._row_loaders = rc
def get_dumper(self, obj: Any, format: Format) -> "Dumper":
# Fast path: return a Dumper class already instantiated from the same type
(CompositeLoader,),
{
"factory": factory,
- "fields_types": tuple(f.type_oid for f in self.fields),
+ "fields_types": [f.type_oid for f in self.fields],
},
)
loader.register(self.oid, context=context, format=Format.TEXT)
i += (8 + length) if length > 0 else 8
def _config_types(self, data: bytes) -> None:
- self._tx.set_row_types(
- [(oid, Format.BINARY) for oid, _, _ in self._walk_record(data)]
- )
+ oids = [r[0] for r in self._walk_record(data)]
+ self._tx.set_row_types(oids, [Format.BINARY] * len(oids))
class CompositeLoader(RecordLoader):
factory: Callable[..., Any]
- fields_types: Tuple[int, ...]
+ fields_types: List[int]
_types_set = False
def load(self, data: bytes) -> Any:
def _config_types(self, data: bytes) -> None:
self._tx.set_row_types(
- [(oid, Format.TEXT) for oid in self.fields_types]
+ self.fields_types, [Format.TEXT] * len(self.fields_types)
)
cdef readonly object connection
cdef readonly object adapters
- cdef dict _dumpers_cache, _loaders_cache
+ cdef dict _dumpers_cache
+ cdef dict _text_loaders
+ cdef dict _binary_loaders
cdef PGresult _pgresult
cdef int _nfields, _ntuples
cdef list _row_loaders
# mapping class, fmt -> Dumper instance
self._dumpers_cache: Dict[Tuple[type, Format], "Dumper"] = {}
- # mapping oid, fmt -> Loader instance
- self._loaders_cache: Dict[Tuple[int, Format], "Loader"] = {}
+ # mapping oid -> Loader instance (text, binary)
+ self._text_loaders = {}
+ self._binary_loaders = {}
self.pgresult = None
self._row_loaders = []
self._ntuples = libpq.PQntuples(res)
cdef int i
- types = [
- (libpq.PQftype(res, i), libpq.PQfformat(res, i))
- for i in range(self._nfields)]
- self.set_row_types(types)
-
- def set_row_types(self, types: Sequence[Tuple[int, Format]]) -> None:
- del self._row_loaders[:]
-
- cdef int i = 0
- cdef dict seen = {}
- for oid_fmt in types:
- if oid_fmt not in seen:
- self._row_loaders.append(
- self._get_row_loader(oid_fmt[0], oid_fmt[1]))
- seen[oid_fmt] = i
+ cdef list types = [None] * self._nfields
+ cdef list formats = [None] * self._nfields
+ for i in range(self._nfields):
+ types[i] = libpq.PQftype(res, i)
+ formats[i] = libpq.PQfformat(res, i)
+ self.set_row_types(types, formats)
+
+ def set_row_types(self, types: Sequence[int], formats: Sequence[Format]) -> None:
+ self._c_set_row_types(types, formats)
+
+ cdef void _c_set_row_types(self, list types, list formats):
+ cdef dict text_loaders = {}
+ cdef dict binary_loaders = {}
+ cdef list loaders = [None] * len(types)
+ cdef libpq.Oid oid
+ cdef int fmt
+ cdef int i
+ for i in range(len(types)):
+ oid = types[i]
+ fmt = formats[i]
+ if fmt == 0:
+ if oid in text_loaders:
+ loaders[i] = text_loaders[oid]
+ else:
+ loaders[i] = text_loaders[oid] \
+ = self._get_row_loader(oid, fmt)
else:
- self._row_loaders.append(self._row_loaders[seen[oid_fmt]])
+ if oid in binary_loaders:
+ loaders[i] = binary_loaders[oid]
+ else:
+ loaders[i] = binary_loaders[oid] \
+ = self._get_row_loader(oid, fmt)
- i += 1
+ self._row_loaders = loaders
cdef RowLoader _get_row_loader(self, libpq.Oid oid, int fmt):
cdef RowLoader row_loader = RowLoader()
- loader = self.get_loader(oid, fmt)
+ loader = self._c_get_loader(oid, fmt)
row_loader.pyloader = loader.load
if isinstance(loader, CLoader):
return rv
- def load_sequence(
- self, record: Sequence[Optional[bytes]]
- ) -> Tuple[Any, ...]:
- cdef list rv = []
- cdef int i
+ def load_sequence(self, record: Sequence[Optional[bytes]]) -> Tuple[Any, ...]:
+ cdef int length = len(record)
+ rv = PyTuple_New(length)
cdef RowLoader loader
- for i in range(len(record)):
+
+ cdef int i
+ for i in range(length):
item = record[i]
if item is None:
- rv.append(None)
+ pyval = None
else:
loader = self._row_loaders[i]
- rv.append(loader.pyloader(item))
+ pyval = loader.pyloader(item)
+ Py_INCREF(pyval)
+ PyTuple_SET_ITEM(rv, i, pyval)
- return tuple(rv)
+ return rv
def get_loader(self, oid: int, format: Format) -> "Loader":
- key = (oid, format)
- try:
- return self._loaders_cache[key]
- except KeyError:
- pass
+ return self._c_get_loader(oid, format)
+
+ cdef object _c_get_loader(self, libpq.Oid oid, int format):
+ cdef dict cache
+ if format == 0:
+ cache = self._text_loaders
+ else:
+ cache = self._binary_loaders
+
+ if oid in cache:
+ return cache[oid]
- loader_cls = self.adapters._loaders.get(key)
+ loader_cls = self.adapters._loaders.get((oid, format))
if loader_cls is None:
loader_cls = self.adapters._loaders[oids.INVALID_OID, format]
- loader = self._loaders_cache[key] = loader_cls(oid, self)
+ loader = cache[oid] = loader_cls(oid, self)
return loader