db_schema 0.4.1 → 0.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a24b0939afdbb0a33cfc0b34f76a70059a22c995513c1c4fec7111b4b7096b2
4
- data.tar.gz: 28c50f5ea2b03c1464bcff2dfc95e58569b1a2f08e4193e04336fec57295d600
3
+ metadata.gz: 1609672a54947a93c2953044d09ae26e4e5653105ca76455364e0746d0157372
4
+ data.tar.gz: 00c069c16b8f8414cacb529e2857c9cbd785e1534786282b6d89bfa966c31cde
5
5
  SHA512:
6
- metadata.gz: c52517e5394f962f6121fd6728a9764055fb1d6ba0dc78817360106f986f2feca1c7e0dd992f7dc4e168112126656934e838132c0a780a4b6de154c4b6d05c73
7
- data.tar.gz: be7a87847222b9bb7e23b6883f844d0923cc0538dd6c456be6facc6d03d0216b6ac21345089e2d1e910752e3712533ac808b980cadfc9c0339584994394723c4
6
+ metadata.gz: 22568b1fd6eeda2a067aec451d9c5e8a2e6ad88fa5cc5801bd4d90a9016394baa00e0b821dcb714d6ec8291462f07d3aebdd68525ad4b37b4b1b323cd641e6b7
7
+ data.tar.gz: d1ad3e5f0d74706bf1aefb41004d8b08cfd9e1d4e61db3a13fde05bdb18c707d52c277f3210fbd0d89f3453a4c9369fca02264ff9b1ed1a94d6aa1a4db5de07a
data/.travis.yml CHANGED
@@ -2,9 +2,10 @@ dist: trusty
2
2
  sudo: false
3
3
  language: ruby
4
4
  rvm:
5
- - 2.2.8
6
- - 2.3.5
7
- - 2.4.2
5
+ - 2.3.8
6
+ - 2.4.5
7
+ - 2.5.3
8
+ - 2.6.0
8
9
  services:
9
10
  - postgresql
10
11
  addons:
data/Gemfile CHANGED
@@ -1,4 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'db_schema-definitions', github: 'db-schema/definitions', branch: 'primary_keys'
4
+ gem 'db_schema-reader-postgres', github: 'db-schema/reader-postgres', branch: 'primary_keys'
5
+
3
6
  # Specify your gem's dependencies in db_schema.gemspec
4
7
  gemspec
data/README.md CHANGED
@@ -53,8 +53,8 @@ But you would lose it even with manual migrations.
53
53
  Add these lines to your application's Gemfile:
54
54
 
55
55
  ``` ruby
56
- gem 'db_schema', '~> 0.4.1'
57
- gem 'db_schema-reader-postgres', '~> 0.1.1'
56
+ gem 'db_schema', '= 0.5.rc1'
57
+ gem 'db_schema-reader-postgres', '= 0.2.rc1'
58
58
  ```
59
59
 
60
60
  And then execute:
data/db_schema.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency 'sequel'
22
22
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
23
- spec.add_runtime_dependency 'db_schema-definitions', '~> 0.1.1'
23
+ spec.add_runtime_dependency 'db_schema-definitions', '= 0.2.rc1'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '~> 1.11'
26
26
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -32,5 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency 'terminal-notifier'
33
33
  spec.add_development_dependency 'terminal-notifier-guard'
34
34
 
35
- spec.add_development_dependency 'db_schema-reader-postgres', '~> 0.1.1'
35
+ spec.add_development_dependency 'db_schema-reader-postgres', '= 0.2.rc1'
36
36
  end
@@ -13,33 +13,6 @@ if defined?(AwesomePrint)
13
13
 
14
14
  def cast_with_dbschema(object, type)
15
15
  case object
16
- when ::DbSchema::Definitions::Schema
17
- :dbschema_schema
18
- when ::DbSchema::Definitions::NullTable,
19
- ::DbSchema::Definitions::NullField,
20
- ::DbSchema::Definitions::NullIndex,
21
- ::DbSchema::Definitions::NullCheckConstraint,
22
- ::DbSchema::Definitions::NullForeignKey,
23
- ::DbSchema::Definitions::NullEnum
24
- :dbschema_null_object
25
- when ::DbSchema::Definitions::Table
26
- :dbschema_table
27
- when ::DbSchema::Definitions::Field::Custom
28
- :dbschema_custom_field
29
- when ::DbSchema::Definitions::Field::Base
30
- :dbschema_field
31
- when ::DbSchema::Definitions::Index
32
- :dbschema_index
33
- when ::DbSchema::Definitions::Index::Column
34
- :dbschema_index_column
35
- when ::DbSchema::Definitions::CheckConstraint
36
- :dbschema_check_constraint
37
- when ::DbSchema::Definitions::ForeignKey
38
- :dbschema_foreign_key
39
- when ::DbSchema::Definitions::Enum
40
- :dbschema_enum
41
- when ::DbSchema::Definitions::Extension
42
- :dbschema_column_operation
43
16
  when ::DbSchema::Operations::CreateTable
44
17
  :dbschema_create_table
45
18
  when ::DbSchema::Operations::DropTable
@@ -58,6 +31,8 @@ if defined?(AwesomePrint)
58
31
  :dbschema_alter_column_default
59
32
  when ::DbSchema::Operations::CreateIndex
60
33
  :dbschema_create_index
34
+ when ::DbSchema::Operations::DropIndex
35
+ :dbschema_drop_index
61
36
  when ::DbSchema::Operations::CreateCheckConstraint
62
37
  :dbschema_create_check_constraint
63
38
  when ::DbSchema::Operations::CreateForeignKey
@@ -76,118 +51,6 @@ if defined?(AwesomePrint)
76
51
  end
77
52
 
78
53
  private
79
- def awesome_dbschema_schema(object)
80
- data = ["tables: #{object.tables.ai}"]
81
- data << "enums: #{object.enums.ai}" if object.enums.any?
82
- data << "extensions: #{object.extensions.ai}" if object.extensions.any?
83
-
84
- data_string = data.join(', ')
85
- "#<DbSchema::Definitions::Schema #{data_string}>"
86
- end
87
-
88
- def awesome_dbschema_table(object)
89
- data = ["fields: #{object.fields.ai}"]
90
- data << "indexes: #{object.indexes.ai}" if object.indexes.any?
91
- data << "checks: #{object.checks.ai}" if object.checks.any?
92
- data << "foreign_keys: #{object.foreign_keys.ai}" if object.foreign_keys.any?
93
-
94
- data_string = indent_lines(data.join(', '))
95
- "#<DbSchema::Definitions::Table #{object.name.ai} #{data_string}>"
96
- end
97
-
98
- def awesome_dbschema_null_object(object)
99
- "#<#{object.class}>"
100
- end
101
-
102
- def awesome_dbschema_field(object)
103
- options = object.options.map do |k, v|
104
- key = colorize("#{k}:", :symbol)
105
-
106
- if (k == :default) && v.is_a?(Symbol)
107
- "#{key} #{colorize(v.to_s, :string)}"
108
- else
109
- "#{key} #{v.ai}"
110
- end
111
- end.unshift(nil).join(', ')
112
-
113
- primary_key = if object.primary_key?
114
- ', ' + colorize('primary key', :nilclass)
115
- else
116
- ''
117
- end
118
-
119
- "#<#{object.class} #{object.name.ai}#{options}#{primary_key}>"
120
- end
121
-
122
- def awesome_dbschema_custom_field(object)
123
- options = object.options.map do |k, v|
124
- key = colorize("#{k}:", :symbol)
125
-
126
- if (k == :default) && v.is_a?(Symbol)
127
- "#{key} #{colorize(v.to_s, :string)}"
128
- else
129
- "#{key} #{v.ai}"
130
- end
131
- end.unshift(nil).join(', ')
132
-
133
- primary_key = if object.primary_key?
134
- ', ' + colorize('primary key', :nilclass)
135
- else
136
- ''
137
- end
138
-
139
- "#<DbSchema::Definitions::Field::Custom (#{object.type.ai}) #{object.name.ai}#{options}#{primary_key}>"
140
- end
141
-
142
- def awesome_dbschema_index(object)
143
- columns = format_dbschema_fields(object.columns)
144
- using = ' using ' + colorize(object.type.to_s, :symbol) unless object.btree?
145
-
146
- data = [nil]
147
- data << colorize('unique', :nilclass) if object.unique?
148
- data << colorize('condition: ', :symbol) + object.condition.ai unless object.condition.nil?
149
-
150
- "#<#{object.class} #{object.name.ai} on #{columns}#{using}#{data.join(', ')}>"
151
- end
152
-
153
- def awesome_dbschema_index_column(object)
154
- data = [object.name.ai]
155
-
156
- if object.desc?
157
- data << colorize('desc', :nilclass)
158
- data << colorize('nulls last', :symbol) if object.nulls == :last
159
- else
160
- data << colorize('nulls first', :symbol) if object.nulls == :first
161
- end
162
-
163
- data.join(' ')
164
- end
165
-
166
- def awesome_dbschema_check_constraint(object)
167
- "#<#{object.class} #{object.name.ai} #{object.condition.ai}>"
168
- end
169
-
170
- def awesome_dbschema_foreign_key(object)
171
- fields = format_dbschema_fields(object.fields)
172
- references = "#{colorize('references', :class)} #{object.table.ai}"
173
- references << ' ' + format_dbschema_fields(object.keys) unless object.references_primary_key?
174
-
175
- data = [nil]
176
- data << colorize("on_update:", :symbol) + " #{object.on_update.ai}" unless object.on_update == :no_action
177
- data << colorize("on_delete:", :symbol) + " #{object.on_delete.ai}" unless object.on_delete == :no_action
178
- data << colorize('deferrable', :nilclass) if object.deferrable?
179
-
180
- "#<#{object.class} #{object.name.ai} on #{fields} #{references}#{data.join(', ')}>"
181
- end
182
-
183
- def awesome_dbschema_enum(object)
184
- values = object.values.map do |value|
185
- colorize(value.to_s, :string)
186
- end.join(', ')
187
-
188
- "#<#{object.class} #{object.name.ai} (#{values})>"
189
- end
190
-
191
54
  def awesome_dbschema_create_table(object)
192
55
  data = ["fields: #{object.table.fields.ai}"]
193
56
  data << "indexes: #{object.table.indexes.ai}" if object.table.indexes.any?
@@ -241,12 +104,20 @@ if defined?(AwesomePrint)
241
104
  using = ' using ' + colorize(object.index.type.to_s, :symbol) unless object.index.btree?
242
105
 
243
106
  data = [nil]
107
+ data << colorize('primary key', :nilclass) if object.index.primary?
244
108
  data << colorize('unique', :nilclass) if object.index.unique?
245
109
  data << colorize('condition: ', :symbol) + object.index.condition.ai unless object.index.condition.nil?
246
110
 
247
111
  "#<#{object.class} #{object.index.name.ai} on #{columns}#{using}#{data.join(', ')}>"
248
112
  end
249
113
 
114
+ def awesome_dbschema_drop_index(object)
115
+ data = [object.name.ai]
116
+ data << colorize('primary key', :nilclass) if object.primary?
117
+
118
+ "#<#{object.class} #{data.join(' ')}>"
119
+ end
120
+
250
121
  def awesome_dbschema_create_check_constraint(object)
251
122
  "#<#{object.class} #{object.check.name.ai} #{object.check.condition.ai}>"
252
123
  end
@@ -67,19 +67,12 @@ module DbSchema
67
67
  if (actual.type != desired.type) || (actual.attributes != desired.attributes)
68
68
  operations << Operations::AlterColumnType.new(
69
69
  actual.name,
70
+ old_type: actual.type,
70
71
  new_type: desired.type,
71
72
  **desired.attributes
72
73
  )
73
74
  end
74
75
 
75
- if desired.primary_key? && !actual.primary_key?
76
- operations << Operations::CreatePrimaryKey.new(actual.name)
77
- end
78
-
79
- if actual.primary_key? && !desired.primary_key?
80
- operations << Operations::DropPrimaryKey.new(actual.name)
81
- end
82
-
83
76
  if desired.null? && !actual.null?
84
77
  operations << Operations::AllowNull.new(actual.name)
85
78
  end
@@ -101,10 +94,10 @@ module DbSchema
101
94
  desired_table.indexes,
102
95
  actual_table.indexes,
103
96
  create: -> (index) { Operations::CreateIndex.new(index) },
104
- drop: -> (index) { Operations::DropIndex.new(index.name) },
97
+ drop: -> (index) { Operations::DropIndex.new(index.name, index.primary?) },
105
98
  change: -> (desired, actual) do
106
99
  [
107
- Operations::DropIndex.new(actual.name),
100
+ Operations::DropIndex.new(actual.name, actual.primary?),
108
101
  Operations::CreateIndex.new(desired)
109
102
  ]
110
103
  end
@@ -220,7 +213,6 @@ module DbSchema
220
213
  Utils.sort_by_class(
221
214
  changes,
222
215
  [
223
- Operations::DropPrimaryKey,
224
216
  Operations::DropCheckConstraint,
225
217
  Operations::DropIndex,
226
218
  Operations::DropColumn,
@@ -230,8 +222,7 @@ module DbSchema
230
222
  Operations::AlterColumnDefault,
231
223
  Operations::CreateColumn,
232
224
  Operations::CreateIndex,
233
- Operations::CreateCheckConstraint,
234
- Operations::CreatePrimaryKey
225
+ Operations::CreateCheckConstraint
235
226
  ]
236
227
  )
237
228
  end
data/lib/db_schema/dsl.rb CHANGED
@@ -16,7 +16,7 @@ module DbSchema
16
16
 
17
17
  @schema.tables << Definitions::Table.new(
18
18
  name,
19
- fields: table_yielder.fields,
19
+ fields: prepare_fields(table_yielder),
20
20
  indexes: table_yielder.indexes,
21
21
  checks: table_yielder.checks,
22
22
  foreign_keys: table_yielder.foreign_keys
@@ -35,6 +35,20 @@ module DbSchema
35
35
  migrations << Migration.new(name, block).migration
36
36
  end
37
37
 
38
+ private
39
+ def prepare_fields(table_yielder)
40
+ primary_key = table_yielder.indexes.find(&:primary?)
41
+ return table_yielder.fields if primary_key.nil?
42
+
43
+ table_yielder.fields.map do |field|
44
+ if primary_key.columns.map(&:name).include?(field.name)
45
+ field.with_null(false)
46
+ else
47
+ field
48
+ end
49
+ end
50
+ end
51
+
38
52
  class TableYielder
39
53
  attr_reader :table_name
40
54
 
@@ -55,12 +69,19 @@ module DbSchema
55
69
  field(name, :array, element_type: of, **options)
56
70
  end
57
71
 
72
+ %i(smallserial serial bigserial).each do |serial_type|
73
+ define_method(serial_type) do |name, **options|
74
+ allowed_options = Utils.filter_by_keys(options, :primary_key, :unique, :index, :references, :check)
75
+ field(name, serial_type, null: false, **allowed_options)
76
+ end
77
+ end
78
+
58
79
  def method_missing(method_name, name, *args, &block)
59
80
  field(name, method_name, args.first || {})
60
81
  end
61
82
 
62
- def primary_key(name)
63
- field(name, :integer, primary_key: true)
83
+ def primary_key(*columns, name: nil)
84
+ index(*columns, name: name, primary: true, unique: true)
64
85
  end
65
86
 
66
87
  def index(*columns, **index_options)
@@ -83,9 +104,13 @@ module DbSchema
83
104
  )
84
105
  end
85
106
 
86
- def field(name, type, unique: false, index: false, references: nil, check: nil, **options)
107
+ def field(name, type, primary_key: false, unique: false, index: false, references: nil, check: nil, **options)
87
108
  fields << Definitions::Field.build(name, type, options)
88
109
 
110
+ if primary_key
111
+ primary_key(name)
112
+ end
113
+
89
114
  if unique
90
115
  index(name, unique: true)
91
116
  elsif index
@@ -118,7 +143,7 @@ module DbSchema
118
143
  end
119
144
 
120
145
  class << self
121
- def build_index(columns, table_name:, name: nil, unique: false, using: :btree, where: nil, **ordered_fields)
146
+ def build_index(columns, table_name:, name: nil, primary: false, unique: false, using: :btree, where: nil, **ordered_fields)
122
147
  if columns.last.is_a?(Hash)
123
148
  *ascending_columns, ordered_expressions = columns
124
149
  else
@@ -151,11 +176,16 @@ module DbSchema
151
176
  end
152
177
  end
153
178
 
154
- index_name = name || "#{table_name}_#{index_columns.map(&:index_name_segment).join('_')}_index"
179
+ index_name = name || if primary
180
+ "#{table_name}_pkey"
181
+ else
182
+ "#{table_name}_#{index_columns.map(&:index_name_segment).join('_')}_index"
183
+ end
155
184
 
156
185
  Definitions::Index.new(
157
186
  name: index_name,
158
187
  columns: index_columns,
188
+ primary: primary,
159
189
  unique: unique,
160
190
  type: using,
161
191
  condition: where
@@ -86,6 +86,7 @@ module DbSchema
86
86
  def alter_column_type(name, new_type, using: nil, **new_attributes)
87
87
  alter_table.changes << Operations::AlterColumnType.new(
88
88
  name,
89
+ old_type: nil,
89
90
  new_type: new_type,
90
91
  using: using,
91
92
  **new_attributes
@@ -104,6 +105,16 @@ module DbSchema
104
105
  alter_table.changes << Operations::AlterColumnDefault.new(name, new_default: new_default)
105
106
  end
106
107
 
108
+ def add_primary_key(*columns)
109
+ alter_table.changes << Operations::CreateIndex.new(
110
+ DSL::TableYielder.build_index(columns, table_name: alter_table.table_name, primary: true)
111
+ )
112
+ end
113
+
114
+ def drop_primary_key
115
+ alter_table.changes << Operations::DropIndex.new(:"#{alter_table.table_name}_pkey", true)
116
+ end
117
+
107
118
  def add_index(*columns, **index_options)
108
119
  alter_table.changes << Operations::CreateIndex.new(
109
120
  DSL::TableYielder.build_index(
@@ -115,7 +126,7 @@ module DbSchema
115
126
  end
116
127
 
117
128
  def drop_index(name)
118
- alter_table.changes << Operations::DropIndex.new(name)
129
+ alter_table.changes << Operations::DropIndex.new(name, false)
119
130
  end
120
131
 
121
132
  def add_check(name, condition)
@@ -13,10 +13,12 @@ module DbSchema
13
13
  connection.transaction do
14
14
  create_extensions!
15
15
  create_enums!
16
+ create_temporary_tables!
17
+ normalized_tables = read_temporary_tables
16
18
 
17
19
  schema.tables = schema.tables.map do |table|
18
20
  if table.has_expressions?
19
- Table.new(table, hash, schema.enums.map(&:name), connection).normalized_table
21
+ normalized_tables.fetch(table.name)
20
22
  else
21
23
  table
22
24
  end
@@ -43,37 +45,10 @@ module DbSchema
43
45
  Runner.new(operations, connection).run!
44
46
  end
45
47
 
46
- def append_hash(name)
47
- "#{name}_#{hash}"
48
- end
49
-
50
- def hash
51
- @hash ||= begin
52
- names = schema.tables.flat_map do |table|
53
- [table.name] + table.fields.map(&:name) + table.indexes.map(&:name) + table.checks.map(&:name)
54
- end
55
-
56
- Digest::MD5.hexdigest(names.join(','))[0..9]
57
- end
58
- end
59
-
60
- class Table
61
- attr_reader :table, :hash, :enum_names, :connection
62
-
63
- def initialize(table, hash, enum_names, connection)
64
- @table = table
65
- @hash = hash
66
- @enum_names = enum_names
67
- @connection = connection
68
- end
48
+ def create_temporary_tables!
49
+ schema.tables.select(&:has_expressions?).each do |table|
50
+ temporary_table_name = append_hash(table.name)
69
51
 
70
- def normalized_table
71
- create_temporary_table!
72
- read_temporary_table
73
- end
74
-
75
- private
76
- def create_temporary_table!
77
52
  operation = Operations::CreateTable.new(
78
53
  table.with_name(temporary_table_name)
79
54
  .with_fields(rename_types(table.fields))
@@ -83,113 +58,127 @@ module DbSchema
83
58
 
84
59
  Runner.new([operation], connection).run!
85
60
  end
61
+ end
86
62
 
87
- def read_temporary_table
88
- temporary_table = Reader.reader_for(connection).read_table(temporary_table_name)
89
-
90
- temporary_table.with_name(table.name)
91
- .with_fields(rename_types_back(temporary_table.fields))
92
- .with_indexes(rename_indexes_back(temporary_table.indexes))
93
- .with_checks(rename_types_in_checks_back(temporary_table.checks))
94
- .with_foreign_keys(table.foreign_keys)
95
- end
63
+ def read_temporary_tables
64
+ all_tables = Reader.reader_for(connection).read_tables
96
65
 
97
- def rename_types(fields)
98
- fields.map do |field|
99
- new_default = if field.default_is_expression?
100
- rename_all_types_in(field.default.to_s).to_sym
101
- else
102
- field.default
103
- end
66
+ schema.tables.select(&:has_expressions?).reduce({}) do |normalized_tables, table|
67
+ temporary_table = all_tables.find do |t|
68
+ t.name == append_hash(table.name).to_sym
69
+ end || raise
104
70
 
105
- if field.custom?
106
- field.with_type(append_hash(field.type))
107
- elsif field.array? && field.custom_element_type?
108
- field.with_attribute(:element_type, append_hash(field.element_type.type).to_sym)
109
- else
110
- field
111
- end.with_default(new_default)
112
- end
71
+ normalized_tables.merge(
72
+ table.name => temporary_table.with_name(table.name)
73
+ .with_fields(rename_types_back(temporary_table.fields))
74
+ .with_indexes(rename_indexes_back(temporary_table.indexes))
75
+ .with_checks(rename_types_in_checks_back(temporary_table.checks))
76
+ .with_foreign_keys(table.foreign_keys)
77
+ )
113
78
  end
79
+ end
114
80
 
115
- def rename_types_back(fields)
116
- fields.map do |field|
117
- new_default = if field.default_is_expression?
118
- rename_all_types_back_in(field.default.to_s).to_sym
119
- else
120
- field.default
121
- end
122
-
123
- if field.custom?
124
- field.with_type(remove_hash(field.type))
125
- elsif field.array? && field.custom_element_type?
126
- field.with_attribute(:element_type, remove_hash(field.element_type.type).to_sym)
127
- else
128
- field
129
- end.with_default(new_default)
81
+ def rename_types(fields)
82
+ fields.map do |field|
83
+ new_default = if field.default_is_expression?
84
+ rename_all_types_in(field.default.to_s).to_sym
85
+ else
86
+ field.default
130
87
  end
131
- end
132
88
 
133
- def rename_indexes(indexes)
134
- indexes.map do |index|
135
- index
136
- .with_name(append_hash(index.name))
137
- .with_condition(rename_all_types_in(index.condition))
138
- end
89
+ if field.custom?
90
+ field.with_type(append_hash(field.type))
91
+ elsif field.array? && field.custom_element_type?
92
+ field.with_attribute(:element_type, append_hash(field.element_type.type).to_sym)
93
+ else
94
+ field
95
+ end.with_default(new_default)
139
96
  end
97
+ end
140
98
 
141
- def rename_indexes_back(indexes)
142
- indexes.map do |index|
143
- index
144
- .with_name(remove_hash(index.name))
145
- .with_condition(rename_all_types_back_in(index.condition))
99
+ def rename_types_back(fields)
100
+ fields.map do |field|
101
+ new_default = if field.default_is_expression?
102
+ rename_all_types_back_in(field.default.to_s).to_sym
103
+ else
104
+ field.default
146
105
  end
147
- end
148
106
 
149
- def rename_types_in_checks(checks)
150
- checks.map do |check|
151
- check.with_condition(rename_all_types_in(check.condition))
152
- end
107
+ if field.custom?
108
+ field.with_type(remove_hash(field.type))
109
+ elsif field.array? && field.custom_element_type?
110
+ field.with_attribute(:element_type, remove_hash(field.element_type.type).to_sym)
111
+ else
112
+ field
113
+ end.with_default(new_default)
153
114
  end
115
+ end
154
116
 
155
- def rename_types_in_checks_back(checks)
156
- checks.map do |check|
157
- check.with_condition(rename_all_types_back_in(check.condition))
158
- end
117
+ def rename_indexes(indexes)
118
+ indexes.map do |index|
119
+ index
120
+ .with_name(append_hash(index.name))
121
+ .with_condition(rename_all_types_in(index.condition))
159
122
  end
123
+ end
160
124
 
161
- def temporary_table_name
162
- append_hash(table.name)
125
+ def rename_indexes_back(indexes)
126
+ indexes.map do |index|
127
+ index
128
+ .with_name(remove_hash(index.name))
129
+ .with_condition(rename_all_types_back_in(index.condition))
163
130
  end
131
+ end
164
132
 
165
- def append_hash(name)
166
- "#{name}_#{hash}"
133
+ def rename_types_in_checks(checks)
134
+ checks.map do |check|
135
+ check.with_condition(rename_all_types_in(check.condition))
167
136
  end
137
+ end
168
138
 
169
- def remove_hash(name)
170
- name.to_s.sub(/_#{Regexp.escape(hash)}$/, '').to_sym
139
+ def rename_types_in_checks_back(checks)
140
+ checks.map do |check|
141
+ check.with_condition(rename_all_types_back_in(check.condition))
171
142
  end
143
+ end
172
144
 
173
- def rename_all_types_in(string)
174
- return string unless string.is_a?(String)
145
+ def append_hash(name)
146
+ "#{name}_#{hash}"
147
+ end
175
148
 
176
- enum_renaming.reduce(string) do |new_string, (from, to)|
177
- new_string.gsub(from, to)
178
- end
149
+ def remove_hash(name)
150
+ name.to_s.sub(/_#{Regexp.escape(hash)}$/, '').to_sym
151
+ end
152
+
153
+ def rename_all_types_in(string)
154
+ return string unless string.is_a?(String)
155
+
156
+ enum_renaming.reduce(string) do |new_string, (from, to)|
157
+ new_string.gsub(from, to)
179
158
  end
159
+ end
180
160
 
181
- def rename_all_types_back_in(string)
182
- return string unless string.is_a?(String)
161
+ def rename_all_types_back_in(string)
162
+ return string unless string.is_a?(String)
183
163
 
184
- enum_renaming.invert.reduce(string) do |new_string, (from, to)|
185
- new_string.gsub(from, to)
186
- end
164
+ enum_renaming.invert.reduce(string) do |new_string, (from, to)|
165
+ new_string.gsub(from, to)
187
166
  end
167
+ end
188
168
 
189
- def enum_renaming
190
- enum_names.reduce({}) do |hash, enum_name|
191
- hash.merge("::#{enum_name}" => "::#{append_hash(enum_name)}")
169
+ def enum_renaming
170
+ schema.enums.reduce({}) do |hash, enum|
171
+ hash.merge("::#{enum.name}" => "::#{append_hash(enum.name)}")
172
+ end
173
+ end
174
+
175
+ def hash
176
+ @hash ||= begin
177
+ names = schema.tables.flat_map do |table|
178
+ [table.name] + table.fields.map(&:name) + table.indexes.map(&:name) + table.checks.map(&:name)
192
179
  end
180
+
181
+ Digest::MD5.hexdigest(names.join(','))[0..9]
193
182
  end
194
183
  end
195
184
  end
@@ -68,10 +68,6 @@ module DbSchema
68
68
  field.type
69
69
  end
70
70
 
71
- def primary_key?
72
- field.primary_key?
73
- end
74
-
75
71
  def options
76
72
  field.options
77
73
  end
@@ -84,21 +80,26 @@ module DbSchema
84
80
  end
85
81
 
86
82
  class AlterColumnType
87
- include Dry::Equalizer(:name, :new_type, :using, :new_attributes)
88
- attr_reader :name, :new_type, :using, :new_attributes
83
+ SERIAL_TYPES = [:smallserial, :serial, :bigserial].freeze
84
+
85
+ include Dry::Equalizer(:name, :old_type, :new_type, :using, :new_attributes)
86
+ attr_reader :name, :old_type, :new_type, :using, :new_attributes
89
87
 
90
- def initialize(name, new_type:, using: nil, **new_attributes)
88
+ def initialize(name, old_type:, new_type:, using: nil, **new_attributes)
91
89
  @name = name
90
+ @old_type = old_type
92
91
  @new_type = new_type
93
92
  @using = using
94
93
  @new_attributes = new_attributes
95
94
  end
96
- end
97
95
 
98
- class CreatePrimaryKey < ColumnOperation
99
- end
96
+ def from_serial?
97
+ SERIAL_TYPES.include?(old_type)
98
+ end
100
99
 
101
- class DropPrimaryKey < ColumnOperation
100
+ def to_serial?
101
+ SERIAL_TYPES.include?(new_type)
102
+ end
102
103
  end
103
104
 
104
105
  class AllowNull < ColumnOperation
@@ -124,9 +125,32 @@ module DbSchema
124
125
  def initialize(index)
125
126
  @index = index
126
127
  end
128
+
129
+ def primary?
130
+ index.primary?
131
+ end
132
+
133
+ def name
134
+ index.name
135
+ end
136
+
137
+ def columns
138
+ index.columns
139
+ end
127
140
  end
128
141
 
129
- class DropIndex < ColumnOperation
142
+ class DropIndex
143
+ include Dry::Equalizer(:name, :primary?)
144
+ attr_reader :name
145
+
146
+ def initialize(name, primary)
147
+ @name = name
148
+ @primary = primary
149
+ end
150
+
151
+ def primary?
152
+ @primary
153
+ end
130
154
  end
131
155
 
132
156
  class CreateCheckConstraint
@@ -44,24 +44,22 @@ module DbSchema
44
44
  def create_table(change)
45
45
  connection.create_table(change.table.name) do
46
46
  change.table.fields.each do |field|
47
- if field.primary_key? && field.type == :integer
48
- primary_key(field.name)
49
- else
50
- options = Runner.map_options(field.class.type, field.options)
51
- column(field.name, field.type.capitalize, options)
52
-
53
- primary_key([field.name]) if field.primary_key?
54
- end
47
+ options = Runner.map_options(field.class.type, field.options)
48
+ column(field.name, field.type.capitalize, options)
55
49
  end
56
50
 
57
51
  change.table.indexes.each do |index|
58
- index(
59
- index.columns_to_sequel,
60
- name: index.name,
61
- unique: index.unique?,
62
- type: index.type,
63
- where: index.condition
64
- )
52
+ if index.primary?
53
+ primary_key(index.columns.map(&:name), name: index.name)
54
+ else
55
+ index(
56
+ index.columns_to_sequel,
57
+ name: index.name,
58
+ unique: index.unique?,
59
+ type: index.type,
60
+ where: index.condition
61
+ )
62
+ end
65
63
  end
66
64
 
67
65
  change.table.checks.each do |check|
@@ -83,25 +81,23 @@ module DbSchema
83
81
  change.changes.each do |element|
84
82
  case element
85
83
  when Operations::CreateColumn
86
- if element.primary_key? && element.type == :integer
87
- add_primary_key(element.name)
88
- else
89
- options = Runner.map_options(element.type, element.options)
90
- add_column(element.name, element.type.capitalize, options)
91
-
92
- add_primary_key([element.name]) if element.primary_key?
93
- end
84
+ options = Runner.map_options(element.type, element.options)
85
+ add_column(element.name, element.type.capitalize, options)
94
86
  when Operations::DropColumn
95
87
  drop_column(element.name)
96
88
  when Operations::RenameColumn
97
89
  rename_column(element.old_name, element.new_name)
98
90
  when Operations::AlterColumnType
91
+ if element.from_serial?
92
+ raise NotImplementedError, 'Changing a SERIAL column to another type is not supported'
93
+ end
94
+
95
+ if element.to_serial?
96
+ raise NotImplementedError, 'Changing a column type to SERIAL is not supported'
97
+ end
98
+
99
99
  attributes = Runner.map_options(element.new_type, element.new_attributes)
100
100
  set_column_type(element.name, element.new_type.capitalize, using: element.using, **attributes)
101
- when Operations::CreatePrimaryKey
102
- raise NotImplementedError, 'Converting an existing column to primary key is currently unsupported'
103
- when Operations::DropPrimaryKey
104
- raise NotImplementedError, 'Removing a primary key while leaving the column is currently unsupported'
105
101
  when Operations::AllowNull
106
102
  set_column_allow_null(element.name)
107
103
  when Operations::DisallowNull
@@ -109,15 +105,23 @@ module DbSchema
109
105
  when Operations::AlterColumnDefault
110
106
  set_column_default(element.name, Runner.default_to_sequel(element.new_default))
111
107
  when Operations::CreateIndex
112
- add_index(
113
- element.index.columns_to_sequel,
114
- name: element.index.name,
115
- unique: element.index.unique?,
116
- type: element.index.type,
117
- where: element.index.condition
118
- )
108
+ if element.primary?
109
+ add_primary_key(element.columns.map(&:name), name: element.name)
110
+ else
111
+ add_index(
112
+ element.index.columns_to_sequel,
113
+ name: element.index.name,
114
+ unique: element.index.unique?,
115
+ type: element.index.type,
116
+ where: element.index.condition
117
+ )
118
+ end
119
119
  when Operations::DropIndex
120
- drop_index([], name: element.name)
120
+ if element.primary?
121
+ drop_constraint(element.name)
122
+ else
123
+ drop_index([], name: element.name)
124
+ end
121
125
  when Operations::CreateCheckConstraint
122
126
  add_constraint(element.check.name, element.check.condition)
123
127
  when Operations::DropCheckConstraint
@@ -5,7 +5,7 @@ module DbSchema
5
5
  class << self
6
6
  def validate(schema)
7
7
  table_errors = schema.tables.each_with_object([]) do |table, errors|
8
- primary_keys_count = table.fields.select(&:primary_key?).count
8
+ primary_keys_count = table.indexes.select(&:primary?).count
9
9
  if primary_keys_count > 1
10
10
  error_message = %(Table "#{table.name}" has #{primary_keys_count} primary keys)
11
11
  errors << error_message
@@ -60,7 +60,7 @@ module DbSchema
60
60
 
61
61
  if referenced_table = schema.tables.find { |table| table.name == fkey.table }
62
62
  if fkey.references_primary_key?
63
- unless referenced_table.fields.any?(&:primary_key?)
63
+ unless referenced_table.indexes.any?(&:primary?)
64
64
  error_message = %(Foreign key "#{fkey.name}" refers to primary key of table "#{fkey.table}" which does not have a primary key)
65
65
  errors << error_message
66
66
  end
@@ -1,3 +1,3 @@
1
1
  module DbSchema
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.rc1'
3
3
  end
data/lib/db_schema.rb CHANGED
@@ -114,8 +114,7 @@ module DbSchema
114
114
  end
115
115
 
116
116
  def run_migrations(migrations, connection)
117
- reader = Reader.reader_for(connection)
118
- @current_schema = reader.read_schema
117
+ @current_schema = Reader.reader_for(connection).read_schema
119
118
 
120
119
  migrations.reduce(@current_schema) do |schema, migration|
121
120
  migrator = Migrator.new(migration)
@@ -123,7 +122,7 @@ module DbSchema
123
122
  if migrator.applicable?(schema)
124
123
  log_migration(migration) if configuration.log_changes?
125
124
  migrator.run!(connection)
126
- reader.read_schema
125
+ Reader.reader_for(connection).read_schema
127
126
  else
128
127
  schema
129
128
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vsevolod Romashov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-04 00:00:00.000000000 Z
11
+ date: 2019-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: db_schema-definitions
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 0.1.1
47
+ version: 0.2.rc1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 0.1.1
54
+ version: 0.2.rc1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -168,16 +168,16 @@ dependencies:
168
168
  name: db_schema-reader-postgres
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - "~>"
171
+ - - '='
172
172
  - !ruby/object:Gem::Version
173
- version: 0.1.1
173
+ version: 0.2.rc1
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - "~>"
178
+ - - '='
179
179
  - !ruby/object:Gem::Version
180
- version: 0.1.1
180
+ version: 0.2.rc1
181
181
  description: A database schema management tool that reads a "single-source-of-truth"
182
182
  schema definition from a ruby file and auto-migrates the database to conform to
183
183
  it.
@@ -228,12 +228,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
228
228
  version: '0'
229
229
  required_rubygems_version: !ruby/object:Gem::Requirement
230
230
  requirements:
231
- - - ">="
231
+ - - ">"
232
232
  - !ruby/object:Gem::Version
233
- version: '0'
233
+ version: 1.3.1
234
234
  requirements: []
235
- rubyforge_project:
236
- rubygems_version: 2.7.6
235
+ rubygems_version: 3.0.1
237
236
  signing_key:
238
237
  specification_version: 4
239
238
  summary: Declarative database schema definition.