odoo序列standard模式其实是pg数据库sequence特性的无代码应用,分别在sql层封装了db序列的创建、删除、修改、查询和预测下一号码的基础能力。然后再根据实施灵活性增强了前后缀、长度不足补0、每个日期范围使用不同序列的层级关系等扩展功能。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | def _create_sequence(cr, seq_name, number_increment, number_next):     """ Create a PostreSQL sequence. """     if number_increment == 0:         raise UserError(_('Step must not be zero.'))     sql = "CREATE SEQUENCE %s INCREMENT BY %%s START WITH %%s" % seq_name     cr.execute(sql, (number_increment, number_next)) def _drop_sequences(cr, seq_names):     """ Drop the PostreSQL sequences if they exist. """     names = sql.SQL(',').join(map(sql.Identifier, seq_names))     # RESTRICT is the default; it prevents dropping the sequence if an     # object depends on it.     cr.execute(sql.SQL("DROP SEQUENCE IF EXISTS {} RESTRICT").format(names)) def _alter_sequence(cr, seq_name, number_increment=None, number_next=None):     """ Alter a PostreSQL sequence. """     if number_increment == 0:         raise UserError(_("Step must not be zero."))     cr.execute("SELECT relname FROM pg_class WHERE relkind=%s AND relname=%s", ('S', seq_name))     if not cr.fetchone():         # sequence is not created yet, we're inside create() so ignore it, will be set later         return     statement = sql.SQL("ALTER SEQUENCE") + sql.Identifier(seq_name)     params = []     if number_increment is not None:         statement += sql.SQL("INCREMENT BY") + sql.Placeholder()         params.append(number_increment)     if number_next is not None:         statement += sql.SQL("RESTART WITH") + sql.Placeholder()         params.append(number_next)     cr.execute(statement.join(' '), params) def _select_nextval(cr, seq_name):     cr.execute("SELECT nextval(%s)", [seq_name])     return cr.fetchone() def _predict_nextval(self, seq_id):     """Predict next value for PostgreSQL sequence without consuming it"""     # Cannot use currval() as it requires prior call to nextval()     seqname = 'ir_sequence_%s' % seq_id     seqtable = sql.Identifier(seqname)     query = sql.SQL("""SELECT last_value,                       (SELECT increment_by                        FROM pg_sequences                        WHERE sequencename = %s),                       is_called                FROM {}""")     params = [seqname]     if self.env.cr._cnx.server_version < 100000:         query = sql.SQL("SELECT last_value, increment_by, is_called FROM {}")         params = []     self.env.cr.execute(query.format(seqtable), params)     (last_value, increment_by, is_called) = self.env.cr.fetchone()     if is_called:         return last_value + increment_by     # sequence has just been RESTARTed to return last_value next time     return last_value | 
odoo同时也额外用select for update nowait数据锁能力来互补实现了一套无间隔的no_gap模式,用来弥补纯sequence在欧度无处不在的事务应用中不会被连带回滚的特性,牺牲一些性能换取序列绝对连号用于某些特殊高要求的场景。如业财一体化应用中的会计分录凭证编号。
| 1 2 3 4 5 6 | def _update_nogap(self, number_increment):     number_next = self.number_next     self._cr.execute("SELECT number_next FROM %s WHERE id=%%s FOR UPDATE NOWAIT" % self._table, [self.id])     self._cr.execute("UPDATE %s SET number_next=number_next+%%s WHERE id=%%s " % self._table, (number_increment, self.id))     self.invalidate_cache(['number_next'], [self.id])     return number_next | 
