diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index f8ca1e6e7c..ab8d1c5d60 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -2,7 +2,9 @@ # this repository contains the full copyright notices and license terms. import datetime import logging +import math import os +import random import threading import time from decimal import Decimal @@ -230,6 +232,10 @@ class SQLiteTrim(Trim): return function + '(%s, %s)' % ( format(self.string), format(self.characters)) + @property + def params(self): + return [self.string, self.characters] + def sign(value): if value > 0: @@ -256,6 +262,18 @@ def least(*args): return None +def cbrt(value): + return math.pow(value, 1 / 3) + + +def div(a, b): + return a // b + + +def trunc(value, digits): + return math.trunc(value * 10 ** digits) / 10 ** digits + + MAPPING = { Extract: SQLiteExtract, Position: SQLitePosition, @@ -333,16 +351,40 @@ class Database(DatabaseInterface): self._conn.create_function('extract', 2, SQLiteExtract.extract) self._conn.create_function('date_trunc', 2, date_trunc) self._conn.create_function('split_part', 3, split_part) - self._conn.create_function('position', 2, SQLitePosition.position) self._conn.create_function('to_char', 2, to_char) - self._conn.create_function('overlay', 3, SQLiteOverlay.overlay) - self._conn.create_function('overlay', 4, SQLiteOverlay.overlay) if sqlite.sqlite_version_info < (3, 3, 14): self._conn.create_function('replace', 3, replace) self._conn.create_function('now', 0, now) - self._conn.create_function('sign', 1, sign) self._conn.create_function('greatest', -1, greatest) self._conn.create_function('least', -1, least) + + # Mathematical functions + self._conn.create_function('cbrt', 1, cbrt) + self._conn.create_function('ceil', 1, math.ceil) + self._conn.create_function('degrees', 1, math.degrees) + self._conn.create_function('div', 2, div) + self._conn.create_function('exp', 1, math.exp) + self._conn.create_function('floor', 1, math.floor) + self._conn.create_function('ln', 1, math.log) + self._conn.create_function('log', 1, math.log10) + self._conn.create_function('mod', 2, math.fmod) + self._conn.create_function('pi', 0, lambda: math.pi) + self._conn.create_function('power', 2, math.pow) + self._conn.create_function('radians', 1, math.radians) + self._conn.create_function('sign', 1, sign) + self._conn.create_function('sqrt', 1, math.sqrt) + self._conn.create_function('trunc', 1, math.trunc) + self._conn.create_function('trunc', 2, trunc) + + # Random functions + self._conn.create_function('random', 0, random.random) + self._conn.create_function('setseed', 1, random.seed) + + # String functions + self._conn.create_function('overlay', 3, SQLiteOverlay.overlay) + self._conn.create_function('overlay', 4, SQLiteOverlay.overlay) + self._conn.create_function('position', 2, SQLitePosition.position) + if (hasattr(self._conn, 'set_trace_callback') and logger.isEnabledFor(logging.DEBUG)): self._conn.set_trace_callback(logger.debug) diff --git a/trytond/tests/test_backend.py b/trytond/tests/test_backend.py index e770e29d79..1a46e2e578 100644 --- a/trytond/tests/test_backend.py +++ b/trytond/tests/test_backend.py @@ -1,9 +1,11 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime +import math import unittest from sql import Select +from sql import functions from sql.functions import CurrentTimestamp, ToChar from trytond.tests.test_tryton import activate_module, with_transaction @@ -84,6 +86,64 @@ class BackendTestCase(unittest.TestCase): self.assertEqual(text, today.strftime('%Y-%m-%d')) + @with_transaction() + def test_functions(self): + "Test functions" + cursor = Transaction().connection.cursor() + tests = [ + (functions.Abs(-1), 1), + (functions.Cbrt(27), 3), + (functions.Ceil(-42.8), -42), + (functions.Degrees(0.5), 28.6478897565412), + (functions.Div(9, 4), 2), + (functions.Exp(1.), math.e), + (functions.Floor(-42.8), -43), + (functions.Ln(2.), 0.693147180559945), + (functions.Log(100.0), 2), + (functions.Mod(9, 4), 1), + (functions.Pi(), math.pi), + (functions.Power(9, 3), 729), + (functions.Radians(45.), math.pi / 4), + (functions.Round(42.4), 42), + (functions.Round(42.4382, 2), 42.44), + (functions.Sign(-8.4), -1), + (functions.Sqrt(2.), 1.4142135623731), + (functions.Trunc(42.8), 42), + (functions.Trunc(42.4348, 2), 42.43), + (functions.CharLength('jose'), 4), + (functions.Lower('TOM'), 'tom'), + (functions.Overlay('Txxxxas', 'hom', 2, 4), 'Thomas'), + (functions.Position('om', 'Thomas'), 3), + (functions.Substring('Thomas', 2, 3), 'hom'), + # (functions.Substring('Thomas', '...$'), 'mas'), + # (functions.Substring('Thomas', '%#"o_a#"_', '#'), 'oma'), + (functions.Trim('yxTomxx', 'BOTH', 'xyz'), 'Tom'), + (functions.Upper('tom'), 'TOM'), + ] + for func, result in tests: + with self.subTest(func=str(func)): + cursor.execute(*Select([func])) + value, = cursor.fetchone() + if isinstance(result, str): + self.assertEqual(value, result) + else: + self.assertAlmostEqual(float(value), float(result)) + + @with_transaction() + def test_function_random(self): + "Test RANDOM function" + cursor = Transaction().connection.cursor() + cursor.execute(*Select([functions.Random()])) + value, = cursor.fetchone() + self.assertGreaterEqual(value, 0) + self.assertLessEqual(value, 1) + + @with_transaction() + def test_function_setseed(self): + "Test SETSEED function" + cursor = Transaction().connection.cursor() + cursor.execute(*Select([functions.SetSeed(1)])) + def suite(): return unittest.TestLoader().loadTestsFromTestCase(BackendTestCase)