db_schema 0.4.1 → 0.5.rc1

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