Skip to content

Commit 7659331

Browse files
authored
Implement Sqlite3 Module (RustPython#4260)
* add supporting for PyAtomic<PyObject> * create sqlite module * add dependency sqlite3-sys * add module constants * import sqlite3 from cpython * adjust lib * add module structure * impl Connection.cursor * add module exceptions * impl lstrip_sql * impl statement new * wip cursor.execute * wip cursor * wip error to exception * add SqliteRaw and SqliteStatementRaw * impl statement parameters binding * wip cursor.execute * add test_sqlite * impl closeable connection * impl closeable cursor * impl cursor.executemany * impl cursor.executescript * impl cursor.fetch* * impl connection.backup * stage 1 * add support connection.backup with progress * fix backup deadlock * support changable isolation_level * impl converter * impl adapter * impl text_factory and blob * impl create_function * impl create_function 2 * fix empty statement * impl blob support * impl create_aggregate * impl create_aggregate 2 * refactor create_* * impl enable_callback_traceback * impl create_collation * refactor create_* with CallbackData * fix text and blob use SQLITE_TRANSIENT * fix str to SQLITE_TEXT * impl thread check * impl Connection Factory * impl busy timeout * shift sqlite3-sys -> libsqlite3-sys * refactor CallbackData * impl create_window_function * refactor callback functions * add module attr converters * fix nullable isolation_level * add module attr adapters * fix nullable adapt proto * impl set_authorizer * impl trace_callback * impl set_progress_handler * impl cancellable sqlite function* * impl attributes for Connection * fix some failed tests * impl Row * impl Blob methods * impl Blob subscript & ass_subscript * pass tests * rebase * no sqlite for wasm * use ThreadId instead u64 * no libsqlite3-sys for wasm * fix into_cstring for all platform * fixup * rebase * fix windows into_bytes * disable sqlite for android * fixup
1 parent 22a5a83 commit 7659331

26 files changed

+8963
-6
lines changed

Cargo.lock

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/sqlite3/__init__.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# pysqlite2/__init__.py: the pysqlite2 package.
2+
#
3+
# Copyright (C) 2005 Gerhard Häring <gh@ghaering.de>
4+
#
5+
# This file is part of pysqlite.
6+
#
7+
# This software is provided 'as-is', without any express or implied
8+
# warranty. In no event will the authors be held liable for any damages
9+
# arising from the use of this software.
10+
#
11+
# Permission is granted to anyone to use this software for any purpose,
12+
# including commercial applications, and to alter it and redistribute it
13+
# freely, subject to the following restrictions:
14+
#
15+
# 1. The origin of this software must not be misrepresented; you must not
16+
# claim that you wrote the original software. If you use this software
17+
# in a product, an acknowledgment in the product documentation would be
18+
# appreciated but is not required.
19+
# 2. Altered source versions must be plainly marked as such, and must not be
20+
# misrepresented as being the original software.
21+
# 3. This notice may not be removed or altered from any source distribution.
22+
23+
"""
24+
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.
26+
27+
To use the module, start by creating a database Connection object:
28+
29+
import sqlite3
30+
cx = sqlite3.connect("test.db") # test.db will be created or opened
31+
32+
The special path name ":memory:" can be provided to connect to a transient
33+
in-memory database:
34+
35+
cx = sqlite3.connect(":memory:") # connect to a database in RAM
36+
37+
Once a connection has been established, create a Cursor object and call
38+
its execute() method to perform SQL queries:
39+
40+
cu = cx.cursor()
41+
42+
# create a table
43+
cu.execute("create table lang(name, first_appeared)")
44+
45+
# insert values into a table
46+
cu.execute("insert into lang values (?, ?)", ("C", 1972))
47+
48+
# execute a query and iterate over the result
49+
for row in cu.execute("select * from lang"):
50+
print(row)
51+
52+
cx.close()
53+
54+
The sqlite3 module is written by Gerhard Häring <gh@ghaering.de>.
55+
"""
56+
57+
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

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""A simple SQLite CLI for the sqlite3 module.
2+
3+
Apart from using 'argparse' for the command-line interface,
4+
this module implements the REPL as a thin wrapper around
5+
the InteractiveConsole class from the 'code' stdlib module.
6+
"""
7+
import sqlite3
8+
import sys
9+
10+
from argparse import ArgumentParser
11+
from code import InteractiveConsole
12+
from textwrap import dedent
13+
14+
15+
def execute(c, sql, suppress_errors=True):
16+
"""Helper that wraps execution of SQL code.
17+
18+
This is used both by the REPL and by direct execution from the CLI.
19+
20+
'c' may be a cursor or a connection.
21+
'sql' is the SQL string to execute.
22+
"""
23+
24+
try:
25+
for row in c.execute(sql):
26+
print(row)
27+
except sqlite3.Error as e:
28+
tp = type(e).__name__
29+
try:
30+
print(f"{tp} ({e.sqlite_errorname}): {e}", file=sys.stderr)
31+
except AttributeError:
32+
print(f"{tp}: {e}", file=sys.stderr)
33+
if not suppress_errors:
34+
sys.exit(1)
35+
36+
37+
class SqliteInteractiveConsole(InteractiveConsole):
38+
"""A simple SQLite REPL."""
39+
40+
def __init__(self, connection):
41+
super().__init__()
42+
self._con = connection
43+
self._cur = connection.cursor()
44+
45+
def runsource(self, source, filename="<input>", symbol="single"):
46+
"""Override runsource, the core of the InteractiveConsole REPL.
47+
48+
Return True if more input is needed; buffering is done automatically.
49+
Return False is input is a complete statement ready for execution.
50+
"""
51+
if source == ".version":
52+
print(f"{sqlite3.sqlite_version}")
53+
elif source == ".help":
54+
print("Enter SQL code and press enter.")
55+
elif source == ".quit":
56+
sys.exit(0)
57+
elif not sqlite3.complete_statement(source):
58+
return True
59+
else:
60+
execute(self._cur, source)
61+
return False
62+
# TODO: RUSTPYTHON match statement supporting
63+
# match source:
64+
# case ".version":
65+
# print(f"{sqlite3.sqlite_version}")
66+
# case ".help":
67+
# print("Enter SQL code and press enter.")
68+
# case ".quit":
69+
# sys.exit(0)
70+
# case _:
71+
# if not sqlite3.complete_statement(source):
72+
# return True
73+
# execute(self._cur, source)
74+
# return False
75+
76+
77+
def main():
78+
parser = ArgumentParser(
79+
description="Python sqlite3 CLI",
80+
prog="python -m sqlite3",
81+
)
82+
parser.add_argument(
83+
"filename", type=str, default=":memory:", nargs="?",
84+
help=(
85+
"SQLite database to open (defaults to ':memory:'). "
86+
"A new database is created if the file does not previously exist."
87+
),
88+
)
89+
parser.add_argument(
90+
"sql", type=str, nargs="?",
91+
help=(
92+
"An SQL query to execute. "
93+
"Any returned rows are printed to stdout."
94+
),
95+
)
96+
parser.add_argument(
97+
"-v", "--version", action="version",
98+
version=f"SQLite version {sqlite3.sqlite_version}",
99+
help="Print underlying SQLite library version",
100+
)
101+
args = parser.parse_args()
102+
103+
if args.filename == ":memory:":
104+
db_name = "a transient in-memory database"
105+
else:
106+
db_name = repr(args.filename)
107+
108+
# Prepare REPL banner and prompts.
109+
banner = dedent(f"""
110+
sqlite3 shell, running on SQLite version {sqlite3.sqlite_version}
111+
Connected to {db_name}
112+
113+
Each command will be run using execute() on the cursor.
114+
Type ".help" for more information; type ".quit" or CTRL-D to quit.
115+
""").strip()
116+
sys.ps1 = "sqlite> "
117+
sys.ps2 = " ... "
118+
119+
con = sqlite3.connect(args.filename, isolation_level=None)
120+
try:
121+
if args.sql:
122+
# SQL statement provided on the command-line; execute it directly.
123+
execute(con, args.sql, suppress_errors=False)
124+
else:
125+
# No SQL provided; start the REPL.
126+
console = SqliteInteractiveConsole(con)
127+
console.interact(banner, exitmsg="")
128+
finally:
129+
con.close()
130+
131+
132+
main()

Lib/sqlite3/dbapi2.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# pysqlite2/dbapi2.py: the DB-API 2.0 interface
2+
#
3+
# Copyright (C) 2004-2005 Gerhard Häring <gh@ghaering.de>
4+
#
5+
# This file is part of pysqlite.
6+
#
7+
# This software is provided 'as-is', without any express or implied
8+
# warranty. In no event will the authors be held liable for any damages
9+
# arising from the use of this software.
10+
#
11+
# Permission is granted to anyone to use this software for any purpose,
12+
# including commercial applications, and to alter it and redistribute it
13+
# freely, subject to the following restrictions:
14+
#
15+
# 1. The origin of this software must not be misrepresented; you must not
16+
# claim that you wrote the original software. If you use this software
17+
# in a product, an acknowledgment in the product documentation would be
18+
# appreciated but is not required.
19+
# 2. Altered source versions must be plainly marked as such, and must not be
20+
# misrepresented as being the original software.
21+
# 3. This notice may not be removed or altered from any source distribution.
22+
23+
import datetime
24+
import time
25+
import collections.abc
26+
27+
from _sqlite3 import *
28+
from _sqlite3 import _deprecated_version
29+
30+
_deprecated_names = frozenset({"version", "version_info"})
31+
32+
paramstyle = "qmark"
33+
34+
apilevel = "2.0"
35+
36+
Date = datetime.date
37+
38+
Time = datetime.time
39+
40+
Timestamp = datetime.datetime
41+
42+
def DateFromTicks(ticks):
43+
return Date(*time.localtime(ticks)[:3])
44+
45+
def TimeFromTicks(ticks):
46+
return Time(*time.localtime(ticks)[3:6])
47+
48+
def TimestampFromTicks(ticks):
49+
return Timestamp(*time.localtime(ticks)[:6])
50+
51+
_deprecated_version_info = tuple(map(int, _deprecated_version.split(".")))
52+
sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")])
53+
54+
Binary = memoryview
55+
collections.abc.Sequence.register(Row)
56+
57+
def register_adapters_and_converters():
58+
from warnings import warn
59+
60+
msg = ("The default {what} is deprecated as of Python 3.12; "
61+
"see the sqlite3 documentation for suggested replacement recipes")
62+
63+
def adapt_date(val):
64+
warn(msg.format(what="date adapter"), DeprecationWarning, stacklevel=2)
65+
return val.isoformat()
66+
67+
def adapt_datetime(val):
68+
warn(msg.format(what="datetime adapter"), DeprecationWarning, stacklevel=2)
69+
return val.isoformat(" ")
70+
71+
def convert_date(val):
72+
warn(msg.format(what="date converter"), DeprecationWarning, stacklevel=2)
73+
return datetime.date(*map(int, val.split(b"-")))
74+
75+
def convert_timestamp(val):
76+
warn(msg.format(what="timestamp converter"), DeprecationWarning, stacklevel=2)
77+
datepart, timepart = val.split(b" ")
78+
year, month, day = map(int, datepart.split(b"-"))
79+
timepart_full = timepart.split(b".")
80+
hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
81+
if len(timepart_full) == 2:
82+
microseconds = int('{:0<6.6}'.format(timepart_full[1].decode()))
83+
else:
84+
microseconds = 0
85+
86+
val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
87+
return val
88+
89+
90+
register_adapter(datetime.date, adapt_date)
91+
register_adapter(datetime.datetime, adapt_datetime)
92+
register_converter("date", convert_date)
93+
register_converter("timestamp", convert_timestamp)
94+
95+
register_adapters_and_converters()
96+
97+
# Clean up namespace
98+
99+
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}")

0 commit comments

Comments
 (0)