mysql2postgres 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](https://github.com/AlphaNodes/mysql2postgres/actions/workflows/rubocop.yml) [](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
|