Skip to content

bpo-34041: Added *deterministic* parameter to sqlite3.Connection.create_function(). #8086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest add a link to the NotSupportedError documentation, then I noticed it wasn't documented. I've opened https://bugs.python.org/issue34061

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
Expand Down
23 changes: 23 additions & 0 deletions Lib/sqlite3/test/userfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -275,6 +276,28 @@ def CheckAnyArguments(self):
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a simple test to make sure that deterministic is a keyword-only argument?

self.con.create_function("deterministic", 0, int, True)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in the following test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.con.create_function("deterministic", 0, int, deterministic=False) should work even on SQLite < 3.8.3, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just pushed the fix.


def CheckFuncDeterministicKeywordOnly(self):
with self.assertRaises(TypeError):
self.con.create_function("deterministic", 0, int, True)


class AggregateTests(unittest.TestCase):
def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add the parameter *deterministic* to the
:meth:`sqlite3.Connection.create_function` method. Patch by Sergey Fedoseev.
32 changes: 28 additions & 4 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down