From: Daniele Varrazzo Date: Sat, 11 Apr 2020 15:54:59 +0000 (+1200) Subject: Added async query prepare functions in pq wrapper X-Git-Tag: 3.0.dev0~567 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6da3201e4061312edbf51da248be567fb093cb6a;p=thirdparty%2Fpsycopg.git Added async query prepare functions in pq wrapper --- diff --git a/psycopg3/pq/_pq_ctypes.py b/psycopg3/pq/_pq_ctypes.py index 0d232466d..b29218977 100644 --- a/psycopg3/pq/_pq_ctypes.py +++ b/psycopg3/pq/_pq_ctypes.py @@ -407,8 +407,23 @@ PQsendQueryParams.argtypes = [ ] PQsendQueryParams.restype = c_int -# TODO: PQsendPrepare PQsendQueryPrepared -# PQsendDescribePrepared PQsendDescribePortal +PQsendPrepare = pq.PQsendPrepare +PQsendPrepare.argtypes = [PGconn_ptr, c_char_p, c_char_p, c_int, POINTER(Oid)] +PQsendPrepare.restype = c_int + +PQsendQueryPrepared = pq.PQsendQueryPrepared +PQsendQueryPrepared.argtypes = [ + PGconn_ptr, + c_char_p, + c_int, + POINTER(c_char_p), + POINTER(c_int), + POINTER(c_int), + c_int, +] +PQsendQueryPrepared.restype = c_int + +# TODO: PQsendDescribePrepared PQsendDescribePortal PQgetResult = pq.PQgetResult PQgetResult.argtypes = [PGconn_ptr] diff --git a/psycopg3/pq/_pq_ctypes.pyi b/psycopg3/pq/_pq_ctypes.pyi index a47e4530e..1a976fabd 100644 --- a/psycopg3/pq/_pq_ctypes.pyi +++ b/psycopg3/pq/_pq_ctypes.pyi @@ -45,6 +45,22 @@ def PQgetvalue( arg1: Optional[PGresult_struct], arg2: int, arg3: int ) -> pointer[c_char]: ... def PQcmdTuples(arg1: Optional[PGresult_struct]) -> bytes: ... +def PQsendPrepare( + arg1: Optional[PGconn_struct], + arg2: bytes, + arg3: bytes, + arg4: int, + arg5: Optional[Array[c_uint]], +) -> int: ... +def PQsendQueryPrepared( + arg1: Optional[PGconn_struct], + arg2: bytes, + arg3: int, + arg4: Optional[Array[c_char_p]], + arg5: Optional[Array[c_int]], + arg6: Optional[Array[c_int]], + arg7: int, +) -> int: ... # fmt: off # autogenerated: start diff --git a/psycopg3/pq/pq_ctypes.py b/psycopg3/pq/pq_ctypes.py index af0df71b9..f36cf9f34 100644 --- a/psycopg3/pq/pq_ctypes.py +++ b/psycopg3/pq/pq_ctypes.py @@ -232,6 +232,48 @@ class PGconn: f"sending query and params failed: {error_message(self)}" ) + def send_prepare( + self, + name: bytes, + command: bytes, + param_types: Optional[Sequence[int]] = None, + ) -> None: + atypes: Optional[Array[impl.Oid]] + if param_types is None: + nparams = 0 + atypes = None + else: + nparams = len(param_types) + atypes = (impl.Oid * nparams)(*param_types) + + self._ensure_pgconn() + if not impl.PQsendPrepare( + self.pgconn_ptr, name, command, nparams, atypes + ): + raise PQerror( + f"sending query and params failed: {error_message(self)}" + ) + + def send_query_prepared( + self, + name: bytes, + param_values: Sequence[Optional[bytes]], + param_formats: Optional[Sequence[Format]] = None, + result_format: Format = Format.TEXT, + ) -> None: + # repurpose this function with a cheeky replacement of query with name, + # drop the param_types from the result + args = self._query_params_args( + name, param_values, None, param_formats, result_format + ) + args = args[:3] + args[4:] + + self._ensure_pgconn() + if not impl.PQsendQueryPrepared(*args): + raise PQerror( + f"sending prepared query failed: {error_message(self)}" + ) + def _query_params_args( self, command: bytes, diff --git a/tests/pq/test_async.py b/tests/pq/test_async.py index 7cedc9574..969e406c7 100644 --- a/tests/pq/test_async.py +++ b/tests/pq/test_async.py @@ -77,7 +77,7 @@ def test_send_query_compact_test(pq, pgconn): def test_send_query_params(pq, pgconn): - res = pgconn.send_query_params(b"select $1::int + $2", [b"5", b"3"]) + pgconn.send_query_params(b"select $1::int + $2", [b"5", b"3"]) (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) assert res.status == pq.ExecStatus.TUPLES_OK assert res.get_value(0, 0) == b"8" @@ -85,3 +85,62 @@ def test_send_query_params(pq, pgconn): pgconn.finish() with pytest.raises(psycopg3.OperationalError): pgconn.send_query_params(b"select $1", [b"1"]) + + +def test_send_prepare(pq, pgconn): + pgconn.send_prepare(b"prep", b"select $1::int + $2::int") + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message + + pgconn.send_query_prepared(b"prep", [b"3", b"5"]) + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.get_value(0, 0) == b"8" + + pgconn.finish() + with pytest.raises(psycopg3.OperationalError): + pgconn.send_prepare(b"prep", b"select $1::int + $2::int") + with pytest.raises(psycopg3.OperationalError): + pgconn.send_query_prepared(b"prep", [b"3", b"5"]) + + +def test_send_prepare_types(pq, pgconn): + pgconn.send_prepare(b"prep", b"select $1 + $2", [23, 23]) + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message + + pgconn.send_query_prepared(b"prep", [b"3", b"5"]) + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.get_value(0, 0) == b"8" + + +def test_send_prepared_binary_in(pq, pgconn): + val = b"foo\00bar" + pgconn.send_prepare(b"", b"select length($1::bytea), length($2::bytea)") + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message + + pgconn.send_query_prepared(b"", [val, val], param_formats=[0, 1]) + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.TUPLES_OK + assert res.get_value(0, 0) == b"3" + assert res.get_value(0, 1) == b"7" + + with pytest.raises(ValueError): + pgconn.exec_params(b"select $1::bytea", [val], param_formats=[1, 1]) + + +@pytest.mark.parametrize( + "fmt, out", [(0, b"\\x666f6f00626172"), (1, b"foo\00bar")] +) +def test_send_prepared_binary_out(pq, pgconn, fmt, out): + val = b"foo\00bar" + pgconn.send_prepare(b"", b"select $1::bytea") + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message + + pgconn.send_query_prepared( + b"", [val], param_formats=[1], result_format=fmt + ) + (res,) = psycopg3.waiting.wait(psycopg3.generators.execute(pgconn)) + assert res.status == pq.ExecStatus.TUPLES_OK + assert res.get_value(0, 0) == out diff --git a/tests/pq/test_exec.py b/tests/pq/test_exec.py index d575d6b06..f090e0afd 100644 --- a/tests/pq/test_exec.py +++ b/tests/pq/test_exec.py @@ -107,6 +107,7 @@ def test_prepare_types(pq, pgconn): def test_exec_prepared_binary_in(pq, pgconn): val = b"foo\00bar" res = pgconn.prepare(b"", b"select length($1::bytea), length($2::bytea)") + assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message res = pgconn.exec_prepared(b"", [val, val], param_formats=[0, 1]) assert res.status == pq.ExecStatus.TUPLES_OK