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

gh-95273: Improve sqlite3.complete_statement docs #95840

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions Doc/includes/sqlite3/complete_statement.py

This file was deleted.

23 changes: 17 additions & 6 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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, /)

Expand Down
23 changes: 23 additions & 0 deletions Lib/sqlite3/__main__.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand All @@ -21,13 +35,19 @@ def execute(c, sql, suppress_errors=True):


class SqliteInteractiveConsole(InteractiveConsole):
"""A simple SQLite REPL."""

def __init__(self, connection):
super().__init__()
self._con = 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}")
Expand Down Expand Up @@ -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}
Expand All @@ -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:
Expand Down