details: https://code.tryton.org/tryton/commit/e42c4fc0b292
branch: default
user: Cédric Krier <[email protected]>
date: Mon Apr 13 12:08:16 2026 +0200
description:
Add support for AGE to SQLite backend
Closes #14763
diffstat:
trytond/CHANGELOG | 1 +
trytond/trytond/backend/sqlite/database.py | 26 ++++++++++++++++++
trytond/trytond/tests/test_backend.py | 43 +++++++++++++++++++++++++++++-
3 files changed, 69 insertions(+), 1 deletions(-)
diffs (111 lines):
diff -r c9d9e966e454 -r e42c4fc0b292 trytond/CHANGELOG
--- a/trytond/CHANGELOG Fri Apr 03 18:17:54 2026 +0200
+++ b/trytond/CHANGELOG Mon Apr 13 12:08:16 2026 +0200
@@ -1,3 +1,4 @@
+* Add support for AGE to SQLite backend
* Add button to mark notification as read
Version 8.0.0 - 2026-04-20
diff -r c9d9e966e454 -r e42c4fc0b292 trytond/trytond/backend/sqlite/database.py
--- a/trytond/trytond/backend/sqlite/database.py Fri Apr 03 18:17:54
2026 +0200
+++ b/trytond/trytond/backend/sqlite/database.py Mon Apr 13 12:08:16
2026 +0200
@@ -218,6 +218,30 @@
return adapt_timedelta(value)
+def age(timestamp1, timestamp2=None):
+ def convert(value):
+ for fromisoformat in [
+ dt.date.fromisoformat,
+ dt.datetime.fromisoformat,
+ ]:
+ try:
+ value = fromisoformat(value)
+ except ValueError:
+ continue
+ break
+ else:
+ raise ValueError
+ if not isinstance(value, dt.datetime):
+ value = dt.datetime.combine(value, dt.time())
+ return value
+ timestamp1 = convert(timestamp1)
+ if timestamp2 is None:
+ timestamp2 = dt.datetime.combine(dt.date.today(), dt.time())
+ else:
+ timestamp1, timestamp2 = convert(timestamp2), timestamp1
+ return adapt_timedelta(timestamp2 - timestamp1)
+
+
def split_part(text, delimiter, count):
if text is None:
return None
@@ -473,6 +497,8 @@
factory=SQLiteConnection)
self._conn.create_function('extract', 2, SQLiteExtract.extract)
self._conn.create_function('date_trunc', 2, date_trunc)
+ self._conn.create_function('age', 1, age)
+ self._conn.create_function('age', 2, age)
self._conn.create_function('split_part', 3, split_part)
self._conn.create_function('to_char', 2, to_char)
self._conn.create_function('now', 0, now)
diff -r c9d9e966e454 -r e42c4fc0b292 trytond/trytond/tests/test_backend.py
--- a/trytond/trytond/tests/test_backend.py Fri Apr 03 18:17:54 2026 +0200
+++ b/trytond/trytond/tests/test_backend.py Mon Apr 13 12:08:16 2026 +0200
@@ -5,7 +5,7 @@
from decimal import Decimal
from sql import Cast, Literal, Select, Table, functions
-from sql.functions import CurrentTimestamp, DateTrunc, Extract, ToChar
+from sql.functions import Age, CurrentTimestamp, DateTrunc, Extract, ToChar
from trytond import backend
from trytond.model import fields
@@ -270,6 +270,47 @@
self.assertEqual(value, None)
@with_transaction()
+ def test_function_age(self):
+ "Test Age function"
+ cursor = Transaction().connection.cursor()
+
+ for t1, t2, result in [
+ (dt.date(2026, 4, 13), dt.date(2026, 4, 1),
+ dt.timedelta(days=12)),
+ (dt.date(2026, 4, 13), dt.datetime(2026, 4, 1, 12, 0),
+ dt.timedelta(days=11, hours=12)),
+ (dt.datetime(2026, 4, 13, 12, 0), dt.date(2026, 4, 1),
+ dt.timedelta(days=12, hours=12)),
+ (dt.datetime(2026, 4, 13, 12, 0),
+ dt.datetime(2026, 4, 1, 12, 0),
+ dt.timedelta(days=12)),
+ ]:
+ for t1, t2, result in [
+ (t1, t2, result),
+ (t2, t1, -result),
+ ]:
+ with self.subTest(t1=t1, t2=t2):
+ query = Select([Age(t1, t2).as_('age')])
+ if backend.name == 'sqlite':
+ sqlite_apply_types(query, ['INTERVAL'])
+ cursor.execute(*query)
+ value, = cursor.fetchone()
+ self.assertEqual(value, result)
+
+ @with_transaction()
+ def test_function_age_current_date(self):
+ "Test Age function with current_date"
+ cursor = Transaction().connection.cursor()
+
+ today = dt.date.today()
+ query = Select([Age(today - dt.timedelta(days=10)).as_('age')])
+ if backend.name == 'sqlite':
+ sqlite_apply_types(query, ['INTERVAL'])
+ cursor.execute(*query)
+ value, = cursor.fetchone()
+ self.assertEqual(value, dt.timedelta(days=10))
+
+ @with_transaction()
def test_operator_range_contain(self):
"Test Range Contain operator"
cursor = Transaction().connection.cursor()