db_schema 0.3 → 0.3.1
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/.rspec +1 -0
- data/README.md +8 -8
- data/db_schema.gemspec +1 -1
- data/lib/db_schema/awesome_print.rb +2 -2
- data/lib/db_schema/changes.rb +155 -141
- data/lib/db_schema/definitions/check_constraint.rb +4 -0
- data/lib/db_schema/definitions/field/base.rb +4 -0
- data/lib/db_schema/definitions/index.rb +10 -0
- data/lib/db_schema/definitions/table.rb +24 -14
- data/lib/db_schema/dsl.rb +4 -4
- data/lib/db_schema/migrator.rb +1 -1
- data/lib/db_schema/normalizer.rb +67 -14
- data/lib/db_schema/reader.rb +11 -11
- data/lib/db_schema/runner.rb +1 -1
- data/lib/db_schema/utils.rb +6 -0
- data/lib/db_schema/validator.rb +1 -1
- 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: 92f4601dd7a4513014fd782391bfe69f8400f06d
|
4
|
+
data.tar.gz: 291caae43bf4582dbf0636b8601de86ebdcf3c25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a8af71a493a89113166e8a1ee0fa1808ba8bbbcbb07b263fa0d9f0884eafe4d297e64d79815a854f460ad78af3bd0c847c82741c96d5175e127452a1333120d
|
7
|
+
data.tar.gz: 41fb922d5f307c481f3595d27983c39a13640cd2c5cdc5e9e6a395ad34c509cf712f4d08f79825c730462855c9d66d178961bb1d48d77b599386e10ab90b831a
|
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# DbSchema [](https://travis-ci.org/db-schema/core) [](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.3.
|
56
|
+
gem 'db_schema', '~> 0.3.1'
|
57
57
|
```
|
58
58
|
|
59
59
|
And then execute:
|
@@ -83,7 +83,7 @@ DbSchema.configure(
|
|
83
83
|
```
|
84
84
|
|
85
85
|
There is also a Rails' `database.yml`-compatible `configure_from_yaml` method. DbSchema configuration
|
86
|
-
is discussed in detail [here](https://github.com/
|
86
|
+
is discussed in detail [here](https://github.com/db-schema/core/wiki/Configuration).
|
87
87
|
|
88
88
|
After DbSchema is configured you can load your schema definition file:
|
89
89
|
|
@@ -113,7 +113,7 @@ DbSchema.describe do |db|
|
|
113
113
|
end
|
114
114
|
```
|
115
115
|
|
116
|
-
Database schema definition DSL is documented [here](https://github.com/
|
116
|
+
Database schema definition DSL is documented [here](https://github.com/db-schema/core/wiki/Schema-definition-DSL).
|
117
117
|
|
118
118
|
If you want to analyze your database structure in any way from your app (e.g. defining methods
|
119
119
|
with `#define_method` for each enum value) you can use `DbSchema.current_schema` - it returns
|
@@ -121,7 +121,7 @@ a cached copy of the database structure as a `DbSchema::Definitions::Schema` obj
|
|
121
121
|
can query in different ways. It is available after the schema was applied by DbSchema
|
122
122
|
(`DbSchema.describe` remembers the current schema of the database and exposes it
|
123
123
|
at `.current_schema`). Documentation for schema analysis DSL can be found
|
124
|
-
[here](https://github.com/
|
124
|
+
[here](https://github.com/db-schema/core/wiki/Schema-analysis-DSL).
|
125
125
|
|
126
126
|
### Production setup
|
127
127
|
|
@@ -217,12 +217,12 @@ instead you have to provide some conditions required to run the migration (the g
|
|
217
217
|
conditions that a) will only trigger if the migration wasn't applied yet and b) are necessary for the
|
218
218
|
migration to work) - like "rename the `users`
|
219
219
|
table to `people` only if the database has a `users` table" (DbSchema also provides
|
220
|
-
a [simple DSL](https://github.com/
|
220
|
+
a [simple DSL](https://github.com/db-schema/core/wiki/Schema-analysis-DSL) for schema analysis).
|
221
221
|
This way the migration won't be applied again and the whole DbSchema process stays idempotent.
|
222
222
|
Also you don't have to keep these migrations forever - once a migration is applied to databases
|
223
223
|
in all environments you can safely delete it (though you can give your teammates a week or two to keep up).
|
224
224
|
|
225
|
-
Conditional migrations are described [here](https://github.com/
|
225
|
+
Conditional migrations are described [here](https://github.com/db-schema/core/wiki/Conditional-Migrations).
|
226
226
|
|
227
227
|
## Known problems and limitations
|
228
228
|
|
@@ -237,7 +237,7 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
237
237
|
|
238
238
|
## Contributing
|
239
239
|
|
240
|
-
Bug reports and pull requests are welcome on GitHub at [
|
240
|
+
Bug reports and pull requests are welcome on GitHub at [db-schema/core](https://github.com/db-schema/core).
|
241
241
|
|
242
242
|
## License
|
243
243
|
|
data/db_schema.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = 'Declarative database schema definition.'
|
12
12
|
spec.description = 'A database schema management tool that reads a "single-source-of-truth" schema definition from a ruby file and auto-migrates the database to conform to it.'
|
13
|
-
spec.homepage = 'https://github.com/
|
13
|
+
spec.homepage = 'https://github.com/db-schema/core'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r(^spec/)) }
|
@@ -87,7 +87,7 @@ if defined?(AwesomePrint)
|
|
87
87
|
|
88
88
|
def awesome_dbschema_table(object)
|
89
89
|
data = ["fields: #{object.fields.ai}"]
|
90
|
-
data << "
|
90
|
+
data << "indexes: #{object.indexes.ai}" if object.indexes.any?
|
91
91
|
data << "checks: #{object.checks.ai}" if object.checks.any?
|
92
92
|
data << "foreign_keys: #{object.foreign_keys.ai}" if object.foreign_keys.any?
|
93
93
|
|
@@ -190,7 +190,7 @@ if defined?(AwesomePrint)
|
|
190
190
|
|
191
191
|
def awesome_dbschema_create_table(object)
|
192
192
|
data = ["fields: #{object.table.fields.ai}"]
|
193
|
-
data << "
|
193
|
+
data << "indexes: #{object.table.indexes.ai}" if object.table.indexes.any?
|
194
194
|
data << "checks: #{object.table.checks.ai}" if object.table.checks.any?
|
195
195
|
|
196
196
|
data_string = indent_lines(data.join(', '))
|
data/lib/db_schema/changes.rb
CHANGED
@@ -5,59 +5,155 @@ module DbSchema
|
|
5
5
|
module Changes
|
6
6
|
class << self
|
7
7
|
def between(desired_schema, actual_schema)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
sort_all_changes(
|
9
|
+
[
|
10
|
+
table_changes(desired_schema, actual_schema),
|
11
|
+
enum_changes(desired_schema, actual_schema),
|
12
|
+
extension_changes(desired_schema, actual_schema)
|
13
|
+
].reduce(:+)
|
14
|
+
)
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
private
|
18
|
+
def table_changes(desired_schema, actual_schema)
|
19
|
+
compare_collections(
|
20
|
+
desired_schema.tables,
|
21
|
+
actual_schema.tables,
|
22
|
+
create: -> (table) do
|
23
|
+
fkey_operations = table.foreign_keys.map do |fkey|
|
24
|
+
Operations::CreateForeignKey.new(table.name, fkey)
|
19
25
|
end
|
20
|
-
changes.concat(fkey_operations)
|
21
|
-
elsif actual && !desired
|
22
|
-
changes << Operations::DropTable.new(table_name)
|
23
26
|
|
24
|
-
|
25
|
-
|
27
|
+
[Operations::CreateTable.new(table), *fkey_operations]
|
28
|
+
end,
|
29
|
+
drop: -> (table) do
|
30
|
+
fkey_operations = table.foreign_keys.map do |fkey|
|
31
|
+
Operations::DropForeignKey.new(table.name, fkey.name)
|
26
32
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
fkey_operations
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
|
34
|
+
[Operations::DropTable.new(table.name), *fkey_operations]
|
35
|
+
end,
|
36
|
+
change: -> (desired, actual) do
|
37
|
+
fkey_operations = foreign_key_changes(desired, actual)
|
38
|
+
|
39
|
+
alter_table_operations = [
|
40
|
+
field_changes(desired, actual),
|
41
|
+
index_changes(desired, actual),
|
42
|
+
check_changes(desired, actual)
|
43
|
+
].reduce(:+)
|
44
|
+
|
45
|
+
if alter_table_operations.any?
|
46
|
+
alter_table = Operations::AlterTable.new(
|
47
|
+
desired.name,
|
48
|
+
sort_alter_table_changes(alter_table_operations)
|
37
49
|
)
|
50
|
+
|
51
|
+
[alter_table, *fkey_operations]
|
52
|
+
else
|
53
|
+
fkey_operations
|
38
54
|
end
|
55
|
+
end
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def field_changes(desired_table, actual_table)
|
60
|
+
compare_collections(
|
61
|
+
desired_table.fields,
|
62
|
+
actual_table.fields,
|
63
|
+
create: -> (field) { Operations::CreateColumn.new(field) },
|
64
|
+
drop: -> (field) { Operations::DropColumn.new(field.name) },
|
65
|
+
change: -> (desired, actual) do
|
66
|
+
[].tap do |operations|
|
67
|
+
if (actual.type != desired.type) || (actual.attributes != desired.attributes)
|
68
|
+
operations << Operations::AlterColumnType.new(
|
69
|
+
actual.name,
|
70
|
+
new_type: desired.type,
|
71
|
+
**desired.attributes
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
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
|
39
82
|
|
40
|
-
|
83
|
+
if desired.null? && !actual.null?
|
84
|
+
operations << Operations::AllowNull.new(actual.name)
|
85
|
+
end
|
86
|
+
|
87
|
+
if actual.null? && !desired.null?
|
88
|
+
operations << Operations::DisallowNull.new(actual.name)
|
89
|
+
end
|
90
|
+
|
91
|
+
if actual.default != desired.default
|
92
|
+
operations << Operations::AlterColumnDefault.new(actual.name, new_default: desired.default)
|
93
|
+
end
|
94
|
+
end
|
41
95
|
end
|
42
|
-
|
96
|
+
)
|
97
|
+
end
|
43
98
|
|
44
|
-
|
99
|
+
def index_changes(desired_table, actual_table)
|
100
|
+
compare_collections(
|
101
|
+
desired_table.indexes,
|
102
|
+
actual_table.indexes,
|
103
|
+
create: -> (index) { Operations::CreateIndex.new(index) },
|
104
|
+
drop: -> (index) { Operations::DropIndex.new(index.name) },
|
105
|
+
change: -> (desired, actual) do
|
106
|
+
[
|
107
|
+
Operations::DropIndex.new(actual.name),
|
108
|
+
Operations::CreateIndex.new(desired)
|
109
|
+
]
|
110
|
+
end
|
111
|
+
)
|
112
|
+
end
|
45
113
|
|
46
|
-
|
47
|
-
|
48
|
-
|
114
|
+
def check_changes(desired_table, actual_table)
|
115
|
+
compare_collections(
|
116
|
+
desired_table.checks,
|
117
|
+
actual_table.checks,
|
118
|
+
create: -> (check) { Operations::CreateCheckConstraint.new(check) },
|
119
|
+
drop: -> (check) { Operations::DropCheckConstraint.new(check.name) },
|
120
|
+
change: -> (desired, actual) do
|
121
|
+
[
|
122
|
+
Operations::DropCheckConstraint.new(actual.name),
|
123
|
+
Operations::CreateCheckConstraint.new(desired)
|
124
|
+
]
|
125
|
+
end
|
126
|
+
)
|
127
|
+
end
|
49
128
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
129
|
+
def foreign_key_changes(desired_table, actual_table)
|
130
|
+
compare_collections(
|
131
|
+
desired_table.foreign_keys,
|
132
|
+
actual_table.foreign_keys,
|
133
|
+
create: -> (foreign_key) { Operations::CreateForeignKey.new(actual_table.name, foreign_key) },
|
134
|
+
drop: -> (foreign_key) { Operations::DropForeignKey.new(actual_table.name, foreign_key.name) },
|
135
|
+
change: -> (desired, actual) do
|
136
|
+
[
|
137
|
+
Operations::DropForeignKey.new(actual_table.name, actual.name),
|
138
|
+
Operations::CreateForeignKey.new(actual_table.name, desired)
|
139
|
+
]
|
140
|
+
end
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def enum_changes(desired_schema, actual_schema)
|
145
|
+
compare_collections(
|
146
|
+
desired_schema.enums,
|
147
|
+
actual_schema.enums,
|
148
|
+
create: -> (enum) { Operations::CreateEnum.new(enum) },
|
149
|
+
drop: -> (enum) { Operations::DropEnum.new(enum.name) },
|
150
|
+
change: -> (desired, actual) do
|
55
151
|
fields = actual_schema.tables.flat_map do |table|
|
56
152
|
table.fields.select do |field|
|
57
153
|
if field.array?
|
58
|
-
field.attributes[:element_type] ==
|
154
|
+
field.attributes[:element_type] == actual.name
|
59
155
|
else
|
60
|
-
field.type ==
|
156
|
+
field.type == actual.name
|
61
157
|
end
|
62
158
|
end.map do |field|
|
63
159
|
if desired_field = desired_schema[table.name][field.name]
|
@@ -73,115 +169,33 @@ module DbSchema
|
|
73
169
|
end
|
74
170
|
end
|
75
171
|
|
76
|
-
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
extension_changes = (desired_schema.extensions - actual_schema.extensions).map do |extension|
|
81
|
-
Operations::CreateExtension.new(extension)
|
82
|
-
end + (actual_schema.extensions - desired_schema.extensions).map do |extension|
|
83
|
-
Operations::DropExtension.new(extension.name)
|
84
|
-
end
|
85
|
-
|
86
|
-
sort_all_changes(table_changes + enum_changes + extension_changes)
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
def field_changes(desired_fields, actual_fields)
|
91
|
-
field_names = [desired_fields, actual_fields].flatten.map(&:name).uniq
|
92
|
-
|
93
|
-
field_names.each.with_object([]) do |field_name, table_changes|
|
94
|
-
desired = desired_fields.find { |field| field.name == field_name }
|
95
|
-
actual = actual_fields.find { |field| field.name == field_name }
|
96
|
-
|
97
|
-
if desired && !actual
|
98
|
-
table_changes << Operations::CreateColumn.new(desired)
|
99
|
-
elsif actual && !desired
|
100
|
-
table_changes << Operations::DropColumn.new(field_name)
|
101
|
-
elsif actual != desired
|
102
|
-
if (actual.type != desired.type) || (actual.attributes != desired.attributes)
|
103
|
-
table_changes << Operations::AlterColumnType.new(
|
104
|
-
field_name,
|
105
|
-
new_type: desired.type,
|
106
|
-
**desired.attributes
|
107
|
-
)
|
108
|
-
end
|
109
|
-
|
110
|
-
if desired.primary_key? && !actual.primary_key?
|
111
|
-
table_changes << Operations::CreatePrimaryKey.new(field_name)
|
112
|
-
end
|
113
|
-
|
114
|
-
if actual.primary_key? && !desired.primary_key?
|
115
|
-
table_changes << Operations::DropPrimaryKey.new(field_name)
|
116
|
-
end
|
117
|
-
|
118
|
-
if desired.null? && !actual.null?
|
119
|
-
table_changes << Operations::AllowNull.new(field_name)
|
120
|
-
end
|
121
|
-
|
122
|
-
if actual.null? && !desired.null?
|
123
|
-
table_changes << Operations::DisallowNull.new(field_name)
|
124
|
-
end
|
125
|
-
|
126
|
-
if actual.default != desired.default
|
127
|
-
table_changes << Operations::AlterColumnDefault.new(field_name, new_default: desired.default)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def index_changes(desired_indices, actual_indices)
|
134
|
-
index_names = [desired_indices, actual_indices].flatten.map(&:name).uniq
|
135
|
-
|
136
|
-
index_names.each.with_object([]) do |index_name, table_changes|
|
137
|
-
desired = desired_indices.find { |index| index.name == index_name }
|
138
|
-
actual = actual_indices.find { |index| index.name == index_name }
|
139
|
-
|
140
|
-
if desired && !actual
|
141
|
-
table_changes << Operations::CreateIndex.new(desired)
|
142
|
-
elsif actual && !desired
|
143
|
-
table_changes << Operations::DropIndex.new(index_name)
|
144
|
-
elsif actual != desired
|
145
|
-
table_changes << Operations::DropIndex.new(index_name)
|
146
|
-
table_changes << Operations::CreateIndex.new(desired)
|
172
|
+
Operations::AlterEnumValues.new(actual.name, desired.values, fields)
|
147
173
|
end
|
148
|
-
|
174
|
+
)
|
149
175
|
end
|
150
176
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
if desired && !actual
|
159
|
-
table_changes << Operations::CreateCheckConstraint.new(desired)
|
160
|
-
elsif actual && !desired
|
161
|
-
table_changes << Operations::DropCheckConstraint.new(check_name)
|
162
|
-
elsif actual != desired
|
163
|
-
table_changes << Operations::DropCheckConstraint.new(check_name)
|
164
|
-
table_changes << Operations::CreateCheckConstraint.new(desired)
|
165
|
-
end
|
166
|
-
end
|
177
|
+
def extension_changes(desired_schema, actual_schema)
|
178
|
+
compare_collections(
|
179
|
+
desired_schema.extensions,
|
180
|
+
actual_schema.extensions,
|
181
|
+
create: -> (extension) { Operations::CreateExtension.new(extension) },
|
182
|
+
drop: -> (extension) { Operations::DropExtension.new(extension.name) }
|
183
|
+
)
|
167
184
|
end
|
168
185
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
elsif
|
179
|
-
|
180
|
-
elsif actual != desired
|
181
|
-
table_changes << Operations::DropForeignKey.new(table_name, key_name)
|
182
|
-
table_changes << Operations::CreateForeignKey.new(table_name, desired)
|
186
|
+
def compare_collections(desired, actual, create:, drop:, change: -> (*) {})
|
187
|
+
desired_hash = Utils.to_hash(desired, :name)
|
188
|
+
actual_hash = Utils.to_hash(actual, :name)
|
189
|
+
|
190
|
+
(desired_hash.keys + actual_hash.keys).uniq.flat_map do |name|
|
191
|
+
if desired_hash.key?(name) && !actual_hash.key?(name)
|
192
|
+
create.(desired_hash[name])
|
193
|
+
elsif actual_hash.key?(name) && !desired_hash.key?(name)
|
194
|
+
drop.(actual_hash[name])
|
195
|
+
elsif actual_hash[name] != desired_hash[name]
|
196
|
+
change.(desired_hash[name], actual_hash[name])
|
183
197
|
end
|
184
|
-
end
|
198
|
+
end.compact
|
185
199
|
end
|
186
200
|
|
187
201
|
def sort_all_changes(changes)
|
@@ -64,6 +64,10 @@ module DbSchema
|
|
64
64
|
Field.build(name, type, **options, primary_key: primary_key?, attr_name => attr_value)
|
65
65
|
end
|
66
66
|
|
67
|
+
def with_default(new_default)
|
68
|
+
Field.build(name, type, **options, primary_key: primary_key?, default: new_default)
|
69
|
+
end
|
70
|
+
|
67
71
|
class << self
|
68
72
|
def register(*types)
|
69
73
|
types.each do |type|
|
@@ -41,6 +41,16 @@ module DbSchema
|
|
41
41
|
condition: condition
|
42
42
|
)
|
43
43
|
end
|
44
|
+
|
45
|
+
def with_condition(new_condition)
|
46
|
+
Index.new(
|
47
|
+
name: name,
|
48
|
+
columns: columns,
|
49
|
+
unique: unique?,
|
50
|
+
type: type,
|
51
|
+
condition: new_condition
|
52
|
+
)
|
53
|
+
end
|
44
54
|
end
|
45
55
|
|
46
56
|
class NullIndex < Index
|
@@ -1,20 +1,20 @@
|
|
1
1
|
module DbSchema
|
2
2
|
module Definitions
|
3
3
|
class Table
|
4
|
-
include Dry::Equalizer(:name, :fields, :
|
5
|
-
attr_reader :name, :fields, :
|
4
|
+
include Dry::Equalizer(:name, :fields, :indexes, :checks, :foreign_keys)
|
5
|
+
attr_reader :name, :fields, :indexes, :checks, :foreign_keys
|
6
6
|
|
7
|
-
def initialize(name, fields: [],
|
7
|
+
def initialize(name, fields: [], indexes: [], checks: [], foreign_keys: [])
|
8
8
|
@name = name.to_sym
|
9
9
|
@fields = fields
|
10
|
-
@
|
10
|
+
@indexes = indexes
|
11
11
|
@checks = checks
|
12
12
|
@foreign_keys = foreign_keys
|
13
13
|
end
|
14
14
|
|
15
15
|
def has_expressions?
|
16
16
|
fields.any?(&:default_is_expression?) ||
|
17
|
-
|
17
|
+
indexes.any?(&:has_expressions?) ||
|
18
18
|
checks.any?
|
19
19
|
end
|
20
20
|
|
@@ -28,7 +28,7 @@ module DbSchema
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def index(index_name)
|
31
|
-
|
31
|
+
indexes.find { |index| index.name == index_name } || NullIndex.new
|
32
32
|
end
|
33
33
|
|
34
34
|
def has_index?(index_name)
|
@@ -36,13 +36,13 @@ module DbSchema
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def has_index_on?(*field_names)
|
39
|
-
|
39
|
+
indexes.any? do |index|
|
40
40
|
index.columns.none?(&:expression?) && index.columns.map(&:name) == field_names
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def has_unique_index_on?(*field_names)
|
45
|
-
|
45
|
+
indexes.any? do |index|
|
46
46
|
index.unique? && index.columns.none?(&:expression?) && index.columns.map(&:name) == field_names
|
47
47
|
end
|
48
48
|
end
|
@@ -71,7 +71,7 @@ module DbSchema
|
|
71
71
|
Table.new(
|
72
72
|
new_name,
|
73
73
|
fields: fields,
|
74
|
-
|
74
|
+
indexes: indexes,
|
75
75
|
checks: checks,
|
76
76
|
foreign_keys: foreign_keys
|
77
77
|
)
|
@@ -81,27 +81,37 @@ module DbSchema
|
|
81
81
|
Table.new(
|
82
82
|
name,
|
83
83
|
fields: new_fields,
|
84
|
-
|
84
|
+
indexes: indexes,
|
85
85
|
checks: checks,
|
86
86
|
foreign_keys: foreign_keys
|
87
87
|
)
|
88
88
|
end
|
89
89
|
|
90
|
-
def
|
90
|
+
def with_indexes(new_indexes)
|
91
91
|
Table.new(
|
92
92
|
name,
|
93
93
|
fields: fields,
|
94
|
-
|
94
|
+
indexes: new_indexes,
|
95
95
|
checks: checks,
|
96
96
|
foreign_keys: foreign_keys
|
97
97
|
)
|
98
98
|
end
|
99
99
|
|
100
|
+
def with_checks(new_checks)
|
101
|
+
Table.new(
|
102
|
+
name,
|
103
|
+
fields: fields,
|
104
|
+
indexes: indexes,
|
105
|
+
checks: new_checks,
|
106
|
+
foreign_keys: foreign_keys
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
100
110
|
def with_foreign_keys(new_foreign_keys)
|
101
111
|
Table.new(
|
102
112
|
name,
|
103
113
|
fields: fields,
|
104
|
-
|
114
|
+
indexes: indexes,
|
105
115
|
checks: checks,
|
106
116
|
foreign_keys: new_foreign_keys
|
107
117
|
)
|
@@ -111,7 +121,7 @@ module DbSchema
|
|
111
121
|
class NullTable < Table
|
112
122
|
def initialize
|
113
123
|
@fields = []
|
114
|
-
@
|
124
|
+
@indexes = []
|
115
125
|
@checks = []
|
116
126
|
@foreign_keys = []
|
117
127
|
end
|
data/lib/db_schema/dsl.rb
CHANGED
@@ -17,7 +17,7 @@ module DbSchema
|
|
17
17
|
@schema.tables << Definitions::Table.new(
|
18
18
|
name,
|
19
19
|
fields: table_yielder.fields,
|
20
|
-
|
20
|
+
indexes: table_yielder.indexes,
|
21
21
|
checks: table_yielder.checks,
|
22
22
|
foreign_keys: table_yielder.foreign_keys
|
23
23
|
)
|
@@ -64,7 +64,7 @@ module DbSchema
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def index(*columns, **index_options)
|
67
|
-
|
67
|
+
indexes << TableYielder.build_index(
|
68
68
|
columns,
|
69
69
|
table_name: table_name,
|
70
70
|
**index_options
|
@@ -105,8 +105,8 @@ module DbSchema
|
|
105
105
|
@fields ||= []
|
106
106
|
end
|
107
107
|
|
108
|
-
def
|
109
|
-
@
|
108
|
+
def indexes
|
109
|
+
@indexes ||= []
|
110
110
|
end
|
111
111
|
|
112
112
|
def checks
|
data/lib/db_schema/migrator.rb
CHANGED
data/lib/db_schema/normalizer.rb
CHANGED
@@ -16,7 +16,7 @@ module DbSchema
|
|
16
16
|
|
17
17
|
schema.tables = schema.tables.map do |table|
|
18
18
|
if table.has_expressions?
|
19
|
-
Table.new(table, hash, connection).normalized_table
|
19
|
+
Table.new(table, hash, schema.enums.map(&:name), connection).normalized_table
|
20
20
|
else
|
21
21
|
table
|
22
22
|
end
|
@@ -50,7 +50,7 @@ module DbSchema
|
|
50
50
|
def hash
|
51
51
|
@hash ||= begin
|
52
52
|
names = schema.tables.flat_map do |table|
|
53
|
-
[table.name] + table.fields.map(&:name) + table.
|
53
|
+
[table.name] + table.fields.map(&:name) + table.indexes.map(&:name) + table.checks.map(&:name)
|
54
54
|
end
|
55
55
|
|
56
56
|
Digest::MD5.hexdigest(names.join(','))[0..9]
|
@@ -58,11 +58,12 @@ module DbSchema
|
|
58
58
|
end
|
59
59
|
|
60
60
|
class Table
|
61
|
-
attr_reader :table, :hash, :connection
|
61
|
+
attr_reader :table, :hash, :enum_names, :connection
|
62
62
|
|
63
|
-
def initialize(table, hash, connection)
|
63
|
+
def initialize(table, hash, enum_names, connection)
|
64
64
|
@table = table
|
65
65
|
@hash = hash
|
66
|
+
@enum_names = enum_names
|
66
67
|
@connection = connection
|
67
68
|
end
|
68
69
|
|
@@ -76,7 +77,8 @@ module DbSchema
|
|
76
77
|
operation = Operations::CreateTable.new(
|
77
78
|
table.with_name(temporary_table_name)
|
78
79
|
.with_fields(rename_types(table.fields))
|
79
|
-
.
|
80
|
+
.with_indexes(rename_indexes(table.indexes))
|
81
|
+
.with_checks(rename_types_in_checks(table.checks))
|
80
82
|
)
|
81
83
|
|
82
84
|
Runner.new([operation], connection).run!
|
@@ -87,43 +89,72 @@ module DbSchema
|
|
87
89
|
|
88
90
|
temporary_table.with_name(table.name)
|
89
91
|
.with_fields(rename_types_back(temporary_table.fields))
|
90
|
-
.
|
92
|
+
.with_indexes(rename_indexes_back(temporary_table.indexes))
|
93
|
+
.with_checks(rename_types_in_checks_back(temporary_table.checks))
|
91
94
|
.with_foreign_keys(table.foreign_keys)
|
92
95
|
end
|
93
96
|
|
94
97
|
def rename_types(fields)
|
95
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
|
104
|
+
|
96
105
|
if field.custom?
|
97
106
|
field.with_type(append_hash(field.type))
|
98
107
|
elsif field.array? && field.custom_element_type?
|
99
108
|
field.with_attribute(:element_type, append_hash(field.element_type.type).to_sym)
|
100
109
|
else
|
101
110
|
field
|
102
|
-
end
|
111
|
+
end.with_default(new_default)
|
103
112
|
end
|
104
113
|
end
|
105
114
|
|
106
115
|
def rename_types_back(fields)
|
107
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
|
+
|
108
123
|
if field.custom?
|
109
124
|
field.with_type(remove_hash(field.type))
|
110
125
|
elsif field.array? && field.custom_element_type?
|
111
126
|
field.with_attribute(:element_type, remove_hash(field.element_type.type).to_sym)
|
112
127
|
else
|
113
128
|
field
|
114
|
-
end
|
129
|
+
end.with_default(new_default)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
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))
|
115
138
|
end
|
116
139
|
end
|
117
140
|
|
118
|
-
def
|
119
|
-
|
120
|
-
index
|
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))
|
121
146
|
end
|
122
147
|
end
|
123
148
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
149
|
+
def rename_types_in_checks(checks)
|
150
|
+
checks.map do |check|
|
151
|
+
check.with_condition(rename_all_types_in(check.condition))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def rename_types_in_checks_back(checks)
|
156
|
+
checks.map do |check|
|
157
|
+
check.with_condition(rename_all_types_back_in(check.condition))
|
127
158
|
end
|
128
159
|
end
|
129
160
|
|
@@ -138,6 +169,28 @@ module DbSchema
|
|
138
169
|
def remove_hash(name)
|
139
170
|
name.to_s.sub(/_#{Regexp.escape(hash)}$/, '').to_sym
|
140
171
|
end
|
172
|
+
|
173
|
+
def rename_all_types_in(string)
|
174
|
+
return string unless string.is_a?(String)
|
175
|
+
|
176
|
+
enum_renaming.reduce(string) do |new_string, (from, to)|
|
177
|
+
new_string.gsub(from, to)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def rename_all_types_back_in(string)
|
182
|
+
return string unless string.is_a?(String)
|
183
|
+
|
184
|
+
enum_renaming.invert.reduce(string) do |new_string, (from, to)|
|
185
|
+
new_string.gsub(from, to)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def enum_renaming
|
190
|
+
enum_names.reduce({}) do |hash, enum_name|
|
191
|
+
hash.merge("::#{enum_name}" => "::#{append_hash(enum_name)}")
|
192
|
+
end
|
193
|
+
end
|
141
194
|
end
|
142
195
|
end
|
143
196
|
end
|
data/lib/db_schema/reader.rb
CHANGED
@@ -78,7 +78,7 @@ SELECT conname AS name,
|
|
78
78
|
AND contype = 'c'
|
79
79
|
SQL
|
80
80
|
|
81
|
-
|
81
|
+
INDEXES_QUERY = <<-SQL.freeze
|
82
82
|
SELECT relname AS name,
|
83
83
|
indkey AS column_positions,
|
84
84
|
indisunique AS unique,
|
@@ -102,7 +102,7 @@ LEFT JOIN pg_am
|
|
102
102
|
GROUP BY name, column_positions, indisunique, index_options, condition, index_type, index_oid
|
103
103
|
SQL
|
104
104
|
|
105
|
-
|
105
|
+
EXPRESSION_INDEXES_QUERY = <<-SQL.freeze
|
106
106
|
WITH index_ids AS (SELECT unnest(?) AS index_id),
|
107
107
|
elements AS (SELECT unnest(?) AS element)
|
108
108
|
SELECT index_id,
|
@@ -142,7 +142,7 @@ SELECT extname
|
|
142
142
|
build_field(column_data, primary_key: column_data[:name] == primary_key_name)
|
143
143
|
end
|
144
144
|
|
145
|
-
|
145
|
+
indexes = indexes_data_for(table_name, connection).map do |index_data|
|
146
146
|
Definitions::Index.new(index_data)
|
147
147
|
end.sort_by(&:name)
|
148
148
|
|
@@ -160,21 +160,21 @@ SELECT extname
|
|
160
160
|
Definitions::Table.new(
|
161
161
|
table_name,
|
162
162
|
fields: fields,
|
163
|
-
|
163
|
+
indexes: indexes,
|
164
164
|
checks: checks,
|
165
165
|
foreign_keys: foreign_keys
|
166
166
|
)
|
167
167
|
end
|
168
168
|
|
169
|
-
def
|
169
|
+
def indexes_data_for(table_name, connection)
|
170
170
|
column_names = connection[COLUMN_NAMES_QUERY, table_name.to_s].reduce({}) do |names, column|
|
171
171
|
names.merge(column[:pos] => column[:name].to_sym)
|
172
172
|
end
|
173
173
|
|
174
|
-
|
175
|
-
expressions_data = index_expressions_data(
|
174
|
+
indexes_data = connection[INDEXES_QUERY, table_name.to_s].to_a
|
175
|
+
expressions_data = index_expressions_data(indexes_data, connection)
|
176
176
|
|
177
|
-
|
177
|
+
indexes_data.map do |index|
|
178
178
|
positions = index[:column_positions].split(' ').map(&:to_i)
|
179
179
|
options = index[:index_options].split(' ').map(&:to_i)
|
180
180
|
|
@@ -227,10 +227,10 @@ SELECT extname
|
|
227
227
|
end
|
228
228
|
|
229
229
|
private
|
230
|
-
def index_expressions_data(
|
230
|
+
def index_expressions_data(indexes_data, connection)
|
231
231
|
all_positions, max_position = {}, 0
|
232
232
|
|
233
|
-
|
233
|
+
indexes_data.each do |index_data|
|
234
234
|
positions = index_data[:column_positions].split(' ').map(&:to_i)
|
235
235
|
expression_positions = positions.each_index.select { |i| positions[i].zero? }
|
236
236
|
|
@@ -242,7 +242,7 @@ SELECT extname
|
|
242
242
|
|
243
243
|
if all_positions.any?
|
244
244
|
connection[
|
245
|
-
|
245
|
+
EXPRESSION_INDEXES_QUERY,
|
246
246
|
Sequel.pg_array(all_positions.keys),
|
247
247
|
Sequel.pg_array((1..max_position.succ).to_a)
|
248
248
|
].each_with_object({}) do |index_data, indexes_data|
|
data/lib/db_schema/runner.rb
CHANGED
data/lib/db_schema/utils.rb
CHANGED
@@ -44,6 +44,12 @@ module DbSchema
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
def to_hash(array, attribute)
|
48
|
+
array.reduce({}) do |hash, object|
|
49
|
+
hash.merge(object.public_send(attribute) => object)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
47
53
|
def sort_by_class(array, sorted_classes)
|
48
54
|
sorted_classes.flat_map do |klass|
|
49
55
|
array.select { |object| object.is_a?(klass) }
|
data/lib/db_schema/validator.rb
CHANGED
@@ -41,7 +41,7 @@ module DbSchema
|
|
41
41
|
|
42
42
|
field_names = table.fields.map(&:name)
|
43
43
|
|
44
|
-
table.
|
44
|
+
table.indexes.each do |index|
|
45
45
|
index.columns.reject(&:expression?).map(&:name).each do |field_name|
|
46
46
|
unless field_names.include?(field_name)
|
47
47
|
error_message = %(Index "#{index.name}" refers to a missing field "#{table.name}.#{field_name}")
|
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:
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vsevolod Romashov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -234,7 +234,7 @@ files:
|
|
234
234
|
- lib/db_schema/utils.rb
|
235
235
|
- lib/db_schema/validator.rb
|
236
236
|
- lib/db_schema/version.rb
|
237
|
-
homepage: https://github.com/
|
237
|
+
homepage: https://github.com/db-schema/core
|
238
238
|
licenses:
|
239
239
|
- MIT
|
240
240
|
metadata: {}
|