Skip to content

Commit d853758

Browse files
pythongh-91922: Fix sqlite connection on nonstardard locales and paths (pythonGH-92926)
1 parent 0e12781 commit d853758

File tree

4 files changed

+40
-64
lines changed

4 files changed

+40
-64
lines changed

Lib/test/test_sqlite3/test_dbapi.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,19 @@
2121
# 3. This notice may not be removed or altered from any source distribution.
2222

2323
import contextlib
24+
import os
2425
import sqlite3 as sqlite
2526
import subprocess
2627
import sys
2728
import threading
2829
import unittest
30+
import urllib.parse
2931

30-
from test.support import (
31-
SHORT_TIMEOUT,
32-
bigmemtest,
33-
check_disallow_instantiation,
34-
threading_helper,
35-
)
32+
from test.support import SHORT_TIMEOUT, bigmemtest, check_disallow_instantiation
33+
from test.support import threading_helper
3634
from _testcapi import INT_MAX, ULLONG_MAX
3735
from os import SEEK_SET, SEEK_CUR, SEEK_END
38-
from test.support.os_helper import TESTFN, unlink, temp_dir
36+
from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE, unlink, temp_dir, FakePath
3937

4038

4139
# Helper for tests using TESTFN
@@ -654,11 +652,19 @@ class OpenTests(unittest.TestCase):
654652
def test_open_with_path_like_object(self):
655653
""" Checks that we can successfully connect to a database using an object that
656654
is PathLike, i.e. has __fspath__(). """
657-
class Path:
658-
def __fspath__(self):
659-
return TESTFN
660-
path = Path()
655+
path = FakePath(TESTFN)
661656
with managed_connect(path) as cx:
657+
self.assertTrue(os.path.exists(path))
658+
cx.execute(self._sql)
659+
660+
@unittest.skipIf(sys.platform == "win32", "skipped on Windows")
661+
@unittest.skipIf(sys.platform == "darwin", "skipped on macOS")
662+
@unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
663+
def test_open_with_undecodable_path(self):
664+
self.addCleanup(unlink, TESTFN_UNDECODABLE)
665+
path = TESTFN_UNDECODABLE
666+
with managed_connect(path) as cx:
667+
self.assertTrue(os.path.exists(path))
662668
cx.execute(self._sql)
663669

664670
def test_open_uri(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix function :func:`sqlite.connect` and the :class:`sqlite.Connection`
2+
constructor on non-UTF-8 locales. Also, they now support bytes paths
3+
non-decodable with the current FS encoding.

Modules/_sqlite/clinic/connection.c.h

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

Modules/_sqlite/connection.c

+14-42
Original file line numberDiff line numberDiff line change
@@ -92,32 +92,6 @@ isolation_level_converter(PyObject *str_or_none, const char **result)
9292
return 1;
9393
}
9494

95-
static int
96-
clinic_fsconverter(PyObject *pathlike, const char **result)
97-
{
98-
PyObject *bytes = NULL;
99-
Py_ssize_t len;
100-
char *str;
101-
102-
if (!PyUnicode_FSConverter(pathlike, &bytes)) {
103-
goto error;
104-
}
105-
if (PyBytes_AsStringAndSize(bytes, &str, &len) < 0) {
106-
goto error;
107-
}
108-
if ((*result = (const char *)PyMem_Malloc(len+1)) == NULL) {
109-
goto error;
110-
}
111-
112-
memcpy((void *)(*result), str, len+1);
113-
Py_DECREF(bytes);
114-
return 1;
115-
116-
error:
117-
Py_XDECREF(bytes);
118-
return 0;
119-
}
120-
12195
#define clinic_state() (pysqlite_get_state_by_type(Py_TYPE(self)))
12296
#include "clinic/connection.c.h"
12397
#undef clinic_state
@@ -159,25 +133,17 @@ new_statement_cache(pysqlite_Connection *self, pysqlite_state *state,
159133
}
160134

161135
/*[python input]
162-
class FSConverter_converter(CConverter):
163-
type = "const char *"
164-
converter = "clinic_fsconverter"
165-
def converter_init(self):
166-
self.c_default = "NULL"
167-
def cleanup(self):
168-
return f"PyMem_Free((void *){self.name});\n"
169-
170136
class IsolationLevel_converter(CConverter):
171137
type = "const char *"
172138
converter = "isolation_level_converter"
173139
174140
[python start generated code]*/
175-
/*[python end generated code: output=da39a3ee5e6b4b0d input=be142323885672ab]*/
141+
/*[python end generated code: output=da39a3ee5e6b4b0d input=cbcfe85b253061c2]*/
176142

177143
/*[clinic input]
178144
_sqlite3.Connection.__init__ as pysqlite_connection_init
179145
180-
database: FSConverter
146+
database: object
181147
timeout: double = 5.0
182148
detect_types: int = 0
183149
isolation_level: IsolationLevel = ""
@@ -188,14 +154,19 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init
188154
[clinic start generated code]*/
189155

190156
static int
191-
pysqlite_connection_init_impl(pysqlite_Connection *self,
192-
const char *database, double timeout,
193-
int detect_types, const char *isolation_level,
157+
pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
158+
double timeout, int detect_types,
159+
const char *isolation_level,
194160
int check_same_thread, PyObject *factory,
195161
int cache_size, int uri)
196-
/*[clinic end generated code: output=7d640ae1d83abfd4 input=342173993434ba1e]*/
162+
/*[clinic end generated code: output=839eb2fee4293bda input=b8ce63dc6f70a383]*/
197163
{
198-
if (PySys_Audit("sqlite3.connect", "s", database) < 0) {
164+
if (PySys_Audit("sqlite3.connect", "O", database) < 0) {
165+
return -1;
166+
}
167+
168+
PyObject *bytes;
169+
if (!PyUnicode_FSConverter(database, &bytes)) {
199170
return -1;
200171
}
201172

@@ -210,14 +181,15 @@ pysqlite_connection_init_impl(pysqlite_Connection *self,
210181
sqlite3 *db;
211182
int rc;
212183
Py_BEGIN_ALLOW_THREADS
213-
rc = sqlite3_open_v2(database, &db,
184+
rc = sqlite3_open_v2(PyBytes_AS_STRING(bytes), &db,
214185
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
215186
(uri ? SQLITE_OPEN_URI : 0), NULL);
216187
if (rc == SQLITE_OK) {
217188
(void)sqlite3_busy_timeout(db, (int)(timeout*1000));
218189
}
219190
Py_END_ALLOW_THREADS
220191

192+
Py_DECREF(bytes);
221193
if (db == NULL && rc == SQLITE_NOMEM) {
222194
PyErr_NoMemory();
223195
return -1;

0 commit comments

Comments
 (0)