Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions trytond/trytond/backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def setnextid(self, connection, table, value):
def currid(self, connection, table):
pass

def estimated_count(self, connection, table):
raise NotImplementedError

@classmethod
def lock(cls, connection, table):
raise NotImplementedError
Expand Down
12 changes: 12 additions & 0 deletions trytond/trytond/backend/postgresql/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from psycopg2 import QueryCanceledError as DatabaseTimeoutError
from psycopg2.extras import register_default_json, register_default_jsonb
from sql import Cast, Flavor, For, Table
from sql.aggregate import Count
from sql.conditionals import Coalesce
from sql.functions import Function
from sql.operators import BinaryOperator, Concat
Expand Down Expand Up @@ -498,6 +499,17 @@ def currid(self, connection, table):
cursor.execute(f"SELECT last_value FROM {sequence_name}")
return cursor.fetchone()[0]

def estimated_count(self, connection, table):
cursor = connection.cursor()
if isinstance(table, Table):
cursor.execute(
'SELECT n_live_tup FROM pg_stat_all_tables '
'WHERE relname = %s',
(table._name,))
else:
cursor.execute(*table.select(Count()))
return cursor.fetchone()[0]

def lock(self, connection, table):
cursor = connection.cursor()
cursor.execute(SQL('LOCK {} IN EXCLUSIVE MODE NOWAIT').format(
Expand Down
6 changes: 6 additions & 0 deletions trytond/trytond/backend/sqlite/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from weakref import WeakKeyDictionary

from sql import Expression, Flavor, Literal, Null, Query, Table
from sql.aggregate import Count
from sql.conditionals import NullIf
from sql.functions import (
CharLength, CurrentTimestamp, Extract, Function, Overlay, Position,
Expand Down Expand Up @@ -605,6 +606,11 @@ def lastid(self, cursor):
# This call is not thread safe
return cursor.lastrowid

def estimated_count(self, connection, table):
cursor = connection.cursor()
cursor.execute(*table.select(Count()))
return cursor.fetchone()[0]

def lock(self, connection, table):
pass

Expand Down
6 changes: 6 additions & 0 deletions trytond/trytond/model/modelsql.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,12 @@ def __raise_data_error(
raise SizeValidationError(
gettext('ir.msg_size_validation', **error_args))

@classmethod
def _get_estimated_count(cls):
transaction = Transaction()
return transaction.database.estimated_count(
transaction.connection, cls.__table__())

@classmethod
def history_revisions(cls, ids):
pool = Pool()
Expand Down
6 changes: 5 additions & 1 deletion trytond/trytond/model/modelstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,10 +633,14 @@ def estimated_count(cls):
"Returns the estimation of the number of records."
count = cls._count_cache.get(cls.__name__)
if count is None:
count = cls.search([], count=True)
count = cls._get_estimated_count()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoe c'est normal qu'on perde le cache ici ?

Copy link
Collaborator Author

@nicoe nicoe Apr 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, bien vu !

cls._count_cache.set(cls.__name__, count)
return count

@classmethod
def _get_estimated_count(cls):
return cls.search([], count=True)

def resources(self):
pool = Pool()
Attachment = pool.get('ir.attachment')
Expand Down
12 changes: 12 additions & 0 deletions trytond/trytond/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sql import Literal, Select, functions
from sql.functions import CurrentTimestamp, DateTrunc, ToChar

from trytond.pool import Pool
from trytond.tests.test_tryton import activate_module, with_transaction
from trytond.transaction import Transaction

Expand Down Expand Up @@ -171,3 +172,14 @@ def test_function_date_trunc(self):
cursor.execute(*Select([DateTrunc(type_, date)]))
value, = cursor.fetchone()
self.assertEqual(str(value), str(result))

@with_transaction()
def test_estimated_count(self):
"Test estimated count queries"
pool = Pool()
database = Transaction().database
connection = Transaction().connection

ModelSQLRead = pool.get('test.modelsql.read')
count = database.estimated_count(connection, ModelSQLRead.__table__())
self.assertGreaterEqual(count, 0)
9 changes: 6 additions & 3 deletions trytond/trytond/tests/test_modelsql.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,9 +1110,12 @@ def test_search_limit(self):

Model.create([{'name': str(i)} for i in range(10)])

self.assertEqual(Model.search([], limit=5, count=True), 5)
self.assertEqual(Model.search([], limit=20, count=True), 10)
self.assertEqual(Model.search([], limit=None, count=True), 10)
# The stats of postgres might not be updated yet
with patch.object(Model, 'estimated_count') as ec:
ec.return_value = 10
self.assertEqual(Model.search([], limit=5, count=True), 5)
self.assertEqual(Model.search([], limit=20, count=True), 10)
self.assertEqual(Model.search([], limit=None, count=True), 10)

@with_transaction()
def test_search_offset(self):
Expand Down