From: Daniele Varrazzo Date: Wed, 13 Oct 2021 23:26:09 +0000 (+0200) Subject: Don't use row dumpers to dump a sequence X-Git-Tag: 3.0.1~1^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db75f1cdddb70e2b90e714d31d12c1f2efedbeff;p=thirdparty%2Fpsycopg.git Don't use row dumpers to dump a sequence Using row dumpers uses the assumption that the types will be the same if we see the same query. It's easy to break this assumption. Being cheeky is a way (like passing arguments 1, '2' to the same query...) but it's easy to trigger the problem dumping numbers and going from in2-size to int4-size. This commit only fixes the Python implementation. Ref. bug #112 --- diff --git a/psycopg/psycopg/_transform.py b/psycopg/psycopg/_transform.py index cd87687b3..44a607ddc 100644 --- a/psycopg/psycopg/_transform.py +++ b/psycopg/psycopg/_transform.py @@ -68,7 +68,7 @@ class Transformer(AdaptContext): # mapping fmt, oid -> Loader instance self._loaders: Tuple[LoaderCache, LoaderCache] = ({}, {}) - self._row_dumpers: List[Optional["Dumper"]] = [] + self._row_dumpers: Optional[List["Dumper"]] = None # sequence of load functions from value to python # the length of the result columns @@ -139,51 +139,30 @@ class Transformer(AdaptContext): nparams = len(params) out: List[Optional[Buffer]] = [None] * nparams - change_state = False + # If we have dumpers, it means set_dumper_types had been called, in + # which case self.types and self.formats are set to sequences of the + # right size. + if self._row_dumpers: + for i in range(nparams): + param = params[i] + if param is not None: + out[i] = self._row_dumpers[i].dump(param) + return out - dumpers: List[Optional[Dumper]] = self._row_dumpers - types: Optional[List[int]] = None - pqformats: Optional[List[pq.Format]] = None - - # If we have dumpers, it means dump_sequnece or set_dumper_types were - # called already, in which case self.types and self.formats are set to - # sequences of the right size. We may change their contents if - # now we find a dumper we didn't have before, for instance because in - # an executemany the first records has a null, the second has a value. - if not dumpers: - change_state = True - dumpers = [None] * nparams - types = [INVALID_OID] * nparams - pqformats = [pq.Format.TEXT] * nparams + types = [INVALID_OID] * nparams + pqformats = [pq.Format.TEXT] * nparams for i in range(nparams): param = params[i] - if param is not None: - dumper = dumpers[i] - if not dumper: - change_state = True - dumper = dumpers[i] = self.get_dumper(param, formats[i]) - - if not types: - types = ( - list(self.types) - if self.types - else [INVALID_OID] * nparams - ) - types[i] = dumper.oid - - if not pqformats: - pqformats = self.formats or [pq.Format.TEXT] * nparams - pqformats[i] = dumper.format - - out[i] = dumper.dump(param) - - if change_state: - self._row_dumpers = dumpers - assert types is not None - self.types = tuple(types) - assert pqformats is not None - self.formats = pqformats + if param is None: + continue + dumper = self.get_dumper(param, formats[i]) + out[i] = dumper.dump(param) + types[i] = dumper.oid + pqformats[i] = dumper.format + + self.types = tuple(types) + self.formats = pqformats return out