From 974022c07224c7eecef2f552cac12eba3fcda6ef Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:03:35 +0200 Subject: [PATCH 01/33] gh-95271: Improve sqlite3 tutorial wording --- Doc/library/sqlite3.rst | 51 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 140dccfea94bdb..c8b3beaa7539e1 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -40,44 +40,45 @@ This document includes four main sections: Tutorial -------- -To use the module, start by creating a :class:`Connection` object that -represents the database. Here the data will be stored in the -:file:`example.db` file:: +To use the module, start by opening a connection to an SQLite database. +We do this by using the :func:`sqlite3.connect` function. +The returned :class:`Connection` object represents our +connection to the database. +In our example, +the database will be stored in the file :file:`example.db`:: import sqlite3 con = sqlite3.connect('example.db') -The special path name ``:memory:`` can be provided to create a temporary -database in RAM. - -Once a :class:`Connection` has been established, create a :class:`Cursor` object -and call its :meth:`~Cursor.execute` method to perform SQL commands:: +Notice that the file :file:`example.db` will be created implicitly +if it does not exist. +Now, create a :class:`Cursor` object using :meth:`~Connection.cursor`. +Call its :meth:`~Cursor.execute` method to perform SQL commands:: cur = con.cursor() - # Create table + # Create table, then insert a row of data. cur.execute('''CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)''') - - # Insert a row of data cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") - # Save (commit) the changes - con.commit() +Notice that the ``INSERT`` statement will +:ref:`implicitly open a transaction `. +We use the connection object to close pending transactions by using +:meth:`~Connection.commit` or `:meth:`~Connection.rollback`:: - # We can also close the connection if we are done with it. - # Just be sure any changes have been committed or they will be lost. - con.close() + con.commit() # Save the changes. -The saved data is persistent: it can be reloaded in a subsequent session even -after restarting the Python interpreter:: - - import sqlite3 - con = sqlite3.connect('example.db') - cur = con.cursor() - -At this point, our database only contains one row:: +Let us verify that our data has been committed and written to disk +by closing the database connection, opening a new connection, +and querying our ``stocks`` table for our newly inserted row:: + # Close the database connection. + >>> con.close() + # Open a new connection and create a new cursor. + >>> con = sqlite3.connect('example.db') + >>> cur = con.cursor() + # Query the 'stocks' table. >>> res = cur.execute('SELECT count(rowid) FROM stocks') >>> print(res.fetchone()) (1,) @@ -100,7 +101,7 @@ to bind Python values to SQL statements, to avoid `SQL injection attacks`_. See the :ref:`placeholders how-to ` for more details. -Then, retrieve the data by iterating over the result of a ``SELECT`` statement:: +Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: >>> for row in cur.execute('SELECT * FROM stocks ORDER BY price'): ... print(row) From f21d777ea1c09a54b557ebb711dce4e7687b1597 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:06:15 +0200 Subject: [PATCH 02/33] Fix rest syntax --- 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 c8b3beaa7539e1..c4fa0316aa096b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -65,7 +65,7 @@ Call its :meth:`~Cursor.execute` method to perform SQL commands:: Notice that the ``INSERT`` statement will :ref:`implicitly open a transaction `. We use the connection object to close pending transactions by using -:meth:`~Connection.commit` or `:meth:`~Connection.rollback`:: +:meth:`~Connection.commit` or :meth:`~Connection.rollback`:: con.commit() # Save the changes. From 4231b7550d5d1a225a5d5377af4a9a208d0c1ff2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:16:25 +0200 Subject: [PATCH 03/33] Improve count query comment --- 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 c4fa0316aa096b..aea3dc2c122015 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -78,7 +78,7 @@ and querying our ``stocks`` table for our newly inserted row:: # Open a new connection and create a new cursor. >>> con = sqlite3.connect('example.db') >>> cur = con.cursor() - # Query the 'stocks' table. + # Use the SQL 'count' function to verify that we've got one row of data. >>> res = cur.execute('SELECT count(rowid) FROM stocks') >>> print(res.fetchone()) (1,) From e08ca43fd5b7b35bc86f8695c0952847dda9157b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:24:17 +0200 Subject: [PATCH 04/33] SQL commands => SQL queries --- 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 aea3dc2c122015..b8a380f725b5fb 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -53,7 +53,7 @@ the database will be stored in the file :file:`example.db`:: Notice that the file :file:`example.db` will be created implicitly if it does not exist. Now, create a :class:`Cursor` object using :meth:`~Connection.cursor`. -Call its :meth:`~Cursor.execute` method to perform SQL commands:: +Call its :meth:`~Cursor.execute` method to perform SQL queries:: cur = con.cursor() From f9a5664727189d06dc11681276d8c3d312c5a4ed Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:28:08 +0200 Subject: [PATCH 05/33] Improve transaction control paragraph --- Doc/library/sqlite3.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index b8a380f725b5fb..b1b9ff3c97fa78 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -62,10 +62,9 @@ Call its :meth:`~Cursor.execute` method to perform SQL queries:: (date text, trans text, symbol text, qty real, price real)''') cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") -Notice that the ``INSERT`` statement will -:ref:`implicitly open a transaction `. -We use the connection object to close pending transactions by using -:meth:`~Connection.commit` or :meth:`~Connection.rollback`:: +The ``INSERT`` statement will implicitly open a transaction. +See :ref:`sqlite3-controlling-transactions>` for more details. +Use connection object to :meth:`~Connection.commit` the pending transaction:: con.commit() # Save the changes. From 484f3418de1e901804e7fab7fbfaeb3e02bb2cf5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:32:10 +0200 Subject: [PATCH 06/33] Be more to the point --- Doc/library/sqlite3.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index b1b9ff3c97fa78..9b599228101795 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -68,9 +68,9 @@ Use connection object to :meth:`~Connection.commit` the pending transaction:: con.commit() # Save the changes. -Let us verify that our data has been committed and written to disk -by closing the database connection, opening a new connection, -and querying our ``stocks`` table for our newly inserted row:: +Verify that data has been committed and written to disk: +close the connection, open a new connection, +then query the database:: # Close the database connection. >>> con.close() From ae0969f8f304d7881de27375dc50e4a5928d45b8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:32:42 +0200 Subject: [PATCH 07/33] Fix ref --- 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 9b599228101795..484038762013bd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -63,7 +63,7 @@ Call its :meth:`~Cursor.execute` method to perform SQL queries:: cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") The ``INSERT`` statement will implicitly open a transaction. -See :ref:`sqlite3-controlling-transactions>` for more details. +See :ref:`sqlite3-controlling-transactions` for more details. Use connection object to :meth:`~Connection.commit` the pending transaction:: con.commit() # Save the changes. From d1d11f4717dbdb3e174495d21dcbda7804683379 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:35:09 +0200 Subject: [PATCH 08/33] Missing 'the'; even more to the point --- 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 484038762013bd..e64b7a857992d9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -64,7 +64,7 @@ Call its :meth:`~Cursor.execute` method to perform SQL queries:: The ``INSERT`` statement will implicitly open a transaction. See :ref:`sqlite3-controlling-transactions` for more details. -Use connection object to :meth:`~Connection.commit` the pending transaction:: +Use the connection object to :meth:`~Connection.commit` the transaction:: con.commit() # Save the changes. @@ -84,7 +84,7 @@ then query the database:: The result is a one-item :class:`tuple`: one row, with one column. -Now, let us insert three more rows of data, +Now, insert three more rows of data, using :meth:`~Cursor.executemany`:: >>> data = [ From 9870036bc72312ae15d3a4d85578dc649b594b06 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:41:12 +0200 Subject: [PATCH 09/33] Be more accurate --- 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 e64b7a857992d9..cb358ac86ca89f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -77,7 +77,7 @@ then query the database:: # Open a new connection and create a new cursor. >>> con = sqlite3.connect('example.db') >>> cur = con.cursor() - # Use the SQL 'count' function to verify that we've got one row of data. + # Use the SQL 'count' function to verify the row count. >>> res = cur.execute('SELECT count(rowid) FROM stocks') >>> print(res.fetchone()) (1,) @@ -110,6 +110,7 @@ Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: ('2006-04-06', 'SELL', 'IBM', 500, 53.0) ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0) +Each row is a five-item :class:`!tuple`. You've now created an SQLite database using the :mod:`!sqlite3` module. .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection From 431dcb5c3ef17ef8329eec1ec3b23f0d03fb5af6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Aug 2022 22:58:41 +0200 Subject: [PATCH 10/33] Remove unneeded comments; the instructions don't need to be reiterated --- Doc/library/sqlite3.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index cb358ac86ca89f..8b7423deba9075 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -72,12 +72,9 @@ Verify that data has been committed and written to disk: close the connection, open a new connection, then query the database:: - # Close the database connection. >>> con.close() - # Open a new connection and create a new cursor. >>> con = sqlite3.connect('example.db') >>> cur = con.cursor() - # Use the SQL 'count' function to verify the row count. >>> res = cur.execute('SELECT count(rowid) FROM stocks') >>> print(res.fetchone()) (1,) From f4b66ac13e62096bf71d4e51ff2013e55dfcf653 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Aug 2022 22:45:06 +0200 Subject: [PATCH 11/33] Address Ezio's initial review --- Doc/library/sqlite3.rst | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index f509f8b8ae7a3d..dbff83d85e4618 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -52,18 +52,22 @@ This document includes four main sections: Tutorial -------- -To use the module, start by opening a connection to an SQLite database. -We do this by using the :func:`sqlite3.connect` function. -The returned :class:`Connection` object represents our -connection to the database. -In our example, -the database will be stored in the file :file:`example.db`:: +In this tutorial you will learn the basics of the :mod:`!sqlite3` API +by creating an on-disk database :file:`example.db`, +and executing SQL queries against it. +A fundamental understanding of database concepts, +like transactions and cursors, is assumed. + +Start by using the :func:`sqlite3.connect` function to +open a database :class:`Connection` to :file:`example.db`; +SQLite will implicitly create the database file :file:`example` +in the current working directory, if it does not exist. +The returned :class:`!Connection` object represents our +connection to the on-disk SQLite database:: import sqlite3 con = sqlite3.connect('example.db') -Notice that the file :file:`example.db` will be created implicitly -if it does not exist. Now, create a :class:`Cursor` object using :meth:`~Connection.cursor`. Call its :meth:`~Cursor.execute` method to perform SQL queries:: @@ -74,15 +78,17 @@ Call its :meth:`~Cursor.execute` method to perform SQL queries:: (date text, trans text, symbol text, qty real, price real)''') cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") -The ``INSERT`` statement will implicitly open a transaction. -See :ref:`sqlite3-controlling-transactions` for more details. +The ``INSERT`` statement implicitly opens a transaction +that needs to be committed before changes are saved in the database. +For more details, see the :ref:`sqlite3-controlling-transactions` how-to. Use the connection object to :meth:`~Connection.commit` the transaction:: con.commit() # Save the changes. Verify that data has been committed and written to disk: close the connection, open a new connection, -then query the database:: +create a new cursor, +then use a ``SELECT`` query to read from the database:: >>> con.close() >>> con = sqlite3.connect('example.db') From 09ac89d3dc5bb5e5d3a41a980c69e2b33bddc1dc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Aug 2022 10:22:34 +0200 Subject: [PATCH 12/33] Address second round of reviews from both Ezio and CAM --- Doc/library/sqlite3.rst | 79 +++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index dbff83d85e4618..f159efba8fec06 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -52,72 +52,80 @@ This document includes four main sections: Tutorial -------- -In this tutorial you will learn the basics of the :mod:`!sqlite3` API +In this tutorial, you will learn the basics of the :mod:`!sqlite3` API by creating an on-disk database :file:`example.db`, and executing SQL queries against it. A fundamental understanding of database concepts, -like transactions and cursors, is assumed. +like `transactions`_ and `cursors`_, is assumed. -Start by using the :func:`sqlite3.connect` function to -open a database :class:`Connection` to :file:`example.db`; -SQLite will implicitly create the database file :file:`example` -in the current working directory, if it does not exist. -The returned :class:`!Connection` object represents our -connection to the on-disk SQLite database:: +To start working with an SQLite database, +you must first open a connection to it. +Call the :func:`sqlite3.connect` function +to create a :class:`Connection` to the database :file:`example.db`:: import sqlite3 con = sqlite3.connect('example.db') -Now, create a :class:`Cursor` object using :meth:`~Connection.cursor`. -Call its :meth:`~Cursor.execute` method to perform SQL queries:: +SQLite will implicitly create the database file :file:`example.db` +in the current working directory if it does not exist. +The returned :class:`Connection` object ``con`` +represents our connection to the on-disk database. + +In order to execute SQL statements and fetch results from SQL queries, +we need to use a database cursor. +Call the :meth:`~Connection.cursor` method of ``con`` to create +:class:`Cursor` ``cur``:: cur = con.cursor() - # Create table, then insert a row of data. +Now, create a database table and insert a row of data into it +by calling the :meth:`~Cursor.execute` method of ``cur``:: + cur.execute('''CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)''') cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") The ``INSERT`` statement implicitly opens a transaction -that needs to be committed before changes are saved in the database. -For more details, see the :ref:`sqlite3-controlling-transactions` how-to. -Use the connection object to :meth:`~Connection.commit` the transaction:: +that needs to be committed before changes are saved in the database +(see :ref:`sqlite3-controlling-transactions` for details). +Call the :meth:`~Connection.commit` on the connection object +to commit the transaction:: - con.commit() # Save the changes. + con.commit() -Verify that data has been committed and written to disk: -close the connection, open a new connection, -create a new cursor, -then use a ``SELECT`` query to read from the database:: +Verify that data has been committed and written to disk by +closing the connection and opening a new one, +creating a new cursor, +and then executing a ``SELECT`` query to read from the database:: >>> con.close() - >>> con = sqlite3.connect('example.db') - >>> cur = con.cursor() - >>> res = cur.execute('SELECT count(rowid) FROM stocks') + >>> con2 = sqlite3.connect('example.db') + >>> cur2 = con2.cursor() + >>> res = cur2.execute('SELECT count(rowid) FROM stocks') >>> print(res.fetchone()) (1,) The result is a one-item :class:`tuple`: one row, with one column. -Now, insert three more rows of data, -using :meth:`~Cursor.executemany`:: +Now, insert three more rows by calling +the :meth:`~Cursor.executemany` method on the cursor object:: - >>> data = [ - ... ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), - ... ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0), - ... ('2006-04-06', 'SELL', 'IBM', 500, 53.0), - ... ] - >>> cur.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) + data = [ + ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), + ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0), + ('2006-04-06', 'SELL', 'IBM', 500, 53.0), + ] + cur2.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) -Notice that we used ``?`` placeholders to bind *data* to the query. +Notice that we use ``?`` placeholders to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` to bind Python values to SQL statements, to avoid `SQL injection attacks`_. -See the :ref:`placeholders how-to ` for more details. +See :ref:`sqlite3-placeholders` for more details. Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: - >>> for row in cur.execute('SELECT * FROM stocks ORDER BY price'): + >>> for row in cur2.execute('SELECT * FROM stocks ORDER BY price'): ... print(row) ('2006-01-05', 'BUY', 'RHAT', 100, 35.14) @@ -126,8 +134,11 @@ Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0) Each row is a five-item :class:`!tuple`. -You've now created an SQLite database using the :mod:`!sqlite3` module. +You've now created an SQLite database using the :mod:`!sqlite3` module, +inserted data and ran several SQL queries against it. +.. _transactions: https://en.wikipedia.org/wiki/Database_transaction +.. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection From cbfb3258b2f0ddbaba23c7747fcaec9950412e1c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Aug 2022 10:52:08 +0200 Subject: [PATCH 13/33] Use Python version history as example database --- Doc/library/sqlite3.rst | 48 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index f159efba8fec06..143e299831e197 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -49,24 +49,31 @@ This document includes four main sections: .. _sqlite3-tutorial: +.. We use the following practises for SQL code: +.. - UPPERCASE for keywords +.. - lowercase for schema +.. - single quotes for string literals +.. - singular for table names +.. - if needed, use double quotes for table and column names + Tutorial -------- In this tutorial, you will learn the basics of the :mod:`!sqlite3` API -by creating an on-disk database :file:`example.db`, -and executing SQL queries against it. +by creating an on-disk database :file:`python.db` +for managing Python release metadata. A fundamental understanding of database concepts, like `transactions`_ and `cursors`_, is assumed. To start working with an SQLite database, you must first open a connection to it. Call the :func:`sqlite3.connect` function -to create a :class:`Connection` to the database :file:`example.db`:: +to create a :class:`Connection` to the database :file:`python.db`:: import sqlite3 - con = sqlite3.connect('example.db') + con = sqlite3.connect("python.db") -SQLite will implicitly create the database file :file:`example.db` +SQLite will implicitly create the database file :file:`python.db` in the current working directory if it does not exist. The returned :class:`Connection` object ``con`` represents our connection to the on-disk database. @@ -78,12 +85,13 @@ Call the :meth:`~Connection.cursor` method of ``con`` to create cur = con.cursor() -Now, create a database table and insert a row of data into it +Now, let us create a database table ``release`` with columns for version +number and release date, and inserting the release data for Python 1.0. +Execute the SQL statements by calling the :meth:`~Cursor.execute` method of ``cur``:: - cur.execute('''CREATE TABLE stocks - (date text, trans text, symbol text, qty real, price real)''') - cur.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)") + cur.execute("""CREATE TABLE release(version, releasedate)""") + cur.execute("INSERT INTO release VALUES('3.0', '2008-12-03')") The ``INSERT`` statement implicitly opens a transaction that needs to be committed before changes are saved in the database @@ -99,9 +107,9 @@ creating a new cursor, and then executing a ``SELECT`` query to read from the database:: >>> con.close() - >>> con2 = sqlite3.connect('example.db') + >>> con2 = sqlite3.connect("python.db") >>> cur2 = con2.cursor() - >>> res = cur2.execute('SELECT count(rowid) FROM stocks') + >>> res = cur2.execute("SELECT count(rowid) FROM release") >>> print(res.fetchone()) (1,) @@ -111,11 +119,10 @@ Now, insert three more rows by calling the :meth:`~Cursor.executemany` method on the cursor object:: data = [ - ('2006-03-28', 'BUY', 'IBM', 1000, 45.0), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0), - ('2006-04-06', 'SELL', 'IBM', 500, 53.0), + ("2.0", "2000-10-16"), + ("1.0", "1994-01-26"), ] - cur2.executemany('INSERT INTO stocks VALUES(?, ?, ?, ?, ?)', data) + cur2.executemany("INSERT INTO release VALUES(?, ?)", data) Notice that we use ``?`` placeholders to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` @@ -125,15 +132,14 @@ See :ref:`sqlite3-placeholders` for more details. Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: - >>> for row in cur2.execute('SELECT * FROM stocks ORDER BY price'): + >>> for row in cur2.execute("SELECT * FROM release ORDER BY version"): ... print(row) - ('2006-01-05', 'BUY', 'RHAT', 100, 35.14) - ('2006-03-28', 'BUY', 'IBM', 1000, 45.0) - ('2006-04-06', 'SELL', 'IBM', 500, 53.0) - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0) + ('1.0', '1994-01-26') + ('2.0', '2000-10-16') + ('3.0', '2008-12-03') -Each row is a five-item :class:`!tuple`. +Each row is a two-item :class:`!tuple`. You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and ran several SQL queries against it. From deb199c2e50cf2bdf17114d3326704cd76f9210d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Aug 2022 11:04:59 +0200 Subject: [PATCH 14/33] move sphinx comment; it creates havoc --- 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 143e299831e197..bf4694ea0177ac 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -47,8 +47,6 @@ This document includes four main sections: PEP written by Marc-André Lemburg. -.. _sqlite3-tutorial: - .. We use the following practises for SQL code: .. - UPPERCASE for keywords .. - lowercase for schema @@ -56,6 +54,8 @@ This document includes four main sections: .. - singular for table names .. - if needed, use double quotes for table and column names +.. _sqlite3-tutorial: + Tutorial -------- From d54ff8d3abf368f94314bea49bc6900b2a22a91a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Aug 2022 23:01:27 +0200 Subject: [PATCH 15/33] Address a new round of reviews from CAM --- Doc/library/sqlite3.rst | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index bf4694ea0177ac..b10c7c6bab0f2b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -48,11 +48,11 @@ This document includes four main sections: .. We use the following practises for SQL code: -.. - UPPERCASE for keywords -.. - lowercase for schema -.. - single quotes for string literals -.. - singular for table names -.. - if needed, use double quotes for table and column names + - UPPERCASE for keywords + - lowercase for schema + - single quotes for string literals + - singular for table names + - if needed, use double quotes for table and column names .. _sqlite3-tutorial: @@ -60,7 +60,7 @@ Tutorial -------- In this tutorial, you will learn the basics of the :mod:`!sqlite3` API -by creating an on-disk database :file:`python.db` +by creating an on-disk database :file:`tutorial.db` for managing Python release metadata. A fundamental understanding of database concepts, like `transactions`_ and `cursors`_, is assumed. @@ -68,35 +68,35 @@ like `transactions`_ and `cursors`_, is assumed. To start working with an SQLite database, you must first open a connection to it. Call the :func:`sqlite3.connect` function -to create a :class:`Connection` to the database :file:`python.db`:: +to create a connection to the database :file:`tutorial.db`:: import sqlite3 - con = sqlite3.connect("python.db") + con = sqlite3.connect("tutorial.db") -SQLite will implicitly create the database file :file:`python.db` +SQLite will implicitly create an empty database file :file:`tutorial.db` in the current working directory if it does not exist. The returned :class:`Connection` object ``con`` -represents our connection to the on-disk database. +represents the connection to the on-disk database. In order to execute SQL statements and fetch results from SQL queries, -we need to use a database cursor. -Call the :meth:`~Connection.cursor` method of ``con`` to create +use a database cursor. +Call :meth:`~Connection.cursor` on ``con`` to create :class:`Cursor` ``cur``:: cur = con.cursor() -Now, let us create a database table ``release`` with columns for version -number and release date, and inserting the release data for Python 1.0. +Now, create a database table ``release`` with columns for version +number and release date, and inserting the data for Python 3.0. Execute the SQL statements -by calling the :meth:`~Cursor.execute` method of ``cur``:: +by calling :meth:`~Cursor.execute` on ``cur``:: cur.execute("""CREATE TABLE release(version, releasedate)""") cur.execute("INSERT INTO release VALUES('3.0', '2008-12-03')") -The ``INSERT`` statement implicitly opens a transaction -that needs to be committed before changes are saved in the database +The ``INSERT`` statement implicitly opens a transaction, +which needs to be committed before changes are saved in the database (see :ref:`sqlite3-controlling-transactions` for details). -Call the :meth:`~Connection.commit` on the connection object +Call :meth:`~Connection.commit` on the connection object to commit the transaction:: con.commit() @@ -107,7 +107,7 @@ creating a new cursor, and then executing a ``SELECT`` query to read from the database:: >>> con.close() - >>> con2 = sqlite3.connect("python.db") + >>> con2 = sqlite3.connect("tutorial.db") >>> cur2 = con2.cursor() >>> res = cur2.execute("SELECT count(rowid) FROM release") >>> print(res.fetchone()) @@ -116,7 +116,7 @@ and then executing a ``SELECT`` query to read from the database:: The result is a one-item :class:`tuple`: one row, with one column. Now, insert three more rows by calling -the :meth:`~Cursor.executemany` method on the cursor object:: +:meth:`~Cursor.executemany` on the cursor object:: data = [ ("2.0", "2000-10-16"), @@ -124,7 +124,7 @@ the :meth:`~Cursor.executemany` method on the cursor object:: ] cur2.executemany("INSERT INTO release VALUES(?, ?)", data) -Notice that we use ``?`` placeholders to bind ``data`` to the query. +Notice that ``?`` placeholders are used to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` to bind Python values to SQL statements, to avoid `SQL injection attacks`_. @@ -140,6 +140,7 @@ Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: ('3.0', '2008-12-03') Each row is a two-item :class:`!tuple`. + You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and ran several SQL queries against it. From e7780123d83b218f78caddd38884d0181bd56412 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 10 Aug 2022 11:21:19 +0200 Subject: [PATCH 16/33] Update the example, be more explicit - Use movie database example - Make sure data is commited - Make the user verify more steps - Also introduce fetchone() and fetchall() cursor methods - Update SQL style to best practice --- Doc/library/sqlite3.rst | 108 ++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 33 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index b10c7c6bab0f2b..f92acc90347721 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -49,7 +49,7 @@ This document includes four main sections: .. We use the following practises for SQL code: - UPPERCASE for keywords - - lowercase for schema + - SnakeCase for schema - single quotes for string literals - singular for table names - if needed, use double quotes for table and column names @@ -60,8 +60,8 @@ Tutorial -------- In this tutorial, you will learn the basics of the :mod:`!sqlite3` API -by creating an on-disk database :file:`tutorial.db` -for managing Python release metadata. +by creating an on-disk movie database :file:`tutorial.db` +an filling it with meta-data about Monty Python movies. A fundamental understanding of database concepts, like `transactions`_ and `cursors`_, is assumed. @@ -85,13 +85,38 @@ Call :meth:`~Connection.cursor` on ``con`` to create cur = con.cursor() -Now, create a database table ``release`` with columns for version -number and release date, and inserting the data for Python 3.0. -Execute the SQL statements +Now, create a database table ``movie`` with columns for title, +year released, and `IMDB`_ review score. +The `flexible typing`_ feature of SQLite makes typenames optional; +for simplicity, just use column names in the table declaration. +Execute the SQL statement by calling :meth:`~Cursor.execute` on ``cur``:: - cur.execute("""CREATE TABLE release(version, releasedate)""") - cur.execute("INSERT INTO release VALUES('3.0', '2008-12-03')") + cur.execute("CREATE TABLE Movie(Title, Year, Score)") + +Verify that the table has been created by running a query against it. +Execute the query by calling :meth:`~Cursor.execute` on ``cur``, +store the result in a variable ``res``, +and call :meth:`~Cursor.fetchone` on ``res`` to fetch the first +(and only) row from the query:: + + >>> res = cur.execute("SELECT count(rowid) FROM Movie") + >>> res.fetchone() + (0,) + +The result is a one-item :class:`tuple`: +one row, with one column. +It correctly shows a row count of zero. + +Now, add two rows of data supplied as SQL literals +by executing an ``INSERT`` statement, +once again by calling :meth:`~Cursor.execute` on ``cur``:: + + cur.execute(""" + INSERT INTO Movie VALUES + ('Monty Python and the Holy Grail', 1975, 8.2), + ('And Now for Something Completely Different', 1971, 7.5) + """) The ``INSERT`` statement implicitly opens a transaction, which needs to be committed before changes are saved in the database @@ -101,28 +126,31 @@ to commit the transaction:: con.commit() -Verify that data has been committed and written to disk by -closing the connection and opening a new one, -creating a new cursor, -and then executing a ``SELECT`` query to read from the database:: +Execute a query to verify that the data was inserted correctly. +Use the now familiar :meth:`~Cursor.execute` method on ``con``, +store the result in ``res``, +and call :meth:`~Cursor.fetchall` on ``res`` to fetch all rows:: - >>> con.close() - >>> con2 = sqlite3.connect("tutorial.db") - >>> cur2 = con2.cursor() - >>> res = cur2.execute("SELECT count(rowid) FROM release") - >>> print(res.fetchone()) - (1,) + >>> res = cur.execute("SELECT Score FROM Movie") + >>> res.fetchall() + [(8.2,), (7.5,)] + +The result is a :class:`list` with two rows of data, +each a one-item :class:`tuple`, +the single item representing the column queried. -The result is a one-item :class:`tuple`: -one row, with one column. Now, insert three more rows by calling -:meth:`~Cursor.executemany` on the cursor object:: +:meth:`~Cursor.executemany` on the cursor object, +once again committing the transaction by calling :meth:`~Connection.commit` +on the connection object:: data = [ - ("2.0", "2000-10-16"), - ("1.0", "1994-01-26"), + ("Monty Python Live at the Hollywood Bowl", 1982, 7.9), + ("Monty Python's The Meaning of Life", 1983, 7.5), + ("Monty Python's Life of Brian", 1979, 8.0), ] - cur2.executemany("INSERT INTO release VALUES(?, ?)", data) + cur.executemany("INSERT INTO Movie VALUES(?, ?, ?)", data) + con.commit() Notice that ``?`` placeholders are used to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` @@ -130,23 +158,37 @@ to bind Python values to SQL statements, to avoid `SQL injection attacks`_. See :ref:`sqlite3-placeholders` for more details. -Now, retrieve the rows by iterating over the result of a ``SELECT`` query:: +Verify that data has been committed and written to disk by +closing the connection and opening a new one, +creating a new cursor, +and then executing a ``SELECT`` query to read from the database:: - >>> for row in cur2.execute("SELECT * FROM release ORDER BY version"): - ... print(row) + >>> con.close() + >>> con2 = sqlite3.connect("tutorial.db") + >>> cur2 = con2.cursor() + +This time, retrieve the rows by iterating over the result +of the ``SELECT`` query:: - ('1.0', '1994-01-26') - ('2.0', '2000-10-16') - ('3.0', '2008-12-03') + >>> for row in cur2.execute("SELECT Year, Title FROM Movie ORDER BY Year"): + ... print(row) + ... + (1971, "And Now for Something Completely Different") + (1975, "Monty Python and the Holy Grail") + (1979, "Monty Python's Life of Brian") + (1982, "Monty Python Live at the Hollywood Bowl") + (1983, "Monty Python's The Meaning of Life") -Each row is a two-item :class:`!tuple`. +Each row is now a two-item :class:`!tuple`. You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and ran several SQL queries against it. -.. _transactions: https://en.wikipedia.org/wiki/Database_transaction -.. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) +.. _IMDB: https://www.imdb.com/ .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection +.. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) +.. _flexible typing: https://www.sqlite.org/flextypegood.html +.. _transactions: https://en.wikipedia.org/wiki/Database_transaction .. _sqlite3-reference: From e9e1ffa7f5736d40341783de836079705d90a560 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 11 Aug 2022 11:52:44 +0200 Subject: [PATCH 17/33] Address review comments --- Doc/library/sqlite3.rst | 85 +++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index f92acc90347721..7422f479325c81 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -61,56 +61,64 @@ Tutorial In this tutorial, you will learn the basics of the :mod:`!sqlite3` API by creating an on-disk movie database :file:`tutorial.db` -an filling it with meta-data about Monty Python movies. -A fundamental understanding of database concepts, -like `transactions`_ and `cursors`_, is assumed. +about Monty Python movies. +It assumes a fundamental understanding of database concepts, +including `cursors`_ and `transactions`_. -To start working with an SQLite database, -you must first open a connection to it. -Call the :func:`sqlite3.connect` function -to create a connection to the database :file:`tutorial.db`:: +First, create a new database to hold the movie data, +and open a database connection to allow :mod:`!sqlite3` to work with it. +Call the :func:`sqlite3.connect` function to +to create a connection to the database :file:`tutorial.db`, +implicitly creating it in the current working directory if it does not exist:: import sqlite3 con = sqlite3.connect("tutorial.db") -SQLite will implicitly create an empty database file :file:`tutorial.db` -in the current working directory if it does not exist. The returned :class:`Connection` object ``con`` represents the connection to the on-disk database. In order to execute SQL statements and fetch results from SQL queries, use a database cursor. -Call :meth:`~Connection.cursor` on ``con`` to create -:class:`Cursor` ``cur``:: +Call :meth:`con.cursor() ` to create the :class:`Cursor`:: cur = con.cursor() Now, create a database table ``movie`` with columns for title, -year released, and `IMDB`_ review score. +release year, and review score. The `flexible typing`_ feature of SQLite makes typenames optional; for simplicity, just use column names in the table declaration. Execute the SQL statement -by calling :meth:`~Cursor.execute` on ``cur``:: +by calling :meth:`con.execute() `:: cur.execute("CREATE TABLE Movie(Title, Year, Score)") -Verify that the table has been created by running a query against it. -Execute the query by calling :meth:`~Cursor.execute` on ``cur``, +.. Ideally, we'd use sqlite_schema instead of sqlite_master below, + but earlier versions of SQLite does not recognise that variant. +You can verify that the table has been created by querying +the ever-present ``sqlite_master`` table. +It should contain an entry for the ``Movie`` table definition. +Execute the query by calling :meth:`cur.execute(...) `, store the result in a variable ``res``, -and call :meth:`~Cursor.fetchone` on ``res`` to fetch the first +and call :meth:`res.fetchone() ` to fetch the first (and only) row from the query:: - >>> res = cur.execute("SELECT count(rowid) FROM Movie") + >>> res = cur.execute("SELECT name FROM sqlite_master where name='Movie'") >>> res.fetchone() - (0,) + ('Movie',) -The result is a one-item :class:`tuple`: -one row, with one column. -It correctly shows a row count of zero. +The result is a one-item :class:`tuple`: one row, with one column. +As expected, the query shows the table is now created. +As an exercise, try querying ``sqlite_master`` for a bogus name ``"abc"``:: + + >>> res = cur.execute("SELECT name FROM sqlite_master where name='abc'") + >>> res.fetchone() + >>> + +As expected, the query returns an empty result. Now, add two rows of data supplied as SQL literals by executing an ``INSERT`` statement, -once again by calling :meth:`~Cursor.execute` on ``cur``:: +once again by calling :meth:`cur.execute(...) `:: cur.execute(""" INSERT INTO Movie VALUES @@ -121,28 +129,26 @@ once again by calling :meth:`~Cursor.execute` on ``cur``:: The ``INSERT`` statement implicitly opens a transaction, which needs to be committed before changes are saved in the database (see :ref:`sqlite3-controlling-transactions` for details). -Call :meth:`~Connection.commit` on the connection object +Call :meth:`con.commit() ` on the connection object to commit the transaction:: con.commit() Execute a query to verify that the data was inserted correctly. -Use the now familiar :meth:`~Cursor.execute` method on ``con``, +Use the now familiar :meth:`con.execute(...) ` to store the result in ``res``, -and call :meth:`~Cursor.fetchall` on ``res`` to fetch all rows:: +and call :meth:`res.fetchall() ` to fetch all rows:: >>> res = cur.execute("SELECT Score FROM Movie") >>> res.fetchall() [(8.2,), (7.5,)] The result is a :class:`list` with two rows of data, -each a one-item :class:`tuple`, -the single item representing the column queried. +each a one-item :class:`!tuple`, +containing the value from the single column queried. Now, insert three more rows by calling -:meth:`~Cursor.executemany` on the cursor object, -once again committing the transaction by calling :meth:`~Connection.commit` -on the connection object:: +:meth:`cur.executemany(...) `:: data = [ ("Monty Python Live at the Hollywood Bowl", 1982, 7.9), @@ -150,7 +156,7 @@ on the connection object:: ("Monty Python's Life of Brian", 1979, 8.0), ] cur.executemany("INSERT INTO Movie VALUES(?, ?, ?)", data) - con.commit() + con.commit() # Remember to commit the transaction after executing INSERT. Notice that ``?`` placeholders are used to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` @@ -158,18 +164,15 @@ to bind Python values to SQL statements, to avoid `SQL injection attacks`_. See :ref:`sqlite3-placeholders` for more details. -Verify that data has been committed and written to disk by -closing the connection and opening a new one, -creating a new cursor, -and then executing a ``SELECT`` query to read from the database:: +Verify that data has been written to disk by +calling :meth:`con.close() ` to close the connection, +opening a new one, creating a new cursor, +then executing a ``SELECT`` query, this time iterating over the results +of the query to read from the database:: >>> con.close() >>> con2 = sqlite3.connect("tutorial.db") >>> cur2 = con2.cursor() - -This time, retrieve the rows by iterating over the result -of the ``SELECT`` query:: - >>> for row in cur2.execute("SELECT Year, Title FROM Movie ORDER BY Year"): ... print(row) ... @@ -179,16 +182,16 @@ of the ``SELECT`` query:: (1982, "Monty Python Live at the Hollywood Bowl") (1983, "Monty Python's The Meaning of Life") -Each row is now a two-item :class:`!tuple`. +Each row is now a two-item :class:`tuple` of ``(Year, Title)``. You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and ran several SQL queries against it. -.. _IMDB: https://www.imdb.com/ .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection .. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) .. _flexible typing: https://www.sqlite.org/flextypegood.html .. _transactions: https://en.wikipedia.org/wiki/Database_transaction +.. _sqlite_master: https://www.sqlite.org/schematab.html .. _sqlite3-reference: From f60e3a25b220b7e1687de0a42ef4773a1af1e30f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 11 Aug 2022 12:05:04 +0200 Subject: [PATCH 18/33] Missing blank line below Sphinx comment --- Doc/library/sqlite3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 7422f479325c81..27627ce840f7ef 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -94,6 +94,7 @@ by calling :meth:`con.execute() `:: .. Ideally, we'd use sqlite_schema instead of sqlite_master below, but earlier versions of SQLite does not recognise that variant. + You can verify that the table has been created by querying the ever-present ``sqlite_master`` table. It should contain an entry for the ``Movie`` table definition. From 5d03a4e252f605fa41780d298f41953d1f76b602 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 12 Aug 2022 12:46:53 +0200 Subject: [PATCH 19/33] Address reviews from 2022-08-11 --- Doc/library/sqlite3.rst | 95 ++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 27627ce840f7ef..15e5cd3b41cce2 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -49,7 +49,7 @@ This document includes four main sections: .. We use the following practises for SQL code: - UPPERCASE for keywords - - SnakeCase for schema + - snake_case for schema - single quotes for string literals - singular for table names - if needed, use double quotes for table and column names @@ -60,22 +60,22 @@ Tutorial -------- In this tutorial, you will learn the basics of the :mod:`!sqlite3` API -by creating an on-disk movie database :file:`tutorial.db` -about Monty Python movies. +by creating a database of Monty Python movies. It assumes a fundamental understanding of database concepts, including `cursors`_ and `transactions`_. -First, create a new database to hold the movie data, +First, you'll need to create a new database to hold the movie data and open a database connection to allow :mod:`!sqlite3` to work with it. Call the :func:`sqlite3.connect` function to -to create a connection to the database :file:`tutorial.db`, -implicitly creating it in the current working directory if it does not exist:: +to create a connection to a database :file:`tutorial.db` +in the current working directory, +implicitly creating it if it does not exist:: import sqlite3 con = sqlite3.connect("tutorial.db") The returned :class:`Connection` object ``con`` -represents the connection to the on-disk database. +represents the connection to the database. In order to execute SQL statements and fetch results from SQL queries, use a database cursor. @@ -85,33 +85,35 @@ Call :meth:`con.cursor() ` to create the :class:`Cursor`:: Now, create a database table ``movie`` with columns for title, release year, and review score. -The `flexible typing`_ feature of SQLite makes typenames optional; -for simplicity, just use column names in the table declaration. -Execute the SQL statement +For simplicitly, just use column names in the table declaration: +thanks to the `flexible typing`_ feature of SQLite, +specifying the data types is optional. +Execute the ``CREATE TABLE`` statement by calling :meth:`con.execute() `:: - cur.execute("CREATE TABLE Movie(Title, Year, Score)") + cur.execute("CREATE TABLE movie(title, year, score)") .. Ideally, we'd use sqlite_schema instead of sqlite_master below, - but earlier versions of SQLite does not recognise that variant. + but earlier versions of SQLite do not recognise that variant. -You can verify that the table has been created by querying -the ever-present ``sqlite_master`` table. -It should contain an entry for the ``Movie`` table definition. -Execute the query by calling :meth:`cur.execute(...) `, +You can verify that the new table has been created by querying +the ``sqlite_master`` table built-in to SQLite. +It should contain an entry for the ``movie`` table definition. +Execute that query by calling :meth:`cur.execute(...) `, store the result in a variable ``res``, and call :meth:`res.fetchone() ` to fetch the first -(and only) row from the query:: +(and only) row that was returned:: - >>> res = cur.execute("SELECT name FROM sqlite_master where name='Movie'") + >>> res = cur.execute("SELECT name FROM sqlite_master") >>> res.fetchone() - ('Movie',) + ('movie',) -The result is a one-item :class:`tuple`: one row, with one column. -As expected, the query shows the table is now created. -As an exercise, try querying ``sqlite_master`` for a bogus name ``"abc"``:: +As expected, the query shows the table is now created +by returning a single :class:`tuple`: with the name of the table. +As an exercise, try querying ``sqlite_master`` +for a non-existent table ``"abc"``:: - >>> res = cur.execute("SELECT name FROM sqlite_master where name='abc'") + >>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='abc'") >>> res.fetchone() >>> @@ -122,7 +124,7 @@ by executing an ``INSERT`` statement, once again by calling :meth:`cur.execute(...) `:: cur.execute(""" - INSERT INTO Movie VALUES + INSERT INTO movie VALUES ('Monty Python and the Holy Grail', 1975, 8.2), ('And Now for Something Completely Different', 1971, 7.5) """) @@ -136,17 +138,16 @@ to commit the transaction:: con.commit() Execute a query to verify that the data was inserted correctly. -Use the now familiar :meth:`con.execute(...) ` to +Use the now-familiar :meth:`con.execute(...) ` to store the result in ``res``, and call :meth:`res.fetchall() ` to fetch all rows:: - >>> res = cur.execute("SELECT Score FROM Movie") + >>> res = cur.execute("SELECT score FROM movie") >>> res.fetchall() [(8.2,), (7.5,)] -The result is a :class:`list` with two rows of data, -each a one-item :class:`!tuple`, -containing the value from the single column queried. +The result is a :class:`list` of two :class:`!tuple`\s, one per row, +each containing a the ``score`` from the query. Now, insert three more rows by calling :meth:`cur.executemany(...) `:: @@ -156,35 +157,45 @@ Now, insert three more rows by calling ("Monty Python's The Meaning of Life", 1983, 7.5), ("Monty Python's Life of Brian", 1979, 8.0), ] - cur.executemany("INSERT INTO Movie VALUES(?, ?, ?)", data) + cur.executemany("INSERT INTO movie VALUES(?, ?, ?)", data) con.commit() # Remember to commit the transaction after executing INSERT. Notice that ``?`` placeholders are used to bind ``data`` to the query. Always use placeholders instead of :ref:`string formatting ` to bind Python values to SQL statements, -to avoid `SQL injection attacks`_. -See :ref:`sqlite3-placeholders` for more details. +to avoid `SQL injection attacks`_ +(see :ref:`sqlite3-placeholders` for more details). -Verify that data has been written to disk by -calling :meth:`con.close() ` to close the connection, +Verify that the new rows were inserted by executing a ``SELECT`` query, +this time iterating over the results of the query:: + + >>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"): + ... print(row) + (1971, "And Now for Something Completely Different") + (1975, "Monty Python and the Holy Grail") + (1979, "Monty Python's Life of Brian") + (1982, "Monty Python Live at the Hollywood Bowl") + (1983, "Monty Python's The Meaning of Life") + +Each row is now a two-item :class:`tuple` of ``(year, title)``. + +At last, verify that the database has been written to disk by +calling :meth:`con.close() ` +to close the existing connection, opening a new one, creating a new cursor, -then executing a ``SELECT`` query, this time iterating over the results -of the query to read from the database:: +then reusing the query from above to read from the database:: >>> con.close() >>> con2 = sqlite3.connect("tutorial.db") >>> cur2 = con2.cursor() - >>> for row in cur2.execute("SELECT Year, Title FROM Movie ORDER BY Year"): + >>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"): ... print(row) - ... (1971, "And Now for Something Completely Different") (1975, "Monty Python and the Holy Grail") (1979, "Monty Python's Life of Brian") (1982, "Monty Python Live at the Hollywood Bowl") (1983, "Monty Python's The Meaning of Life") -Each row is now a two-item :class:`tuple` of ``(Year, Title)``. - You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and ran several SQL queries against it. @@ -194,6 +205,10 @@ inserted data and ran several SQL queries against it. .. _transactions: https://en.wikipedia.org/wiki/Database_transaction .. _sqlite_master: https://www.sqlite.org/schematab.html +.. seealso:: + + * :ref:`sqlite3-howtos` for details how to handle specific tasks. + * :ref:`sqlite3-explanation` for in-depth background on transaction control. .. _sqlite3-reference: From 3ec75d1fd118ecfca9c8be4cacdba57e29b6c6e6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 12 Aug 2022 13:16:32 +0200 Subject: [PATCH 20/33] Tone: use second person singular more often --- Doc/library/sqlite3.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 15e5cd3b41cce2..c4c87bddded52c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -78,12 +78,12 @@ The returned :class:`Connection` object ``con`` represents the connection to the database. In order to execute SQL statements and fetch results from SQL queries, -use a database cursor. +you'll use a database cursor. Call :meth:`con.cursor() ` to create the :class:`Cursor`:: cur = con.cursor() -Now, create a database table ``movie`` with columns for title, +Now you'll create a database table ``movie`` with columns for title, release year, and review score. For simplicitly, just use column names in the table declaration: thanks to the `flexible typing`_ feature of SQLite, @@ -119,7 +119,7 @@ for a non-existent table ``"abc"``:: As expected, the query returns an empty result. -Now, add two rows of data supplied as SQL literals +Now, you'll add two rows of data supplied as SQL literals by executing an ``INSERT`` statement, once again by calling :meth:`cur.execute(...) `:: @@ -137,7 +137,8 @@ to commit the transaction:: con.commit() -Execute a query to verify that the data was inserted correctly. +You can verify that the data was inserted correctly +by executing a ``SELECT`` query. Use the now-familiar :meth:`con.execute(...) ` to store the result in ``res``, and call :meth:`res.fetchall() ` to fetch all rows:: @@ -149,7 +150,7 @@ and call :meth:`res.fetchall() ` to fetch all rows:: The result is a :class:`list` of two :class:`!tuple`\s, one per row, each containing a the ``score`` from the query. -Now, insert three more rows by calling +Now, you'll insert three more rows by calling :meth:`cur.executemany(...) `:: data = [ @@ -166,7 +167,8 @@ to bind Python values to SQL statements, to avoid `SQL injection attacks`_ (see :ref:`sqlite3-placeholders` for more details). -Verify that the new rows were inserted by executing a ``SELECT`` query, +You can verify that the new rows were inserted +by executing a ``SELECT`` query, this time iterating over the results of the query:: >>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"): @@ -179,7 +181,7 @@ this time iterating over the results of the query:: Each row is now a two-item :class:`tuple` of ``(year, title)``. -At last, verify that the database has been written to disk by +At last, you'll verify that the database has been written to disk by calling :meth:`con.close() ` to close the existing connection, opening a new one, creating a new cursor, From 84d798fd8602a9599794a8ca1ab8fdcfc7afdf27 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 14 Aug 2022 23:22:30 +0200 Subject: [PATCH 21/33] Adressing the last rounds of comments from CAM, Daniele, and Ezio --- Doc/library/sqlite3.rst | 72 +++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c4c87bddded52c..cbfe590860fd4a 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -59,59 +59,58 @@ This document includes four main sections: Tutorial -------- -In this tutorial, you will learn the basics of the :mod:`!sqlite3` API -by creating a database of Monty Python movies. +In this tutorial, you will create a database of Monty Python movies, +using basic :mod:`!sqlite3` APIs. It assumes a fundamental understanding of database concepts, including `cursors`_ and `transactions`_. -First, you'll need to create a new database to hold the movie data -and open a database connection to allow :mod:`!sqlite3` to work with it. -Call the :func:`sqlite3.connect` function to -to create a connection to a database :file:`tutorial.db` -in the current working directory, +First, you will need to create a new database and open +a database connection to allow :mod:`!sqlite3` to work with it. +Call :func:`sqlite3.connect` to to create a connection to +the database :file:`tutorial.db` in the current working directory, implicitly creating it if it does not exist:: import sqlite3 con = sqlite3.connect("tutorial.db") The returned :class:`Connection` object ``con`` -represents the connection to the database. +represents the connection to the on-disk database. In order to execute SQL statements and fetch results from SQL queries, -you'll use a database cursor. +you will use a database cursor. Call :meth:`con.cursor() ` to create the :class:`Cursor`:: cur = con.cursor() -Now you'll create a database table ``movie`` with columns for title, +Now that you've got a database connection and a cursor, +you can create a database table ``movie`` with columns for title, release year, and review score. -For simplicitly, just use column names in the table declaration: +For simplicity, just use column names in the table declaration -- thanks to the `flexible typing`_ feature of SQLite, specifying the data types is optional. Execute the ``CREATE TABLE`` statement -by calling :meth:`con.execute() `:: +by calling :meth:`con.execute(...) `:: cur.execute("CREATE TABLE movie(title, year, score)") .. Ideally, we'd use sqlite_schema instead of sqlite_master below, - but earlier versions of SQLite do not recognise that variant. + but SQLite versions older than 3.33.0 do not recognise that variant. You can verify that the new table has been created by querying -the ``sqlite_master`` table built-in to SQLite. -It should contain an entry for the ``movie`` table definition. +the ``sqlite_master`` table built-in to SQLite, +which should contain an entry for the ``movie`` table definition. Execute that query by calling :meth:`cur.execute(...) `, -store the result in a variable ``res``, -and call :meth:`res.fetchone() ` to fetch the first -(and only) row that was returned:: +assigning the result to ``res``, +and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res = cur.execute("SELECT name FROM sqlite_master") >>> res.fetchone() ('movie',) -As expected, the query shows the table is now created -by returning a single :class:`tuple`: with the name of the table. +As expected, the query shows the table has been created, +as it returns a :class:`tuple`: containing the name of the table. As an exercise, try querying ``sqlite_master`` -for a non-existent table ``"abc"``:: +for a non-existent table ``abc``:: >>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='abc'") >>> res.fetchone() @@ -140,15 +139,18 @@ to commit the transaction:: You can verify that the data was inserted correctly by executing a ``SELECT`` query. Use the now-familiar :meth:`con.execute(...) ` to -store the result in ``res``, -and call :meth:`res.fetchall() ` to fetch all rows:: +assign the result to ``res``, +and call :meth:`res.fetchall() ` to +fetch a :class:`list` of all resulting rows:: >>> res = cur.execute("SELECT score FROM movie") >>> res.fetchall() [(8.2,), (7.5,)] +As expected, the resulting :class:`!list` contains two rows +as :class:`!tuple`\s of ``(score,)``. The result is a :class:`list` of two :class:`!tuple`\s, one per row, -each containing a the ``score`` from the query. +each containing a the ``score`` selected in the query. Now, you'll insert three more rows by calling :meth:`cur.executemany(...) `:: @@ -179,18 +181,19 @@ this time iterating over the results of the query:: (1982, "Monty Python Live at the Hollywood Bowl") (1983, "Monty Python's The Meaning of Life") -Each row is now a two-item :class:`tuple` of ``(year, title)``. +Each row is a two-item :class:`tuple` of ``(year, title)``, +matching the columns selected in the query. -At last, you'll verify that the database has been written to disk by +Finally, you'll verify that the database has been written to disk by calling :meth:`con.close() ` to close the existing connection, opening a new one, creating a new cursor, then reusing the query from above to read from the database:: >>> con.close() - >>> con2 = sqlite3.connect("tutorial.db") - >>> cur2 = con2.cursor() - >>> for row in cur.execute("SELECT year, title FROM movie ORDER BY year"): + >>> new_con = sqlite3.connect("tutorial.db") + >>> new_cur = new_con.cursor() + >>> for row in new_cur.execute("SELECT year, title FROM movie ORDER BY year"): ... print(row) (1971, "And Now for Something Completely Different") (1975, "Monty Python and the Holy Grail") @@ -199,7 +202,7 @@ then reusing the query from above to read from the database:: (1983, "Monty Python's The Meaning of Life") You've now created an SQLite database using the :mod:`!sqlite3` module, -inserted data and ran several SQL queries against it. +inserted data and retrieved values from it in multiple ways. .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection .. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) @@ -209,7 +212,14 @@ inserted data and ran several SQL queries against it. .. seealso:: - * :ref:`sqlite3-howtos` for details how to handle specific tasks. + * :ref:`sqlite3-howtos` for further reading: + + * :ref:`sqlite3-placeholders` + * :ref:`sqlite3-adapters` + * :ref:`sqlite3-converters` + * :ref:`sqlite3-columns-by-name` + * :ref:`sqlite3-connection-context-manager` + * :ref:`sqlite3-explanation` for in-depth background on transaction control. .. _sqlite3-reference: From d09d9f6395822b701bbabafe107a606f0a9a6fbd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 14 Aug 2022 23:29:36 +0200 Subject: [PATCH 22/33] you will need to => you need to --- 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 cbfe590860fd4a..6159e9a8fd9c4e 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -64,7 +64,7 @@ using basic :mod:`!sqlite3` APIs. It assumes a fundamental understanding of database concepts, including `cursors`_ and `transactions`_. -First, you will need to create a new database and open +First, you need to create a new database and open a database connection to allow :mod:`!sqlite3` to work with it. Call :func:`sqlite3.connect` to to create a connection to the database :file:`tutorial.db` in the current working directory, @@ -77,7 +77,7 @@ The returned :class:`Connection` object ``con`` represents the connection to the on-disk database. In order to execute SQL statements and fetch results from SQL queries, -you will use a database cursor. +you use a database cursor. Call :meth:`con.cursor() ` to create the :class:`Cursor`:: cur = con.cursor() From d6f4775c929e7d88f1d295ed6aa9e0e7a270a709 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 14 Aug 2022 23:34:45 +0200 Subject: [PATCH 23/33] Address more review comments --- Doc/library/sqlite3.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6159e9a8fd9c4e..c4d7c61ee430c0 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -140,17 +140,14 @@ You can verify that the data was inserted correctly by executing a ``SELECT`` query. Use the now-familiar :meth:`con.execute(...) ` to assign the result to ``res``, -and call :meth:`res.fetchall() ` to -fetch a :class:`list` of all resulting rows:: +and call :meth:`res.fetchall() ` to all resulting rows:: >>> res = cur.execute("SELECT score FROM movie") >>> res.fetchall() [(8.2,), (7.5,)] -As expected, the resulting :class:`!list` contains two rows -as :class:`!tuple`\s of ``(score,)``. The result is a :class:`list` of two :class:`!tuple`\s, one per row, -each containing a the ``score`` selected in the query. +each containing that row's ``score`` value. Now, you'll insert three more rows by calling :meth:`cur.executemany(...) `:: From b46942baf3d345128877ec6ff226fae81870f43d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 15 Aug 2022 09:21:00 +0200 Subject: [PATCH 24/33] Address CAM's last round of comments; use first person plural --- Doc/library/sqlite3.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c4d7c61ee430c0..5d04041258e492 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -59,12 +59,12 @@ This document includes four main sections: Tutorial -------- -In this tutorial, you will create a database of Monty Python movies, -using basic :mod:`!sqlite3` APIs. +In this tutorial, you will create a database of Monty Python movies +using basic :mod:`!sqlite3` functionality. It assumes a fundamental understanding of database concepts, including `cursors`_ and `transactions`_. -First, you need to create a new database and open +First, we need to create a new database and open a database connection to allow :mod:`!sqlite3` to work with it. Call :func:`sqlite3.connect` to to create a connection to the database :file:`tutorial.db` in the current working directory, @@ -77,15 +77,15 @@ The returned :class:`Connection` object ``con`` represents the connection to the on-disk database. In order to execute SQL statements and fetch results from SQL queries, -you use a database cursor. +we will need to use a database cursor. Call :meth:`con.cursor() ` to create the :class:`Cursor`:: cur = con.cursor() -Now that you've got a database connection and a cursor, -you can create a database table ``movie`` with columns for title, +Now that we've got a database connection and a cursor, +we can create a database table ``movie`` with columns for title, release year, and review score. -For simplicity, just use column names in the table declaration -- +For simplicity, we can just use column names in the table declaration -- thanks to the `flexible typing`_ feature of SQLite, specifying the data types is optional. Execute the ``CREATE TABLE`` statement @@ -96,11 +96,11 @@ by calling :meth:`con.execute(...) `:: .. Ideally, we'd use sqlite_schema instead of sqlite_master below, but SQLite versions older than 3.33.0 do not recognise that variant. -You can verify that the new table has been created by querying +We can verify that the new table has been created by querying the ``sqlite_master`` table built-in to SQLite, which should contain an entry for the ``movie`` table definition. Execute that query by calling :meth:`cur.execute(...) `, -assigning the result to ``res``, +assign the result to ``res``, and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res = cur.execute("SELECT name FROM sqlite_master") @@ -108,7 +108,7 @@ and call :meth:`res.fetchone() ` to fetch the resulting row:: ('movie',) As expected, the query shows the table has been created, -as it returns a :class:`tuple`: containing the name of the table. +as it returns a :class:`tuple`: containing the table's name. As an exercise, try querying ``sqlite_master`` for a non-existent table ``abc``:: @@ -116,9 +116,9 @@ for a non-existent table ``abc``:: >>> res.fetchone() >>> -As expected, the query returns an empty result. +Indeed, the query returns an empty result. -Now, you'll add two rows of data supplied as SQL literals +Now, add two rows of data supplied as SQL literals by executing an ``INSERT`` statement, once again by calling :meth:`cur.execute(...) `:: @@ -136,11 +136,11 @@ to commit the transaction:: con.commit() -You can verify that the data was inserted correctly +We can verify that the data was inserted correctly by executing a ``SELECT`` query. Use the now-familiar :meth:`con.execute(...) ` to assign the result to ``res``, -and call :meth:`res.fetchall() ` to all resulting rows:: +and call :meth:`res.fetchall() ` to return all resulting rows:: >>> res = cur.execute("SELECT score FROM movie") >>> res.fetchall() @@ -149,7 +149,7 @@ and call :meth:`res.fetchall() ` to all resulting rows:: The result is a :class:`list` of two :class:`!tuple`\s, one per row, each containing that row's ``score`` value. -Now, you'll insert three more rows by calling +Now, insert three more rows by calling :meth:`cur.executemany(...) `:: data = [ @@ -166,7 +166,7 @@ to bind Python values to SQL statements, to avoid `SQL injection attacks`_ (see :ref:`sqlite3-placeholders` for more details). -You can verify that the new rows were inserted +We can verify that the new rows were inserted by executing a ``SELECT`` query, this time iterating over the results of the query:: @@ -181,8 +181,8 @@ this time iterating over the results of the query:: Each row is a two-item :class:`tuple` of ``(year, title)``, matching the columns selected in the query. -Finally, you'll verify that the database has been written to disk by -calling :meth:`con.close() ` +Finally, verify that the database has been written to disk +by calling :meth:`con.close() ` to close the existing connection, opening a new one, creating a new cursor, then reusing the query from above to read from the database:: From 4a51e1e6dba0a1ef29d2f3f61e6cde2cbe6cd04b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 15 Aug 2022 09:23:23 +0200 Subject: [PATCH 25/33] Typos --- 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 5d04041258e492..c8ec12e2055f91 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -89,7 +89,7 @@ For simplicity, we can just use column names in the table declaration -- thanks to the `flexible typing`_ feature of SQLite, specifying the data types is optional. Execute the ``CREATE TABLE`` statement -by calling :meth:`con.execute(...) `:: +by calling :meth:`cur.execute(...) `:: cur.execute("CREATE TABLE movie(title, year, score)") @@ -138,7 +138,7 @@ to commit the transaction:: We can verify that the data was inserted correctly by executing a ``SELECT`` query. -Use the now-familiar :meth:`con.execute(...) ` to +Use the now-familiar :meth:`cur.execute(...) ` to assign the result to ``res``, and call :meth:`res.fetchall() ` to return all resulting rows:: From 92fa6935848df760712160919eaecafc26b0edd1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 15 Aug 2022 09:53:28 +0200 Subject: [PATCH 26/33] Nits and a link --- Doc/library/sqlite3.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c8ec12e2055f91..e82244f61e2745 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -98,7 +98,8 @@ by calling :meth:`cur.execute(...) `:: We can verify that the new table has been created by querying the ``sqlite_master`` table built-in to SQLite, -which should contain an entry for the ``movie`` table definition. +which should now contain an entry for the ``movie`` table definition +(see `The Schema Table`_ for details). Execute that query by calling :meth:`cur.execute(...) `, assign the result to ``res``, and call :meth:`res.fetchone() ` to fetch the resulting row:: @@ -108,7 +109,7 @@ and call :meth:`res.fetchone() ` to fetch the resulting row:: ('movie',) As expected, the query shows the table has been created, -as it returns a :class:`tuple`: containing the table's name. +as it returns a :class:`tuple` containing the table's name. As an exercise, try querying ``sqlite_master`` for a non-existent table ``abc``:: @@ -202,6 +203,7 @@ You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and retrieved values from it in multiple ways. .. _SQL injection attacks: https://en.wikipedia.org/wiki/SQL_injection +.. _The Schema Table: https://www.sqlite.org/schematab.html .. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) .. _flexible typing: https://www.sqlite.org/flextypegood.html .. _transactions: https://en.wikipedia.org/wiki/Database_transaction From d40c2a7717e5a5361ee06333a8bdd24fb79c3e3f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 16 Aug 2022 10:19:09 +0200 Subject: [PATCH 27/33] Adjust 'non-existent' example; adjust final query; sort links --- Doc/library/sqlite3.rst | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index e82244f61e2745..fdf578198b620c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -110,15 +110,13 @@ and call :meth:`res.fetchone() ` to fetch the resulting row:: As expected, the query shows the table has been created, as it returns a :class:`tuple` containing the table's name. -As an exercise, try querying ``sqlite_master`` -for a non-existent table ``abc``:: +If we query ``sqlite_master`` for a non-existent table ``spam``, +:meth:`!res.fetchone()` will return ``None``:: - >>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='abc'") + >>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='spam'") >>> res.fetchone() >>> -Indeed, the query returns an empty result. - Now, add two rows of data supplied as SQL literals by executing an ``INSERT`` statement, once again by calling :meth:`cur.execute(...) `:: @@ -184,20 +182,16 @@ matching the columns selected in the query. Finally, verify that the database has been written to disk by calling :meth:`con.close() ` -to close the existing connection, -opening a new one, creating a new cursor, -then reusing the query from above to read from the database:: +to close the existing connection, opening a new one, +creating a new cursor, then querying the database:: >>> con.close() >>> new_con = sqlite3.connect("tutorial.db") >>> new_cur = new_con.cursor() - >>> for row in new_cur.execute("SELECT year, title FROM movie ORDER BY year"): - ... print(row) - (1971, "And Now for Something Completely Different") - (1975, "Monty Python and the Holy Grail") - (1979, "Monty Python's Life of Brian") - (1982, "Monty Python Live at the Hollywood Bowl") - (1983, "Monty Python's The Meaning of Life") + >>> res = new_cur.execute("SELECT year, title FROM movie ORDER BY score DESC"): + >>> title, year = res.fetchone() + >>> print(f'The highest scoring Monty Python movie is "{title}", released in {year}') + 'The highest scoring Monty Python movie is "Monty Python and the Holy Grail", released in 1975' You've now created an SQLite database using the :mod:`!sqlite3` module, inserted data and retrieved values from it in multiple ways. @@ -206,8 +200,8 @@ inserted data and retrieved values from it in multiple ways. .. _The Schema Table: https://www.sqlite.org/schematab.html .. _cursors: https://en.wikipedia.org/wiki/Cursor_(databases) .. _flexible typing: https://www.sqlite.org/flextypegood.html -.. _transactions: https://en.wikipedia.org/wiki/Database_transaction .. _sqlite_master: https://www.sqlite.org/schematab.html +.. _transactions: https://en.wikipedia.org/wiki/Database_transaction .. seealso:: From d2c39686e09acaf3356f3c8744bf4e9c22de6368 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 16 Aug 2022 10:38:12 +0200 Subject: [PATCH 28/33] Rewrite an 'as expected' --- 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 fdf578198b620c..7bd0ae7490c97c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -108,7 +108,7 @@ and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res.fetchone() ('movie',) -As expected, the query shows the table has been created, +The query shows that the table has been created, as it returns a :class:`tuple` containing the table's name. If we query ``sqlite_master`` for a non-existent table ``spam``, :meth:`!res.fetchone()` will return ``None``:: From 32a19e9d0949f1a73c5dc5f6390d3a15c52632bc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 17 Aug 2022 22:35:49 +0200 Subject: [PATCH 29/33] Fix string format anti-pattern --- 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 7bd0ae7490c97c..599c7fd28a65d8 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -190,7 +190,7 @@ creating a new cursor, then querying the database:: >>> new_cur = new_con.cursor() >>> res = new_cur.execute("SELECT year, title FROM movie ORDER BY score DESC"): >>> title, year = res.fetchone() - >>> print(f'The highest scoring Monty Python movie is "{title}", released in {year}') + >>> print(f'The highest scoring Monty Python movie is {title!r}, released in {year}') 'The highest scoring Monty Python movie is "Monty Python and the Holy Grail", released in 1975' You've now created an SQLite database using the :mod:`!sqlite3` module, From dd3858735fdc94d7267aca4f6cddd63232fad7ff Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 18 Aug 2022 11:10:12 +0200 Subject: [PATCH 30/33] Clarify how we verify that the table has been created Co-authored-by: C.A.M. Gerlach --- 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 599c7fd28a65d8..c96553564cd821 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -108,8 +108,8 @@ and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res.fetchone() ('movie',) -The query shows that the table has been created, -as it returns a :class:`tuple` containing the table's name. +We can see that the table has been created, +as the query returns a :class:`tuple` containing the table's name. If we query ``sqlite_master`` for a non-existent table ``spam``, :meth:`!res.fetchone()` will return ``None``:: From f41be9b54d6c4b72ea916113ac121661ef1bf774 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 18 Aug 2022 19:32:36 +0200 Subject: [PATCH 31/33] Update Doc/library/sqlite3.rst Co-authored-by: C.A.M. Gerlach --- 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 c96553564cd821..aa063f367366d9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -114,8 +114,8 @@ If we query ``sqlite_master`` for a non-existent table ``spam``, :meth:`!res.fetchone()` will return ``None``:: >>> res = cur.execute("SELECT name FROM sqlite_master WHERE name='spam'") - >>> res.fetchone() - >>> + >>> res.fetchone() is None + True Now, add two rows of data supplied as SQL literals by executing an ``INSERT`` statement, From 0e0398dfd423edfffd50be6dc13a8d1ddb53eafc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 18 Aug 2022 19:34:12 +0200 Subject: [PATCH 32/33] Update Doc/library/sqlite3.rst --- 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 aa063f367366d9..db6c5e6f20e26c 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -105,7 +105,8 @@ assign the result to ``res``, and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res = cur.execute("SELECT name FROM sqlite_master") - >>> res.fetchone() + >>> res.fetchone() is None + True ('movie',) We can see that the table has been created, From cb41c494fa3a0cdb727f7b1294b9cf655bd39163 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 18 Aug 2022 19:37:35 +0200 Subject: [PATCH 33/33] Revert "Update Doc/library/sqlite3.rst" This reverts commit 0e0398dfd423edfffd50be6dc13a8d1ddb53eafc. --- Doc/library/sqlite3.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index db6c5e6f20e26c..aa063f367366d9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -105,8 +105,7 @@ assign the result to ``res``, and call :meth:`res.fetchone() ` to fetch the resulting row:: >>> res = cur.execute("SELECT name FROM sqlite_master") - >>> res.fetchone() is None - True + >>> res.fetchone() ('movie',) We can see that the table has been created,