From bfad36a9b5bb6f3265347da414f38171412a02e1 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 22 Jan 2024 16:12:41 +0100 Subject: [PATCH 01/12] gh-91602: Add iterdump() support for filtering database objects. --- Lib/sqlite3/dump.py | 17 ++++++-- Lib/test/test_sqlite3/test_dump.py | 23 ++++++++++ Modules/_sqlite/clinic/connection.c.h | 60 +++++++++++++++++++++++---- Modules/_sqlite/connection.c | 13 ++++-- 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py index 719dfc8947697d..b43127a98ccf5e 100644 --- a/Lib/sqlite3/dump.py +++ b/Lib/sqlite3/dump.py @@ -15,7 +15,7 @@ def _quote_value(value): return "'{0}'".format(value.replace("'", "''")) -def _iterdump(connection): +def _iterdump(connection, *, filter=None): """ Returns an iterator to the dump of the database in an SQL text format. @@ -32,15 +32,23 @@ def _iterdump(connection): yield('PRAGMA foreign_keys=OFF;') yield('BEGIN TRANSACTION;') + if filter: + # Return database objects which match the filter pattern. + filter_name_clause = 'AND "name" LIKE %s' + params = [filter] + else: + filter_name_clause = "" + params = [] # sqlite_master table contains the SQL CREATE statements for the database. - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" == 'table' + {filter_name_clause} ORDER BY "name" """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) sqlite_sequence = [] for table_name, type, sql in schema_res.fetchall(): if table_name == 'sqlite_sequence': @@ -82,11 +90,12 @@ def _iterdump(connection): yield("{0};".format(row[0])) # Now when the type is 'index', 'trigger', or 'view' - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" IN ('index', 'trigger', 'view') + {filter_name_clause} """ schema_res = cu.execute(q) for name, type, sql in schema_res.fetchall(): diff --git a/Lib/test/test_sqlite3/test_dump.py b/Lib/test/test_sqlite3/test_dump.py index 2e1f0b80c10f46..e8ebf8ec90973b 100644 --- a/Lib/test/test_sqlite3/test_dump.py +++ b/Lib/test/test_sqlite3/test_dump.py @@ -54,6 +54,29 @@ def test_table_dump(self): [self.assertEqual(expected_sqls[i], actual_sqls[i]) for i in range(len(expected_sqls))] + def test_table_dump_filter(self): + all_table_sqls = [ + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + ] + all_views_sqls = [ + """CREATE VIEW view_1 AS SELECT * FROM some_table_2""", + """CREATE VIEW view_2 AS SELECT * FROM test_table_1""", + ] + # Create database structure. + for sql in [*all_table_sqls, *all_views_sqls]: + self.cu.execute(sql) + # %_table_% matches all tables. + dump_sqls = list(self.cx.iterdump(filter="%_table_%")) + self.assertEqual(dump_sqls, all_table_sqls) + # view_% matches all views. + dump_sqls = list(self.cx.iterdump(filter="view_%")) + self.assertEqual(dump_sqls, all_views_sqls) + def test_dump_autoincrement(self): expected = [ 'CREATE TABLE "t1" (id integer primary key autoincrement);', diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index db5eb77891e52e..1ff67f30636677 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1204,21 +1204,67 @@ pysqlite_connection_interrupt(pysqlite_Connection *self, PyObject *Py_UNUSED(ign } PyDoc_STRVAR(pysqlite_connection_iterdump__doc__, -"iterdump($self, /)\n" +"iterdump($self, /, *, filter=None)\n" "--\n" "\n" -"Returns iterator to the dump of the database in an SQL text format."); +"Returns iterator to the dump of the database in an SQL text format.\n" +"\n" +" filter\n" +" An optional LIKE pattern for database objects to dump"); #define PYSQLITE_CONNECTION_ITERDUMP_METHODDEF \ - {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, pysqlite_connection_iterdump__doc__}, + {"iterdump", _PyCFunction_CAST(pysqlite_connection_iterdump), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_iterdump__doc__}, static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self); +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter); static PyObject * -pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *Py_UNUSED(ignored)) +pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return pysqlite_connection_iterdump_impl(self); + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(filter), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"filter", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "iterdump", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *filter = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + filter = args[0]; +skip_optional_kwonly: + return_value = pysqlite_connection_iterdump_impl(self, filter); + +exit: + return return_value; } PyDoc_STRVAR(pysqlite_connection_backup__doc__, @@ -1818,4 +1864,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=90b5b9c14261b8d7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a1f9f2e19ace69d2 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 0a6633972cc5ef..b3cd36f1b574cd 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1979,12 +1979,17 @@ pysqlite_connection_interrupt_impl(pysqlite_Connection *self) /*[clinic input] _sqlite3.Connection.iterdump as pysqlite_connection_iterdump + * + filter: object = None + An optional LIKE pattern for database objects to dump + Returns iterator to the dump of the database in an SQL text format. [clinic start generated code]*/ static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self) -/*[clinic end generated code: output=586997aaf9808768 input=1911ca756066da89]*/ +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter) +/*[clinic end generated code: output=fd81069c4bdeb6b0 input=4ae6d9a898f108df]*/ { if (!pysqlite_check_connection(self)) { return NULL; @@ -1998,8 +2003,8 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self) } return NULL; } - - PyObject *retval = PyObject_CallOneArg(iterdump, (PyObject *)self); + PyObject *args[1] = {(PyObject *)self}; + PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, filter); Py_DECREF(iterdump); return retval; } From c414251b49eb5e9f08b04132b20f7c31289d2e8f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 08:58:57 +0100 Subject: [PATCH 02/12] Run regen-all. --- Include/internal/pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 1 + Include/internal/pycore_unicodeobject_generated.h | 3 +++ 4 files changed, 6 insertions(+) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 0a24b127192c9b..13eb9b2f1c5843 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -935,6 +935,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fileno)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filepath)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fillvalue)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filter)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filters)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(final)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(find_class)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index efb659c5806e6e..178d31145824ee 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -424,6 +424,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fileno) STRUCT_FOR_ID(filepath) STRUCT_FOR_ID(fillvalue) + STRUCT_FOR_ID(filter) STRUCT_FOR_ID(filters) STRUCT_FOR_ID(final) STRUCT_FOR_ID(find_class) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index e3ebd80745e610..b79ff942475d04 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -933,6 +933,7 @@ extern "C" { INIT_ID(fileno), \ INIT_ID(filepath), \ INIT_ID(fillvalue), \ + INIT_ID(filter), \ INIT_ID(filters), \ INIT_ID(final), \ INIT_ID(find_class), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 9fa6c896c1a328..beef54c047ec8b 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1113,6 +1113,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(fillvalue); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(filter); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(filters); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); From b1ae087a0b757556279c554f5140542700ce38b6 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 09:43:44 +0100 Subject: [PATCH 03/12] Fixed PyObject_Vectorcall() call and SQL placeholder. --- Lib/sqlite3/dump.py | 4 ++-- Modules/_sqlite/connection.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py index b43127a98ccf5e..9dcce7dc76ced4 100644 --- a/Lib/sqlite3/dump.py +++ b/Lib/sqlite3/dump.py @@ -34,7 +34,7 @@ def _iterdump(connection, *, filter=None): if filter: # Return database objects which match the filter pattern. - filter_name_clause = 'AND "name" LIKE %s' + filter_name_clause = 'AND "name" LIKE ?' params = [filter] else: filter_name_clause = "" @@ -97,7 +97,7 @@ def _iterdump(connection, *, filter=None): "type" IN ('index', 'trigger', 'view') {filter_name_clause} """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) for name, type, sql in schema_res.fetchall(): yield('{0};'.format(sql)) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index b3cd36f1b574cd..41be6647a74ba7 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2003,8 +2003,10 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, } return NULL; } - PyObject *args[1] = {(PyObject *)self}; - PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, filter); + PyObject *args[2] = {(PyObject *)self, filter}; + PyObject *kwnames = PyTuple_New(1); + PyTuple_SET_ITEM(kwnames, 0, PyUnicode_FromString("filter")); + PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); Py_DECREF(iterdump); return retval; } From e181dcbbdcbf2c410a8d13453c7e227a73e8cd3a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 10:05:38 +0100 Subject: [PATCH 04/12] More tests. --- Lib/test/test_sqlite3/test_dump.py | 61 ++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dump.py b/Lib/test/test_sqlite3/test_dump.py index e8ebf8ec90973b..7261b7f0dc93d0 100644 --- a/Lib/test/test_sqlite3/test_dump.py +++ b/Lib/test/test_sqlite3/test_dump.py @@ -56,26 +56,73 @@ def test_table_dump(self): def test_table_dump_filter(self): all_table_sqls = [ - """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", - """INSERT INTO "test_table_1" VALUES(1);""", - """INSERT INTO "test_table_1" VALUES(2);""", """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", """INSERT INTO "some_table_2" VALUES(3);""", """INSERT INTO "some_table_2" VALUES(4);""", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", ] all_views_sqls = [ - """CREATE VIEW view_1 AS SELECT * FROM some_table_2""", - """CREATE VIEW view_2 AS SELECT * FROM test_table_1""", + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", ] # Create database structure. for sql in [*all_table_sqls, *all_views_sqls]: self.cu.execute(sql) # %_table_% matches all tables. dump_sqls = list(self.cx.iterdump(filter="%_table_%")) - self.assertEqual(dump_sqls, all_table_sqls) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, "COMMIT;"], + ) # view_% matches all views. dump_sqls = list(self.cx.iterdump(filter="view_%")) - self.assertEqual(dump_sqls, all_views_sqls) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_views_sqls, "COMMIT;"], + ) + # %_1 matches tables and views with the _1 suffix. + dump_sqls = list(self.cx.iterdump(filter="%_1")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + "COMMIT;" + ], + ) + # some_% matches some_table_2. + dump_sqls = list(self.cx.iterdump(filter="some_%")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + "COMMIT;" + ], + ) + # Only single object. + dump_sqls = list(self.cx.iterdump(filter="view_2")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", + "COMMIT;" + ], + ) + # % matches all objects. + dump_sqls = list(self.cx.iterdump(filter="%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, *all_views_sqls, "COMMIT;"], + ) def test_dump_autoincrement(self): expected = [ From 048a6a20a44a9535aecc626954dbe458158f1247 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 16:24:39 +0100 Subject: [PATCH 05/12] Added error handling for Py_*. --- Modules/_sqlite/connection.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 41be6647a74ba7..8c2bc9fc20eb97 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2005,10 +2005,26 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, } PyObject *args[2] = {(PyObject *)self, filter}; PyObject *kwnames = PyTuple_New(1); - PyTuple_SET_ITEM(kwnames, 0, PyUnicode_FromString("filter")); + PyObject *py_filter = NULL; + + if (!kwnames) { + goto error; + } + py_filter = PyUnicode_FromString("filter"); + if (!py_filter) { + goto error; + } + PyTuple_SET_ITEM(kwnames, 0, py_filter); + PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); Py_DECREF(iterdump); return retval; + +error: + Py_DECREF(iterdump); + Py_DECREF(args); + Py_XDECREF(kwnames); + return NULL; } /*[clinic input] From f09fa463199eda25aee964b06a1292864c0c187a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 19:15:49 +0100 Subject: [PATCH 06/12] Corrected error handling. --- Modules/_sqlite/connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 8c2bc9fc20eb97..dcf527be99468a 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2018,11 +2018,11 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); Py_DECREF(iterdump); + Py_DECREF(kwnames); return retval; error: Py_DECREF(iterdump); - Py_DECREF(args); Py_XDECREF(kwnames); return NULL; } From f3bab4720e79103daa922cc214a4a58a4c48315f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Jan 2024 20:47:49 +0100 Subject: [PATCH 07/12] Added docs and release notes. --- Doc/library/sqlite3.rst | 11 ++++++++++- .../2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c3406b166c3d89..a78093462fc153 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1137,12 +1137,19 @@ Connection objects .. _Loading an Extension: https://www.sqlite.org/loadext.html#loading_an_extension_ - .. method:: iterdump + .. method:: iterdump(*, filter=None) Return an :term:`iterator` to dump the database as SQL source code. Useful when saving an in-memory database for later restoration. Similar to the ``.dump`` command in the :program:`sqlite3` shell. + :param entrypoint: + + An optional ``LIKE`` pattern for database objects to dump, e.g. ``prefix_%``. + If ``None`` (the default), all database objects will be included. + + :type entrypoint: str | None + Example: .. testcode:: @@ -1158,6 +1165,8 @@ Connection objects :ref:`sqlite3-howto-encoding` + .. versionchanged:: 3.13 + Added the *filter* parameter. .. method:: backup(target, *, pages=-1, progress=None, name="main", sleep=0.250) diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst new file mode 100644 index 00000000000000..39dd6e7933a7d1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst @@ -0,0 +1,3 @@ +Added ``filter`` keyword-only parameter to +:meth:`sqlite3.Connection.iterdump` for filtering database objects to dump. +Patch by Mariusz Felisiak. From 321b3e0e03edfa6b5481e4856619b0ed41704679 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 25 Jan 2024 13:34:44 +0100 Subject: [PATCH 08/12] Used Py_BuildValue(). --- Modules/_sqlite/connection.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index dcf527be99468a..128eae2d60fdf7 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2004,17 +2004,10 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, return NULL; } PyObject *args[2] = {(PyObject *)self, filter}; - PyObject *kwnames = PyTuple_New(1); - PyObject *py_filter = NULL; - + PyObject *kwnames = Py_BuildValue("(s)", "filter"); if (!kwnames) { goto error; } - py_filter = PyUnicode_FromString("filter"); - if (!py_filter) { - goto error; - } - PyTuple_SET_ITEM(kwnames, 0, py_filter); PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); Py_DECREF(iterdump); From 8cf51d42353fabf370aa8857b3ee8e9f6c14fbea Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 25 Jan 2024 14:05:44 +0100 Subject: [PATCH 09/12] `goto` no longer needed. --- Modules/_sqlite/connection.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 128eae2d60fdf7..39250f207ab114 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2006,18 +2006,14 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, PyObject *args[2] = {(PyObject *)self, filter}; PyObject *kwnames = Py_BuildValue("(s)", "filter"); if (!kwnames) { - goto error; + Py_DECREF(iterdump); + return NULL; } PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); Py_DECREF(iterdump); Py_DECREF(kwnames); return retval; - -error: - Py_DECREF(iterdump); - Py_XDECREF(kwnames); - return NULL; } /*[clinic input] From d993e114e5fc515ef1ac70bdb69a7afc605f7009 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 25 Jan 2024 17:41:36 +0100 Subject: [PATCH 10/12] Used PyVectorcall_NARGS(). --- Modules/_sqlite/connection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 39250f207ab114..7222132494cf97 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2009,8 +2009,8 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, Py_DECREF(iterdump); return NULL; } - - PyObject *retval = PyObject_Vectorcall(iterdump, args, 1, kwnames); + Py_ssize_t nargs = PyVectorcall_NARGS(1); + PyObject *retval = PyObject_Vectorcall(iterdump, args, nargs, kwnames); Py_DECREF(iterdump); Py_DECREF(kwnames); return retval; From 4de91bef164cd2a1bee4fcd66e1b108af02080b3 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 25 Jan 2024 20:26:09 +0100 Subject: [PATCH 11/12] Arguments offset. --- Modules/_sqlite/connection.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 7222132494cf97..f97afcf5fcf16e 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -2003,14 +2003,14 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self, } return NULL; } - PyObject *args[2] = {(PyObject *)self, filter}; + PyObject *args[3] = {NULL, (PyObject *)self, filter}; PyObject *kwnames = Py_BuildValue("(s)", "filter"); if (!kwnames) { Py_DECREF(iterdump); return NULL; } - Py_ssize_t nargs = PyVectorcall_NARGS(1); - PyObject *retval = PyObject_Vectorcall(iterdump, args, nargs, kwnames); + Py_ssize_t nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; + PyObject *retval = PyObject_Vectorcall(iterdump, args + 1, nargsf, kwnames); Py_DECREF(iterdump); Py_DECREF(kwnames); return retval; From e3141530bec5d60003fa973a86458d3394f084ff Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 26 Jan 2024 09:39:20 +0100 Subject: [PATCH 12/12] Corrected docs. --- Doc/library/sqlite3.rst | 4 ++-- Doc/whatsnew/3.13.rst | 4 ++++ .../Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index a78093462fc153..87d5ef1e42ca3a 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1143,12 +1143,12 @@ Connection objects Useful when saving an in-memory database for later restoration. Similar to the ``.dump`` command in the :program:`sqlite3` shell. - :param entrypoint: + :param filter: An optional ``LIKE`` pattern for database objects to dump, e.g. ``prefix_%``. If ``None`` (the default), all database objects will be included. - :type entrypoint: str | None + :type filter: str | None Example: diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8c2bb05920d5b6..468a1e02238274 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -375,6 +375,10 @@ sqlite3 object is not :meth:`closed ` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +* Add *filter* keyword-only parameter to :meth:`sqlite3.Connection.iterdump` + for filtering database objects to dump. + (Contributed by Mariusz Felisiak in :gh:`91602`.) + subprocess ---------- diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst index 39dd6e7933a7d1..21d39df43e035b 100644 --- a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst +++ b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst @@ -1,3 +1,3 @@ -Added ``filter`` keyword-only parameter to +Add *filter* keyword-only parameter to :meth:`sqlite3.Connection.iterdump` for filtering database objects to dump. Patch by Mariusz Felisiak.