From: Daniele Varrazzo Date: Mon, 23 Nov 2020 08:44:46 +0000 (+0000) Subject: Added Cursor.query and params. X-Git-Tag: 3.0.dev0~322 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f31978bd1874fbdd219c60351f8c08374393a901;p=thirdparty%2Fpsycopg.git Added Cursor.query and params. --- diff --git a/docs/cursor.rst b/docs/cursor.rst index 245afeeda..0aaecf1c9 100644 --- a/docs/cursor.rst +++ b/docs/cursor.rst @@ -110,6 +110,18 @@ The `!Cursor` class .. autoattribute:: rowcount :annotation: int + .. autoattribute:: query + :annotation: Optional[bytes] + + The query will be in PostgreSQL format (with ``$1``, ``$2``... + parameters), the parameters will *not* be merged to the query: see + `params`. + + .. autoattribute:: params + :annotation: Optional[List[Optional[bytes]]] + + The parameters are adapted to PostgreSQL format. + The `!AsyncCursor` class ------------------------ diff --git a/psycopg3/psycopg3/cursor.py b/psycopg3/psycopg3/cursor.py index f077c244a..8cf5b6887 100644 --- a/psycopg3/psycopg3/cursor.py +++ b/psycopg3/psycopg3/cursor.py @@ -182,6 +182,8 @@ class BaseCursor(Generic[ConnectionType]): self._pos = 0 self._iresult = 0 self._rowcount = -1 + self._query: Optional[bytes] = None + self._params: Optional[List[Optional[bytes]]] = None @property def connection(self) -> ConnectionType: @@ -199,6 +201,16 @@ class BaseCursor(Generic[ConnectionType]): res = self.pgresult return res.status if res else None + @property + def query(self) -> Optional[bytes]: + """The last query sent to the server, if available.""" + return self._query + + @property + def params(self) -> Optional[List[Optional[bytes]]]: + """The last set of parameters sent to the server, if available.""" + return self._params + @property def pgresult(self) -> Optional["PGresult"]: """The `~psycopg3.pq.PGresult` exposed by the cursor.""" @@ -281,6 +293,8 @@ class BaseCursor(Generic[ConnectionType]): pgq.convert(query, vars) if pgq.params or no_pqexec or self.format == pq.Format.BINARY: + self._query = pgq.query + self._params = pgq.params self._conn.pgconn.send_query_params( pgq.query, pgq.params, @@ -291,6 +305,8 @@ class BaseCursor(Generic[ConnectionType]): else: # if we don't have to, let's use exec_ as it can run more than # one query in one go + self._query = pgq.query + self._params = None self._conn.pgconn.send_query(pgq.query) def _execute_results(self, results: Sequence["PGresult"]) -> None: @@ -339,11 +355,13 @@ class BaseCursor(Generic[ConnectionType]): pgq = PostgresQuery(self._transformer) pgq.convert(query, vars) + self._query = pgq.query self._conn.pgconn.send_prepare(name, pgq.query, param_types=pgq.types) return pgq def _send_query_prepared(self, name: bytes, pgq: PostgresQuery) -> None: + self._params = pgq.params self._conn.pgconn.send_query_prepared( name, pgq.params, diff --git a/tests/test_copy.py b/tests/test_copy.py index a38d156fb..8beec2f10 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -291,6 +291,14 @@ def test_copy_rowcount(conn): assert cur.rowcount == -1 +def test_copy_query(conn): + cur = conn.cursor() + with cur.copy("copy (select 1) to stdout") as copy: + assert cur.query == b"copy (select 1) to stdout" + assert cur.params is None + list(copy) + + def ensure_table(cur, tabledef, name="copy_in"): cur.execute(f"drop table if exists {name}") cur.execute(f"create table {name} ({tabledef})") diff --git a/tests/test_copy_async.py b/tests/test_copy_async.py index da491a4bb..3bbd8f25f 100644 --- a/tests/test_copy_async.py +++ b/tests/test_copy_async.py @@ -279,6 +279,15 @@ async def test_copy_rowcount(aconn): assert cur.rowcount == -1 +async def test_copy_query(aconn): + cur = await aconn.cursor() + async with cur.copy("copy (select 1) to stdout") as copy: + assert cur.query == b"copy (select 1) to stdout" + assert cur.params is None + async for record in copy: + pass + + async def ensure_table(cur, tabledef, name="copy_in"): await cur.execute(f"drop table if exists {name}") await cur.execute(f"create table {name} ({tabledef})") diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 8a0829b47..24d4e4004 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -234,6 +234,39 @@ def test_iter_stop(conn): assert list(cur) == [] +def test_query_params_execute(conn): + cur = conn.cursor() + assert cur.query is None + assert cur.params is None + + cur.execute("select %s, %s", [1, None]) + assert cur.query == b"select $1, $2" + assert cur.params == [b"1", None] + + cur.execute("select 1") + assert cur.query == b"select 1" + assert cur.params is None + + with pytest.raises(psycopg3.DataError): + cur.execute("select %s::int", ["wat"]) + + assert cur.query == b"select $1::int" + assert cur.params == [b"wat"] + + +def test_query_params_executemany(conn): + cur = conn.cursor() + + cur.executemany("select %s, %s", [[1, 2], [3, 4]]) + assert cur.query == b"select $1, $2" + assert cur.params == [b"3", b"4"] + + with pytest.raises(psycopg3.DataError): + cur.executemany("select %s::int", [[1], ["x"], [2]]) + assert cur.query == b"select $1::int" + assert cur.params == [b"x"] + + class TestColumn: def test_description_attribs(self, conn): curs = conn.cursor() diff --git a/tests/test_cursor_async.py b/tests/test_cursor_async.py index 7603126da..9a1644f4b 100644 --- a/tests/test_cursor_async.py +++ b/tests/test_cursor_async.py @@ -222,6 +222,39 @@ async def test_iter(aconn): assert res == [(1,), (2,), (3,)] +async def test_query_params_execute(aconn): + cur = await aconn.cursor() + assert cur.query is None + assert cur.params is None + + await cur.execute("select %s, %s", [1, None]) + assert cur.query == b"select $1, $2" + assert cur.params == [b"1", None] + + await cur.execute("select 1") + assert cur.query == b"select 1" + assert cur.params is None + + with pytest.raises(psycopg3.DataError): + await cur.execute("select %s::int", ["wat"]) + + assert cur.query == b"select $1::int" + assert cur.params == [b"wat"] + + +async def test_query_params_executemany(aconn): + cur = await aconn.cursor() + + await cur.executemany("select %s, %s", [[1, 2], [3, 4]]) + assert cur.query == b"select $1, $2" + assert cur.params == [b"3", b"4"] + + with pytest.raises(psycopg3.DataError): + await cur.executemany("select %s::int", [[1], ["x"], [2]]) + assert cur.query == b"select $1::int" + assert cur.params == [b"x"] + + async def test_iter_stop(aconn): cur = await aconn.cursor() await cur.execute("select generate_series(1, 3)")