gh-95273: Improve sqlite3.complete_statement docs (#95840)

Co-authored-by: Ezio Melotti <ezio.melotti@gmail.com>
Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
Erlend E. Aasland 2022-08-12 01:05:12 +02:00 committed by GitHub
parent 6f6a4e6cc5
commit e6623e7083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 39 deletions

View file

@ -1,33 +0,0 @@
# A minimal SQLite shell for experiments
import sqlite3
con = sqlite3.connect(":memory:")
con.isolation_level = None
cur = con.cursor()
buffer = ""
print("Enter your SQL commands to execute in sqlite3.")
print("Enter a blank line to exit.")
while True:
line = input()
if line == "":
break
buffer += line
if sqlite3.complete_statement(buffer):
try:
buffer = buffer.strip()
cur.execute(buffer)
if buffer.lstrip().upper().startswith("SELECT"):
print(cur.fetchall())
except sqlite3.Error as e:
err_msg = str(e)
err_code = e.sqlite_errorcode
err_name = e.sqlite_errorname
print(f"{err_name} ({err_code}): {err_msg}")
buffer = ""
con.close()

View file

@ -222,14 +222,25 @@ Module functions
.. function:: complete_statement(statement)
Returns ``True`` if the string *statement* contains one or more complete SQL
statements terminated by semicolons. It does not verify that the SQL is
syntactically correct, only that there are no unclosed string literals and the
statement is terminated by a semicolon.
Return ``True`` if the string *statement* appears to contain
one or more complete SQL statements.
No syntactic verification or parsing of any kind is performed,
other than checking that there are no unclosed string literals
and the statement is terminated by a semicolon.
This can be used to build a shell for SQLite, as in the following example:
For example::
.. literalinclude:: ../includes/sqlite3/complete_statement.py
>>> sqlite3.complete_statement("SELECT foo FROM bar;")
True
>>> sqlite3.complete_statement("SELECT foo")
False
This function may be useful during command-line input
to determine if the entered text seems to form a complete SQL statement,
or if additional input is needed before calling :meth:`~Cursor.execute`.
See :func:`!runsource` in :source:`Lib/sqlite3/__main__.py`
for real-world use.
.. function:: enable_callback_tracebacks(flag, /)

View file

@ -1,3 +1,9 @@
"""A simple SQLite CLI for the sqlite3 module.
Apart from using 'argparse' for the command-line interface,
this module implements the REPL as a thin wrapper around
the InteractiveConsole class from the 'code' stdlib module.
"""
import sqlite3
import sys
@ -7,6 +13,14 @@
def execute(c, sql, suppress_errors=True):
"""Helper that wraps execution of SQL code.
This is used both by the REPL and by direct execution from the CLI.
'c' may be a cursor or a connection.
'sql' is the SQL string to execute.
"""
try:
for row in c.execute(sql):
print(row)
@ -21,6 +35,7 @@ def execute(c, sql, suppress_errors=True):
class SqliteInteractiveConsole(InteractiveConsole):
"""A simple SQLite REPL."""
def __init__(self, connection):
super().__init__()
@ -28,6 +43,11 @@ def __init__(self, connection):
self._cur = connection.cursor()
def runsource(self, source, filename="<input>", symbol="single"):
"""Override runsource, the core of the InteractiveConsole REPL.
Return True if more input is needed; buffering is done automatically.
Return False is input is a complete statement ready for execution.
"""
match source:
case ".version":
print(f"{sqlite3.sqlite_version}")
@ -73,6 +93,7 @@ def main():
else:
db_name = repr(args.filename)
# Prepare REPL banner and prompts.
banner = dedent(f"""
sqlite3 shell, running on SQLite version {sqlite3.sqlite_version}
Connected to {db_name}
@ -86,8 +107,10 @@ def main():
con = sqlite3.connect(args.filename, isolation_level=None)
try:
if args.sql:
# SQL statement provided on the command-line; execute it directly.
execute(con, args.sql, suppress_errors=False)
else:
# No SQL provided; start the REPL.
console = SqliteInteractiveConsole(con)
console.interact(banner, exitmsg="")
finally: