import datetime as dt
from types import ModuleType
-from typing import Any, List
+from typing import Any, List, Optional
import pytest
import psycopg
from psycopg import pq, sql, postgres
from psycopg import errors as e
+from psycopg.abc import Buffer
from psycopg.adapt import Transformer, PyFormat, Dumper, Loader
from psycopg._cmodule import _psycopg
from psycopg.postgres import types as builtins
from psycopg.types.array import ListDumper, ListBinaryDumper
+from psycopg.types.string import StrDumper, StrBinaryDumper
@pytest.mark.parametrize(
assert dumper.quote(data) == result
+@pytest.mark.parametrize(
+ "data, result",
+ [
+ ("hello", b"'hello'"),
+ ("", b"NULL"),
+ ],
+)
+def test_quote_none(data, result, global_adapters):
+ psycopg.adapters.register_dumper(str, StrNoneDumper)
+ t = Transformer()
+ dumper = t.get_dumper(data, PyFormat.TEXT)
+ assert dumper.quote(data) == result
+
+
def test_register_dumper_by_class(conn):
dumper = make_dumper("x")
assert conn.adapters.get_dumper(MyStr, PyFormat.TEXT) is not dumper
def test_subclass_dumper(conn):
- # This might be a C fast object: make sure that the Python code is called
- from psycopg.types.string import StrDumper
-
class MyStrDumper(StrDumper):
def dump(self, obj):
return (obj * 2).encode()
assert cur.execute("select %s", ["hello"]).fetchone()[0] == "hellob"
-@pytest.mark.parametrize("fmt_in", [PyFormat.TEXT, PyFormat.BINARY])
+@pytest.mark.parametrize("fmt_in", PyFormat)
def test_none_type_argument(conn, fmt_in):
cur = conn.cursor()
cur.execute("create table none_args (id serial primary key, num integer)")
- cur.execute("insert into none_args (num) values (%s) returning id", (None,))
+ cur.execute(
+ f"insert into none_args (num) values (%{fmt_in.value}) returning id", (None,)
+ )
assert cur.fetchone()[0]
+@pytest.mark.parametrize("fmt_in", [PyFormat.TEXT, PyFormat.BINARY])
+def test_dump_to_none(conn, fmt_in):
+ cur = conn.cursor()
+ dumper = StrNoneDumper if fmt_in == PyFormat.TEXT else StrNoneBinaryDumper
+ cur.adapters.register_dumper(str, dumper)
+ cur.execute("create table none_args (id serial primary key, data text)")
+ for s in ["foo", ""]:
+ cur.execute("insert into none_args (data) values (%s)", (s,))
+ cur.execute("select data from none_args order by id")
+ assert cur.fetchall() == [("foo",), (None,)]
+
+
@pytest.mark.crdb("skip", reason="test in crdb test suite")
@pytest.mark.parametrize("fmt_in", PyFormat)
def test_return_untyped(conn, fmt_in):
pass
+class StrNoneDumper(StrDumper):
+ def dump(self, obj: str) -> Optional[Buffer]:
+ return super().dump(obj) if obj else None
+
+
+class StrNoneBinaryDumper(StrBinaryDumper):
+ def dump(self, obj: str) -> Optional[Buffer]:
+ return super().dump(obj) if obj else None
+
+
def make_dumper(suffix):
"""Create a test dumper appending a suffix to the bytes representation."""
from psycopg.postgres import types as builtins
from psycopg.types.array import register_array
+from ..test_adapt import StrNoneDumper, StrNoneBinaryDumper
tests_str = [
([[[[[["a"]]]]]], "{{{{{{a}}}}}}"),
assert cur.fetchone()[0]
+@pytest.mark.parametrize("fmt_in", PyFormat)
+def test_dump_list_str_none(conn, fmt_in):
+ cur = conn.cursor()
+ cur.adapters.register_dumper(str, StrNoneDumper)
+ cur.adapters.register_dumper(str, StrNoneBinaryDumper)
+
+ cur.execute(f"select %{fmt_in.value}::text[]", (["foo", "", "bar"],))
+ assert cur.fetchone()[0] == ["foo", None, "bar"]
+
+
@pytest.mark.parametrize("fmt_out", pq.Format)
def test_load_empty_list_str(conn, fmt_out):
cur = conn.cursor(binary=fmt_out)
import enum
-from decimal import Decimal
from math import isnan, isinf, exp
+from typing import Optional
+from decimal import Decimal
import pytest
import psycopg
from psycopg import pq
from psycopg import sql
+from psycopg.abc import Buffer
from psycopg.adapt import Transformer, PyFormat
-from psycopg.types.numeric import FloatLoader
+from psycopg.types.numeric import Int8, Int8Dumper, Int8BinaryDumper, FloatLoader
from ..fix_crdb import is_crdb
assert ok
+@pytest.mark.parametrize("fmt_in", [PyFormat.TEXT, PyFormat.BINARY])
+def test_int_none(conn, fmt_in):
+ Base: type = Int8Dumper if fmt_in == PyFormat.TEXT else Int8BinaryDumper
+
+ class MyDumper(Base): # type: ignore
+ def dump(self, obj: int) -> Optional[Buffer]:
+ if not obj:
+ return None
+ else:
+ return super().dump(obj) # type: ignore
+
+ conn.adapters.register_dumper(Int8, MyDumper)
+ cur = conn.execute("select %s, %s", [Int8(0), Int8(1)])
+ assert cur.fetchone() == (None, 1)
+
+
class MyEnum(enum.IntEnum):
foo = 42