mysql2postgres 0.3.2
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 +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
|