db_schema 0.3.1 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +7 -3
  3. data/db_schema.gemspec +2 -1
  4. data/lib/db_schema.rb +4 -3
  5. data/lib/db_schema/normalizer.rb +2 -2
  6. data/lib/db_schema/reader.rb +8 -350
  7. data/lib/db_schema/version.rb +1 -1
  8. metadata +22 -43
  9. data/lib/db_schema/definitions.rb +0 -10
  10. data/lib/db_schema/definitions/check_constraint.rb +0 -21
  11. data/lib/db_schema/definitions/enum.rb +0 -23
  12. data/lib/db_schema/definitions/extension.rb +0 -12
  13. data/lib/db_schema/definitions/field.rb +0 -47
  14. data/lib/db_schema/definitions/field/array.rb +0 -30
  15. data/lib/db_schema/definitions/field/base.rb +0 -108
  16. data/lib/db_schema/definitions/field/binary.rb +0 -9
  17. data/lib/db_schema/definitions/field/bit_string.rb +0 -15
  18. data/lib/db_schema/definitions/field/boolean.rb +0 -9
  19. data/lib/db_schema/definitions/field/character.rb +0 -19
  20. data/lib/db_schema/definitions/field/custom.rb +0 -32
  21. data/lib/db_schema/definitions/field/datetime.rb +0 -30
  22. data/lib/db_schema/definitions/field/extensions/chkpass.rb +0 -9
  23. data/lib/db_schema/definitions/field/extensions/citext.rb +0 -9
  24. data/lib/db_schema/definitions/field/extensions/cube.rb +0 -9
  25. data/lib/db_schema/definitions/field/extensions/hstore.rb +0 -9
  26. data/lib/db_schema/definitions/field/extensions/isn.rb +0 -37
  27. data/lib/db_schema/definitions/field/extensions/ltree.rb +0 -9
  28. data/lib/db_schema/definitions/field/extensions/seg.rb +0 -9
  29. data/lib/db_schema/definitions/field/geometric.rb +0 -33
  30. data/lib/db_schema/definitions/field/json.rb +0 -13
  31. data/lib/db_schema/definitions/field/monetary.rb +0 -9
  32. data/lib/db_schema/definitions/field/network.rb +0 -17
  33. data/lib/db_schema/definitions/field/numeric.rb +0 -30
  34. data/lib/db_schema/definitions/field/range.rb +0 -29
  35. data/lib/db_schema/definitions/field/text_search.rb +0 -13
  36. data/lib/db_schema/definitions/field/uuid.rb +0 -9
  37. data/lib/db_schema/definitions/foreign_key.rb +0 -44
  38. data/lib/db_schema/definitions/index.rb +0 -66
  39. data/lib/db_schema/definitions/index/column.rb +0 -32
  40. data/lib/db_schema/definitions/index/expression.rb +0 -19
  41. data/lib/db_schema/definitions/index/table_field.rb +0 -19
  42. data/lib/db_schema/definitions/schema.rb +0 -36
  43. data/lib/db_schema/definitions/table.rb +0 -130
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 92f4601dd7a4513014fd782391bfe69f8400f06d
4
- data.tar.gz: 291caae43bf4582dbf0636b8601de86ebdcf3c25
2
+ SHA256:
3
+ metadata.gz: 63847549e7e2a6350f2804c5a2a251388f6b65ff206d51fc34403b4a56d883aa
4
+ data.tar.gz: a2d2ff387fa992afac8764cd6a0c5dff3b5f34bcd1f632d4fcb3ec216f13360c
5
5
  SHA512:
6
- metadata.gz: 7a8af71a493a89113166e8a1ee0fa1808ba8bbbcbb07b263fa0d9f0884eafe4d297e64d79815a854f460ad78af3bd0c847c82741c96d5175e127452a1333120d
7
- data.tar.gz: 41fb922d5f307c481f3595d27983c39a13640cd2c5cdc5e9e6a395ad34c509cf712f4d08f79825c730462855c9d66d178961bb1d48d77b599386e10ab90b831a
6
+ metadata.gz: 8d2793a8eed21c593be07e7c5a067a86e0d2566938f99e410808d3657898b37abaab8e1db1adff8ff8b8c311fb123204b05713f650cf9ff2de882689f2ebabf0
7
+ data.tar.gz: c2b2e6dca0d6c7df815bf8e8e060bb3b35727bd02ccd8c18c943c6f7373777d1e77151f0ba87f0f64014affc371848e8e05dfa6fd0a6c9387fc340caa42bfccc
data/README.md CHANGED
@@ -50,10 +50,11 @@ But you would lose it even with manual migrations.
50
50
 
51
51
  ## Installation
52
52
 
53
- Add this line to your application's Gemfile:
53
+ Add these lines to your application's Gemfile:
54
54
 
55
55
  ``` ruby
56
- gem 'db_schema', '~> 0.3.1'
56
+ gem 'db_schema', '~> 0.4.0'
57
+ gem 'db_schema-reader-postgres', '~> 0.1.1'
57
58
  ```
58
59
 
59
60
  And then execute:
@@ -65,9 +66,12 @@ $ bundle
65
66
  Or install it yourself as:
66
67
 
67
68
  ``` sh
68
- $ gem install db_schema
69
+ $ gem install db_schema db_schema-reader-postgres
69
70
  ```
70
71
 
72
+ The `db_schema-reader-postgres` [gem](https://github.com/db-schema/reader-postgres) is a PostgreSQL adapter
73
+ for `DbSchema::Reader` (a module which is responsible for reading the current database schema).
74
+
71
75
  ## Usage
72
76
 
73
77
  First you need to configure DbSchema so it knows how to connect to your database. This should happen
data/db_schema.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency 'sequel'
22
22
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
23
+ spec.add_runtime_dependency 'db_schema-definitions', '~> 0.1.1'
23
24
 
24
25
  spec.add_development_dependency 'bundler', '~> 1.11'
25
26
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -31,5 +32,5 @@ Gem::Specification.new do |spec|
31
32
  spec.add_development_dependency 'terminal-notifier'
32
33
  spec.add_development_dependency 'terminal-notifier-guard'
33
34
 
34
- spec.add_development_dependency 'pg'
35
+ spec.add_development_dependency 'db_schema-reader-postgres', '~> 0.1.1'
35
36
  end
data/lib/db_schema.rb CHANGED
@@ -114,7 +114,8 @@ module DbSchema
114
114
  end
115
115
 
116
116
  def run_migrations(migrations, connection)
117
- @current_schema = Reader.read_schema(connection)
117
+ reader = Reader.reader_for(connection)
118
+ @current_schema = reader.read_schema
118
119
 
119
120
  migrations.reduce(@current_schema) do |schema, migration|
120
121
  migrator = Migrator.new(migration)
@@ -122,7 +123,7 @@ module DbSchema
122
123
  if migrator.applicable?(schema)
123
124
  log_migration(migration) if configuration.log_changes?
124
125
  migrator.run!(connection)
125
- Reader.read_schema(connection)
126
+ reader.read_schema
126
127
  else
127
128
  schema
128
129
  end
@@ -145,7 +146,7 @@ module DbSchema
145
146
  end
146
147
 
147
148
  def perform_post_check(desired_schema, connection)
148
- unapplied_changes = Changes.between(desired_schema, Reader.read_schema(connection))
149
+ unapplied_changes = Changes.between(desired_schema, Reader.reader_for(connection).read_schema)
149
150
  return if unapplied_changes.empty?
150
151
 
151
152
  readable_changes = if unapplied_changes.respond_to?(:ai)
@@ -28,7 +28,7 @@ module DbSchema
28
28
 
29
29
  private
30
30
  def create_extensions!
31
- operations = (schema.extensions - Reader.read_extensions(connection)).map do |extension|
31
+ operations = (schema.extensions - Reader.reader_for(connection).read_extensions).map do |extension|
32
32
  Operations::CreateExtension.new(extension)
33
33
  end
34
34
 
@@ -85,7 +85,7 @@ module DbSchema
85
85
  end
86
86
 
87
87
  def read_temporary_table
88
- temporary_table = Reader.read_table(temporary_table_name, connection)
88
+ temporary_table = Reader.reader_for(connection).read_table(temporary_table_name)
89
89
 
90
90
  temporary_table.with_name(table.name)
91
91
  .with_fields(rename_types_back(temporary_table.fields))
@@ -1,360 +1,18 @@
1
1
  module DbSchema
2
2
  module Reader
3
3
  class << self
4
- def read_schema(connection)
5
- adapter(connection).read_schema(connection)
6
- end
7
-
8
- def read_table(table_name, connection)
9
- adapter(connection).read_table(table_name, connection)
10
- end
11
-
12
- def read_enums(connection)
13
- adapter(connection).read_enums(connection)
14
- end
15
-
16
- def read_extensions(connection)
17
- adapter(connection).read_extensions(connection)
18
- end
19
-
20
- private
21
- def adapter(connection)
22
- registry.fetch(connection.adapter_scheme) do |adapter_name|
23
- raise NotImplementedError, "DbSchema::Reader does not support #{adapter_name}."
24
- end
25
- end
26
-
27
- def registry
28
- @registry ||= {}
29
- end
30
- end
31
-
32
- module Postgres
33
- DEFAULT_VALUE = /\A(
34
- ('(?<date>\d{4}-\d{2}-\d{2})'::date)
35
- |
36
- ('(?<time>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}([+-]\d{2})?)'::timestamp)
37
- |
38
- ('(?<string>.*)')
39
- |
40
- (?<float>\d+\.\d+)
41
- |
42
- (?<integer>\d+)
43
- |
44
- (?<boolean>true|false)
45
- )/x
46
-
47
- COLUMN_NAMES_QUERY = <<-SQL.freeze
48
- SELECT c.column_name AS name,
49
- c.ordinal_position AS pos,
50
- c.column_default AS default,
51
- c.is_nullable AS null,
52
- c.data_type AS type,
53
- c.udt_name AS custom_type_name,
54
- c.character_maximum_length AS char_length,
55
- c.numeric_precision AS num_precision,
56
- c.numeric_scale AS num_scale,
57
- c.datetime_precision AS dt_precision,
58
- c.interval_type,
59
- e.data_type AS element_type,
60
- e.udt_name AS element_custom_type_name
61
- FROM information_schema.columns AS c
62
- LEFT JOIN information_schema.element_types AS e
63
- ON e.object_catalog = c.table_catalog
64
- AND e.object_schema = c.table_schema
65
- AND e.object_name = c.table_name
66
- AND e.object_type = 'TABLE'
67
- AND e.collection_type_identifier = c.dtd_identifier
68
- WHERE c.table_schema = 'public'
69
- AND c.table_name = ?
70
- SQL
71
-
72
- CONSTRAINTS_QUERY = <<-SQL.freeze
73
- SELECT conname AS name,
74
- pg_get_expr(conbin, conrelid, true) AS condition
75
- FROM pg_constraint, pg_class
76
- WHERE conrelid = pg_class.oid
77
- AND relname = ?
78
- AND contype = 'c'
79
- SQL
80
-
81
- INDEXES_QUERY = <<-SQL.freeze
82
- SELECT relname AS name,
83
- indkey AS column_positions,
84
- indisunique AS unique,
85
- indoption AS index_options,
86
- pg_get_expr(indpred, indrelid, true) AS condition,
87
- amname AS index_type,
88
- indexrelid AS index_oid
89
- FROM pg_class, pg_index
90
- LEFT JOIN pg_opclass
91
- ON pg_opclass.oid = ANY(pg_index.indclass::int[])
92
- LEFT JOIN pg_am
93
- ON pg_am.oid = pg_opclass.opcmethod
94
- WHERE pg_class.oid = pg_index.indexrelid
95
- AND pg_class.oid IN (
96
- SELECT indexrelid
97
- FROM pg_index, pg_class
98
- WHERE pg_class.relname = ?
99
- AND pg_class.oid = pg_index.indrelid
100
- AND indisprimary != 't'
101
- )
102
- GROUP BY name, column_positions, indisunique, index_options, condition, index_type, index_oid
103
- SQL
104
-
105
- EXPRESSION_INDEXES_QUERY = <<-SQL.freeze
106
- WITH index_ids AS (SELECT unnest(?) AS index_id),
107
- elements AS (SELECT unnest(?) AS element)
108
- SELECT index_id,
109
- array_agg(pg_get_indexdef(index_id, element, 't')) AS definitions
110
- FROM index_ids, elements
111
- GROUP BY index_id;
112
- SQL
113
-
114
- ENUMS_QUERY = <<-SQL.freeze
115
- SELECT t.typname AS name,
116
- array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
117
- FROM pg_enum AS e
118
- JOIN pg_type AS t
119
- ON t.oid = e.enumtypid
120
- GROUP BY name
121
- SQL
122
-
123
- EXTENSIONS_QUERY = <<-SQL.freeze
124
- SELECT extname
125
- FROM pg_extension
126
- WHERE extname != 'plpgsql'
127
- SQL
128
-
129
- class << self
130
- def read_schema(connection)
131
- Definitions::Schema.new(
132
- tables: read_tables(connection),
133
- enums: read_enums(connection),
134
- extensions: read_extensions(connection)
135
- )
136
- end
137
-
138
- def read_table(table_name, connection)
139
- primary_key_name = connection.primary_key(table_name)
140
-
141
- fields = connection[COLUMN_NAMES_QUERY, table_name.to_s].map do |column_data|
142
- build_field(column_data, primary_key: column_data[:name] == primary_key_name)
143
- end
144
-
145
- indexes = indexes_data_for(table_name, connection).map do |index_data|
146
- Definitions::Index.new(index_data)
147
- end.sort_by(&:name)
148
-
149
- foreign_keys = connection.foreign_key_list(table_name).map do |foreign_key_data|
150
- build_foreign_key(foreign_key_data, connection)
151
- end
152
-
153
- checks = connection[CONSTRAINTS_QUERY, table_name.to_s].map do |check_data|
154
- Definitions::CheckConstraint.new(
155
- name: check_data[:name].to_sym,
156
- condition: check_data[:condition]
157
- )
158
- end
159
-
160
- Definitions::Table.new(
161
- table_name,
162
- fields: fields,
163
- indexes: indexes,
164
- checks: checks,
165
- foreign_keys: foreign_keys
166
- )
167
- end
168
-
169
- def indexes_data_for(table_name, connection)
170
- column_names = connection[COLUMN_NAMES_QUERY, table_name.to_s].reduce({}) do |names, column|
171
- names.merge(column[:pos] => column[:name].to_sym)
172
- end
173
-
174
- indexes_data = connection[INDEXES_QUERY, table_name.to_s].to_a
175
- expressions_data = index_expressions_data(indexes_data, connection)
176
-
177
- indexes_data.map do |index|
178
- positions = index[:column_positions].split(' ').map(&:to_i)
179
- options = index[:index_options].split(' ').map(&:to_i)
180
-
181
- columns = positions.zip(options).map do |column_position, column_order_options|
182
- options = case column_order_options
183
- when 0
184
- {}
185
- when 3
186
- { order: :desc }
187
- when 2
188
- { nulls: :first }
189
- when 1
190
- { order: :desc, nulls: :last }
191
- end
192
-
193
- if column_position.zero?
194
- expression = expressions_data.fetch(index[:index_oid]).shift
195
- DbSchema::Definitions::Index::Expression.new(expression, **options)
196
- else
197
- DbSchema::Definitions::Index::TableField.new(column_names.fetch(column_position), **options)
198
- end
199
- end
200
-
201
- {
202
- name: index[:name].to_sym,
203
- columns: columns,
204
- unique: index[:unique],
205
- type: index[:index_type].to_sym,
206
- condition: index[:condition]
207
- }
208
- end
209
- end
210
-
211
- def read_tables(connection)
212
- connection.tables.map do |table_name|
213
- read_table(table_name, connection)
214
- end
215
- end
216
-
217
- def read_enums(connection)
218
- connection[ENUMS_QUERY].map do |enum_data|
219
- Definitions::Enum.new(enum_data[:name].to_sym, enum_data[:values].map(&:to_sym))
220
- end
221
- end
222
-
223
- def read_extensions(connection)
224
- connection[EXTENSIONS_QUERY].map do |extension_data|
225
- Definitions::Extension.new(extension_data[:extname].to_sym)
226
- end
227
- end
228
-
229
- private
230
- def index_expressions_data(indexes_data, connection)
231
- all_positions, max_position = {}, 0
232
-
233
- indexes_data.each do |index_data|
234
- positions = index_data[:column_positions].split(' ').map(&:to_i)
235
- expression_positions = positions.each_index.select { |i| positions[i].zero? }
236
-
237
- if expression_positions.any?
238
- all_positions[index_data[:index_oid]] = expression_positions
239
- max_position = [max_position, expression_positions.max].max
240
- end
241
- end
242
-
243
- if all_positions.any?
244
- connection[
245
- EXPRESSION_INDEXES_QUERY,
246
- Sequel.pg_array(all_positions.keys),
247
- Sequel.pg_array((1..max_position.succ).to_a)
248
- ].each_with_object({}) do |index_data, indexes_data|
249
- index_id = index_data[:index_id]
250
- expressions = all_positions[index_id].map { |pos| index_data[:definitions][pos] }
251
-
252
- indexes_data[index_id] = expressions
253
- end
254
- else
255
- {}
256
- end
257
- end
258
-
259
- def build_field(data, primary_key: false)
260
- type = data[:type].to_sym.downcase
261
- if type == :'user-defined'
262
- type = data[:custom_type_name].to_sym
263
- end
264
-
265
- nullable = (data[:null] != 'NO')
266
-
267
- unless primary_key || data[:default].nil?
268
- default = if match = DEFAULT_VALUE.match(data[:default])
269
- if match[:date]
270
- Date.parse(match[:date])
271
- elsif match[:time]
272
- Time.parse(match[:time])
273
- elsif match[:string]
274
- match[:string]
275
- elsif match[:integer]
276
- match[:integer].to_i
277
- elsif match[:float]
278
- match[:float].to_f
279
- elsif match[:boolean]
280
- match[:boolean] == 'true'
281
- end
282
- else
283
- data[:default].to_sym
284
- end
285
- end
286
-
287
- options = case type
288
- when :character, :'character varying', :bit, :'bit varying'
289
- Utils.rename_keys(
290
- Utils.filter_by_keys(data, :char_length),
291
- char_length: :length
292
- )
293
- when :numeric
294
- Utils.rename_keys(
295
- Utils.filter_by_keys(data, :num_precision, :num_scale),
296
- num_precision: :precision,
297
- num_scale: :scale
298
- )
299
- when :interval
300
- Utils.rename_keys(
301
- Utils.filter_by_keys(data, :dt_precision, :interval_type),
302
- dt_precision: :precision
303
- ) do |attributes|
304
- if interval_type = attributes.delete(:interval_type)
305
- attributes[:fields] = interval_type.gsub(/\(\d\)/, '').downcase.to_sym
306
- end
307
- end
308
- when :array
309
- Utils.rename_keys(
310
- Utils.filter_by_keys(data, :element_type, :element_custom_type_name)
311
- ) do |attributes|
312
- attributes[:element_type] = if attributes[:element_type] == 'USER-DEFINED'
313
- attributes[:element_custom_type_name]
314
- else
315
- attributes[:element_type]
316
- end.to_sym
317
- end
318
- else
319
- {}
320
- end
321
-
322
- Definitions::Field.build(
323
- data[:name].to_sym,
324
- type,
325
- primary_key: primary_key,
326
- null: nullable,
327
- default: default,
328
- **options
329
- )
330
- end
331
-
332
- def build_foreign_key(data, connection)
333
- keys = if data[:key] == [primary_key_for(data[:table], connection)]
334
- [] # this foreign key references a primary key
335
- else
336
- data[:key]
4
+ def reader_for(connection)
5
+ case connection.adapter_scheme
6
+ when :postgres
7
+ unless defined?(Reader::Postgres)
8
+ raise 'You need the \'db_schema-reader-postgres\' gem in order to work with PostgreSQL database structure.'
337
9
  end
338
10
 
339
- Definitions::ForeignKey.new(
340
- name: data[:name],
341
- fields: data[:columns],
342
- table: data[:table],
343
- keys: keys,
344
- on_delete: data[:on_delete],
345
- on_update: data[:on_update],
346
- deferrable: data[:deferrable]
347
- )
348
- end
349
-
350
- def primary_key_for(table_name, connection)
351
- if pkey = connection.primary_key(table_name)
352
- pkey.to_sym
353
- end
11
+ Reader::Postgres.new(connection)
12
+ else
13
+ raise NotImplementedError, "DbSchema::Reader does not support #{connection.adapter_scheme}."
354
14
  end
355
15
  end
356
16
  end
357
-
358
- registry[:postgres] = Postgres
359
17
  end
360
18
  end