mysql2postgres 0.3.3 → 0.4.0
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 +0 -1
- data/lib/mysql2postgres/mysql_reader.rb +18 -14
- data/lib/mysql2postgres/postgres_db_writer.rb +4 -5
- data/lib/mysql2postgres/postgres_file_writer.rb +20 -6
- data/lib/mysql2postgres/postgres_writer.rb +65 -30
- 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 +13 -81
- 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: cc905b9634e92298f5b5d192b6f4388294f6153a777b551b408bc5e944247b3f
|
4
|
+
data.tar.gz: 108d90760f0600e55f117df749147f8137d3044a7724dd901f76869db7b45caf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b08edc13623c942b61d03cecce1f577eca9fce3906e3c06ef17c848396dfebadb39017cb7ae7aecb5193e91cffc85d2a4cea2e49054f0e7dd3dffc8f53012eb
|
7
|
+
data.tar.gz: 5ef72f3a6ac5961102d282b38cf2eb5bc62d301f5e84b590dc4e9dd7d057fa51c43f5272b112478ab6b6d3513fc3ac67e8a24bae0d9f23d3f162c0ddd8f591e0
|
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
|
@@ -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,20 +215,6 @@ 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
219
|
@tables ||= @mysql.query('SHOW TABLES').map { |row| Table.new(self, row.first) }
|
216
220
|
end
|
@@ -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
|
@@ -57,9 +58,14 @@ class Mysql2postgres
|
|
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 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(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
|
@@ -1,20 +1,21 @@
|
|
1
1
|
mysql:
|
2
|
-
hostname:
|
2
|
+
hostname: 127.0.0.1
|
3
3
|
port: 3306
|
4
|
-
|
5
|
-
|
6
|
-
password: secretpassword
|
4
|
+
username: root
|
5
|
+
password: BestPasswordEver
|
7
6
|
database: somename
|
7
|
+
encoding: utf8mb4
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
# if MYSQL2POSTGRES_ENV and RAILS_ENV is missing, development is used!
|
10
|
+
destinations:
|
11
|
+
development:
|
12
|
+
adapter: file
|
13
|
+
filename: /tmp/exported_file.sql
|
13
14
|
test:
|
14
15
|
hostname: localhost
|
15
16
|
port: 5432
|
16
|
-
username:
|
17
|
-
password:
|
17
|
+
username: postgres
|
18
|
+
password: postgres
|
18
19
|
database: somename
|
19
20
|
|
20
21
|
# if tables is given, only the listed tables will be converted. leave empty to convert all tables.
|
@@ -29,8 +30,10 @@ exclude_tables:
|
|
29
30
|
- table5
|
30
31
|
- table6
|
31
32
|
|
33
|
+
dump_file_directory: /tmp
|
34
|
+
|
32
35
|
# if suppress_data is true, only the schema definition will be exported/migrated, and not the data
|
33
|
-
suppress_data:
|
36
|
+
suppress_data: false
|
34
37
|
|
35
38
|
# if suppress_ddl is true, only the data will be exported/imported, and not the schema
|
36
39
|
suppress_ddl: false
|
@@ -0,0 +1,16 @@
|
|
1
|
+
mysql:
|
2
|
+
hostname: localhost
|
3
|
+
username: root
|
4
|
+
password: BestPasswordEver
|
5
|
+
database: somename
|
6
|
+
|
7
|
+
# if MYSQL2POSTGRES_ENV and RAILS_ENV is missing, development is used!
|
8
|
+
destinations:
|
9
|
+
test:
|
10
|
+
hostname: localhost
|
11
|
+
port: 5432
|
12
|
+
username: somename
|
13
|
+
password: secretpassword
|
14
|
+
database: somename
|
15
|
+
|
16
|
+
# all options are false as default
|
@@ -0,0 +1,31 @@
|
|
1
|
+
mysql:
|
2
|
+
hostname: 127.0.0.1
|
3
|
+
port: 3306
|
4
|
+
username: root
|
5
|
+
password: BestPasswordEver
|
6
|
+
database: somename
|
7
|
+
encoding: utf8mb4
|
8
|
+
|
9
|
+
# if MYSQL2POSTGRES_ENV and RAILS_ENV is missing, development is used!
|
10
|
+
destinations:
|
11
|
+
test:
|
12
|
+
adapter: file
|
13
|
+
filename: /tmp/exported_file.sql
|
14
|
+
|
15
|
+
# if tables is given, only the listed tables will be converted. leave empty to convert all tables.
|
16
|
+
tables:
|
17
|
+
- table1
|
18
|
+
- table2
|
19
|
+
- table3
|
20
|
+
- table4
|
21
|
+
|
22
|
+
dump_file_directory: /tmp
|
23
|
+
|
24
|
+
# if suppress_data is true, only the schema definition will be exported/migrated, and not the data
|
25
|
+
suppress_data: false
|
26
|
+
|
27
|
+
# if suppress_ddl is true, only the data will be exported/imported, and not the schema
|
28
|
+
suppress_ddl: false
|
29
|
+
|
30
|
+
# if force_truncate is true, forces a table truncate before table loading
|
31
|
+
force_truncate: false
|
@@ -6,18 +6,25 @@ class ConvertToDbTest < Test::Unit::TestCase
|
|
6
6
|
class << self
|
7
7
|
def startup
|
8
8
|
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_db_convert_all
|
10
|
-
@mysql2postgres = Mysql2postgres.new @options
|
11
|
-
@mysql2postgres.convert
|
12
|
-
@mysql2postgres.writer.open
|
13
9
|
end
|
10
|
+
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
def setup
|
13
|
+
@mysql2postgres = instance_from_file
|
14
|
+
@options = @mysql2postgres.options
|
15
|
+
@options[:force_truncate] = true
|
16
|
+
@options.delete :tables # convert all available tables
|
17
|
+
|
18
|
+
@mysql2postgres.convert
|
19
|
+
@mysql2postgres.writer.connection.open
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
@mysql2postgres&.writer&.connection&.finish
|
18
24
|
end
|
19
25
|
|
20
26
|
def test_table_creation
|
21
|
-
|
27
|
+
tables = @mysql2postgres.writer.connection.tables
|
28
|
+
assert tables.include?('numeric_types_basics')
|
22
29
|
end
|
23
30
|
end
|
@@ -3,18 +3,22 @@
|
|
3
3
|
require File.expand_path '../test_helper', __dir__
|
4
4
|
|
5
5
|
class ConvertToFileTest < Test::Unit::TestCase
|
6
|
+
attr_reader :content
|
7
|
+
|
6
8
|
class << self
|
7
9
|
def startup
|
8
|
-
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_file_convert_all
|
10
|
-
@mysql2postgres = Mysql2postgres.new @options
|
11
|
-
@mysql2postgres.convert
|
12
|
-
@content = File.read @mysql2postgres.options[:destfile]
|
10
|
+
seed_test_database option_file: 'config_to_file'
|
13
11
|
end
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
17
|
-
|
14
|
+
def setup
|
15
|
+
@mysql2postgres = instance_from_file 'config_to_file'
|
16
|
+
@options = @mysql2postgres.options
|
17
|
+
@options[:force_truncate] = true
|
18
|
+
@options.delete :tables # convert all available tables
|
19
|
+
|
20
|
+
@mysql2postgres.convert
|
21
|
+
@content = File.read @mysql2postgres.options[:destination][:filename]
|
18
22
|
end
|
19
23
|
|
20
24
|
def test_table_creation
|
@@ -23,46 +27,57 @@ class ConvertToFileTest < Test::Unit::TestCase
|
|
23
27
|
end
|
24
28
|
|
25
29
|
def test_basic_numerics_tinyint
|
26
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_tinyint" smallint,.*\)', Regexp::MULTILINE)
|
30
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_tinyint" smallint,.*\)', Regexp::MULTILINE)
|
31
|
+
.match(content)
|
27
32
|
end
|
28
33
|
|
29
34
|
def test_basic_numerics_smallint
|
30
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_smallint" integer,.*\)', Regexp::MULTILINE)
|
35
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_smallint" integer,.*\)', Regexp::MULTILINE)
|
36
|
+
.match(content)
|
31
37
|
end
|
32
38
|
|
33
39
|
def test_basic_numerics_mediumint
|
34
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_mediumint" integer,.*\)', Regexp::MULTILINE)
|
40
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_mediumint" integer,.*\)', Regexp::MULTILINE)
|
41
|
+
.match(content)
|
35
42
|
end
|
36
43
|
|
37
44
|
def test_basic_numerics_int
|
38
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_int" integer,.*\)', Regexp::MULTILINE)
|
45
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_int" integer,.*\)', Regexp::MULTILINE)
|
46
|
+
.match(content)
|
39
47
|
end
|
40
48
|
|
41
49
|
def test_basic_numerics_integer
|
42
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_integer" integer,.*\)', Regexp::MULTILINE)
|
50
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_integer" integer,.*\)', Regexp::MULTILINE)
|
51
|
+
.match(content)
|
43
52
|
end
|
44
53
|
|
45
54
|
def test_basic_numerics_bigint
|
46
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_bigint" bigint,.*\)', Regexp::MULTILINE)
|
55
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_bigint" bigint,.*\)', Regexp::MULTILINE)
|
56
|
+
.match(content)
|
47
57
|
end
|
48
58
|
|
49
59
|
def test_basic_numerics_real
|
50
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_real" double precision,.*\)', Regexp::MULTILINE)
|
60
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_real" double precision,.*\)', Regexp::MULTILINE)
|
61
|
+
.match(content)
|
51
62
|
end
|
52
63
|
|
53
64
|
def test_basic_numerics_double
|
54
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_double" double precision,.*\)', Regexp::MULTILINE)
|
65
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_double" double precision,.*\)', Regexp::MULTILINE)
|
66
|
+
.match(content)
|
55
67
|
end
|
56
68
|
|
57
69
|
def test_basic_numerics_float
|
58
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_float" double precision,.*\)', Regexp::MULTILINE)
|
70
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_float" double precision,.*\)', Regexp::MULTILINE)
|
71
|
+
.match(content)
|
59
72
|
end
|
60
73
|
|
61
74
|
def test_basic_numerics_decimal
|
62
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_decimal" numeric\(10, 0\),.*\)', Regexp::MULTILINE)
|
75
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_decimal" numeric\(10, 0\),.*\)', Regexp::MULTILINE)
|
76
|
+
.match(content)
|
63
77
|
end
|
64
78
|
|
65
79
|
def test_basic_numerics_numeric
|
66
|
-
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_numeric" numeric\(10, 0\)[\w\n]*\)', Regexp::MULTILINE)
|
80
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_numeric" numeric\(10, 0\)[\w\n]*\)', Regexp::MULTILINE)
|
81
|
+
.match(content)
|
67
82
|
end
|
68
83
|
end
|
@@ -5,15 +5,22 @@ require File.expand_path '../test_helper', __dir__
|
|
5
5
|
class ConverterTest < Test::Unit::TestCase
|
6
6
|
class << self
|
7
7
|
def startup
|
8
|
-
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_file_convert_nothing
|
8
|
+
seed_test_database option_file: 'config_to_file'
|
10
9
|
end
|
11
10
|
end
|
12
11
|
|
12
|
+
def setup
|
13
|
+
@options = options_from_file 'config_to_file'
|
14
|
+
@options[:suppress_data] = true
|
15
|
+
@options[:suppress_ddl] = true
|
16
|
+
|
17
|
+
@destfile = get_temp_file 'mysql2postgres_test'
|
18
|
+
end
|
19
|
+
|
13
20
|
def test_new_converter
|
14
21
|
assert_nothing_raised do
|
15
22
|
reader = get_test_reader @options
|
16
|
-
writer =
|
23
|
+
writer = Mysql2postgres::PostgresFileWriter.new @destfile, @options[:destination]
|
17
24
|
converter = Mysql2postgres::Converter.new reader, writer, @options
|
18
25
|
assert_equal 0, converter.convert
|
19
26
|
end
|
@@ -2,23 +2,26 @@
|
|
2
2
|
|
3
3
|
require File.expand_path '../test_helper', __dir__
|
4
4
|
|
5
|
-
class
|
5
|
+
class MysqlReaderConnectionTest < Test::Unit::TestCase
|
6
6
|
class << self
|
7
7
|
def startup
|
8
|
-
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_file_convert_nothing
|
8
|
+
seed_test_database option_file: 'config_to_file'
|
10
9
|
end
|
11
10
|
end
|
12
11
|
|
12
|
+
def setup
|
13
|
+
@options = options_from_file 'config_to_file'
|
14
|
+
end
|
15
|
+
|
13
16
|
def test_mysql_connection
|
14
17
|
assert_nothing_raised do
|
15
|
-
|
18
|
+
Mysql2postgres::MysqlReader.new @options
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
19
22
|
def test_mysql_reconnect
|
20
23
|
assert_nothing_raised do
|
21
|
-
reader =
|
24
|
+
reader = Mysql2postgres::MysqlReader.new @options
|
22
25
|
reader.reconnect
|
23
26
|
end
|
24
27
|
end
|
@@ -27,7 +30,7 @@ class MysqlReaderBaseTest < Test::Unit::TestCase
|
|
27
30
|
assert_nothing_raised do
|
28
31
|
@options[:mysql][:port] = ''
|
29
32
|
@options[:mysql][:socket] = ''
|
30
|
-
|
33
|
+
Mysql2postgres::MysqlReader.new @options
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|
@@ -5,12 +5,15 @@ require File.expand_path '../test_helper', __dir__
|
|
5
5
|
class MysqlReaderTest < Test::Unit::TestCase
|
6
6
|
class << self
|
7
7
|
def startup
|
8
|
-
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_file_convert_nothing
|
10
|
-
@reader = get_test_reader @options
|
8
|
+
seed_test_database option_file: 'config_to_file'
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
12
|
+
def setup
|
13
|
+
@options = options_from_file 'config_to_file'
|
14
|
+
@reader = get_test_reader @options
|
15
|
+
end
|
16
|
+
|
14
17
|
def test_db_connection
|
15
18
|
assert_nothing_raised do
|
16
19
|
@reader.mysql.ping
|
@@ -26,7 +29,7 @@ class MysqlReaderTest < Test::Unit::TestCase
|
|
26
29
|
def test_paginated_read
|
27
30
|
expected_rows = 3
|
28
31
|
page_size = 2
|
29
|
-
expected_pages = (1.0 * expected_rows / page_size).ceil
|
32
|
+
# expected_pages = (1.0 * expected_rows / page_size).ceil
|
30
33
|
|
31
34
|
row_count = my_row_count = 0
|
32
35
|
table = @reader.tables.find { |t| t.name == 'numeric_types_basics' }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class PostgresDbWriterTest < Test::Unit::TestCase
|
6
|
+
class << self
|
7
|
+
def startup
|
8
|
+
seed_test_database
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@options = options_from_file
|
14
|
+
@options[:suppress_data] = true
|
15
|
+
@options[:suppress_ddl] = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_pg_connection
|
19
|
+
assert_nothing_raised do
|
20
|
+
Mysql2postgres::PostgresDbWriter.new Tempfile.new('mysql2postgres_test_').path,
|
21
|
+
@options[:destination]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -4,47 +4,37 @@ require 'rubygems'
|
|
4
4
|
require 'test/unit'
|
5
5
|
require 'debug' if ENV.fetch('ENABLE_DEBUG', nil) == '1'
|
6
6
|
|
7
|
-
require File.expand_path('
|
8
|
-
require File.expand_path('../lib/mysql2postgres/converter', __dir__)
|
9
|
-
require File.expand_path('../lib/mysql2postgres/mysql_reader', __dir__)
|
10
|
-
require File.expand_path('../lib/mysql2postgres/writer', __dir__)
|
11
|
-
require File.expand_path('../lib/mysql2postgres/postgres_writer', __dir__)
|
12
|
-
require File.expand_path('../lib/mysql2postgres/postgres_file_writer', __dir__)
|
13
|
-
require File.expand_path('../lib/mysql2postgres/postgres_db_writer', __dir__)
|
7
|
+
require File.expand_path('lib/mysql2postgres')
|
14
8
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
rc = system "mysql -u#{options[:mysql][:username]} #{options[:mysql][:database]} < #{seedfilepath}"
|
19
|
-
raise StandardError unless rc
|
9
|
+
def load_yaml_file(file = 'config_all_options')
|
10
|
+
YAML.load_file "#{__dir__}/fixtures/#{file}.yml"
|
11
|
+
end
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
raise 'Failed to seed integration test db. See README for setup requirements.'
|
13
|
+
def instance_from_file(file = 'config_all_options')
|
14
|
+
Mysql2postgres.new load_yaml_file(file)
|
24
15
|
end
|
25
16
|
|
26
|
-
def
|
27
|
-
|
28
|
-
mysql2postgres::MysqlReader.new options
|
29
|
-
rescue StandardError
|
30
|
-
raise 'Failed to initialize integration test db. See README for setup requirements.'
|
17
|
+
def options_from_file(file = 'config_all_options')
|
18
|
+
instance_from_file(file).options
|
31
19
|
end
|
32
20
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
21
|
+
def seed_test_database(option_file: 'config_all_options', sql_file: 'seed_integration_tests.sql')
|
22
|
+
options = options_from_file option_file
|
23
|
+
seedfilepath = File.expand_path "test/fixtures/#{sql_file}"
|
24
|
+
system 'mysql ' \
|
25
|
+
"--host #{options[:mysql][:hostname]} " \
|
26
|
+
"--port #{options[:mysql][:port]} " \
|
27
|
+
"-u#{options[:mysql][:username]} " \
|
28
|
+
"-p#{options[:mysql][:password]} " \
|
29
|
+
"#{options[:mysql][:database]} < #{seedfilepath}", exception: true
|
30
|
+
rescue StandardError
|
31
|
+
raise 'Failed to seed integration test db. See README for setup requirements.'
|
39
32
|
end
|
40
33
|
|
41
|
-
def
|
42
|
-
|
43
|
-
reader = get_test_reader options
|
44
|
-
writer = get_test_file_writer options
|
45
|
-
mysql2postgres::Converter.new reader, writer, options
|
34
|
+
def get_test_reader(options)
|
35
|
+
Mysql2postgres::MysqlReader.new options
|
46
36
|
rescue StandardError
|
47
|
-
raise
|
37
|
+
raise 'Failed to initialize integration test db. See README for setup requirements.'
|
48
38
|
end
|
49
39
|
|
50
40
|
def get_temp_file(basename)
|
@@ -54,38 +44,3 @@ def get_temp_file(basename)
|
|
54
44
|
f.close!
|
55
45
|
path
|
56
46
|
end
|
57
|
-
|
58
|
-
def get_new_test_config(include_tables: [], exclude_tables: [], to_file: false,
|
59
|
-
suppress_data: false, suppress_ddl: false, force_truncate: false)
|
60
|
-
|
61
|
-
to_filename = to_file ? get_temp_file(['mysql2postgres_tmp_output_', '.yml']) : nil
|
62
|
-
configtext = Mysql2postgres::Config.template to_filename, include_tables, exclude_tables, suppress_data, suppress_ddl, force_truncate
|
63
|
-
configfile = get_temp_file 'mysql2postgres_tmp_config'
|
64
|
-
File.open(configfile, 'w:UTF-8') { |f| f.write(configtext) }
|
65
|
-
yaml = YAML.load_file configfile
|
66
|
-
Mysql2postgres::ConfigBase.new yaml
|
67
|
-
rescue StandardError
|
68
|
-
raise "Failed to initialize options from #{configfile}. See README for setup requirements."
|
69
|
-
end
|
70
|
-
|
71
|
-
def get_test_config_by_label(name)
|
72
|
-
case name
|
73
|
-
when :localmysql_to_file_convert_nothing
|
74
|
-
get_new_test_config to_file: true,
|
75
|
-
tables: ['unobtainium'],
|
76
|
-
exclude_tables: ['kryptonite'],
|
77
|
-
suppress_data: true,
|
78
|
-
suppress_ddl: true
|
79
|
-
when :localmysql_to_file_convert_all
|
80
|
-
get_new_test_config to_file: true
|
81
|
-
when :localmysql_to_db_convert_all
|
82
|
-
get_new_test_config
|
83
|
-
when :localmysql_to_db_convert_nothing
|
84
|
-
get_new_test_config tables: ['unobtainium'],
|
85
|
-
exclude_tables: ['kryptonite'],
|
86
|
-
suppress_data: true,
|
87
|
-
suppress_ddl: true
|
88
|
-
else
|
89
|
-
raise "Invalid label: #{name}"
|
90
|
-
end
|
91
|
-
end
|
data/test/units/option_test.rb
CHANGED
@@ -4,14 +4,11 @@ require File.expand_path '../test_helper', __dir__
|
|
4
4
|
require 'yaml'
|
5
5
|
|
6
6
|
class SettingTest < Test::Unit::TestCase
|
7
|
-
attr_reader :config_all_opts
|
8
|
-
|
9
|
-
def setup
|
10
|
-
@config_all_opts = YAML.load_file "#{__dir__}/../fixtures/config_all_options.yml"
|
11
|
-
end
|
12
|
-
|
13
7
|
def test_options_loaded
|
14
|
-
|
15
|
-
|
8
|
+
options = options_from_file
|
9
|
+
|
10
|
+
assert_equal false, options[:suppress_data]
|
11
|
+
assert_equal 'postgres', options[:destination][:username]
|
12
|
+
assert_equal 'somename', options[:destination][:database]
|
16
13
|
end
|
17
14
|
end
|
@@ -15,10 +15,12 @@ class PostgresFileWriterTest < Test::Unit::TestCase
|
|
15
15
|
File.delete destfile
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def test_file_writer
|
19
|
+
destination = { filename: '/tmp/test.sql' }
|
20
|
+
writer = Mysql2postgres::PostgresFileWriter.new @destfile, destination
|
20
21
|
writer.close
|
21
22
|
content = File.read destfile
|
23
|
+
|
22
24
|
assert_not_nil content.match("SET client_encoding = 'UTF8'")
|
23
25
|
assert_nil content.match('unobtanium')
|
24
26
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mysql2postgres
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max Lapshin <max@maxidoors.ru>
|
@@ -24,22 +24,8 @@ authors:
|
|
24
24
|
autorequire:
|
25
25
|
bindir: bin
|
26
26
|
cert_chain: []
|
27
|
-
date: 2022-07-
|
27
|
+
date: 2022-07-31 00:00:00.000000000 Z
|
28
28
|
dependencies:
|
29
|
-
- !ruby/object:Gem::Dependency
|
30
|
-
name: rake
|
31
|
-
requirement: !ruby/object:Gem::Requirement
|
32
|
-
requirements:
|
33
|
-
- - ">="
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: '0'
|
36
|
-
type: :runtime
|
37
|
-
prerelease: false
|
38
|
-
version_requirements: !ruby/object:Gem::Requirement
|
39
|
-
requirements:
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: '0'
|
43
29
|
- !ruby/object:Gem::Dependency
|
44
30
|
name: pg
|
45
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,19 +41,19 @@ dependencies:
|
|
55
41
|
- !ruby/object:Gem::Version
|
56
42
|
version: 1.2.2
|
57
43
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
44
|
+
name: rake
|
59
45
|
requirement: !ruby/object:Gem::Requirement
|
60
46
|
requirements:
|
61
|
-
- - "
|
47
|
+
- - ">="
|
62
48
|
- !ruby/object:Gem::Version
|
63
|
-
version: '0
|
49
|
+
version: '0'
|
64
50
|
type: :runtime
|
65
51
|
prerelease: false
|
66
52
|
version_requirements: !ruby/object:Gem::Requirement
|
67
53
|
requirements:
|
68
|
-
- - "
|
54
|
+
- - ">="
|
69
55
|
- !ruby/object:Gem::Version
|
70
|
-
version: '0
|
56
|
+
version: '0'
|
71
57
|
- !ruby/object:Gem::Dependency
|
72
58
|
name: ruby-mysql
|
73
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,62 +68,6 @@ dependencies:
|
|
82
68
|
- - "~>"
|
83
69
|
- !ruby/object:Gem::Version
|
84
70
|
version: '3.0'
|
85
|
-
- !ruby/object:Gem::Dependency
|
86
|
-
name: debug
|
87
|
-
requirement: !ruby/object:Gem::Requirement
|
88
|
-
requirements:
|
89
|
-
- - ">="
|
90
|
-
- !ruby/object:Gem::Version
|
91
|
-
version: '0'
|
92
|
-
type: :development
|
93
|
-
prerelease: false
|
94
|
-
version_requirements: !ruby/object:Gem::Requirement
|
95
|
-
requirements:
|
96
|
-
- - ">="
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
version: '0'
|
99
|
-
- !ruby/object:Gem::Dependency
|
100
|
-
name: rubocop-minitest
|
101
|
-
requirement: !ruby/object:Gem::Requirement
|
102
|
-
requirements:
|
103
|
-
- - ">="
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
version: '0'
|
106
|
-
type: :development
|
107
|
-
prerelease: false
|
108
|
-
version_requirements: !ruby/object:Gem::Requirement
|
109
|
-
requirements:
|
110
|
-
- - ">="
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
version: '0'
|
113
|
-
- !ruby/object:Gem::Dependency
|
114
|
-
name: rubocop-performance
|
115
|
-
requirement: !ruby/object:Gem::Requirement
|
116
|
-
requirements:
|
117
|
-
- - ">="
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: '0'
|
120
|
-
type: :development
|
121
|
-
prerelease: false
|
122
|
-
version_requirements: !ruby/object:Gem::Requirement
|
123
|
-
requirements:
|
124
|
-
- - ">="
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
version: '0'
|
127
|
-
- !ruby/object:Gem::Dependency
|
128
|
-
name: test-unit
|
129
|
-
requirement: !ruby/object:Gem::Requirement
|
130
|
-
requirements:
|
131
|
-
- - "~>"
|
132
|
-
- !ruby/object:Gem::Version
|
133
|
-
version: 3.5.3
|
134
|
-
type: :development
|
135
|
-
prerelease: false
|
136
|
-
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
requirements:
|
138
|
-
- - "~>"
|
139
|
-
- !ruby/object:Gem::Version
|
140
|
-
version: 3.5.3
|
141
71
|
description: Translates MySQL -> PostgreSQL
|
142
72
|
email: a.meindl@alphanodes.com
|
143
73
|
executables:
|
@@ -158,20 +88,22 @@ files:
|
|
158
88
|
- lib/mysql2postgres/postgres_file_writer.rb
|
159
89
|
- lib/mysql2postgres/postgres_writer.rb
|
160
90
|
- lib/mysql2postgres/version.rb
|
161
|
-
- lib/mysql2postgres/writer.rb
|
162
91
|
- mysql2postgres.gemspec
|
163
92
|
- test/fixtures/config_all_options.yml
|
93
|
+
- test/fixtures/config_min_options.yml
|
94
|
+
- test/fixtures/config_to_file.yml
|
164
95
|
- test/fixtures/seed_integration_tests.sql
|
165
96
|
- test/integration/convert_to_db_test.rb
|
166
97
|
- test/integration/convert_to_file_test.rb
|
167
98
|
- test/integration/converter_test.rb
|
168
|
-
- test/integration/
|
99
|
+
- test/integration/mysql_reader_connection_test.rb
|
169
100
|
- test/integration/mysql_reader_test.rb
|
170
|
-
- test/integration/
|
101
|
+
- test/integration/postgres_db_writer_test.rb
|
171
102
|
- test/test_helper.rb
|
103
|
+
- test/units/mysql_test.rb
|
172
104
|
- test/units/option_test.rb
|
173
105
|
- test/units/postgres_file_writer_test.rb
|
174
|
-
homepage: https://
|
106
|
+
homepage: https://github.com/AlphaNodes/mysql2postgres
|
175
107
|
licenses:
|
176
108
|
- MIT
|
177
109
|
metadata:
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path '../test_helper', __dir__
|
4
|
-
|
5
|
-
class PostgresDbWriterBaseTest < Test::Unit::TestCase
|
6
|
-
class << self
|
7
|
-
def startup
|
8
|
-
seed_test_database
|
9
|
-
@options = get_test_config_by_label :localmysql_to_db_convert_nothing
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_pg_connection
|
14
|
-
assert_nothing_raised do
|
15
|
-
mysql2postgres::PostgresDbWriter.new Tempfile.new('mysql2postgres_test_').path, @options
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|