From: Daniele Varrazzo Date: Mon, 16 Mar 2020 06:48:05 +0000 (+1300) Subject: Added prepared statements and description X-Git-Tag: 3.0.dev0~705 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ed7a7350747692ee2d092800443bcd92676abce;p=thirdparty%2Fpsycopg.git Added prepared statements and description --- diff --git a/psycopg3/pq/_pq_ctypes.py b/psycopg3/pq/_pq_ctypes.py index 64ed4d400..ebd320590 100644 --- a/psycopg3/pq/_pq_ctypes.py +++ b/psycopg3/pq/_pq_ctypes.py @@ -203,6 +203,30 @@ PQexecParams.argtypes = [ ] PQexecParams.restype = PGresult_ptr +PQprepare = pq.PQprepare +PQprepare.argtypes = [PGconn_ptr, c_char_p, c_char_p, c_int, POINTER(Oid)] +PQprepare.restype = PGresult_ptr + +PQexecPrepared = pq.PQexecPrepared +PQexecPrepared.argtypes = [ + PGconn_ptr, + c_char_p, + c_int, + POINTER(c_char_p), + POINTER(c_int), + POINTER(c_int), + c_int, +] +PQexecPrepared.restype = PGresult_ptr + +PQdescribePrepared = pq.PQdescribePrepared +PQdescribePrepared.argtypes = [PGconn_ptr, c_char_p] +PQdescribePrepared.restype = PGresult_ptr + +PQdescribePortal = pq.PQdescribePortal +PQdescribePortal.argtypes = [PGconn_ptr, c_char_p] +PQdescribePortal.restype = PGresult_ptr + PQresultStatus = pq.PQresultStatus PQresultStatus.argtypes = [PGresult_ptr] PQresultStatus.restype = c_int diff --git a/psycopg3/pq/pq_ctypes.py b/psycopg3/pq/pq_ctypes.py index d01edb31b..64032c09f 100644 --- a/psycopg3/pq/pq_ctypes.py +++ b/psycopg3/pq/pq_ctypes.py @@ -239,6 +239,82 @@ class PGconn: raise MemoryError("couldn't allocate PGresult") return PGresult(rv) + def prepare( + self, name, command, param_types=None, + ): + if not isinstance(name, bytes): + raise TypeError( + "'name' must be bytes, got %s instead" % type(name).__name__ + ) + + if not isinstance(command, bytes): + raise TypeError( + "'command' must be bytes, got %s instead" + % type(command).__name__ + ) + + if param_types is None: + nparams = 0 + atypes = None + else: + nparams = len(param_types) + atypes = (impl.Oid * nparams)(*param_types) + + rv = impl.PQprepare(self.pgconn_ptr, name, command, nparams, atypes,) + if rv is None: + raise MemoryError("couldn't allocate PGresult") + return PGresult(rv) + + def exec_prepared( + self, name, param_values, param_formats=None, result_format=0, + ): + if not isinstance(name, bytes): + raise TypeError( + "'name' must be bytes, got %s instead" % type(name).__name__ + ) + + nparams = len(param_values) + if nparams: + aparams = (c_char_p * nparams)(*param_values) + alenghts = (c_int * nparams)( + *(len(p) if p is not None else 0 for p in param_values) + ) + else: + aparams = alenghts = None + + if param_formats is None: + aformats = None + else: + if len(param_formats) != nparams: + raise ValueError( + "got %d param_values but %d param_types" + % (nparams, len(param_formats)) + ) + aformats = (c_int * nparams)(*param_formats) + + rv = impl.PQexecPrepared( + self.pgconn_ptr, + name, + nparams, + aparams, + alenghts, + aformats, + result_format, + ) + if rv is None: + raise MemoryError("couldn't allocate PGresult") + return PGresult(rv) + + def describe_prepared(self, name): + if not isinstance(name, bytes): + raise TypeError( + "'name' must be bytes, got %s instead" % type(name).__name__ + ) + rv = impl.PQdescribePrepared(self.pgconn_ptr, name) + if rv is None: + raise MemoryError("couldn't allocate PGresult") + return PGresult(rv) + class PGresult: __slots__ = ("pgresult_ptr",) @@ -310,6 +386,13 @@ class PGresult: else: return b"" + @property + def nparams(self): + return impl.PQnparams(self.pgresult_ptr) + + def param_type(self, param_number): + return impl.PQparamtype(self.pgresult_ptr, param_number) + ConninfoOption = namedtuple( "ConninfoOption", "keyword envvar compiled val label dispatcher dispsize" diff --git a/tests/pq/test_exec.py b/tests/pq/test_exec.py index cfbe6de2d..73d26db0e 100644 --- a/tests/pq/test_exec.py +++ b/tests/pq/test_exec.py @@ -70,3 +70,47 @@ def test_exec_params_binary_out(pq, pgconn, fmt, out): ) assert res.status == pq.ExecStatus.PGRES_TUPLES_OK assert res.get_value(0, 0) == out + + +def test_prepare(pq, pgconn): + res = pgconn.prepare(b"prep", b"select $1::int + $2::int") + assert res.status == pq.ExecStatus.PGRES_COMMAND_OK, res.error_message + + res = pgconn.exec_prepared(b"prep", [b"3", b"5"]) + assert res.get_value(0, 0) == b"8" + + +def test_prepare_types(pq, pgconn): + res = pgconn.prepare(b"prep", b"select $1 + $2", [23, 23]) + assert res.status == pq.ExecStatus.PGRES_COMMAND_OK, res.error_message + + res = pgconn.exec_prepared(b"prep", [b"3", b"5"]) + assert res.get_value(0, 0) == b"8" + + +def test_exec_prepared_binary_in(pq, pgconn): + val = b"foo\00bar" + res = pgconn.prepare(b"", b"select length($1::bytea), length($2::bytea)") + + res = pgconn.exec_prepared(b"", [val, val], param_formats=[0, 1]) + assert res.status == pq.ExecStatus.PGRES_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_exec_prepared_binary_out(pq, pgconn, fmt, out): + val = b"foo\00bar" + res = pgconn.prepare(b"", b"select $1::bytea") + assert res.status == pq.ExecStatus.PGRES_COMMAND_OK, res.error_message + + res = pgconn.exec_prepared( + b"", [val], param_formats=[1], result_format=fmt + ) + assert res.status == pq.ExecStatus.PGRES_TUPLES_OK + assert res.get_value(0, 0) == out diff --git a/tests/pq/test_pgresult.py b/tests/pq/test_pgresult.py index 530a9748b..e151d8799 100644 --- a/tests/pq/test_pgresult.py +++ b/tests/pq/test_pgresult.py @@ -111,3 +111,15 @@ def test_get_value(pq, pgconn): assert res.get_value(0, 0) == b"a" assert res.get_value(0, 1) == b"" assert res.get_value(0, 2) is None + + +def test_nparams_types(pq, pgconn): + res = pgconn.prepare(b"", b"select $1::int, $2::text") + assert res.status == pq.ExecStatus.PGRES_COMMAND_OK, res.error_message + + res = pgconn.describe_prepared(b"") + assert res.status == pq.ExecStatus.PGRES_COMMAND_OK, res.error_message + + assert res.nparams == 2 + assert res.param_type(0) == 23 + assert res.param_type(1) == 25