summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSergey Fedoseev <fedoseev.sergey@gmail.com>2018-07-08 07:09:20 (GMT)
committerBerker Peksag <berker.peksag@gmail.com>2018-07-08 07:09:20 (GMT)
commit0830858aeedecc9ece60349f8c31c2690d1a99f8 (patch)
tree8cd32ec3cb898edd3631df2378662aa13e8b761a
parent8d41278045ee4e8bf1cadb58a7db58d70ad55237 (diff)
downloadcpython-0830858aeedecc9ece60349f8c31c2690d1a99f8.zip
cpython-0830858aeedecc9ece60349f8c31c2690d1a99f8.tar.gz
cpython-0830858aeedecc9ece60349f8c31c2690d1a99f8.tar.bz2
bpo-34041: Allow creating deterministic functions in Connection.create_function() (GH-8086)
-rw-r--r--Doc/library/sqlite3.rst11
-rw-r--r--Lib/sqlite3/test/userfunctions.py23
-rw-r--r--Misc/NEWS.d/next/Library/2018-07-06-15-06-32.bpo-34041.0zrKLh.rst2
-rw-r--r--Modules/_sqlite/connection.c32
4 files changed, 62 insertions, 6 deletions
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index efc74a6..d30e4d4 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -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
diff --git a/Lib/sqlite3/test/userfunctions.py b/Lib/sqlite3/test/userfunctions.py
index 4075045..9501f53 100644
--- a/Lib/sqlite3/test/userfunctions.py
+++ b/Lib/sqlite3/test/userfunctions.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):
diff --git a/Misc/NEWS.d/next/Library/2018-07-06-15-06-32.bpo-34041.0zrKLh.rst b/Misc/NEWS.d/next/Library/2018-07-06-15-06-32.bpo-34041.0zrKLh.rst
new file mode 100644
index 0000000..c41876b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-07-06-15-06-32.bpo-34041.0zrKLh.rst
@@ -0,0 +1,2 @@
+Add the parameter *deterministic* to the
+:meth:`sqlite3.Connection.create_function` method. Patch by Sergey Fedoseev.
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index ef2daeb..b8470df 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -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 */