Skip to content

Commit

Permalink
Add support for Sqlite deterministic functions.
Browse files Browse the repository at this point in the history
This requires Sqlite 3.20 or newer and the stdlib sqlite3 for python 3.8
or newer. pysqlite3 also implements support for this flag for some time,
so should be safe for most people to start adopting.

Fixes #2782
  • Loading branch information
coleifer committed Sep 22, 2023
1 parent ade292f commit 6f27e10
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 10 deletions.
12 changes: 10 additions & 2 deletions docs/peewee/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -732,23 +732,31 @@ Database
extra attribute provides a shorthand way to generate the SQL necessary
to use our custom collation.

.. py:method:: register_function(fn[, name=None[, num_params=-1]])
.. py:method:: register_function(fn[, name=None[, num_params=-1[, deterministic=None]]])
:param fn: The user-defined scalar function.
:param str name: Name of function (defaults to function name)
:param int num_params: Number of arguments the function accepts, or
-1 for any number.
:param bool deterministic: Whether the function is deterministic for a
given input (this is required to use the function in an index).
Requires Sqlite 3.20 or newer, and ``sqlite3`` driver support
(added to stdlib in Python 3.8).

Register a user-defined scalar function. The function will be
registered each time a new connection is opened. Additionally, if a
connection is already open, the function will be registered with the
open connection.

.. py:method:: func([name=None[, num_params=-1]])
.. py:method:: func([name=None[, num_params=-1[, deterministic=None]]])
:param str name: Name of the function (defaults to function name).
:param int num_params: Number of parameters the function accepts,
or -1 for any number.
:param bool deterministic: Whether the function is deterministic for a
given input (this is required to use the function in an index).
Requires Sqlite 3.20 or newer, and ``sqlite3`` driver support
(added to stdlib in Python 3.8).

Decorator to register a user-defined scalar function.

Expand Down
14 changes: 8 additions & 6 deletions peewee.py
Original file line number Diff line number Diff line change
Expand Up @@ -3624,8 +3624,9 @@ def _load_collations(self, conn):
conn.create_collation(name, fn)

def _load_functions(self, conn):
for name, (fn, num_params) in self._functions.items():
conn.create_function(name, num_params, fn)
for name, (fn, n_params, deterministic) in self._functions.items():
args = (deterministic,) if deterministic else ()
conn.create_function(name, n_params, fn, *args)

def _load_window_functions(self, conn):
for name, (klass, num_params) in self._window_functions.items():
Expand Down Expand Up @@ -3658,14 +3659,15 @@ def decorator(fn):
return fn
return decorator

def register_function(self, fn, name=None, num_params=-1):
self._functions[name or fn.__name__] = (fn, num_params)
def register_function(self, fn, name=None, num_params=-1,
deterministic=None):
self._functions[name or fn.__name__] = (fn, num_params, deterministic)
if not self.is_closed():
self._load_functions(self.connection())

def func(self, name=None, num_params=-1):
def func(self, name=None, num_params=-1, deterministic=None):
def decorator(fn):
self.register_function(fn, name, num_params)
self.register_function(fn, name, num_params, deterministic)
return fn
return decorator

Expand Down
5 changes: 3 additions & 2 deletions playhouse/apsw_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ def _load_collations(self, conn):
conn.createcollation(name, fn)

def _load_functions(self, conn):
for name, (fn, num_params) in self._functions.items():
conn.createscalarfunction(name, fn, num_params)
for name, (fn, num_params, deterministic) in self._functions.items():
args = (deterministic,) if deterministic else ()
conn.createscalarfunction(name, fn, num_params, *args)

def _load_extensions(self, conn):
conn.enableloadextension(True)
Expand Down
26 changes: 26 additions & 0 deletions tests/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2616,3 +2616,29 @@ def test_fk_set_correctly(self):
tweet = Tweet(user=user, content='t1')
user.save()
tweet.save()


@skip_unless(database.server_version >= (3, 20, 0), 'sqlite deterministic requires >= 3.20')
@skip_unless(sys.version_info <= (3, 8, 0), 'sqlite deterministic requires Python >= 3.8')
class TestDeterministicFunction(ModelTestCase):
database = get_in_memory_db()

def test_deterministic(self):
db = self.database
@db.func(deterministic=True)
def pylower(s):
if s is not None:
return s.lower()

class Reg(db.Model):
key = TextField()
class Meta:
indexes = [
SQL('create unique index "reg_pylower_key" '
'on "reg" (pylower("key"))')]

db.create_tables([Reg])
Reg.create(key='k1')
with self.assertRaises(IntegrityError):
with db.atomic():
Reg.create(key='K1')

0 comments on commit 6f27e10

Please sign in to comment.