class NumberDumper(Dumper):
- _special: Dict[bytes, bytes] = {}
-
def dump(self, obj: Any) -> bytes:
return str(obj).encode("utf8")
+ def quote(self, obj: Any) -> bytes:
+ value = self.dump(obj)
+ return value if obj >= 0 else b" " + value
+
+
+class SpecialValuesDumper(NumberDumper):
+ _special: Dict[bytes, bytes] = {}
+
def quote(self, obj: Any) -> bytes:
value = self.dump(obj)
@Dumper.text(float)
-class FloatDumper(NumberDumper):
+class FloatDumper(SpecialValuesDumper):
oid = builtins["float8"].oid
_special = {
@Dumper.text(Decimal)
-class DecimalDumper(NumberDumper):
+class DecimalDumper(SpecialValuesDumper):
oid = builtins["numeric"].oid
_special = {
from cpython.bytes cimport PyBytes_AsStringAndSize
-from psycopg3_c.adapt cimport cloader_func, get_context_func
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.pq.enums import Format
logger = logging.getLogger("psycopg3.adapt")
+cdef class CDumper:
+ cdef object src
+ cdef object context
+ cdef object connection
+
+ def __init__(self, src: type, context: AdaptContext = None):
+ self.src = src
+ self.context = context
+ self.connection = _connection_from_context(context)
+
+ def dump(self, obj: Any) -> bytes:
+ raise NotImplementedError()
+
+ def quote(self, obj: Any) -> bytes:
+ # TODO: can be optimized
+ cdef bytes value = self.dump(obj)
+ cdef bytes tmp
+ cdef Escaping esc
+
+ if self.connection:
+ esc = Escaping(self.connection.pgconn)
+ return esc.escape_literal(value)
+
+ else:
+ esc = Escaping()
+ tmp = esc.escape_string(value)
+ return b"'%s'" % tmp
+
+ @property
+ def oid(self) -> int:
+ return 0
+
+ @classmethod
+ def register(
+ cls,
+ src: Union[type, str],
+ context: AdaptContext = None,
+ format: Format = Format.TEXT,
+ ) -> None:
+ if not isinstance(src, (str, type)):
+ raise TypeError(
+ f"dumpers should be registered on classes, got {src} instead"
+ )
+ from psycopg3.adapt import Dumper
+
+ where = context.dumpers if context else Dumper.globals
+ where[src, format] = cls
+
+ @classmethod
+ def register_binary(
+ cls, src: Union[type, str], context: AdaptContext = None
+ ) -> None:
+ cls.register(src, context, format=Format.BINARY)
+
+
cdef class CLoader:
cdef impl.Oid oid
cdef object context
cdef object connection
def __init__(self, oid: int, context: "AdaptContext" = None):
- from psycopg3.adapt import _connection_from_context
-
self.oid = oid
self.context = context
self.connection = _connection_from_context(context)
cls.register(oid, context, format=Format.BINARY)
+cdef _connection_from_context(object context):
+ from psycopg3.adapt import _connection_from_context
+ return _connection_from_context(context)
+
+
def register_builtin_c_adapters():
"""
Register all the builtin optimized adpaters.
@staticmethod
cdef PGcancel _from_ptr(impl.PGcancel *ptr)
+
+
+cdef class Escaping:
+ cdef PGconn conn
cdef class Escaping:
- cdef PGconn conn
-
def __init__(self, conn: Optional[PGconn] = None):
self.conn = conn
# Copyright (C) 2020 The Psycopg Team
from libc.stdint cimport *
-from psycopg3_c.endian cimport be16toh, be32toh, be64toh
+from psycopg3_c.endian cimport be16toh, be32toh, be64toh, htobe64
-from cpython.long cimport PyLong_FromString, PyLong_FromLong
+from cpython.long cimport PyLong_FromString, PyLong_FromLong, PyLong_AsLongLong
from cpython.long cimport PyLong_FromLongLong, PyLong_FromUnsignedLong
from cpython.float cimport PyFloat_FromDouble
-# work around https://github.com/cython/cython/issues/3909
cdef extern from "Python.h":
+ # work around https://github.com/cython/cython/issues/3909
double PyOS_string_to_double(
const char *s, char **endptr, object overflow_exception) except? -1.0
+ int PyOS_snprintf(char *str, size_t size, const char *format, ...)
+
+
+cdef class IntDumper(CDumper):
+ oid = 20 # TODO: int8 oid
+
+ def dump(self, obj: Any) -> bytes:
+ cdef char buf[22]
+ cdef long long val = PyLong_AsLongLong(obj)
+ cdef int written = PyOS_snprintf(buf, sizeof(buf), "%lld", val)
+ return buf[:written]
+
+ def quote(self, obj: Any) -> bytes:
+ cdef char buf[23]
+ cdef long long val = PyLong_AsLongLong(obj)
+ cdef int written
+ if val >= 0:
+ written = PyOS_snprintf(buf, sizeof(buf), "%lld", val)
+ else:
+ written = PyOS_snprintf(buf, sizeof(buf), " %lld", val)
+
+ return buf[:written]
+
+
+cdef class IntBinaryDumper(IntDumper):
+ def dump(self, obj: Any) -> bytes:
+ cdef long long val = PyLong_AsLongLong(obj)
+ cdef uint64_t *ptvar = <uint64_t *>(&val)
+ cdef int64_t beval = htobe64(ptvar[0])
+ cdef char *buf = <char *>&beval
+ return buf[:sizeof(beval)]
+
cdef class IntLoader(CLoader):
cdef object cload(self, const char *data, size_t length):
logger.debug("registering optimised numeric c adapters")
from psycopg3.oids import builtins
- from psycopg3.adapt import Loader
+
+ IntDumper.register(int)
+ IntBinaryDumper.register_binary(int)
IntLoader.register(builtins["int2"].oid)
IntLoader.register(builtins["int4"].oid)