bpo-34041: Allow creating deterministic functions in Connection.create_function() (GH-8086)
This commit is contained in:
parent
8d41278045
commit
0830858aee
|
@ -337,17 +337,24 @@ Connection Objects
|
|||
:meth:`~Cursor.executescript` method with the given *sql_script*, and
|
||||
returns the cursor.
|
||||
|
||||
.. method:: create_function(name, num_params, func)
|
||||
.. method:: create_function(name, num_params, func, *, deterministic=False)
|
||||
|
||||
Creates a user-defined function that you can later use from within SQL
|
||||
statements under the function name *name*. *num_params* is the number of
|
||||
parameters the function accepts (if *num_params* is -1, the function may
|
||||
take any number of arguments), and *func* is a Python callable that is
|
||||
called as the SQL function.
|
||||
called as the SQL function. If *deterministic* is true, the created function
|
||||
is marked as `deterministic <https://sqlite.org/deterministic.html>`_, which
|
||||
allows SQLite to perform additional optimizations. This flag is supported by
|
||||
SQLite 3.8.3 or higher, ``sqlite3.NotSupportedError`` will be raised if used
|
||||
with older versions.
|
||||
|
||||
The function can return any of the types supported by SQLite: bytes, str, int,
|
||||
float and ``None``.
|
||||
|
||||
.. versionchanged:: 3.8
|
||||
The *deterministic* parameter was added.
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: ../includes/sqlite3/md5func.py
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
# 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import sqlite3 as sqlite
|
||||
|
||||
def func_returntext():
|
||||
|
@ -275,6 +276,28 @@ class FunctionTests(unittest.TestCase):
|
|||
val = cur.fetchone()[0]
|
||||
self.assertEqual(val, 2)
|
||||
|
||||
def CheckFuncNonDeterministic(self):
|
||||
mock = unittest.mock.Mock(return_value=None)
|
||||
self.con.create_function("deterministic", 0, mock, deterministic=False)
|
||||
self.con.execute("select deterministic() = deterministic()")
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
|
||||
@unittest.skipIf(sqlite.sqlite_version_info < (3, 8, 3), "deterministic parameter not supported")
|
||||
def CheckFuncDeterministic(self):
|
||||
mock = unittest.mock.Mock(return_value=None)
|
||||
self.con.create_function("deterministic", 0, mock, deterministic=True)
|
||||
self.con.execute("select deterministic() = deterministic()")
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
|
||||
@unittest.skipIf(sqlite.sqlite_version_info >= (3, 8, 3), "SQLite < 3.8.3 needed")
|
||||
def CheckFuncDeterministicNotSupported(self):
|
||||
with self.assertRaises(sqlite.NotSupportedError):
|
||||
self.con.create_function("deterministic", 0, int, deterministic=True)
|
||||
|
||||
def CheckFuncDeterministicKeywordOnly(self):
|
||||
with self.assertRaises(TypeError):
|
||||
self.con.create_function("deterministic", 0, int, True)
|
||||
|
||||
|
||||
class AggregateTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add the parameter *deterministic* to the
|
||||
:meth:`sqlite3.Connection.create_function` method. Patch by Sergey Fedoseev.
|
|
@ -810,24 +810,48 @@ static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self)
|
|||
|
||||
PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
|
||||
{
|
||||
static char *kwlist[] = {"name", "narg", "func", NULL, NULL};
|
||||
static char *kwlist[] = {"name", "narg", "func", "deterministic", NULL};
|
||||
|
||||
PyObject* func;
|
||||
char* name;
|
||||
int narg;
|
||||
int rc;
|
||||
int deterministic = 0;
|
||||
int flags = SQLITE_UTF8;
|
||||
|
||||
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO", kwlist,
|
||||
&name, &narg, &func))
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|$p", kwlist,
|
||||
&name, &narg, &func, &deterministic))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
|
||||
if (deterministic) {
|
||||
#if SQLITE_VERSION_NUMBER < 3008003
|
||||
PyErr_SetString(pysqlite_NotSupportedError,
|
||||
"deterministic=True requires SQLite 3.8.3 or higher");
|
||||
return NULL;
|
||||
#else
|
||||
if (sqlite3_libversion_number() < 3008003) {
|
||||
PyErr_SetString(pysqlite_NotSupportedError,
|
||||
"deterministic=True requires SQLite 3.8.3 or higher");
|
||||
return NULL;
|
||||
}
|
||||
flags |= SQLITE_DETERMINISTIC;
|
||||
#endif
|
||||
}
|
||||
|
||||
rc = sqlite3_create_function(self->db,
|
||||
name,
|
||||
narg,
|
||||
flags,
|
||||
(void*)func,
|
||||
_pysqlite_func_callback,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
/* Workaround for SQLite bug: no error code or string is available here */
|
||||
|
|
Loading…
Reference in New Issue