]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Cursor.callproc dropped
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 22 Nov 2020 17:28:36 +0000 (17:28 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 22 Nov 2020 17:28:36 +0000 (17:28 +0000)
The method has a simplistic semantic which doesn't account for
PostgreSQL positional parameters, procedures, set-returning functions.
Users can use a normal `Cursor.execute()` with SELECT
function_name(...)` or CALL procedure_name(...)` instead.

docs/from_pg2.rst
psycopg3/psycopg3/cursor.py
tests/test_cursor.py
tests/test_cursor_async.py

index 29eb3865fdc1fe301256e350c63325c4b7a81c2b..5a5f1f2ec78d8aca56f684b42845e41ea3b45434 100644 (file)
@@ -75,11 +75,17 @@ See :ref:`copy` for the details.
 Other differences
 -----------------
 
-When the connection is used as context manager, at the end of the context the
-connection will be closed. In psycopg2 only the transaction is closed, so a
-connection can be used in several contexts, but the behaviour is surprising
-for people used to several other Python classes wrapping resources, such as
-files.
+- When the connection is used as context manager, at the end of the context
+  the connection will be closed. In psycopg2 only the transaction is closed,
+  so a connection can be used in several contexts, but the behaviour is
+  surprising for people used to several other Python classes wrapping
+  resources, such as files.
+
+- `cursor.callproc()` is not implemented. The method has a simplistic
+  semantic which doesn't account for PostgreSQL positional parameters,
+  procedures, set-returning functions. Use a normal
+  `~psycopg3.Cursor.execute()` with :sql:`SELECT function_name(...)` or
+  :sql:`CALL procedure_name(...)` instead.
 
 
 What's new in psycopg3
index c08d1ed339a6a4357287f7b87582bc79098f07b0..4a96df26b17c54494b0c08de23953ab6de69548b 100644 (file)
@@ -7,13 +7,12 @@ psycopg3 cursor objects
 import sys
 from types import TracebackType
 from typing import Any, AsyncIterator, Callable, Generic, Iterator, List
-from typing import Mapping, Optional, Sequence, Type, TYPE_CHECKING, Union
+from typing import Optional, Sequence, Type, TYPE_CHECKING
 from operator import attrgetter
 from contextlib import contextmanager
 
 from . import errors as e
 from . import pq
-from . import sql
 from .oids import builtins
 from .copy import Copy, AsyncCopy
 from .proto import ConnectionType, Query, Params, DumpersMap, LoadersMap, PQGen
@@ -361,55 +360,6 @@ class BaseCursor(Generic[ConnectionType]):
                 "the last operation didn't produce a result"
             )
 
-    def _callproc_sql(
-        self,
-        name: Union[str, sql.Identifier],
-        args: Optional[Params] = None,
-        kwargs: Optional[Mapping[str, Any]] = None,
-    ) -> sql.Composable:
-        if args and not isinstance(args, (Sequence, Mapping)):
-            raise TypeError(
-                f"callproc args should be a sequence or a mapping,"
-                f" got {type(args).__name__}"
-            )
-        if isinstance(args, Mapping) and kwargs:
-            raise TypeError(
-                "callproc supports only one args sequence and one kwargs mapping"
-            )
-
-        if not kwargs and isinstance(args, Mapping):
-            kwargs = args
-            args = None
-
-        if kwargs and not isinstance(kwargs, Mapping):
-            raise TypeError(
-                f"callproc kwargs should be a mapping,"
-                f" got {type(kwargs).__name__}"
-            )
-
-        qparts: List[sql.Composable] = [
-            sql.SQL("select * from "),
-            name if isinstance(name, sql.Identifier) else sql.Identifier(name),
-            sql.SQL("("),
-        ]
-
-        if args:
-            for i, item in enumerate(args):
-                if i:
-                    qparts.append(sql.SQL(","))
-                qparts.append(sql.Literal(item))
-
-        if kwargs:
-            for i, (k, v) in enumerate(kwargs.items()):
-                if i:
-                    qparts.append(sql.SQL(","))
-                qparts.extend(
-                    [sql.Identifier(k), sql.SQL(":="), sql.Literal(v)]
-                )
-
-        qparts.append(sql.SQL(")"))
-        return sql.Composed(qparts)
-
     def _check_copy_results(self, results: Sequence["PGresult"]) -> None:
         """
         Check that the value returned in a copy() operation is a legit COPY.
@@ -495,18 +445,6 @@ class Cursor(BaseCursor["Connection"]):
 
         return self
 
-    def callproc(
-        self,
-        name: Union[str, sql.Identifier],
-        args: Optional[Params] = None,
-        kwargs: Optional[Mapping[str, Any]] = None,
-    ) -> Optional[Params]:
-        """
-        Call a stored procedure to the database.
-        """
-        self.execute(self._callproc_sql(name, args))
-        return args
-
     def fetchone(self) -> Optional[Sequence[Any]]:
         """
         Return the next record from the current recordset.
@@ -642,15 +580,6 @@ class AsyncCursor(BaseCursor["AsyncConnection"]):
 
         return self
 
-    async def callproc(
-        self,
-        name: Union[str, sql.Identifier],
-        args: Optional[Params] = None,
-        kwargs: Optional[Mapping[str, Any]] = None,
-    ) -> Optional[Params]:
-        await self.execute(self._callproc_sql(name, args, kwargs))
-        return args
-
     async def fetchone(self) -> Optional[Sequence[Any]]:
         self._check_result()
         rv = self._transformer.load_row(self._pos)
index 7b3adfedccc849e1981def5c07c6621a0192f27c..8a0829b4702fdf7ce43c3939709672343dbf4fa9 100644 (file)
@@ -1,10 +1,8 @@
 import gc
 import pytest
 import weakref
-from collections import namedtuple
 
 import psycopg3
-from psycopg3 import sql
 from psycopg3.oids import builtins
 
 
@@ -194,83 +192,6 @@ def test_executemany_badquery(conn, query):
         cur.executemany(query, [(10, "hello"), (20, "world")])
 
 
-def test_callproc_args(conn):
-    cur = conn.cursor()
-    cur.execute(
-        """
-        create function testfunc(a int, b text) returns text[] language sql as
-            'select array[$1::text, $2]'
-        """
-    )
-    assert cur.callproc("testfunc", [10, "twenty"]) == [10, "twenty"]
-    assert cur.fetchone() == (["10", "twenty"],)
-
-
-def test_callproc_badparam(conn):
-    cur = conn.cursor()
-    with pytest.raises(TypeError):
-        cur.callproc("lower", 42)
-    with pytest.raises(TypeError):
-        cur.callproc(42, ["lower"])
-
-
-def make_testfunc(conn):
-    # This parameter name tests for injection and quote escaping
-    paramname = """Robert'); drop table "students" --"""
-    procname = "randall"
-
-    # Set up the temporary function
-    stmt = (
-        sql.SQL(
-            """
-        create function {}({} numeric) returns numeric language sql as
-            'select $1 * $1'
-        """
-        )
-        .format(sql.Identifier(procname), sql.Identifier(paramname))
-        .as_string(conn)
-        .encode(conn.client_encoding)
-    )
-
-    # execute regardless of sync/async conn
-    conn.pgconn.exec_(stmt)
-
-    return namedtuple("Thang", "name, param")(procname, paramname)
-
-
-def test_callproc_dict(conn):
-
-    testfunc = make_testfunc(conn)
-    cur = conn.cursor()
-
-    cur.callproc(testfunc.name, [2])
-    assert cur.fetchone() == (4,)
-    cur.callproc(testfunc.name, {testfunc.param: 2})
-    assert cur.fetchone() == (4,)
-    cur.callproc(sql.Identifier(testfunc.name), {testfunc.param: 2})
-    assert cur.fetchone() == (4,)
-
-
-@pytest.mark.parametrize(
-    "args, exc",
-    [
-        ({"_p": 2, "foo": "bar"}, psycopg3.ProgrammingError),
-        ({"_p": "two"}, psycopg3.DataError),
-        ({"bj\xc3rn": 2}, psycopg3.ProgrammingError),
-        ({3: 2}, TypeError),
-        ({(): 2}, TypeError),
-    ],
-)
-def test_callproc_dict_bad(conn, args, exc):
-    testfunc = make_testfunc(conn)
-    if "_p" in args:
-        args[testfunc.param] = args.pop("_p")
-
-    cur = conn.cursor()
-    with pytest.raises(exc):
-        cur.callproc(testfunc.name, args)
-
-
 def test_rowcount(conn):
     cur = conn.cursor()
 
index 7ec01a67ec787d0f8d8755dad1d53667e3e80f2b..7603126daa626089b59082d9427428d387da6b3e 100644 (file)
@@ -3,9 +3,6 @@ import pytest
 import weakref
 
 import psycopg3
-from psycopg3 import sql
-
-from .test_cursor import make_testfunc
 
 pytestmark = pytest.mark.asyncio
 
@@ -196,59 +193,6 @@ async def test_executemany_badquery(aconn, query):
         await cur.executemany(query, [(10, "hello"), (20, "world")])
 
 
-async def test_callproc_args(aconn):
-    cur = await aconn.cursor()
-    await cur.execute(
-        """
-        create function testfunc(a int, b text) returns text[] language sql as
-            'select array[$1::text, $2]'
-        """
-    )
-    assert (await cur.callproc("testfunc", [10, "twenty"])) == [10, "twenty"]
-    assert (await cur.fetchone()) == (["10", "twenty"],)
-
-
-async def test_callproc_badparam(aconn):
-    cur = await aconn.cursor()
-    with pytest.raises(TypeError):
-        await cur.callproc("lower", 42)
-    with pytest.raises(TypeError):
-        await cur.callproc(42, ["lower"])
-
-
-async def test_callproc_dict(aconn):
-    testfunc = make_testfunc(aconn)
-
-    cur = await aconn.cursor()
-
-    await cur.callproc(testfunc.name, [2])
-    assert (await cur.fetchone()) == (4,)
-    await cur.callproc(testfunc.name, {testfunc.param: 2})
-    assert await (cur.fetchone()) == (4,)
-    await cur.callproc(sql.Identifier(testfunc.name), {testfunc.param: 2})
-    assert await (cur.fetchone()) == (4,)
-
-
-@pytest.mark.parametrize(
-    "args, exc",
-    [
-        ({"_p": 2, "foo": "bar"}, psycopg3.ProgrammingError),
-        ({"_p": "two"}, psycopg3.DataError),
-        ({"bj\xc3rn": 2}, psycopg3.ProgrammingError),
-        ({3: 2}, TypeError),
-        ({(): 2}, TypeError),
-    ],
-)
-async def test_callproc_dict_bad(aconn, args, exc):
-    testfunc = make_testfunc(aconn)
-    if "_p" in args:
-        args[testfunc.param] = args.pop("_p")
-
-    cur = await aconn.cursor()
-    with pytest.raises(exc):
-        await cur.callproc(testfunc.name, args)
-
-
 async def test_rowcount(aconn):
     cur = await aconn.cursor()