]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Added Cursor.query and params.
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 23 Nov 2020 08:44:46 +0000 (08:44 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 23 Nov 2020 08:44:46 +0000 (08:44 +0000)
docs/cursor.rst
psycopg3/psycopg3/cursor.py
tests/test_copy.py
tests/test_copy_async.py
tests/test_cursor.py
tests/test_cursor_async.py

index 245afeeda68c226b226e720f4514792547ae2ee9..0aaecf1c9566d43d235c42ee4f1aa4b9bc2e5149 100644 (file)
@@ -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
 ------------------------
index f077c244a8e51877de4f9acc090c3ba005196a69..8cf5b68878afcc9241cbe53089004f426723e9fe 100644 (file)
@@ -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,
index a38d156fb3f4946dceb846116e588301161e0db8..8beec2f103bf5b6517083d09349cafa837e0f136 100644 (file)
@@ -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})")
index da491a4bb9dfe98af6493378cb560c49b230627c..3bbd8f25f73cd97e97cc8bcb7b84e289b2fc41cf 100644 (file)
@@ -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})")
index 8a0829b4702fdf7ce43c3939709672343dbf4fa9..24d4e4004eef36857f344032cf1d302b91c78654 100644 (file)
@@ -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()
index 7603126daa626089b59082d9427428d387da6b3e..9a1644f4bbc46293c75eb76c70038cb84699838e 100644 (file)
@@ -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)")