Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

LoggingTransaction.executemany should have a proper type hint #15439

Open
DMRobertson opened this issue Apr 14, 2023 · 2 comments
Open

LoggingTransaction.executemany should have a proper type hint #15439

DMRobertson opened this issue Apr 14, 2023 · 2 comments
Labels
S-Minor Blocks non-critical functionality, workarounds exist. T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks.

Comments

@DMRobertson
Copy link
Contributor

executemany is also presumably vulnerable to the same kind of problem?

Originally posted by @DMRobertson in #15432 (comment)

The context here is that we committed a bug to develop that wasn't spotted by tests and wasn't spotted by linting, because the annotation for LoggingTransaction.execute wasn't strict enough. It looks like executemany has a similar problem.

@DMRobertson
Copy link
Contributor Author

Starting from the checkout of #15432, if I try to apply a better type hint

diff --git a/synapse/storage/database.py b/synapse/storage/database.py
index 1f5f5eb6f..e0080f468 100644
--- a/synapse/storage/database.py
+++ b/synapse/storage/database.py
@@ -37,6 +37,7 @@
     Union,
     cast,
     overload,
+    Sequence,
 )
 
 import attr
@@ -407,12 +408,14 @@ def execute_values(
     def execute(self, sql: str, parameters: SQLQueryParameters = ()) -> None:
         self._do_execute(self.txn.execute, sql, parameters)
 
-    def executemany(self, sql: str, *args: Any) -> None:
+    def executemany(
+        self, sql: str, parameter_groups: Sequence[SQLQueryParameters]
+    ) -> None:
         # TODO: we should add a type for *args here. Looking at Cursor.executemany
         # and DBAPI2 it ought to be Sequence[_Parameter], but we pass in
         # Iterable[Iterable[Any]] in execute_batch and execute_values above, which mypy
         # complains about.
-        self._do_execute(self.txn.executemany, sql, *args)
+        self._do_execute(self.txn.executemany, sql, parameter_groups)
 
     def executescript(self, sql: str) -> None:
         if isinstance(self.database_engine, Sqlite3Engine):

I see

$ mypy
synapse/storage/database.py:387: error: Argument 2 to "executemany" of "LoggingTransaction" has incompatible type "Iterable[Iterable[Any]]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/room.py:1108: error: Argument 2 to "executemany" of "LoggingTransaction" has incompatible type "Generator[Tuple[Optional[str], str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
Found 2 errors in 2 files (checked 795 source files)

so it looks like we're always passing generators around throughout Synapse.

The DBAPI2 says that executemany takes a sequence (rather than an iterable) of parameters, so I'm not sure if this is legit.

Having said that, even *args: Any to args: Any would be an improvement.

@DMRobertson
Copy link
Contributor Author

Trying to tighten the annotation on execute_batch

$ git --no-pager diff 
diff --git a/synapse/storage/database.py b/synapse/storage/database.py
index 1f5f5eb6f..7af51eb19 100644
--- a/synapse/storage/database.py
+++ b/synapse/storage/database.py
@@ -37,6 +37,7 @@
     Union,
     cast,
     overload,
+    Sequence,
 )
 
 import attr
@@ -361,7 +362,9 @@ def rowcount(self) -> int:
     def description(self) -> Any:
         return self.txn.description
 
-    def execute_batch(self, sql: str, args: Iterable[Iterable[Any]]) -> None:
+    def execute_batch(
+        self, sql: str, parameter_groups: Sequence[SQLQueryParameters]
+    ) -> None:
         """Similar to `executemany`, except `txn.rowcount` will not be correct
         afterwards.
 
@@ -375,7 +378,7 @@ def execute_batch(self, sql: str, args: Iterable[Iterable[Any]]) -> None:
             # https://www.psycopg.org/docs/extras.html?highlight=execute_batch#psycopg2.extras.execute_batch
             # suggests each arg in args should be a sequence or mapping
             self._do_execute(
-                lambda the_sql: execute_batch(self.txn, the_sql, args), sql
+                lambda the_sql: execute_batch(self.txn, the_sql, parameter_groups), sql
             )
         else:
             # TODO: is it safe for values to be Iterable[Iterable[Any]] here?
@@ -383,7 +386,7 @@ def execute_batch(self, sql: str, args: Iterable[Iterable[Any]]) -> None:
             # suggests that the outer collection may be iterable, but
             # https://docs.python.org/3/library/sqlite3.html?highlight=sqlite3#how-to-use-placeholders-to-bind-values-in-sql-queries
             # suggests that the inner collection should be a sequence or dict.
-            self.executemany(sql, args)
+            self.executemany(sql, parameter_groups)
 
     def execute_values(
         self, sql: str, values: Iterable[Iterable[Any]], fetch: bool = True
@@ -407,12 +410,14 @@ def execute_values(
     def execute(self, sql: str, parameters: SQLQueryParameters = ()) -> None:
         self._do_execute(self.txn.execute, sql, parameters)
 
-    def executemany(self, sql: str, *args: Any) -> None:
+    def executemany(
+        self, sql: str, parameter_groups: Sequence[SQLQueryParameters]
+    ) -> None:
         # TODO: we should add a type for *args here. Looking at Cursor.executemany
         # and DBAPI2 it ought to be Sequence[_Parameter], but we pass in
         # Iterable[Iterable[Any]] in execute_batch and execute_values above, which mypy
         # complains about.
-        self._do_execute(self.txn.executemany, sql, *args)
+        self._do_execute(self.txn.executemany, sql, parameter_groups)
 
     def executescript(self, sql: str) -> None:

throws more errors:

$ mypy
synapse/storage/database.py:1163: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Iterable[Iterable[Any]]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/media_repository.py:537: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[int, str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/media_repository.py:548: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[int, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/state/store.py:768: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[int], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/state/store.py:772: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[int], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/search.py:101: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[str, str, str, str, Optional[int], int], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/room.py:1108: error: Argument 2 to "executemany" of "LoggingTransaction" has incompatible type "Generator[Tuple[Optional[str], str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/events.py:1099: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[int, str, str, str, str, Optional[str], str, str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/events.py:1119: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[str, str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/events.py:1151: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[str, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/events.py:2200: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[str, Optional[int], int, str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/events.py:2215: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[str], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
synapse/storage/databases/main/devices.py:1328: error: Argument 2 to "execute_batch" of "LoggingTransaction" has incompatible type "Generator[Tuple[Any, Any], None, None]"; expected "Sequence[Union[Sequence[Any], Mapping[str, Any]]]"  [arg-type]
Found 13 errors in 7 files (checked 795 source files)

@reivilibre reivilibre added S-Minor Blocks non-critical functionality, workarounds exist. T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks. labels Apr 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
S-Minor Blocks non-critical functionality, workarounds exist. T-Task Refactoring, removal, replacement, enabling or disabling functionality, other engineering tasks.
Projects
None yet
Development

No branches or pull requests

2 participants