from ..oids import builtins, INVALID_OID
from ..adapt import Dumper, Loader
from ..proto import AdaptContext, EncodeFunc, DecodeFunc
+from ..errors import DataError
from ..pq import Escaping
if TYPE_CHECKING:
from ..pq.proto import Escaping as EscapingProto
-@Dumper.text(str)
-@Dumper.binary(str)
-class StringDumper(Dumper):
+class _StringDumper(Dumper):
def __init__(self, src: type, context: AdaptContext):
super().__init__(src, context)
else:
self._encode = codecs.lookup("utf8").encode
+
+@Dumper.binary(str)
+class StringBinaryDumper(_StringDumper):
def dump(self, obj: str) -> bytes:
return self._encode(obj)[0]
+@Dumper.text(str)
+class StringDumper(_StringDumper):
+ def dump(self, obj: str) -> bytes:
+ if "\x00" in obj:
+ raise DataError(
+ "PostgreSQL text fields cannot contain NUL (0x00) bytes"
+ )
+ else:
+ return self._encode(obj)[0]
+
+
@Loader.text(builtins["text"].oid)
@Loader.binary(builtins["text"].oid)
@Loader.text(builtins["varchar"].oid)
import pytest
-from psycopg3 import DatabaseError, sql
+import psycopg3
+from psycopg3 import sql
from psycopg3.adapt import Format
eur = "\u20ac"
assert cur.fetchone()[0] is True, chr(i)
+@pytest.mark.parametrize("fmt_in", [Format.TEXT, Format.BINARY])
+def test_dump_zero(conn, fmt_in):
+ cur = conn.cursor()
+ ph = "%s" if fmt_in == Format.TEXT else "%b"
+ s = "foo\x00bar"
+ with pytest.raises(psycopg3.DataError):
+ cur.execute(f"select {ph}", (s,))
+
+
+def test_quote_zero(conn):
+ cur = conn.cursor()
+ s = "foo\x00bar"
+ with pytest.raises(psycopg3.DataError):
+ cur.execute(sql.SQL("select {}").format(sql.Literal(s)))
+
+
# the only way to make this pass is to reduce %% -> % every time
# not only when there are query arguments
# see https://github.com/psycopg/psycopg2/issues/825
cur = conn.cursor(format=fmt_out)
conn.client_encoding = "latin1"
- with pytest.raises(DatabaseError):
+ with pytest.raises(psycopg3.DatabaseError):
cur.execute(f"select chr(%s::int)::{typename}", (ord(eur),))