Skip to content

Commit 0830858

Browse files
sir-sigurdberkerpeksag
authored andcommitted
bpo-34041: Allow creating deterministic functions in Connection.create_function() (GH-8086)
1 parent 8d41278 commit 0830858

File tree

4 files changed

+62
-6
lines changed

4 files changed

+62
-6
lines changed

Doc/library/sqlite3.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,17 +337,24 @@ Connection Objects
337337
:meth:`~Cursor.executescript` method with the given *sql_script*, and
338338
returns the cursor.
339339

340-
.. method:: create_function(name, num_params, func)
340+
.. method:: create_function(name, num_params, func, *, deterministic=False)
341341

342342
Creates a user-defined function that you can later use from within SQL
343343
statements under the function name *name*. *num_params* is the number of
344344
parameters the function accepts (if *num_params* is -1, the function may
345345
take any number of arguments), and *func* is a Python callable that is
346-
called as the SQL function.
346+
called as the SQL function. If *deterministic* is true, the created function
347+
is marked as `deterministic <https://sqlite.org/deterministic.html>`_, which
348+
allows SQLite to perform additional optimizations. This flag is supported by
349+
SQLite 3.8.3 or higher, ``sqlite3.NotSupportedError`` will be raised if used
350+
with older versions.
347351

348352
The function can return any of the types supported by SQLite: bytes, str, int,
349353
float and ``None``.
350354

355+
.. versionchanged:: 3.8
356+
The *deterministic* parameter was added.
357+
351358
Example:
352359

353360
.. literalinclude:: ../includes/sqlite3/md5func.py

Lib/sqlite3/test/userfunctions.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
# 3. This notice may not be removed or altered from any source distribution.
2424

2525
import unittest
26+
import unittest.mock
2627
import sqlite3 as sqlite
2728

2829
def func_returntext():
@@ -275,6 +276,28 @@ def CheckAnyArguments(self):
275276
val = cur.fetchone()[0]
276277
self.assertEqual(val, 2)
277278

279+
def CheckFuncNonDeterministic(self):
280+
mock = unittest.mock.Mock(return_value=None)
281+
self.con.create_function("deterministic", 0, mock, deterministic=False)
282+
self.con.execute("select deterministic() = deterministic()")
283+
self.assertEqual(mock.call_count, 2)
284+
285+
@unittest.skipIf(sqlite.sqlite_version_info < (3, 8, 3), "deterministic parameter not supported")
286+
def CheckFuncDeterministic(self):
287+
mock = unittest.mock.Mock(return_value=None)
288+
self.con.create_function("deterministic", 0, mock, deterministic=True)
289+
self.con.execute("select deterministic() = deterministic()")
290+
self.assertEqual(mock.call_count, 1)
291+
292+
@unittest.skipIf(sqlite.sqlite_version_info >= (3, 8, 3), "SQLite < 3.8.3 needed")
293+
def CheckFuncDeterministicNotSupported(self):
294+
with self.assertRaises(sqlite.NotSupportedError):
295+
self.con.create_function("deterministic", 0, int, deterministic=True)
296+
297+
def CheckFuncDeterministicKeywordOnly(self):
298+
with self.assertRaises(TypeError):
299+
self.con.create_function("deterministic", 0, int, True)
300+
278301

279302
class AggregateTests(unittest.TestCase):
280303
def setUp(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add the parameter *deterministic* to the
2+
:meth:`sqlite3.Connection.create_function` method. Patch by Sergey Fedoseev.

Modules/_sqlite/connection.c

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -810,24 +810,48 @@ static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self)
810810

811811
PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
812812
{
813-
static char *kwlist[] = {"name", "narg", "func", NULL, NULL};
813+
static char *kwlist[] = {"name", "narg", "func", "deterministic", NULL};
814814

815815
PyObject* func;
816816
char* name;
817817
int narg;
818818
int rc;
819+
int deterministic = 0;
820+
int flags = SQLITE_UTF8;
819821

820822
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
821823
return NULL;
822824
}
823825

824-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO", kwlist,
825-
&name, &narg, &func))
826+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|$p", kwlist,
827+
&name, &narg, &func, &deterministic))
826828
{
827829
return NULL;
828830
}
829831

830-
rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
832+
if (deterministic) {
833+
#if SQLITE_VERSION_NUMBER < 3008003
834+
PyErr_SetString(pysqlite_NotSupportedError,
835+
"deterministic=True requires SQLite 3.8.3 or higher");
836+
return NULL;
837+
#else
838+
if (sqlite3_libversion_number() < 3008003) {
839+
PyErr_SetString(pysqlite_NotSupportedError,
840+
"deterministic=True requires SQLite 3.8.3 or higher");
841+
return NULL;
842+
}
843+
flags |= SQLITE_DETERMINISTIC;
844+
#endif
845+
}
846+
847+
rc = sqlite3_create_function(self->db,
848+
name,
849+
narg,
850+
flags,
851+
(void*)func,
852+
_pysqlite_func_callback,
853+
NULL,
854+
NULL);
831855

832856
if (rc != SQLITE_OK) {
833857
/* Workaround for SQLite bug: no error code or string is available here */

0 commit comments

Comments
 (0)