mirror of https://github.com/python/cpython
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
|
:meth:`~Cursor.executescript` method with the given *sql_script*, and
|
||||||
returns the cursor.
|
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
|
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
|
statements under the function name *name*. *num_params* is the number of
|
||||||
parameters the function accepts (if *num_params* is -1, the function may
|
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
|
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,
|
The function can return any of the types supported by SQLite: bytes, str, int,
|
||||||
float and ``None``.
|
float and ``None``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
The *deterministic* parameter was added.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
.. literalinclude:: ../includes/sqlite3/md5func.py
|
.. literalinclude:: ../includes/sqlite3/md5func.py
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
# 3. This notice may not be removed or altered from any source distribution.
|
# 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
|
|
||||||
def func_returntext():
|
def func_returntext():
|
||||||
|
@ -275,6 +276,28 @@ class FunctionTests(unittest.TestCase):
|
||||||
val = cur.fetchone()[0]
|
val = cur.fetchone()[0]
|
||||||
self.assertEqual(val, 2)
|
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):
|
class AggregateTests(unittest.TestCase):
|
||||||
def setUp(self):
|
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)
|
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;
|
PyObject* func;
|
||||||
char* name;
|
char* name;
|
||||||
int narg;
|
int narg;
|
||||||
int rc;
|
int rc;
|
||||||
|
int deterministic = 0;
|
||||||
|
int flags = SQLITE_UTF8;
|
||||||
|
|
||||||
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO", kwlist,
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|$p", kwlist,
|
||||||
&name, &narg, &func))
|
&name, &narg, &func, &deterministic))
|
||||||
{
|
{
|
||||||
return NULL;
|
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) {
|
if (rc != SQLITE_OK) {
|
||||||
/* Workaround for SQLite bug: no error code or string is available here */
|
/* Workaround for SQLite bug: no error code or string is available here */
|
||||||
|
|
Loading…
Reference in New Issue