db_schema 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/7even/db_schema.svg?branch=master)](https://travis-ci.org/7even/db_schema) [![Gem Version](https://badge.fury.io/rb/db_schema.svg)](https://badge.fury.io/rb/db_schema)
|
1
|
+
# DbSchema [![Build Status](https://travis-ci.org/7even/db_schema.svg?branch=master)](https://travis-ci.org/7even/db_schema) [![Gem Version](https://badge.fury.io/rb/db_schema.svg)](https://badge.fury.io/rb/db_schema) [![Join the chat at https://gitter.im/7even/db_schema](https://badges.gitter.im/7even/db_schema.svg)](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.
|