Skip to content

Commit 00440b1

Browse files
authored
Update sqlite3 and the tests to CPython 3.14.2 (RustPython#6787)
* Update sqlite3, dbm.sqlite3 and tests to CPython 3.14.2 * Add skip decorators for failing sqlite3 tests Skip tests that fail due to unimplemented features or behavior differences: - _iterdump not implemented (test_dump.py) - Unraisable exception handling not implemented (test_hooks.py, test_userfunctions.py) - Keyword-only arguments not supported for various methods - Autocommit behavior differences (test_transactions.py) - TransactionTests skipped due to timeout parameter type issue - Various error message differences (test_dbapi.py) - SQLITE_DBCONFIG constants not implemented - Row and Connection signature inspection issues All tests now pass with 95 skipped out of 493 total tests. * Change @unittest.skip to @unittest.expectedFailure per code review - Convert @unittest.skip decorators to @unittest.expectedFailure for tests that fail without panic/hang - Keep @unittest.skip only for TransactionTests class (setUp fails with timeout=0 int type) * fixup
1 parent 130bb82 commit 00440b1

17 files changed

+1404
-789
lines changed

Lib/sqlite3/__init__.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
"""
2424
The sqlite3 extension module provides a DB-API 2.0 (PEP 249) compliant
25-
interface to the SQLite library, and requires SQLite 3.7.15 or newer.
25+
interface to the SQLite library, and requires SQLite 3.15.2 or newer.
2626
2727
To use the module, start by creating a database Connection object:
2828
@@ -55,16 +55,3 @@
5555
"""
5656

5757
from sqlite3.dbapi2 import *
58-
from sqlite3.dbapi2 import (_deprecated_names,
59-
_deprecated_version_info,
60-
_deprecated_version)
61-
62-
63-
def __getattr__(name):
64-
if name in _deprecated_names:
65-
from warnings import warn
66-
67-
warn(f"{name} is deprecated and will be removed in Python 3.14",
68-
DeprecationWarning, stacklevel=2)
69-
return globals()[f"_deprecated_{name}"]
70-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Lib/sqlite3/__main__.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,34 @@ def runsource(self, source, filename="<input>", symbol="single"):
4646
"""Override runsource, the core of the InteractiveConsole REPL.
4747
4848
Return True if more input is needed; buffering is done automatically.
49-
Return False is input is a complete statement ready for execution.
49+
Return False if input is a complete statement ready for execution.
5050
"""
51-
match source:
52-
case ".version":
53-
print(f"{sqlite3.sqlite_version}")
54-
case ".help":
55-
print("Enter SQL code and press enter.")
56-
case ".quit":
57-
sys.exit(0)
58-
case _:
59-
if not sqlite3.complete_statement(source):
60-
return True
61-
execute(self._cur, source)
51+
if not source or source.isspace():
52+
return False
53+
if source[0] == ".":
54+
match source[1:].strip():
55+
case "version":
56+
print(f"{sqlite3.sqlite_version}")
57+
case "help":
58+
print("Enter SQL code and press enter.")
59+
case "quit":
60+
sys.exit(0)
61+
case "":
62+
pass
63+
case _ as unknown:
64+
self.write("Error: unknown command or invalid arguments:"
65+
f' "{unknown}".\n')
66+
else:
67+
if not sqlite3.complete_statement(source):
68+
return True
69+
execute(self._cur, source)
6270
return False
6371

6472

65-
def main():
73+
def main(*args):
6674
parser = ArgumentParser(
6775
description="Python sqlite3 CLI",
68-
prog="python -m sqlite3",
76+
color=True,
6977
)
7078
parser.add_argument(
7179
"filename", type=str, default=":memory:", nargs="?",
@@ -86,20 +94,24 @@ def main():
8694
version=f"SQLite version {sqlite3.sqlite_version}",
8795
help="Print underlying SQLite library version",
8896
)
89-
args = parser.parse_args()
97+
args = parser.parse_args(*args)
9098

9199
if args.filename == ":memory:":
92100
db_name = "a transient in-memory database"
93101
else:
94102
db_name = repr(args.filename)
95103

96104
# Prepare REPL banner and prompts.
105+
if sys.platform == "win32" and "idlelib.run" not in sys.modules:
106+
eofkey = "CTRL-Z"
107+
else:
108+
eofkey = "CTRL-D"
97109
banner = dedent(f"""
98110
sqlite3 shell, running on SQLite version {sqlite3.sqlite_version}
99111
Connected to {db_name}
100112
101113
Each command will be run using execute() on the cursor.
102-
Type ".help" for more information; type ".quit" or CTRL-D to quit.
114+
Type ".help" for more information; type ".quit" or {eofkey} to quit.
103115
""").strip()
104116
sys.ps1 = "sqlite> "
105117
sys.ps2 = " ... "
@@ -112,9 +124,16 @@ def main():
112124
else:
113125
# No SQL provided; start the REPL.
114126
console = SqliteInteractiveConsole(con)
127+
try:
128+
import readline # noqa: F401
129+
except ImportError:
130+
pass
115131
console.interact(banner, exitmsg="")
116132
finally:
117133
con.close()
118134

135+
sys.exit(0)
136+
119137

120-
main()
138+
if __name__ == "__main__":
139+
main(sys.argv[1:])

Lib/sqlite3/dbapi2.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
import collections.abc
2626

2727
from _sqlite3 import *
28-
from _sqlite3 import _deprecated_version
29-
30-
_deprecated_names = frozenset({"version", "version_info"})
3128

3229
paramstyle = "qmark"
3330

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

51-
_deprecated_version_info = tuple(map(int, _deprecated_version.split(".")))
48+
5249
sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")])
5350

5451
Binary = memoryview
@@ -97,12 +94,3 @@ def convert_timestamp(val):
9794
# Clean up namespace
9895

9996
del(register_adapters_and_converters)
100-
101-
def __getattr__(name):
102-
if name in _deprecated_names:
103-
from warnings import warn
104-
105-
warn(f"{name} is deprecated and will be removed in Python 3.14",
106-
DeprecationWarning, stacklevel=2)
107-
return globals()[f"_deprecated_{name}"]
108-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Lib/sqlite3/dump.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
# future enhancements, you should normally quote any identifier that
88
# is an English language word, even if you do not have to."
99

10-
def _iterdump(connection):
10+
def _quote_name(name):
11+
return '"{0}"'.format(name.replace('"', '""'))
12+
13+
14+
def _quote_value(value):
15+
return "'{0}'".format(value.replace("'", "''"))
16+
17+
18+
def _iterdump(connection, *, filter=None):
1119
"""
1220
Returns an iterator to the dump of the database in an SQL text format.
1321
@@ -16,64 +24,87 @@ def _iterdump(connection):
1624
directly but instead called from the Connection method, iterdump().
1725
"""
1826

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

36+
if filter:
37+
# Return database objects which match the filter pattern.
38+
filter_name_clause = 'AND "name" LIKE ?'
39+
params = [filter]
40+
else:
41+
filter_name_clause = ""
42+
params = []
2243
# sqlite_master table contains the SQL CREATE statements for the database.
23-
q = """
44+
q = f"""
2445
SELECT "name", "type", "sql"
2546
FROM "sqlite_master"
2647
WHERE "sql" NOT NULL AND
2748
"type" == 'table'
49+
{filter_name_clause}
2850
ORDER BY "name"
2951
"""
30-
schema_res = cu.execute(q)
52+
schema_res = cu.execute(q, params)
3153
sqlite_sequence = []
3254
for table_name, type, sql in schema_res.fetchall():
3355
if table_name == 'sqlite_sequence':
34-
rows = cu.execute('SELECT * FROM "sqlite_sequence";').fetchall()
56+
rows = cu.execute('SELECT * FROM "sqlite_sequence";')
3557
sqlite_sequence = ['DELETE FROM "sqlite_sequence"']
3658
sqlite_sequence += [
37-
f'INSERT INTO "sqlite_sequence" VALUES(\'{row[0]}\',{row[1]})'
38-
for row in rows
59+
f'INSERT INTO "sqlite_sequence" VALUES({_quote_value(table_name)},{seq_value})'
60+
for table_name, seq_value in rows.fetchall()
3961
]
4062
continue
4163
elif table_name == 'sqlite_stat1':
4264
yield('ANALYZE "sqlite_master";')
4365
elif table_name.startswith('sqlite_'):
4466
continue
45-
# NOTE: Virtual table support not implemented
46-
#elif sql.startswith('CREATE VIRTUAL TABLE'):
47-
# qtable = table_name.replace("'", "''")
48-
# yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"\
49-
# "VALUES('table','{0}','{0}',0,'{1}');".format(
50-
# qtable,
51-
# sql.replace("''")))
67+
elif sql.startswith('CREATE VIRTUAL TABLE'):
68+
if not writeable_schema:
69+
writeable_schema = True
70+
yield('PRAGMA writable_schema=ON;')
71+
yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
72+
"VALUES('table',{0},{0},0,{1});".format(
73+
_quote_value(table_name),
74+
_quote_value(sql),
75+
))
5276
else:
5377
yield('{0};'.format(sql))
5478

5579
# Build the insert statement for each row of the current table
56-
table_name_ident = table_name.replace('"', '""')
57-
res = cu.execute('PRAGMA table_info("{0}")'.format(table_name_ident))
80+
table_name_ident = _quote_name(table_name)
81+
res = cu.execute(f'PRAGMA table_info({table_name_ident})')
5882
column_names = [str(table_info[1]) for table_info in res.fetchall()]
59-
q = """SELECT 'INSERT INTO "{0}" VALUES({1})' FROM "{0}";""".format(
83+
q = "SELECT 'INSERT INTO {0} VALUES('{1}')' FROM {0};".format(
6084
table_name_ident,
61-
",".join("""'||quote("{0}")||'""".format(col.replace('"', '""')) for col in column_names))
85+
"','".join(
86+
"||quote({0})||".format(_quote_name(col)) for col in column_names
87+
)
88+
)
6289
query_res = cu.execute(q)
6390
for row in query_res:
6491
yield("{0};".format(row[0]))
6592

6693
# Now when the type is 'index', 'trigger', or 'view'
67-
q = """
94+
q = f"""
6895
SELECT "name", "type", "sql"
6996
FROM "sqlite_master"
7097
WHERE "sql" NOT NULL AND
7198
"type" IN ('index', 'trigger', 'view')
99+
{filter_name_clause}
72100
"""
73-
schema_res = cu.execute(q)
101+
schema_res = cu.execute(q, params)
74102
for name, type, sql in schema_res.fetchall():
75103
yield('{0};'.format(sql))
76104

105+
if writeable_schema:
106+
yield('PRAGMA writable_schema=OFF;')
107+
77108
# gh-79009: Yield statements concerning the sqlite_sequence table at the
78109
# end of the transaction.
79110
for row in sqlite_sequence:

Lib/test/test_dbm_sqlite3.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import os
22
import stat
33
import sys
4-
import test.support
54
import unittest
65
from contextlib import closing
76
from functools import partial
87
from pathlib import Path
9-
from test.support import cpython_only, import_helper, os_helper
8+
from test.support import import_helper, os_helper
109

1110
dbm_sqlite3 = import_helper.import_module("dbm.sqlite3")
1211
# N.B. The test will fail on some platforms without sqlite3
@@ -44,7 +43,7 @@ def test_uri_substitutions(self):
4443
)
4544
for path, normalized in dataset:
4645
with self.subTest(path=path, normalized=normalized):
47-
self.assertTrue(_normalize_uri(path).endswith(normalized))
46+
self.assertEndsWith(_normalize_uri(path), normalized)
4847

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

6867

6968
class ReadOnly(_SQLiteDbmTests):

Lib/test/test_sqlite3/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
# Implement the unittest "load tests" protocol.
1010
def load_tests(*args):
11+
if verbose:
12+
print(f"test_sqlite3: testing with SQLite version {sqlite3.sqlite_version}")
1113
pkg_dir = os.path.dirname(__file__)
1214
return load_package_tests(pkg_dir, *args)
13-
14-
if verbose:
15-
print(f"test_sqlite3: testing with SQLite version {sqlite3.sqlite_version}")

0 commit comments

Comments
 (0)