sequel_migration_builder 0.0.4 → 0.0.5
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.
- data/README.rdoc +4 -0
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/lib/sequel/migration_builder.rb +49 -30
- data/lib/sequel/schema/db_column.rb +87 -19
- data/lib/sequel/schema/db_schema_parser.rb +27 -19
- data/sequel_migration_builder.gemspec +2 -2
- data/spec/db_column_spec.rb +51 -2
- data/spec/db_schema_parser_spec.rb +23 -1
- data/spec/spec_helper.rb +2 -0
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -19,6 +19,8 @@ as a hash. A sample YAML version might look like:
|
|
19
19
|
|
20
20
|
example_table:
|
21
21
|
primary_key: id
|
22
|
+
table_options:
|
23
|
+
engine: myisam
|
22
24
|
columns:
|
23
25
|
- name: id
|
24
26
|
column_type: integer
|
@@ -39,6 +41,8 @@ as a hash. A sample YAML version might look like:
|
|
39
41
|
* Dropping tables when they are removed from the schema
|
40
42
|
* Automigrate functionality
|
41
43
|
* Dealing with renames in some way (even if just logging that they would be possible).
|
44
|
+
* Changing table options with alter table
|
45
|
+
* More database type parsers
|
42
46
|
|
43
47
|
== Note on Patches/Pull Requests
|
44
48
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.5
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'sequel/extensions/blank'
|
1
2
|
require 'sequel/schema/db_column'
|
2
3
|
require 'sequel/schema/db_schema_parser'
|
3
4
|
require 'sequel/schema/alter_table_operations'
|
@@ -60,52 +61,58 @@ module Sequel
|
|
60
61
|
|
61
62
|
add_line "down do"
|
62
63
|
alter_tables(current_tables, tables, :down)
|
63
|
-
|
64
|
-
new_tables.reverse.each {|table_name| add_line "drop_table #{table_name.inspect}" }
|
65
|
-
end
|
64
|
+
drop_new_tables(new_tables)
|
66
65
|
add_line "end"
|
67
66
|
end
|
68
67
|
|
69
68
|
# Generates any create table statements for new tables.
|
70
69
|
#
|
71
|
-
def create_new_tables(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
indent { create_table_statement table_name, tables[table_name] }
|
76
|
-
add_blank_line unless i == tables.size
|
70
|
+
def create_new_tables(new_table_names, tables)
|
71
|
+
each_table(new_table_names, tables) do |table_name, table, last_table|
|
72
|
+
create_table_statement table_name, table
|
73
|
+
add_blank_line unless last_table
|
77
74
|
end
|
78
75
|
end
|
79
76
|
|
80
|
-
# Generates any
|
77
|
+
# Generates any drop table statements for new tables.
|
81
78
|
#
|
82
|
-
def
|
83
|
-
i = 0
|
79
|
+
def drop_new_tables(new_table_names)
|
84
80
|
indent do
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
81
|
+
new_table_names.reverse.each {|table_name| add_line "drop_table #{table_name.inspect}" }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generates any alter table statements for current tables.
|
86
|
+
#
|
87
|
+
def alter_tables(current_table_names, tables, direction)
|
88
|
+
each_table(current_table_names, tables) do |table_name, table, last_table|
|
89
|
+
hsh = table.dup
|
90
|
+
hsh[:columns] = hsh[:columns].map {|c| Schema::DbColumn.build_from_hash(c) }
|
91
|
+
operations = Schema::AlterTableOperations.build(@db_tables[table_name], hsh)
|
92
|
+
unless operations.empty?
|
93
|
+
alter_table_statement table_name, operations, direction
|
94
|
+
add_blank_line unless last_table
|
98
95
|
end
|
99
96
|
end
|
100
97
|
end
|
101
98
|
|
99
|
+
# Generates an individual alter table statement.
|
100
|
+
#
|
101
|
+
def alter_table_statement(table_name, operations, direction)
|
102
|
+
add_line "alter_table #{table_name.inspect} do"
|
103
|
+
indent do
|
104
|
+
operations.each {|op| add_line op.__send__(direction) }
|
105
|
+
end
|
106
|
+
add_line "end"
|
107
|
+
end
|
108
|
+
|
102
109
|
# Generates an individual create_table statement.
|
103
110
|
#
|
104
111
|
def create_table_statement(table_name, table)
|
105
|
-
add_line "create_table #{table_name.inspect}#{
|
112
|
+
add_line "create_table #{table_name.inspect}#{pretty_hash(table[:table_options])} do"
|
106
113
|
indent do
|
107
|
-
table[:columns].
|
108
|
-
add_line
|
114
|
+
table[:columns].each do |c|
|
115
|
+
add_line Schema::DbColumn.build_from_hash(c).define_statement
|
109
116
|
end
|
110
117
|
if table[:primary_key]
|
111
118
|
add_blank_line
|
@@ -119,8 +126,20 @@ module Sequel
|
|
119
126
|
|
120
127
|
attr_reader :result
|
121
128
|
|
122
|
-
def
|
123
|
-
|
129
|
+
def each_table(table_names, tables)
|
130
|
+
i = 0
|
131
|
+
indent do
|
132
|
+
table_names.each do |table_name|
|
133
|
+
i += 1
|
134
|
+
yield table_name, tables[table_name], i == tables.size
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a string representing a hash as ':foo => :bar'
|
140
|
+
# rather than '{:foo=.:bar}'
|
141
|
+
def pretty_hash(hash)
|
142
|
+
", " + hash.inspect.gsub(/^\{|\}$/,'').gsub("=>", " => ") if hash
|
124
143
|
end
|
125
144
|
|
126
145
|
def table_names(tables)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Sequel
|
2
4
|
module Schema
|
3
5
|
DbColumn = Struct.new(:name, :column_type, :null, :default, :unsigned, :size, :elements)
|
@@ -8,11 +10,20 @@ module Sequel
|
|
8
10
|
# migration operations.
|
9
11
|
#
|
10
12
|
class DbColumn
|
13
|
+
# Database column types that hold integers.
|
14
|
+
INTEGER_TYPES = [:tinyint, :integer, :smallint, :mediumint, :bigint]
|
15
|
+
|
16
|
+
# Database column types that hold fractional values.
|
17
|
+
DECIMAL_TYPES = [:decimal, :float, :bigdecimal]
|
18
|
+
|
19
|
+
# All numeric database column types.
|
20
|
+
NUMERIC_TYPES = INTEGER_TYPES + DECIMAL_TYPES
|
21
|
+
|
11
22
|
# Builds a DbColumn from a Hash of attribute values. Keys
|
12
23
|
# can be strings or symbols.
|
13
24
|
#
|
14
25
|
def self.build_from_hash(attrs={})
|
15
|
-
new *members.map {|key| attrs[key] || attrs[key.to_sym] }
|
26
|
+
self.new *members.map {|key| attrs[key] || attrs[key.to_sym] }
|
16
27
|
end
|
17
28
|
|
18
29
|
# Returns a Sequel migration statement to define a column in a
|
@@ -56,40 +67,97 @@ module Sequel
|
|
56
67
|
["set_column_type #{name.inspect}", column_type.inspect, change_options].compact.join(", ")
|
57
68
|
end
|
58
69
|
|
59
|
-
# Returns an
|
70
|
+
# Returns an Set of attributes that are different between this
|
60
71
|
# and another column.
|
61
72
|
#
|
62
73
|
def diff(other)
|
63
|
-
|
64
|
-
|
65
|
-
|
74
|
+
{ :null => :boolean_attribute_different?,
|
75
|
+
:unsigned => :boolean_attribute_different?,
|
76
|
+
:name => :attribute_different?,
|
77
|
+
:column_type => :attribute_different?,
|
78
|
+
:elements => :attribute_different?,
|
79
|
+
:default => :defaults_different?,
|
80
|
+
:size => :sizes_different?
|
81
|
+
}.select {|attribute, method| __send__(method, attribute, other) }.map {|a| a.first }.to_set
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns true if this column is numeric
|
85
|
+
#
|
86
|
+
def numeric?
|
87
|
+
NUMERIC_TYPES.include?(column_type)
|
66
88
|
end
|
67
89
|
|
68
90
|
private
|
69
91
|
|
92
|
+
def attribute_different?(sym, other)
|
93
|
+
other[sym] != self[sym]
|
94
|
+
end
|
95
|
+
|
96
|
+
def boolean_attribute_different?(sym, other)
|
97
|
+
(!!other[sym]) != (!!self[sym])
|
98
|
+
end
|
99
|
+
|
100
|
+
def numeric_attribute_different?(sym, other)
|
101
|
+
(self[sym] || 0) != (other[sym] || 0)
|
102
|
+
end
|
103
|
+
|
104
|
+
def sizes_different?(_, other)
|
105
|
+
# Null size indicates 'lack of interest' in the size
|
106
|
+
size && other.size && other.size != size
|
107
|
+
end
|
108
|
+
|
109
|
+
def defaults_different?(_, other)
|
110
|
+
# Complicated by dealing with database defaults if the column
|
111
|
+
# does not allow null values.
|
112
|
+
if null == true || other.null == true
|
113
|
+
attribute_different?(:default, other)
|
114
|
+
elsif numeric? && other.numeric?
|
115
|
+
numeric_attribute_different?(:default, other)
|
116
|
+
else
|
117
|
+
! (default.blank? && other.default.blank?) && other.default != default
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
70
121
|
def change_options
|
71
|
-
opts =
|
122
|
+
opts = OptionBuilder.new
|
72
123
|
|
73
|
-
opts
|
124
|
+
opts.set :default, default
|
74
125
|
# seems odd, but we only want to output if unsigned is a true
|
75
126
|
# boolean, not if it is nil.
|
76
|
-
opts
|
77
|
-
opts
|
78
|
-
opts
|
79
|
-
|
80
|
-
opts.
|
127
|
+
opts.set :unsigned, unsigned if numeric? && (unsigned == true || unsigned == false)
|
128
|
+
opts.set :size, size if size
|
129
|
+
opts.set :elements, elements if elements
|
130
|
+
|
131
|
+
opts.render
|
81
132
|
end
|
82
133
|
|
83
134
|
def options
|
84
|
-
opts =
|
135
|
+
opts = OptionBuilder.new
|
136
|
+
|
137
|
+
opts.set :null, !!null if null != true || column_type == :timestamp
|
138
|
+
opts.set :default, default if default || column_type == :timestamp
|
139
|
+
opts.set :unsigned, true if numeric? && unsigned
|
140
|
+
opts.set :size, size if size
|
141
|
+
opts.set :elements, elements if elements
|
85
142
|
|
86
|
-
opts
|
87
|
-
|
88
|
-
opts << ":unsigned => true" if unsigned
|
89
|
-
opts << ":size => #{size.inspect}" if size
|
90
|
-
opts << ":elements => #{elements.inspect}" if elements
|
143
|
+
opts.render
|
144
|
+
end
|
91
145
|
|
92
|
-
|
146
|
+
# Formats column options in a Sequel migration
|
147
|
+
class OptionBuilder
|
148
|
+
def initialize
|
149
|
+
@opts = []
|
150
|
+
end
|
151
|
+
|
152
|
+
# Sets column option name to a value.
|
153
|
+
def set(name, value)
|
154
|
+
@opts << "#{name.inspect} => #{value.inspect}"
|
155
|
+
end
|
156
|
+
|
157
|
+
# Renders the column option hash in a pretty format.
|
158
|
+
def render
|
159
|
+
@opts.join(", ") unless @opts.empty?
|
160
|
+
end
|
93
161
|
end
|
94
162
|
end
|
95
163
|
end
|
@@ -27,11 +27,10 @@ module Sequel
|
|
27
27
|
# :table2 => { ... } }
|
28
28
|
#
|
29
29
|
def parse_db_schema
|
30
|
-
result
|
31
|
-
@db.tables.each do |table_name|
|
30
|
+
@db.tables.inject({}) do |result, table_name|
|
32
31
|
result[table_name] = {:columns => parse_table_schema(@db.schema(table_name))}
|
32
|
+
result
|
33
33
|
end
|
34
|
-
result
|
35
34
|
end
|
36
35
|
|
37
36
|
# Extracts an array of hashes representing the columns in the
|
@@ -39,17 +38,14 @@ module Sequel
|
|
39
38
|
#
|
40
39
|
def parse_table_schema(db_schema)
|
41
40
|
db_schema.map do |column|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
attrs[:elements] = extract_enum_elements(column) if attrs[:column_type] == :enum
|
51
|
-
|
52
|
-
DbColumn.build_from_hash(attrs)
|
41
|
+
type = parse_type(column.last[:db_type])
|
42
|
+
DbColumn.build_from_hash(:name => column.first,
|
43
|
+
:default => column.last[:ruby_default],
|
44
|
+
:null => column.last[:allow_null],
|
45
|
+
:column_type => type,
|
46
|
+
:unsigned => extract_unsigned(column.last[:db_type], type),
|
47
|
+
:size => extract_size(column.last[:db_type], type),
|
48
|
+
:elements => extract_enum_elements(column.last[:db_type], type))
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
@@ -80,13 +76,25 @@ module Sequel
|
|
80
76
|
|
81
77
|
private
|
82
78
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
79
|
+
def extract_unsigned(db_type_string, type)
|
80
|
+
return unless DbColumn::NUMERIC_TYPES.include?(type)
|
81
|
+
db_type_string.include?(" unsigned")
|
86
82
|
end
|
87
83
|
|
88
|
-
def
|
89
|
-
|
84
|
+
def extract_size(db_type_string, type)
|
85
|
+
return if DbColumn::INTEGER_TYPES.include?(type)
|
86
|
+
|
87
|
+
match = db_type_string.match(/\(([0-9, ]+)\)/)
|
88
|
+
if match && match[1]
|
89
|
+
n = match[1].split(/\s*,\s*/).map {|i| i.to_i }
|
90
|
+
n.size == 1 ? n.first : n
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def extract_enum_elements(db_type_string, type)
|
95
|
+
return unless type == :enum
|
96
|
+
|
97
|
+
match = db_type_string.match(/\(([^)]+)\)/)
|
90
98
|
eval('[' + match[1] + ']') if match[1]
|
91
99
|
end
|
92
100
|
end
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{sequel_migration_builder}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Roland Swingler"]
|
12
|
-
s.date = %q{2010-06-
|
12
|
+
s.date = %q{2010-06-23}
|
13
13
|
s.description = %q{Build Sequel Migrations based on the differences between two schemas}
|
14
14
|
s.email = %q{roland.swingler@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
data/spec/db_column_spec.rb
CHANGED
@@ -32,10 +32,59 @@ describe Sequel::Schema::DbColumn do
|
|
32
32
|
|
33
33
|
it "should be diffable with another DbColumn" do
|
34
34
|
other = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 10, true, 10, nil)
|
35
|
-
@column.diff(other).should == [:column_type]
|
35
|
+
@column.diff(other).should == [:column_type].to_set
|
36
36
|
|
37
37
|
other = Sequel::Schema::DbColumn.new(:foo, :integer, true, 11, true, 10, nil)
|
38
|
-
@column.diff(other).should == [:null, :default]
|
38
|
+
@column.diff(other).should == [:null, :default].to_set
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not consider allowing null being nil different from false" do
|
42
|
+
a = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 10, true, 10, nil)
|
43
|
+
b = Sequel::Schema::DbColumn.new(:foo, :smallint, nil, 10, true, 10, nil)
|
44
|
+
a.diff(b).should be_empty
|
45
|
+
b.diff(a).should be_empty
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not consider size to be different if one of the sizes is nil" do
|
49
|
+
a = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 10, true, 10, nil)
|
50
|
+
b = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 10, true, nil, nil)
|
51
|
+
a.diff(b).should be_empty
|
52
|
+
b.diff(a).should be_empty
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not consider 0 to be different from null if the column does not allow nulls" do
|
56
|
+
a = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 0, true, 10, nil)
|
57
|
+
b = Sequel::Schema::DbColumn.new(:foo, :smallint, false, nil, true, nil, nil)
|
58
|
+
a.diff(b).should be_empty
|
59
|
+
b.diff(a).should be_empty
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should consider 0 to be different from null if the column does allow nulls" do
|
63
|
+
a = Sequel::Schema::DbColumn.new(:foo, :smallint, true, 0, true, 10, nil)
|
64
|
+
b = Sequel::Schema::DbColumn.new(:foo, :smallint, true, nil, true, nil, nil)
|
65
|
+
a.diff(b).should == [:default].to_set
|
66
|
+
b.diff(a).should == [:default].to_set
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should consider 1 to be different from null" do
|
70
|
+
a = Sequel::Schema::DbColumn.new(:foo, :smallint, false, 1, true, 10, nil)
|
71
|
+
b = Sequel::Schema::DbColumn.new(:foo, :smallint, false, nil, true, nil, nil)
|
72
|
+
a.diff(b).should == [:default].to_set
|
73
|
+
b.diff(a).should == [:default].to_set
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not consider '' to be different from null if the column does not allow nulls" do
|
77
|
+
a = Sequel::Schema::DbColumn.new(:foo, :varchar, false, '', true, 10, nil)
|
78
|
+
b = Sequel::Schema::DbColumn.new(:foo, :varchar, false, nil, true, nil, nil)
|
79
|
+
a.diff(b).should be_empty
|
80
|
+
b.diff(a).should be_empty
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should consider '' to be different from null if the column allows null" do
|
84
|
+
a = Sequel::Schema::DbColumn.new(:foo, :varchar, true, '', true, 10, nil)
|
85
|
+
b = Sequel::Schema::DbColumn.new(:foo, :varchar, true, nil, true, nil, nil)
|
86
|
+
a.diff(b).should == [:default].to_set
|
87
|
+
b.diff(a).should == [:default].to_set
|
39
88
|
end
|
40
89
|
|
41
90
|
it "should be buildable from a Hash" do
|
@@ -66,16 +66,38 @@ describe "A hash in the array returned by Sequel::Schema::DbSchemaParser#parse_t
|
|
66
66
|
@parser.parse_table_schema(@schema).first.size.should == 20
|
67
67
|
end
|
68
68
|
|
69
|
+
it "should contain a :size attribute for decimal columns" do
|
70
|
+
set_db_type "decimal(14,5)"
|
71
|
+
@parser.parse_table_schema(@schema).first.size.should == [14,5]
|
72
|
+
end
|
73
|
+
|
69
74
|
it "should contain :unsigned false if a numeric column is not unsigned" do
|
70
75
|
set_db_type "int(10)"
|
71
76
|
@parser.parse_table_schema(@schema).first.unsigned.should == false
|
72
77
|
end
|
73
78
|
|
74
|
-
it "should contain :unsigned true if
|
79
|
+
it "should contain :unsigned true if an integer column is unsigned" do
|
75
80
|
set_db_type "int(10) unsigned"
|
76
81
|
@parser.parse_table_schema(@schema).first.unsigned.should == true
|
77
82
|
end
|
78
83
|
|
84
|
+
it "should contain :unsigned true if a decimal column is unsigned" do
|
85
|
+
@schema = [[:example_column,
|
86
|
+
{ :type => nil,
|
87
|
+
:default => "1",
|
88
|
+
:ruby_default => 1,
|
89
|
+
:primary_key => false,
|
90
|
+
:db_type => "decimal(10,2) unsigned",
|
91
|
+
:allow_null => true }]]
|
92
|
+
|
93
|
+
@parser.parse_table_schema(@schema).first.unsigned.should == true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not contain an :unsigned value if not a numeric column" do
|
97
|
+
set_db_type "varchar(10)", :string
|
98
|
+
@parser.parse_table_schema(@schema).first.unsigned.should == nil
|
99
|
+
end
|
100
|
+
|
79
101
|
it "should contain the elements of an enum column" do
|
80
102
|
set_db_type "enum('foo','bar')"
|
81
103
|
@parser.parse_table_schema(@schema).first.elements.should == ['foo', 'bar']
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 5
|
9
|
+
version: 0.0.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Roland Swingler
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-06-
|
17
|
+
date: 2010-06-23 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|