db_schema 0.3.1 → 0.4

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