Skip to content
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
15 changes: 1 addition & 14 deletions Lib/sqlite3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

"""
The sqlite3 extension module provides a DB-API 2.0 (PEP 249) compliant
interface to the SQLite library, and requires SQLite 3.7.15 or newer.
interface to the SQLite library, and requires SQLite 3.15.2 or newer.

To use the module, start by creating a database Connection object:

Expand Down Expand Up @@ -55,16 +55,3 @@
"""

from sqlite3.dbapi2 import *
from sqlite3.dbapi2 import (_deprecated_names,
_deprecated_version_info,
_deprecated_version)


def __getattr__(name):
if name in _deprecated_names:
from warnings import warn

warn(f"{name} is deprecated and will be removed in Python 3.14",
DeprecationWarning, stacklevel=2)
return globals()[f"_deprecated_{name}"]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
53 changes: 36 additions & 17 deletions Lib/sqlite3/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,34 @@ 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.
Return False if input is a complete statement ready for execution.
"""
match source:
case ".version":
print(f"{sqlite3.sqlite_version}")
case ".help":
print("Enter SQL code and press enter.")
case ".quit":
sys.exit(0)
case _:
if not sqlite3.complete_statement(source):
return True
execute(self._cur, source)
if not source or source.isspace():
return False
if source[0] == ".":
match source[1:].strip():
case "version":
print(f"{sqlite3.sqlite_version}")
case "help":
print("Enter SQL code and press enter.")
case "quit":
sys.exit(0)
case "":
pass
case _ as unknown:
self.write("Error: unknown command or invalid arguments:"
f' "{unknown}".\n')
else:
if not sqlite3.complete_statement(source):
return True
execute(self._cur, source)
return False


def main():
def main(*args):
parser = ArgumentParser(
description="Python sqlite3 CLI",
prog="python -m sqlite3",
color=True,
)
parser.add_argument(
"filename", type=str, default=":memory:", nargs="?",
Expand All @@ -86,20 +94,24 @@ def main():
version=f"SQLite version {sqlite3.sqlite_version}",
help="Print underlying SQLite library version",
)
args = parser.parse_args()
args = parser.parse_args(*args)

if args.filename == ":memory:":
db_name = "a transient in-memory database"
else:
db_name = repr(args.filename)

# Prepare REPL banner and prompts.
if sys.platform == "win32" and "idlelib.run" not in sys.modules:
eofkey = "CTRL-Z"
else:
eofkey = "CTRL-D"
banner = dedent(f"""
sqlite3 shell, running on SQLite version {sqlite3.sqlite_version}
Connected to {db_name}

Each command will be run using execute() on the cursor.
Type ".help" for more information; type ".quit" or CTRL-D to quit.
Type ".help" for more information; type ".quit" or {eofkey} to quit.
""").strip()
sys.ps1 = "sqlite> "
sys.ps2 = " ... "
Expand All @@ -112,9 +124,16 @@ def main():
else:
# No SQL provided; start the REPL.
console = SqliteInteractiveConsole(con)
try:
import readline # noqa: F401
except ImportError:
pass
console.interact(banner, exitmsg="")
finally:
con.close()

sys.exit(0)


main()
if __name__ == "__main__":
main(sys.argv[1:])
14 changes: 1 addition & 13 deletions Lib/sqlite3/dbapi2.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
import collections.abc

from _sqlite3 import *
from _sqlite3 import _deprecated_version

_deprecated_names = frozenset({"version", "version_info"})

paramstyle = "qmark"

Expand All @@ -48,7 +45,7 @@ def TimeFromTicks(ticks):
def TimestampFromTicks(ticks):
return Timestamp(*time.localtime(ticks)[:6])

_deprecated_version_info = tuple(map(int, _deprecated_version.split(".")))

sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")])

Binary = memoryview
Expand Down Expand Up @@ -97,12 +94,3 @@ def convert_timestamp(val):
# Clean up namespace

del(register_adapters_and_converters)

def __getattr__(name):
if name in _deprecated_names:
from warnings import warn

warn(f"{name} is deprecated and will be removed in Python 3.14",
DeprecationWarning, stacklevel=2)
return globals()[f"_deprecated_{name}"]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
69 changes: 50 additions & 19 deletions Lib/sqlite3/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
# future enhancements, you should normally quote any identifier that
# is an English language word, even if you do not have to."

def _iterdump(connection):
def _quote_name(name):
return '"{0}"'.format(name.replace('"', '""'))


def _quote_value(value):
return "'{0}'".format(value.replace("'", "''"))


def _iterdump(connection, *, filter=None):
"""
Returns an iterator to the dump of the database in an SQL text format.

Expand All @@ -16,64 +24,87 @@ def _iterdump(connection):
directly but instead called from the Connection method, iterdump().
"""

writeable_schema = False
cu = connection.cursor()
cu.row_factory = None # Make sure we get predictable results.
# Disable foreign key constraints, if there is any foreign key violation.
violations = cu.execute("PRAGMA foreign_key_check").fetchall()
if violations:
yield('PRAGMA foreign_keys=OFF;')
yield('BEGIN TRANSACTION;')

if filter:
# Return database objects which match the filter pattern.
filter_name_clause = 'AND "name" LIKE ?'
params = [filter]
else:
filter_name_clause = ""
params = []
# sqlite_master table contains the SQL CREATE statements for the database.
q = """
q = f"""
SELECT "name", "type", "sql"
FROM "sqlite_master"
WHERE "sql" NOT NULL AND
"type" == 'table'
{filter_name_clause}
ORDER BY "name"
"""
schema_res = cu.execute(q)
schema_res = cu.execute(q, params)
sqlite_sequence = []
for table_name, type, sql in schema_res.fetchall():
if table_name == 'sqlite_sequence':
rows = cu.execute('SELECT * FROM "sqlite_sequence";').fetchall()
rows = cu.execute('SELECT * FROM "sqlite_sequence";')
sqlite_sequence = ['DELETE FROM "sqlite_sequence"']
sqlite_sequence += [
f'INSERT INTO "sqlite_sequence" VALUES(\'{row[0]}\',{row[1]})'
for row in rows
f'INSERT INTO "sqlite_sequence" VALUES({_quote_value(table_name)},{seq_value})'
for table_name, seq_value in rows.fetchall()
]
continue
elif table_name == 'sqlite_stat1':
yield('ANALYZE "sqlite_master";')
elif table_name.startswith('sqlite_'):
continue
# NOTE: Virtual table support not implemented
#elif sql.startswith('CREATE VIRTUAL TABLE'):
# qtable = table_name.replace("'", "''")
# yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"\
# "VALUES('table','{0}','{0}',0,'{1}');".format(
# qtable,
# sql.replace("''")))
elif sql.startswith('CREATE VIRTUAL TABLE'):
if not writeable_schema:
writeable_schema = True
yield('PRAGMA writable_schema=ON;')
yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
"VALUES('table',{0},{0},0,{1});".format(
_quote_value(table_name),
_quote_value(sql),
))
else:
yield('{0};'.format(sql))

# Build the insert statement for each row of the current table
table_name_ident = table_name.replace('"', '""')
res = cu.execute('PRAGMA table_info("{0}")'.format(table_name_ident))
table_name_ident = _quote_name(table_name)
res = cu.execute(f'PRAGMA table_info({table_name_ident})')
column_names = [str(table_info[1]) for table_info in res.fetchall()]
q = """SELECT 'INSERT INTO "{0}" VALUES({1})' FROM "{0}";""".format(
q = "SELECT 'INSERT INTO {0} VALUES('{1}')' FROM {0};".format(
table_name_ident,
",".join("""'||quote("{0}")||'""".format(col.replace('"', '""')) for col in column_names))
"','".join(
"||quote({0})||".format(_quote_name(col)) for col in column_names
)
)
query_res = cu.execute(q)
for row in query_res:
yield("{0};".format(row[0]))

# Now when the type is 'index', 'trigger', or 'view'
q = """
q = f"""
SELECT "name", "type", "sql"
FROM "sqlite_master"
WHERE "sql" NOT NULL AND
"type" IN ('index', 'trigger', 'view')
{filter_name_clause}
"""
schema_res = cu.execute(q)
schema_res = cu.execute(q, params)
for name, type, sql in schema_res.fetchall():
yield('{0};'.format(sql))

if writeable_schema:
yield('PRAGMA writable_schema=OFF;')

# gh-79009: Yield statements concerning the sqlite_sequence table at the
# end of the transaction.
for row in sqlite_sequence:
Expand Down
7 changes: 3 additions & 4 deletions Lib/test/test_dbm_sqlite3.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import os
import stat
import sys
import test.support
import unittest
from contextlib import closing
from functools import partial
from pathlib import Path
from test.support import cpython_only, import_helper, os_helper
from test.support import import_helper, os_helper

dbm_sqlite3 = import_helper.import_module("dbm.sqlite3")
# N.B. The test will fail on some platforms without sqlite3
Expand Down Expand Up @@ -44,7 +43,7 @@ def test_uri_substitutions(self):
)
for path, normalized in dataset:
with self.subTest(path=path, normalized=normalized):
self.assertTrue(_normalize_uri(path).endswith(normalized))
self.assertEndsWith(_normalize_uri(path), normalized)

@unittest.skipUnless(sys.platform == "win32", "requires Windows")
def test_uri_windows(self):
Expand All @@ -63,7 +62,7 @@ def test_uri_windows(self):
with self.subTest(path=path, normalized=normalized):
if not Path(path).is_absolute():
self.skipTest(f"skipping relative path: {path!r}")
self.assertTrue(_normalize_uri(path).endswith(normalized))
self.assertEndsWith(_normalize_uri(path), normalized)


class ReadOnly(_SQLiteDbmTests):
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/test_sqlite3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

# Implement the unittest "load tests" protocol.
def load_tests(*args):
if verbose:
print(f"test_sqlite3: testing with SQLite version {sqlite3.sqlite_version}")
pkg_dir = os.path.dirname(__file__)
return load_package_tests(pkg_dir, *args)

if verbose:
print(f"test_sqlite3: testing with SQLite version {sqlite3.sqlite_version}")
Loading
Loading