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.
@@ -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