mysql2postgres 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +50 -0
- data/bin/mysql2psql +26 -0
- data/lib/mysql2psql/connection.rb +129 -0
- data/lib/mysql2psql/converter.rb +95 -0
- data/lib/mysql2psql/mysql_reader.rb +234 -0
- data/lib/mysql2psql/postgres_db_writer.rb +26 -0
- data/lib/mysql2psql/postgres_file_writer.rb +143 -0
- data/lib/mysql2psql/postgres_writer.rb +133 -0
- data/lib/mysql2psql/version.rb +5 -0
- data/lib/mysql2psql/writer.rb +9 -0
- data/lib/mysql2psql.rb +59 -0
- data/mysql2postgres.gemspec +80 -0
- data/test/fixtures/config_all_options.yml +39 -0
- data/test/fixtures/seed_integration_tests.sql +24 -0
- data/test/integration/convert_to_db_test.rb +23 -0
- data/test/integration/convert_to_file_test.rb +68 -0
- data/test/integration/converter_test.rb +21 -0
- data/test/integration/mysql_reader_base_test.rb +33 -0
- data/test/integration/mysql_reader_test.rb +40 -0
- data/test/integration/postgres_db_writer_base_test.rb +18 -0
- data/test/test_helper.rb +92 -0
- data/test/units/option_test.rb +17 -0
- data/test/units/postgres_file_writer_test.rb +25 -0
- metadata +199 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mysql2psql/postgres_writer'
|
4
|
+
|
5
|
+
class Mysql2psql
|
6
|
+
class PostgresFileWriter < PostgresWriter
|
7
|
+
def initialize(file)
|
8
|
+
super()
|
9
|
+
|
10
|
+
@f = File.open file, 'w+:UTF-8'
|
11
|
+
@f << <<~SQL_HEADER
|
12
|
+
-- MySQL 2 PostgreSQL dump\n
|
13
|
+
SET client_encoding = 'UTF8';
|
14
|
+
SET standard_conforming_strings = off;
|
15
|
+
SET check_function_bodies = false;
|
16
|
+
SET client_min_messages = warning;
|
17
|
+
|
18
|
+
SQL_HEADER
|
19
|
+
end
|
20
|
+
|
21
|
+
def truncate(table)
|
22
|
+
serial_key = nil
|
23
|
+
maxval = nil
|
24
|
+
|
25
|
+
table.columns.map do |column|
|
26
|
+
if column[:auto_increment]
|
27
|
+
serial_key = column[:name]
|
28
|
+
maxval = column[:maxval].to_i < 1 ? 1 : column[:maxval] + 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@f << <<~SQL_TRUNCATE
|
33
|
+
-- TRUNCATE #{table.name};
|
34
|
+
TRUNCATE #{PG::Connection.quote_ident table.name} CASCADE;
|
35
|
+
|
36
|
+
SQL_TRUNCATE
|
37
|
+
|
38
|
+
return unless serial_key
|
39
|
+
|
40
|
+
@f << <<~SQL_SERIAL
|
41
|
+
SELECT pg_catalog.setval(pg_get_serial_sequence('#{table.name}', '#{serial_key}'), #{maxval}, true);
|
42
|
+
SQL_SERIAL
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_table(table)
|
46
|
+
primary_keys = []
|
47
|
+
serial_key = nil
|
48
|
+
maxval = nil
|
49
|
+
|
50
|
+
columns = table.columns.map do |column|
|
51
|
+
if column[:auto_increment]
|
52
|
+
serial_key = column[:name]
|
53
|
+
maxval = column[:maxval].to_i < 1 ? 1 : column[:maxval] + 1
|
54
|
+
end
|
55
|
+
primary_keys << column[:name] if column[:primary_key]
|
56
|
+
" #{column_description column}"
|
57
|
+
end.join(",\n")
|
58
|
+
|
59
|
+
if serial_key
|
60
|
+
@f << <<~SQL_SEQUENCE
|
61
|
+
--
|
62
|
+
-- Name: #{table.name}_#{serial_key}_seq; Type: SEQUENCE; Schema: public
|
63
|
+
--
|
64
|
+
|
65
|
+
DROP SEQUENCE IF EXISTS #{table.name}_#{serial_key}_seq CASCADE;
|
66
|
+
|
67
|
+
CREATE SEQUENCE #{table.name}_#{serial_key}_seq
|
68
|
+
INCREMENT BY 1
|
69
|
+
NO MAXVALUE
|
70
|
+
NO MINVALUE
|
71
|
+
CACHE 1;
|
72
|
+
|
73
|
+
|
74
|
+
SELECT pg_catalog.setval('#{table.name}_#{serial_key}_seq', #{maxval}, true);
|
75
|
+
|
76
|
+
SQL_SEQUENCE
|
77
|
+
end
|
78
|
+
|
79
|
+
@f << <<~SQL_TABLE
|
80
|
+
-- Table: #{table.name}
|
81
|
+
|
82
|
+
-- DROP TABLE #{table.name};
|
83
|
+
DROP TABLE IF EXISTS #{PG::Connection.quote_ident table.name} CASCADE;
|
84
|
+
|
85
|
+
CREATE TABLE #{PG::Connection.quote_ident table.name} (
|
86
|
+
SQL_TABLE
|
87
|
+
|
88
|
+
@f << columns
|
89
|
+
|
90
|
+
if (primary_index = table.indexes.find { |index| index[:primary] })
|
91
|
+
@f << ",\n CONSTRAINT #{table.name}_pkey PRIMARY KEY(#{primary_index[:columns].map { |col| PG::Connection.quote_ident(col) }.join(', ')})"
|
92
|
+
end
|
93
|
+
|
94
|
+
@f << <<~SQL_OIDS
|
95
|
+
\n)
|
96
|
+
WITHOUT OIDS;
|
97
|
+
SQL_OIDS
|
98
|
+
|
99
|
+
table.indexes.each do |index|
|
100
|
+
next if index[:primary]
|
101
|
+
|
102
|
+
unique = index[:unique] ? 'UNIQUE ' : nil
|
103
|
+
@f << <<~SQL_INDEX
|
104
|
+
DROP INDEX IF EXISTS #{PG::Connection.quote_ident index[:name]} CASCADE;
|
105
|
+
CREATE #{unique}INDEX #{PG::Connection.quote_ident index[:name]}
|
106
|
+
ON #{PG::Connection.quote_ident table.name} (#{index[:columns].map { |col| PG::Connection.quote_ident(col) }.join(', ')});
|
107
|
+
SQL_INDEX
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def write_indexes(_table); end
|
112
|
+
|
113
|
+
def write_constraints(table)
|
114
|
+
table.foreign_keys.each do |key|
|
115
|
+
@f << "ALTER TABLE #{PG::Connection.quote_ident table.name} " \
|
116
|
+
"ADD FOREIGN KEY (#{key[:column].map { |c| PG::Connection.quote_ident(c) }.join(', ')}) " \
|
117
|
+
"REFERENCES #{PG::Connection.quote_ident key[:ref_table]}(#{key[:ref_column].map { |c| PG::Connection.quote_ident(c) }.join(', ')}) " \
|
118
|
+
"ON UPDATE #{key[:on_update]} ON DELETE #{key[:on_delete]};\n"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def write_contents(table, reader)
|
123
|
+
@f << <<~SQL_COPY
|
124
|
+
--
|
125
|
+
-- Data for Name: #{table.name}; Type: TABLE DATA; Schema: public
|
126
|
+
--
|
127
|
+
|
128
|
+
COPY "#{table.name}" (#{table.columns.map { |column| PG::Connection.quote_ident(column[:name]) }.join(', ')}) FROM stdin;
|
129
|
+
SQL_COPY
|
130
|
+
|
131
|
+
reader.paginated_read table, 1000 do |row, _counter|
|
132
|
+
process_row table, row
|
133
|
+
@f << row.join("\t")
|
134
|
+
@f << "\n"
|
135
|
+
end
|
136
|
+
@f << "\\.\n\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
def close
|
140
|
+
@f.close
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
require 'mysql2psql/writer'
|
5
|
+
|
6
|
+
class Mysql2psql
|
7
|
+
class PostgresWriter < Writer
|
8
|
+
def column_description(column)
|
9
|
+
"#{PG::Connection.quote_ident column[:name]} #{column_type_info column}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_type(column)
|
13
|
+
column_type_info(column).split.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def column_type_info(column)
|
17
|
+
return "integer DEFAULT nextval('#{column[:table_name]}_#{column[:name]}_seq'::regclass) NOT NULL" if column[:auto_increment]
|
18
|
+
|
19
|
+
default = if column[:default]
|
20
|
+
" DEFAULT #{column[:default].nil? ? 'NULL' : "'#{PG::Connection.escape column[:default]}'"}"
|
21
|
+
end
|
22
|
+
null = column[:null] ? '' : ' NOT NULL'
|
23
|
+
type = case column[:type]
|
24
|
+
# String types
|
25
|
+
when 'char'
|
26
|
+
default += '::char' if default
|
27
|
+
"character(#{column[:length]})"
|
28
|
+
when 'varchar'
|
29
|
+
default += '::character varying' if default
|
30
|
+
"character varying(#{column[:length]})"
|
31
|
+
# Integer and numeric types
|
32
|
+
when 'integer'
|
33
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default].to_i}" if default
|
34
|
+
'integer'
|
35
|
+
when 'bigint'
|
36
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default].to_i}" if default
|
37
|
+
'bigint'
|
38
|
+
when 'tinyint'
|
39
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default].to_i}" if default
|
40
|
+
'smallint'
|
41
|
+
when 'boolean'
|
42
|
+
default = " DEFAULT #{column[:default].to_i == 1 ? 'true' : 'false'}" if default
|
43
|
+
'boolean'
|
44
|
+
when 'float', 'float unsigned'
|
45
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default].to_f}" if default
|
46
|
+
'real'
|
47
|
+
when 'decimal'
|
48
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default]}" if default
|
49
|
+
"numeric(#{column[:length] || 10}, #{column[:decimals] || 0})"
|
50
|
+
when 'double precision'
|
51
|
+
default = " DEFAULT #{column[:default].nil? ? 'NULL' : column[:default]}" if default
|
52
|
+
'double precision'
|
53
|
+
when 'datetime'
|
54
|
+
default = nil
|
55
|
+
'timestamp without time zone'
|
56
|
+
when 'date'
|
57
|
+
default = nil
|
58
|
+
'date'
|
59
|
+
when 'timestamp'
|
60
|
+
default = ' DEFAULT CURRENT_TIMESTAMP' if column[:default] == 'CURRENT_TIMESTAMP'
|
61
|
+
default = " DEFAULT '1970-01-01 00:00'" if column[:default] == '0000-00-00 00:00'
|
62
|
+
default = " DEFAULT '1970-01-01 00:00:00'" if column[:default] == '0000-00-00 00:00:00'
|
63
|
+
'timestamp without time zone'
|
64
|
+
when 'time'
|
65
|
+
default = ' DEFAULT NOW()' if default
|
66
|
+
'time without time zone'
|
67
|
+
when 'blob', 'longblob', 'mediumblob', 'tinyblob', 'varbinary'
|
68
|
+
'bytea'
|
69
|
+
when 'text', 'tinytext', 'mediumtext', 'longtext'
|
70
|
+
'text'
|
71
|
+
when /^enum/
|
72
|
+
default += '::character varying' if default
|
73
|
+
enum = column[:type].gsub(/enum|\(|\)/, '')
|
74
|
+
max_enum_size = enum.split(',').map { |check| check.size - 2 }.max
|
75
|
+
"character varying(#{max_enum_size}) check( \"#{column[:name]}\" in (#{enum}))"
|
76
|
+
when 'geometry', 'multipolygon'
|
77
|
+
'geometry'
|
78
|
+
else
|
79
|
+
puts "Unknown #{column.inspect}"
|
80
|
+
column[:type].inspect
|
81
|
+
return ''
|
82
|
+
end
|
83
|
+
|
84
|
+
"#{type}#{default}#{null}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def process_row(table, row)
|
88
|
+
table.columns.each_with_index do |column, index|
|
89
|
+
row[index] = Time.at(row[index]).utc.strftime('%H:%M:%S') if column[:type] == 'time' && row[index]
|
90
|
+
|
91
|
+
if row[index].is_a? Time
|
92
|
+
row[index] = row[index].to_s.gsub '0000-00-00 00:00', '1970-01-01 00:00'
|
93
|
+
row[index] = row[index].to_s.gsub '0000-00-00 00:00:00', '1970-01-01 00:00:00'
|
94
|
+
end
|
95
|
+
|
96
|
+
if column_type(column) == 'boolean'
|
97
|
+
row[index] = if row[index] == 1
|
98
|
+
't'
|
99
|
+
else
|
100
|
+
row[index].zero? ? 'f' : row[index]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
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
|
126
|
+
|
127
|
+
row[index] = '\N' unless row[index]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def truncate(_table) end
|
132
|
+
end
|
133
|
+
end
|
data/lib/mysql2psql.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pg'
|
4
|
+
require 'pg_ext'
|
5
|
+
require 'pg/exceptions'
|
6
|
+
require 'pg/constants'
|
7
|
+
require 'pg/connection'
|
8
|
+
require 'pg/result'
|
9
|
+
|
10
|
+
require 'mysql2psql/version'
|
11
|
+
require 'mysql2psql/converter'
|
12
|
+
require 'mysql2psql/mysql_reader'
|
13
|
+
require 'mysql2psql/writer'
|
14
|
+
require 'mysql2psql/postgres_writer'
|
15
|
+
require 'mysql2psql/postgres_file_writer'
|
16
|
+
require 'mysql2psql/postgres_db_writer'
|
17
|
+
|
18
|
+
require 'debug' if ENV.fetch('ENABLE_DEBUG', nil) == '1'
|
19
|
+
|
20
|
+
class Mysql2psql
|
21
|
+
attr_reader :options, :reader, :writer
|
22
|
+
|
23
|
+
def initialize(yaml)
|
24
|
+
@options = build_options yaml
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_options(yaml)
|
28
|
+
yaml.transform_keys(&:to_sym).tap do |opts|
|
29
|
+
opts[:mysql].transform_keys!(&:to_sym)
|
30
|
+
opts[:destination].transform_keys!(&:to_sym)
|
31
|
+
opts[:destination].each do |env, settings|
|
32
|
+
opts[:destination][env] = settings.transform_keys(&:to_sym)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def send_file_to_postgres(path)
|
38
|
+
connection = Connection.new options
|
39
|
+
connection.load_file path
|
40
|
+
end
|
41
|
+
|
42
|
+
def convert
|
43
|
+
@reader = MysqlReader.new options
|
44
|
+
|
45
|
+
tag = Time.new.strftime '%Y%m%d-%H%M%S'
|
46
|
+
|
47
|
+
path = './'
|
48
|
+
path = options[:dump_file_directory] if options[:dump_file_directory]
|
49
|
+
|
50
|
+
filename = File.expand_path File.join(path, "output_#{tag}.sql")
|
51
|
+
puts "Dumpfile: #{filename}"
|
52
|
+
|
53
|
+
@writer = PostgresDbWriter.new filename, options
|
54
|
+
|
55
|
+
Converter.new(reader, writer, options).convert
|
56
|
+
|
57
|
+
File.delete filename if options[:remove_dump_file] && File.exist?(filename)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path '../lib', __FILE__
|
4
|
+
puts lib
|
5
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
|
6
|
+
require 'mysql2psql/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'mysql2postgres'
|
10
|
+
s.version = Mysql2psql::VERSION
|
11
|
+
s.licenses = ['MIT']
|
12
|
+
|
13
|
+
s.authors = [
|
14
|
+
'Max Lapshin <max@maxidoors.ru>',
|
15
|
+
'Anton Ageev <anton@ageev.name>',
|
16
|
+
'Samuel Tribehou <cracoucax@gmail.com>',
|
17
|
+
'Marco Nenciarini <marco.nenciarini@devise.it>',
|
18
|
+
'James Nobis <jnobis@jnobis.controldocs.com>',
|
19
|
+
'quel <github@quelrod.net>',
|
20
|
+
'Holger Amann <keeney@fehu.org>',
|
21
|
+
'Maxim Dobriakov <closer.main@gmail.com>',
|
22
|
+
'Michael Kimsal <mgkimsal@gmail.com>',
|
23
|
+
'Jacob Coby <jcoby@portallabs.com>',
|
24
|
+
'Neszt Tibor <neszt@tvnetwork.hu>',
|
25
|
+
'Miroslav Kratochvil <exa.exa@gmail.com>',
|
26
|
+
'Paul Gallagher <gallagher.paul@gmail.com>',
|
27
|
+
'Alex C Jokela <ajokela@umn.edu>',
|
28
|
+
'Peter Clark <pclark@umn.edu>',
|
29
|
+
'Juga Paazmaya <olavic@gmail.com>',
|
30
|
+
'Alexander Meindl <a.meindl@alphanodes.com'
|
31
|
+
]
|
32
|
+
s.description = 'Translates MySQL -> PostgreSQL'
|
33
|
+
s.email = 'a.meindl@alphanodes.com'
|
34
|
+
s.metadata = { 'rubygems_mfa_required' => 'true' }
|
35
|
+
s.executables = ['mysql2psql']
|
36
|
+
s.required_ruby_version = '>= 2.7'
|
37
|
+
|
38
|
+
s.files = [
|
39
|
+
'.gitignore',
|
40
|
+
'MIT-LICENSE',
|
41
|
+
'README.md',
|
42
|
+
'Rakefile',
|
43
|
+
'bin/mysql2psql',
|
44
|
+
'lib/mysql2psql.rb',
|
45
|
+
'lib/mysql2psql/converter.rb',
|
46
|
+
'lib/mysql2psql/connection.rb',
|
47
|
+
'lib/mysql2psql/mysql_reader.rb',
|
48
|
+
'lib/mysql2psql/postgres_db_writer.rb',
|
49
|
+
'lib/mysql2psql/postgres_file_writer.rb',
|
50
|
+
'lib/mysql2psql/postgres_db_writer.rb',
|
51
|
+
'lib/mysql2psql/postgres_writer.rb',
|
52
|
+
'lib/mysql2psql/version.rb',
|
53
|
+
'lib/mysql2psql/writer.rb',
|
54
|
+
'mysql2postgres.gemspec',
|
55
|
+
'test/fixtures/config_all_options.yml',
|
56
|
+
'test/fixtures/seed_integration_tests.sql',
|
57
|
+
'test/integration/convert_to_db_test.rb',
|
58
|
+
'test/integration/convert_to_file_test.rb',
|
59
|
+
'test/integration/converter_test.rb',
|
60
|
+
'test/integration/mysql_reader_base_test.rb',
|
61
|
+
'test/integration/mysql_reader_test.rb',
|
62
|
+
'test/integration/postgres_db_writer_base_test.rb',
|
63
|
+
'test/units/option_test.rb',
|
64
|
+
'test/units/postgres_file_writer_test.rb',
|
65
|
+
'test/test_helper.rb'
|
66
|
+
]
|
67
|
+
s.homepage = 'https://code.alphanodes.com/alphanodes/mysql2psql'
|
68
|
+
s.rdoc_options = ['--charset=UTF-8']
|
69
|
+
s.require_paths = ['lib']
|
70
|
+
s.summary = 'MySQL to PostgreSQL Data Translation'
|
71
|
+
|
72
|
+
s.add_dependency 'rake'
|
73
|
+
s.add_runtime_dependency 'pg', '~> 1.2.2'
|
74
|
+
s.add_runtime_dependency 'postgres-pr', '~> 0.7'
|
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
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
mysql:
|
2
|
+
hostname: localhost
|
3
|
+
port: 3306
|
4
|
+
socket: /tmp/mysql.sock
|
5
|
+
username: somename
|
6
|
+
password: secretpassword
|
7
|
+
database: somename
|
8
|
+
|
9
|
+
|
10
|
+
destination:
|
11
|
+
# if file is given, output goes to file, else postgres
|
12
|
+
file: somefile
|
13
|
+
test:
|
14
|
+
hostname: localhost
|
15
|
+
port: 5432
|
16
|
+
username: somename
|
17
|
+
password: secretpassword
|
18
|
+
database: somename
|
19
|
+
|
20
|
+
# if tables is given, only the listed tables will be converted. leave empty to convert all tables.
|
21
|
+
tables:
|
22
|
+
- table1
|
23
|
+
- table2
|
24
|
+
- table3
|
25
|
+
- table4
|
26
|
+
|
27
|
+
# if exclude_tables is given, exclude the listed tables from the conversion.
|
28
|
+
exclude_tables:
|
29
|
+
- table5
|
30
|
+
- table6
|
31
|
+
|
32
|
+
# if suppress_data is true, only the schema definition will be exported/migrated, and not the data
|
33
|
+
suppress_data: true
|
34
|
+
|
35
|
+
# if suppress_ddl is true, only the data will be exported/imported, and not the schema
|
36
|
+
suppress_ddl: false
|
37
|
+
|
38
|
+
# if force_truncate is true, forces a table truncate before table loading
|
39
|
+
force_truncate: false
|
@@ -0,0 +1,24 @@
|
|
1
|
+
-- seed data for integration tests
|
2
|
+
|
3
|
+
DROP TABLE IF EXISTS numeric_types_basics;
|
4
|
+
CREATE TABLE numeric_types_basics (
|
5
|
+
id int,
|
6
|
+
f_tinyint TINYINT,
|
7
|
+
f_smallint SMALLINT,
|
8
|
+
f_mediumint MEDIUMINT,
|
9
|
+
f_int INT,
|
10
|
+
f_integer INTEGER,
|
11
|
+
f_bigint BIGINT,
|
12
|
+
f_real REAL,
|
13
|
+
f_double DOUBLE,
|
14
|
+
f_float FLOAT,
|
15
|
+
f_decimal DECIMAL,
|
16
|
+
f_numeric NUMERIC
|
17
|
+
);
|
18
|
+
|
19
|
+
INSERT INTO numeric_types_basics VALUES
|
20
|
+
(1,1,1,1,1,1,1,1,1,1,1,1),
|
21
|
+
(2,2,2,2,2,2,2,2,2,2,2,2),
|
22
|
+
(23,23,23,23,23,23,23,23,23,23,23,23);
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class ConvertToDbTest < Test::Unit::TestCase
|
6
|
+
class << self
|
7
|
+
def startup
|
8
|
+
seed_test_database
|
9
|
+
@options = get_test_config_by_label :localmysql_to_db_convert_all
|
10
|
+
@mysql2psql = Mysql2psql.new @options
|
11
|
+
@mysql2psql.convert
|
12
|
+
@mysql2psql.writer.open
|
13
|
+
end
|
14
|
+
|
15
|
+
def shutdown
|
16
|
+
@@mysql2psql.writer.close
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_table_creation
|
21
|
+
assert_true @mysql2psql.writer.exists?('numeric_types_basics')
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class ConvertToFileTest < Test::Unit::TestCase
|
6
|
+
class << self
|
7
|
+
def startup
|
8
|
+
seed_test_database
|
9
|
+
@options = get_test_config_by_label :localmysql_to_file_convert_all
|
10
|
+
@mysql2psql = Mysql2psql.new @options
|
11
|
+
@mysql2psql.convert
|
12
|
+
@content = File.read @mysql2psql.options[:destfile]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
@@content
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_table_creation
|
21
|
+
assert_not_nil content.match('DROP TABLE IF EXISTS "numeric_types_basics" CASCADE')
|
22
|
+
assert_not_nil content.include?('CREATE TABLE "numeric_types_basics"')
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_basic_numerics_tinyint
|
26
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_tinyint" smallint,.*\)', Regexp::MULTILINE).match(content)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_basic_numerics_smallint
|
30
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_smallint" integer,.*\)', Regexp::MULTILINE).match(content)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_basic_numerics_mediumint
|
34
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_mediumint" integer,.*\)', Regexp::MULTILINE).match(content)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_basic_numerics_int
|
38
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_int" integer,.*\)', Regexp::MULTILINE).match(content)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_basic_numerics_integer
|
42
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_integer" integer,.*\)', Regexp::MULTILINE).match(content)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_basic_numerics_bigint
|
46
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_bigint" bigint,.*\)', Regexp::MULTILINE).match(content)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_basic_numerics_real
|
50
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_real" double precision,.*\)', Regexp::MULTILINE).match(content)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_basic_numerics_double
|
54
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_double" double precision,.*\)', Regexp::MULTILINE).match(content)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_basic_numerics_float
|
58
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_float" double precision,.*\)', Regexp::MULTILINE).match(content)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_basic_numerics_decimal
|
62
|
+
assert_not_nil Regexp.new('CREATE TABLE "numeric_types_basics".*"f_decimal" numeric\(10, 0\),.*\)', Regexp::MULTILINE).match(content)
|
63
|
+
end
|
64
|
+
|
65
|
+
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).match(content)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class ConverterTest < Test::Unit::TestCase
|
6
|
+
class << self
|
7
|
+
def startup
|
8
|
+
seed_test_database
|
9
|
+
@options = get_test_config_by_label :localmysql_to_file_convert_nothing
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_new_converter
|
14
|
+
assert_nothing_raised do
|
15
|
+
reader = get_test_reader @options
|
16
|
+
writer = get_test_file_writer @options
|
17
|
+
converter = Mysql2psql::Converter.new reader, writer, @options
|
18
|
+
assert_equal 0, converter.convert
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class MysqlReaderBaseTest < Test::Unit::TestCase
|
6
|
+
class << self
|
7
|
+
def startup
|
8
|
+
seed_test_database
|
9
|
+
@options = get_test_config_by_label :localmysql_to_file_convert_nothing
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_mysql_connection
|
14
|
+
assert_nothing_raised do
|
15
|
+
Mysql2psql::MysqlReader.new @options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_mysql_reconnect
|
20
|
+
assert_nothing_raised do
|
21
|
+
reader = Mysql2psql::MysqlReader.new @options
|
22
|
+
reader.reconnect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_mysql_connection_without_port
|
27
|
+
assert_nothing_raised do
|
28
|
+
@options[:mysql][:port] = ''
|
29
|
+
@options[:mysql][:socket] = ''
|
30
|
+
Mysql2psql::MysqlReader.new @options
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path '../test_helper', __dir__
|
4
|
+
|
5
|
+
class MysqlReaderTest < Test::Unit::TestCase
|
6
|
+
class << self
|
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
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_db_connection
|
15
|
+
assert_nothing_raised do
|
16
|
+
@reader.mysql.ping
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_tables_collection
|
21
|
+
values = @reader.tables.select { |t| t.name == 'numeric_types_basics' }
|
22
|
+
assert_true values.length == 1
|
23
|
+
assert_equal 'numeric_types_basics', values[0].name
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_paginated_read
|
27
|
+
expected_rows = 3
|
28
|
+
page_size = 2
|
29
|
+
expected_pages = (1.0 * expected_rows / page_size).ceil
|
30
|
+
|
31
|
+
row_count = my_row_count = 0
|
32
|
+
table = @reader.tables.find { |t| t.name == 'numeric_types_basics' }
|
33
|
+
@reader.paginated_read table, page_size do |_row, counter|
|
34
|
+
row_count = counter
|
35
|
+
my_row_count += 1
|
36
|
+
end
|
37
|
+
assert_equal expected_rows, row_count
|
38
|
+
assert_equal expected_rows, my_row_count
|
39
|
+
end
|
40
|
+
end
|