diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27b1a74..7dd82f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,16 +2,23 @@ name: Build Wheels on: pull_request: - banches: + branches: - main push: tags: - '*' +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: - PYTHON_VERSION: '3.11.9' - CIBW_VERSION: '2.21.3' - CIBW_BUILD: 'cp311-*' + PYTHON_VERSION: '3.13.14' + CIBW_VERSION: '3.4.1' + CIBW_BUILD: 'cp313-* cp313t-*' + CIBW_ENABLE: 'cpython-freethreading' CIBW_SKIP: '*-musllinux_*' CIBW_BUILD_VERBOSITY: 1 CIBW_BUILD_FRONTEND: 'build[uv]' @@ -27,7 +34,7 @@ jobs: run: | git clone https://github.com/sqlite/sqlite.git sqlite-src cd sqlite-src - git checkout version-${{ env.SQLITE_VERSION }} + git checkout "version-${SQLITE_VERSION}" - name: Build SQLite amalgamation run: | @@ -38,7 +45,7 @@ jobs: make sqlite3.c - name: Upload SQLite amalgamation files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sqlite-amalgamation path: | @@ -47,41 +54,47 @@ jobs: retention-days: 1 build_wheels_macos: - name: Build macOS wheels (${{ matrix.arch }}) - runs-on: macos-13 + name: Build macOS wheels (${{ matrix.os.arch }}) + runs-on: ${{ matrix.os.runner }} needs: build_sqlite_amalgamation strategy: matrix: - arch: [x86_64, arm64] + os: + - runner: macos-15 + arch: arm64 + - runner: macos-15-intel + arch: x86_64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Download SQLite amalgamation - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: sqlite-amalgamation path: . - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install cibuildwheel run: | - pip install cibuildwheel[uv]==${{ env.CIBW_VERSION }} + python -m pip install "cibuildwheel[uv]==${CIBW_VERSION}" - name: Build wheels env: - CIBW_ARCHS_MACOS: ${{ matrix.arch }} + CIBW_ARCHS_MACOS: ${{ matrix.os.arch }} run: | python -m cibuildwheel --output-dir dist - name: Upload macOS wheels - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: wheel-macos-${{ matrix.arch }}-cp311 + name: wheel-macos-${{ matrix.os.arch }}-cp313-cp313t path: dist/*.whl build_wheels_linux: @@ -97,22 +110,24 @@ jobs: runner: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Download SQLite amalgamation - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: sqlite-amalgamation path: . - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install cibuildwheel run: | - pip install cibuildwheel[uv]==${{ env.CIBW_VERSION }} + python -m pip install "cibuildwheel[uv]==${CIBW_VERSION}" - name: Build wheels env: @@ -121,9 +136,9 @@ jobs: python -m cibuildwheel --output-dir dist - name: Upload Linux wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: wheel-linux-${{ matrix.arch.name }}-cp311 + name: wheel-linux-${{ matrix.arch.name }}-cp313-cp313t path: dist/*.whl build_wheels_windows: @@ -132,31 +147,33 @@ jobs: needs: build_sqlite_amalgamation steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - name: Download SQLite amalgamation - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: sqlite-amalgamation path: . - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install cibuildwheel run: | - pip install cibuildwheel[uv]==${{ env.CIBW_VERSION }} + python -m pip install "cibuildwheel[uv]==$env:CIBW_VERSION" - name: Build wheels run: | python -m cibuildwheel --output-dir dist - name: Upload Windows wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: wheel-windows-cp311 + name: wheel-windows-cp313-cp313t path: dist/*.whl build_sdist: @@ -165,19 +182,19 @@ jobs: needs: build_sqlite_amalgamation steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true persist-credentials: false - name: Download SQLite amalgamation - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: sqlite-amalgamation path: . - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ env.PYTHON_VERSION }} @@ -190,7 +207,7 @@ jobs: python -m build -s - name: Upload sdist - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pypi-sdist path: dist/*.tar.gz @@ -202,7 +219,7 @@ jobs: steps: - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: all-artifacts pattern: '*' @@ -216,7 +233,7 @@ jobs: ls -la dist/ - name: Upload combined distributions - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: python-package-distributions path: dist/* @@ -236,10 +253,10 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 diff --git a/README.md b/README.md index 3fc46e3..17f4fcb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # rsqlite3 -A Python SQLite3 extension module with static SQLite build support. +A Python 3.13 SQLite3 extension module with static SQLite build support. + +The release workflow builds wheels for both standard (`cp313`) and +free-threaded (`cp313t`) Python 3.13 interpreters. ## Building diff --git a/_sqlite/blob.c b/_sqlite/blob.c index 76d261b..c09c858 100644 --- a/_sqlite/blob.c +++ b/_sqlite/blob.c @@ -1,5 +1,10 @@ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "blob.h" #include "util.h" +#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() #define clinic_state() (pysqlite_get_state_by_type(Py_TYPE(self))) #include "clinic/blob.c.h" @@ -47,9 +52,7 @@ blob_dealloc(pysqlite_Blob *self) close_blob(self); - if (self->in_weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)self); - } + FT_CLEAR_WEAKREFS((PyObject*)self, self->in_weakreflist); tp->tp_clear((PyObject *)self); tp->tp_free(self); Py_DECREF(tp); @@ -97,10 +100,12 @@ pysqlite_close_all_blobs(pysqlite_Connection *self) { for (int i = 0; i < PyList_GET_SIZE(self->blobs); i++) { PyObject *weakref = PyList_GET_ITEM(self->blobs, i); - PyObject *blob = PyWeakref_GetObject(weakref); - if (!Py_IsNone(blob)) { - close_blob((pysqlite_Blob *)blob); + PyObject *blob; + if (!PyWeakref_GetRef(weakref, &blob)) { + continue; } + close_blob((pysqlite_Blob *)blob); + Py_DECREF(blob); } } @@ -108,14 +113,6 @@ static void blob_seterror(pysqlite_Blob *self, int rc) { assert(self->connection != NULL); -#if SQLITE_VERSION_NUMBER < 3008008 - // SQLite pre 3.8.8 does not set this blob error on the connection - if (rc == SQLITE_ABORT) { - PyErr_SetString(self->connection->OperationalError, - "Cannot operate on an expired blob handle"); - return; - } -#endif _pysqlite_seterror(self->connection->state, self->connection->db); } @@ -170,14 +167,14 @@ _sqlite3.Blob.read as blob_read Read data at the current offset position. -If the end of the blob is reached, the data up to end of file will be returned. -When length is not specified, or is negative, Blob.read() will read until the -end of the blob. +If the end of the blob is reached, the data up to end of file will +be returned. When length is not specified, or is negative, +Blob.read() will read until the end of the blob. [clinic start generated code]*/ static PyObject * blob_read_impl(pysqlite_Blob *self, int length) -/*[clinic end generated code: output=1fc99b2541360dde input=f2e4aa4378837250]*/ +/*[clinic end generated code: output=1fc99b2541360dde input=6b745ad37720e556]*/ { if (!check_blob(self)) { return NULL; @@ -237,13 +234,13 @@ _sqlite3.Blob.write as blob_write Write data at the current offset. -This function cannot change the blob length. Writing beyond the end of the -blob will result in an exception being raised. +This function cannot change the blob length. Writing beyond the end +of the blob will result in an exception being raised. [clinic start generated code]*/ static PyObject * blob_write_impl(pysqlite_Blob *self, Py_buffer *data) -/*[clinic end generated code: output=b34cf22601b570b2 input=a84712f24a028e6d]*/ +/*[clinic end generated code: output=b34cf22601b570b2 input=0d372cb0240a5d49]*/ { if (!check_blob(self)) { return NULL; @@ -267,14 +264,15 @@ _sqlite3.Blob.seek as blob_seek Set the current access position to offset. -The origin argument defaults to os.SEEK_SET (absolute blob positioning). -Other values for origin are os.SEEK_CUR (seek relative to the current position) -and os.SEEK_END (seek relative to the blob's end). +The origin argument defaults to os.SEEK_SET (absolute blob +positioning). Other values for origin are os.SEEK_CUR (seek +relative to the current position) and os.SEEK_END (seek relative to +the blob's end). [clinic start generated code]*/ static PyObject * blob_seek_impl(pysqlite_Blob *self, int offset, int origin) -/*[clinic end generated code: output=854c5a0e208547a5 input=5da9a07e55fe6bb6]*/ +/*[clinic end generated code: output=854c5a0e208547a5 input=84aea1b6b48607dd]*/ { if (!check_blob(self)) { return NULL; @@ -431,6 +429,10 @@ subscript_slice(pysqlite_Blob *self, PyObject *item) return NULL; } + if (len == 0) { + return PyBytes_FromStringAndSize(NULL, 0); + } + if (step == 1) { return read_multiple(self, len, start); } @@ -515,21 +517,25 @@ ass_subscript_slice(pysqlite_Blob *self, PyObject *item, PyObject *value) return -1; } - if (len == 0) { - return 0; - } - Py_buffer vbuf; if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) { return -1; } - int rc = -1; if (vbuf.len != len) { PyErr_SetString(PyExc_IndexError, "Blob slice assignment is wrong size"); + PyBuffer_Release(&vbuf); + return -1; } - else if (step == 1) { + + if (len == 0) { + PyBuffer_Release(&vbuf); + return 0; + } + + int rc = -1; + if (step == 1) { rc = inner_write(self, vbuf.buf, len, start); } else { @@ -578,7 +584,7 @@ static PyMethodDef blob_methods[] = { }; static struct PyMemberDef blob_members[] = { - {"__weaklistoffset__", T_PYSSIZET, offsetof(pysqlite_Blob, in_weakreflist), READONLY}, + {"__weaklistoffset__", Py_T_PYSSIZET, offsetof(pysqlite_Blob, in_weakreflist), Py_READONLY}, {NULL}, }; diff --git a/_sqlite/clinic/_sqlite3.connect.c.h b/_sqlite/clinic/_sqlite3.connect.c.h new file mode 100644 index 0000000..1bcda77 --- /dev/null +++ b/_sqlite/clinic/_sqlite3.connect.c.h @@ -0,0 +1,31 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif +#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() + +PyDoc_STRVAR(pysqlite_connect__doc__, +"connect($module, /, database, timeout=5.0, detect_types=0,\n" +" isolation_level=\'\', check_same_thread=True,\n" +" factory=ConnectionType, cached_statements=128, uri=False, *,\n" +" autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL)\n" +"--\n" +"\n" +"Open a connection to the SQLite database file \'database\'.\n" +"\n" +"You can use \":memory:\" to open a database connection to a database that\n" +"resides in RAM instead of on disk.\n" +"\n" +"Note: Passing more than 1 positional argument to _sqlite3.connect() is\n" +"deprecated. Parameters \'timeout\', \'detect_types\', \'isolation_level\',\n" +"\'check_same_thread\', \'factory\', \'cached_statements\' and \'uri\' will\n" +"become keyword-only parameters in Python 3.15.\n" +""); + +#define PYSQLITE_CONNECT_METHODDEF \ + {"connect", _PyCFunction_CAST(pysqlite_connect), METH_FASTCALL|METH_KEYWORDS, pysqlite_connect__doc__}, +/*[clinic end generated code: output=69b9b00da71c3c0a input=a9049054013a1b77]*/ diff --git a/_sqlite/clinic/blob.c.h b/_sqlite/clinic/blob.c.h index b467c99..bfafcf0 100644 --- a/_sqlite/clinic/blob.c.h +++ b/_sqlite/clinic/blob.c.h @@ -2,6 +2,8 @@ preserve [clinic start generated code]*/ +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + PyDoc_STRVAR(blob_close__doc__, "close($self, /)\n" "--\n" @@ -29,9 +31,9 @@ PyDoc_STRVAR(blob_read__doc__, " length\n" " Read length in bytes.\n" "\n" -"If the end of the blob is reached, the data up to end of file will be returned.\n" -"When length is not specified, or is negative, Blob.read() will read until the\n" -"end of the blob."); +"If the end of the blob is reached, the data up to end of file will\n" +"be returned. When length is not specified, or is negative,\n" +"Blob.read() will read until the end of the blob."); #define BLOB_READ_METHODDEF \ {"read", _PyCFunction_CAST(blob_read), METH_FASTCALL, blob_read__doc__}, @@ -51,7 +53,7 @@ blob_read(pysqlite_Blob *self, PyObject *const *args, Py_ssize_t nargs) if (nargs < 1) { goto skip_optional; } - length = _PyLong_AsInt(args[0]); + length = PyLong_AsInt(args[0]); if (length == -1 && PyErr_Occurred()) { goto exit; } @@ -68,8 +70,8 @@ PyDoc_STRVAR(blob_write__doc__, "\n" "Write data at the current offset.\n" "\n" -"This function cannot change the blob length. Writing beyond the end of the\n" -"blob will result in an exception being raised."); +"This function cannot change the blob length. Writing beyond the end\n" +"of the blob will result in an exception being raised."); #define BLOB_WRITE_METHODDEF \ {"write", (PyCFunction)blob_write, METH_O, blob_write__doc__}, @@ -86,10 +88,6 @@ blob_write(pysqlite_Blob *self, PyObject *arg) if (PyObject_GetBuffer(arg, &data, PyBUF_SIMPLE) != 0) { goto exit; } - if (!PyBuffer_IsContiguous(&data, 'C')) { - _PyArg_BadArgument("write", "argument", "contiguous buffer", arg); - goto exit; - } return_value = blob_write_impl(self, &data); exit: @@ -107,9 +105,10 @@ PyDoc_STRVAR(blob_seek__doc__, "\n" "Set the current access position to offset.\n" "\n" -"The origin argument defaults to os.SEEK_SET (absolute blob positioning).\n" -"Other values for origin are os.SEEK_CUR (seek relative to the current position)\n" -"and os.SEEK_END (seek relative to the blob\'s end)."); +"The origin argument defaults to os.SEEK_SET (absolute blob\n" +"positioning). Other values for origin are os.SEEK_CUR (seek\n" +"relative to the current position) and os.SEEK_END (seek relative to\n" +"the blob\'s end)."); #define BLOB_SEEK_METHODDEF \ {"seek", _PyCFunction_CAST(blob_seek), METH_FASTCALL, blob_seek__doc__}, @@ -127,14 +126,14 @@ blob_seek(pysqlite_Blob *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("seek", nargs, 1, 2)) { goto exit; } - offset = _PyLong_AsInt(args[0]); + offset = PyLong_AsInt(args[0]); if (offset == -1 && PyErr_Occurred()) { goto exit; } if (nargs < 2) { goto skip_optional; } - origin = _PyLong_AsInt(args[1]); + origin = PyLong_AsInt(args[1]); if (origin == -1 && PyErr_Occurred()) { goto exit; } @@ -213,4 +212,4 @@ blob_exit(pysqlite_Blob *self, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=382cbf0977bb158a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=45168bcaeea61140 input=a9049054013a1b77]*/ diff --git a/_sqlite/clinic/connection.c.h b/_sqlite/clinic/connection.c.h index 4bdf98b..4eede40 100644 --- a/_sqlite/clinic/connection.c.h +++ b/_sqlite/clinic/connection.c.h @@ -2,20 +2,61 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif +#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() + static int pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, double timeout, int detect_types, const char *isolation_level, int check_same_thread, PyObject *factory, - int cache_size, int uri); + int cache_size, int uri, + enum autocommit_mode autocommit); + +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.__init__'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.__init__'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.__init__'." +# endif +#endif static int pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "Connection", 0}; - PyObject *argsbuf[8]; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 9 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(database), &_Py_ID(timeout), &_Py_ID(detect_types), &_Py_ID(isolation_level), &_Py_ID(check_same_thread), &_Py_ID(factory), &_Py_ID(cached_statements), &_Py_ID(uri), &_Py_ID(autocommit), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", "uri", "autocommit", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "Connection", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[9]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1; @@ -27,7 +68,19 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *factory = (PyObject*)clinic_state()->ConnectionType; int cache_size = 128; int uri = 0; - + enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL; + + if (nargs > 1 && nargs <= 8) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing more than 1 positional argument to _sqlite3.Connection()" + " is deprecated. Parameters 'timeout', 'detect_types', " + "'isolation_level', 'check_same_thread', 'factory', " + "'cached_statements' and 'uri' will become keyword-only " + "parameters in Python 3.15.", 1)) + { + goto exit; + } + } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { goto exit; @@ -52,7 +105,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) } } if (fastargs[2]) { - detect_types = _PyLong_AsInt(fastargs[2]); + detect_types = PyLong_AsInt(fastargs[2]); if (detect_types == -1 && PyErr_Occurred()) { goto exit; } @@ -69,8 +122,8 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) } } if (fastargs[4]) { - check_same_thread = _PyLong_AsInt(fastargs[4]); - if (check_same_thread == -1 && PyErr_Occurred()) { + check_same_thread = PyObject_IsTrue(fastargs[4]); + if (check_same_thread < 0) { goto exit; } if (!--noptargs) { @@ -84,7 +137,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) } } if (fastargs[6]) { - cache_size = _PyLong_AsInt(fastargs[6]); + cache_size = PyLong_AsInt(fastargs[6]); if (cache_size == -1 && PyErr_Occurred()) { goto exit; } @@ -92,12 +145,24 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) goto skip_optional_pos; } } - uri = PyObject_IsTrue(fastargs[7]); - if (uri < 0) { - goto exit; + if (fastargs[7]) { + uri = PyObject_IsTrue(fastargs[7]); + if (uri < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } } skip_optional_pos: - return_value = pysqlite_connection_init_impl((pysqlite_Connection *)self, database, timeout, detect_types, isolation_level, check_same_thread, factory, cache_size, uri); + if (!noptargs) { + goto skip_optional_kwonly; + } + if (!autocommit_converter(fastargs[8], &autocommit)) { + goto exit; + } +skip_optional_kwonly: + return_value = pysqlite_connection_init_impl((pysqlite_Connection *)self, database, timeout, detect_types, isolation_level, check_same_thread, factory, cache_size, uri, autocommit); exit: return return_value; @@ -119,8 +184,31 @@ static PyObject * pysqlite_connection_cursor(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(factory), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"factory", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "cursor", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "cursor", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; PyObject *factory = NULL; @@ -141,7 +229,7 @@ pysqlite_connection_cursor(pysqlite_Connection *self, PyObject *const *args, Py_ } PyDoc_STRVAR(blobopen__doc__, -"blobopen($self, table, column, row, /, *, readonly=False, name=\'main\')\n" +"blobopen($self, table, column, rowid, /, *, readonly=False, name=\'main\')\n" "--\n" "\n" "Open and return a BLOB object.\n" @@ -150,8 +238,8 @@ PyDoc_STRVAR(blobopen__doc__, " Table name.\n" " column\n" " Column name.\n" -" row\n" -" Row index.\n" +" rowid\n" +" Row id.\n" " readonly\n" " Open the BLOB without write permissions.\n" " name\n" @@ -168,8 +256,31 @@ static PyObject * blobopen(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(readonly), &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"", "", "", "readonly", "name", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "blobopen", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "blobopen", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 3; const char *table; @@ -215,8 +326,8 @@ blobopen(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyO goto skip_optional_kwonly; } if (args[3]) { - readonly = _PyLong_AsInt(args[3]); - if (readonly == -1 && PyErr_Occurred()) { + readonly = PyObject_IsTrue(args[3]); + if (readonly < 0) { goto exit; } if (!--noptargs) { @@ -307,7 +418,12 @@ PyDoc_STRVAR(pysqlite_connection_create_function__doc__, "create_function($self, /, name, narg, func, *, deterministic=False)\n" "--\n" "\n" -"Creates a new function."); +"Creates a new function.\n" +"\n" +"Note: Passing keyword arguments \'name\', \'narg\' and \'func\' to\n" +"_sqlite3.Connection.create_function() is deprecated. Parameters\n" +"\'name\', \'narg\' and \'func\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_CREATE_FUNCTION_METHODDEF \ {"create_function", _PyCFunction_CAST(pysqlite_connection_create_function), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_create_function__doc__}, @@ -318,12 +434,46 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self, int narg, PyObject *func, int deterministic); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.create_function'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.create_function'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.create_function'." +# endif +#endif + static PyObject * pysqlite_connection_create_function(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(name), &_Py_ID(narg), &_Py_ID(func), &_Py_ID(deterministic), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"name", "narg", "func", "deterministic", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "create_function", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "create_function", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[4]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 3; const char *name; @@ -335,6 +485,16 @@ pysqlite_connection_create_function(pysqlite_Connection *self, PyTypeObject *cls if (!args) { goto exit; } + if (nargs < 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'name', 'narg' and 'func' to " + "_sqlite3.Connection.create_function() is deprecated. Parameters " + "'name', 'narg' and 'func' will become positional-only in Python " + "3.15.", 1)) + { + goto exit; + } + } if (!PyUnicode_Check(args[0])) { _PyArg_BadArgument("create_function", "argument 'name'", "str", args[0]); goto exit; @@ -348,7 +508,7 @@ pysqlite_connection_create_function(pysqlite_Connection *self, PyTypeObject *cls PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; } - narg = _PyLong_AsInt(args[1]); + narg = PyLong_AsInt(args[1]); if (narg == -1 && PyErr_Occurred()) { goto exit; } @@ -396,8 +556,19 @@ static PyObject * create_window_function(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + static const char * const _keywords[] = {"", "", "", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "create_window_function", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "create_window_function", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[3]; const char *name; int num_params; @@ -420,7 +591,7 @@ create_window_function(pysqlite_Connection *self, PyTypeObject *cls, PyObject *c PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; } - num_params = _PyLong_AsInt(args[1]); + num_params = PyLong_AsInt(args[1]); if (num_params == -1 && PyErr_Occurred()) { goto exit; } @@ -437,7 +608,13 @@ PyDoc_STRVAR(pysqlite_connection_create_aggregate__doc__, "create_aggregate($self, /, name, n_arg, aggregate_class)\n" "--\n" "\n" -"Creates a new aggregate."); +"Creates a new aggregate.\n" +"\n" +"Note: Passing keyword arguments \'name\', \'n_arg\' and \'aggregate_class\'\n" +"to _sqlite3.Connection.create_aggregate() is deprecated. Parameters\n" +"\'name\', \'n_arg\' and \'aggregate_class\' will become positional-only in\n" +"Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_CREATE_AGGREGATE_METHODDEF \ {"create_aggregate", _PyCFunction_CAST(pysqlite_connection_create_aggregate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_create_aggregate__doc__}, @@ -448,12 +625,46 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self, const char *name, int n_arg, PyObject *aggregate_class); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.create_aggregate'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.create_aggregate'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.create_aggregate'." +# endif +#endif + static PyObject * pysqlite_connection_create_aggregate(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(name), &_Py_ID(n_arg), &_Py_ID(aggregate_class), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"name", "n_arg", "aggregate_class", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "create_aggregate", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "create_aggregate", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[3]; const char *name; int n_arg; @@ -463,6 +674,16 @@ pysqlite_connection_create_aggregate(pysqlite_Connection *self, PyTypeObject *cl if (!args) { goto exit; } + if (nargs < 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'name', 'n_arg' and 'aggregate_class' " + "to _sqlite3.Connection.create_aggregate() is deprecated. " + "Parameters 'name', 'n_arg' and 'aggregate_class' will become " + "positional-only in Python 3.15.", 1)) + { + goto exit; + } + } if (!PyUnicode_Check(args[0])) { _PyArg_BadArgument("create_aggregate", "argument 'name'", "str", args[0]); goto exit; @@ -476,7 +697,7 @@ pysqlite_connection_create_aggregate(pysqlite_Connection *self, PyTypeObject *cl PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; } - n_arg = _PyLong_AsInt(args[1]); + n_arg = PyLong_AsInt(args[1]); if (n_arg == -1 && PyErr_Occurred()) { goto exit; } @@ -491,7 +712,12 @@ PyDoc_STRVAR(pysqlite_connection_set_authorizer__doc__, "set_authorizer($self, /, authorizer_callback)\n" "--\n" "\n" -"Sets authorizer callback."); +"Set authorizer callback.\n" +"\n" +"Note: Passing keyword argument \'authorizer_callback\' to\n" +"_sqlite3.Connection.set_authorizer() is deprecated. Parameter\n" +"\'authorizer_callback\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF \ {"set_authorizer", _PyCFunction_CAST(pysqlite_connection_set_authorizer), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_authorizer__doc__}, @@ -501,12 +727,46 @@ pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_authorizer'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_authorizer'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_authorizer'." +# endif +#endif + static PyObject * pysqlite_connection_set_authorizer(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(authorizer_callback), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"authorizer_callback", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "set_authorizer", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_authorizer", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; PyObject *callable; @@ -514,6 +774,16 @@ pysqlite_connection_set_authorizer(pysqlite_Connection *self, PyTypeObject *cls, if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'authorizer_callback' to " + "_sqlite3.Connection.set_authorizer() is deprecated. Parameter " + "'authorizer_callback' will become positional-only in Python " + "3.15.", 1)) + { + goto exit; + } + } callable = args[0]; return_value = pysqlite_connection_set_authorizer_impl(self, cls, callable); @@ -525,7 +795,23 @@ PyDoc_STRVAR(pysqlite_connection_set_progress_handler__doc__, "set_progress_handler($self, /, progress_handler, n)\n" "--\n" "\n" -"Sets progress handler callback."); +"Set progress handler callback.\n" +"\n" +" progress_handler\n" +" A callable that takes no arguments.\n" +" If the callable returns non-zero, the current query is\n" +" terminated, and an exception is raised.\n" +" n\n" +" The number of SQLite virtual machine instructions that are\n" +" executed between invocations of \'progress_handler\'.\n" +"\n" +"If \'progress_handler\' is None or \'n\' is 0, the progress handler is\n" +"disabled.\n" +"\n" +"Note: Passing keyword argument \'progress_handler\' to\n" +"_sqlite3.Connection.set_progress_handler() is deprecated. Parameter\n" +"\'progress_handler\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF \ {"set_progress_handler", _PyCFunction_CAST(pysqlite_connection_set_progress_handler), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_progress_handler__doc__}, @@ -535,12 +821,46 @@ pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable, int n); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_progress_handler'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_progress_handler'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_progress_handler'." +# endif +#endif + static PyObject * pysqlite_connection_set_progress_handler(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(progress_handler), _Py_LATIN1_CHR('n'), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"progress_handler", "n", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "set_progress_handler", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_progress_handler", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[2]; PyObject *callable; int n; @@ -549,8 +869,18 @@ pysqlite_connection_set_progress_handler(pysqlite_Connection *self, PyTypeObject if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'progress_handler' to " + "_sqlite3.Connection.set_progress_handler() is deprecated. " + "Parameter 'progress_handler' will become positional-only in " + "Python 3.15.", 1)) + { + goto exit; + } + } callable = args[0]; - n = _PyLong_AsInt(args[1]); + n = PyLong_AsInt(args[1]); if (n == -1 && PyErr_Occurred()) { goto exit; } @@ -564,7 +894,12 @@ PyDoc_STRVAR(pysqlite_connection_set_trace_callback__doc__, "set_trace_callback($self, /, trace_callback)\n" "--\n" "\n" -"Sets a trace callback called for each SQL statement (passed as unicode)."); +"Set a trace callback called for each SQL statement (passed as unicode).\n" +"\n" +"Note: Passing keyword argument \'trace_callback\' to\n" +"_sqlite3.Connection.set_trace_callback() is deprecated. Parameter\n" +"\'trace_callback\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF \ {"set_trace_callback", _PyCFunction_CAST(pysqlite_connection_set_trace_callback), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_trace_callback__doc__}, @@ -574,12 +909,46 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_trace_callback'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_trace_callback'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_trace_callback'." +# endif +#endif + static PyObject * pysqlite_connection_set_trace_callback(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(trace_callback), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"trace_callback", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "set_trace_callback", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "set_trace_callback", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; PyObject *callable; @@ -587,6 +956,16 @@ pysqlite_connection_set_trace_callback(pysqlite_Connection *self, PyTypeObject * if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'trace_callback' to " + "_sqlite3.Connection.set_trace_callback() is deprecated. " + "Parameter 'trace_callback' will become positional-only in Python" + " 3.15.", 1)) + { + goto exit; + } + } callable = args[0]; return_value = pysqlite_connection_set_trace_callback_impl(self, cls, callable); @@ -615,8 +994,8 @@ pysqlite_connection_enable_load_extension(pysqlite_Connection *self, PyObject *a PyObject *return_value = NULL; int onoff; - onoff = _PyLong_AsInt(arg); - if (onoff == -1 && PyErr_Occurred()) { + onoff = PyObject_IsTrue(arg); + if (onoff < 0) { goto exit; } return_value = pysqlite_connection_enable_load_extension_impl(self, onoff); @@ -630,30 +1009,63 @@ pysqlite_connection_enable_load_extension(pysqlite_Connection *self, PyObject *a #if defined(PY_SQLITE_ENABLE_LOAD_EXTENSION) PyDoc_STRVAR(pysqlite_connection_load_extension__doc__, -"load_extension($self, name, /)\n" +"load_extension($self, name, /, *, entrypoint=None)\n" "--\n" "\n" "Load SQLite extension module."); #define PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF \ - {"load_extension", (PyCFunction)pysqlite_connection_load_extension, METH_O, pysqlite_connection_load_extension__doc__}, + {"load_extension", _PyCFunction_CAST(pysqlite_connection_load_extension), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_load_extension__doc__}, static PyObject * pysqlite_connection_load_extension_impl(pysqlite_Connection *self, - const char *extension_name); + const char *extension_name, + const char *entrypoint); static PyObject * -pysqlite_connection_load_extension(pysqlite_Connection *self, PyObject *arg) +pysqlite_connection_load_extension(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(entrypoint), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "entrypoint", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "load_extension", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; const char *extension_name; + const char *entrypoint = NULL; - if (!PyUnicode_Check(arg)) { - _PyArg_BadArgument("load_extension", "argument", "str", arg); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!PyUnicode_Check(args[0])) { + _PyArg_BadArgument("load_extension", "argument 1", "str", args[0]); goto exit; } Py_ssize_t extension_name_length; - extension_name = PyUnicode_AsUTF8AndSize(arg, &extension_name_length); + extension_name = PyUnicode_AsUTF8AndSize(args[0], &extension_name_length); if (extension_name == NULL) { goto exit; } @@ -661,7 +1073,29 @@ pysqlite_connection_load_extension(pysqlite_Connection *self, PyObject *arg) PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; } - return_value = pysqlite_connection_load_extension_impl(self, extension_name); + if (!noptargs) { + goto skip_optional_kwonly; + } + if (args[1] == Py_None) { + entrypoint = NULL; + } + else if (PyUnicode_Check(args[1])) { + Py_ssize_t entrypoint_length; + entrypoint = PyUnicode_AsUTF8AndSize(args[1], &entrypoint_length); + if (entrypoint == NULL) { + goto exit; + } + if (strlen(entrypoint) != (size_t)entrypoint_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + } + else { + _PyArg_BadArgument("load_extension", "argument 'entrypoint'", "str or None", args[1]); + goto exit; + } +skip_optional_kwonly: + return_value = pysqlite_connection_load_extension_impl(self, extension_name, entrypoint); exit: return return_value; @@ -696,9 +1130,6 @@ pysqlite_connection_execute(pysqlite_Connection *self, PyObject *const *args, Py _PyArg_BadArgument("execute", "argument 1", "str", args[0]); goto exit; } - if (PyUnicode_READY(args[0]) == -1) { - goto exit; - } sql = args[0]; if (nargs < 2) { goto skip_optional; @@ -738,9 +1169,6 @@ pysqlite_connection_executemany(pysqlite_Connection *self, PyObject *const *args _PyArg_BadArgument("executemany", "argument 1", "str", args[0]); goto exit; } - if (PyUnicode_READY(args[0]) == -1) { - goto exit; - } sql = args[0]; parameters = args[1]; return_value = pysqlite_connection_executemany_impl(self, sql, parameters); @@ -777,21 +1205,67 @@ pysqlite_connection_interrupt(pysqlite_Connection *self, PyObject *Py_UNUSED(ign } PyDoc_STRVAR(pysqlite_connection_iterdump__doc__, -"iterdump($self, /)\n" +"iterdump($self, /, *, filter=None)\n" "--\n" "\n" -"Returns iterator to the dump of the database in an SQL text format."); +"Returns iterator to the dump of the database in an SQL text format.\n" +"\n" +" filter\n" +" An optional LIKE pattern for database objects to dump"); #define PYSQLITE_CONNECTION_ITERDUMP_METHODDEF \ - {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, pysqlite_connection_iterdump__doc__}, + {"iterdump", _PyCFunction_CAST(pysqlite_connection_iterdump), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_iterdump__doc__}, static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self); +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter); static PyObject * -pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *Py_UNUSED(ignored)) +pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return pysqlite_connection_iterdump_impl(self); + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(filter), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"filter", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "iterdump", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *filter = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + filter = args[0]; +skip_optional_kwonly: + return_value = pysqlite_connection_iterdump_impl(self, filter); + +exit: + return return_value; } PyDoc_STRVAR(pysqlite_connection_backup__doc__, @@ -814,8 +1288,31 @@ static PyObject * pysqlite_connection_backup(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 5 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(target), &_Py_ID(pages), &_Py_ID(progress), &_Py_ID(name), &_Py_ID(sleep), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"target", "pages", "progress", "name", "sleep", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "backup", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "backup", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; pysqlite_Connection *target; @@ -837,7 +1334,7 @@ pysqlite_connection_backup(pysqlite_Connection *self, PyObject *const *args, Py_ goto skip_optional_kwonly; } if (args[1]) { - pages = _PyLong_AsInt(args[1]); + pages = PyLong_AsInt(args[1]); if (pages == -1 && PyErr_Occurred()) { goto exit; } @@ -905,8 +1402,19 @@ static PyObject * pysqlite_connection_create_collation(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty) + #else + # define KWTUPLE NULL + #endif + static const char * const _keywords[] = {"", "", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "create_collation", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "create_collation", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[2]; const char *name; PyObject *callable; @@ -946,10 +1454,10 @@ PyDoc_STRVAR(serialize__doc__, " name\n" " Which database to serialize.\n" "\n" -"For an ordinary on-disk database file, the serialization is just a copy of the\n" -"disk file. For an in-memory database or a \"temp\" database, the serialization is\n" -"the same sequence of bytes which would be written to disk if that database\n" -"were backed up to disk."); +"For an ordinary on-disk database file, the serialization is just\n" +"a copy of the disk file. For an in-memory database or a \"temp\"\n" +"database, the serialization is the same sequence of bytes which\n" +"would be written to disk if that database were backed up to disk."); #define SERIALIZE_METHODDEF \ {"serialize", _PyCFunction_CAST(serialize), METH_FASTCALL|METH_KEYWORDS, serialize__doc__}, @@ -961,8 +1469,31 @@ static PyObject * serialize(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"name", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "serialize", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "serialize", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; const char *name = "main"; @@ -1009,12 +1540,13 @@ PyDoc_STRVAR(deserialize__doc__, " name\n" " Which database to reopen with the deserialization.\n" "\n" -"The deserialize interface causes the database connection to disconnect from the\n" -"target database, and then reopen it as an in-memory database based on the given\n" -"serialized data.\n" +"The deserialize interface causes the database connection to\n" +"disconnect from the target database, and then reopen it as\n" +"an in-memory database based on the given serialized data.\n" "\n" -"The deserialize interface will fail with SQLITE_BUSY if the database is\n" -"currently in a read transaction or is involved in a backup operation."); +"The deserialize interface will fail with SQLITE_BUSY if the database\n" +"is currently in a read transaction or is involved in a backup\n" +"operation."); #define DESERIALIZE_METHODDEF \ {"deserialize", _PyCFunction_CAST(deserialize), METH_FASTCALL|METH_KEYWORDS, deserialize__doc__}, @@ -1027,8 +1559,31 @@ static PyObject * deserialize(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"", "name", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "deserialize", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "deserialize", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; @@ -1044,16 +1599,14 @@ deserialize(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { goto exit; } - if (!PyBuffer_IsContiguous(&data, 'C')) { - _PyArg_BadArgument("deserialize", "argument 1", "contiguous buffer", args[0]); - goto exit; - } } if (!noptargs) { goto skip_optional_kwonly; @@ -1111,7 +1664,8 @@ PyDoc_STRVAR(pysqlite_connection_exit__doc__, "\n" "Called when the connection is used as a context manager.\n" "\n" -"If there was any exception, a rollback takes place; otherwise we commit."); +"If there was any exception, a rollback takes place; otherwise we\n" +"commit."); #define PYSQLITE_CONNECTION_EXIT_METHODDEF \ {"__exit__", _PyCFunction_CAST(pysqlite_connection_exit), METH_FASTCALL, pysqlite_connection_exit__doc__}, @@ -1149,12 +1703,12 @@ PyDoc_STRVAR(setlimit__doc__, " category\n" " The limit category to be set.\n" " limit\n" -" The new limit. If the new limit is a negative number, the limit is\n" -" unchanged.\n" +" The new limit. If the new limit is a negative number, the limit\n" +" is unchanged.\n" "\n" -"Attempts to increase a limit above its hard upper bound are silently truncated\n" -"to the hard upper bound. Regardless of whether or not the limit was changed,\n" -"the prior value of the limit is returned."); +"Attempts to increase a limit above its hard upper bound are silently\n" +"truncated to the hard upper bound. Regardless of whether or not the\n" +"limit was changed, the prior value of the limit is returned."); #define SETLIMIT_METHODDEF \ {"setlimit", _PyCFunction_CAST(setlimit), METH_FASTCALL, setlimit__doc__}, @@ -1172,11 +1726,11 @@ setlimit(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("setlimit", nargs, 2, 2)) { goto exit; } - category = _PyLong_AsInt(args[0]); + category = PyLong_AsInt(args[0]); if (category == -1 && PyErr_Occurred()) { goto exit; } - limit = _PyLong_AsInt(args[1]); + limit = PyLong_AsInt(args[1]); if (limit == -1 && PyErr_Occurred()) { goto exit; } @@ -1207,7 +1761,7 @@ getlimit(pysqlite_Connection *self, PyObject *arg) PyObject *return_value = NULL; int category; - category = _PyLong_AsInt(arg); + category = PyLong_AsInt(arg); if (category == -1 && PyErr_Occurred()) { goto exit; } @@ -1217,6 +1771,85 @@ getlimit(pysqlite_Connection *self, PyObject *arg) return return_value; } +PyDoc_STRVAR(setconfig__doc__, +"setconfig($self, op, enable=True, /)\n" +"--\n" +"\n" +"Set a boolean connection configuration option.\n" +"\n" +" op\n" +" The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes."); + +#define SETCONFIG_METHODDEF \ + {"setconfig", _PyCFunction_CAST(setconfig), METH_FASTCALL, setconfig__doc__}, + +static PyObject * +setconfig_impl(pysqlite_Connection *self, int op, int enable); + +static PyObject * +setconfig(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int op; + int enable = 1; + + if (!_PyArg_CheckPositional("setconfig", nargs, 1, 2)) { + goto exit; + } + op = PyLong_AsInt(args[0]); + if (op == -1 && PyErr_Occurred()) { + goto exit; + } + if (nargs < 2) { + goto skip_optional; + } + enable = PyObject_IsTrue(args[1]); + if (enable < 0) { + goto exit; + } +skip_optional: + return_value = setconfig_impl(self, op, enable); + +exit: + return return_value; +} + +PyDoc_STRVAR(getconfig__doc__, +"getconfig($self, op, /)\n" +"--\n" +"\n" +"Query a boolean connection configuration option.\n" +"\n" +" op\n" +" The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes."); + +#define GETCONFIG_METHODDEF \ + {"getconfig", (PyCFunction)getconfig, METH_O, getconfig__doc__}, + +static int +getconfig_impl(pysqlite_Connection *self, int op); + +static PyObject * +getconfig(pysqlite_Connection *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int op; + int _return_value; + + op = PyLong_AsInt(arg); + if (op == -1 && PyErr_Occurred()) { + goto exit; + } + _return_value = getconfig_impl(self, op); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + #ifndef CREATE_WINDOW_FUNCTION_METHODDEF #define CREATE_WINDOW_FUNCTION_METHODDEF #endif /* !defined(CREATE_WINDOW_FUNCTION_METHODDEF) */ @@ -1236,4 +1869,4 @@ getlimit(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=67e5b3dbade4a15b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5e0bcef289eb64cc input=a9049054013a1b77]*/ diff --git a/_sqlite/clinic/cursor.c.h b/_sqlite/clinic/cursor.c.h index b29c333..56b849d 100644 --- a/_sqlite/clinic/cursor.c.h +++ b/_sqlite/clinic/cursor.c.h @@ -2,6 +2,13 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif +#include "pycore_long.h" // _PyLong_Size_t_Converter() +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + static int pysqlite_cursor_init_impl(pysqlite_Cursor *self, pysqlite_Connection *connection); @@ -10,10 +17,11 @@ static int pysqlite_cursor_init(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; + PyTypeObject *base_tp = clinic_state()->CursorType; pysqlite_Connection *connection; - if ((Py_IS_TYPE(self, clinic_state()->CursorType) || - Py_TYPE(self)->tp_new == clinic_state()->CursorType->tp_new) && + if ((Py_IS_TYPE(self, base_tp) || + Py_TYPE(self)->tp_new == base_tp->tp_new) && !_PyArg_NoKeywords("Cursor", kwargs)) { goto exit; } @@ -58,9 +66,6 @@ pysqlite_cursor_execute(pysqlite_Cursor *self, PyObject *const *args, Py_ssize_t _PyArg_BadArgument("execute", "argument 1", "str", args[0]); goto exit; } - if (PyUnicode_READY(args[0]) == -1) { - goto exit; - } sql = args[0]; if (nargs < 2) { goto skip_optional; @@ -100,9 +105,6 @@ pysqlite_cursor_executemany(pysqlite_Cursor *self, PyObject *const *args, Py_ssi _PyArg_BadArgument("executemany", "argument 1", "str", args[0]); goto exit; } - if (PyUnicode_READY(args[0]) == -1) { - goto exit; - } sql = args[0]; seq_of_parameters = args[1]; return_value = pysqlite_cursor_executemany_impl(self, sql, seq_of_parameters); @@ -180,17 +182,40 @@ PyDoc_STRVAR(pysqlite_cursor_fetchmany__doc__, {"fetchmany", _PyCFunction_CAST(pysqlite_cursor_fetchmany), METH_FASTCALL|METH_KEYWORDS, pysqlite_cursor_fetchmany__doc__}, static PyObject * -pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, int maxrows); +pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, size_t maxrows); static PyObject * pysqlite_cursor_fetchmany(pysqlite_Cursor *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(size), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"size", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "fetchmany", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "fetchmany", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; - int maxrows = self->arraysize; + size_t maxrows = ((pysqlite_Cursor *)self)->arraysize; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); if (!args) { @@ -199,8 +224,7 @@ pysqlite_cursor_fetchmany(pysqlite_Cursor *self, PyObject *const *args, Py_ssize if (!noptargs) { goto skip_optional_pos; } - maxrows = _PyLong_AsInt(args[0]); - if (maxrows == -1 && PyErr_Occurred()) { + if (!_PyLong_Size_t_Converter(args[0], &maxrows)) { goto exit; } skip_optional_pos: @@ -289,4 +313,46 @@ pysqlite_cursor_close(pysqlite_Cursor *self, PyObject *Py_UNUSED(ignored)) { return pysqlite_cursor_close_impl(self); } -/*[clinic end generated code: output=2b9c6a3ca8a8caff input=a9049054013a1b77]*/ + +#if !defined(_sqlite3_Cursor_arraysize_DOCSTR) +# define _sqlite3_Cursor_arraysize_DOCSTR NULL +#endif +#if defined(_SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF) +# undef _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF +# define _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF {"arraysize", (getter)_sqlite3_Cursor_arraysize_get, (setter)_sqlite3_Cursor_arraysize_set, _sqlite3_Cursor_arraysize_DOCSTR}, +#else +# define _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF {"arraysize", (getter)_sqlite3_Cursor_arraysize_get, NULL, _sqlite3_Cursor_arraysize_DOCSTR}, +#endif + +static PyObject * +_sqlite3_Cursor_arraysize_get_impl(pysqlite_Cursor *self); + +static PyObject * +_sqlite3_Cursor_arraysize_get(pysqlite_Cursor *self, void *Py_UNUSED(context)) +{ + return _sqlite3_Cursor_arraysize_get_impl(self); +} + +#if !defined(_sqlite3_Cursor_arraysize_DOCSTR) +# define _sqlite3_Cursor_arraysize_DOCSTR NULL +#endif +#if defined(_SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF) +# undef _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF +# define _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF {"arraysize", (getter)_sqlite3_Cursor_arraysize_get, (setter)_sqlite3_Cursor_arraysize_set, _sqlite3_Cursor_arraysize_DOCSTR}, +#else +# define _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF {"arraysize", NULL, (setter)_sqlite3_Cursor_arraysize_set, NULL}, +#endif + +static int +_sqlite3_Cursor_arraysize_set_impl(pysqlite_Cursor *self, PyObject *value); + +static int +_sqlite3_Cursor_arraysize_set(pysqlite_Cursor *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + return_value = _sqlite3_Cursor_arraysize_set_impl(self, value); + + return return_value; +} +/*[clinic end generated code: output=332c298249ae57b0 input=a9049054013a1b77]*/ diff --git a/_sqlite/clinic/module.c.h b/_sqlite/clinic/module.c.h index f527807..529dc4e 100644 --- a/_sqlite/clinic/module.c.h +++ b/_sqlite/clinic/module.c.h @@ -2,6 +2,12 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif +#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() + PyDoc_STRVAR(pysqlite_complete_statement__doc__, "complete_statement($module, /, statement)\n" "--\n" @@ -18,8 +24,31 @@ static PyObject * pysqlite_complete_statement(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(statement), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + static const char * const _keywords[] = {"statement", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "complete_statement", 0}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "complete_statement", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE PyObject *argsbuf[1]; const char *statement; @@ -46,46 +75,6 @@ pysqlite_complete_statement(PyObject *module, PyObject *const *args, Py_ssize_t return return_value; } -PyDoc_STRVAR(pysqlite_enable_shared_cache__doc__, -"enable_shared_cache($module, /, do_enable)\n" -"--\n" -"\n" -"Enable or disable shared cache mode for the calling thread.\n" -"\n" -"This method is deprecated and will be removed in Python 3.12.\n" -"Shared cache is strongly discouraged by the SQLite 3 documentation.\n" -"If shared cache must be used, open the database in URI mode using\n" -"the cache=shared query parameter."); - -#define PYSQLITE_ENABLE_SHARED_CACHE_METHODDEF \ - {"enable_shared_cache", _PyCFunction_CAST(pysqlite_enable_shared_cache), METH_FASTCALL|METH_KEYWORDS, pysqlite_enable_shared_cache__doc__}, - -static PyObject * -pysqlite_enable_shared_cache_impl(PyObject *module, int do_enable); - -static PyObject * -pysqlite_enable_shared_cache(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"do_enable", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "enable_shared_cache", 0}; - PyObject *argsbuf[1]; - int do_enable; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - do_enable = _PyLong_AsInt(args[0]); - if (do_enable == -1 && PyErr_Occurred()) { - goto exit; - } - return_value = pysqlite_enable_shared_cache_impl(module, do_enable); - -exit: - return return_value; -} - PyDoc_STRVAR(pysqlite_register_adapter__doc__, "register_adapter($module, type, adapter, /)\n" "--\n" @@ -144,9 +133,6 @@ pysqlite_register_converter(PyObject *module, PyObject *const *args, Py_ssize_t _PyArg_BadArgument("register_converter", "argument 1", "str", args[0]); goto exit; } - if (PyUnicode_READY(args[0]) == -1) { - goto exit; - } orig_name = args[0]; callable = args[1]; return_value = pysqlite_register_converter_impl(module, orig_name, callable); @@ -173,7 +159,7 @@ pysqlite_enable_callback_trace(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int enable; - enable = _PyLong_AsInt(arg); + enable = PyLong_AsInt(arg); if (enable == -1 && PyErr_Occurred()) { goto exit; } @@ -222,4 +208,4 @@ pysqlite_adapt(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=ecaf4e0a239c2685 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=457ab0fdbb9e1880 input=a9049054013a1b77]*/ diff --git a/_sqlite/clinic/row.c.h b/_sqlite/clinic/row.c.h index c936ef7..e8d1dbf 100644 --- a/_sqlite/clinic/row.c.h +++ b/_sqlite/clinic/row.c.h @@ -2,6 +2,8 @@ preserve [clinic start generated code]*/ +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + static PyObject * pysqlite_row_new_impl(PyTypeObject *type, pysqlite_Cursor *cursor, PyObject *data); @@ -10,11 +12,11 @@ static PyObject * pysqlite_row_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; + PyTypeObject *base_tp = clinic_state()->RowType; pysqlite_Cursor *cursor; PyObject *data; - if ((type == clinic_state()->RowType || - type->tp_init == clinic_state()->RowType->tp_init) && + if ((type == base_tp || type->tp_init == base_tp->tp_init) && !_PyArg_NoKeywords("Row", kwargs)) { goto exit; } @@ -54,4 +56,4 @@ pysqlite_row_keys(pysqlite_Row *self, PyObject *Py_UNUSED(ignored)) { return pysqlite_row_keys_impl(self); } -/*[clinic end generated code: output=9d54919dbb4ba5f1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=788bf817acc02b8e input=a9049054013a1b77]*/ diff --git a/_sqlite/connection.c b/_sqlite/connection.c index e3650de..bf5307f 100644 --- a/_sqlite/connection.c +++ b/_sqlite/connection.c @@ -21,8 +21,12 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "module.h" -#include "structmember.h" // PyMemberDef + #include "connection.h" #include "statement.h" #include "cursor.h" @@ -30,9 +34,13 @@ #include "prepare_protocol.h" #include "util.h" -#if SQLITE_VERSION_NUMBER >= 3014000 -#define HAVE_TRACE_V2 -#endif +#include "pycore_import.h" // _PyImport_GetModuleAttrString() +#include "pycore_modsupport.h" // _PyArg_NoKeywords() +#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() +#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() +#include "pycore_weakref.h" + +#include #if SQLITE_VERSION_NUMBER >= 3025000 #define HAVE_WINDOW_FUNCTIONS @@ -68,16 +76,10 @@ isolation_level_converter(PyObject *str_or_none, const char **result) *result = NULL; } else if (PyUnicode_Check(str_or_none)) { - Py_ssize_t sz; - const char *str = PyUnicode_AsUTF8AndSize(str_or_none, &sz); + const char *str = _PyUnicode_AsUTF8NoNUL(str_or_none); if (str == NULL) { return 0; } - if (strlen(str) != (size_t)sz) { - PyErr_SetString(PyExc_ValueError, "embedded null character"); - return 0; - } - const char *level = get_isolation_level(str); if (level == NULL) { return 0; @@ -92,6 +94,30 @@ isolation_level_converter(PyObject *str_or_none, const char **result) return 1; } +static int +autocommit_converter(PyObject *val, enum autocommit_mode *result) +{ + if (Py_IsTrue(val)) { + *result = AUTOCOMMIT_ENABLED; + return 1; + } + if (Py_IsFalse(val)) { + *result = AUTOCOMMIT_DISABLED; + return 1; + } + if (PyLong_Check(val) && + PyLong_AsLong(val) == LEGACY_TRANSACTION_CONTROL) + { + *result = AUTOCOMMIT_LEGACY; + return 1; + } + + PyErr_SetString(PyExc_ValueError, + "autocommit must be True, False, or " + "sqlite3.LEGACY_TRANSACTION_CONTROL"); + return 0; +} + static int sqlite3_int64_converter(PyObject *obj, sqlite3_int64 *result) { @@ -116,11 +142,12 @@ class _sqlite3.Connection "pysqlite_Connection *" "clinic_state()->ConnectionTyp [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=67369db2faf80891]*/ -static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self); -static void free_callback_context(callback_context *ctx); +static int _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self); +static void incref_callback_context(callback_context *ctx); +static void decref_callback_context(callback_context *ctx); static void set_callback_context(callback_context **ctx_pp, callback_context *ctx); -static void connection_close(pysqlite_Connection *self); +static int connection_close(pysqlite_Connection *self); PyObject *_pysqlite_query_execute(pysqlite_Cursor *, int, PyObject *, PyObject *); static PyObject * @@ -146,30 +173,57 @@ new_statement_cache(pysqlite_Connection *self, pysqlite_state *state, return res; } +static inline int +connection_exec_stmt(pysqlite_Connection *self, const char *sql) +{ + int rc; + Py_BEGIN_ALLOW_THREADS + int len = (int)strlen(sql) + 1; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(self->db, sql, len, &stmt, NULL); + if (rc == SQLITE_OK) { + (void)sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + } + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + (void)_pysqlite_seterror(self->state, self->db); + return -1; + } + return 0; +} + /*[python input] class IsolationLevel_converter(CConverter): type = "const char *" converter = "isolation_level_converter" +class Autocommit_converter(CConverter): + type = "enum autocommit_mode" + converter = "autocommit_converter" + class sqlite3_int64_converter(CConverter): type = "sqlite3_int64" converter = "sqlite3_int64_converter" [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=e9bee126e0500e61]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=dff8760fb1eba6a1]*/ -// NB: This needs to be in sync with the sqlite3.connect docstring /*[clinic input] _sqlite3.Connection.__init__ as pysqlite_connection_init database: object + * [from 3.15] timeout: double = 5.0 detect_types: int = 0 isolation_level: IsolationLevel = "" - check_same_thread: bool(accept={int}) = True + check_same_thread: bool = True factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType cached_statements as cache_size: int = 128 uri: bool = False + * + autocommit: Autocommit(c_default='LEGACY_TRANSACTION_CONTROL') = sqlite3.LEGACY_TRANSACTION_CONTROL [clinic start generated code]*/ static int @@ -177,8 +231,9 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, double timeout, int detect_types, const char *isolation_level, int check_same_thread, PyObject *factory, - int cache_size, int uri) -/*[clinic end generated code: output=839eb2fee4293bda input=b8ce63dc6f70a383]*/ + int cache_size, int uri, + enum autocommit_mode autocommit) +/*[clinic end generated code: output=cba057313ea7712f input=219c3dbecbae7d99]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; @@ -190,10 +245,13 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, } if (self->initialized) { + self->initialized = 0; + PyTypeObject *tp = Py_TYPE(self); tp->tp_clear((PyObject *)self); - connection_close(self); - self->initialized = 0; + if (connection_close(self) < 0) { + return -1; + } } // Create and configure SQLite database object. @@ -245,6 +303,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->state = state; self->detect_types = detect_types; self->isolation_level = isolation_level; + self->autocommit = autocommit; self->check_same_thread = check_same_thread; self->thread_ident = PyThread_get_thread_ident(); self->statement_cache = statement_cache; @@ -274,6 +333,12 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, } self->initialized = 1; + + if (autocommit == AUTOCOMMIT_DISABLED) { + if (connection_exec_stmt(self, "BEGIN") < 0) { + return -1; + } + } return 0; error: @@ -284,6 +349,34 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, return -1; } +/*[clinic input] +# Create a new destination 'connect' for the docstring and methoddef only. +# This makes it possible to keep the signatures for Connection.__init__ and +# sqlite3.connect() synchronised. +output push +destination connect new file '{dirname}/clinic/_sqlite3.connect.c.h' + +# Only output the docstring and the PyMethodDef entry. +output everything suppress +output docstring_definition connect +output methoddef_define connect + +# Define the sqlite3.connect function by cloning Connection.__init__. +_sqlite3.connect as pysqlite_connect = _sqlite3.Connection.__init__ + +Open a connection to the SQLite database file 'database'. + +You can use ":memory:" to open a database connection to a database that +resides in RAM instead of on disk. +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=92260edff95d1720]*/ + +/*[clinic input] +# Restore normal Argument Clinic operation for the rest of this file. +output pop +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b899ba9273edcce7]*/ + #define VISIT_CALLBACK_CONTEXT(ctx) \ do { \ if (ctx) { \ @@ -340,31 +433,93 @@ free_callback_contexts(pysqlite_Connection *self) } static void -connection_close(pysqlite_Connection *self) +remove_callbacks(sqlite3 *db) { - if (self->db) { - free_callback_contexts(self); + /* None of these APIs can fail, as long as they are given a valid + * database pointer. */ + assert(db != NULL); + int rc = sqlite3_trace_v2(db, SQLITE_TRACE_STMT, 0, 0); + assert(rc == SQLITE_OK), (void)rc; - sqlite3 *db = self->db; - self->db = NULL; + sqlite3_progress_handler(db, 0, 0, (void *)0); - Py_BEGIN_ALLOW_THREADS - int rc = sqlite3_close_v2(db); - assert(rc == SQLITE_OK), (void)rc; - Py_END_ALLOW_THREADS + rc = sqlite3_set_authorizer(db, NULL, NULL); + assert(rc == SQLITE_OK), (void)rc; +} + +static int +connection_close(pysqlite_Connection *self) +{ + if (self->db == NULL) { + return 0; } + + int rc = 0; + if (self->autocommit == AUTOCOMMIT_DISABLED && + !sqlite3_get_autocommit(self->db)) + { + if (connection_exec_stmt(self, "ROLLBACK") < 0) { + rc = -1; + } + } + + sqlite3 *db = self->db; + self->db = NULL; + + Py_BEGIN_ALLOW_THREADS + /* The v2 close call always returns SQLITE_OK if given a valid database + * pointer (which we do), so we can safely ignore the return value */ + (void)sqlite3_close_v2(db); + Py_END_ALLOW_THREADS + + free_callback_contexts(self); + return rc; } static void -connection_dealloc(pysqlite_Connection *self) +connection_finalize(PyObject *self) { - PyTypeObject *tp = Py_TYPE(self); - PyObject_GC_UnTrack(self); - tp->tp_clear((PyObject *)self); + pysqlite_Connection *con = (pysqlite_Connection *)self; + PyObject *exc = PyErr_GetRaisedException(); + + /* If close is implicitly called as a result of interpreter + * tear-down, we must not call back into Python. */ + PyInterpreterState *interp = PyInterpreterState_Get(); + int teardown = _Py_IsInterpreterFinalizing(interp); + if (teardown && con->db) { + remove_callbacks(con->db); + } /* Clean up if user has not called .close() explicitly. */ - connection_close(self); + if (con->db) { + if (PyErr_ResourceWarning(self, 1, "unclosed database in %R", self)) { + /* Spurious errors can appear at shutdown */ + if (PyErr_ExceptionMatches(PyExc_Warning)) { + PyErr_WriteUnraisable(self); + } + } + } + if (connection_close(con) < 0) { + if (teardown) { + PyErr_Clear(); + } + else { + PyErr_WriteUnraisable((PyObject *)self); + } + } + PyErr_SetRaisedException(exc); +} + +static void +connection_dealloc(PyObject *self) +{ + if (PyObject_CallFinalizerFromDealloc(self) < 0) { + return; + } + PyTypeObject *tp = Py_TYPE(self); + PyObject_GC_UnTrack(self); + tp->tp_clear(self); tp->tp_free(self); Py_DECREF(tp); } @@ -402,7 +557,10 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) return NULL; } - _pysqlite_drop_unused_cursor_references(self); + if (_pysqlite_drop_unused_cursor_references(self) < 0) { + Py_DECREF(cursor); + return NULL; + } if (cursor && self->row_factory != Py_None) { Py_INCREF(self->row_factory); @@ -412,6 +570,47 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) return cursor; } +static PyObject * +connection_get_row_factory(PyObject *op, void *closure) +{ + pysqlite_Connection *self = (pysqlite_Connection *)op; + return Py_NewRef(self->row_factory); +} + +static int +connection_set_row_factory(PyObject *op, PyObject *value, void *closure) +{ + pysqlite_Connection *self = (pysqlite_Connection *)op; + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, + "cannot delete row_factory attribute"); + return -1; + } + Py_XSETREF(self->row_factory, Py_NewRef(value)); + return 0; +} + +static PyObject * +connection_get_text_factory(PyObject *op, void *closure) +{ + pysqlite_Connection *self = (pysqlite_Connection *)op; + return Py_NewRef(self->text_factory); +} + +static int +connection_set_text_factory(PyObject *op, PyObject *value, void *closure) +{ + pysqlite_Connection *self = (pysqlite_Connection *)op; + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, + "cannot delete text_factory attribute"); + return -1; + } + Py_XSETREF(self->text_factory, Py_NewRef(value)); + return 0; +} + + /*[clinic input] _sqlite3.Connection.blobopen as blobopen @@ -419,11 +618,11 @@ _sqlite3.Connection.blobopen as blobopen Table name. column as col: str Column name. - row: sqlite3_int64 - Row index. + rowid as row: sqlite3_int64 + Row id. / * - readonly: bool(accept={int}) = False + readonly: bool = False Open the BLOB without write permissions. name: str = "main" Database name. @@ -434,7 +633,7 @@ Open and return a BLOB object. static PyObject * blobopen_impl(pysqlite_Connection *self, const char *table, const char *col, sqlite3_int64 row, int readonly, const char *name) -/*[clinic end generated code: output=6a02d43efb885d1c input=4180b11a0591d80d]*/ +/*[clinic end generated code: output=6a02d43efb885d1c input=cc3d4b47dac08401]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -512,7 +711,9 @@ pysqlite_connection_close_impl(pysqlite_Connection *self) pysqlite_close_all_blobs(self); Py_CLEAR(self->statement_cache); - connection_close(self); + if (connection_close(self) < 0) { + return NULL; + } Py_RETURN_NONE; } @@ -556,24 +757,21 @@ pysqlite_connection_commit_impl(pysqlite_Connection *self) return NULL; } - if (!sqlite3_get_autocommit(self->db)) { - int rc; - - Py_BEGIN_ALLOW_THREADS - sqlite3_stmt *statement; - rc = sqlite3_prepare_v2(self->db, "COMMIT", 7, &statement, NULL); - if (rc == SQLITE_OK) { - (void)sqlite3_step(statement); - rc = sqlite3_finalize(statement); + if (self->autocommit == AUTOCOMMIT_LEGACY) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "COMMIT") < 0) { + return NULL; + } } - Py_END_ALLOW_THREADS - - if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (connection_exec_stmt(self, "COMMIT") < 0) { + return NULL; + } + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } } - Py_RETURN_NONE; } @@ -593,25 +791,21 @@ pysqlite_connection_rollback_impl(pysqlite_Connection *self) return NULL; } - if (!sqlite3_get_autocommit(self->db)) { - int rc; - - Py_BEGIN_ALLOW_THREADS - sqlite3_stmt *statement; - rc = sqlite3_prepare_v2(self->db, "ROLLBACK", 9, &statement, NULL); - if (rc == SQLITE_OK) { - (void)sqlite3_step(statement); - rc = sqlite3_finalize(statement); + if (self->autocommit == AUTOCOMMIT_LEGACY) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "ROLLBACK") < 0) { + return NULL; + } } - Py_END_ALLOW_THREADS - - if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (connection_exec_stmt(self, "ROLLBACK") < 0) { + return NULL; + } + if (connection_exec_stmt(self, "BEGIN") < 0) { return NULL; } - } - Py_RETURN_NONE; } @@ -778,8 +972,9 @@ func_callback(sqlite3_context *context, int argc, sqlite3_value **argv) args = _pysqlite_build_py_params(context, argc, argv); if (args) { callback_context *ctx = (callback_context *)sqlite3_user_data(context); - assert(ctx != NULL); + incref_callback_context(ctx); py_retval = PyObject_CallObject(ctx->callable, args); + decref_callback_context(ctx); Py_DECREF(args); } @@ -806,9 +1001,14 @@ step_callback(sqlite3_context *context, int argc, sqlite3_value **params) PyObject* stepmethod = NULL; callback_context *ctx = (callback_context *)sqlite3_user_data(context); - assert(ctx != NULL); + incref_callback_context(ctx); aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, sizeof(PyObject*)); + if (aggregate_instance == NULL) { + (void)PyErr_NoMemory(); + set_sqlite_error(context, "unable to allocate SQLite aggregate context"); + goto error; + } if (*aggregate_instance == NULL) { *aggregate_instance = PyObject_CallNoArgs(ctx->callable); if (!*aggregate_instance) { @@ -839,6 +1039,7 @@ step_callback(sqlite3_context *context, int argc, sqlite3_value **params) } error: + decref_callback_context(ctx); Py_XDECREF(stepmethod); Py_XDECREF(function_result); @@ -853,7 +1054,6 @@ final_callback(sqlite3_context *context) PyObject* function_result; PyObject** aggregate_instance; int ok; - PyObject *exception, *value, *tb; aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, 0); if (aggregate_instance == NULL) { @@ -868,12 +1068,13 @@ final_callback(sqlite3_context *context) } // Keep the exception (if any) of the last call to step, value, or inverse - PyErr_Fetch(&exception, &value, &tb); + PyObject *exc = PyErr_GetRaisedException(); callback_context *ctx = (callback_context *)sqlite3_user_data(context); - assert(ctx != NULL); + incref_callback_context(ctx); function_result = PyObject_CallMethodNoArgs(*aggregate_instance, ctx->state->str_finalize); + decref_callback_context(ctx); Py_DECREF(*aggregate_instance); ok = 0; @@ -883,7 +1084,7 @@ final_callback(sqlite3_context *context) } if (!ok) { int attr_err = PyErr_ExceptionMatches(PyExc_AttributeError); - _PyErr_ChainExceptions(exception, value, tb); + _PyErr_ChainExceptions1(exc); /* Note: contrary to the step, value, and inverse callbacks, SQLite * does _not_, as of SQLite 3.38.0, propagate errors to sqlite3_step() @@ -894,42 +1095,43 @@ final_callback(sqlite3_context *context) : "user-defined aggregate's 'finalize' method raised error"); } else { - PyErr_Restore(exception, value, tb); + PyErr_SetRaisedException(exc); } error: PyGILState_Release(threadstate); } -static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self) +static int +_pysqlite_drop_unused_cursor_references(pysqlite_Connection* self) { - PyObject* new_list; - PyObject* weakref; - int i; - /* we only need to do this once in a while */ if (self->created_cursors++ < 200) { - return; + return 0; } self->created_cursors = 0; - new_list = PyList_New(0); + PyObject* new_list = PyList_New(0); if (!new_list) { - return; + return -1; } - for (i = 0; i < PyList_Size(self->cursors); i++) { - weakref = PyList_GetItem(self->cursors, i); - if (PyWeakref_GetObject(weakref) != Py_None) { - if (PyList_Append(new_list, weakref) != 0) { - Py_DECREF(new_list); - return; - } + assert(PyList_CheckExact(self->cursors)); + Py_ssize_t imax = PyList_GET_SIZE(self->cursors); + for (Py_ssize_t i = 0; i < imax; i++) { + PyObject* weakref = PyList_GET_ITEM(self->cursors, i); + if (_PyWeakref_IsDead(weakref)) { + continue; + } + if (PyList_Append(new_list, weakref) != 0) { + Py_DECREF(new_list); + return -1; } } Py_SETREF(self->cursors, new_list); + return 0; } /* Allocate a UDF/callback context structure. In order to ensure that the state @@ -942,12 +1144,16 @@ static callback_context * create_callback_context(PyTypeObject *cls, PyObject *callable) { callback_context *ctx = PyMem_Malloc(sizeof(callback_context)); - if (ctx != NULL) { - PyObject *module = PyType_GetModule(cls); - ctx->callable = Py_NewRef(callable); - ctx->module = Py_NewRef(module); - ctx->state = pysqlite_get_state(module); + if (ctx == NULL) { + PyErr_NoMemory(); + return NULL; } + + PyObject *module = PyType_GetModule(cls); + ctx->refcount = 1; + ctx->callable = Py_NewRef(callable); + ctx->module = Py_NewRef(module); + ctx->state = pysqlite_get_state(module); return ctx; } @@ -955,11 +1161,33 @@ static void free_callback_context(callback_context *ctx) { assert(ctx != NULL); + assert(ctx->refcount == 0); Py_XDECREF(ctx->callable); Py_XDECREF(ctx->module); PyMem_Free(ctx); } +static inline void +incref_callback_context(callback_context *ctx) +{ + assert(PyGILState_Check()); + assert(ctx != NULL); + assert(ctx->refcount > 0); + ctx->refcount++; +} + +static inline void +decref_callback_context(callback_context *ctx) +{ + assert(PyGILState_Check()); + assert(ctx != NULL); + assert(ctx->refcount > 0); + ctx->refcount--; + if (ctx->refcount == 0) { + free_callback_context(ctx); + } +} + static void set_callback_context(callback_context **ctx_pp, callback_context *ctx) { @@ -967,7 +1195,7 @@ set_callback_context(callback_context **ctx_pp, callback_context *ctx) callback_context *tmp = *ctx_pp; *ctx_pp = ctx; if (tmp != NULL) { - free_callback_context(tmp); + decref_callback_context(tmp); } } @@ -978,7 +1206,7 @@ destructor_callback(void *ctx) // This function may be called without the GIL held, so we need to // ensure that we destroy 'ctx' with the GIL held. PyGILState_STATE gstate = PyGILState_Ensure(); - free_callback_context((callback_context *)ctx); + decref_callback_context((callback_context *)ctx); PyGILState_Release(gstate); } } @@ -991,6 +1219,7 @@ _sqlite3.Connection.create_function as pysqlite_connection_create_function name: str narg: int func: object + / [from 3.15] * deterministic: bool = False @@ -1002,7 +1231,7 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self, PyTypeObject *cls, const char *name, int narg, PyObject *func, int deterministic) -/*[clinic end generated code: output=8a811529287ad240 input=b3e8e1d8ddaffbef]*/ +/*[clinic end generated code: output=8a811529287ad240 input=c7c313b0ca8b519e]*/ { int rc; int flags = SQLITE_UTF8; @@ -1012,18 +1241,7 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self, } if (deterministic) { -#if SQLITE_VERSION_NUMBER < 3008003 - PyErr_SetString(self->NotSupportedError, - "deterministic=True requires SQLite 3.8.3 or higher"); - return NULL; -#else - if (sqlite3_libversion_number() < 3008003) { - PyErr_SetString(self->NotSupportedError, - "deterministic=True requires SQLite 3.8.3 or higher"); - return NULL; - } flags |= SQLITE_DETERMINISTIC; -#endif } callback_context *ctx = create_callback_context(cls, func); if (ctx == NULL) { @@ -1033,7 +1251,7 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self, func_callback, NULL, NULL, - &destructor_callback); // will decref func + &destructor_callback); // will free 'ctx' if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ @@ -1057,7 +1275,7 @@ inverse_callback(sqlite3_context *context, int argc, sqlite3_value **params) PyGILState_STATE gilstate = PyGILState_Ensure(); callback_context *ctx = (callback_context *)sqlite3_user_data(context); - assert(ctx != NULL); + incref_callback_context(ctx); int size = sizeof(PyObject *); PyObject **cls = (PyObject **)sqlite3_aggregate_context(context, size); @@ -1089,6 +1307,7 @@ inverse_callback(sqlite3_context *context, int argc, sqlite3_value **params) Py_DECREF(res); exit: + decref_callback_context(ctx); Py_XDECREF(method); PyGILState_Release(gilstate); } @@ -1105,7 +1324,7 @@ value_callback(sqlite3_context *context) PyGILState_STATE gilstate = PyGILState_Ensure(); callback_context *ctx = (callback_context *)sqlite3_user_data(context); - assert(ctx != NULL); + incref_callback_context(ctx); int size = sizeof(PyObject *); PyObject **cls = (PyObject **)sqlite3_aggregate_context(context, size); @@ -1113,6 +1332,8 @@ value_callback(sqlite3_context *context) assert(*cls != NULL); PyObject *res = PyObject_CallMethodNoArgs(*cls, ctx->state->str_value); + decref_callback_context(ctx); + if (res == NULL) { int attr_err = PyErr_ExceptionMatches(PyExc_AttributeError); set_sqlite_error(context, attr_err @@ -1204,6 +1425,7 @@ _sqlite3.Connection.create_aggregate as pysqlite_connection_create_aggregate name: str n_arg: int aggregate_class: object + / [from 3.15] Creates a new aggregate. [clinic start generated code]*/ @@ -1213,7 +1435,7 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self, PyTypeObject *cls, const char *name, int n_arg, PyObject *aggregate_class) -/*[clinic end generated code: output=1b02d0f0aec7ff96 input=68a2a26366d4c686]*/ +/*[clinic end generated code: output=1b02d0f0aec7ff96 input=8087056db6eae1cf]*/ { int rc; @@ -1229,7 +1451,7 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self, 0, &step_callback, &final_callback, - &destructor_callback); // will decref func + &destructor_callback); // will free 'ctx' if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(self->OperationalError, "Error creating aggregate"); @@ -1239,7 +1461,7 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self, } static int -authorizer_callback(void *ctx, int action, const char *arg1, +authorizer_callback(void *ctx_vp, int action, const char *arg1, const char *arg2 , const char *dbname, const char *access_attempt_source) { @@ -1248,8 +1470,9 @@ authorizer_callback(void *ctx, int action, const char *arg1, PyObject *ret; int rc = SQLITE_DENY; - assert(ctx != NULL); - PyObject *callable = ((callback_context *)ctx)->callable; + callback_context *ctx = (callback_context *)ctx_vp; + incref_callback_context(ctx); + PyObject *callable = ctx->callable; ret = PyObject_CallFunction(callable, "issss", action, arg1, arg2, dbname, access_attempt_source); @@ -1259,7 +1482,7 @@ authorizer_callback(void *ctx, int action, const char *arg1, } else { if (PyLong_Check(ret)) { - rc = _PyLong_AsInt(ret); + rc = PyLong_AsInt(ret); if (rc == -1 && PyErr_Occurred()) { print_or_clear_traceback(ctx); rc = SQLITE_DENY; @@ -1271,21 +1494,23 @@ authorizer_callback(void *ctx, int action, const char *arg1, Py_DECREF(ret); } + decref_callback_context(ctx); PyGILState_Release(gilstate); return rc; } static int -progress_callback(void *ctx) +progress_callback(void *ctx_vp) { PyGILState_STATE gilstate = PyGILState_Ensure(); int rc; PyObject *ret; - assert(ctx != NULL); - PyObject *callable = ((callback_context *)ctx)->callable; - ret = PyObject_CallNoArgs(callable); + callback_context *ctx = (callback_context *)ctx_vp; + incref_callback_context(ctx); + + ret = PyObject_CallNoArgs(ctx->callable); if (!ret) { /* abort query if error occurred */ rc = -1; @@ -1298,11 +1523,11 @@ progress_callback(void *ctx) print_or_clear_traceback(ctx); } + decref_callback_context(ctx); PyGILState_Release(gilstate); return rc; } -#ifdef HAVE_TRACE_V2 /* * From https://sqlite.org/c3ref/trace_v2.html: * The integer return value from the callback is currently ignored, though this @@ -1310,26 +1535,20 @@ progress_callback(void *ctx) * to ensure future compatibility. */ static int -trace_callback(unsigned int type, void *ctx, void *stmt, void *sql) -#else -static void -trace_callback(void *ctx, const char *sql) -#endif +trace_callback(unsigned int type, void *ctx_vp, void *stmt, void *sql) { -#ifdef HAVE_TRACE_V2 if (type != SQLITE_TRACE_STMT) { return 0; } -#endif PyGILState_STATE gilstate = PyGILState_Ensure(); - assert(ctx != NULL); - pysqlite_state *state = ((callback_context *)ctx)->state; + callback_context *ctx = (callback_context *)ctx_vp; + incref_callback_context(ctx); + pysqlite_state *state = ctx->state; assert(state != NULL); PyObject *py_statement = NULL; -#ifdef HAVE_TRACE_V2 const char *expanded_sql = sqlite3_expanded_sql((sqlite3_stmt *)stmt); if (expanded_sql == NULL) { sqlite3 *db = sqlite3_db_handle((sqlite3_stmt *)stmt); @@ -1340,7 +1559,7 @@ trace_callback(void *ctx, const char *sql) PyErr_SetString(state->DataError, "Expanded SQL string exceeds the maximum string length"); - print_or_clear_traceback((callback_context *)ctx); + print_or_clear_traceback(ctx); // Fall back to unexpanded sql py_statement = PyUnicode_FromString((const char *)sql); @@ -1349,47 +1568,36 @@ trace_callback(void *ctx, const char *sql) py_statement = PyUnicode_FromString(expanded_sql); sqlite3_free((void *)expanded_sql); } -#else - if (sql == NULL) { - PyErr_SetString(state->DataError, - "Expanded SQL string exceeds the maximum string length"); - print_or_clear_traceback((callback_context *)ctx); - goto exit; - } - py_statement = PyUnicode_FromString(sql); -#endif if (py_statement) { - PyObject *callable = ((callback_context *)ctx)->callable; - PyObject *ret = PyObject_CallOneArg(callable, py_statement); + PyObject *ret = PyObject_CallOneArg(ctx->callable, py_statement); Py_DECREF(py_statement); Py_XDECREF(ret); } if (PyErr_Occurred()) { - print_or_clear_traceback((callback_context *)ctx); + print_or_clear_traceback(ctx); } exit: + decref_callback_context(ctx); PyGILState_Release(gilstate); -#ifdef HAVE_TRACE_V2 return 0; -#endif } /*[clinic input] _sqlite3.Connection.set_authorizer as pysqlite_connection_set_authorizer cls: defining_class - / authorizer_callback as callable: object + / [from 3.15] -Sets authorizer callback. +Set authorizer callback. [clinic start generated code]*/ static PyObject * pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable) -/*[clinic end generated code: output=75fa60114fc971c3 input=605d32ba92dd3eca]*/ +/*[clinic end generated code: output=75fa60114fc971c3 input=a52bd4937c588752]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1421,18 +1629,26 @@ pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, _sqlite3.Connection.set_progress_handler as pysqlite_connection_set_progress_handler cls: defining_class - / progress_handler as callable: object + A callable that takes no arguments. + If the callable returns non-zero, the current query is + terminated, and an exception is raised. + / [from 3.15] n: int + The number of SQLite virtual machine instructions that are + executed between invocations of 'progress_handler'. + +Set progress handler callback. -Sets progress handler callback. +If 'progress_handler' is None or 'n' is 0, the progress handler is +disabled. [clinic start generated code]*/ static PyObject * pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable, int n) -/*[clinic end generated code: output=0739957fd8034a50 input=f7c1837984bd86db]*/ +/*[clinic end generated code: output=0739957fd8034a50 input=d46887797f0c0ecf]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1458,17 +1674,17 @@ pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, _sqlite3.Connection.set_trace_callback as pysqlite_connection_set_trace_callback cls: defining_class - / trace_callback as callable: object + / [from 3.15] -Sets a trace callback called for each SQL statement (passed as unicode). +Set a trace callback called for each SQL statement (passed as unicode). [clinic start generated code]*/ static PyObject * pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable) -/*[clinic end generated code: output=d91048c03bfcee05 input=351a94210c5f81bb]*/ +/*[clinic end generated code: output=d91048c03bfcee05 input=d705d592ec03cf28]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1482,11 +1698,7 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, * - https://sqlite.org/c3ref/c_trace.html * - https://sqlite.org/c3ref/trace_v2.html */ -#ifdef HAVE_TRACE_V2 sqlite3_trace_v2(self->db, SQLITE_TRACE_STMT, 0, 0); -#else - sqlite3_trace(self->db, 0, (void*)0); -#endif set_callback_context(&self->trace_ctx, NULL); } else { @@ -1494,11 +1706,7 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, if (ctx == NULL) { return NULL; } -#ifdef HAVE_TRACE_V2 sqlite3_trace_v2(self->db, SQLITE_TRACE_STMT, trace_callback, ctx); -#else - sqlite3_trace(self->db, trace_callback, ctx); -#endif set_callback_context(&self->trace_ctx, ctx); } @@ -1509,7 +1717,7 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, /*[clinic input] _sqlite3.Connection.enable_load_extension as pysqlite_connection_enable_load_extension - enable as onoff: bool(accept={int}) + enable as onoff: bool / Enable dynamic loading of SQLite extension modules. @@ -1518,7 +1726,7 @@ Enable dynamic loading of SQLite extension modules. static PyObject * pysqlite_connection_enable_load_extension_impl(pysqlite_Connection *self, int onoff) -/*[clinic end generated code: output=9cac37190d388baf input=5f00e93f7a9d3540]*/ +/*[clinic end generated code: output=9cac37190d388baf input=2a1e87931486380f]*/ { int rc; @@ -1547,14 +1755,17 @@ _sqlite3.Connection.load_extension as pysqlite_connection_load_extension name as extension_name: str / + * + entrypoint: str(accept={str, NoneType}) = None Load SQLite extension module. [clinic start generated code]*/ static PyObject * pysqlite_connection_load_extension_impl(pysqlite_Connection *self, - const char *extension_name) -/*[clinic end generated code: output=47eb1d7312bc97a7 input=edd507389d89d621]*/ + const char *extension_name, + const char *entrypoint) +/*[clinic end generated code: output=7e61a7add9de0286 input=c36b14ea702e04f5]*/ { int rc; char* errmsg; @@ -1567,7 +1778,7 @@ pysqlite_connection_load_extension_impl(pysqlite_Connection *self, return NULL; } - rc = sqlite3_load_extension(self->db, extension_name, 0, &errmsg); + rc = sqlite3_load_extension(self->db, extension_name, entrypoint, &errmsg); if (rc != 0) { PyErr_SetString(self->OperationalError, errmsg); return NULL; @@ -1607,9 +1818,8 @@ static PyObject* pysqlite_connection_get_total_changes(pysqlite_Connection* self { if (!pysqlite_check_connection(self)) { return NULL; - } else { - return Py_BuildValue("i", sqlite3_total_changes(self->db)); } + return PyLong_FromLong(sqlite3_total_changes(self->db)); } static PyObject* pysqlite_connection_get_in_transaction(pysqlite_Connection* self, void* unused) @@ -1784,6 +1994,8 @@ collation_callback(void *context, int text1_length, const void *text1_data, PyObject* retval = NULL; long longval; int result = 0; + callback_context *ctx = (callback_context *)context; + incref_callback_context(ctx); /* This callback may be executed multiple times per sqlite3_step(). Bail if * the previous call failed */ @@ -1800,8 +2012,6 @@ collation_callback(void *context, int text1_length, const void *text1_data, goto finally; } - callback_context *ctx = (callback_context *)context; - assert(ctx != NULL); PyObject *args[] = { NULL, string1, string2 }; // Borrowed refs. size_t nargsf = 2 | PY_VECTORCALL_ARGUMENTS_OFFSET; retval = PyObject_Vectorcall(ctx->callable, args + 1, nargsf, NULL); @@ -1823,6 +2033,7 @@ collation_callback(void *context, int text1_length, const void *text1_data, } finally: + decref_callback_context(ctx); Py_XDECREF(string1); Py_XDECREF(string2); Py_XDECREF(retval); @@ -1861,50 +2072,40 @@ pysqlite_connection_interrupt_impl(pysqlite_Connection *self) /*[clinic input] _sqlite3.Connection.iterdump as pysqlite_connection_iterdump + * + filter: object = None + An optional LIKE pattern for database objects to dump + Returns iterator to the dump of the database in an SQL text format. [clinic start generated code]*/ static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self) -/*[clinic end generated code: output=586997aaf9808768 input=1911ca756066da89]*/ +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter) +/*[clinic end generated code: output=fd81069c4bdeb6b0 input=4ae6d9a898f108df]*/ { - PyObject* retval = NULL; - PyObject* module = NULL; - PyObject* module_dict; - PyObject* pyfn_iterdump; - if (!pysqlite_check_connection(self)) { - goto finally; - } - - module = PyImport_ImportModule(MODULE_NAME ".dump"); - if (!module) { - goto finally; - } - - module_dict = PyModule_GetDict(module); - if (!module_dict) { - goto finally; + return NULL; } - PyObject *meth = PyUnicode_InternFromString("_iterdump"); - if (meth == NULL) { - goto finally; - } - pyfn_iterdump = PyDict_GetItemWithError(module_dict, meth); - Py_DECREF(meth); - if (!pyfn_iterdump) { + PyObject *iterdump = _PyImport_GetModuleAttrString(MODULE_NAME ".dump", "_iterdump"); + if (!iterdump) { if (!PyErr_Occurred()) { PyErr_SetString(self->OperationalError, "Failed to obtain _iterdump() reference"); } - goto finally; + return NULL; } - - retval = PyObject_CallOneArg(pyfn_iterdump, (PyObject *)self); - -finally: - Py_XDECREF(module); + PyObject *args[3] = {NULL, (PyObject *)self, filter}; + PyObject *kwnames = Py_BuildValue("(s)", "filter"); + if (!kwnames) { + Py_DECREF(iterdump); + return NULL; + } + Py_ssize_t nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; + PyObject *retval = PyObject_Vectorcall(iterdump, args + 1, nargsf, kwnames); + Py_DECREF(iterdump); + Py_DECREF(kwnames); return retval; } @@ -1946,15 +2147,6 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self, return NULL; } -#if SQLITE_VERSION_NUMBER < 3008008 - /* Since 3.8.8 this is already done, per commit - https://www.sqlite.org/src/info/169b5505498c0a7e */ - if (!sqlite3_get_autocommit(target->db)) { - PyErr_SetString(self->OperationalError, "target is in transaction"); - return NULL; - } -#endif - if (progress != Py_None && !PyCallable_Check(progress)) { PyErr_SetString(PyExc_TypeError, "progress argument must be a callable"); return NULL; @@ -2065,7 +2257,7 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self, * the context before returning. */ if (callable != Py_None) { - free_callback_context(ctx); + decref_callback_context(ctx); } _pysqlite_seterror(self->state, self->db); return NULL; @@ -2084,15 +2276,15 @@ _sqlite3.Connection.serialize as serialize Serialize a database into a byte string. -For an ordinary on-disk database file, the serialization is just a copy of the -disk file. For an in-memory database or a "temp" database, the serialization is -the same sequence of bytes which would be written to disk if that database -were backed up to disk. +For an ordinary on-disk database file, the serialization is just +a copy of the disk file. For an in-memory database or a "temp" +database, the serialization is the same sequence of bytes which +would be written to disk if that database were backed up to disk. [clinic start generated code]*/ static PyObject * serialize_impl(pysqlite_Connection *self, const char *name) -/*[clinic end generated code: output=97342b0e55239dd3 input=d2eb5194a65abe2b]*/ +/*[clinic end generated code: output=97342b0e55239dd3 input=7e48654e8e082fa8]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -2137,18 +2329,19 @@ _sqlite3.Connection.deserialize as deserialize Load a serialized database. -The deserialize interface causes the database connection to disconnect from the -target database, and then reopen it as an in-memory database based on the given -serialized data. +The deserialize interface causes the database connection to +disconnect from the target database, and then reopen it as +an in-memory database based on the given serialized data. -The deserialize interface will fail with SQLITE_BUSY if the database is -currently in a read transaction or is involved in a backup operation. +The deserialize interface will fail with SQLITE_BUSY if the database +is currently in a read transaction or is involved in a backup +operation. [clinic start generated code]*/ static PyObject * deserialize_impl(pysqlite_Connection *self, Py_buffer *data, const char *name) -/*[clinic end generated code: output=e394c798b98bad89 input=1be4ca1faacf28f2]*/ +/*[clinic end generated code: output=e394c798b98bad89 input=5d20e028d98c0686]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -2221,13 +2414,14 @@ _sqlite3.Connection.__exit__ as pysqlite_connection_exit Called when the connection is used as a context manager. -If there was any exception, a rollback takes place; otherwise we commit. +If there was any exception, a rollback takes place; otherwise we +commit. [clinic start generated code]*/ static PyObject * pysqlite_connection_exit_impl(pysqlite_Connection *self, PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb) -/*[clinic end generated code: output=0705200e9321202a input=bd66f1532c9c54a7]*/ +/*[clinic end generated code: output=0705200e9321202a input=8fdb0392ee6f3466]*/ { int commit = 0; PyObject* result; @@ -2244,15 +2438,14 @@ pysqlite_connection_exit_impl(pysqlite_Connection *self, PyObject *exc_type, if (commit) { /* Commit failed; try to rollback in order to unlock the database. * If rollback also fails, chain the exceptions. */ - PyObject *exc, *val, *tb; - PyErr_Fetch(&exc, &val, &tb); + PyObject *exc = PyErr_GetRaisedException(); result = pysqlite_connection_rollback_impl(self); if (result == NULL) { - _PyErr_ChainExceptions(exc, val, tb); + _PyErr_ChainExceptions1(exc); } else { Py_DECREF(result); - PyErr_Restore(exc, val, tb); + PyErr_SetRaisedException(exc); } } return NULL; @@ -2268,20 +2461,20 @@ _sqlite3.Connection.setlimit as setlimit category: int The limit category to be set. limit: int - The new limit. If the new limit is a negative number, the limit is - unchanged. + The new limit. If the new limit is a negative number, the limit + is unchanged. / Set connection run-time limits. -Attempts to increase a limit above its hard upper bound are silently truncated -to the hard upper bound. Regardless of whether or not the limit was changed, -the prior value of the limit is returned. +Attempts to increase a limit above its hard upper bound are silently +truncated to the hard upper bound. Regardless of whether or not the +limit was changed, the prior value of the limit is returned. [clinic start generated code]*/ static PyObject * setlimit_impl(pysqlite_Connection *self, int category, int limit) -/*[clinic end generated code: output=0d208213f8d68ccd input=9bd469537e195635]*/ +/*[clinic end generated code: output=0d208213f8d68ccd input=5c2e430091206677]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -2312,6 +2505,163 @@ getlimit_impl(pysqlite_Connection *self, int category) return setlimit_impl(self, category, -1); } +static inline bool +is_int_config(const int op) +{ + switch (op) { + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: +#if SQLITE_VERSION_NUMBER >= 3016000 + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: +#endif +#if SQLITE_VERSION_NUMBER >= 3020000 + case SQLITE_DBCONFIG_ENABLE_QPSG: +#endif +#if SQLITE_VERSION_NUMBER >= 3022000 + case SQLITE_DBCONFIG_TRIGGER_EQP: +#endif +#if SQLITE_VERSION_NUMBER >= 3024000 + case SQLITE_DBCONFIG_RESET_DATABASE: +#endif +#if SQLITE_VERSION_NUMBER >= 3026000 + case SQLITE_DBCONFIG_DEFENSIVE: +#endif +#if SQLITE_VERSION_NUMBER >= 3028000 + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: +#endif +#if SQLITE_VERSION_NUMBER >= 3029000 + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: +#endif +#if SQLITE_VERSION_NUMBER >= 3030000 + case SQLITE_DBCONFIG_ENABLE_VIEW: +#endif +#if SQLITE_VERSION_NUMBER >= 3031000 + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: +#endif + return true; + default: + return false; + } +} + +/*[clinic input] +_sqlite3.Connection.setconfig as setconfig + + op: int + The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes. + enable: bool = True + / + +Set a boolean connection configuration option. +[clinic start generated code]*/ + +static PyObject * +setconfig_impl(pysqlite_Connection *self, int op, int enable) +/*[clinic end generated code: output=c60b13e618aff873 input=a10f1539c2d7da6b]*/ +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + if (!is_int_config(op)) { + return PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op); + } + + int actual; + int rc = sqlite3_db_config(self->db, op, enable, &actual); + if (rc != SQLITE_OK) { + (void)_pysqlite_seterror(self->state, self->db); + return NULL; + } + if (enable != actual) { + PyErr_SetString(self->state->OperationalError, "Unable to set config"); + return NULL; + } + Py_RETURN_NONE; +} + +/*[clinic input] +_sqlite3.Connection.getconfig as getconfig -> bool + + op: int + The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes. + / + +Query a boolean connection configuration option. +[clinic start generated code]*/ + +static int +getconfig_impl(pysqlite_Connection *self, int op) +/*[clinic end generated code: output=25ac05044c7b78a3 input=b0526d7e432e3f2f]*/ +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return -1; + } + if (!is_int_config(op)) { + PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op); + return -1; + } + + int current; + int rc = sqlite3_db_config(self->db, op, -1, ¤t); + if (rc != SQLITE_OK) { + (void)_pysqlite_seterror(self->state, self->db); + return -1; + } + return current; +} + +static PyObject * +get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx)) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + if (self->autocommit == AUTOCOMMIT_ENABLED) { + Py_RETURN_TRUE; + } + if (self->autocommit == AUTOCOMMIT_DISABLED) { + Py_RETURN_FALSE; + } + return PyLong_FromLong(LEGACY_TRANSACTION_CONTROL); +} + +static int +set_autocommit(pysqlite_Connection *self, PyObject *val, void *Py_UNUSED(ctx)) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return -1; + } + if (!autocommit_converter(val, &self->autocommit)) { + return -1; + } + if (self->autocommit == AUTOCOMMIT_ENABLED) { + if (!sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "COMMIT") < 0) { + return -1; + } + } + } + else if (self->autocommit == AUTOCOMMIT_DISABLED) { + if (sqlite3_get_autocommit(self->db)) { + if (connection_exec_stmt(self, "BEGIN") < 0) { + return -1; + } + } + } + return 0; +} + +static PyObject * +get_sig(PyObject *self, void *Py_UNUSED(ctx)) +{ + return PyUnicode_FromString("(sql, /)"); +} + static const char connection_doc[] = PyDoc_STR("SQLite database connection object."); @@ -2320,6 +2670,12 @@ static PyGetSetDef connection_getset[] = { {"isolation_level", (getter)pysqlite_connection_get_isolation_level, (setter)pysqlite_connection_set_isolation_level}, {"total_changes", (getter)pysqlite_connection_get_total_changes, (setter)0}, {"in_transaction", (getter)pysqlite_connection_get_in_transaction, (setter)0}, + {"autocommit", (getter)get_autocommit, (setter)set_autocommit}, + {"__text_signature__", get_sig, (setter)0}, + {"row_factory", connection_get_row_factory, + connection_set_row_factory}, + {"text_factory", connection_get_text_factory, + connection_set_text_factory}, {NULL} }; @@ -2350,27 +2706,28 @@ static PyMethodDef connection_methods[] = { DESERIALIZE_METHODDEF CREATE_WINDOW_FUNCTION_METHODDEF BLOBOPEN_METHODDEF + SETCONFIG_METHODDEF + GETCONFIG_METHODDEF {NULL, NULL} }; static struct PyMemberDef connection_members[] = { - {"Warning", T_OBJECT, offsetof(pysqlite_Connection, Warning), READONLY}, - {"Error", T_OBJECT, offsetof(pysqlite_Connection, Error), READONLY}, - {"InterfaceError", T_OBJECT, offsetof(pysqlite_Connection, InterfaceError), READONLY}, - {"DatabaseError", T_OBJECT, offsetof(pysqlite_Connection, DatabaseError), READONLY}, - {"DataError", T_OBJECT, offsetof(pysqlite_Connection, DataError), READONLY}, - {"OperationalError", T_OBJECT, offsetof(pysqlite_Connection, OperationalError), READONLY}, - {"IntegrityError", T_OBJECT, offsetof(pysqlite_Connection, IntegrityError), READONLY}, - {"InternalError", T_OBJECT, offsetof(pysqlite_Connection, InternalError), READONLY}, - {"ProgrammingError", T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), READONLY}, - {"NotSupportedError", T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), READONLY}, - {"row_factory", T_OBJECT, offsetof(pysqlite_Connection, row_factory)}, - {"text_factory", T_OBJECT, offsetof(pysqlite_Connection, text_factory)}, + {"Warning", _Py_T_OBJECT, offsetof(pysqlite_Connection, Warning), Py_READONLY}, + {"Error", _Py_T_OBJECT, offsetof(pysqlite_Connection, Error), Py_READONLY}, + {"InterfaceError", _Py_T_OBJECT, offsetof(pysqlite_Connection, InterfaceError), Py_READONLY}, + {"DatabaseError", _Py_T_OBJECT, offsetof(pysqlite_Connection, DatabaseError), Py_READONLY}, + {"DataError", _Py_T_OBJECT, offsetof(pysqlite_Connection, DataError), Py_READONLY}, + {"OperationalError", _Py_T_OBJECT, offsetof(pysqlite_Connection, OperationalError), Py_READONLY}, + {"IntegrityError", _Py_T_OBJECT, offsetof(pysqlite_Connection, IntegrityError), Py_READONLY}, + {"InternalError", _Py_T_OBJECT, offsetof(pysqlite_Connection, InternalError), Py_READONLY}, + {"ProgrammingError", _Py_T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), Py_READONLY}, + {"NotSupportedError", _Py_T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), Py_READONLY}, {NULL} }; static PyType_Slot connection_slots[] = { + {Py_tp_finalize, connection_finalize}, {Py_tp_dealloc, connection_dealloc}, {Py_tp_doc, (void *)connection_doc}, {Py_tp_methods, connection_methods}, diff --git a/_sqlite/connection.h b/_sqlite/connection.h index 629fe3d..703396a 100644 --- a/_sqlite/connection.h +++ b/_sqlite/connection.h @@ -23,7 +23,6 @@ #ifndef PYSQLITE_CONNECTION_H #define PYSQLITE_CONNECTION_H -#define PY_SSIZE_T_CLEAN #include "Python.h" #include "pythread.h" #include "structmember.h" @@ -37,8 +36,15 @@ typedef struct _callback_context PyObject *callable; PyObject *module; pysqlite_state *state; + Py_ssize_t refcount; } callback_context; +enum autocommit_mode { + AUTOCOMMIT_LEGACY = LEGACY_TRANSACTION_CONTROL, + AUTOCOMMIT_ENABLED = 1, + AUTOCOMMIT_DISABLED = 0, +}; + typedef struct { PyObject_HEAD @@ -51,6 +57,7 @@ typedef struct /* NULL for autocommit, otherwise a string with the isolation level */ const char *isolation_level; + enum autocommit_mode autocommit; /* 1 if a check should be performed for each API call if the connection is * used from the same thread it was created in */ diff --git a/_sqlite/cursor.c b/_sqlite/cursor.c index 3f5cfef..cc014c3 100644 --- a/_sqlite/cursor.c +++ b/_sqlite/cursor.c @@ -21,11 +21,18 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "cursor.h" #include "microprotocols.h" #include "module.h" #include "util.h" +#include "pycore_pyerrors.h" // _PyErr_FormatFromCause() +#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() + typedef enum { TYPE_LONG, TYPE_FLOAT, @@ -130,14 +137,10 @@ stmt_reset(pysqlite_Statement *self) { int rc = SQLITE_OK; - if (self->in_use && self->st) { + if (self->st != NULL) { Py_BEGIN_ALLOW_THREADS rc = sqlite3_reset(self->st); Py_END_ALLOW_THREADS - - if (rc == SQLITE_OK) { - self->in_use = 0; - } } return rc; @@ -178,9 +181,7 @@ cursor_dealloc(pysqlite_Cursor *self) { PyTypeObject *tp = Py_TYPE(self); PyObject_GC_UnTrack(self); - if (self->in_weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)self); - } + FT_CLEAR_WEAKREFS((PyObject*)self, self->in_weakreflist); tp->tp_clear((PyObject *)self); tp->tp_free(self); Py_DECREF(tp); @@ -465,6 +466,9 @@ static int check_cursor(pysqlite_Cursor* cur) return 0; } + assert(cur->connection != NULL); + assert(cur->connection->state != NULL); + if (cur->closed) { PyErr_SetString(cur->connection->state->ProgrammingError, "Cannot operate on a closed cursor."); @@ -561,43 +565,40 @@ bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, switch (paramtype) { case TYPE_LONG: { sqlite_int64 value = _pysqlite_long_as_int64(parameter); - if (value == -1 && PyErr_Occurred()) - rc = -1; - else - rc = sqlite3_bind_int64(self->st, pos, value); + rc = (value == -1 && PyErr_Occurred()) + ? SQLITE_ERROR + : sqlite3_bind_int64(self->st, pos, value); break; } case TYPE_FLOAT: { double value = PyFloat_AsDouble(parameter); - if (value == -1 && PyErr_Occurred()) { - rc = -1; - } - else { - rc = sqlite3_bind_double(self->st, pos, value); - } + rc = (value == -1 && PyErr_Occurred()) + ? SQLITE_ERROR + : sqlite3_bind_double(self->st, pos, value); break; } case TYPE_UNICODE: string = PyUnicode_AsUTF8AndSize(parameter, &buflen); - if (string == NULL) - return -1; + if (string == NULL) { + return SQLITE_ERROR; + } if (buflen > INT_MAX) { PyErr_SetString(PyExc_OverflowError, "string longer than INT_MAX bytes"); - return -1; + return SQLITE_ERROR; } rc = sqlite3_bind_text(self->st, pos, string, (int)buflen, SQLITE_TRANSIENT); break; case TYPE_BUFFER: { Py_buffer view; if (PyObject_GetBuffer(parameter, &view, PyBUF_SIMPLE) != 0) { - return -1; + return SQLITE_ERROR; } if (view.len > INT_MAX) { PyErr_SetString(PyExc_OverflowError, "BLOB longer than INT_MAX bytes"); PyBuffer_Release(&view); - return -1; + return SQLITE_ERROR; } rc = sqlite3_bind_blob(self->st, pos, view.buf, (int)view.len, SQLITE_TRANSIENT); PyBuffer_Release(&view); @@ -607,7 +608,7 @@ bind_param(pysqlite_state *state, pysqlite_Statement *self, int pos, PyErr_Format(state->ProgrammingError, "Error binding parameter %d: type '%s' is not supported", pos, Py_TYPE(parameter)->tp_name); - rc = -1; + rc = SQLITE_ERROR; } final: @@ -666,6 +667,19 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, return; } for (i = 0; i < num_params; i++) { + const char *name = sqlite3_bind_parameter_name(self->st, i+1); + if (name != NULL && name[0] != '?') { + int ret = PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "Binding %d ('%s') is a named parameter, but you " + "supplied a sequence which requires nameless (qmark) " + "placeholders. Starting with Python 3.14 an " + "sqlite3.ProgrammingError will be raised.", + i+1, name); + if (ret < 0) { + return; + } + } + if (PyTuple_CheckExact(parameters)) { PyObject *item = PyTuple_GET_ITEM(parameters, i); current_param = Py_NewRef(item); @@ -696,18 +710,16 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, Py_DECREF(adapted); if (rc != SQLITE_OK) { - PyObject *exc, *val, *tb; - PyErr_Fetch(&exc, &val, &tb); + PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); _pysqlite_seterror(state, db); - _PyErr_ChainExceptions(exc, val, tb); + _PyErr_ChainExceptions1(exc); return; } } } else if (PyDict_Check(parameters)) { /* parameters passed as dictionary */ for (i = 1; i <= num_params_needed; i++) { - PyObject *binding_name_obj; Py_BEGIN_ALLOW_THREADS binding_name = sqlite3_bind_parameter_name(self->st, i); Py_END_ALLOW_THREADS @@ -719,23 +731,17 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, } binding_name++; /* skip first char (the colon) */ - binding_name_obj = PyUnicode_FromString(binding_name); - if (!binding_name_obj) { + PyObject *current_param = NULL; + int found = PyMapping_GetOptionalItemString(parameters, + binding_name, + ¤t_param); + if (found == -1) { return; } - if (PyDict_CheckExact(parameters)) { - PyObject *item = PyDict_GetItemWithError(parameters, binding_name_obj); - current_param = Py_XNewRef(item); - } else { - current_param = PyObject_GetItem(parameters, binding_name_obj); - } - Py_DECREF(binding_name_obj); - if (!current_param) { - if (!PyErr_Occurred() || PyErr_ExceptionMatches(PyExc_LookupError)) { - PyErr_Format(state->ProgrammingError, - "You did not supply a value for binding " - "parameter :%s.", binding_name); - } + else if (found == 0) { + PyErr_Format(state->ProgrammingError, + "You did not supply a value for binding " + "parameter :%s.", binding_name); return; } @@ -756,11 +762,10 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, Py_DECREF(adapted); if (rc != SQLITE_OK) { - PyObject *exc, *val, *tb; - PyErr_Fetch(&exc, &val, &tb); + PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); _pysqlite_seterror(state, db); - _PyErr_ChainExceptions(exc, val, tb); + _PyErr_ChainExceptions1(exc); return; } } @@ -770,12 +775,6 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, } } -static inline void -stmt_mark_dirty(pysqlite_Statement *self) -{ - self->in_use = 1; -} - PyObject * _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation, PyObject* second_argument) { @@ -830,16 +829,12 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } } - if (self->statement != NULL) { - /* There is an active statement */ - stmt_reset(self->statement); - } - /* reset description */ Py_INCREF(Py_None); Py_SETREF(self->description, Py_None); if (self->statement) { + // Reset pending statements on this cursor. (void)stmt_reset(self->statement); } @@ -856,7 +851,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation goto error; } - if (self->statement->in_use) { + if (sqlite3_stmt_busy(self->statement->st)) { Py_SETREF(self->statement, pysqlite_statement_create(self->connection, operation)); if (self->statement == NULL) { @@ -864,13 +859,13 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } } - stmt_reset(self->statement); - stmt_mark_dirty(self->statement); + (void)stmt_reset(self->statement); self->rowcount = self->statement->is_dml ? 0L : -1L; /* We start a transaction implicitly before a DML statement. SELECT is the only exception. See #9924. */ - if (self->connection->isolation_level + if (self->connection->autocommit == AUTOCOMMIT_LEGACY + && self->connection->isolation_level && self->statement->is_dml && sqlite3_get_autocommit(self->connection->db)) { @@ -879,14 +874,13 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } } + assert(!sqlite3_stmt_busy(self->statement->st)); while (1) { parameters = PyIter_Next(parameters_iter); if (!parameters) { break; } - stmt_mark_dirty(self->statement); - bind_parameters(state, self->statement, parameters); if (PyErr_Occurred()) { goto error; @@ -902,7 +896,6 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation PyErr_Clear(); } } - (void)stmt_reset(self->statement); _pysqlite_seterror(state, self->connection->db); goto error; } @@ -944,16 +937,8 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation } } - if (rc == SQLITE_DONE && !multiple) { + if (rc == SQLITE_DONE) { if (self->statement->is_dml) { - self->rowcount = (long)sqlite3_changes(self->connection->db); - } - stmt_reset(self->statement); - Py_CLEAR(self->statement); - } - - if (multiple) { - if (self->statement->is_dml && rc == SQLITE_DONE) { self->rowcount += (long)sqlite3_changes(self->connection->db); } stmt_reset(self->statement); @@ -980,11 +965,17 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation self->locked = 0; if (PyErr_Occurred()) { + if (self->statement) { + (void)stmt_reset(self->statement); + Py_CLEAR(self->statement); + } self->rowcount = -1L; return NULL; - } else { - return Py_NewRef((PyObject *)self); } + if (self->statement && !sqlite3_stmt_busy(self->statement->st)) { + Py_CLEAR(self->statement); + } + return Py_NewRef((PyObject *)self); } /*[clinic input] @@ -1052,7 +1043,9 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, // Commit if needed sqlite3 *db = self->connection->db; - if (!sqlite3_get_autocommit(db)) { + if (self->connection->autocommit == AUTOCOMMIT_LEGACY + && !sqlite3_get_autocommit(db)) + { int rc = SQLITE_OK; Py_BEGIN_ALLOW_THREADS @@ -1111,11 +1104,7 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self) sqlite3_stmt *stmt = self->statement->st; assert(stmt != NULL); - if (sqlite3_data_count(stmt) == 0) { - (void)stmt_reset(self->statement); - Py_CLEAR(self->statement); - return NULL; - } + assert(sqlite3_data_count(stmt) != 0); self->locked = 1; // GH-80254: Prevent recursive use of cursors. PyObject *row = _pysqlite_fetch_one_row(self); @@ -1143,8 +1132,7 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self) PyObject *factory = self->row_factory; PyObject *args[] = { (PyObject *)self, row, }; PyObject *new_row = PyObject_Vectorcall(factory, args, 2, NULL); - Py_DECREF(row); - row = new_row; + Py_SETREF(row, new_row); } return row; } @@ -1172,35 +1160,37 @@ pysqlite_cursor_fetchone_impl(pysqlite_Cursor *self) /*[clinic input] _sqlite3.Cursor.fetchmany as pysqlite_cursor_fetchmany - size as maxrows: int(c_default='self->arraysize') = 1 + size as maxrows: size_t(c_default='((pysqlite_Cursor *)self)->arraysize') = 1 The default value is set by the Cursor.arraysize attribute. Fetches several rows from the resultset. [clinic start generated code]*/ static PyObject * -pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, int maxrows) -/*[clinic end generated code: output=a8ef31fea64d0906 input=c26e6ca3f34debd0]*/ +pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, size_t maxrows) +/*[clinic end generated code: output=8bff46d4f24c9e84 input=5485f1f2ae46c2ff]*/ { + if (maxrows > (size_t)UINT32_MAX) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large for C uint32_t"); + return NULL; + } + PyObject* row; PyObject* list; - int counter = 0; list = PyList_New(0); if (!list) { return NULL; } - while ((row = pysqlite_cursor_iternext(self))) { - if (PyList_Append(list, row) < 0) { - Py_DECREF(row); - break; - } + while (maxrows > 0 && (row = pysqlite_cursor_iternext(self))) { + int rc = PyList_Append(list, row); Py_DECREF(row); - - if (++counter == maxrows) { + if (rc < 0) { break; } + maxrows--; } if (PyErr_Occurred()) { @@ -1314,6 +1304,40 @@ pysqlite_cursor_close_impl(pysqlite_Cursor *self) Py_RETURN_NONE; } +/*[clinic input] +@getter +_sqlite3.Cursor.arraysize +[clinic start generated code]*/ + +static PyObject * +_sqlite3_Cursor_arraysize_get_impl(pysqlite_Cursor *self) +/*[clinic end generated code: output=e0919d97175e6c50 input=3278f8d3ecbd90e3]*/ +{ + return PyLong_FromSize_t(self->arraysize); +} + +/*[clinic input] +@setter +_sqlite3.Cursor.arraysize +[clinic start generated code]*/ + +static int +_sqlite3_Cursor_arraysize_set_impl(pysqlite_Cursor *self, PyObject *value) +/*[clinic end generated code: output=af59a6b09f8cce6e input=ace48cb114e26060]*/ +{ + size_t converted; + if (!_PyLong_Size_t_Converter(value, &converted)) { + return -1; + } + if (converted > (size_t)UINT32_MAX) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } + self->arraysize = (uint32_t)converted; + return 0; +} + static PyMethodDef cursor_methods[] = { PYSQLITE_CURSOR_CLOSE_METHODDEF PYSQLITE_CURSOR_EXECUTEMANY_METHODDEF @@ -1329,16 +1353,20 @@ static PyMethodDef cursor_methods[] = { static struct PyMemberDef cursor_members[] = { - {"connection", T_OBJECT, offsetof(pysqlite_Cursor, connection), READONLY}, - {"description", T_OBJECT, offsetof(pysqlite_Cursor, description), READONLY}, - {"arraysize", T_INT, offsetof(pysqlite_Cursor, arraysize), 0}, - {"lastrowid", T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), READONLY}, - {"rowcount", T_LONG, offsetof(pysqlite_Cursor, rowcount), READONLY}, - {"row_factory", T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0}, - {"__weaklistoffset__", T_PYSSIZET, offsetof(pysqlite_Cursor, in_weakreflist), READONLY}, + {"connection", _Py_T_OBJECT, offsetof(pysqlite_Cursor, connection), Py_READONLY}, + {"description", _Py_T_OBJECT, offsetof(pysqlite_Cursor, description), Py_READONLY}, + {"lastrowid", _Py_T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), Py_READONLY}, + {"rowcount", Py_T_LONG, offsetof(pysqlite_Cursor, rowcount), Py_READONLY}, + {"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0}, + {"__weaklistoffset__", Py_T_PYSSIZET, offsetof(pysqlite_Cursor, in_weakreflist), Py_READONLY}, {NULL} }; +static struct PyGetSetDef cursor_getsets[] = { + _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF + {NULL}, +}; + static const char cursor_doc[] = PyDoc_STR("SQLite database cursor class."); @@ -1349,6 +1377,7 @@ static PyType_Slot cursor_slots[] = { {Py_tp_iternext, pysqlite_cursor_iternext}, {Py_tp_methods, cursor_methods}, {Py_tp_members, cursor_members}, + {Py_tp_getset, cursor_getsets}, {Py_tp_init, pysqlite_cursor_init}, {Py_tp_traverse, cursor_traverse}, {Py_tp_clear, cursor_clear}, diff --git a/_sqlite/cursor.h b/_sqlite/cursor.h index 0bcdddc..c840a3d 100644 --- a/_sqlite/cursor.h +++ b/_sqlite/cursor.h @@ -23,7 +23,6 @@ #ifndef PYSQLITE_CURSOR_H #define PYSQLITE_CURSOR_H -#define PY_SSIZE_T_CLEAN #include "Python.h" #include "statement.h" @@ -36,7 +35,7 @@ typedef struct pysqlite_Connection* connection; PyObject* description; PyObject* row_cast_map; - int arraysize; + uint32_t arraysize; PyObject* lastrowid; long rowcount; PyObject* row_factory; diff --git a/_sqlite/microprotocols.c b/_sqlite/microprotocols.c index a79f006..f77458d 100644 --- a/_sqlite/microprotocols.c +++ b/_sqlite/microprotocols.c @@ -57,7 +57,7 @@ pysqlite_microprotocols_add(pysqlite_state *state, PyTypeObject *type, assert(type != NULL); assert(proto != NULL); - key = Py_BuildValue("(OO)", (PyObject*)type, proto); + key = PyTuple_Pack(2, (PyObject *)type, proto); if (!key) { return -1; } @@ -81,24 +81,23 @@ pysqlite_microprotocols_adapt(pysqlite_state *state, PyObject *obj, way to get a quotable object to be its instance */ /* look for an adapter in the registry */ - key = Py_BuildValue("(OO)", (PyObject*)Py_TYPE(obj), proto); + key = PyTuple_Pack(2, (PyObject *)Py_TYPE(obj), proto); if (!key) { return NULL; } - adapter = PyDict_GetItemWithError(state->psyco_adapters, key); + if (PyDict_GetItemRef(state->psyco_adapters, key, &adapter) < 0) { + Py_DECREF(key); + return NULL; + } Py_DECREF(key); if (adapter) { - Py_INCREF(adapter); adapted = PyObject_CallOneArg(adapter, obj); Py_DECREF(adapter); return adapted; } - if (PyErr_Occurred()) { - return NULL; - } /* try to have the protocol adapt this object */ - if (_PyObject_LookupAttr(proto, state->str___adapt__, &adapter) < 0) { + if (PyObject_GetOptionalAttr(proto, state->str___adapt__, &adapter) < 0) { return NULL; } if (adapter) { @@ -117,7 +116,7 @@ pysqlite_microprotocols_adapt(pysqlite_state *state, PyObject *obj, } /* and finally try to have the object adapt itself */ - if (_PyObject_LookupAttr(obj, state->str___conform__, &adapter) < 0) { + if (PyObject_GetOptionalAttr(obj, state->str___conform__, &adapter) < 0) { return NULL; } if (adapter) { diff --git a/_sqlite/microprotocols.h b/_sqlite/microprotocols.h index 6bde9d0..8a8c335 100644 --- a/_sqlite/microprotocols.h +++ b/_sqlite/microprotocols.h @@ -26,7 +26,6 @@ #ifndef PSYCOPG_MICROPROTOCOLS_H #define PSYCOPG_MICROPROTOCOLS_H 1 -#define PY_SSIZE_T_CLEAN #include /** exported functions **/ diff --git a/_sqlite/module.c b/_sqlite/module.c index e633a63..3d828ab 100644 --- a/_sqlite/module.c +++ b/_sqlite/module.c @@ -21,6 +21,10 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "connection.h" #include "statement.h" #include "cursor.h" @@ -29,8 +33,10 @@ #include "row.h" #include "blob.h" -#if SQLITE_VERSION_NUMBER < 3007015 -#error "SQLite 3.7.15 or higher required" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() + +#if SQLITE_VERSION_NUMBER < 3015002 +#error "SQLite 3.15.2 or higher required" #endif #define clinic_state() (pysqlite_get_state(module)) @@ -42,30 +48,33 @@ module _sqlite3 [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=81e330492d57488e]*/ -// NB: This needs to be in sync with the Connection.__init__ docstring. -PyDoc_STRVAR(module_connect_doc, -"connect($module, /, database, timeout=5.0, detect_types=0,\n" -" isolation_level='', check_same_thread=True,\n" -" factory=ConnectionType, cached_statements=128, uri=False)\n" -"--\n" -"\n" -"Opens a connection to the SQLite database file database.\n" -"\n" -"You can use \":memory:\" to open a database connection to a database that resides\n" -"in RAM instead of on disk."); - -#define PYSQLITE_CONNECT_METHODDEF \ - {"connect", _PyCFunction_CAST(module_connect), METH_FASTCALL|METH_KEYWORDS, module_connect_doc}, +/* + * We create 'clinic/_sqlite3.connect.c.h' in connection.c, in order to + * keep the signatures of sqlite3.Connection.__init__ and + * sqlite3.connect() synchronised. + */ +#include "clinic/_sqlite3.connect.c.h" static PyObject * -module_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargsf, - PyObject *kwnames) +pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargsf, + PyObject *kwnames) { pysqlite_state *state = pysqlite_get_state(module); PyObject *factory = (PyObject *)state->ConnectionType; static const int FACTORY_POS = 5; Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + if (nargs > 1 && nargs <= 8) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing more than 1 positional argument to sqlite3.connect()" + " is deprecated. Parameters 'timeout', 'detect_types', " + "'isolation_level', 'check_same_thread', 'factory', " + "'cached_statements' and 'uri' will become keyword-only " + "parameters in Python 3.15.", 1)) + { + return NULL; + } + } if (nargs > FACTORY_POS) { factory = args[FACTORY_POS]; } @@ -101,36 +110,6 @@ pysqlite_complete_statement_impl(PyObject *module, const char *statement) } } -/*[clinic input] -_sqlite3.enable_shared_cache as pysqlite_enable_shared_cache - - do_enable: int - -Enable or disable shared cache mode for the calling thread. - -This method is deprecated and will be removed in Python 3.12. -Shared cache is strongly discouraged by the SQLite 3 documentation. -If shared cache must be used, open the database in URI mode using -the cache=shared query parameter. -[clinic start generated code]*/ - -static PyObject * -pysqlite_enable_shared_cache_impl(PyObject *module, int do_enable) -/*[clinic end generated code: output=259c74eedee1516b input=26e40d5971d3487d]*/ -{ - int rc; - - rc = sqlite3_enable_shared_cache(do_enable); - - if (rc != SQLITE_OK) { - pysqlite_state *state = pysqlite_get_state(module); - PyErr_SetString(state->OperationalError, "Changing the shared_cache flag failed"); - return NULL; - } else { - Py_RETURN_NONE; - } -} - /*[clinic input] _sqlite3.register_adapter as pysqlite_register_adapter @@ -254,14 +233,8 @@ static int converters_init(PyObject* module) static int load_functools_lru_cache(PyObject *module) { - PyObject *functools = PyImport_ImportModule("functools"); - if (functools == NULL) { - return -1; - } - pysqlite_state *state = pysqlite_get_state(module); - state->lru_cache = PyObject_GetAttrString(functools, "lru_cache"); - Py_DECREF(functools); + state->lru_cache = _PyImport_GetModuleAttrString("functools", "lru_cache"); if (state->lru_cache == NULL) { return -1; } @@ -271,9 +244,8 @@ load_functools_lru_cache(PyObject *module) static PyMethodDef module_methods[] = { PYSQLITE_ADAPT_METHODDEF PYSQLITE_COMPLETE_STATEMENT_METHODDEF - PYSQLITE_CONNECT_METHODDEF + {"connect", _PyCFunction_CAST(pysqlite_connect), METH_FASTCALL|METH_KEYWORDS, pysqlite_connect__doc__}, PYSQLITE_ENABLE_CALLBACK_TRACE_METHODDEF - PYSQLITE_ENABLE_SHARED_CACHE_METHODDEF PYSQLITE_REGISTER_ADAPTER_METHODDEF PYSQLITE_REGISTER_CONVERTER_METHODDEF {NULL, NULL} @@ -281,12 +253,6 @@ static PyMethodDef module_methods[] = { /* SQLite C API result codes. See also: * - https://www.sqlite.org/c3ref/c_abort_rollback.html - * - https://sqlite.org/changes.html#version_3_3_8 - * - https://sqlite.org/changes.html#version_3_7_16 - * - https://sqlite.org/changes.html#version_3_7_17 - * - https://sqlite.org/changes.html#version_3_8_0 - * - https://sqlite.org/changes.html#version_3_8_3 - * - https://sqlite.org/changes.html#version_3_14 * * Note: the SQLite changelogs rarely mention new result codes, so in order to * keep the 'error_codes' table in sync with SQLite, we must manually inspect @@ -330,10 +296,8 @@ static const struct { DECLARE_ERROR_CODE(SQLITE_ROW), DECLARE_ERROR_CODE(SQLITE_SCHEMA), DECLARE_ERROR_CODE(SQLITE_TOOBIG), -#if SQLITE_VERSION_NUMBER >= 3007017 DECLARE_ERROR_CODE(SQLITE_NOTICE), DECLARE_ERROR_CODE(SQLITE_WARNING), -#endif // Extended result code list DECLARE_ERROR_CODE(SQLITE_ABORT_ROLLBACK), DECLARE_ERROR_CODE(SQLITE_BUSY_RECOVERY), @@ -367,7 +331,6 @@ static const struct { DECLARE_ERROR_CODE(SQLITE_LOCKED_SHAREDCACHE), DECLARE_ERROR_CODE(SQLITE_READONLY_CANTLOCK), DECLARE_ERROR_CODE(SQLITE_READONLY_RECOVERY), -#if SQLITE_VERSION_NUMBER >= 3007016 DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_CHECK), DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_COMMITHOOK), DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_FOREIGNKEY), @@ -378,39 +341,20 @@ static const struct { DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_UNIQUE), DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_VTAB), DECLARE_ERROR_CODE(SQLITE_READONLY_ROLLBACK), -#endif -#if SQLITE_VERSION_NUMBER >= 3007017 DECLARE_ERROR_CODE(SQLITE_IOERR_MMAP), DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_ROLLBACK), DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_WAL), -#endif -#if SQLITE_VERSION_NUMBER >= 3008000 DECLARE_ERROR_CODE(SQLITE_BUSY_SNAPSHOT), DECLARE_ERROR_CODE(SQLITE_IOERR_GETTEMPPATH), DECLARE_ERROR_CODE(SQLITE_WARNING_AUTOINDEX), -#endif -#if SQLITE_VERSION_NUMBER >= 3008001 DECLARE_ERROR_CODE(SQLITE_CANTOPEN_CONVPATH), DECLARE_ERROR_CODE(SQLITE_IOERR_CONVPATH), -#endif -#if SQLITE_VERSION_NUMBER >= 3008002 DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_ROWID), -#endif -#if SQLITE_VERSION_NUMBER >= 3008003 DECLARE_ERROR_CODE(SQLITE_READONLY_DBMOVED), -#endif -#if SQLITE_VERSION_NUMBER >= 3008007 DECLARE_ERROR_CODE(SQLITE_AUTH_USER), -#endif -#if SQLITE_VERSION_NUMBER >= 3009000 DECLARE_ERROR_CODE(SQLITE_IOERR_VNODE), -#endif -#if SQLITE_VERSION_NUMBER >= 3010000 DECLARE_ERROR_CODE(SQLITE_IOERR_AUTH), -#endif -#if SQLITE_VERSION_NUMBER >= 3014001 DECLARE_ERROR_CODE(SQLITE_OK_LOAD_PERMANENTLY), -#endif #if SQLITE_VERSION_NUMBER >= 3021000 DECLARE_ERROR_CODE(SQLITE_IOERR_BEGIN_ATOMIC), DECLARE_ERROR_CODE(SQLITE_IOERR_COMMIT_ATOMIC), @@ -517,9 +461,7 @@ add_integer_constants(PyObject *module) { ADD_INT(SQLITE_DROP_VTABLE); ADD_INT(SQLITE_FUNCTION); ADD_INT(SQLITE_SAVEPOINT); -#if SQLITE_VERSION_NUMBER >= 3008003 ADD_INT(SQLITE_RECURSIVE); -#endif // Run-time limit categories ADD_INT(SQLITE_LIMIT_LENGTH); ADD_INT(SQLITE_LIMIT_SQL_LENGTH); @@ -532,8 +474,45 @@ add_integer_constants(PyObject *module) { ADD_INT(SQLITE_LIMIT_LIKE_PATTERN_LENGTH); ADD_INT(SQLITE_LIMIT_VARIABLE_NUMBER); ADD_INT(SQLITE_LIMIT_TRIGGER_DEPTH); -#if SQLITE_VERSION_NUMBER >= 3008007 ADD_INT(SQLITE_LIMIT_WORKER_THREADS); + + /* + * Database connection configuration options. + * See https://www.sqlite.org/c3ref/c_dbconfig_defensive.html + */ + ADD_INT(SQLITE_DBCONFIG_ENABLE_FKEY); + ADD_INT(SQLITE_DBCONFIG_ENABLE_TRIGGER); + ADD_INT(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER); + ADD_INT(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION); +#if SQLITE_VERSION_NUMBER >= 3016000 + ADD_INT(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE); +#endif +#if SQLITE_VERSION_NUMBER >= 3020000 + ADD_INT(SQLITE_DBCONFIG_ENABLE_QPSG); +#endif +#if SQLITE_VERSION_NUMBER >= 3022000 + ADD_INT(SQLITE_DBCONFIG_TRIGGER_EQP); +#endif +#if SQLITE_VERSION_NUMBER >= 3024000 + ADD_INT(SQLITE_DBCONFIG_RESET_DATABASE); +#endif +#if SQLITE_VERSION_NUMBER >= 3026000 + ADD_INT(SQLITE_DBCONFIG_DEFENSIVE); +#endif +#if SQLITE_VERSION_NUMBER >= 3028000 + ADD_INT(SQLITE_DBCONFIG_WRITABLE_SCHEMA); +#endif +#if SQLITE_VERSION_NUMBER >= 3029000 + ADD_INT(SQLITE_DBCONFIG_DQS_DDL); + ADD_INT(SQLITE_DBCONFIG_DQS_DML); + ADD_INT(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE); +#endif +#if SQLITE_VERSION_NUMBER >= 3030000 + ADD_INT(SQLITE_DBCONFIG_ENABLE_VIEW); +#endif +#if SQLITE_VERSION_NUMBER >= 3031000 + ADD_INT(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT); + ADD_INT(SQLITE_DBCONFIG_TRUSTED_SCHEMA); #endif #undef ADD_INT return 0; @@ -671,8 +650,8 @@ do { \ static int module_exec(PyObject *module) { - if (sqlite3_libversion_number() < 3007015) { - PyErr_SetString(PyExc_ImportError, MODULE_NAME ": SQLite 3.7.15 or higher required"); + if (sqlite3_libversion_number() < 3015002) { + PyErr_SetString(PyExc_ImportError, MODULE_NAME ": SQLite 3.15.2 or higher required"); return -1; } @@ -735,7 +714,7 @@ module_exec(PyObject *module) goto error; } - if (PyModule_AddStringConstant(module, "version", PYSQLITE_VERSION) < 0) { + if (PyModule_AddStringConstant(module, "_deprecated_version", PYSQLITE_VERSION) < 0) { goto error; } @@ -743,6 +722,10 @@ module_exec(PyObject *module) goto error; } + if (PyModule_AddIntMacro(module, LEGACY_TRANSACTION_CONTROL) < 0) { + goto error; + } + int threadsafety = get_threadsafety(state); if (threadsafety < 0) { goto error; @@ -768,12 +751,13 @@ module_exec(PyObject *module) return 0; error: - sqlite3_shutdown(); return -1; } static struct PyModuleDef_Slot module_slots[] = { {Py_mod_exec, module_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0, NULL}, }; diff --git a/_sqlite/module.h b/_sqlite/module.h index dbb491a..95ae6ad 100644 --- a/_sqlite/module.h +++ b/_sqlite/module.h @@ -23,10 +23,14 @@ #ifndef PYSQLITE_MODULE_H #define PYSQLITE_MODULE_H -#define PY_SSIZE_T_CLEAN #include "Python.h" +#define LEGACY_TRANSACTION_CONTROL -1 + #define PYSQLITE_VERSION "2.6.0" +#ifndef MODULE_NAME +# define MODULE_NAME "sqlite3" +#endif typedef struct { PyObject *DataError; diff --git a/_sqlite/row.c b/_sqlite/row.c index 1a19432..3427fae 100644 --- a/_sqlite/row.c +++ b/_sqlite/row.c @@ -21,6 +21,10 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "row.h" #include "cursor.h" @@ -128,7 +132,6 @@ static PyObject * pysqlite_row_subscript(pysqlite_Row *self, PyObject *idx) { Py_ssize_t _idx; - Py_ssize_t nitems, i; if (PyLong_Check(idx)) { _idx = PyNumber_AsSsize_t(idx, PyExc_IndexError); @@ -140,9 +143,13 @@ pysqlite_row_subscript(pysqlite_Row *self, PyObject *idx) PyObject *item = PyTuple_GetItem(self->data, _idx); return Py_XNewRef(item); } else if (PyUnicode_Check(idx)) { - nitems = PyTuple_Size(self->description); + if (Py_IsNone(self->description)) { + PyErr_Format(PyExc_IndexError, "No item with key %R", idx); + return NULL; + } + Py_ssize_t nitems = PyTuple_GET_SIZE(self->description); - for (i = 0; i < nitems; i++) { + for (Py_ssize_t i = 0; i < nitems; i++) { PyObject *obj; obj = PyTuple_GET_ITEM(self->description, i); obj = PyTuple_GET_ITEM(obj, 0); @@ -185,17 +192,19 @@ static PyObject * pysqlite_row_keys_impl(pysqlite_Row *self) /*[clinic end generated code: output=efe3dfb3af6edc07 input=7549a122827c5563]*/ { - PyObject* list; - Py_ssize_t nitems, i; - - list = PyList_New(0); + PyObject *list = PyList_New(0); if (!list) { return NULL; } - nitems = PyTuple_Size(self->description); + if (Py_IsNone(self->description)) { + return list; + } - for (i = 0; i < nitems; i++) { - if (PyList_Append(list, PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0)) != 0) { + Py_ssize_t nitems = PyTuple_GET_SIZE(self->description); + for (Py_ssize_t i = 0; i < nitems; i++) { + PyObject *descr = PyTuple_GET_ITEM(self->description, i); + PyObject *name = PyTuple_GET_ITEM(descr, 0); + if (PyList_Append(list, name) < 0) { Py_DECREF(list); return NULL; } diff --git a/_sqlite/row.h b/_sqlite/row.h index b519098..d42b781 100644 --- a/_sqlite/row.h +++ b/_sqlite/row.h @@ -23,7 +23,6 @@ #ifndef PYSQLITE_ROW_H #define PYSQLITE_ROW_H -#define PY_SSIZE_T_CLEAN #include "Python.h" typedef struct _Row diff --git a/_sqlite/statement.c b/_sqlite/statement.c index aee4607..229bfc3 100644 --- a/_sqlite/statement.c +++ b/_sqlite/statement.c @@ -88,7 +88,6 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql) } self->st = stmt; - self->in_use = 0; self->is_dml = is_dml; PyObject_GC_Track(self); diff --git a/_sqlite/statement.h b/_sqlite/statement.h index 5e61227..b18f170 100644 --- a/_sqlite/statement.h +++ b/_sqlite/statement.h @@ -23,7 +23,6 @@ #ifndef PYSQLITE_STATEMENT_H #define PYSQLITE_STATEMENT_H -#define PY_SSIZE_T_CLEAN #include "Python.h" #include "connection.h" @@ -33,7 +32,6 @@ typedef struct { PyObject_HEAD sqlite3_stmt* st; - int in_use; int is_dml; } pysqlite_Statement; diff --git a/_sqlite/util.c b/_sqlite/util.c index 2b3bbfe..b0622e6 100644 --- a/_sqlite/util.c +++ b/_sqlite/util.c @@ -21,7 +21,12 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "module.h" +#include "pycore_long.h" // _PyLong_AsByteArray() #include "connection.h" // Returns non-NULL if a new exception should be raised @@ -129,6 +134,7 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db) /* Create and set the exception. */ int extended_errcode = sqlite3_extended_errcode(db); + // sqlite3_errmsg() always returns an UTF-8 encoded message const char *errmsg = sqlite3_errmsg(db); raise_exception(exc_class, extended_errcode, errmsg); return extended_errcode; @@ -157,7 +163,7 @@ _pysqlite_long_as_int64(PyObject * py_val) sqlite_int64 int64val; if (_PyLong_AsByteArray((PyLongObject *)py_val, (unsigned char *)&int64val, sizeof(int64val), - IS_LITTLE_ENDIAN, 1 /* signed */) >= 0) { + IS_LITTLE_ENDIAN, 1 /* signed */, 0) >= 0) { return int64val; } } diff --git a/_sqlite/util.h b/_sqlite/util.h index a22bcd8..68b1a8c 100644 --- a/_sqlite/util.h +++ b/_sqlite/util.h @@ -23,7 +23,6 @@ #ifndef PYSQLITE_UTIL_H #define PYSQLITE_UTIL_H -#define PY_SSIZE_T_CLEAN #include "Python.h" #include "pythread.h" #include "sqlite3.h" diff --git a/pyproject.toml b/pyproject.toml index c52115f..58c3bfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "rotki-sqlite" -requires-python = "==3.11.*" -version = "2025.08.01" +requires-python = "==3.13.*" +version = "2026.07.01" description = "DB-API 2.0 interface for Sqlite 3.x" readme = "README.md" license = { text = "zlib/libpng" } @@ -21,6 +21,7 @@ classifiers = [ "Operating System :: POSIX", "Programming Language :: C", "Programming Language :: Python", + "Programming Language :: Python :: 3.13", "Topic :: Database :: Database Engines/Servers", "Topic :: Software Development :: Libraries :: Python Modules", ] diff --git a/rsqlite/__init__.py b/rsqlite/__init__.py index cd2a626..43413b6 100644 --- a/rsqlite/__init__.py +++ b/rsqlite/__init__.py @@ -1,6 +1,6 @@ # pysqlite2/__init__.py: the pysqlite2 package. # -# Copyright (C) 2005 Gerhard Haring +# Copyright (C) 2005 Gerhard Häring # # This file is part of pysqlite. # @@ -20,4 +20,51 @@ # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. -from rsqlite.dbapi2 import * \ No newline at end of file +""" +The sqlite3 extension module provides a DB-API 2.0 (PEP 249) compliant +interface to the SQLite library, and requires SQLite 3.15.2 or newer. + +To use the module, start by creating a database Connection object: + + import rsqlite + cx = rsqlite.connect("test.db") # test.db will be created or opened + +The special path name ":memory:" can be provided to connect to a transient +in-memory database: + + cx = rsqlite.connect(":memory:") # connect to a database in RAM + +Once a connection has been established, create a Cursor object and call +its execute() method to perform SQL queries: + + cu = cx.cursor() + + # create a table + cu.execute("create table lang(name, first_appeared)") + + # insert values into a table + cu.execute("insert into lang values (?, ?)", ("C", 1972)) + + # execute a query and iterate over the result + for row in cu.execute("select * from lang"): + print(row) + + cx.close() + +The sqlite3 module is written by Gerhard Häring . +""" + +from rsqlite.dbapi2 import * +from rsqlite.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}") diff --git a/rsqlite/dbapi2.py b/rsqlite/dbapi2.py index a6fbae7..a8362db 100644 --- a/rsqlite/dbapi2.py +++ b/rsqlite/dbapi2.py @@ -25,6 +25,9 @@ import collections.abc from ._sqlite3 import * +from ._sqlite3 import _deprecated_version + +_deprecated_names = frozenset({"version", "version_info"}) paramstyle = "qmark" @@ -45,7 +48,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 @@ -94,3 +97,12 @@ 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}") diff --git a/rsqlite/dump.py b/rsqlite/dump.py new file mode 100644 index 0000000..57e6a3b --- /dev/null +++ b/rsqlite/dump.py @@ -0,0 +1,113 @@ +# Mimic the sqlite3 console shell's .dump command +# Author: Paul Kippes + +# Every identifier in sql is quoted based on a comment in sqlite +# documentation "SQLite adds new keywords from time to time when it +# takes on new features. So to prevent your code from being broken by +# future enhancements, you should normally quote any identifier that +# is an English language word, even if you do not have to." + +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. + + Used to produce an SQL dump of the database. Useful to save an in-memory + database for later restoration. This function should not be called + 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 = 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, params) + sqlite_sequence = [] + for table_name, type, sql in schema_res.fetchall(): + if table_name == 'sqlite_sequence': + rows = cu.execute('SELECT * FROM "sqlite_sequence";') + sqlite_sequence = ['DELETE FROM "sqlite_sequence"'] + sqlite_sequence += [ + 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 + 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 = _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( + table_name_ident, + "','".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 = 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, 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: + yield('{0};'.format(row)) + + yield('COMMIT;') diff --git a/setup.py b/setup.py index bb89d88..2e1205d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,9 @@ -from setuptools import setup, Extension +import sysconfig + +from setuptools import Extension, setup + + +python_internal_headers = f"{sysconfig.get_path('include')}/internal" sqlite_ext = Extension( "rsqlite._sqlite3", @@ -14,7 +19,7 @@ "_sqlite/util.c", "sqlite3.c" ], - include_dirs=["."], + include_dirs=[".", python_internal_headers], define_macros=[ ("MODULE_NAME", '"rsqlite"'), ("PY_SQLITE_ENABLE_LOAD_EXTENSION", "1"), @@ -30,4 +35,4 @@ setup( ext_modules=[sqlite_ext] -) \ No newline at end of file +) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..c32d5bf --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = "==3.13.*" + +[[package]] +name = "rotki-sqlite" +version = "2026.7.1" +source = { editable = "." }