@@ -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)
|
||||
|
||||
@@ -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``.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user