Commit 0830858a authored by Sergey Fedoseev's avatar Sergey Fedoseev Committed by Berker Peksag

bpo-34041: Allow creating deterministic functions in Connection.create_function() (GH-8086)

parent 8d412780
......@@ -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):
......
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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment