我的rails项目从sqlserver到postgres迁移之路
2013-04-18 10:28
113 查看
近期想把我们的某些rails项目从sqlserver迁移到postgres,由于我们的rails项目里夹杂很多的find_by_sql,而sqlserver与postgreSQL里很多语法/方法不同,需要sql的翻译处理。没有找到特别合适的插件做这件事情,只好自己动手。
1.导数据
利用Navicat for PostgreSQL将数据从sqlserver拷贝到postgreSQL
由于我们已有数据,所以设置sequence时要指定start with n,其中n = max(id) + 1 。
特别要注意的是postgreSQL表名、字段名、序列名等最好不要用大写;序列名大写时会报错: relation "sequence_name" does not exist。
还有一点需要ruby程序员注意的是postgreSQL中的单双引号与ruby中单双引号意义完全不同,一不小心容易出错。
由于项目表数目太多,我写了一段ruby代码来完成这件事情(第二步和第三步),没有优化,先将就用着,至于怎么调用,不用多说了吧,哈哈:
[:col_name] unless all_primary_keys.blank?
fixed_get_assumed_pk(table)
#{:col_name => pk_record.colname, :type => pk_record.typename, :pk_name => pk_record.pk_name}
end
#如果已有主键,会出错
def fixed_set_primary_key(table)
assumed_pk = fixed_get_assumed_pk(table)
return false if assumed_pk.nil?
sql = "alter table \"#{table}\" add primary key (\"#{assumed_pk}\") "
Article.find_by_sql(sql)
assumed_pk
end
#猜测可能会成为主键的名字
def fixed_get_assumed_pk(table)
sql = "select table_schema,table_name,column_name,data_type,column_default,is_nullable
from information_schema.columns where table_name = '#{table}' "
columns = Article.find_by_sql(sql).map{|col| [col.column_name, col.data_type]}
possible_pk_cols = ["id","#{table}ID","#{table}_id"]
possible_model_name = table.singularize
possible_pk_cols << "#{possible_model_name}_id" << "#{possible_model_name}ID"
possible_pk_cols.uniq!
possible_pk_cols.each do |possible_pk_col|
return possible_pk_col if fixed_test_column_is_pk?(possible_pk_col,columns)
end
nil
end
def fixed_test_column_is_pk?(test_col,columns)
columns.select{|col| col[0] == test_col && col[1].downcase == "integer"}.size > 0
end
def fixed_has_primary_key?(table)
!Common.all_primary_keys.blank?
end
def fixed_all_table_names
system_tables = ["sysdiagrams", #sqlserver系统表
"othertables"]
special_tables = [] #特殊表,或者不适用/不想加入本次处理的表
sql = "SELECT
pg_catalog.pg_relation_filenode(c.oid) as \"Filenode\",
relname as \"TableName\"
FROM
pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_database d ON d.datname = 'postgres'
WHERE
relkind IN ('r')
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
AND n.nspname !~ '^pg_toast'
ORDER BY
relname"
Article.find_by_sql(sql).map(&:TableName) - system_tables - special_tables
end
#{table_name => {col_name,type,pk_name}}
def all_primary_keys
sql = "select pg_constraint.conname as pk_name,pg_class.relname as table_name,pg_attribute.attname as colname,pg_type.typname as typename from
pg_constraint inner join pg_class
on pg_constraint.conrelid = pg_class.oid
inner join pg_attribute on pg_attribute.attrelid = pg_class.oid
and pg_attribute.attnum = pg_constraint.conkey[1]
inner join pg_type on pg_type.oid = pg_attribute.atttypid
where pg_constraint.contype='p'
--and pg_class.relname = 'yourtablename' "
res = {}
pk_records = Article.find_by_sql(sql)
pk_records.each do |pk_record|
res[pk_record.table_name] = {:col_name => pk_record.colname, :type => pk_record.typename, :pk_name => pk_record.pk_name}
end
res
end
end
end
end
一. 环境:
rails版本:3.0.7; ruby版本:1.8.7;二. 主要步骤和方法:
1.导数据
利用Navicat for PostgreSQL将数据从sqlserver拷贝到postgreSQL2.导完数据发现表无id,添加id
添加id有各种方法,你都可以尝试。当然你也可以用我在下面写的ruby代码。3.添加id后发现id不自增长,为id添加sequence。
具体sql操作方法参可以参考http://tian-wang.iteye.com/blog/1051227。由于我们已有数据,所以设置sequence时要指定start with n,其中n = max(id) + 1 。
特别要注意的是postgreSQL表名、字段名、序列名等最好不要用大写;序列名大写时会报错: relation "sequence_name" does not exist。
还有一点需要ruby程序员注意的是postgreSQL中的单双引号与ruby中单双引号意义完全不同,一不小心容易出错。
由于项目表数目太多,我写了一段ruby代码来完成这件事情(第二步和第三步),没有优化,先将就用着,至于怎么调用,不用多说了吧,哈哈:
module CommonExtensions module OneTimeUsedForMigrate extend ActiveSupport::Concern #注意:postgreSQL的单双引号 和 ruby的单双引号不同,要特别注意 #postgreSQL表名、字段名、序列名等最好不要用大写 #序列名大写时会报错:ActiveRecord::StatementInvalid: PG::Error: ERROR: relation "tables_id_seq" does not exist module ClassMethods def fixed_for_pk tables = fixed_all_table_names Common.fixed_deal_with_primary_keys(tables) end def fixed_deal_with_primary_keys(tables) tables.each do |table| if fixed_has_primary_key?(table) pk = fixed_get_pk_name(table) else pk = fixed_set_primary_key(table) end if pk sequence_name = fixed_add_sequence(table, pk, true, true) fixed_associate_pk_and_sequence(table, pk, sequence_name) end end end def fixed_add_sequence(table,pk,reset_seq=false,force=false) sequence_name = fixed_get_pk_sequence_name(table,pk) if reset_seq #强制删除主键上的sequence? begin delete_sql = "DROP SEQUENCE \"#{sequence_name}\" CASCADE " Article.find_by_sql(delete_sql) rescue end end if force #删除主键为空的列 delete_null_id_sql = "delete from \"public\".\"#{table}\" where \"#{pk}\" is null" Article.find_by_sql(delete_null_id_sql) end #设置主键从多少开始自增长 base_num = fixed_get_sequence_based_num(table,pk) add_sql = "CREATE SEQUENCE \"#{sequence_name}\" START WITH :base_num INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 " Article.find_by_sql([add_sql, {:base_num => base_num}]) sequence_name end #序列从?开始增长? def fixed_get_sequence_based_num(table,pk) base_num_record = Article.find_by_sql("select max(\"#{pk}\") as max_num from \"#{table}\"").first base_num = base_num_record ? (base_num_record.max_num.to_i + 1) : 1 end def fixed_associate_pk_and_sequence(table,pk,sequence_name) sql = "alter table \"#{table}\" alter column \"#{pk}\" set default nextval('#{sequence_name}') " Article.find_by_sql(sql) true end #生成序列名 def fixed_get_pk_sequence_name(table,pk = false) adaptive_pk = pk || fixed_get_pk_name(table) "#{table}_#{adaptive_pk}_seq".downcase end #获取主键所在的列的名字(并不是主键名) def fixed_get_pk_name(table) return all_primary_keys