benhutton-mysql2psql 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|