Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-20364: Improve sqlite3 placeholder docs #25003

Merged
merged 11 commits into from
Apr 14, 2021
14 changes: 11 additions & 3 deletions Doc/includes/sqlite3/execute_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
print(cur.fetchall())

con.close()
70 changes: 32 additions & 38 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)::
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved

# 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
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
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
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
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 <sequence>`. For the named style, it can be either a
:term:`sequence <sequence>` or :class:`dict` instance. The length of the
:term:`sequence <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
Expand Down Expand Up @@ -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 <sqlite3-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
Expand All @@ -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 a :ref:`parametrized <sqlite3-placeholders>` SQL command
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
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.
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved

.. literalinclude:: ../includes/sqlite3/executemany_1.py

Expand Down