from ..pq.abc import Escaping as EscapingProto
-class _StrDumper(Dumper):
+class _BaseStrDumper(Dumper):
_encoding = "utf-8"
self._encoding = enc
-class StrBinaryDumper(_StrDumper):
+class StrBinaryDumper(_BaseStrDumper):
format = Format.BINARY
_oid = postgres.types["text"].oid
return obj.encode(self._encoding)
-class StrDumper(_StrDumper):
+class _StrDumper(_BaseStrDumper):
format = Format.TEXT
return obj.encode(self._encoding)
+class StrDumper(_StrDumper):
+ """
+ Dumper for strings in text format to the text oid.
+
+ Note that this dumper is not used by deafult because the type is too strict
+ and PostgreSQL would require an explicit casts to everything that is not a
+ text field. However it is useful where the unknown oid is ambiguous and the
+ text oid is required, for instance with variadic functions.
+ """
+
+ _oid = postgres.types["text"].oid
+
+
+class StrDumperUnknown(_StrDumper):
+ """
+ Dumper for strings in text format to the unknown oid.
+
+ This dumper is the default dumper for strings and allows to use Python
+ strings to represent almost every data type. In a few places, however, the
+ unknown oid is not accepted (for instance in variadic functions such as
+ 'concat()'). In that case either a cast on the placeholder ('%s::text) or
+ the StrTextDumper should be used.
+ """
+
+ pass
+
+
class TextLoader(Loader):
format = Format.TEXT
# Normally, binary is the default dumper, except for text (which plays
# the role of unknown, so it can be cast automatically to other types).
adapters.register_dumper(str, StrBinaryDumper)
- adapters.register_dumper(str, StrDumper)
+ adapters.register_dumper(str, StrDumperUnknown)
adapters.register_loader(postgres.INVALID_OID, TextLoader)
adapters.register_loader("bpchar", TextLoader)
adapters.register_loader("name", TextLoader)
const char *PyUnicode_AsUTF8AndSize(unicode obj, Py_ssize_t *size) except NULL
-cdef class _StrDumper(CDumper):
+cdef class _BaseStrDumper(CDumper):
cdef int is_utf8
cdef char *encoding
cdef bytes _bytes_encoding # needed to keep `encoding` alive
@cython.final
-cdef class StrBinaryDumper(_StrDumper):
+cdef class StrBinaryDumper(_BaseStrDumper):
format = PQ_BINARY
self.oid = oids.TEXT_OID
-@cython.final
-cdef class StrDumper(_StrDumper):
+cdef class _StrDumper(_BaseStrDumper):
format = PQ_TEXT
return size
+@cython.final
+cdef class StrDumper(_StrDumper):
+
+ def __cinit__(self):
+ self.oid = oids.TEXT_OID
+
+
+@cython.final
+cdef class StrDumperUnknown(_StrDumper):
+ pass
+
+
cdef class _TextLoader(CLoader):
format = PQ_TEXT
import psycopg
from psycopg import pq, sql, postgres
+from psycopg import errors as e
from psycopg.adapt import Transformer, PyFormat as Format, Dumper, Loader
from psycopg._cmodule import _psycopg
from psycopg.postgres import types as builtins, TEXT_OID
# Currently string are passed as unknown oid to libpq. This is because
# unknown is more easily cast by postgres to different types (see jsonb
# later).
- cur.execute("select %s, %s", ["hello", 10])
+ cur.execute(f"select %{fmt_in}, %{fmt_in}", ["hello", 10])
assert cur.fetchone() == ("hello", 10)
cur.execute("create table testjson(data jsonb)")
- cur.execute("insert into testjson (data) values (%s)", ["{}"])
- assert cur.execute("select data from testjson").fetchone() == ({},)
+ if fmt_in != Format.BINARY:
+ cur.execute(f"insert into testjson (data) values (%{fmt_in})", ["{}"])
+ assert cur.execute("select data from testjson").fetchone() == ({},)
+ else:
+ # Binary types cannot be passed as unknown oids.
+ with pytest.raises(e.DatatypeMismatch):
+ cur.execute(
+ f"insert into testjson (data) values (%{fmt_in})", ["{}"]
+ )
@pytest.mark.parametrize("fmt_in", [Format.AUTO, Format.TEXT, Format.BINARY])
import psycopg
from psycopg import pq
from psycopg import sql
+from psycopg import errors as e
from psycopg.adapt import PyFormat as Format
from psycopg import Binary
assert res == "foo"
+@pytest.mark.parametrize("fmt_in", [Format.AUTO, Format.TEXT])
+def test_dump_text_oid(conn, fmt_in):
+ conn.autocommit = True
+
+ with pytest.raises(e.IndeterminateDatatype):
+ conn.execute(f"select concat(%{fmt_in}, %{fmt_in})", ["foo", "bar"])
+ conn.adapters.register_dumper(str, psycopg.types.string.StrDumper)
+ cur = conn.execute(f"select concat(%{fmt_in}, %{fmt_in})", ["foo", "bar"])
+ assert cur.fetchone()[0] == "foobar"
+
+
@pytest.mark.parametrize("fmt_out", [pq.Format.TEXT, pq.Format.BINARY])
@pytest.mark.parametrize("encoding", ["utf8", "latin9"])
@pytest.mark.parametrize("typename", ["text", "varchar", "name", "bpchar"])