benhutton-mysql2psql 0.2.0
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/CHANGELOG +19 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +34 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +148 -0
- data/Rakefile +95 -0
- data/bin/mysql2psql +7 -0
- data/lib/mysql2psql/config.rb +142 -0
- data/lib/mysql2psql/config_base.rb +39 -0
- data/lib/mysql2psql/converter.rb +62 -0
- data/lib/mysql2psql/errors.rb +16 -0
- data/lib/mysql2psql/mysql_reader.rb +230 -0
- data/lib/mysql2psql/postgres_db_writer.rb +201 -0
- data/lib/mysql2psql/postgres_file_writer.rb +152 -0
- data/lib/mysql2psql/postgres_writer.rb +168 -0
- data/lib/mysql2psql/version.rb +9 -0
- data/lib/mysql2psql/writer.rb +6 -0
- data/lib/mysql2psql.rb +41 -0
- data/mysql2psql.gemspec +106 -0
- data/test/fixtures/config_all_options.yml +50 -0
- data/test/fixtures/seed_integration_tests.sql +119 -0
- data/test/integration/convert_to_db_test.rb +137 -0
- data/test/integration/convert_to_file_test.rb +121 -0
- data/test/integration/converter_test.rb +28 -0
- data/test/integration/mysql_reader_base_test.rb +34 -0
- data/test/integration/mysql_reader_test.rb +41 -0
- data/test/integration/postgres_db_writer_base_test.rb +22 -0
- data/test/lib/ext_test_unit.rb +33 -0
- data/test/lib/test_helper.rb +131 -0
- data/test/units/config_base_test.rb +49 -0
- data/test/units/config_test.rb +73 -0
- data/test/units/postgres_file_writer_test.rb +29 -0
- metadata +220 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'pg'
|
2
|
+
|
3
|
+
require 'mysql2psql/writer'
|
4
|
+
|
5
|
+
class Mysql2psql
|
6
|
+
|
7
|
+
class PostgresWriter < Writer
|
8
|
+
def column_description(column, options)
|
9
|
+
"#{PGconn.quote_ident(column[:name])} #{column_type_info(column, options)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_type(column, options={})
|
13
|
+
if column[:auto_increment]
|
14
|
+
'integer'
|
15
|
+
else
|
16
|
+
case column[:type]
|
17
|
+
when 'char'
|
18
|
+
"character(#{column[:length]})"
|
19
|
+
when 'varchar'
|
20
|
+
"character varying(#{column[:length]})"
|
21
|
+
when /tinyint|smallint/
|
22
|
+
'smallint'
|
23
|
+
when 'real', /float/, 'double precision'
|
24
|
+
'double precision'
|
25
|
+
when 'decimal'
|
26
|
+
# TODO: seven1m thinks "real" instead?
|
27
|
+
"numeric(#{column[:length] || 10}, #{column[:decimals] || 5})"
|
28
|
+
when 'datetime', 'timestamp'
|
29
|
+
"timestamp with#{options[:use_timezones] ? '' : 'out'} time zone"
|
30
|
+
when 'time'
|
31
|
+
"time with#{options[:use_timezones] ? '' : 'out'} time zone"
|
32
|
+
when 'tinyblob', 'mediumblob', 'longblob', 'blob', 'varbinary'
|
33
|
+
'bytea'
|
34
|
+
when 'tinytext', 'mediumtext', 'longtext', 'text'
|
35
|
+
'text'
|
36
|
+
when /^enum/
|
37
|
+
enum = column[:type].gsub(/enum|\(|\)/, '')
|
38
|
+
max_enum_size = enum.split(',').map{ |check| check.size() -2}.sort[-1]
|
39
|
+
"character varying(#{max_enum_size}) check( #{column[:name]} in (#{enum}))"
|
40
|
+
when 'integer', 'bigint', 'boolean', 'date'
|
41
|
+
column[:type]
|
42
|
+
else
|
43
|
+
puts "Unknown #{column.inspect}"
|
44
|
+
''
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def column_default(column)
|
50
|
+
if column[:auto_increment]
|
51
|
+
"nextval('#{column[:table_name]}_#{column[:name]}_seq'::regclass)"
|
52
|
+
elsif column[:default]
|
53
|
+
case column[:type]
|
54
|
+
when 'char'
|
55
|
+
"'#{PGconn.escape(column[:default])}'::char"
|
56
|
+
when 'varchar', /^enum/
|
57
|
+
"'#{PGconn.escape(column[:default])}'::character varying"
|
58
|
+
when 'integer', 'bigint', /tinyint|smallint/
|
59
|
+
column[:default].to_i
|
60
|
+
when 'real', /float/
|
61
|
+
column[:default].to_f
|
62
|
+
when 'decimal', 'double precision'
|
63
|
+
column[:default]
|
64
|
+
when 'boolean'
|
65
|
+
case column[:default]
|
66
|
+
when nil
|
67
|
+
'NULL'
|
68
|
+
when 0, '0', "b'0'"
|
69
|
+
'false'
|
70
|
+
else
|
71
|
+
# Case for 1, '1', "b'1'" (for BIT(1) the data type), or anything non-nil and non-zero (for the TINYINT(1) type)
|
72
|
+
'true'
|
73
|
+
end
|
74
|
+
when 'timestamp', 'datetime', 'date'
|
75
|
+
case column[:default]
|
76
|
+
when 'CURRENT_TIMESTAMP'
|
77
|
+
'CURRENT_TIMESTAMP'
|
78
|
+
when '0000-00-00'
|
79
|
+
"'1970-01-01'"
|
80
|
+
when '0000-00-00 00:00'
|
81
|
+
"'1970-01-01 00:00'"
|
82
|
+
when '0000-00-00 00:00:00'
|
83
|
+
"'1970-01-01 00:00:00'"
|
84
|
+
else
|
85
|
+
"'#{PGconn.escape(column[:default])}'"
|
86
|
+
end
|
87
|
+
when 'time'
|
88
|
+
"'#{PGconn.escape(column[:default])}'"
|
89
|
+
else
|
90
|
+
# TODO: column[:default] will never be nil here.
|
91
|
+
# Perhaps we should also issue a warning if this case is encountered.
|
92
|
+
"#{column[:default] == nil ? 'NULL' : "'"+PGconn.escape(column[:default])+"'"}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def column_type_info(column, options)
|
98
|
+
type = column_type(column, options)
|
99
|
+
if type
|
100
|
+
not_null = !column[:null] || column[:auto_increment] ? ' NOT NULL' : ''
|
101
|
+
default = column[:default] || column[:auto_increment] ? " DEFAULT #{column_default(column)}" : ''
|
102
|
+
"#{type}#{default}#{not_null}"
|
103
|
+
else
|
104
|
+
''
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_row(table, row)
|
109
|
+
table.columns.each_with_index do |column, index|
|
110
|
+
if column[:type] == 'time'
|
111
|
+
begin
|
112
|
+
row[index] = "%02d:%02d:%02d" % [row[index].hour, row[index].minute, row[index].second]
|
113
|
+
rescue
|
114
|
+
# Don't fail on nil date/time.
|
115
|
+
end
|
116
|
+
elsif row[index].is_a?(Mysql::Time)
|
117
|
+
row[index] = row[index].to_s.gsub('0000-00-00 00:00', '1970-01-01 00:00')
|
118
|
+
row[index] = row[index].to_s.gsub('0000-00-00 00:00:00', '1970-01-01 00:00:00')
|
119
|
+
elsif column[:type] == 'boolean'
|
120
|
+
row[index] = (
|
121
|
+
case row[index]
|
122
|
+
when nil
|
123
|
+
'\N' # See note below about null values.
|
124
|
+
when 0, "\0"
|
125
|
+
'f'
|
126
|
+
else
|
127
|
+
# Case for 1, "\1" (for the BIT(1) data type), or anything non-nil and non-zero (to handle the TINYINT(1) type)
|
128
|
+
't'
|
129
|
+
end
|
130
|
+
)
|
131
|
+
elsif row[index].is_a?(String)
|
132
|
+
if column_type(column) == "bytea"
|
133
|
+
row[index] = PGconn.escape_bytea(row[index])
|
134
|
+
else
|
135
|
+
if row[index] == '\N' || row[index] == '\.'
|
136
|
+
row[index] = '\\' + row[index] # Escape our two PostgreSQL-text-mode-special strings.
|
137
|
+
else
|
138
|
+
# Awesome side-effect producing conditional. Don't do this at home.
|
139
|
+
unless row[index].gsub!(/\0/, '').nil?
|
140
|
+
puts "Removed null bytes from string since PostgreSQL TEXT types don't allow the storage of null bytes."
|
141
|
+
end
|
142
|
+
|
143
|
+
row[index] = row[index].dump
|
144
|
+
row[index] = row[index].slice(1, row[index].size-2)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
elsif row[index].nil?
|
148
|
+
# Note: '\N' not "\N" is correct here:
|
149
|
+
# The string containing the literal backslash followed by 'N'
|
150
|
+
# represents database NULL value in PostgreSQL's text mode.
|
151
|
+
row[index] = '\N'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def truncate(table)
|
157
|
+
end
|
158
|
+
|
159
|
+
def sqlfor_set_serial_sequence(table, serial_key_seq, max_value)
|
160
|
+
"SELECT pg_catalog.setval('#{serial_key_seq}', #{max_value}, true);"
|
161
|
+
end
|
162
|
+
def sqlfor_reset_serial_sequence(table, serial_key, max_value)
|
163
|
+
"SELECT pg_catalog.setval(pg_get_serial_sequence('#{table.name}', '#{serial_key}'), #{max_value}, true);"
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
data/lib/mysql2psql.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'mysql2psql/errors'
|
2
|
+
require 'mysql2psql/version'
|
3
|
+
require 'mysql2psql/config'
|
4
|
+
require 'mysql2psql/converter'
|
5
|
+
require 'mysql2psql/mysql_reader'
|
6
|
+
require 'mysql2psql/writer'
|
7
|
+
require 'mysql2psql/postgres_writer'
|
8
|
+
require 'mysql2psql/postgres_db_writer.rb'
|
9
|
+
require 'mysql2psql/postgres_file_writer.rb'
|
10
|
+
|
11
|
+
|
12
|
+
class Mysql2psql
|
13
|
+
|
14
|
+
attr_reader :options, :reader, :writer
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
help if args.length==1 && args[0] =~ /^-.?|^-*he?l?p?$/i
|
18
|
+
configfile = args[0] || File.expand_path('mysql2psql.yml')
|
19
|
+
@options = Config.new( configfile, true )
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert
|
23
|
+
@reader = MysqlReader.new( options )
|
24
|
+
|
25
|
+
if options.destfile(nil)
|
26
|
+
@writer = PostgresFileWriter.new(options.destfile)
|
27
|
+
else
|
28
|
+
@writer = PostgresDbWriter.new(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
Converter.new(reader, writer, options).convert
|
32
|
+
end
|
33
|
+
|
34
|
+
def help
|
35
|
+
puts <<EOS
|
36
|
+
MySQL to PostgreSQL Conversion
|
37
|
+
|
38
|
+
EOS
|
39
|
+
exit -2
|
40
|
+
end
|
41
|
+
end
|
data/mysql2psql.gemspec
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mysql2psql}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Max Lapshin <max@maxidoors.ru>", "Anton Ageev <anton@ageev.name>", "Samuel Tribehou <cracoucax@gmail.com>", "Marco Nenciarini <marco.nenciarini@devise.it>", "James Nobis <jnobis@jnobis.controldocs.com>", "quel <github@quelrod.net>", "Holger Amann <keeney@fehu.org>", "Maxim Dobriakov <closer.main@gmail.com>", "Michael Kimsal <mgkimsal@gmail.com>", "Jacob Coby <jcoby@portallabs.com>", "Neszt Tibor <neszt@tvnetwork.hu>", "Miroslav Kratochvil <exa.exa@gmail.com>", "Paul Gallagher <gallagher.paul@gmail.com>", "James Coleman <jtc331@gmail.com>", "Aaron Peckham", "James Tippett", "Tim Morgan", "dakhota", "Matthew Soldo"]
|
12
|
+
s.date = %q{2012-02-13}
|
13
|
+
s.default_executable = %q{mysql2psql}
|
14
|
+
s.description = %q{It can create postgresql dump from mysql database or directly load data from mysql to
|
15
|
+
postgresql (at about 100 000 records per minute). Translates most data types and indexes.}
|
16
|
+
s.email = %q{gallagher.paul@gmail.com}
|
17
|
+
s.executables = ["mysql2psql"]
|
18
|
+
s.extra_rdoc_files = [
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"CHANGELOG",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"MIT-LICENSE",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"bin/mysql2psql",
|
29
|
+
"lib/mysql2psql.rb",
|
30
|
+
"lib/mysql2psql/config.rb",
|
31
|
+
"lib/mysql2psql/config_base.rb",
|
32
|
+
"lib/mysql2psql/converter.rb",
|
33
|
+
"lib/mysql2psql/errors.rb",
|
34
|
+
"lib/mysql2psql/mysql_reader.rb",
|
35
|
+
"lib/mysql2psql/postgres_db_writer.rb",
|
36
|
+
"lib/mysql2psql/postgres_file_writer.rb",
|
37
|
+
"lib/mysql2psql/postgres_writer.rb",
|
38
|
+
"lib/mysql2psql/version.rb",
|
39
|
+
"lib/mysql2psql/writer.rb",
|
40
|
+
"mysql2psql.gemspec",
|
41
|
+
"test/fixtures/config_all_options.yml",
|
42
|
+
"test/fixtures/seed_integration_tests.sql",
|
43
|
+
"test/integration/convert_to_db_test.rb",
|
44
|
+
"test/integration/convert_to_file_test.rb",
|
45
|
+
"test/integration/converter_test.rb",
|
46
|
+
"test/integration/mysql_reader_base_test.rb",
|
47
|
+
"test/integration/mysql_reader_test.rb",
|
48
|
+
"test/integration/postgres_db_writer_base_test.rb",
|
49
|
+
"test/lib/ext_test_unit.rb",
|
50
|
+
"test/lib/test_helper.rb",
|
51
|
+
"test/units/config_base_test.rb",
|
52
|
+
"test/units/config_test.rb",
|
53
|
+
"test/units/postgres_file_writer_test.rb"
|
54
|
+
]
|
55
|
+
s.homepage = %q{https://github.com/tardate/mysql2postgres}
|
56
|
+
s.require_paths = ["lib"]
|
57
|
+
s.rubygems_version = %q{1.6.2}
|
58
|
+
s.summary = %q{Tool for converting mysql database to postgresql}
|
59
|
+
s.test_files = [
|
60
|
+
"test/integration/convert_to_db_test.rb",
|
61
|
+
"test/integration/convert_to_file_test.rb",
|
62
|
+
"test/integration/converter_test.rb",
|
63
|
+
"test/integration/mysql_reader_base_test.rb",
|
64
|
+
"test/integration/mysql_reader_test.rb",
|
65
|
+
"test/integration/postgres_db_writer_base_test.rb",
|
66
|
+
"test/lib/ext_test_unit.rb",
|
67
|
+
"test/lib/test_helper.rb",
|
68
|
+
"test/units/config_base_test.rb",
|
69
|
+
"test/units/config_test.rb",
|
70
|
+
"test/units/postgres_file_writer_test.rb"
|
71
|
+
]
|
72
|
+
|
73
|
+
if s.respond_to? :specification_version then
|
74
|
+
s.specification_version = 3
|
75
|
+
|
76
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
77
|
+
s.add_runtime_dependency(%q<mysql2psql>, [">= 0"])
|
78
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.21"])
|
79
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
80
|
+
s.add_development_dependency(%q<test-unit>, [">= 2.1.1"])
|
81
|
+
s.add_development_dependency(%q<rake>, ["~> 0.9.2.2"])
|
82
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
83
|
+
s.add_runtime_dependency(%q<mysql>, ["= 2.8.1"])
|
84
|
+
s.add_runtime_dependency(%q<pg>)
|
85
|
+
else
|
86
|
+
s.add_dependency(%q<mysql2psql>, [">= 0"])
|
87
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.21"])
|
88
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
89
|
+
s.add_dependency(%q<test-unit>, [">= 2.1.1"])
|
90
|
+
s.add_dependency(%q<rake>, ["~> 0.9.2.2"])
|
91
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
92
|
+
s.add_dependency(%q<mysql>, ["= 2.8.1"])
|
93
|
+
s.add_dependency(%q<pg>)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
s.add_dependency(%q<mysql2psql>, [">= 0"])
|
97
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.21"])
|
98
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
99
|
+
s.add_dependency(%q<test-unit>, [">= 2.1.1"])
|
100
|
+
s.add_dependency(%q<rake>, ["~> 0.9.2.2"])
|
101
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
102
|
+
s.add_dependency(%q<mysql>, ["= 2.8.1"])
|
103
|
+
s.add_dependency(%q<pg>)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
mysql:
|
2
|
+
hostname: localhost
|
3
|
+
port: 3306
|
4
|
+
socket:
|
5
|
+
username: somename
|
6
|
+
password: secretpassword
|
7
|
+
database: somename
|
8
|
+
|
9
|
+
destination:
|
10
|
+
# if file is given, output goes to file, else postgres
|
11
|
+
file: somefile
|
12
|
+
postgres:
|
13
|
+
hostname: localhost
|
14
|
+
port: 5432
|
15
|
+
username: somename
|
16
|
+
password: secretpassword
|
17
|
+
database: somename
|
18
|
+
|
19
|
+
# if tables is given, only the listed tables will be converted. leave empty to convert all tables.
|
20
|
+
tables:
|
21
|
+
- table1
|
22
|
+
- table2
|
23
|
+
- table3
|
24
|
+
- table4
|
25
|
+
|
26
|
+
# if exclude_tables is given, exclude the listed tables from the conversion.
|
27
|
+
exclude_tables:
|
28
|
+
- table5
|
29
|
+
- table6
|
30
|
+
|
31
|
+
# if suppress_data is true, only the schema definition will be exported/migrated, and not the data
|
32
|
+
suppress_data: true
|
33
|
+
|
34
|
+
# if suppress_ddl is true, only the data will be exported/imported, and not the schema
|
35
|
+
suppress_ddl: false
|
36
|
+
|
37
|
+
# if suppress_sequence_update is true, the sequences for serial (auto-incrementing) columns
|
38
|
+
# will not be update to the current maximum value of that column in the database
|
39
|
+
# if suppress_ddl is not set to true, then this option is implied to be false as well (unless overridden here)
|
40
|
+
suppress_sequence_update: false
|
41
|
+
|
42
|
+
# if suppress_indexes is true, indexes will not be exported/migrated.
|
43
|
+
suppress_indexes: false
|
44
|
+
|
45
|
+
# if force_truncate is true, forces a table truncate before table loading
|
46
|
+
force_truncate: false
|
47
|
+
|
48
|
+
# if use_timezones is true, timestamp/time columns will be created in postgres as "with time zone"
|
49
|
+
# rather than "without time zone"
|
50
|
+
use_timezones: false
|
@@ -0,0 +1,119 @@
|
|
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_tinyint_u TINYINT UNSIGNED,
|
8
|
+
f_smallint SMALLINT,
|
9
|
+
f_smallint_u SMALLINT UNSIGNED,
|
10
|
+
f_mediumint MEDIUMINT,
|
11
|
+
f_mediumint_u MEDIUMINT UNSIGNED,
|
12
|
+
f_int INT,
|
13
|
+
f_int_u INT UNSIGNED,
|
14
|
+
f_integer INTEGER,
|
15
|
+
f_integer_u INTEGER UNSIGNED,
|
16
|
+
f_bigint BIGINT,
|
17
|
+
f_bigint_u BIGINT UNSIGNED,
|
18
|
+
f_real REAL,
|
19
|
+
f_double DOUBLE,
|
20
|
+
f_float FLOAT,
|
21
|
+
f_float_u FLOAT UNSIGNED,
|
22
|
+
f_decimal DECIMAL,
|
23
|
+
f_numeric NUMERIC
|
24
|
+
);
|
25
|
+
|
26
|
+
INSERT INTO numeric_types_basics VALUES
|
27
|
+
( 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19),
|
28
|
+
( 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
|
29
|
+
( 3,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23),
|
30
|
+
( 4, -128, 0,-32768, 0,-8388608, 0,-2147483648, 0,-2147483648, 0,-9223372036854775808, 0, 1, 1, 1, 1, 1, 1),
|
31
|
+
( 5, 127, 255, 32767, 65535, 8388607, 16777215, 2147483647, 4294967295, 2147483647, 4294967295, 9223372036854775807, 18446744073709551615, 1, 1, 1, 1, 1, 1);
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
DROP TABLE IF EXISTS basic_autoincrement;
|
36
|
+
CREATE TABLE basic_autoincrement (
|
37
|
+
auto_id INT(11) NOT NULL AUTO_INCREMENT,
|
38
|
+
auto_dummy INT,
|
39
|
+
PRIMARY KEY (auto_id)
|
40
|
+
);
|
41
|
+
|
42
|
+
INSERT INTO basic_autoincrement(auto_dummy) VALUES
|
43
|
+
(1),(2),(23);
|
44
|
+
|
45
|
+
-- see GH#22 float conversion error
|
46
|
+
DROP TABLE IF EXISTS numeric_type_floats;
|
47
|
+
CREATE TABLE numeric_type_floats (
|
48
|
+
latitude FLOAT,
|
49
|
+
longitude FLOAT
|
50
|
+
);
|
51
|
+
|
52
|
+
INSERT INTO numeric_type_floats(latitude,longitude) VALUES
|
53
|
+
(1.1,2.2);
|
54
|
+
|
55
|
+
-- see GH#18 smallint error
|
56
|
+
DROP TABLE IF EXISTS gh18_smallint;
|
57
|
+
CREATE TABLE gh18_smallint (
|
58
|
+
s_smallint SMALLINT,
|
59
|
+
u_smallint SMALLINT UNSIGNED
|
60
|
+
);
|
61
|
+
|
62
|
+
INSERT INTO gh18_smallint(s_smallint,u_smallint) VALUES
|
63
|
+
(-32768,32767),
|
64
|
+
(-1,0),
|
65
|
+
(32767,65535);
|
66
|
+
|
67
|
+
-- see https://github.com/maxlapshin/mysql2postgres/issues/27
|
68
|
+
DROP TABLE IF EXISTS test_boolean_conversion;
|
69
|
+
CREATE TABLE test_boolean_conversion (
|
70
|
+
test_name VARCHAR(25),
|
71
|
+
bit_1 BIT(1),
|
72
|
+
tinyint_1 TINYINT(1),
|
73
|
+
bit_1_default_0 BIT(1) DEFAULT 0,
|
74
|
+
bit_1_default_1 BIT(1) DEFAULT 1,
|
75
|
+
tinyint_1_default_0 TINYINT(1) DEFAULT 0,
|
76
|
+
tinyint_1_default_1 TINYINT(1) DEFAULT 1,
|
77
|
+
tinyint_1_default_2 TINYINT(1) DEFAULT 2 -- Test the fact that 1 byte isn't limited to [0,1]
|
78
|
+
);
|
79
|
+
|
80
|
+
INSERT INTO test_boolean_conversion (test_name, bit_1, tinyint_1)
|
81
|
+
VALUES ('test-null', NULL, NULL),
|
82
|
+
('test-false', 0, 0),
|
83
|
+
('test-true', 1, 1);
|
84
|
+
INSERT INTO test_boolean_conversion (test_name, tinyint_1) VALUES ('test-true-nonzero', 2);
|
85
|
+
|
86
|
+
CREATE OR REPLACE VIEW test_view AS
|
87
|
+
SELECT b.test_name
|
88
|
+
FROM test_boolean_conversion b;
|
89
|
+
|
90
|
+
DROP TABLE IF EXISTS test_null_conversion;
|
91
|
+
CREATE TABLE test_null_conversion (column_a VARCHAR(10));
|
92
|
+
INSERT INTO test_null_conversion (column_a) VALUES (NULL);
|
93
|
+
|
94
|
+
DROP TABLE IF EXISTS test_datetime_conversion;
|
95
|
+
CREATE TABLE test_datetime_conversion (
|
96
|
+
column_a DATETIME,
|
97
|
+
column_b TIMESTAMP,
|
98
|
+
column_c DATETIME DEFAULT '0000-00-00',
|
99
|
+
column_d DATETIME DEFAULT '0000-00-00 00:00',
|
100
|
+
column_e DATETIME DEFAULT '0000-00-00 00:00:00',
|
101
|
+
column_f TIME
|
102
|
+
);
|
103
|
+
INSERT INTO test_datetime_conversion (column_a, column_f) VALUES ('0000-00-00 00:00', '08:15:30');
|
104
|
+
|
105
|
+
DROP TABLE IF EXISTS test_index_conversion;
|
106
|
+
CREATE TABLE test_index_conversion (column_a VARCHAR(10));
|
107
|
+
CREATE UNIQUE INDEX index_test_index_conversion_on_column_a ON test_index_conversion (column_a);
|
108
|
+
|
109
|
+
DROP TABLE IF EXISTS test_foreign_keys_child;
|
110
|
+
DROP TABLE IF EXISTS test_foreign_keys_parent;
|
111
|
+
CREATE TABLE test_foreign_keys_parent (id INT NOT NULL, PRIMARY KEY (id)) ENGINE=INNODB;
|
112
|
+
CREATE TABLE test_foreign_keys_child (id INT, test_foreign_keys_parent_id INT,
|
113
|
+
INDEX par_ind (test_foreign_keys_parent_id),
|
114
|
+
FOREIGN KEY (test_foreign_keys_parent_id) REFERENCES test_foreign_keys_parent(id) ON DELETE CASCADE
|
115
|
+
) ENGINE=INNODB;
|
116
|
+
|
117
|
+
DROP TABLE IF EXISTS test_enum;
|
118
|
+
CREATE TABLE test_enum (name ENUM('small', 'medium', 'large'));
|
119
|
+
INSERT INTO test_enum (name) VALUES ('medium');
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'mysql2psql'
|
4
|
+
|
5
|
+
class ConvertToDbTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
$stdout = StringIO.new
|
9
|
+
$stderr = StringIO.new
|
10
|
+
|
11
|
+
seed_test_database
|
12
|
+
@options=get_test_config_by_label(:localmysql_to_db_convert_all)
|
13
|
+
@mysql2psql = Mysql2psql.new([@options.filepath])
|
14
|
+
@mysql2psql.convert
|
15
|
+
@mysql2psql.writer.open
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
@mysql2psql.writer.close
|
20
|
+
delete_files_for_test_config(@options)
|
21
|
+
|
22
|
+
$stdout = STDOUT
|
23
|
+
$stderr = STDERR
|
24
|
+
end
|
25
|
+
|
26
|
+
def exec_sql_on_psql(sql, parameters=nil)
|
27
|
+
@mysql2psql.writer.conn.exec(sql, parameters)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_boolean_test_record(name)
|
31
|
+
exec_sql_on_psql('SELECT * FROM test_boolean_conversion WHERE test_name = $1', [name]).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_table_creation
|
35
|
+
assert_true @mysql2psql.writer.exists?('numeric_types_basics')
|
36
|
+
assert_true @mysql2psql.writer.exists?('basic_autoincrement')
|
37
|
+
assert_true @mysql2psql.writer.exists?('numeric_type_floats')
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_boolean_conversion_to_true
|
41
|
+
true_record = get_boolean_test_record('test-true')
|
42
|
+
assert_equal 't', true_record['bit_1']
|
43
|
+
assert_equal 't', true_record['tinyint_1']
|
44
|
+
|
45
|
+
true_nonzero_record = get_boolean_test_record('test-true-nonzero')
|
46
|
+
assert_equal 't', true_nonzero_record['tinyint_1']
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_boolean_conversion_to_false
|
50
|
+
false_record = get_boolean_test_record('test-false')
|
51
|
+
assert_equal 'f', false_record['bit_1']
|
52
|
+
assert_equal 'f', false_record['tinyint_1']
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_boolean_conversion_of_null
|
56
|
+
null_record = get_boolean_test_record('test-null')
|
57
|
+
assert_nil null_record['bit_1']
|
58
|
+
assert_nil null_record['tinyint_1']
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_null_conversion
|
62
|
+
result = exec_sql_on_psql('SELECT column_a FROM test_null_conversion').first
|
63
|
+
assert_nil result['column_a']
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_datetime_conversion
|
67
|
+
result = exec_sql_on_psql('SELECT column_a, column_f FROM test_datetime_conversion').first
|
68
|
+
assert_equal '1970-01-01 00:00:00', result['column_a']
|
69
|
+
assert_equal '08:15:30', result['column_f']
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_datetime_defaults
|
73
|
+
result = exec_sql_on_psql(<<-SQL)
|
74
|
+
SELECT a.attname,
|
75
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod),
|
76
|
+
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)
|
77
|
+
FROM pg_catalog.pg_attrdef d
|
78
|
+
WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) AS default
|
79
|
+
FROM pg_catalog.pg_attribute a
|
80
|
+
WHERE a.attrelid = 'test_datetime_conversion'::regclass AND a.attnum > 0
|
81
|
+
SQL
|
82
|
+
|
83
|
+
assert_equal 6, result.count
|
84
|
+
|
85
|
+
result.each do |row|
|
86
|
+
if row["attname"] == "column_f"
|
87
|
+
assert_equal "time without time zone", row["format_type"]
|
88
|
+
else
|
89
|
+
assert_equal "timestamp without time zone", row["format_type"]
|
90
|
+
end
|
91
|
+
|
92
|
+
case row["attname"]
|
93
|
+
when "column_a"
|
94
|
+
assert_nil row["default"]
|
95
|
+
when "column_b"
|
96
|
+
assert_equal "now()", row["default"]
|
97
|
+
when "column_c", "column_d", "column_e"
|
98
|
+
assert_equal "'1970-01-01 00:00:00'::timestamp without time zone", row["default"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_index_conversion
|
104
|
+
result = exec_sql_on_psql(%(
|
105
|
+
SELECT pg_get_indexdef(indexrelid)
|
106
|
+
FROM pg_index
|
107
|
+
join pg_class on pg_class.oid=pg_index.indrelid
|
108
|
+
where pg_class.relname='test_index_conversion'
|
109
|
+
)).first
|
110
|
+
assert_equal "CREATE UNIQUE INDEX index_test_index_conversion_on_column_a ON test_index_conversion USING btree (column_a)", result["pg_get_indexdef"]
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_foreign_keys
|
114
|
+
result = exec_sql_on_psql("SELECT conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = 'test_foreign_keys_child'::regclass")
|
115
|
+
expected = {"condef" => "FOREIGN KEY (test_foreign_keys_parent_id) REFERENCES test_foreign_keys_parent(id) ON UPDATE RESTRICT ON DELETE CASCADE", "conname" => "test_foreign_keys_child_test_foreign_keys_parent_id_fkey"}
|
116
|
+
assert_equal expected, result.first
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_output
|
120
|
+
$stdout.rewind
|
121
|
+
actual = $stdout.read
|
122
|
+
|
123
|
+
assert_match /Counting rows of test_foreign_keys_child/, actual
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_enum
|
127
|
+
result = exec_sql_on_psql(<<-SQL)
|
128
|
+
SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true)
|
129
|
+
FROM pg_catalog.pg_constraint r
|
130
|
+
WHERE r.conrelid = 'test_enum'::regclass AND r.contype = 'c'
|
131
|
+
ORDER BY 1
|
132
|
+
SQL
|
133
|
+
|
134
|
+
assert_equal 1, result.count
|
135
|
+
assert_equal "CHECK (name::text = ANY (ARRAY['small'::character varying, 'medium'::character varying, 'large'::character varying]::text[]))", result.first["pg_get_constraintdef"]
|
136
|
+
end
|
137
|
+
end
|