db_schema 0.2.4 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +11 -9
- data/lib/db_schema.rb +1 -1
- data/lib/db_schema/awesome_print.rb +51 -20
- data/lib/db_schema/changes.rb +76 -95
- data/lib/db_schema/definitions.rb +66 -0
- data/lib/db_schema/definitions/field/array.rb +15 -4
- data/lib/db_schema/definitions/field/base.rb +16 -0
- data/lib/db_schema/definitions/field/custom.rb +10 -1
- data/lib/db_schema/dsl.rb +6 -0
- data/lib/db_schema/normalizer.rb +108 -87
- data/lib/db_schema/reader.rb +1 -1
- data/lib/db_schema/runner.rb +44 -25
- data/lib/db_schema/validator.rb +23 -2
- data/lib/db_schema/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd3abbf119d7700e83eedf09b10b100cfc744f9e
|
4
|
+
data.tar.gz: d3e8cfd07c1fdcd902e705b337eed52f2bdf8829
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 251c126151289b49ea663c2d57c46334d9ba98c9419110121acbb436aee006871eec48aeabbe3ea1a6e6369b1a76fd4ff07e5e9d3cbb0c88e12e13ba609269b2
|
7
|
+
data.tar.gz: a6163f3a147938bad96b7d891f26990fc9c86068f716ec236e4647dda13019c1af9bf1a2ba566fe3c4c8f0b46d5edd6db29ed002eb3212d56590e62fc6baf84f
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# DbSchema [](https://travis-ci.org/7even/db_schema) [](https://badge.fury.io/rb/db_schema)
|
1
|
+
# DbSchema [](https://travis-ci.org/7even/db_schema) [](https://badge.fury.io/rb/db_schema) [](https://gitter.im/7even/db_schema?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
2
2
|
|
3
3
|
DbSchema is an opinionated database schema management tool that lets you maintain your DB schema with a single ruby file.
|
4
4
|
|
@@ -53,7 +53,7 @@ But you would lose it even with manual migrations.
|
|
53
53
|
Add this line to your application's Gemfile:
|
54
54
|
|
55
55
|
``` ruby
|
56
|
-
gem 'db_schema', '~> 0.2.
|
56
|
+
gem 'db_schema', '~> 0.2.5'
|
57
57
|
```
|
58
58
|
|
59
59
|
And then execute:
|
@@ -148,9 +148,9 @@ If your production setup doesn't include multiple workers starting simultaneousl
|
|
148
148
|
## DSL
|
149
149
|
|
150
150
|
Database schema is defined with a block passed to `DbSchema.describe` method.
|
151
|
-
This block receives a `db` object on which you can call `#table` to define a table
|
152
|
-
|
153
|
-
is described in a block passed to `#table`.
|
151
|
+
This block receives a `db` object on which you can call `#table` to define a table,
|
152
|
+
`#enum` to define a custom enum type and `#extension` to plug a Postgres extension into your database.
|
153
|
+
Everything that belongs to a specific table is described in a block passed to `#table`.
|
154
154
|
|
155
155
|
``` ruby
|
156
156
|
DbSchema.describe do |db|
|
@@ -500,13 +500,15 @@ db.table :users do |t|
|
|
500
500
|
end
|
501
501
|
```
|
502
502
|
|
503
|
-
|
503
|
+
Arrays of enums are also supported - they are described just like arrays of any other element type:
|
504
504
|
|
505
505
|
``` ruby
|
506
|
-
db.enum :
|
507
|
-
```
|
506
|
+
db.enum :user_role, [:user, :manager, :admin]
|
508
507
|
|
509
|
-
|
508
|
+
db.table :users do |t|
|
509
|
+
t.array :roles, of: :user_role, default: '{user}'
|
510
|
+
end
|
511
|
+
```
|
510
512
|
|
511
513
|
### Extensions
|
512
514
|
|
data/lib/db_schema.rb
CHANGED
@@ -18,7 +18,7 @@ module DbSchema
|
|
18
18
|
def describe(&block)
|
19
19
|
desired_schema = DSL.new(block).schema
|
20
20
|
validate(desired_schema)
|
21
|
-
Normalizer.
|
21
|
+
Normalizer.new(desired_schema).normalize_tables
|
22
22
|
|
23
23
|
actual_schema = Reader.read_schema
|
24
24
|
changes = Changes.between(desired_schema, actual_schema)
|
@@ -15,6 +15,8 @@ if defined?(AwesomePrint)
|
|
15
15
|
case object
|
16
16
|
when ::DbSchema::Definitions::Schema
|
17
17
|
:dbschema_schema
|
18
|
+
when ::DbSchema::Definitions::NullTable
|
19
|
+
:dbschema_null_table
|
18
20
|
when ::DbSchema::Definitions::Table
|
19
21
|
:dbschema_table
|
20
22
|
when ::DbSchema::Definitions::Field::Custom
|
@@ -55,11 +57,11 @@ if defined?(AwesomePrint)
|
|
55
57
|
when ::DbSchema::Changes::AlterColumnDefault
|
56
58
|
:dbschema_alter_column_default
|
57
59
|
when ::DbSchema::Changes::CreateIndex
|
58
|
-
:
|
60
|
+
:dbschema_create_index
|
59
61
|
when ::DbSchema::Changes::DropIndex
|
60
62
|
:dbschema_column_operation
|
61
63
|
when ::DbSchema::Changes::CreateCheckConstraint
|
62
|
-
:
|
64
|
+
:dbschema_create_check_constraint
|
63
65
|
when ::DbSchema::Changes::DropCheckConstraint
|
64
66
|
:dbschema_column_operation
|
65
67
|
when ::DbSchema::Changes::CreateForeignKey
|
@@ -67,13 +69,14 @@ if defined?(AwesomePrint)
|
|
67
69
|
when ::DbSchema::Changes::DropForeignKey
|
68
70
|
:dbschema_drop_foreign_key
|
69
71
|
when ::DbSchema::Changes::CreateEnum
|
70
|
-
:
|
72
|
+
:dbschema_create_enum
|
71
73
|
when ::DbSchema::Changes::DropEnum
|
72
74
|
:dbschema_column_operation
|
73
|
-
when ::DbSchema::Changes::
|
74
|
-
:
|
75
|
-
when ::DbSchema::Changes::CreateExtension
|
76
|
-
|
75
|
+
when ::DbSchema::Changes::AlterEnumValues
|
76
|
+
:dbschema_alter_enum_values
|
77
|
+
when ::DbSchema::Changes::CreateExtension
|
78
|
+
:dbschema_create_extension
|
79
|
+
when ::DbSchema::Changes::DropExtension
|
77
80
|
:dbschema_column_operation
|
78
81
|
else
|
79
82
|
cast_without_dbschema(object, type)
|
@@ -100,6 +103,10 @@ if defined?(AwesomePrint)
|
|
100
103
|
"#<DbSchema::Definitions::Table #{object.name.ai} #{data_string}>"
|
101
104
|
end
|
102
105
|
|
106
|
+
def awesome_dbschema_null_table(object)
|
107
|
+
'#<DbSchema::Definitions::NullTable>'
|
108
|
+
end
|
109
|
+
|
103
110
|
def awesome_dbschema_field(object)
|
104
111
|
options = object.options.map do |k, v|
|
105
112
|
key = colorize("#{k}:", :symbol)
|
@@ -190,12 +197,12 @@ if defined?(AwesomePrint)
|
|
190
197
|
end
|
191
198
|
|
192
199
|
def awesome_dbschema_create_table(object)
|
193
|
-
data = ["fields: #{object.fields.ai}"]
|
194
|
-
data << "indices: #{object.indices.ai}" if object.indices.any?
|
195
|
-
data << "checks: #{object.checks.ai}" if object.checks.any?
|
200
|
+
data = ["fields: #{object.table.fields.ai}"]
|
201
|
+
data << "indices: #{object.table.indices.ai}" if object.table.indices.any?
|
202
|
+
data << "checks: #{object.table.checks.ai}" if object.table.checks.any?
|
196
203
|
|
197
204
|
data_string = indent_lines(data.join(', '))
|
198
|
-
"#<DbSchema::Changes::CreateTable #{object.name.ai} #{data_string}>"
|
205
|
+
"#<DbSchema::Changes::CreateTable #{object.table.name.ai} #{data_string}>"
|
199
206
|
end
|
200
207
|
|
201
208
|
def awesome_dbschema_drop_table(object)
|
@@ -203,12 +210,7 @@ if defined?(AwesomePrint)
|
|
203
210
|
end
|
204
211
|
|
205
212
|
def awesome_dbschema_alter_table(object)
|
206
|
-
|
207
|
-
data << "indices: #{object.indices.ai}" if object.indices.any?
|
208
|
-
data << "checks: #{object.checks.ai}" if object.checks.any?
|
209
|
-
|
210
|
-
data_string = indent_lines(data.join(', '))
|
211
|
-
"#<DbSchema::Changes::AlterTable #{object.name.ai} #{data_string}>"
|
213
|
+
"#<DbSchema::Changes::AlterTable #{object.table_name.ai} #{indent_lines(object.changes.ai)}>"
|
212
214
|
end
|
213
215
|
|
214
216
|
def awesome_dbschema_create_column(object)
|
@@ -242,6 +244,21 @@ if defined?(AwesomePrint)
|
|
242
244
|
"#<DbSchema::Changes::AlterColumnDefault #{object.name.ai}, #{new_default}>"
|
243
245
|
end
|
244
246
|
|
247
|
+
def awesome_dbschema_create_index(object)
|
248
|
+
columns = format_dbschema_fields(object.index.columns)
|
249
|
+
using = ' using ' + colorize(object.index.type.to_s, :symbol) unless object.index.btree?
|
250
|
+
|
251
|
+
data = [nil]
|
252
|
+
data << colorize('unique', :nilclass) if object.index.unique?
|
253
|
+
data << colorize('condition: ', :symbol) + object.index.condition.ai unless object.index.condition.nil?
|
254
|
+
|
255
|
+
"#<#{object.class} #{object.index.name.ai} on #{columns}#{using}#{data.join(', ')}>"
|
256
|
+
end
|
257
|
+
|
258
|
+
def awesome_dbschema_create_check_constraint(object)
|
259
|
+
"#<#{object.class} #{object.check.name.ai} #{object.check.condition.ai}>"
|
260
|
+
end
|
261
|
+
|
245
262
|
def awesome_dbschema_create_foreign_key(object)
|
246
263
|
"#<DbSchema::Changes::CreateForeignKey #{object.foreign_key.ai} on #{object.table_name.ai}>"
|
247
264
|
end
|
@@ -250,14 +267,28 @@ if defined?(AwesomePrint)
|
|
250
267
|
"#<DbSchema::Changes::DropForeignKey #{object.fkey_name.ai} on #{object.table_name.ai}>"
|
251
268
|
end
|
252
269
|
|
270
|
+
def awesome_dbschema_create_enum(object)
|
271
|
+
values = object.enum.values.map do |value|
|
272
|
+
colorize(value.to_s, :string)
|
273
|
+
end.join(', ')
|
274
|
+
|
275
|
+
"#<#{object.class} #{object.enum.name.ai} (#{values})>"
|
276
|
+
end
|
277
|
+
|
253
278
|
def awesome_dbschema_column_operation(object)
|
254
279
|
"#<#{object.class} #{object.name.ai}>"
|
255
280
|
end
|
256
281
|
|
257
|
-
def
|
258
|
-
|
282
|
+
def awesome_dbschema_alter_enum_values(object)
|
283
|
+
values = object.new_values.map do |value|
|
284
|
+
colorize(value.to_s, :string)
|
285
|
+
end.join(', ')
|
286
|
+
|
287
|
+
"#<DbSchema::Changes::AlterEnumValues #{object.enum_name.ai} to (#{values})>"
|
288
|
+
end
|
259
289
|
|
260
|
-
|
290
|
+
def awesome_dbschema_create_extension(object)
|
291
|
+
"#<#{object.class} #{object.extension.name.ai}>"
|
261
292
|
end
|
262
293
|
|
263
294
|
def format_dbschema_fields(fields)
|
data/lib/db_schema/changes.rb
CHANGED
@@ -12,12 +12,7 @@ module DbSchema
|
|
12
12
|
actual = actual_schema.tables.find { |table| table.name == table_name }
|
13
13
|
|
14
14
|
if desired && !actual
|
15
|
-
changes << CreateTable.new(
|
16
|
-
table_name,
|
17
|
-
fields: desired.fields,
|
18
|
-
indices: desired.indices,
|
19
|
-
checks: desired.checks
|
20
|
-
)
|
15
|
+
changes << CreateTable.new(desired)
|
21
16
|
|
22
17
|
fkey_operations = desired.foreign_keys.map do |fkey|
|
23
18
|
CreateForeignKey.new(table_name, fkey)
|
@@ -38,9 +33,7 @@ module DbSchema
|
|
38
33
|
if field_operations.any? || index_operations.any? || check_operations.any?
|
39
34
|
changes << AlterTable.new(
|
40
35
|
table_name,
|
41
|
-
|
42
|
-
indices: index_operations,
|
43
|
-
checks: check_operations
|
36
|
+
field_operations + index_operations + check_operations
|
44
37
|
)
|
45
38
|
end
|
46
39
|
|
@@ -55,36 +48,37 @@ module DbSchema
|
|
55
48
|
actual = actual_schema.enums.find { |enum| enum.name == enum_name }
|
56
49
|
|
57
50
|
if desired && !actual
|
58
|
-
changes << CreateEnum.new(
|
51
|
+
changes << CreateEnum.new(desired)
|
59
52
|
elsif actual && !desired
|
60
53
|
changes << DropEnum.new(enum_name)
|
61
54
|
elsif actual != desired
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
changes << AddValueToEnum.new(enum_name, value, before: next_value)
|
55
|
+
fields = actual_schema.tables.flat_map do |table|
|
56
|
+
table.fields.select do |field|
|
57
|
+
if field.array?
|
58
|
+
field.attributes[:element_type] == enum_name
|
59
|
+
else
|
60
|
+
field.type == enum_name
|
61
|
+
end
|
62
|
+
end.map do |field|
|
63
|
+
if desired_field = desired_schema[table.name][field.name]
|
64
|
+
new_default = desired_field.default
|
65
|
+
end
|
66
|
+
|
67
|
+
{
|
68
|
+
table_name: table.name,
|
69
|
+
field_name: field.name,
|
70
|
+
new_default: new_default,
|
71
|
+
array: field.array?
|
72
|
+
}
|
81
73
|
end
|
82
74
|
end
|
75
|
+
|
76
|
+
changes << AlterEnumValues.new(enum_name, desired.values, fields)
|
83
77
|
end
|
84
78
|
end
|
85
79
|
|
86
80
|
extension_changes = (desired_schema.extensions - actual_schema.extensions).map do |extension|
|
87
|
-
CreateExtension.new(extension
|
81
|
+
CreateExtension.new(extension)
|
88
82
|
end + (actual_schema.extensions - desired_schema.extensions).map do |extension|
|
89
83
|
DropExtension.new(extension.name)
|
90
84
|
end
|
@@ -144,24 +138,12 @@ module DbSchema
|
|
144
138
|
actual = actual_indices.find { |index| index.name == index_name }
|
145
139
|
|
146
140
|
if desired && !actual
|
147
|
-
table_changes << CreateIndex.new(
|
148
|
-
name: index_name,
|
149
|
-
columns: desired.columns,
|
150
|
-
unique: desired.unique?,
|
151
|
-
type: desired.type,
|
152
|
-
condition: desired.condition
|
153
|
-
)
|
141
|
+
table_changes << CreateIndex.new(desired)
|
154
142
|
elsif actual && !desired
|
155
143
|
table_changes << DropIndex.new(index_name)
|
156
144
|
elsif actual != desired
|
157
145
|
table_changes << DropIndex.new(index_name)
|
158
|
-
table_changes << CreateIndex.new(
|
159
|
-
name: index_name,
|
160
|
-
columns: desired.columns,
|
161
|
-
unique: desired.unique?,
|
162
|
-
type: desired.type,
|
163
|
-
condition: desired.condition
|
164
|
-
)
|
146
|
+
table_changes << CreateIndex.new(desired)
|
165
147
|
end
|
166
148
|
end
|
167
149
|
end
|
@@ -174,18 +156,12 @@ module DbSchema
|
|
174
156
|
actual = actual_checks.find { |check| check.name == check_name }
|
175
157
|
|
176
158
|
if desired && !actual
|
177
|
-
table_changes << CreateCheckConstraint.new(
|
178
|
-
name: check_name,
|
179
|
-
condition: desired.condition
|
180
|
-
)
|
159
|
+
table_changes << CreateCheckConstraint.new(desired)
|
181
160
|
elsif actual && !desired
|
182
161
|
table_changes << DropCheckConstraint.new(check_name)
|
183
162
|
elsif actual != desired
|
184
163
|
table_changes << DropCheckConstraint.new(check_name)
|
185
|
-
table_changes << CreateCheckConstraint.new(
|
186
|
-
name: check_name,
|
187
|
-
condition: desired.condition
|
188
|
-
)
|
164
|
+
table_changes << CreateCheckConstraint.new(desired)
|
189
165
|
end
|
190
166
|
end
|
191
167
|
end
|
@@ -197,37 +173,24 @@ module DbSchema
|
|
197
173
|
desired = desired_foreign_keys.find { |key| key.name == key_name }
|
198
174
|
actual = actual_foreign_keys.find { |key| key.name == key_name }
|
199
175
|
|
200
|
-
foreign_key = Definitions::ForeignKey.new(
|
201
|
-
name: key_name,
|
202
|
-
fields: desired.fields,
|
203
|
-
table: desired.table,
|
204
|
-
keys: desired.keys,
|
205
|
-
on_delete: desired.on_delete,
|
206
|
-
on_update: desired.on_update,
|
207
|
-
deferrable: desired.deferrable?
|
208
|
-
) if desired
|
209
|
-
|
210
176
|
if desired && !actual
|
211
|
-
table_changes << CreateForeignKey.new(table_name,
|
177
|
+
table_changes << CreateForeignKey.new(table_name, desired)
|
212
178
|
elsif actual && !desired
|
213
179
|
table_changes << DropForeignKey.new(table_name, key_name)
|
214
180
|
elsif actual != desired
|
215
181
|
table_changes << DropForeignKey.new(table_name, key_name)
|
216
|
-
table_changes << CreateForeignKey.new(table_name,
|
182
|
+
table_changes << CreateForeignKey.new(table_name, desired)
|
217
183
|
end
|
218
184
|
end
|
219
185
|
end
|
220
186
|
end
|
221
187
|
|
222
188
|
class CreateTable
|
223
|
-
include Dry::Equalizer(:
|
224
|
-
attr_reader :
|
225
|
-
|
226
|
-
def initialize(
|
227
|
-
@
|
228
|
-
@fields = fields
|
229
|
-
@indices = indices
|
230
|
-
@checks = checks
|
189
|
+
include Dry::Equalizer(:table)
|
190
|
+
attr_reader :table
|
191
|
+
|
192
|
+
def initialize(table)
|
193
|
+
@table = table
|
231
194
|
end
|
232
195
|
end
|
233
196
|
|
@@ -241,14 +204,12 @@ module DbSchema
|
|
241
204
|
end
|
242
205
|
|
243
206
|
class AlterTable
|
244
|
-
include Dry::Equalizer(:
|
245
|
-
attr_reader :
|
246
|
-
|
247
|
-
def initialize(
|
248
|
-
@
|
249
|
-
@
|
250
|
-
@indices = indices
|
251
|
-
@checks = checks
|
207
|
+
include Dry::Equalizer(:table_name, :changes)
|
208
|
+
attr_reader :table_name, :changes
|
209
|
+
|
210
|
+
def initialize(table_name, changes)
|
211
|
+
@table_name = table_name
|
212
|
+
@changes = changes
|
252
213
|
end
|
253
214
|
end
|
254
215
|
|
@@ -332,13 +293,25 @@ module DbSchema
|
|
332
293
|
end
|
333
294
|
end
|
334
295
|
|
335
|
-
class CreateIndex
|
296
|
+
class CreateIndex
|
297
|
+
include Dry::Equalizer(:index)
|
298
|
+
attr_reader :index
|
299
|
+
|
300
|
+
def initialize(index)
|
301
|
+
@index = index
|
302
|
+
end
|
336
303
|
end
|
337
304
|
|
338
305
|
class DropIndex < ColumnOperation
|
339
306
|
end
|
340
307
|
|
341
|
-
class CreateCheckConstraint
|
308
|
+
class CreateCheckConstraint
|
309
|
+
include Dry::Equalizer(:check)
|
310
|
+
attr_reader :check
|
311
|
+
|
312
|
+
def initialize(check)
|
313
|
+
@check = check
|
314
|
+
end
|
342
315
|
end
|
343
316
|
|
344
317
|
class DropCheckConstraint < ColumnOperation
|
@@ -364,28 +337,36 @@ module DbSchema
|
|
364
337
|
end
|
365
338
|
end
|
366
339
|
|
367
|
-
class CreateEnum
|
340
|
+
class CreateEnum
|
341
|
+
include Dry::Equalizer(:enum)
|
342
|
+
attr_reader :enum
|
343
|
+
|
344
|
+
def initialize(enum)
|
345
|
+
@enum = enum
|
346
|
+
end
|
368
347
|
end
|
369
348
|
|
370
349
|
class DropEnum < ColumnOperation
|
371
350
|
end
|
372
351
|
|
373
|
-
class
|
374
|
-
include Dry::Equalizer(:enum_name, :
|
375
|
-
attr_reader :enum_name, :
|
376
|
-
|
377
|
-
def initialize(enum_name, new_value, before: nil)
|
378
|
-
@enum_name = enum_name
|
379
|
-
@new_value = new_value
|
380
|
-
@before = before
|
381
|
-
end
|
352
|
+
class AlterEnumValues
|
353
|
+
include Dry::Equalizer(:enum_name, :new_values, :enum_fields)
|
354
|
+
attr_reader :enum_name, :new_values, :enum_fields
|
382
355
|
|
383
|
-
def
|
384
|
-
|
356
|
+
def initialize(enum_name, new_values, enum_fields)
|
357
|
+
@enum_name = enum_name
|
358
|
+
@new_values = new_values
|
359
|
+
@enum_fields = enum_fields
|
385
360
|
end
|
386
361
|
end
|
387
362
|
|
388
|
-
class CreateExtension
|
363
|
+
class CreateExtension
|
364
|
+
include Dry::Equalizer(:extension)
|
365
|
+
attr_reader :extension
|
366
|
+
|
367
|
+
def initialize(extension)
|
368
|
+
@extension = extension
|
369
|
+
end
|
389
370
|
end
|
390
371
|
|
391
372
|
class DropExtension < ColumnOperation
|
@@ -12,6 +12,10 @@ module DbSchema
|
|
12
12
|
@enums = enums
|
13
13
|
@extensions = extensions
|
14
14
|
end
|
15
|
+
|
16
|
+
def [](table_name)
|
17
|
+
tables.find { |table| table.name == table_name } || NullTable.new
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
class Table
|
@@ -31,6 +35,54 @@ module DbSchema
|
|
31
35
|
indices.any?(&:has_expressions?) ||
|
32
36
|
checks.any?
|
33
37
|
end
|
38
|
+
|
39
|
+
def [](field_name)
|
40
|
+
fields.find { |field| field.name == field_name }
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_name(new_name)
|
44
|
+
Table.new(
|
45
|
+
new_name,
|
46
|
+
fields: fields,
|
47
|
+
indices: indices,
|
48
|
+
checks: checks,
|
49
|
+
foreign_keys: foreign_keys
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def with_fields(new_fields)
|
54
|
+
Table.new(
|
55
|
+
name,
|
56
|
+
fields: new_fields,
|
57
|
+
indices: indices,
|
58
|
+
checks: checks,
|
59
|
+
foreign_keys: foreign_keys
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_indices(new_indices)
|
64
|
+
Table.new(
|
65
|
+
name,
|
66
|
+
fields: fields,
|
67
|
+
indices: new_indices,
|
68
|
+
checks: checks,
|
69
|
+
foreign_keys: foreign_keys
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_foreign_keys(new_foreign_keys)
|
74
|
+
Table.new(
|
75
|
+
name,
|
76
|
+
fields: fields,
|
77
|
+
indices: indices,
|
78
|
+
checks: checks,
|
79
|
+
foreign_keys: new_foreign_keys
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class NullTable < Table
|
85
|
+
def initialize; end
|
34
86
|
end
|
35
87
|
|
36
88
|
class Index
|
@@ -65,6 +117,16 @@ module DbSchema
|
|
65
117
|
!condition.nil? || columns.any?(&:expression?)
|
66
118
|
end
|
67
119
|
|
120
|
+
def with_name(new_name)
|
121
|
+
Index.new(
|
122
|
+
name: new_name,
|
123
|
+
columns: columns,
|
124
|
+
unique: unique?,
|
125
|
+
type: type,
|
126
|
+
condition: condition
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
68
130
|
class Column
|
69
131
|
include Dry::Equalizer(:name, :order, :nulls)
|
70
132
|
attr_reader :name, :order, :nulls
|
@@ -173,6 +235,10 @@ module DbSchema
|
|
173
235
|
@name = name
|
174
236
|
@values = values
|
175
237
|
end
|
238
|
+
|
239
|
+
def with_name(new_name)
|
240
|
+
Enum.new(new_name, values)
|
241
|
+
end
|
176
242
|
end
|
177
243
|
|
178
244
|
class Extension
|
@@ -3,16 +3,27 @@ module DbSchema
|
|
3
3
|
module Field
|
4
4
|
class Array < Base
|
5
5
|
register :array
|
6
|
-
attr_reader :element_type
|
7
6
|
|
8
|
-
def initialize(name,
|
9
|
-
|
10
|
-
|
7
|
+
def initialize(name, **options)
|
8
|
+
type_class = Field.type_class_for(options[:element_type])
|
9
|
+
super(name, **options.merge(element_type: type_class))
|
11
10
|
end
|
12
11
|
|
13
12
|
def attributes
|
14
13
|
super.merge(element_type: element_type.type)
|
15
14
|
end
|
15
|
+
|
16
|
+
def array?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def element_type
|
21
|
+
@attributes[:element_type]
|
22
|
+
end
|
23
|
+
|
24
|
+
def custom_element_type?
|
25
|
+
element_type.superclass == Custom
|
26
|
+
end
|
16
27
|
end
|
17
28
|
end
|
18
29
|
end
|
@@ -25,6 +25,14 @@ module DbSchema
|
|
25
25
|
default.is_a?(Symbol)
|
26
26
|
end
|
27
27
|
|
28
|
+
def array?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def custom?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
28
36
|
def options
|
29
37
|
attributes.tap do |options|
|
30
38
|
options[:null] = false unless null?
|
@@ -48,6 +56,14 @@ module DbSchema
|
|
48
56
|
self.class.type
|
49
57
|
end
|
50
58
|
|
59
|
+
def with_type(new_type)
|
60
|
+
Field.build(name, new_type, **options, primary_key: primary_key?)
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_attribute(attr_name, attr_value)
|
64
|
+
Field.build(name, type, **options, primary_key: primary_key?, attr_name => attr_value)
|
65
|
+
end
|
66
|
+
|
51
67
|
class << self
|
52
68
|
def register(*types)
|
53
69
|
types.each do |type|
|
@@ -6,7 +6,7 @@ module DbSchema
|
|
6
6
|
def class_for(type_name)
|
7
7
|
raise ArgumentError if type_name.nil?
|
8
8
|
|
9
|
-
Class.new(self) do
|
9
|
+
custom_types[type_name] ||= Class.new(self) do
|
10
10
|
define_method :type do
|
11
11
|
type_name
|
12
12
|
end
|
@@ -14,8 +14,17 @@ module DbSchema
|
|
14
14
|
define_singleton_method :type do
|
15
15
|
type_name
|
16
16
|
end
|
17
|
+
|
18
|
+
define_method :custom? do
|
19
|
+
true
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def custom_types
|
26
|
+
@custom_types ||= {}
|
27
|
+
end
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
data/lib/db_schema/dsl.rb
CHANGED
@@ -42,11 +42,17 @@ module DbSchema
|
|
42
42
|
end
|
43
43
|
|
44
44
|
DbSchema::Definitions::Field.registry.keys.each do |type|
|
45
|
+
next if type == :array
|
46
|
+
|
45
47
|
define_method(type) do |name, **options|
|
46
48
|
field(name, type, options)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
52
|
+
def array(name, of:, **options)
|
53
|
+
field(name, :array, element_type: of, **options)
|
54
|
+
end
|
55
|
+
|
50
56
|
def method_missing(method_name, name, *args, &block)
|
51
57
|
field(name, method_name, args.first || {})
|
52
58
|
end
|
data/lib/db_schema/normalizer.rb
CHANGED
@@ -2,118 +2,139 @@ require 'digest/md5'
|
|
2
2
|
|
3
3
|
module DbSchema
|
4
4
|
class Normalizer
|
5
|
-
attr_reader :
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
new(table).normalized_table
|
16
|
-
else
|
17
|
-
table
|
18
|
-
end
|
19
|
-
end
|
5
|
+
attr_reader :schema
|
6
|
+
|
7
|
+
def initialize(schema)
|
8
|
+
@schema = schema
|
9
|
+
end
|
10
|
+
|
11
|
+
def normalize_tables
|
12
|
+
DbSchema.connection.transaction do
|
13
|
+
create_extensions!
|
14
|
+
create_enums!
|
20
15
|
|
21
|
-
|
16
|
+
schema.tables = schema.tables.map do |table|
|
17
|
+
if table.has_expressions?
|
18
|
+
Table.new(table, hash).normalized_table
|
19
|
+
else
|
20
|
+
table
|
21
|
+
end
|
22
22
|
end
|
23
|
+
|
24
|
+
raise Sequel::Rollback
|
23
25
|
end
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
DbSchema::Runner.new([operation]).run!
|
30
|
-
end
|
28
|
+
private
|
29
|
+
def create_extensions!
|
30
|
+
operations = (schema.extensions - Reader.read_extensions).map do |extension|
|
31
|
+
Changes::CreateExtension.new(extension)
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
enums.each do |enum|
|
36
|
-
next if existing_enums_names.include?(enum.name)
|
34
|
+
Runner.new(operations).run!
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def create_enums!
|
38
|
+
operations = schema.enums.map do |enum|
|
39
|
+
Changes::CreateEnum.new(enum.with_name(append_hash(enum.name)))
|
41
40
|
end
|
42
|
-
end
|
43
41
|
|
44
|
-
|
45
|
-
@table = table
|
42
|
+
Runner.new(operations).run!
|
46
43
|
end
|
47
44
|
|
48
|
-
def
|
49
|
-
|
50
|
-
read_temporary_table
|
45
|
+
def append_hash(name)
|
46
|
+
"#{name}_#{hash}"
|
51
47
|
end
|
52
48
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Runner.new([operation]).run!
|
49
|
+
def hash
|
50
|
+
@hash ||= begin
|
51
|
+
names = schema.tables.flat_map do |table|
|
52
|
+
[table.name] + table.fields.map(&:name) + table.indices.map(&:name) + table.checks.map(&:name)
|
53
|
+
end
|
54
|
+
|
55
|
+
Digest::MD5.hexdigest(names.join(','))[0..9]
|
56
|
+
end
|
63
57
|
end
|
64
58
|
|
65
|
-
|
66
|
-
|
59
|
+
class Table
|
60
|
+
attr_reader :table, :hash
|
67
61
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
checks: temporary_table.checks,
|
73
|
-
foreign_keys: table.foreign_keys
|
74
|
-
)
|
75
|
-
end
|
62
|
+
def initialize(table, hash)
|
63
|
+
@table = table
|
64
|
+
@hash = hash
|
65
|
+
end
|
76
66
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
name: append_hash(index.name),
|
81
|
-
columns: index.columns,
|
82
|
-
unique: index.unique?,
|
83
|
-
type: index.type,
|
84
|
-
condition: index.condition
|
85
|
-
)
|
67
|
+
def normalized_table
|
68
|
+
create_temporary_table!
|
69
|
+
read_temporary_table
|
86
70
|
end
|
87
|
-
end
|
88
71
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
type: index.type,
|
96
|
-
condition: index.condition
|
72
|
+
private
|
73
|
+
def create_temporary_table!
|
74
|
+
operation = Changes::CreateTable.new(
|
75
|
+
table.with_name(temporary_table_name)
|
76
|
+
.with_fields(rename_types(table.fields))
|
77
|
+
.with_indices(rename_indices(table.indices))
|
97
78
|
)
|
79
|
+
|
80
|
+
Runner.new([operation]).run!
|
98
81
|
end
|
99
|
-
end
|
100
82
|
|
101
|
-
|
102
|
-
|
103
|
-
end
|
83
|
+
def read_temporary_table
|
84
|
+
temporary_table = Reader.read_table(temporary_table_name)
|
104
85
|
|
105
|
-
|
106
|
-
|
107
|
-
|
86
|
+
temporary_table.with_name(table.name)
|
87
|
+
.with_fields(rename_types_back(temporary_table.fields))
|
88
|
+
.with_indices(rename_indices_back(temporary_table.indices))
|
89
|
+
.with_foreign_keys(table.foreign_keys)
|
90
|
+
end
|
108
91
|
|
109
|
-
|
110
|
-
|
111
|
-
|
92
|
+
def rename_types(fields)
|
93
|
+
fields.map do |field|
|
94
|
+
if field.custom?
|
95
|
+
field.with_type(append_hash(field.type))
|
96
|
+
elsif field.array? && field.custom_element_type?
|
97
|
+
field.with_attribute(:element_type, append_hash(field.element_type.type).to_sym)
|
98
|
+
else
|
99
|
+
field
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
112
103
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
104
|
+
def rename_types_back(fields)
|
105
|
+
fields.map do |field|
|
106
|
+
if field.custom?
|
107
|
+
field.with_type(remove_hash(field.type))
|
108
|
+
elsif field.array? && field.custom_element_type?
|
109
|
+
field.with_attribute(:element_type, remove_hash(field.element_type.type).to_sym)
|
110
|
+
else
|
111
|
+
field
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rename_indices(indices)
|
117
|
+
indices.map do |index|
|
118
|
+
index.with_name(append_hash(index.name))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def rename_indices_back(indices)
|
123
|
+
indices.map do |index|
|
124
|
+
index.with_name(remove_hash(index.name))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def temporary_table_name
|
129
|
+
append_hash(table.name)
|
130
|
+
end
|
131
|
+
|
132
|
+
def append_hash(name)
|
133
|
+
"#{name}_#{hash}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def remove_hash(name)
|
137
|
+
name.to_s.sub(/_#{Regexp.escape(hash)}$/, '').to_sym
|
117
138
|
end
|
118
139
|
end
|
119
140
|
end
|
data/lib/db_schema/reader.rb
CHANGED
@@ -298,7 +298,7 @@ SELECT extname
|
|
298
298
|
Utils.rename_keys(
|
299
299
|
Utils.filter_by_keys(data, :element_type, :element_custom_type_name)
|
300
300
|
) do |attributes|
|
301
|
-
attributes[:
|
301
|
+
attributes[:element_type] = if attributes[:element_type] == 'USER-DEFINED'
|
302
302
|
attributes[:element_custom_type_name]
|
303
303
|
else
|
304
304
|
attributes[:element_type]
|
data/lib/db_schema/runner.rb
CHANGED
@@ -24,6 +24,8 @@ module DbSchema
|
|
24
24
|
self.class.create_enum(change)
|
25
25
|
when Changes::DropEnum
|
26
26
|
self.class.drop_enum(change)
|
27
|
+
when Changes::AlterEnumValues
|
28
|
+
self.class.alter_enum_values(change)
|
27
29
|
when Changes::CreateExtension
|
28
30
|
self.class.create_extension(change)
|
29
31
|
when Changes::DropExtension
|
@@ -31,11 +33,6 @@ module DbSchema
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
34
|
-
|
35
|
-
# Postgres doesn't allow modifying enums inside a transaction
|
36
|
-
Utils.filter_by_class(changes, Changes::AddValueToEnum).each do |change|
|
37
|
-
self.class.add_value_to_enum(change)
|
38
|
-
end
|
39
36
|
end
|
40
37
|
|
41
38
|
private
|
@@ -44,8 +41,8 @@ module DbSchema
|
|
44
41
|
changes,
|
45
42
|
[
|
46
43
|
Changes::CreateExtension,
|
47
|
-
Changes::AddValueToEnum,
|
48
44
|
Changes::DropForeignKey,
|
45
|
+
Changes::AlterEnumValues,
|
49
46
|
Changes::CreateEnum,
|
50
47
|
Changes::CreateTable,
|
51
48
|
Changes::AlterTable,
|
@@ -59,8 +56,8 @@ module DbSchema
|
|
59
56
|
|
60
57
|
class << self
|
61
58
|
def create_table(change)
|
62
|
-
DbSchema.connection.create_table(change.name) do
|
63
|
-
change.fields.each do |field|
|
59
|
+
DbSchema.connection.create_table(change.table.name) do
|
60
|
+
change.table.fields.each do |field|
|
64
61
|
if field.primary_key?
|
65
62
|
primary_key(field.name)
|
66
63
|
else
|
@@ -69,7 +66,7 @@ module DbSchema
|
|
69
66
|
end
|
70
67
|
end
|
71
68
|
|
72
|
-
change.indices.each do |index|
|
69
|
+
change.table.indices.each do |index|
|
73
70
|
index(
|
74
71
|
index.columns_to_sequel,
|
75
72
|
name: index.name,
|
@@ -79,7 +76,7 @@ module DbSchema
|
|
79
76
|
)
|
80
77
|
end
|
81
78
|
|
82
|
-
change.checks.each do |check|
|
79
|
+
change.table.checks.each do |check|
|
83
80
|
constraint(check.name, check.condition)
|
84
81
|
end
|
85
82
|
end
|
@@ -90,9 +87,9 @@ module DbSchema
|
|
90
87
|
end
|
91
88
|
|
92
89
|
def alter_table(change)
|
93
|
-
DbSchema.connection.alter_table(change.
|
90
|
+
DbSchema.connection.alter_table(change.table_name) do
|
94
91
|
Utils.sort_by_class(
|
95
|
-
change.
|
92
|
+
change.changes,
|
96
93
|
[
|
97
94
|
DbSchema::Changes::DropPrimaryKey,
|
98
95
|
DbSchema::Changes::DropCheckConstraint,
|
@@ -136,16 +133,16 @@ module DbSchema
|
|
136
133
|
set_column_default(element.name, Runner.default_to_sequel(element.new_default))
|
137
134
|
when Changes::CreateIndex
|
138
135
|
add_index(
|
139
|
-
element.columns_to_sequel,
|
140
|
-
name: element.name,
|
141
|
-
unique: element.unique?,
|
142
|
-
type: element.type,
|
143
|
-
where: element.condition
|
136
|
+
element.index.columns_to_sequel,
|
137
|
+
name: element.index.name,
|
138
|
+
unique: element.index.unique?,
|
139
|
+
type: element.index.type,
|
140
|
+
where: element.index.condition
|
144
141
|
)
|
145
142
|
when Changes::DropIndex
|
146
143
|
drop_index([], name: element.name)
|
147
144
|
when Changes::CreateCheckConstraint
|
148
|
-
add_constraint(element.name, element.condition)
|
145
|
+
add_constraint(element.check.name, element.check.condition)
|
149
146
|
when Changes::DropCheckConstraint
|
150
147
|
drop_constraint(element.name)
|
151
148
|
end
|
@@ -166,23 +163,45 @@ module DbSchema
|
|
166
163
|
end
|
167
164
|
|
168
165
|
def create_enum(change)
|
169
|
-
DbSchema.connection.create_enum(change.name, change.values)
|
166
|
+
DbSchema.connection.create_enum(change.enum.name, change.enum.values)
|
170
167
|
end
|
171
168
|
|
172
169
|
def drop_enum(change)
|
173
170
|
DbSchema.connection.drop_enum(change.name)
|
174
171
|
end
|
175
172
|
|
176
|
-
def
|
177
|
-
|
178
|
-
DbSchema.connection.
|
179
|
-
|
180
|
-
|
173
|
+
def alter_enum_values(change)
|
174
|
+
change.enum_fields.each do |field_data|
|
175
|
+
DbSchema.connection.alter_table(field_data[:table_name]) do
|
176
|
+
set_column_type(field_data[:field_name], :VARCHAR)
|
177
|
+
set_column_default(field_data[:field_name], nil)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
DbSchema.connection.drop_enum(change.enum_name)
|
182
|
+
DbSchema.connection.create_enum(change.enum_name, change.new_values)
|
183
|
+
|
184
|
+
change.enum_fields.each do |field_data|
|
185
|
+
DbSchema.connection.alter_table(field_data[:table_name]) do
|
186
|
+
field_type = if field_data[:array]
|
187
|
+
"#{change.enum_name}[]"
|
188
|
+
else
|
189
|
+
change.enum_name
|
190
|
+
end
|
191
|
+
|
192
|
+
set_column_type(
|
193
|
+
field_data[:field_name],
|
194
|
+
field_type,
|
195
|
+
using: "#{field_data[:field_name]}::#{field_type}"
|
196
|
+
)
|
197
|
+
|
198
|
+
set_column_default(field_data[:field_name], field_data[:new_default]) unless field_data[:new_default].nil?
|
199
|
+
end
|
181
200
|
end
|
182
201
|
end
|
183
202
|
|
184
203
|
def create_extension(change)
|
185
|
-
DbSchema.connection.run(%Q(CREATE EXTENSION "#{change.name}"))
|
204
|
+
DbSchema.connection.run(%Q(CREATE EXTENSION "#{change.extension.name}"))
|
186
205
|
end
|
187
206
|
|
188
207
|
def drop_extension(change)
|
data/lib/db_schema/validator.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'sequel/extensions/pg_array'
|
2
|
+
|
1
3
|
module DbSchema
|
2
4
|
module Validator
|
3
5
|
class << self
|
@@ -10,10 +12,29 @@ module DbSchema
|
|
10
12
|
end
|
11
13
|
|
12
14
|
table.fields.each do |field|
|
13
|
-
if field.
|
14
|
-
|
15
|
+
if field.custom?
|
16
|
+
type = schema.enums.find { |enum| enum.name == field.type }
|
17
|
+
|
18
|
+
if type.nil?
|
15
19
|
error_message = %(Field "#{table.name}.#{field.name}" has unknown type "#{field.type}")
|
16
20
|
errors << error_message
|
21
|
+
elsif !field.default.nil? && !type.values.include?(field.default.to_sym)
|
22
|
+
errors << %(Field "#{table.name}.#{field.name}" has invalid default value "#{field.default}" (valid values are #{type.values.map(&:to_s)}))
|
23
|
+
end
|
24
|
+
elsif field.array? && field.custom_element_type?
|
25
|
+
type = schema.enums.find { |enum| enum.name == field.element_type.type }
|
26
|
+
|
27
|
+
if type.nil?
|
28
|
+
error_message = %(Array field "#{table.name}.#{field.name}" has unknown element type "#{field.element_type.type}")
|
29
|
+
errors << error_message
|
30
|
+
elsif !field.default.nil?
|
31
|
+
default_array = Sequel::Postgres::PGArray::Parser.new(field.default).parse.map(&:to_sym)
|
32
|
+
invalid_values = default_array - type.values
|
33
|
+
|
34
|
+
if invalid_values.any?
|
35
|
+
error_message = %(Array field "#{table.name}.#{field.name}" has invalid default value #{default_array.map(&:to_s)} (valid values are #{type.values.map(&:to_s)}))
|
36
|
+
errors << error_message
|
37
|
+
end
|
17
38
|
end
|
18
39
|
end
|
19
40
|
end
|
data/lib/db_schema/version.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vsevolod Romashov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -240,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
240
240
|
version: '0'
|
241
241
|
requirements: []
|
242
242
|
rubyforge_project:
|
243
|
-
rubygems_version: 2.5.
|
243
|
+
rubygems_version: 2.5.2
|
244
244
|
signing_key:
|
245
245
|
specification_version: 4
|
246
246
|
summary: Declarative database schema definition.
|