]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Reduce re-allocations in objects quoting
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 17 Dec 2020 12:55:56 +0000 (13:55 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 17 Dec 2020 16:29:19 +0000 (17:29 +0100)
psycopg3/psycopg3/adapt.py
psycopg3/psycopg3/proto.py
psycopg3_c/psycopg3_c/adapt.pyx
psycopg3_c/psycopg3_c/pq_cython.pxd
psycopg3_c/psycopg3_c/pq_cython.pyx

index 03eca5904e90492fc383f3181c3a84756e02b7da..55dfbded4ff622705cf751415a53bf15ca3356f8 100644 (file)
@@ -36,6 +36,8 @@ class Dumper(ABC):
         """Convert the object *obj* to PostgreSQL representation."""
         ...
 
+    # TODO: the protocol signature should probably return a Buffer like object
+    # (the C implementation may return bytearray)
     def quote(self, obj: Any) -> bytes:
         """Convert the object *obj* to escaped representation."""
         value = self.dump(obj)
index 328f716bce11a17bdaeb3ab41ccfa2cb798e355d..3deb72dc27f331acee47d345fe84bfa072aef817 100644 (file)
@@ -53,6 +53,9 @@ LoadFunc = Callable[[bytes], Any]
 LoaderType = Type["Loader"]
 LoadersMap = Dict[Tuple[int, Format], LoaderType]
 
+# TODO: Loader, Dumper should probably become protocols
+# as there are both C and a Python implementation
+
 
 class Transformer(Protocol):
     def __init__(self, context: AdaptContext = None):
index 303265478a47aaac5476d33f6572bb1919d1f9d6..62c9281260dbfc60b6812290e8d8e1134a20c550 100644 (file)
@@ -16,12 +16,16 @@ equivalent C implementations.
 from typing import Any
 
 from cpython.bytes cimport PyBytes_AsStringAndSize
+from cpython.bytearray cimport PyByteArray_FromStringAndSize, PyByteArray_Resize
+from cpython.bytearray cimport PyByteArray_AS_STRING
 
 from psycopg3_c cimport libpq as impl
 from psycopg3_c.adapt cimport cloader_func, get_context_func
-from psycopg3_c.pq_cython cimport Escaping
+from psycopg3_c.pq_cython cimport Escaping, _buffer_as_string_and_size
 
+from psycopg3 import errors as e
 from psycopg3.pq import Format
+from psycopg3.pq.misc import error_message
 
 import logging
 logger = logging.getLogger("psycopg3.adapt")
@@ -56,27 +60,38 @@ cdef class CDumper:
     def dump(self, obj: Any) -> bytes:
         raise NotImplementedError()
 
-    def quote(self, obj: Any) -> bytes:
-        # TODO: can be optimized
-        cdef object ovalue = self.dump(obj)
-
-        cdef bytes value
-        if isinstance(ovalue, bytes):
-            value = ovalue
+    def quote(self, obj: Any) -> bytearray:
+        cdef char *ptr
+        cdef char *ptr_out
+        cdef Py_ssize_t length, len_out
+        cdef int error
+        cdef bytearray rv
+
+        pyout = self.dump(obj)
+        _buffer_as_string_and_size(pyout, &ptr, &length)
+        rv = PyByteArray_FromStringAndSize("", 0)
+        PyByteArray_Resize(rv, length * 2 + 3)  # Must include the quotes
+        ptr_out = PyByteArray_AS_STRING(rv)
+
+        if self._pgconn is not None:
+            if self._pgconn.pgconn_ptr == NULL:
+                raise e.OperationalError("the connection is closed")
+
+            len_out = impl.PQescapeStringConn(
+                self._pgconn.pgconn_ptr, ptr_out + 1, ptr, length, &error
+            )
+            if error:
+                raise e.OperationalError(
+                    f"escape_string failed: {error_message(self.connection)}"
+                )
         else:
-            value = bytes(ovalue)
+            len_out = impl.PQescapeString(ptr_out + 1, ptr, length)
 
-        cdef bytes tmp
-        cdef Escaping esc
+        ptr_out[0] = b'\''
+        ptr_out[len_out + 1] = b'\''
+        PyByteArray_Resize(rv, len_out + 2)
 
-        if self.connection:
-            esc = Escaping(self._pgconn)
-            return bytes(esc.escape_literal(value))
-
-        else:
-            esc = Escaping()
-            tmp = bytes(esc.escape_string(value))
-            return b"'%s'" % tmp
+        return rv
 
     @property
     def oid(self) -> int:
index faeb46094137210847ae0275a907a235e0ea98a4..b6537409e817ff3e47aaca73cee1a9a332faa293 100644 (file)
@@ -44,3 +44,8 @@ cdef class PQBuffer:
 
     @staticmethod
     cdef PQBuffer _from_buffer(unsigned char *buf, Py_ssize_t len)
+
+
+cdef int _buffer_as_string_and_size(
+    data: "Buffer", char **ptr, Py_ssize_t *length
+) except -1
index a86e540aab0c9781de8e8f9dfb1c478c9126c525..fac39a5b2638de1ba1d91b7e00ab760e6f6cbee9 100644 (file)
@@ -962,7 +962,9 @@ cdef class PQBuffer:
         pass
 
 
-cdef int _buffer_as_string_and_size(data: "Buffer", char **ptr, Py_ssize_t *length) except -1:
+cdef int _buffer_as_string_and_size(
+    data: "Buffer", char **ptr, Py_ssize_t *length
+) except -1:
     cdef Py_buffer buf
 
     if isinstance(data, bytes):