From b6461381c2ecaffaa8c4cab1ea76f397108d277f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 23 Mar 2021 22:53:44 +0100 Subject: [PATCH 01/11] bpo-20364: Improve sqlite3 placeholder docs --- Doc/includes/sqlite3/execute_1.py | 14 +++++-- Doc/library/sqlite3.rst | 70 ++++++++++++++----------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/Doc/includes/sqlite3/execute_1.py b/Doc/includes/sqlite3/execute_1.py index 3466b1265a5bf2..47c911cb5589f6 100644 --- a/Doc/includes/sqlite3/execute_1.py +++ b/Doc/includes/sqlite3/execute_1.py @@ -10,9 +10,17 @@ # This is the qmark style: cur.execute("insert into people values (?, ?)", (who, age)) -# And this is the named style: -cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age}) +# The qmark style used with executemany(): +people = [ + ('Chirac', 70), + ('Finnbogadóttir', 72), + ('Schröder', 58), +] +cur.executemany("insert into people values (?, ?)", people) -print(cur.fetchone()) +# And this is the named style: +cur.execute("select * from people where name_last=:who and age=:age", + {"who": who, "age": age}) +print(cur.fetchall()) con.close() diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 1da5c7f3ab187a..7ddd6b1e08422e 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -55,33 +55,6 @@ The data you've saved is persistent and is available in subsequent sessions:: con = sqlite3.connect('example.db') cur = con.cursor() -Usually your SQL operations will need to use values from Python variables. You -shouldn't assemble your query using Python's string operations because doing so -is insecure; it makes your program vulnerable to an SQL injection attack -(see https://xkcd.com/327/ for humorous example of what can go wrong). - -Instead, use the DB-API's parameter substitution. Put ``?`` as a placeholder -wherever you want to use a value, and then provide a tuple of values as the -second argument to the cursor's :meth:`~Cursor.execute` method. (Other database -modules may use a different placeholder, such as ``%s`` or ``:1``.) For -example:: - - # Never do this -- insecure! - symbol = 'RHAT' - cur.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol) - - # Do this instead - t = ('RHAT',) - cur.execute('SELECT * FROM stocks WHERE symbol=?', t) - print(cur.fetchone()) - - # Larger example that inserts many records at a time - purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'IBM', 500, 53.00), - ] - cur.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases) - To retrieve data after executing a SELECT statement, you can either treat the cursor as an :term:`iterator`, call the cursor's :meth:`~Cursor.fetchone` method to retrieve a single matching row, or call :meth:`~Cursor.fetchall` to get a list of the @@ -98,6 +71,32 @@ This example uses the iterator form:: ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0) +.. _sqlite3-placeholders: + +Usually your SQL operations will need to use values from Python variables. You +shouldn't assemble your query using Python's string operations because doing so +is insecure; it makes your program vulnerable to an SQL injection attack +(see https://xkcd.com/327/ for a humorous example of what can go wrong):: + + # Never do this -- insecure! + symbol = 'RHAT' + cur.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol) + +Instead, use the DB-API's parameter substitution. Put a placeholder wherever +you want to use a value, and then provide a tuple of values as the second +argument to the cursor's :meth:`~Cursor.execute` method. A statement may use +one of two kinds of placeholders: question marks (qmark style) or named +placeholders (named style). For the qmark style, `parameters` must be a +:term:`sequence `. For the named style, it can be either a +:term:`sequence ` or :class:`dict` instance. The length of the +:term:`sequence ` must match the number of placeholders, or a +:exc:`ProgrammingError` is raised. If a :class:`dict` is given, it must contain +keys for all named parameters. Any extra items are ignored. Here's an example +of both styles: + +.. literalinclude:: ../includes/sqlite3/execute_1.py + + .. seealso:: https://www.sqlite.org @@ -607,14 +606,8 @@ Cursor Objects .. method:: execute(sql[, parameters]) - Executes an SQL statement. The SQL statement may be parameterized (i. e. - placeholders instead of SQL literals). The :mod:`sqlite3` module supports two - kinds of placeholders: question marks (qmark style) and named placeholders - (named style). - - Here's an example of both styles: - - .. literalinclude:: ../includes/sqlite3/execute_1.py + Executes an SQL statement. The SQL statement may be parameterized (that is + :ref:`placeholders ` instead of SQL literals). :meth:`execute` will only execute a single SQL statement. If you try to execute more than one statement with it, it will raise a :exc:`.Warning`. Use @@ -624,9 +617,10 @@ Cursor Objects .. method:: executemany(sql, seq_of_parameters) - Executes an SQL command against all parameter sequences or mappings found in - the sequence *seq_of_parameters*. The :mod:`sqlite3` module also allows - using an :term:`iterator` yielding parameters instead of a sequence. + Executes an SQL command against all parameter sequences bound to the + :ref:`placeholders ` in the sequence + *seq_of_parameters*. The :mod:`sqlite3` module also allows using an + :term:`iterator` yielding parameters instead of a sequence. .. literalinclude:: ../includes/sqlite3/executemany_1.py From 91962aa07da530f3b2d1bf83703cc64eae66578c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 23 Mar 2021 23:05:04 +0100 Subject: [PATCH 02/11] Fix default role --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7ddd6b1e08422e..eb15ea482d745f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -86,7 +86,7 @@ Instead, use the DB-API's parameter substitution. Put a placeholder wherever you want to use a value, and then provide a tuple of values as the second argument to the cursor's :meth:`~Cursor.execute` method. A statement may use one of two kinds of placeholders: question marks (qmark style) or named -placeholders (named style). For the qmark style, `parameters` must be a +placeholders (named style). For the qmark style, ``parameters`` must be a :term:`sequence `. For the named style, it can be either a :term:`sequence ` or :class:`dict` instance. The length of the :term:`sequence ` must match the number of placeholders, or a From ebc7c1a062926a76b501f1f94fbe8c6accb2ce06 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 23 Mar 2021 23:33:21 +0100 Subject: [PATCH 03/11] Fix executemany() --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index eb15ea482d745f..5326665bbebc3b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -617,8 +617,8 @@ Cursor Objects .. method:: executemany(sql, seq_of_parameters) - Executes an SQL command against all parameter sequences bound to the - :ref:`placeholders ` in the sequence + Executes a :ref:`parametrized ` SQL command + against all parameter sequences or mappings found in the sequence *seq_of_parameters*. The :mod:`sqlite3` module also allows using an :term:`iterator` yielding parameters instead of a sequence. From 74618e848810945294022398aebb3e8c44cde09f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 2 Apr 2021 18:39:29 +0200 Subject: [PATCH 04/11] Simplify execute() docs --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5326665bbebc3b..ae838828cfc45d 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -606,8 +606,8 @@ Cursor Objects .. method:: execute(sql[, parameters]) - Executes an SQL statement. The SQL statement may be parameterized (that is - :ref:`placeholders ` instead of SQL literals). + Executes an SQL statement. Values may be bound to the statement using + :ref:`placeholders `. :meth:`execute` will only execute a single SQL statement. If you try to execute more than one statement with it, it will raise a :exc:`.Warning`. Use From f19c5316d3577e5d4632a19ebfc0b88844188ba9 Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Mon, 5 Apr 2021 18:37:17 +0200 Subject: [PATCH 05/11] parametrized => parameterized Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> --- Doc/library/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index ae838828cfc45d..b6b5128c7d1447 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -617,7 +617,7 @@ Cursor Objects .. method:: executemany(sql, seq_of_parameters) - Executes a :ref:`parametrized ` SQL command + Executes a :ref:`parameterized ` SQL command against all parameter sequences or mappings found in the sequence *seq_of_parameters*. The :mod:`sqlite3` module also allows using an :term:`iterator` yielding parameters instead of a sequence. From 1482f21209a9b8ddd34705a59964dd046e406990 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 5 Apr 2021 18:52:10 +0200 Subject: [PATCH 06/11] Address review: statement => SQL statement --- Doc/library/sqlite3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index b6b5128c7d1447..ae0b588efaa14b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -84,8 +84,8 @@ is insecure; it makes your program vulnerable to an SQL injection attack Instead, use the DB-API's parameter substitution. Put a placeholder wherever you want to use a value, and then provide a tuple of values as the second -argument to the cursor's :meth:`~Cursor.execute` method. A statement may use -one of two kinds of placeholders: question marks (qmark style) or named +argument to the cursor's :meth:`~Cursor.execute` method. An SQL statement may +use one of two kinds of placeholders: question marks (qmark style) or named placeholders (named style). For the qmark style, ``parameters`` must be a :term:`sequence `. For the named style, it can be either a :term:`sequence ` or :class:`dict` instance. The length of the From ade4fdd9b3a11ba0d44790f3f8b625c793061b88 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 14 Apr 2021 13:00:15 +0200 Subject: [PATCH 07/11] Address review: beautify xkcd link --- Doc/library/sqlite3.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index ae0b588efaa14b..6c9f6e1a400eac 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -76,7 +76,8 @@ This example uses the iterator form:: Usually your SQL operations will need to use values from Python variables. You shouldn't assemble your query using Python's string operations because doing so is insecure; it makes your program vulnerable to an SQL injection attack -(see https://xkcd.com/327/ for a humorous example of what can go wrong):: +(see the `xkcd webcomic `_ for a humorous example of +what can go wrong):: # Never do this -- insecure! symbol = 'RHAT' From 39801ace6469b3d3a06c674f41ee9c9682880494 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 14 Apr 2021 13:12:43 +0200 Subject: [PATCH 08/11] Address review: use programming languages as example database --- Doc/includes/sqlite3/execute_1.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Doc/includes/sqlite3/execute_1.py b/Doc/includes/sqlite3/execute_1.py index 47c911cb5589f6..1566af8e2ab92c 100644 --- a/Doc/includes/sqlite3/execute_1.py +++ b/Doc/includes/sqlite3/execute_1.py @@ -2,25 +2,24 @@ con = sqlite3.connect(":memory:") cur = con.cursor() -cur.execute("create table people (name_last, age)") - -who = "Yeltsin" -age = 72 +cur.execute("create table lang (lang_name, lang_age)") # This is the qmark style: -cur.execute("insert into people values (?, ?)", (who, age)) +name = "C" +age = 49 +cur.execute("insert into lang values (?, ?)", (name, age)) # The qmark style used with executemany(): -people = [ - ('Chirac', 70), - ('Finnbogadóttir', 72), - ('Schröder', 58), +lang_list = [ + ("Fortran", 64), + ("Python", 30), + ("Go", 11), ] -cur.executemany("insert into people values (?, ?)", people) +cur.executemany("insert into lang values (?, ?)", lang_list) # And this is the named style: -cur.execute("select * from people where name_last=:who and age=:age", - {"who": who, "age": age}) +cur.execute("select * from lang where lang_name=:name and lang_age=:age", + {"name": "C", "age": 49}) print(cur.fetchall()) con.close() From 1dc6531035e543323c5849b4cb33dada979033e9 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 14 Apr 2021 13:28:19 +0200 Subject: [PATCH 09/11] Address review: get rid of more temp vars --- Doc/includes/sqlite3/execute_1.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/includes/sqlite3/execute_1.py b/Doc/includes/sqlite3/execute_1.py index 1566af8e2ab92c..42aad4d5839f06 100644 --- a/Doc/includes/sqlite3/execute_1.py +++ b/Doc/includes/sqlite3/execute_1.py @@ -5,9 +5,7 @@ cur.execute("create table lang (lang_name, lang_age)") # This is the qmark style: -name = "C" -age = 49 -cur.execute("insert into lang values (?, ?)", (name, age)) +cur.execute("insert into lang values (?, ?)", ("C", 49)) # The qmark style used with executemany(): lang_list = [ From c2366b53dc5c31330033645341d666c9ea69aded Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 14 Apr 2021 13:53:21 +0200 Subject: [PATCH 10/11] Adapt sqlite3 suspicious doc rules --- Doc/tools/susp-ignored.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index fd27ab5d80dc23..5314bc04798c71 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -209,9 +209,9 @@ library/smtplib,,:port,method must support that as well as a regular host:port library/socket,,::,'5aef:2b::8' library/socket,,:can,"return (can_id, can_dlc, data[:can_dlc])" library/socket,,:len,fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) -library/sqlite3,,:age,"cur.execute(""select * from people where name_last=:who and age=:age"", {""who"": who, ""age"": age})" +library/sqlite3,,:name,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age"", {""name"": ""C"", ""age"": 49})" +library/sqlite3,,:age,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age"", {""name"": ""C"", ""age"": 49})" library/sqlite3,,:memory, -library/sqlite3,,:who,"cur.execute(""select * from people where name_last=:who and age=:age"", {""who"": who, ""age"": age})" library/sqlite3,,:path,"db = sqlite3.connect('file:path/to/database?mode=ro', uri=True)" library/ssl,,:My,"Organizational Unit Name (eg, section) []:My Group" library/ssl,,:My,"Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc." From f3ed86e941e59968b76830a4bfd0a9c9bb1d2b6f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 14 Apr 2021 14:06:09 +0200 Subject: [PATCH 11/11] Fix suspicious rules --- Doc/tools/susp-ignored.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 5314bc04798c71..a2a373ba6c38d0 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -209,8 +209,8 @@ library/smtplib,,:port,method must support that as well as a regular host:port library/socket,,::,'5aef:2b::8' library/socket,,:can,"return (can_id, can_dlc, data[:can_dlc])" library/socket,,:len,fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) -library/sqlite3,,:name,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age"", {""name"": ""C"", ""age"": 49})" -library/sqlite3,,:age,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age"", {""name"": ""C"", ""age"": 49})" +library/sqlite3,,:name,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age""," +library/sqlite3,,:age,"cur.execute(""select * from lang where lang_name=:name and lang_age=:age""," library/sqlite3,,:memory, library/sqlite3,,:path,"db = sqlite3.connect('file:path/to/database?mode=ro', uri=True)" library/ssl,,:My,"Organizational Unit Name (eg, section) []:My Group"