]> granicus.if.org Git - python/commitdiff
bpo-34041: Allow creating deterministic functions in Connection.create_function(...
authorSergey Fedoseev <fedoseev.sergey@gmail.com>
Sun, 8 Jul 2018 07:09:20 +0000 (12:09 +0500)
committerBerker Peksag <berker.peksag@gmail.com>
Sun, 8 Jul 2018 07:09:20 +0000 (10:09 +0300)
Doc/library/sqlite3.rst
Lib/sqlite3/test/userfunctions.py
Misc/NEWS.d/next/Library/2018-07-06-15-06-32.bpo-34041.0zrKLh.rst [new file with mode: 0644]
Modules/_sqlite/connection.c

index efc74a6bab8ca567683f94abc63b98440e11eb93..d30e4d4157996d7141528ffb3a697607c86fdbca 100644 (file)
@@ -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
index 4075045b727e892908eec76f95bbad6f685adf52..9501f535c499996ee62d2294794e923ac2f66f27 100644 (file)
@@ -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 (file)
index 0000000..c41876b
--- /dev/null
@@ -0,0 +1,2 @@
+Add the parameter *deterministic* to the
+:meth:`sqlite3.Connection.create_function` method. Patch by Sergey Fedoseev.
index ef2daeb0c25763ea2261871a7dc1ce2ba52af662..b8470df7fb8ffd874f1bd47e9ac3e71628e78ce7 100644 (file)
@@ -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 */