benhutton-mysql2psql 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ class Mysql2psql
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ PATCH = 0
6
+
7
+ STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class Mysql2psql
2
+
3
+ class Writer
4
+ end
5
+
6
+ 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
@@ -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