快速OpenERP迁移框架
项目描述
此工具的开发考虑了以下初始目标,按照此优先级顺序
合并 2个不同的OpenERP数据库到一个多公司数据库
迁移 从OpenERP 6.1到7.0
从旧版业务应用(Access、Delphi等)迁移数据
从Dolibarr迁移到OpenERP
该工具的原理是从旧应用程序(目前仅限OpenERP)导出CSV数据,然后处理CSV文件以便导入到新安装的OpenERP数据库。这与OpenERP或OpenUpgrade的现场迁移策略完全不同,允许从干净的数据库重新开始,同时保留历史记录。导入和导出使用PostgreSQL特定的COPY命令进行,可实现极快的导出和导入。结合纯内存Python csv处理,该工具通常能够达到超过2000行/秒的整体迁移速率。
安装
此工具仅适用于Python 2.7!
使用virtualenv
$ virtualenv sandbox $ sandbox/bin/pip install anybox.migration.openerp $ sandbox/bin/migrate -h
使用Buildout
如果您使用Buildout,您可以在新的部分中添加此工具,例如
[migration] recipe = zc.recipe.egg eggs = anybox.migration.openerp
然后,别忘了在[buildout]部分的parts中添加migration部分。在重新启动bin/buildout后,migrate脚本将出现在buildout的bin目录中。
从OpenERP buildout配方的1.7版开始,您也可以直接在您的[openerp]部分中安装此工具,只需添加
eggs = anybox.migration.openerp scripts = migrate
使用方法
此工具提供单个migrate脚本
$ migrate -h
您可以列出可用的默认映射文件
$ migrate -l openerp6.1-openerp7.0.yml
您应指定源和目标数据库、要迁移的源表的选择以及要使用的映射文件。然后,该工具负责选择相关表
$ migrate -s source_dbname -t target_dbname -r res_partner account_move -p openerp6.1-openerp7.0.yml custom.yml
如果您想检查创建的临时CSV文件,请使用--keepcsv选项。它们将存储在当前目录下的临时目录中。
除非您指定-w选项以在末尾提交事务,否则此脚本实际上不会在目标数据库中写入任何内容。
迁移过程最重要的部分是YML映射文件,该文件描述了如何按表和列处理数据。提供了一个默认映射文件,并作为将两个6.1数据库迁移到单个7.0多公司数据库的实映射文件使用。您可以将提供的默认6.1到7.0文件与其它自定义yml文件混合,它们将被合并。
内部结构
此工具非常受以下灵感的启发
外部参照OpenERP模块
OpenUpgrade项目
Talend Open Studio
不同的内部步骤如下
从旧数据库导出CSV
将CSV转换为匹配目标数据库
使用区分符检测目标DB中存在的数据
后处理CSV文件以修复外键
重新注入到OpenERP
使用传入数据更新可能存在的现有数据
CSV文件的处理使用Yaml编写的映射文件。映射文件的初始版本是在OpenUpgrade分析文件的帮助下编写的。
映射文件
您应该记住,此迁移工具仅处理数据库表和列:它不知道OpenERP字段。每个表、每行、每个源数据库的单元格都独立处理,映射文件告诉当前单元格要做什么。这导致了限制,并且此工具无法处理极其复杂的迁移。但它足够强大,可以同时将两个6.1数据库合并并迁移到7.0多公司数据库。
对于实际例子,您可以查看此工具的mappings目录中提供的OpenERP 6.1到7.0映射文件。
复制数据
列映射的最简单和基本YML语句如下
module: table1.column1: table2.column2: __copy__
这表明,如果OpenERP 模块 安装在 目标 数据库中,则源DB的 table1 的 column1 应该复制到目标DB中 table2 的 column2。
甚至可以省略 __copy__ 指令,上述语句与以下语句等价
module: table1.column1: table2.column2:
实际上,此语句内部被转换为Python字典
{'module': {'table1.column1': {'table2.column2': '__copy__'}}
整个yml文件被转换为一个大映射字典,其叶子是能够处理数据的语句或函数。
复制表的所有列
如果您的目标表与源表结构相同,则可以避免为每个列指定映射语句,并使用通配符
module: table1.*:
这意味着:将源DB中table1的所有列复制到目标DB中的table1。此类映射通常在源和表结构相似时用作起点。然后您可以添加特定列的映射语句以覆盖此通配符。
复制所有列到不同的表
如果源表仅已重命名,则可以将源表table1的所有列复制到目标表table2
module: table1.*: table2.*:
复制所有内容
如果源数据库和目标数据库具有完全相同的结构,并且您只想迁移数据,则可以使用全局通配符(但我们还没有机会真正尝试这个功能)
module: .*:
这意味着:不进行任何处理,将所有表复制到目标数据库。与裸数据导出和恢复相比,这似乎没有用,但请记住,这种方式不仅可以替换数据,还可以向目标数据库追加数据。在这种情况下,您应该注意现有数据,如果表有约束(请参阅下面的判别器)
将一条源行拆分到多个表中(表拆分)
对于来自源表的单个源行,您可以向多个目标表提供数据。只需将多个目标行按如下方式放置即可完成此操作
module: table1.column1: table2.column2: table3.column3:
这意味着:对于源DB的 table1 中的每个 column1,创建两个目标行:一个用于 table2,另一个用于 table3。
在处理当前行期间,其他映射语句可以提供相同的目标行。以下是一个示例
module: table1.column1: table2.column2: table3.column3: table1.column2: table2.column2: table3.column4:
在这种情况下,table1 中的数据将被引导到 table2 和 table3。然后您可以添加更多行以处理 table1 的所有列
然而,在上面的示例中,存在冲突,因为两个源单元格被引导到同一目标单元格(table2.column2)。在这种情况下,无法预测将使用哪一个(因为映射是Python 字典,而字典是无序的)。您应该避免此类冲突。
在从OpenERP 6.1迁移到7.0的情况下,此类映射实际上用于将一个源 res_users 行迁移到三个不同的行:一个在 res_users 中,一个在 res_partner 中,一个在 mail_alias 中。请参阅默认映射以获取实际示例。
数据移动到另一个表中(表合并)
当输入行必须移动到不同的表时,您希望保持指向它们的引用键,以便迁移后它们指向新表,您应该使用 __moved__ 语句。
OpenERP中当前的唯一情况是 res_partner_address 数据移动到 res_partner 表
base: res_partner_address.id: res_partner.id: __moved__
此声明必须与指向移动表的全部外键的 __fk__ 声明一起使用(参见 __fk__ 章节)。
不迁移一列
如果您想删除表中的一列,请使用 __forget__ 声明
module: table1.column1: __forget__
当您定义了通配符,此声明可用来防止迁移特定的列。
使用Python代码转换数据
除了使用 __copy__ 声明复制数据外,您还可以使用任何Python代码。Python代码应写在字面Yaml块中,并作为函数体直接执行,因此您必须在某个地方插入一个 return 声明。
mail 模块的示例
mail: mail_message.type: mail_message.type: return 'email'
这意味着 mail_message 表的 type 列将被填充为 'email' 字符串,无论源列有什么数据。
使用Python代码块构建的函数的最终签名是
def mapping_function(self, source_row, target_rows):
这意味着在函数体内,您可以访问完整的 source_row,它是一个包含当前正在处理的行的所有键(列名)和值的字典。但请注意,在此时,您正在处理这一行的一个特定单元格,并且您应该返回将被插入目标表相应单元格中的值。这可以用来将两个源单元格的数据聚合到目标单元格中。
base: table1.firstname: __forget__ table1.name: table1.name: return source_row['firstname'] + ' ' + source_row['name']
您还可以访问正在填充的 target_rows,这样来自源单元格的数据就可以影响目标行中的多个单元格,甚至不同的目标表。以下是一个示例
base: table1.id: table1.id: table2.id: table1.name: table1.name: | name = source_row['firstname'] + ' ' + source_row['name'] target_rows['table1']['display_name'] = name target_rows['table2']['display_name'] = name return name table2.name
注意,在上面的示例中,Python代码跨越了多行,您应该使用 | 定义Yaml字面块。上面的示例最终意味着:将来自 table1 的 firstname 追加到 name,并将其放入目标 table1 和 table2 的 display_name 单元格中。目标 name 单元格将包含源 name 单元格的副本。
如果目标行不应该与源行有相同的 id,您可以使用 newid() 函数创建一个新的 id。这个函数在每次调用时都会返回不同的值,并负责增加 id。以下是一个示例
base: res_users.id: res_users.id: res_users.partner_id: res_partner.notification_email_send: return 'comment' res_partner.id: | i = newid() target_rows['res_users']['partner_id'] = i target_rows['res_partner']['id'] = i target_rows['res_partner']['name'] = source_row['name'] target_rows['res_partner']['email'] = source_row['user_email'] return i
每个 res_users 行将生成一个具有新 id 的 res_partner 行,而 res_users 的 id 将与源相同。(实际上不会相同,因为对所有 ids 都应用了一个偏移量)。
为新列提供数据
如果目标列应该包含数据但没有在源表中对应的列,您可以使用 ‘_’ 作为不存在源列名称的替代。
base: res_partner._: res_partner.is_company: return False
与现有数据合并
当数据插入目标表时,您可能希望将其与现有数据合并。
想象一下,目标 res_users 表已经包含一个 admin 账户,您不希望通过从源 res_users 表迁移数据来重复此账户。在这种情况下,您应该告诉映射如何识别现有数据。这是通过替换源列名称为 __discriminator__ 声明,并提供一个用于识别现有数据的列名称列表来完成的。
base: res_users.__discriminator__: - login
使用此语句,您可以安装一个新的OpenERP数据库及其管理员账户,并将所有现有账户与从源表来的数据合并。将使用登录列来匹配数据。现有的管理员账户不会重复,但会用源表中的管理员账户更新。
在多公司场景下的另一个用途是合并目标数据库中存在的合作伙伴,但保持两个公司的合作伙伴独立。
base: res_partner.__discriminator__: - name - company_id
无约束的外键
迁移的第一步是自动检测源表和目标表的所有外键。有时,OpenERP会在没有约束的情况下定义外键。这主要发生在具有store=True的相关字段上,它创建了一个没有约束的整数列。如果您不想__忘记__这样的列,您必须告诉映射外键的目标是什么,如下面的真实示例所示
account: account_move.company_id: account_move.company_id: __fk__ res_company
这是对crm_lead表的一个示例,该表可能包含来自一个__移动__表的字段。想象一下,您希望OpenERP 7.0中CRM线索的partner_id字段来自OpenERP 6.1中同一表的partner_address_id字段。新字段是到res_partner的外键,而旧的一个指向res_partner_address。您可以使用以下语句来告知这一点
crm_lead.partner_address_id: crm_lead.partner_id: __fk__ res_partner_address
但是,您也不应该忘记忘记partner_id字段,否则如果您为表使用了通配符,您将遇到冲突并混合数据
crm_lead.*: crm_lead.partner_id: __forget__
参考字段
有时列定义了到另一表的动态引用ID,就像外键一样,只是表的名称实际存储在另一个列中。
id |
model |
res_id |
---|---|---|
1 |
cr.claim |
23 |
2 |
cr.claim |
35 |
3 |
base.action.rule |
27 |
在上面的例子中,由于res_id不是一个真正的外键,其值不会固定以对应目标数据库。在这种情况下,您应该使用__ref__语句,后跟包含表或模型名称的列名。此语句假设OpenERP使用模型到表的转换(将“.”替换为“_”)
mail_message.res_id: mail_message.res_id: __ref__ model
处理循环依赖的表
在最后一步中,逐个导入迁移的CSV文件。一些表通过外键约束依赖于其他表,并且这种依赖有时会形成循环。在这种情况下,无法导入表,因为它们都依赖于另一个表。一种解决方案是__忘记__该列,这通常是不希望的,因为您会丢失数据。为了能够保留此类数据,您应该使用__defer__语句,这样列将在所有数据导入后更新
base: res_users.create_uid: res_users.create_uid: __defer__ res_users.write_uid: res_users.write_uid: __defer__
在迁移期间运行SQL请求
如果所需的迁移过于复杂,无法通过常规语句处理,则可以在源数据库和目标数据库上运行SQL查询。这应在有限的情况下使用,因为查询将针对映射定义的每个源单元格执行,迁移可能会减慢,除非您通过手动缓存来限制查询。(请参阅映射中的工作流程迁移)
映射文件中提供了一个简单的sql()函数,其签名如下
sql(db, query, args) where: - db is the string 'source' or 'target' - query is the SQL query - args is the arguments to insert in the query The query is actually executed with: cursor.execute(query, args)
以下是一个示例
base: res_users._: (...) mail_alias.alias_model_id: return sql('target', "select id from ir_model where model='res.users'")[0][0]
字段大小限制
在运行迁移时,您可能会遇到csv.Error: field larger than field limit错误。这是由于csv模块默认将csv字段大小限制为128k。默认值已增加到20MB。如果这对您的迁移还不够,您可以通过插入对csv.field_size_limit()的直接调用来增加限制。
例如
module: table1.column1: table2.column2: | import csv csv.field_size_limit(262144) return source_row['column1']
整体迁移过程
迁移需要以下几个步骤。如果您需要,可以轻松编写一个小脚本来自动化整个过程。
迁移前
迁移前的不同步骤如下。它们对于迁移的成功至关重要,应在目标数据库上执行。
使用最新的迁移代码创建一个干净的目标数据库,不包含演示数据。
安装预期的模块。
重命名目标公司,使其名称与源数据库中的公司名称完全一致。
通过运行以下SQL语句删除所有内部序列的公司: update ir_sequence set company_id=NULL;。这将允许在迁移后删除重复项。
迁移
迁移包括运行migrate脚本并选择正确的选项。如果目标数据库中的数据不是您预期的,您必须调整选项和映射文件以获得所需的结果。
这里有一个真实示例
../bin/migrate -s sourcedb -t targetdb -p openerp6.1-openerp7.0.yml custom.yml -r res_partner account_move res_users pos_order pos_order_line account_move_line account_journal sale_order_line stock_inventory_line account_tax product_supplierinfo wkf_instance wkf_workitem wkf_triggers -w
迁移后
仅migrate脚本可能不足以使您的数据库干净且可用。您可能需要处理额外的修正。请彻底测试您的实例!从版本0.6开始,您不需要手动修复内部序列,因为它们现在由映射文件处理。您只需将其清理以删除重复项(从菜单设置/技术/序列和标识符/序列)。然而,所需剩余的修正之一是删除一些parent_left和parent_right列。以下是以会计模块为例的示例
psql targetdb -c 'alter table account_account drop parent_left;' psql targetdb -c 'alter table account_account drop parent_right;'
您可能还需要强制重新计算数据库中持久化的新或更改的相关字段(store=True)。以下是以account_report_company模块为例的示例
psql targetdb -c 'alter table account_invoice drop commercial_partner_id;'
最后,您应该运行数据库的最终全局更新。如果您使用的是buildout配方,它应该看起来像这样
../bin/start_openerp -u all -d targetdb --stop-after-init
理解错误
使用此工具最困难的部分是在处理过程中理解错误,因为它需要深入了解其内部工作原理。大多数错误通常来自错误的映射文件。错误可能在处理CSV文件时发生,但最困难的是在最后导入步骤,因为某些表可能无法导入。在这种情况下,您应仔细查看日志消息,并尝试理解约束错误或为什么表无法导入。您还应使用--keepcsv选项,并检查中间CSV文件以了解问题。使用此选项,您将最终得到一个目录,其中包含每个表的五个CSV文件。
例如,对于res_partner表,您将找到以下文件
res_partner.csv是从源数据库导出的原始数据
res_partner.target.csv包含经过映射文件第一次处理后的数据,但外键错误
res_partner.target2.csv包含最终数据,外键已修复,最终将导入
res_partner.update.csv包含在目标数据库中检测到的现有数据,但外键错误。
res_partner.update2.csv包含最终现有数据,外键已修复,将在导入后用于更新目标表。
如果您在导入步骤中遇到外键错误而陷入困境,请查看此日志,因为它包含解决大多数常见问题的信息:https://bitbucket.org/anybox/anybox.migration.openerp/issue/3/foreign-key-constraints
贡献
代码仓库和错误跟踪器
请看这里:https://bitbucket.org/anybox/anybox.migration.openerp
请随时给我们反馈,报告错误或贡献映射文件到Bitbucket。
更改
0.9 (2013-12-31)
清理并添加缺失的表
许多映射改进、修复和清理
添加了人力资源迁移映射
0.8 (2013-09-30)
恢复了用户/公司分配的映射
改进了消息迁移
0.7 (2013-09-24)
修复了CRM迁移
迁移email_template
迁移email_message
支持使用__ref__语句的引用
修复并改进了映射
将默认的csv字段大小限制增加到20MB
0.6 (2013-08-24)
迁移ir_sequence无需使用后迁移脚本
修复了工作流实例和工作项迁移
数据库合并情况下的主要性能提升(3倍)
修复了由于外键区分符偏移错误引起的不必要的合并
打破一些依赖循环和其他映射改进
0.5 (2013-08-02)
修复了指向已存在数据的__moved__表的 外键
更新文档
0.4 (2013-07-28)
修复了潜在客户和采购订单的迁移
简化了__moved__语句处理
改进了工作流迁移
迁移员工和费用
默认将供应商设置为公司
如何在buildout中安装
更新文档
0.3 (2013-07-11)
针对6.1到7.0迁移的大量改进
修复了由于错误引用导致的导入错误
允许m2o到m2m迁移,无需自定义代码
添加了项目、CRM和auth_ldap模块的映射
修复了移动行
允许请求源数据库
改进了文档
运行工作流的迁移
0.2 (2013-07-01)
初始发布
项目详情
anybox.migration.openerp-0.9.tar.gz的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | ec356d2b81d0e32254fc718303b506f2eb9c840bc9e974ad99cd4514eaec83af |
|
MD5 | 8faabd52ebd197c444a6450b526f5aee |
|
BLAKE2b-256 | 4bf82f72c3f8cbbb496447c81d80b2b4dc2f4d9630e4077f8cebae78016706cd |