schema_ferry 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +20 -1
- data/README.md +34 -32
- data/lib/schema_ferry/cli.rb +19 -10
- data/lib/schema_ferry/config.rb +109 -0
- data/lib/schema_ferry/{converter → core}/column_converter.rb +4 -4
- data/lib/schema_ferry/{converter → core}/enum_check_builder.rb +8 -6
- data/lib/schema_ferry/{converter/identifier_shortener.rb → core/identifier_shortenable.rb} +4 -14
- data/lib/schema_ferry/core/schema_model.rb +60 -0
- data/lib/schema_ferry/core/schemafile_renderer.rb +109 -0
- data/lib/schema_ferry/core/table_converter.rb +159 -0
- data/lib/schema_ferry/core/translator.rb +53 -0
- data/lib/schema_ferry/{converter → core}/type_mapper.rb +2 -2
- data/lib/schema_ferry/errors.rb +2 -0
- data/lib/schema_ferry/io/mysql_reader.rb +80 -0
- data/lib/schema_ferry/{target/ridgepole_runner.rb → io/postgres_writer.rb} +11 -6
- data/lib/schema_ferry/pipeline.rb +24 -7
- data/lib/schema_ferry/support/drop_detectable.rb +17 -0
- data/lib/schema_ferry/support/warnings.rb +13 -0
- data/lib/schema_ferry/version.rb +1 -1
- data/lib/schema_ferry.rb +14 -14
- metadata +20 -14
- data/lib/schema_ferry/converter/schema_converter.rb +0 -172
- data/lib/schema_ferry/dsl/config.rb +0 -77
- data/lib/schema_ferry/dsl/table_rule.rb +0 -34
- data/lib/schema_ferry/schema_model.rb +0 -56
- data/lib/schema_ferry/source/connection_registry.rb +0 -29
- data/lib/schema_ferry/source/mysql_reader.rb +0 -88
- data/lib/schema_ferry/target/schemafile_renderer.rb +0 -143
- data/lib/schema_ferry/warnings.rb +0 -13
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SchemaFerry
|
|
4
|
+
module Core
|
|
5
|
+
class TableConverter
|
|
6
|
+
include Support::Warnings
|
|
7
|
+
include IdentifierShortenable
|
|
8
|
+
|
|
9
|
+
UNSUPPORTED_INDEX_TYPES = %i[fulltext spatial].freeze
|
|
10
|
+
SPATIAL_SQL_TYPES = /\A(?:geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|
|
|
11
|
+
geometrycollection)\z/ix
|
|
12
|
+
|
|
13
|
+
def initialize(raw, rule:, foreign_keys:, fk_columns:, column_converter:, enum_check:)
|
|
14
|
+
@raw = raw
|
|
15
|
+
@name = raw[:name]
|
|
16
|
+
@rule = rule
|
|
17
|
+
@foreign_keys = foreign_keys
|
|
18
|
+
@fk_columns = fk_columns
|
|
19
|
+
@column_converter = column_converter
|
|
20
|
+
@enum_check = enum_check
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def convert
|
|
24
|
+
check_table_name_length!
|
|
25
|
+
|
|
26
|
+
TableSchema.new(
|
|
27
|
+
name: @name,
|
|
28
|
+
primary_key: @raw[:primary_key],
|
|
29
|
+
pk_type: convert_pk_type,
|
|
30
|
+
pk_limit: convert_pk_limit,
|
|
31
|
+
comment: @raw[:comment].presence,
|
|
32
|
+
columns: convert_columns,
|
|
33
|
+
indexes: convert_indexes,
|
|
34
|
+
foreign_keys: convert_foreign_keys,
|
|
35
|
+
check_constraints: build_check_constraints
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def check_table_name_length!
|
|
42
|
+
return if @name.bytesize <= MAX_BYTES
|
|
43
|
+
|
|
44
|
+
raise ConversionError, "table name #{@name.inspect} exceeds PostgreSQL's #{MAX_BYTES}-byte " \
|
|
45
|
+
"identifier limit. Exclude it with ignore_table :#{@name}, or rename it in MySQL."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def pk_column
|
|
49
|
+
pk = @raw[:primary_key]
|
|
50
|
+
pk.is_a?(String) ? @raw[:columns].find { |c| c[:name] == pk } : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A single-column PK is rendered via create_table's id: option, so it is
|
|
54
|
+
# excluded from the column list. Composite PK columns must stay:
|
|
55
|
+
# primary_key: [...] does not create them.
|
|
56
|
+
def data_columns
|
|
57
|
+
@raw[:columns].reject { |c| c[:name] == @raw[:primary_key] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# MySQL BIGINT comes through AR as :integer with limit 8, so the sql_type
|
|
61
|
+
# is the only reliable source for the id: option.
|
|
62
|
+
def convert_pk_type
|
|
63
|
+
col = pk_column
|
|
64
|
+
return nil if col.nil?
|
|
65
|
+
return col[:type] unless col[:type] == :integer
|
|
66
|
+
|
|
67
|
+
sql_type = col[:sql_type].to_s
|
|
68
|
+
if sql_type.include?("unsigned")
|
|
69
|
+
if sql_type.start_with?("bigint")
|
|
70
|
+
emit_warning "table #{@name}: BIGINT UNSIGNED primary key has no PostgreSQL " \
|
|
71
|
+
"equivalent; using signed bigint (values above 2^63-1 will not fit)."
|
|
72
|
+
end
|
|
73
|
+
:bigint
|
|
74
|
+
elsif sql_type.start_with?("bigint")
|
|
75
|
+
:bigint
|
|
76
|
+
else
|
|
77
|
+
:integer
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def convert_pk_limit
|
|
82
|
+
col = pk_column
|
|
83
|
+
col[:limit] if col && col[:type] == :string
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def convert_columns
|
|
87
|
+
data_columns
|
|
88
|
+
.reject { |c| @rule.ignored_columns.include?(c[:name]) }
|
|
89
|
+
.map do |c|
|
|
90
|
+
check_spatial_type!(c)
|
|
91
|
+
@column_converter.call(c, @name, @rule, @fk_columns)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def check_spatial_type!(col)
|
|
96
|
+
return if @rule.column_type_overrides.key?(col[:name]) # handled downstream, like any other override
|
|
97
|
+
|
|
98
|
+
if col[:sql_type].to_s.match?(SPATIAL_SQL_TYPES)
|
|
99
|
+
raise ConversionError, "#{@name}.#{col[:name]}: MySQL #{col[:sql_type]} columns have no " \
|
|
100
|
+
"PostgreSQL equivalent without PostGIS, which schema_ferry does not " \
|
|
101
|
+
"manage. Exclude it with ignore_column :#{col[:name]}."
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def convert_indexes
|
|
106
|
+
@raw[:indexes]
|
|
107
|
+
.reject { |idx| @rule.ignored_indexes.include?(idx[:name]) }
|
|
108
|
+
.reject { |idx| idx[:columns].intersect?(@rule.ignored_columns) }
|
|
109
|
+
.map do |idx|
|
|
110
|
+
check_unsupported_index_type!(idx)
|
|
111
|
+
build_index_schema(idx)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check_unsupported_index_type!(idx)
|
|
116
|
+
if UNSUPPORTED_INDEX_TYPES.include?(idx[:type])
|
|
117
|
+
raise ConversionError, "#{@name}: #{idx[:type].to_s.upcase} index #{idx[:name].inspect} " \
|
|
118
|
+
"has no PostgreSQL equivalent. Exclude it with ignore_index :#{idx[:name]}."
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_index_schema(idx)
|
|
123
|
+
IndexSchema.new(
|
|
124
|
+
name: shorten_identifier(idx[:name], kind: "index", table: @name),
|
|
125
|
+
columns: idx[:columns],
|
|
126
|
+
unique: idx[:unique],
|
|
127
|
+
using: idx[:using],
|
|
128
|
+
# AR reports "no prefix lengths / orders" as an empty hash; rendering
|
|
129
|
+
# a literal lengths: {} would make ridgepole re-create the index on
|
|
130
|
+
# every run.
|
|
131
|
+
lengths: idx[:lengths].presence,
|
|
132
|
+
orders: idx[:orders].presence
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def convert_foreign_keys
|
|
137
|
+
@foreign_keys.map { |fk| build_fk_schema(fk) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def build_fk_schema(foreign_key)
|
|
141
|
+
ForeignKeySchema.new(
|
|
142
|
+
from_table: foreign_key[:from_table],
|
|
143
|
+
to_table: foreign_key[:to_table],
|
|
144
|
+
column: foreign_key[:column],
|
|
145
|
+
primary_key: foreign_key[:primary_key],
|
|
146
|
+
on_update: foreign_key[:on_update],
|
|
147
|
+
on_delete: foreign_key[:on_delete],
|
|
148
|
+
name: shorten_identifier(foreign_key[:name], kind: "foreign key", table: foreign_key[:from_table])
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_check_constraints
|
|
153
|
+
return [] unless @enum_check
|
|
154
|
+
|
|
155
|
+
@enum_check.call(@raw, @rule)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SchemaFerry
|
|
4
|
+
module Core
|
|
5
|
+
class Translator
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
@column_converter = ColumnConverter.new(TypeMapper.new(config.global_type_overrides))
|
|
9
|
+
@enum_check = (EnumCheckBuilder.new if config.enum_mode == :check)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def translate(raw_tables)
|
|
13
|
+
kept_tables = raw_tables.reject { |t| @config.ignored_tables.include?(t[:name]) }
|
|
14
|
+
foreign_keys = kept_tables.to_h { |t| [t[:name], surviving_foreign_keys(t)] }
|
|
15
|
+
fk_columns = collect_fk_columns(foreign_keys)
|
|
16
|
+
|
|
17
|
+
pg_tables = kept_tables.map do |raw|
|
|
18
|
+
TableConverter.new(
|
|
19
|
+
raw,
|
|
20
|
+
rule: @config.table_rules[raw[:name]],
|
|
21
|
+
foreign_keys: foreign_keys.fetch(raw[:name]),
|
|
22
|
+
fk_columns: fk_columns.fetch(raw[:name], []),
|
|
23
|
+
column_converter: @column_converter,
|
|
24
|
+
enum_check: @enum_check
|
|
25
|
+
).convert
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
SchemafileRenderer.new.render(pg_tables)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def surviving_foreign_keys(raw)
|
|
34
|
+
ignored = @config.table_rules[raw[:name]].ignored_columns
|
|
35
|
+
raw[:foreign_keys]
|
|
36
|
+
.reject { |fk| @config.ignored_tables.include?(fk[:to_table]) }
|
|
37
|
+
.reject { |fk| ignored.include?(fk[:column]) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Columns on either side of a surviving foreign key must land in
|
|
41
|
+
# PostgreSQL's integer type family: a numeric(20, 0) column cannot
|
|
42
|
+
# reference a bigint key.
|
|
43
|
+
def collect_fk_columns(foreign_keys_by_table)
|
|
44
|
+
foreign_keys_by_table.each_with_object({}) do |(table_name, fks), acc|
|
|
45
|
+
fks.each do |fk|
|
|
46
|
+
(acc[table_name] ||= []) << fk[:column]
|
|
47
|
+
(acc[fk[:to_table]] ||= []) << fk[:primary_key]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SchemaFerry
|
|
4
|
-
module
|
|
4
|
+
module Core
|
|
5
5
|
class TypeMapper
|
|
6
6
|
# PG has no limit concept for text/binary/bigint/float; drop the
|
|
7
7
|
# MySQL-derived limits (float's limit: 53 is DOUBLE's internal bit
|
|
@@ -36,7 +36,7 @@ module SchemaFerry
|
|
|
36
36
|
def call(ar_type, options = {})
|
|
37
37
|
unless KNOWN_TYPES.include?(ar_type)
|
|
38
38
|
raise ConversionError, "Unknown MySQL AR type: #{ar_type.inspect}. " \
|
|
39
|
-
"Use map_type or
|
|
39
|
+
"Use map_type or column's map_type_to: to specify a PostgreSQL type."
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
pg_type = @overrides.fetch(ar_type, ar_type)
|
data/lib/schema_ferry/errors.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SchemaFerry
|
|
4
|
+
# Base class so the CLI can rescue every schema_ferry error in one clause.
|
|
4
5
|
class Error < StandardError; end
|
|
5
6
|
class ConfigError < Error; end
|
|
6
7
|
class ConnectionError < Error; end
|
|
@@ -8,4 +9,5 @@ module SchemaFerry
|
|
|
8
9
|
class ConversionError < Error; end
|
|
9
10
|
class RidgepoleNotFoundError < Error; end
|
|
10
11
|
class RidgepoleError < Error; end
|
|
12
|
+
class DropNotAllowedError < Error; end
|
|
11
13
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SchemaFerry
|
|
4
|
+
module IO
|
|
5
|
+
class MysqlReader
|
|
6
|
+
def initialize(url)
|
|
7
|
+
@url = url
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read_all
|
|
11
|
+
with_connection do |conn|
|
|
12
|
+
conn.tables.sort.map { |table_name| read_table(conn, table_name) }
|
|
13
|
+
end
|
|
14
|
+
rescue ConnectionError
|
|
15
|
+
raise
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
raise ReadError, e.message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Uses a fresh ConnectionHandler per call so the pool is fully isolated
|
|
23
|
+
# from ActiveRecord::Base and any host Rails app connections.
|
|
24
|
+
# AR 7.2+ banned anonymous AR subclasses, so we bypass that path entirely.
|
|
25
|
+
def with_connection
|
|
26
|
+
handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
|
27
|
+
begin
|
|
28
|
+
pool = handler.establish_connection(@url, owner_name: "schema_ferry")
|
|
29
|
+
conn = pool.checkout
|
|
30
|
+
conn.verify!
|
|
31
|
+
rescue ActiveRecord::ActiveRecordError => e
|
|
32
|
+
raise ConnectionError, e.message
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
yield conn
|
|
37
|
+
ensure
|
|
38
|
+
pool.checkin(conn)
|
|
39
|
+
end
|
|
40
|
+
ensure
|
|
41
|
+
handler&.clear_all_connections!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def read_table(conn, name)
|
|
45
|
+
{
|
|
46
|
+
name: name,
|
|
47
|
+
primary_key: conn.primary_key(name),
|
|
48
|
+
comment: conn.table_comment(name),
|
|
49
|
+
columns: serialize_columns(conn.columns(name)),
|
|
50
|
+
indexes: serialize_indexes(conn.indexes(name)),
|
|
51
|
+
foreign_keys: serialize_foreign_keys(conn.foreign_keys(name))
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def serialize_columns(columns)
|
|
56
|
+
fields = %i[name type sql_type limit precision scale null default default_function comment]
|
|
57
|
+
columns.map { |c| fields.to_h { |field| [field, c.public_send(field)] } }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def serialize_indexes(indexes)
|
|
61
|
+
indexes.map do |idx|
|
|
62
|
+
{
|
|
63
|
+
name: idx.name,
|
|
64
|
+
columns: Array(idx.columns), # idx.columns can be an Array or a String
|
|
65
|
+
unique: idx.unique,
|
|
66
|
+
using: idx.using,
|
|
67
|
+
type: idx.type,
|
|
68
|
+
lengths: idx.lengths,
|
|
69
|
+
orders: idx.orders
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def serialize_foreign_keys(fkeys)
|
|
75
|
+
fields = %i[from_table to_table column primary_key on_update on_delete name]
|
|
76
|
+
fkeys.map { |fk| fields.to_h { |field| [field, fk.public_send(field)] } }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -4,12 +4,12 @@ require "open3"
|
|
|
4
4
|
require "tempfile"
|
|
5
5
|
|
|
6
6
|
module SchemaFerry
|
|
7
|
-
module
|
|
7
|
+
module IO
|
|
8
8
|
# Runs ridgepole as a subprocess rather than via Ridgepole::Client:
|
|
9
|
-
#
|
|
10
|
-
# in-process, which would hijack a host Rails app's database
|
|
11
|
-
#
|
|
12
|
-
class
|
|
9
|
+
# Ridgepole::Client calls ActiveRecord::Base.establish_connection and
|
|
10
|
+
# patches AR in-process, which would hijack a host Rails app's database
|
|
11
|
+
# connection.
|
|
12
|
+
class PostgresWriter
|
|
13
13
|
def initialize(target_url)
|
|
14
14
|
@target_url = target_url
|
|
15
15
|
end
|
|
@@ -17,12 +17,17 @@ module SchemaFerry
|
|
|
17
17
|
def run(schema_content, dry_run: false)
|
|
18
18
|
bin = find_ridgepole!
|
|
19
19
|
|
|
20
|
+
# ridgepole only reads the schema from -f FILE.
|
|
20
21
|
Tempfile.create(["schema_ferry_", ".rb"]) do |f|
|
|
21
22
|
f.write(schema_content)
|
|
22
23
|
f.flush
|
|
23
24
|
|
|
24
25
|
cmd = [bin, "--apply", "-c", @target_url, "-f", f.path]
|
|
25
|
-
|
|
26
|
+
if dry_run
|
|
27
|
+
cmd << "--dry-run"
|
|
28
|
+
else
|
|
29
|
+
cmd += ["--pre-query", "BEGIN", "--post-query", "COMMIT"]
|
|
30
|
+
end
|
|
26
31
|
|
|
27
32
|
stdout, stderr, status = Open3.capture3(*cmd)
|
|
28
33
|
raise RidgepoleError, [stdout, stderr].reject { |s| s.strip.empty? }.join("\n") unless status.success?
|
|
@@ -2,22 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
module SchemaFerry
|
|
4
4
|
class Pipeline
|
|
5
|
+
include Support::DropDetectable
|
|
6
|
+
|
|
5
7
|
def initialize(config)
|
|
6
8
|
@config = config
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
def dry_run
|
|
10
|
-
|
|
12
|
+
write(build_schemafile, dry_run: true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def apply!(allow_drops: true)
|
|
16
|
+
schemafile = build_schemafile
|
|
17
|
+
|
|
18
|
+
unless allow_drops
|
|
19
|
+
drops = detect_drops(write(schemafile, dry_run: true))
|
|
20
|
+
unless drops.empty?
|
|
21
|
+
raise DropNotAllowedError,
|
|
22
|
+
"refused: the diff contains drop(s):\n#{drops.join("\n")}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
write(schemafile, dry_run: false)
|
|
11
27
|
end
|
|
12
28
|
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_schemafile
|
|
32
|
+
IO::MysqlReader.new(@config.source_url).read_all
|
|
33
|
+
.then { |mysql_tables| Core::Translator.new(@config).translate(mysql_tables) }
|
|
15
34
|
end
|
|
16
35
|
|
|
17
|
-
def schemafile
|
|
18
|
-
|
|
19
|
-
pg_tables = Converter::SchemaConverter.new(@config).convert(mysql_tables)
|
|
20
|
-
Target::SchemafileRenderer.new.render(pg_tables)
|
|
36
|
+
def write(schemafile, dry_run:)
|
|
37
|
+
IO::PostgresWriter.new(@config.target_url).run(schemafile, dry_run: dry_run)
|
|
21
38
|
end
|
|
22
39
|
end
|
|
23
40
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SchemaFerry
|
|
4
|
+
module Support
|
|
5
|
+
module DropDetectable
|
|
6
|
+
DROP_LINE = /^(?:drop_table|remove_column|remove_index|remove_foreign_key|
|
|
7
|
+
remove_check_constraint|remove_exclusion_constraint|
|
|
8
|
+
remove_unique_constraint)\(.*\)$/x
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def detect_drops(ddl)
|
|
13
|
+
ddl.each_line.map(&:chomp).grep(DROP_LINE)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/schema_ferry/version.rb
CHANGED
data/lib/schema_ferry.rb
CHANGED
|
@@ -4,23 +4,23 @@ require "active_record"
|
|
|
4
4
|
|
|
5
5
|
require_relative "schema_ferry/version"
|
|
6
6
|
require_relative "schema_ferry/errors"
|
|
7
|
-
require_relative "schema_ferry/warnings"
|
|
8
|
-
require_relative "schema_ferry/
|
|
9
|
-
require_relative "schema_ferry/
|
|
10
|
-
require_relative "schema_ferry/
|
|
11
|
-
require_relative "schema_ferry/
|
|
12
|
-
require_relative "schema_ferry/
|
|
13
|
-
require_relative "schema_ferry/
|
|
14
|
-
require_relative "schema_ferry/
|
|
15
|
-
require_relative "schema_ferry/
|
|
16
|
-
require_relative "schema_ferry/
|
|
17
|
-
require_relative "schema_ferry/
|
|
18
|
-
require_relative "schema_ferry/
|
|
19
|
-
require_relative "schema_ferry/
|
|
7
|
+
require_relative "schema_ferry/support/warnings"
|
|
8
|
+
require_relative "schema_ferry/support/drop_detectable"
|
|
9
|
+
require_relative "schema_ferry/config"
|
|
10
|
+
require_relative "schema_ferry/io/mysql_reader"
|
|
11
|
+
require_relative "schema_ferry/core/schema_model"
|
|
12
|
+
require_relative "schema_ferry/core/type_mapper"
|
|
13
|
+
require_relative "schema_ferry/core/identifier_shortenable"
|
|
14
|
+
require_relative "schema_ferry/core/column_converter"
|
|
15
|
+
require_relative "schema_ferry/core/enum_check_builder"
|
|
16
|
+
require_relative "schema_ferry/core/table_converter"
|
|
17
|
+
require_relative "schema_ferry/core/translator"
|
|
18
|
+
require_relative "schema_ferry/core/schemafile_renderer"
|
|
19
|
+
require_relative "schema_ferry/io/postgres_writer"
|
|
20
20
|
require_relative "schema_ferry/pipeline"
|
|
21
21
|
|
|
22
22
|
module SchemaFerry
|
|
23
23
|
def self.define(&)
|
|
24
|
-
Pipeline.new(
|
|
24
|
+
Pipeline.new(Config.build(&))
|
|
25
25
|
end
|
|
26
26
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schema_ferry
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kyuuri1791
|
|
@@ -65,6 +65,11 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '3.0'
|
|
68
|
+
description: schema_ferry keeps a PostgreSQL schema in sync with a MySQL source during
|
|
69
|
+
a gradual migration. It reads the MySQL schema through ActiveRecord, converts it
|
|
70
|
+
to PostgreSQL equivalents — with a declarative DSL for type mappings, enum handling,
|
|
71
|
+
and per-table overrides — and applies only the diff, idempotently, via ridgepole.
|
|
72
|
+
Ships a Ruby API and a cron-friendly CLI.
|
|
68
73
|
executables:
|
|
69
74
|
- schema_ferry
|
|
70
75
|
extensions: []
|
|
@@ -76,28 +81,29 @@ files:
|
|
|
76
81
|
- exe/schema_ferry
|
|
77
82
|
- lib/schema_ferry.rb
|
|
78
83
|
- lib/schema_ferry/cli.rb
|
|
79
|
-
- lib/schema_ferry/
|
|
80
|
-
- lib/schema_ferry/
|
|
81
|
-
- lib/schema_ferry/
|
|
82
|
-
- lib/schema_ferry/
|
|
83
|
-
- lib/schema_ferry/
|
|
84
|
-
- lib/schema_ferry/
|
|
85
|
-
- lib/schema_ferry/
|
|
84
|
+
- lib/schema_ferry/config.rb
|
|
85
|
+
- lib/schema_ferry/core/column_converter.rb
|
|
86
|
+
- lib/schema_ferry/core/enum_check_builder.rb
|
|
87
|
+
- lib/schema_ferry/core/identifier_shortenable.rb
|
|
88
|
+
- lib/schema_ferry/core/schema_model.rb
|
|
89
|
+
- lib/schema_ferry/core/schemafile_renderer.rb
|
|
90
|
+
- lib/schema_ferry/core/table_converter.rb
|
|
91
|
+
- lib/schema_ferry/core/translator.rb
|
|
92
|
+
- lib/schema_ferry/core/type_mapper.rb
|
|
86
93
|
- lib/schema_ferry/errors.rb
|
|
94
|
+
- lib/schema_ferry/io/mysql_reader.rb
|
|
95
|
+
- lib/schema_ferry/io/postgres_writer.rb
|
|
87
96
|
- lib/schema_ferry/pipeline.rb
|
|
88
|
-
- lib/schema_ferry/
|
|
89
|
-
- lib/schema_ferry/
|
|
90
|
-
- lib/schema_ferry/source/mysql_reader.rb
|
|
91
|
-
- lib/schema_ferry/target/ridgepole_runner.rb
|
|
92
|
-
- lib/schema_ferry/target/schemafile_renderer.rb
|
|
97
|
+
- lib/schema_ferry/support/drop_detectable.rb
|
|
98
|
+
- lib/schema_ferry/support/warnings.rb
|
|
93
99
|
- lib/schema_ferry/version.rb
|
|
94
|
-
- lib/schema_ferry/warnings.rb
|
|
95
100
|
homepage: https://github.com/kyuuri1791/schema_ferry
|
|
96
101
|
licenses:
|
|
97
102
|
- MIT
|
|
98
103
|
metadata:
|
|
99
104
|
homepage_uri: https://github.com/kyuuri1791/schema_ferry
|
|
100
105
|
source_code_uri: https://github.com/kyuuri1791/schema_ferry
|
|
106
|
+
changelog_uri: https://github.com/kyuuri1791/schema_ferry/blob/main/CHANGELOG.md
|
|
101
107
|
rubygems_mfa_required: 'true'
|
|
102
108
|
rdoc_options: []
|
|
103
109
|
require_paths:
|