]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add mapping oid -> Dumper in AdaptersMap
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 27 Aug 2021 00:17:53 +0000 (02:17 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 27 Aug 2021 22:23:47 +0000 (00:23 +0200)
psycopg/psycopg/__init__.py
psycopg/psycopg/_adapters_map.py
psycopg/psycopg/abc.py
psycopg/psycopg/types/datetime.py
psycopg/psycopg/types/numeric.py
psycopg/psycopg/types/string.py
tests/fix_psycopg.py

index 18894ccb3baf240e135b2b3e8f5ed3fb88f62fed..acc4fbb7059b36afde3b31d042c39c0420c2ef4b 100644 (file)
@@ -37,7 +37,6 @@ if logger.level == logging.NOTSET:
 
 # register default adapters for PostgreSQL
 adapters = postgres.adapters  # exposed by the package
-postgres.register_default_adapters(adapters)
 
 # DBAPI compliancy
 connect = Connection.connect
@@ -47,6 +46,8 @@ paramstyle = "pyformat"
 adapters.register_dumper(Binary, BinaryTextDumper)  # dbapi20
 adapters.register_dumper(Binary, BinaryBinaryDumper)  # dbapi20
 
+# After registering the dbapi20 dumpers to clobber the oid they set
+postgres.register_default_adapters(adapters)
 
 # Note: defining the exported methods helps both Sphynx in documenting that
 # this is the canonical place to obtain them and should be used by MyPy too,
index b572e7818f2d23d755e3fced094d0802e2ca2a58..d0ddb3cafc68dd8dbf2ca5aba3b3f7c3c6edbf2e 100644 (file)
@@ -57,6 +57,7 @@ class AdaptersMap:
     types: TypesRegistry
 
     _dumpers: Dict[PyFormat, Dict[Union[type, str], Type[Dumper]]]
+    _dumpers_by_oid: List[Dict[int, Type[Dumper]]]
     _loaders: List[Dict[int, Type[Loader]]]
 
     # Record if a dumper or loader has an optimised version.
@@ -71,15 +72,27 @@ class AdaptersMap:
             self._dumpers = template._dumpers.copy()
             self._own_dumpers = _dumpers_shared.copy()
             template._own_dumpers = _dumpers_shared.copy()
+
+            self._dumpers_by_oid = template._dumpers_by_oid[:]
+            self._own_dumpers_by_oid = [False, False]
+            template._own_dumpers_by_oid = [False, False]
+
             self._loaders = template._loaders[:]
             self._own_loaders = [False, False]
             template._own_loaders = [False, False]
+
             self.types = TypesRegistry(template.types)
+
         else:
             self._dumpers = {fmt: {} for fmt in PyFormat}
             self._own_dumpers = _dumpers_owned.copy()
+
+            self._dumpers_by_oid = [{}, {}]
+            self._own_dumpers_by_oid = [True, True]
+
             self._loaders = [{}, {}]
             self._own_loaders = [True, True]
+
             self.types = types or TypesRegistry()
 
     # implement the AdaptContext protocol too
@@ -127,6 +140,16 @@ class AdaptersMap:
 
             self._dumpers[fmt][cls] = dumper
 
+        # Register the dumper by oid, if the oid of the dumper is fixed
+        if dumper.oid:
+            if not self._own_dumpers_by_oid[dumper.format]:
+                self._dumpers_by_oid[dumper.format] = self._dumpers_by_oid[
+                    dumper.format
+                ].copy()
+                self._own_dumpers_by_oid[dumper.format] = True
+
+            self._dumpers_by_oid[dumper.format][dumper.oid] = dumper
+
     def register_loader(
         self, oid: Union[int, str], loader: Type["Loader"]
     ) -> None:
index fffc5ba4d8beb4a0d9dd1ccaa7eeb3ab79a3fde4..cc7d72572234c4c4f6e026799e925d10299bfb49 100644 (file)
@@ -87,8 +87,10 @@ class Dumper(Protocol):
     """
 
     format: pq.Format
+    """The format this dumper produces (class attirbute)."""
+
     oid: int
-    """The oid to pass to the server, if known; 0 otherwise."""
+    """The oid to pass to the server, if known; 0 otherwise (class attribute)."""
 
     def __init__(self, cls: type, context: Optional[AdaptContext] = None):
         ...
index f423d50885d93cd98d91e90611d45359f134da04..f501fbd388793f899143bd49850f9b3c290f51c8 100644 (file)
@@ -770,10 +770,20 @@ def register_default_adapters(context: AdaptContext) -> None:
     adapters = context.adapters
     adapters.register_dumper("datetime.date", DateDumper)
     adapters.register_dumper("datetime.date", DateBinaryDumper)
+
+    # first register dumpers for 'timetz' oid, then the proper ones on time type.
+    adapters.register_dumper("datetime.time", TimeTzDumper)
+    adapters.register_dumper("datetime.time", TimeTzBinaryDumper)
     adapters.register_dumper("datetime.time", TimeDumper)
     adapters.register_dumper("datetime.time", TimeBinaryDumper)
+
+    # first register dumpers for 'timestamp' oid, then the proper ones
+    # on the datetime type.
+    adapters.register_dumper("datetime.datetime", DatetimeNoTzDumper)
+    adapters.register_dumper("datetime.datetime", DatetimeNoTzBinaryDumper)
     adapters.register_dumper("datetime.datetime", DatetimeDumper)
     adapters.register_dumper("datetime.datetime", DatetimeBinaryDumper)
+
     adapters.register_dumper("datetime.timedelta", TimedeltaDumper)
     adapters.register_dumper("datetime.timedelta", TimedeltaBinaryDumper)
     adapters.register_loader("date", DateLoader)
index 0b126132c8237109d47827e9228a8f5b6ae089f6..c11828e773757ba42078eb963a360b4bcd7e942c 100644 (file)
@@ -454,15 +454,18 @@ def register_default_adapters(context: AdaptContext) -> None:
     adapters.register_dumper(int, IntBinaryDumper)
     adapters.register_dumper(float, FloatDumper)
     adapters.register_dumper(float, FloatBinaryDumper)
-    # The binary dumper is currently some 30% slower, so default to text
-    # (see tests/scripts/testdec.py for a rough benchmark)
-    adapters.register_dumper("decimal.Decimal", DecimalBinaryDumper)
-    adapters.register_dumper("decimal.Decimal", DecimalDumper)
     adapters.register_dumper(Int2, Int2Dumper)
     adapters.register_dumper(Int4, Int4Dumper)
     adapters.register_dumper(Int8, Int8Dumper)
     adapters.register_dumper(IntNumeric, IntNumericDumper)
     adapters.register_dumper(Oid, OidDumper)
+
+    # The binary dumper is currently some 30% slower, so default to text
+    # (see tests/scripts/testdec.py for a rough benchmark)
+    # Also, must be after IntNumericDumper
+    adapters.register_dumper("decimal.Decimal", DecimalBinaryDumper)
+    adapters.register_dumper("decimal.Decimal", DecimalDumper)
+
     adapters.register_dumper(Float4, Float4Dumper)
     adapters.register_dumper(Float8, FloatDumper)
     adapters.register_dumper(Int2, Int2BinaryDumper)
index f256689566395971457d4ceeb71ae608a2186963..a65fd3fbdd9caa73555dfc756ba959bf77bacda0 100644 (file)
@@ -185,11 +185,14 @@ class ByteaBinaryLoader(Loader):
 def register_default_adapters(context: AdaptContext) -> None:
     adapters = context.adapters
 
-    # NOTE: the order the dumpers are registered is relevant.
-    # The last one registered becomes the default for each type.
-    # Normally, binary is the default dumper, except for text (which plays
-    # the role of unknown, so it can be cast automatically to other types).
+    # NOTE: the order the dumpers are registered is relevant. The last one
+    # registered becomes the default for each type. Usually, binary is the
+    # default dumper. For text we use the text dumper as default because it
+    # plays the role of unknown, and it can be cast automatically to other
+    # types. However, before that, we register a dumper with the text oid,
+    # which will be used when a text dumper is looked up by oid.
     adapters.register_dumper(str, StrBinaryDumper)
+    adapters.register_dumper(str, StrDumper)
     adapters.register_dumper(str, StrDumperUnknown)
 
     adapters.register_loader(postgres.INVALID_OID, TextLoader)
index 74b82b3cbc503973fc82166bea9fc4d930d8438e..f036c9857073397fd46a131a872ab5d45585f59b 100644 (file)
@@ -9,12 +9,14 @@ def global_adapters():
     from psycopg import adapters
 
     dumpers = deepcopy(adapters._dumpers)
+    dumpers_by_oid = deepcopy(adapters._dumpers_by_oid)
     loaders = deepcopy(adapters._loaders)
     types = list(adapters.types)
 
     yield None
 
     adapters._dumpers = dumpers
+    adapters._dumpers_by_oid = dumpers_by_oid
     adapters._loaders = loaders
     adapters.types.clear()
     for t in types: