mysql2postgres 0.3.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +9 -4
- data/Rakefile +1 -1
- data/bin/mysql2postgres +6 -14
- data/lib/mysql2postgres/connection.rb +19 -19
- data/lib/mysql2postgres/converter.rb +1 -2
- data/lib/mysql2postgres/mysql_reader.rb +20 -16
- data/lib/mysql2postgres/postgres_db_writer.rb +4 -5
- data/lib/mysql2postgres/postgres_file_writer.rb +20 -6
- data/lib/mysql2postgres/postgres_writer.rb +66 -31
- data/lib/mysql2postgres/version.rb +1 -1
- data/lib/mysql2postgres.rb +58 -25
- data/mysql2postgres.gemspec +7 -11
- data/test/fixtures/config_all_options.yml +14 -11
- data/test/fixtures/config_min_options.yml +16 -0
- data/test/fixtures/config_to_file.yml +31 -0
- data/test/integration/convert_to_db_test.rb +15 -8
- data/test/integration/convert_to_file_test.rb +33 -18
- data/test/integration/converter_test.rb +10 -3
- data/test/integration/{mysql_reader_base_test.rb → mysql_reader_connection_test.rb} +9 -6
- data/test/integration/mysql_reader_test.rb +7 -4
- data/test/integration/postgres_db_writer_test.rb +24 -0
- data/test/test_helper.rb +22 -67
- data/test/units/mysql_test.rb +10 -0
- data/test/units/option_test.rb +5 -8
- data/test/units/postgres_file_writer_test.rb +4 -2
- metadata +14 -82
- data/lib/mysql2postgres/writer.rb +0 -9
- data/test/integration/postgres_db_writer_base_test.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1108264fc3155986e78c07a12abd11c6ef39a1fb671a252e6921bf4bbf7f3a4b
|
4
|
+
data.tar.gz: 19837bf3a64bf6a204c9325b1271566e982467f7a3e9a53f966681c8a680e1ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5c54b071adaa63e1bfa5819fcadc7520f1063a4a0d2218adb5337361df259ddf574ab37bec2bc7f7683a0c4024679fd87c6d6715fe692a56768365cb5021a71
|
7
|
+
data.tar.gz: 992c22cdfa9e8ebea90fdecb10f95a00367b3ba9cd744c6cb0d954a0a2be81138376319a7f657337e8a50b83cd4969e577e7b6de480369e34edd1b9dcc8697e6
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Run Linters](https://github.com/AlphaNodes/mysql2postgres/workflows/Run%20Rubocop/badge.svg)](https://github.com/AlphaNodes/mysql2postgres/actions/workflows/rubocop.yml) [![Run Tests](https://github.com/AlphaNodes/mysql2postgres/workflows/Tests/badge.svg)](https://github.com/AlphaNodes/mysql2postgres/actions/workflows/tests.yml)
|
4
4
|
|
5
|
-
|
6
|
-
and the next release will have the same requirement.
|
5
|
+
Convert MySQL database to PostgreSQL database.
|
7
6
|
|
8
7
|
## Requirements
|
9
8
|
|
@@ -22,16 +21,22 @@ gem 'mysql2postgres'
|
|
22
21
|
Configuration is written in [YAML format](http://www.yaml.org/ "YAML Ain't Markup Language")
|
23
22
|
and passed as the first argument on the command line.
|
24
23
|
|
25
|
-
Configuration file has be provided with config/database.yml, see config/default.database.yml for
|
26
|
-
an example.
|
24
|
+
Configuration file has be provided with config/database.yml, see [config/default.database.yml](config/default.database.yml) for an example and for configuration information.
|
27
25
|
|
28
26
|
## Usage
|
29
27
|
|
30
28
|
After providing settings, start migration with
|
31
29
|
|
32
30
|
```sh
|
31
|
+
# set destination to use
|
32
|
+
MYSQL2POSTGRES_ENV=test
|
33
|
+
# use can also use (MYSQL2POSTGRES_ENV is used, if both are defined)
|
34
|
+
RAILS_ENV=test
|
33
35
|
|
36
|
+
# with default configuration, which use config/database.yml
|
34
37
|
bundle exec mysql2postgres
|
38
|
+
# OR with specified configuration file
|
39
|
+
bundle exec mysql2postgres /home/you/mysql2postgres.yml
|
35
40
|
```
|
36
41
|
|
37
42
|
## Tests
|
data/Rakefile
CHANGED
data/bin/mysql2postgres
CHANGED
@@ -7,21 +7,13 @@ require 'rubygems'
|
|
7
7
|
require 'bundler/setup'
|
8
8
|
require 'mysql2postgres'
|
9
9
|
|
10
|
-
|
10
|
+
config_file = ARGV.empty? ? File.join(File.dirname(__dir__), 'config', 'database.yml') : File.expand_path(ARGV[0])
|
11
11
|
|
12
|
-
|
13
|
-
ARGV[0]
|
14
|
-
else
|
15
|
-
CONFIG_FILE
|
16
|
-
end
|
12
|
+
raise "'#{config_file}' does not exist" unless FileTest.exist? config_file
|
17
13
|
|
18
|
-
|
19
|
-
raise "'#{file}' does not exist"
|
20
|
-
end
|
14
|
+
db_yaml = YAML.safe_load File.read(config_file)
|
21
15
|
|
22
|
-
|
16
|
+
raise "'#{config_file}' does not contain a mysql configuration directive for conversion" unless db_yaml.key? 'mysql'
|
17
|
+
raise "'#{config_file}' does not contain destinations configuration directive for conversion" unless db_yaml.key? 'destinations'
|
23
18
|
|
24
|
-
|
25
|
-
raise "'#{file}' does not contain a destination configuration directive for conversion" unless db_yaml.key? 'destination'
|
26
|
-
|
27
|
-
Mysql2postgres.new(db_yaml).convert
|
19
|
+
Mysql2postgres.new(db_yaml, config_file).convert
|
@@ -3,29 +3,17 @@
|
|
3
3
|
class Mysql2postgres
|
4
4
|
class Connection
|
5
5
|
attr_reader :conn,
|
6
|
-
:adapter,
|
7
6
|
:hostname,
|
8
7
|
:login,
|
9
8
|
:password,
|
10
9
|
:database,
|
11
10
|
:schema,
|
12
11
|
:port,
|
13
|
-
:environment,
|
14
12
|
:copy_manager,
|
15
13
|
:stream,
|
16
14
|
:is_copying
|
17
15
|
|
18
|
-
def initialize(
|
19
|
-
@environment = (ENV['RAILS_ENV'] || 'development').to_sym
|
20
|
-
|
21
|
-
if options[:destination].nil? ||
|
22
|
-
options[:destination].empty? ||
|
23
|
-
options[:destination][environment].nil? ||
|
24
|
-
options[:destination][environment].empty?
|
25
|
-
raise 'Unable to locate PostgreSQL destination environment in the configuration file'
|
26
|
-
end
|
27
|
-
|
28
|
-
pg_options = options[:destination][environment]
|
16
|
+
def initialize(pg_options)
|
29
17
|
@hostname = pg_options[:hostname] || 'localhost'
|
30
18
|
@login = pg_options[:username]
|
31
19
|
@password = pg_options[:password]
|
@@ -33,18 +21,20 @@ class Mysql2postgres
|
|
33
21
|
@port = (pg_options[:port] || 5432).to_s
|
34
22
|
|
35
23
|
@database, @schema = database.split ':'
|
36
|
-
@adapter = pg_options[:adapter] || 'jdbcpostgresql'
|
37
24
|
|
25
|
+
@conn = open
|
26
|
+
raise_nil_connection if conn.nil?
|
27
|
+
|
28
|
+
@is_copying = false
|
29
|
+
@current_statement = ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def open
|
38
33
|
@conn = PG::Connection.open dbname: database,
|
39
34
|
user: login,
|
40
35
|
password: password,
|
41
36
|
host: hostname,
|
42
37
|
port: port
|
43
|
-
|
44
|
-
raise_nil_connection if conn.nil?
|
45
|
-
|
46
|
-
@is_copying = false
|
47
|
-
@current_statement = ''
|
48
38
|
end
|
49
39
|
|
50
40
|
# ensure that the copy is completed, in case we hadn't seen a '\.' in the data stream.
|
@@ -120,6 +110,16 @@ class Mysql2postgres
|
|
120
110
|
raise 'No Connection'
|
121
111
|
end
|
122
112
|
|
113
|
+
def tables
|
114
|
+
result = run_statement <<~SQL_TABLES
|
115
|
+
SELECT table_name
|
116
|
+
FROM information_schema.tables
|
117
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
118
|
+
SQL_TABLES
|
119
|
+
|
120
|
+
result.map { |t| t['table_name'] }
|
121
|
+
end
|
122
|
+
|
123
123
|
private
|
124
124
|
|
125
125
|
def run_statement(statement)
|
@@ -16,7 +16,6 @@ class Mysql2postgres
|
|
16
16
|
def initialize(reader, writer, options)
|
17
17
|
@reader = reader
|
18
18
|
@writer = writer
|
19
|
-
@options = options
|
20
19
|
@exclude_tables = options[:exclude_tables] || []
|
21
20
|
@only_tables = options[:tables]
|
22
21
|
@suppress_data = options[:suppress_data] || false
|
@@ -28,7 +27,7 @@ class Mysql2postgres
|
|
28
27
|
|
29
28
|
def convert
|
30
29
|
tables = reader.tables
|
31
|
-
tables.reject! { |table| exclude_tables.include?
|
30
|
+
tables.reject! { |table| exclude_tables.include? table.name }
|
32
31
|
tables.select! { |table| only_tables ? only_tables.include?(table.name) : true }
|
33
32
|
|
34
33
|
# preserve order only works, if only_tables are specified
|
@@ -125,7 +125,7 @@ class Mysql2postgres
|
|
125
125
|
@indexes << index
|
126
126
|
elsif (match_data = /PRIMARY KEY .*\((.*)\)/.match(line))
|
127
127
|
index[:primary] = true
|
128
|
-
index[:columns] = match_data[1].split(',').map { |col| col.strip.delete
|
128
|
+
index[:columns] = match_data[1].split(',').map { |col| col.strip.delete '`' }
|
129
129
|
@indexes << index
|
130
130
|
end
|
131
131
|
end
|
@@ -164,6 +164,24 @@ class Mysql2postgres
|
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
|
+
attr_reader :mysql
|
168
|
+
|
169
|
+
def initialize(options)
|
170
|
+
@host = options[:mysql][:hostname]
|
171
|
+
@user = options[:mysql][:username]
|
172
|
+
@passwd = options[:mysql][:password]
|
173
|
+
@db = options[:mysql][:database]
|
174
|
+
@port = if options[:mysql][:port]
|
175
|
+
options[:mysql][:port] unless options[:mysql][:port].to_s.empty?
|
176
|
+
else
|
177
|
+
3306
|
178
|
+
end
|
179
|
+
@sock = options[:mysql][:socket] && !options[:mysql][:socket].empty? ? options[:mysql][:socket] : nil
|
180
|
+
@flag = options[:mysql][:flag] && !options[:mysql][:flag].empty? ? options[:mysql][:flag] : nil
|
181
|
+
|
182
|
+
connect
|
183
|
+
end
|
184
|
+
|
167
185
|
def connect
|
168
186
|
@mysql = ::Mysql.connect @host, @user, @passwd, @db, @port, @sock
|
169
187
|
# utf8_unicode_ci :: https://rubydoc.info/gems/ruby-mysql/Mysql/Charset
|
@@ -197,22 +215,8 @@ class Mysql2postgres
|
|
197
215
|
end
|
198
216
|
end
|
199
217
|
|
200
|
-
def initialize(options)
|
201
|
-
@host = options[:mysql][:hostname]
|
202
|
-
@user = options[:mysql][:username]
|
203
|
-
@passwd = options[:mysql][:password]
|
204
|
-
@db = options[:mysql][:database]
|
205
|
-
@port = options[:mysql][:port] || 3306
|
206
|
-
@sock = options[:mysql][:socket] && !options[:mysql][:socket].empty? ? options[:mysql][:socket] : nil
|
207
|
-
@sock = options[:mysql][:flag] && !options[:mysql][:flag].empty? ? options[:mysql][:flag] : nil
|
208
|
-
|
209
|
-
connect
|
210
|
-
end
|
211
|
-
|
212
|
-
attr_reader :mysql
|
213
|
-
|
214
218
|
def tables
|
215
|
-
@tables ||= @mysql.query('SHOW TABLES').map { |row| Table.new
|
219
|
+
@tables ||= @mysql.query('SHOW TABLES').map { |row| Table.new self, row.first }
|
216
220
|
end
|
217
221
|
|
218
222
|
def paginated_read(table, page_size)
|
@@ -5,14 +5,13 @@ require 'mysql2postgres/connection'
|
|
5
5
|
|
6
6
|
class Mysql2postgres
|
7
7
|
class PostgresDbWriter < PostgresFileWriter
|
8
|
-
attr_reader :connection
|
8
|
+
attr_reader :connection
|
9
9
|
|
10
|
-
def initialize(
|
10
|
+
def initialize(file, destination)
|
11
11
|
# NOTE: the superclass opens and truncates filename for writing
|
12
|
-
super
|
12
|
+
super
|
13
13
|
|
14
|
-
@
|
15
|
-
@connection = Connection.new options
|
14
|
+
@connection = Connection.new destination
|
16
15
|
end
|
17
16
|
|
18
17
|
def inload(path = filename)
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'mysql2postgres/postgres_writer'
|
4
|
+
require 'fileutils'
|
4
5
|
|
5
6
|
class Mysql2postgres
|
6
7
|
class PostgresFileWriter < PostgresWriter
|
7
|
-
def initialize(file)
|
8
|
+
def initialize(file, destination)
|
8
9
|
super()
|
9
10
|
|
11
|
+
@filename = file
|
12
|
+
@destination = destination
|
13
|
+
|
10
14
|
@f = File.open file, 'w+:UTF-8'
|
11
15
|
@f << <<~SQL_HEADER
|
12
16
|
-- MySQL 2 PostgreSQL dump\n
|
@@ -88,7 +92,7 @@ class Mysql2postgres
|
|
88
92
|
@f << columns
|
89
93
|
|
90
94
|
if (primary_index = table.indexes.find { |index| index[:primary] })
|
91
|
-
@f << ",\n CONSTRAINT #{table.name}_pkey PRIMARY KEY(#{primary_index[:columns]
|
95
|
+
@f << ",\n CONSTRAINT #{table.name}_pkey PRIMARY KEY(#{quoted_list primary_index[:columns]})"
|
92
96
|
end
|
93
97
|
|
94
98
|
@f << <<~SQL_OIDS
|
@@ -103,7 +107,7 @@ class Mysql2postgres
|
|
103
107
|
@f << <<~SQL_INDEX
|
104
108
|
DROP INDEX IF EXISTS #{PG::Connection.quote_ident index[:name]} CASCADE;
|
105
109
|
CREATE #{unique}INDEX #{PG::Connection.quote_ident index[:name]}
|
106
|
-
ON #{PG::Connection.quote_ident table.name} (#{index[:columns]
|
110
|
+
ON #{PG::Connection.quote_ident table.name} (#{quoted_list index[:columns]});
|
107
111
|
SQL_INDEX
|
108
112
|
end
|
109
113
|
end
|
@@ -113,8 +117,8 @@ class Mysql2postgres
|
|
113
117
|
def write_constraints(table)
|
114
118
|
table.foreign_keys.each do |key|
|
115
119
|
@f << "ALTER TABLE #{PG::Connection.quote_ident table.name} " \
|
116
|
-
"ADD FOREIGN KEY (#{key[:column]
|
117
|
-
"REFERENCES #{PG::Connection.quote_ident key[:ref_table]}(#{key[:ref_column]
|
120
|
+
"ADD FOREIGN KEY (#{quoted_list key[:column]}) " \
|
121
|
+
"REFERENCES #{PG::Connection.quote_ident key[:ref_table]}(#{quoted_list key[:ref_column]}) " \
|
118
122
|
"ON UPDATE #{key[:on_update]} ON DELETE #{key[:on_delete]};\n"
|
119
123
|
end
|
120
124
|
end
|
@@ -125,7 +129,7 @@ class Mysql2postgres
|
|
125
129
|
-- Data for Name: #{table.name}; Type: TABLE DATA; Schema: public
|
126
130
|
--
|
127
131
|
|
128
|
-
COPY "#{table.name}" (#{table.columns.map { |
|
132
|
+
COPY "#{table.name}" (#{quoted_list(table.columns.map { |m| m[:name] })}) FROM stdin;
|
129
133
|
SQL_COPY
|
130
134
|
|
131
135
|
reader.paginated_read table, 1000 do |row, _counter|
|
@@ -139,5 +143,15 @@ class Mysql2postgres
|
|
139
143
|
def close
|
140
144
|
@f.close
|
141
145
|
end
|
146
|
+
|
147
|
+
def inload
|
148
|
+
puts "\nSkip import to PostgreSQL DB. SQL file created successfully."
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def quoted_list(list)
|
154
|
+
list.map { |c| PG::Connection.quote_ident c }.join(', ')
|
155
|
+
end
|
142
156
|
end
|
143
157
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'zlib'
|
4
|
-
require 'mysql2postgres/writer'
|
5
4
|
|
6
5
|
class Mysql2postgres
|
7
|
-
class PostgresWriter
|
6
|
+
class PostgresWriter
|
7
|
+
attr_reader :filename, :destination
|
8
|
+
|
8
9
|
def column_description(column)
|
9
10
|
"#{PG::Connection.quote_ident column[:name]} #{column_type_info column}"
|
10
11
|
end
|
@@ -50,16 +51,21 @@ class Mysql2postgres
|
|
50
51
|
when 'double precision'
|
51
52
|
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default]}" if default
|
52
53
|
'double precision'
|
53
|
-
when 'datetime'
|
54
|
+
when 'datetime', 'datetime(6)'
|
54
55
|
default = nil
|
55
56
|
'timestamp without time zone'
|
56
57
|
when 'date'
|
57
58
|
default = nil
|
58
59
|
'date'
|
59
60
|
when 'timestamp'
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
case column[:default]
|
62
|
+
when 'CURRENT_TIMESTAMP'
|
63
|
+
default = ' DEFAULT CURRENT_TIMESTAMP'
|
64
|
+
when datetime_zero
|
65
|
+
default = " DEFAULT '#{datetime_zero_fix}'"
|
66
|
+
when datetime_zero(with_seconds: true) # rubocop: disable Style/MethodCallWithArgsParentheses
|
67
|
+
default = " DEFAULT '#{datetime_zero_fix with_seconds: true}'"
|
68
|
+
end
|
63
69
|
'timestamp without time zone'
|
64
70
|
when 'time'
|
65
71
|
default = ' DEFAULT NOW()' if default
|
@@ -89,45 +95,74 @@ class Mysql2postgres
|
|
89
95
|
row[index] = Time.at(row[index]).utc.strftime('%H:%M:%S') if column[:type] == 'time' && row[index]
|
90
96
|
|
91
97
|
if row[index].is_a? Time
|
92
|
-
row[index] = row[index].to_s.gsub
|
93
|
-
row[index] = row[index].to_s.gsub
|
98
|
+
row[index] = row[index].to_s.gsub datetime_zero, datetime_zero_fix
|
99
|
+
row[index] = row[index].to_s.gsub datetime_zero(with_seconds: true), datetime_zero_fix(with_seconds: true)
|
94
100
|
end
|
95
101
|
|
96
102
|
if column_type(column) == 'boolean'
|
97
103
|
row[index] = if row[index] == 1
|
98
104
|
't'
|
105
|
+
elsif row[index]&.zero?
|
106
|
+
'f'
|
99
107
|
else
|
100
|
-
row[index]
|
108
|
+
row[index]
|
101
109
|
end
|
102
110
|
end
|
103
111
|
|
104
|
-
if row[index].is_a? String
|
105
|
-
row[index] = if column_type(column) == 'bytea'
|
106
|
-
if column[:name] == 'data'
|
107
|
-
with_gzip = false
|
108
|
-
table.columns.each_with_index do |column_data, index_data|
|
109
|
-
if column_data[:name] == 'compression' && row[index_data] == 'gzip'
|
110
|
-
with_gzip = true
|
111
|
-
break
|
112
|
-
end
|
113
|
-
end
|
114
|
-
if with_gzip
|
115
|
-
PG::Connection.escape_bytea(Zlib::Inflate.inflate(row[index])).gsub(/\\/, '\\\\\\').gsub(/''/, "'")
|
116
|
-
else
|
117
|
-
PG::Connection.escape_bytea(row[index]).gsub(/\\/, '\\\\\\').gsub(/''/, "'")
|
118
|
-
end
|
119
|
-
else
|
120
|
-
PG::Connection.escape_bytea(row[index]).gsub(/\\/, '\\\\\\').gsub(/''/, "'")
|
121
|
-
end
|
122
|
-
else
|
123
|
-
row[index].gsub(/\\/, '\\\\\\').gsub(/\n/, '\n').gsub(/\t/, '\t').gsub(/\r/, '\r').gsub(/\0/, '')
|
124
|
-
end
|
125
|
-
end
|
112
|
+
row[index] = string_data table, row, index, column if row[index].is_a? String
|
126
113
|
|
127
114
|
row[index] = '\N' unless row[index]
|
128
115
|
end
|
129
116
|
end
|
130
117
|
|
131
118
|
def truncate(_table) end
|
119
|
+
|
120
|
+
def inload
|
121
|
+
raise "Method 'inload' needs to be overridden..."
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def datetime_zero(with_seconds: false)
|
127
|
+
datetime_value date: '0000-00-00', with_seconds: with_seconds
|
128
|
+
end
|
129
|
+
|
130
|
+
def datetime_zero_fix(with_seconds: false)
|
131
|
+
datetime_value date: '1970-01-01', with_seconds: with_seconds
|
132
|
+
end
|
133
|
+
|
134
|
+
def datetime_value(date:, with_seconds: false)
|
135
|
+
value = ["#{date} 00:00"]
|
136
|
+
value << '00' if with_seconds
|
137
|
+
value.join ':'
|
138
|
+
end
|
139
|
+
|
140
|
+
def string_data(table, row, index, column)
|
141
|
+
if column_type(column) == 'bytea'
|
142
|
+
if column[:name] == 'data'
|
143
|
+
with_gzip = false
|
144
|
+
table.columns.each_with_index do |column_data, index_data|
|
145
|
+
if column_data[:name] == 'compression' && row[index_data] == 'gzip'
|
146
|
+
with_gzip = true
|
147
|
+
break
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
escape_bytea(with_gzip ? Zlib::Inflate.inflate(row[index]) : row[index])
|
152
|
+
else
|
153
|
+
escape_bytea row[index]
|
154
|
+
end
|
155
|
+
else
|
156
|
+
escape_data(row[index]).gsub("\n", '\n').gsub("\t", '\t').gsub("\r", '\r').gsub(/\0/, '')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def escape_bytea(data)
|
161
|
+
escape_data(PG::Connection.escape_bytea(data)).gsub("''", "'")
|
162
|
+
end
|
163
|
+
|
164
|
+
def escape_data(value)
|
165
|
+
value.gsub '\\', '\\\\\\'
|
166
|
+
end
|
132
167
|
end
|
133
168
|
end
|
data/lib/mysql2postgres.rb
CHANGED
@@ -12,7 +12,6 @@ require 'pg/result'
|
|
12
12
|
require 'mysql2postgres/version'
|
13
13
|
require 'mysql2postgres/converter'
|
14
14
|
require 'mysql2postgres/mysql_reader'
|
15
|
-
require 'mysql2postgres/writer'
|
16
15
|
require 'mysql2postgres/postgres_writer'
|
17
16
|
require 'mysql2postgres/postgres_file_writer'
|
18
17
|
require 'mysql2postgres/postgres_db_writer'
|
@@ -20,42 +19,76 @@ require 'mysql2postgres/postgres_db_writer'
|
|
20
19
|
require 'debug' if ENV.fetch('ENABLE_DEBUG', nil) == '1'
|
21
20
|
|
22
21
|
class Mysql2postgres
|
23
|
-
attr_reader :options, :reader, :writer
|
22
|
+
attr_reader :options, :config_file, :reader, :writer
|
24
23
|
|
25
|
-
def initialize(yaml)
|
24
|
+
def initialize(yaml, config_file = nil)
|
25
|
+
@config_file = config_file
|
26
26
|
@options = build_options yaml
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
def convert
|
30
|
+
@reader = MysqlReader.new options
|
31
|
+
|
32
|
+
puts "mysql2postgres #{Mysql2postgres::VERSION}"
|
33
|
+
puts "Config file: #{config_file}"
|
34
|
+
puts "Dumpfile: #{dump_file}"
|
35
|
+
|
36
|
+
@writer = if to_file?
|
37
|
+
puts 'Target: File'
|
38
|
+
PostgresFileWriter.new dump_file, options[:destination]
|
39
|
+
else
|
40
|
+
puts "Target: PostgreSQL DB (#{adapter})"
|
41
|
+
PostgresDbWriter.new dump_file, options[:destination]
|
42
|
+
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
connection.load_file path
|
44
|
+
Converter.new(reader, writer, options).convert
|
45
|
+
File.delete dump_file if options[:remove_dump_file] && File.exist?(dump_file)
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
|
-
@reader = MysqlReader.new options
|
48
|
+
private
|
46
49
|
|
47
|
-
|
50
|
+
def adapter
|
51
|
+
if options[:destination][:adapter].nil? || options[:destination][:adapter].empty?
|
52
|
+
'postgresql'
|
53
|
+
else
|
54
|
+
options[:destination][:adapter]
|
55
|
+
end
|
56
|
+
end
|
48
57
|
|
49
|
-
|
50
|
-
|
58
|
+
def environment
|
59
|
+
if ENV['MYSQL2POSTGRES_ENV']
|
60
|
+
ENV['MYSQL2POSTGRES_ENV']
|
61
|
+
elsif ENV['RAILS_ENV']
|
62
|
+
ENV['RAILS_ENV']
|
63
|
+
else
|
64
|
+
'development'
|
65
|
+
end
|
66
|
+
end
|
51
67
|
|
52
|
-
|
53
|
-
|
68
|
+
def to_file?
|
69
|
+
adapter == 'file'
|
70
|
+
end
|
54
71
|
|
55
|
-
|
72
|
+
def build_options(yaml)
|
73
|
+
yaml.transform_keys(&:to_sym).tap do |opts|
|
74
|
+
opts[:mysql].transform_keys!(&:to_sym)
|
56
75
|
|
57
|
-
|
76
|
+
destinations = opts.delete :destinations
|
77
|
+
opts[:destination] = destinations[environment]&.transform_keys(&:to_sym)
|
78
|
+
|
79
|
+
if opts[:destination].nil? || opts[:destination].empty?
|
80
|
+
raise "no configuration for environment '#{environment}' in destinations available. Use MYSQL2POSTGRES_ENV or RAILS_ENV."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
58
84
|
|
59
|
-
|
85
|
+
def dump_file
|
86
|
+
@dump_file ||= if to_file? && options[:destination][:filename] && options[:destination][:filename] != ''
|
87
|
+
options[:destination][:filename]
|
88
|
+
else
|
89
|
+
tag = Time.new.strftime '%Y%m%d-%H%M%S'
|
90
|
+
path = options[:dump_file_directory] || './'
|
91
|
+
File.expand_path File.join(path, "output_#{tag}.sql")
|
92
|
+
end
|
60
93
|
end
|
61
94
|
end
|
data/mysql2postgres.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
lib = File.expand_path '../lib', __FILE__
|
4
|
-
puts lib
|
5
4
|
$LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
|
6
5
|
require 'mysql2postgres/version'
|
7
6
|
|
@@ -50,31 +49,28 @@ Gem::Specification.new do |s|
|
|
50
49
|
'lib/mysql2postgres/postgres_db_writer.rb',
|
51
50
|
'lib/mysql2postgres/postgres_writer.rb',
|
52
51
|
'lib/mysql2postgres/version.rb',
|
53
|
-
'lib/mysql2postgres/writer.rb',
|
54
52
|
'mysql2postgres.gemspec',
|
55
53
|
'test/fixtures/config_all_options.yml',
|
54
|
+
'test/fixtures/config_min_options.yml',
|
55
|
+
'test/fixtures/config_to_file.yml',
|
56
56
|
'test/fixtures/seed_integration_tests.sql',
|
57
57
|
'test/integration/convert_to_db_test.rb',
|
58
58
|
'test/integration/convert_to_file_test.rb',
|
59
59
|
'test/integration/converter_test.rb',
|
60
|
-
'test/integration/
|
60
|
+
'test/integration/mysql_reader_connection_test.rb',
|
61
61
|
'test/integration/mysql_reader_test.rb',
|
62
|
-
'test/integration/
|
62
|
+
'test/integration/postgres_db_writer_test.rb',
|
63
|
+
'test/units/mysql_test.rb',
|
63
64
|
'test/units/option_test.rb',
|
64
65
|
'test/units/postgres_file_writer_test.rb',
|
65
66
|
'test/test_helper.rb'
|
66
67
|
]
|
67
|
-
s.homepage = 'https://
|
68
|
+
s.homepage = 'https://github.com/AlphaNodes/mysql2postgres'
|
68
69
|
s.rdoc_options = ['--charset=UTF-8']
|
69
70
|
s.require_paths = ['lib']
|
70
71
|
s.summary = 'MySQL to PostgreSQL Data Translation'
|
71
72
|
|
72
|
-
s.add_dependency 'rake'
|
73
73
|
s.add_runtime_dependency 'pg', '~> 1.2.2'
|
74
|
-
s.add_runtime_dependency '
|
74
|
+
s.add_runtime_dependency 'rake'
|
75
75
|
s.add_runtime_dependency 'ruby-mysql', '~> 3.0'
|
76
|
-
s.add_development_dependency 'debug'
|
77
|
-
s.add_development_dependency 'rubocop-minitest'
|
78
|
-
s.add_development_dependency 'rubocop-performance'
|
79
|
-
s.add_development_dependency 'test-unit', '~> 3.5.3'
|
80
76
|
end
|