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 |