Support getting many sequences at once

Closes #13474
This commit is contained in:
Cédric Krier
2024-08-25 11:02:42 +02:00
parent 546247b635
commit b595b68b79
7 changed files with 98 additions and 45 deletions
+1
View File
@@ -1,3 +1,4 @@
* Support getting many sequences at once
* Add sequence_nextval function to Database
* Retrieve groups of actions without checking access (#13506)
* Check read access of report records (#13505)
+4
View File
@@ -192,6 +192,10 @@ Database
Advances the named sequence and returns its next value.
.. method:: Database.sequence_nextvals(connection, name, n)
Advances the named sequence n times and yields the next values.
.. method:: Database.sequence_next_number(connection, name)
Return the next number fo the named sequence using the ``connection``.
+5
View File
@@ -161,6 +161,11 @@ class DatabaseInterface(object):
return
raise NotImplementedError
def sequence_nextvals(self, connection, name, n):
if not self.has_sequence():
return
raise NotImplementedError
def sequence_next_number(self, connection, name):
if not self.has_sequence():
return
@@ -738,6 +738,13 @@ class Database(DatabaseInterface):
cursor.execute('SELECT NEXTVAL(%s)', (name,))
return cursor.fetchone()[0]
def sequence_nextvals(self, connection, name, n):
cursor = connection.cursor()
cursor.execute(
'SELECT NEXTVAL(%s) FROM generate_series(1, %s)', (name, n))
for val, in cursor:
yield val
def sequence_next_number(self, connection, name):
cursor = connection.cursor()
version = self.get_version(connection)
-3
View File
@@ -285,9 +285,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="text">The condition "%(condition)s" is not a valid PYSON expression for button rule "%(rule)s".</field>
</record>
<record model="ir.message" id="msg_sequence_missing">
<field name="text">Missing sequence.</field>
</record>
<record model="ir.message" id="msg_sequence_invalid_prefix">
<field name="text">Invalid prefix "%(affix)s" for sequence "%(sequence)s".</field>
</record>
+42 -42
View File
@@ -380,37 +380,40 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return int(
(time.time() - self.timestamp_offset) / self.timestamp_rounding)
@classmethod
def _get_sequence(cls, sequence):
if sequence.type == 'incremental':
if sql_sequence and not cls._strict:
def _get_many(self, n=1):
if self.type == 'incremental':
if sql_sequence and not self._strict:
transaction = Transaction()
number_next = transaction.database.sequence_nextval(
transaction.connection, sequence._sql_sequence_name)
numbers = transaction.database.sequence_nextvals(
transaction.connection, self._sql_sequence_name, n)
# clean cache
transaction.counter += 1
sequence._local_cache.pop(sequence.id, None)
self._local_cache.pop(self.id, None)
else:
# Pre-fetch number_next
number_next = sequence.number_next_internal
cls.write([sequence], {
'number_next_internal': (number_next
+ sequence.number_increment),
})
return f'{number_next:0>{sequence.padding}d}'
elif sequence.type in ('decimal timestamp', 'hexadecimal timestamp'):
timestamp = sequence.last_timestamp
while timestamp == sequence.last_timestamp:
timestamp = sequence._timestamp()
cls.write([sequence], {
'last_timestamp': timestamp,
})
if sequence.type == 'decimal timestamp':
return f'{timestamp:d}'
# pre-fetch number_next
start = self.number_next_internal
end = start + n * self.number_increment
numbers = list(range(start, end, self.number_increment))
self.number_next_internal = end
self.save()
for number in numbers:
yield f'{number:0>{self.padding}d}'
elif self.type in {'decimal timestamp', 'hexadecimal timestamp'}:
timestamps = []
last_timestamp = timestamp = self.last_timestamp
for _ in range(n):
while timestamp == last_timestamp:
timestamp = self._timestamp()
timestamps.append(timestamp)
last_timestamp = timestamp
self.last_timestamp = last_timestamp
self.save()
if self.type == 'decimal timestamp':
for timestamp in timestamps:
yield f'{timestamp:d}'
else:
return hex(timestamp)[2:].upper()
return ''
for timestamp in timestamps:
yield hex(timestamp)[2:].upper()
@fields.depends('type', 'padding', 'number_next', methods=['_timestamp'])
def _get_preview_sequence(self):
@@ -426,23 +429,20 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
return hex(timestamp)[2:].upper()
return ''
def get(self):
"Returns the next sequence value"
return next(self.get_many(n=1))
@without_check_access
def get(self, _lock=False):
'''
Return the next sequence value
'''
cls = self.__class__
try:
sequence = cls(self.id)
except TypeError:
raise MissingError(gettext('ir.msg_sequence_missing'))
def get_many(self, n=1, _lock=False):
"Yield the n next sequence values"
assert n > 0, n
if _lock:
self.lock()
return '%s%s%s' % (
cls._process(sequence.prefix),
cls._get_sequence(sequence),
cls._process(sequence.suffix),
)
for value in self._get_many(n=n):
prefix = self._process(self.prefix)
suffix = self._process(self.suffix)
yield f'{prefix}{value}{suffix}'
@fields.depends('prefix', 'suffix', methods=['_get_preview_sequence'])
def on_change_with_preview(self, name=None):
@@ -459,5 +459,5 @@ class SequenceStrict(Sequence):
_table = None # Needed to reset Sequence._table
_strict = True
def get(self, _lock=True):
return super().get(_lock=True)
def get_many(self, n=1, _lock=True):
yield from super().get_many(n=n, _lock=True)
+39
View File
@@ -168,6 +168,45 @@ class IrTestCase(ModuleTestCase):
sequence.prefix = '${date_K}'
sequence.save()
@with_transaction()
def test_sequence_get(self):
"Test get sequences"
pool = Pool()
Sequence = pool.get('ir.sequence')
SequenceType = pool.get('ir.sequence.type')
try:
Group = pool.get('res.group')
groups = Group.search([])
except KeyError:
groups = []
sequence_type = SequenceType(name='Test', groups=groups)
sequence_type.save()
sequence = Sequence(name='Test Sequence', sequence_type=sequence_type)
sequence.save()
self.assertEqual(sequence.get(), '1')
@with_transaction()
def test_sequence_get_many(self):
"Test get many sequences"
pool = Pool()
Sequence = pool.get('ir.sequence')
SequenceType = pool.get('ir.sequence.type')
try:
Group = pool.get('res.group')
groups = Group.search([])
except KeyError:
groups = []
sequence_type = SequenceType(name='Test', groups=groups)
sequence_type.save()
sequence = Sequence(name='Test Sequence', sequence_type=sequence_type)
sequence.save()
self.assertEqual(
list(sequence.get_many(10)), list(map(str, range(1, 11))))
@with_transaction()
def test_global_search(self):
'Test Global Search'