db_schema 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -3
- data/lib/db_schema/awesome_print.rb +5 -5
- data/lib/db_schema/changes.rb +2 -2
- data/lib/db_schema/definitions.rb +44 -8
- data/lib/db_schema/dsl.rb +21 -8
- data/lib/db_schema/reader.rb +48 -8
- data/lib/db_schema/runner.rb +2 -14
- data/lib/db_schema/validator.rb +1 -1
- data/lib/db_schema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a6dfe3738279a4199344a79ce5bfbbb7e504d50
|
4
|
+
data.tar.gz: 035b90071d29f3447ac99d79cd324b403e4838d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a19fa80ca01c4a3fd95983a73e58cc09628a5f453ca49fc290cfa7238e9eb136bf8de25e8ae7f8f179d9c65ff24a3e18418c6d380e9db7f351f8903cfe8d839f
|
7
|
+
data.tar.gz: 1fe00c3cada2070f96d397920aa785b4ef70cc77856e0ac6818eae46f92281cfdf92d9153c3a7db978a2ad1b13919edf1f2b734331501379c133812437ba8242
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# DbSchema [](https://travis-ci.org/7even/db_schema)
|
1
|
+
# DbSchema [](https://travis-ci.org/7even/db_schema) [](https://badge.fury.io/rb/db_schema)
|
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.1.
|
56
|
+
gem 'db_schema', '~> 0.1.2'
|
57
57
|
```
|
58
58
|
|
59
59
|
And then execute:
|
@@ -356,6 +356,19 @@ end
|
|
356
356
|
|
357
357
|
Be warned though that you have to specify the condition exactly as PostgreSQL outputs it in `psql` with `\d table_name` command; otherwise your index will be recreated on each DbSchema run. This will be fixed in a later DbSchema version.
|
358
358
|
|
359
|
+
If you need an index on expression you can use the same syntax replacing column names with SQL strings containing the expressions:
|
360
|
+
|
361
|
+
``` ruby
|
362
|
+
db.table :users do |t|
|
363
|
+
t.timestamp :created_at
|
364
|
+
t.index 'date(created_at)'
|
365
|
+
end
|
366
|
+
```
|
367
|
+
|
368
|
+
Expression indexes syntax allows specifying an order exactly like in a common index on table fields - just use a hash form like `t.index 'date(created_at)' => :desc`. You can also use an expression in a multiple index.
|
369
|
+
|
370
|
+
As with partial index condition (and all other SQL segments in `db_schema`), you must write the expression in a way `psql` outputs it, so instead of `lower(email)` you should use `lower(email::text)` (assuming that `email` is a varchar field).
|
371
|
+
|
359
372
|
#### Foreign keys
|
360
373
|
|
361
374
|
The `#foreign_key` method defines a foreign key. In it's minimal form it takes a referencing field name and referenced table name:
|
@@ -519,7 +532,7 @@ All configuration options are described in the following table:
|
|
519
532
|
|
520
533
|
By default DbSchema logs the changes it applies to your database; you can disable that by setting `log_changes` to false.
|
521
534
|
|
522
|
-
DbSchema provides an opt-out post-run schema check; it ensures that there are no remaining differences between your `schema.rb` and the actual database schema. If DbSchema still sees any differences it will keep applying them on each run - usually this is harmless (because it does not really change your schema) but in the case of a partial index with a complex condition it may rebuild the index which is an expensive operation on a large table. You can set `post_check` to false if you are 100% sure that your persistent changes are not a problem for you but I strongly recommend that you turn it on from time to time just to make sure nothing dangerous appears in these persistent changes.
|
535
|
+
DbSchema provides an opt-out post-run schema check; it ensures that there are no remaining differences between your `schema.rb` and the actual database schema. If DbSchema still sees any differences it will keep applying them on each run - usually this is harmless (because it does not really change your schema) but in the case of a partial index with a complex condition or an index on some expression it may rebuild the index which is an expensive operation on a large table. You can set `post_check` to false if you are 100% sure that your persistent changes are not a problem for you but I strongly recommend that you turn it on from time to time just to make sure nothing dangerous appears in these persistent changes.
|
523
536
|
|
524
537
|
The `post_check` option is likely to become off by default when DbSchema becomes more stable and battle-tested, and when the partial index problem will be solved.
|
525
538
|
|
@@ -21,8 +21,8 @@ if defined?(AwesomePrint)
|
|
21
21
|
:dbschema_field
|
22
22
|
when ::DbSchema::Definitions::Index
|
23
23
|
:dbschema_index
|
24
|
-
when ::DbSchema::Definitions::Index::
|
25
|
-
:
|
24
|
+
when ::DbSchema::Definitions::Index::Column
|
25
|
+
:dbschema_index_column
|
26
26
|
when ::DbSchema::Definitions::CheckConstraint
|
27
27
|
:dbschema_check_constraint
|
28
28
|
when ::DbSchema::Definitions::ForeignKey
|
@@ -120,17 +120,17 @@ if defined?(AwesomePrint)
|
|
120
120
|
end
|
121
121
|
|
122
122
|
def awesome_dbschema_index(object)
|
123
|
-
|
123
|
+
columns = format_dbschema_fields(object.columns)
|
124
124
|
using = ' using ' + colorize(object.type.to_s, :symbol) unless object.btree?
|
125
125
|
|
126
126
|
data = [nil]
|
127
127
|
data << colorize('unique', :nilclass) if object.unique?
|
128
128
|
data << colorize('condition: ', :symbol) + object.condition.ai unless object.condition.nil?
|
129
129
|
|
130
|
-
"#<#{object.class} #{object.name.ai} on #{
|
130
|
+
"#<#{object.class} #{object.name.ai} on #{columns}#{using}#{data.join(', ')}>"
|
131
131
|
end
|
132
132
|
|
133
|
-
def
|
133
|
+
def awesome_dbschema_index_column(object)
|
134
134
|
data = [object.name.ai]
|
135
135
|
|
136
136
|
if object.desc?
|
data/lib/db_schema/changes.rb
CHANGED
@@ -155,7 +155,7 @@ module DbSchema
|
|
155
155
|
if desired && !actual
|
156
156
|
table_changes << CreateIndex.new(
|
157
157
|
name: index_name,
|
158
|
-
|
158
|
+
columns: desired.columns,
|
159
159
|
unique: desired.unique?,
|
160
160
|
type: desired.type,
|
161
161
|
condition: desired.condition
|
@@ -166,7 +166,7 @@ module DbSchema
|
|
166
166
|
table_changes << DropIndex.new(index_name)
|
167
167
|
table_changes << CreateIndex.new(
|
168
168
|
name: index_name,
|
169
|
-
|
169
|
+
columns: desired.columns,
|
170
170
|
unique: desired.unique?,
|
171
171
|
type: desired.type,
|
172
172
|
condition: desired.condition
|
@@ -3,12 +3,12 @@ require 'dry/equalizer'
|
|
3
3
|
module DbSchema
|
4
4
|
module Definitions
|
5
5
|
class Index
|
6
|
-
include Dry::Equalizer(:name, :
|
7
|
-
attr_reader :name, :
|
6
|
+
include Dry::Equalizer(:name, :columns, :unique?, :type, :condition)
|
7
|
+
attr_reader :name, :columns, :type, :condition
|
8
8
|
|
9
|
-
def initialize(name:,
|
9
|
+
def initialize(name:, columns:, unique: false, type: :btree, condition: nil)
|
10
10
|
@name = name.to_sym
|
11
|
-
@
|
11
|
+
@columns = columns
|
12
12
|
@unique = unique
|
13
13
|
@type = type
|
14
14
|
@condition = condition
|
@@ -22,7 +22,15 @@ module DbSchema
|
|
22
22
|
type == :btree
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
def columns_to_sequel
|
26
|
+
if btree?
|
27
|
+
columns.map(&:ordered_expression)
|
28
|
+
else
|
29
|
+
columns.map(&:to_sequel)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Column
|
26
34
|
include Dry::Equalizer(:name, :order, :nulls)
|
27
35
|
attr_reader :name, :order, :nulls
|
28
36
|
|
@@ -40,14 +48,42 @@ module DbSchema
|
|
40
48
|
@order == :desc
|
41
49
|
end
|
42
50
|
|
43
|
-
def
|
51
|
+
def ordered_expression
|
44
52
|
if asc?
|
45
|
-
Sequel.asc(
|
53
|
+
Sequel.asc(to_sequel, nulls: nulls)
|
46
54
|
else
|
47
|
-
Sequel.desc(
|
55
|
+
Sequel.desc(to_sequel, nulls: nulls)
|
48
56
|
end
|
49
57
|
end
|
50
58
|
end
|
59
|
+
|
60
|
+
class TableField < Column
|
61
|
+
def expression?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def index_name_segment
|
66
|
+
name
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_sequel
|
70
|
+
name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Expression < Column
|
75
|
+
def expression?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def index_name_segment
|
80
|
+
name.scan(/\b[A-Za-z0-9_]+\b/).join('_')
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_sequel
|
84
|
+
Sequel.lit("(#{name})")
|
85
|
+
end
|
86
|
+
end
|
51
87
|
end
|
52
88
|
|
53
89
|
class ForeignKey
|
data/lib/db_schema/dsl.rb
CHANGED
@@ -55,11 +55,20 @@ module DbSchema
|
|
55
55
|
fields << Definitions::Field.build(name, type, options)
|
56
56
|
end
|
57
57
|
|
58
|
-
def index(*
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
def index(*columns, name: nil, unique: false, using: :btree, where: nil, **ordered_fields)
|
59
|
+
if columns.last.is_a?(Hash)
|
60
|
+
*ascending_columns, ordered_expressions = columns
|
61
|
+
else
|
62
|
+
ascending_columns = columns
|
63
|
+
ordered_expressions = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
columns_data = ascending_columns.each_with_object({}) do |column_name, columns|
|
67
|
+
columns[column_name] = :asc
|
68
|
+
end.merge(ordered_fields).merge(ordered_expressions)
|
69
|
+
|
70
|
+
index_columns = columns_data.map do |column_name, column_order_options|
|
71
|
+
options = case column_order_options
|
63
72
|
when :asc
|
64
73
|
{}
|
65
74
|
when :desc
|
@@ -72,14 +81,18 @@ module DbSchema
|
|
72
81
|
raise ArgumentError, 'Only :asc, :desc, :asc_nulls_first and :desc_nulls_last options are supported.'
|
73
82
|
end
|
74
83
|
|
75
|
-
|
84
|
+
if column_name.is_a?(String)
|
85
|
+
Definitions::Index::Expression.new(column_name, **options)
|
86
|
+
else
|
87
|
+
Definitions::Index::TableField.new(column_name, **options)
|
88
|
+
end
|
76
89
|
end
|
77
90
|
|
78
|
-
index_name = name || "#{table_name}_#{
|
91
|
+
index_name = name || "#{table_name}_#{index_columns.map(&:index_name_segment).join('_')}_index"
|
79
92
|
|
80
93
|
indices << Definitions::Index.new(
|
81
94
|
name: index_name,
|
82
|
-
|
95
|
+
columns: index_columns,
|
83
96
|
unique: unique,
|
84
97
|
type: using,
|
85
98
|
condition: where
|
data/lib/db_schema/reader.rb
CHANGED
@@ -60,8 +60,8 @@ SELECT conname AS name,
|
|
60
60
|
indisunique AS unique,
|
61
61
|
indoption AS index_options,
|
62
62
|
pg_get_expr(indpred, indrelid, true) AS condition,
|
63
|
-
|
64
|
-
|
63
|
+
amname AS index_type,
|
64
|
+
indexrelid AS index_oid
|
65
65
|
FROM pg_class, pg_index
|
66
66
|
LEFT JOIN pg_opclass
|
67
67
|
ON pg_opclass.oid = ANY(pg_index.indclass::int[])
|
@@ -75,7 +75,16 @@ LEFT JOIN pg_am
|
|
75
75
|
AND pg_class.oid = pg_index.indrelid
|
76
76
|
AND indisprimary != 't'
|
77
77
|
)
|
78
|
-
GROUP BY name, column_positions, indisunique, index_options, condition,
|
78
|
+
GROUP BY name, column_positions, indisunique, index_options, condition, index_type, index_oid
|
79
|
+
SQL
|
80
|
+
|
81
|
+
EXPRESSION_INDICES_QUERY = <<-SQL.freeze
|
82
|
+
WITH index_ids AS (SELECT unnest(?) AS index_id),
|
83
|
+
elements AS (SELECT unnest(?) AS element)
|
84
|
+
SELECT index_id,
|
85
|
+
array_agg(pg_get_indexdef(index_id, element, 't')) AS definitions
|
86
|
+
FROM index_ids, elements
|
87
|
+
GROUP BY index_id;
|
79
88
|
SQL
|
80
89
|
|
81
90
|
ENUMS_QUERY = <<-SQL.freeze
|
@@ -139,14 +148,17 @@ SELECT extname
|
|
139
148
|
|
140
149
|
def indices_data_for(table_name)
|
141
150
|
column_names = DbSchema.connection[COLUMN_NAMES_QUERY, table_name.to_s].reduce({}) do |names, column|
|
142
|
-
names.merge(column[:pos] => column[:name])
|
151
|
+
names.merge(column[:pos] => column[:name].to_sym)
|
143
152
|
end
|
144
153
|
|
145
|
-
DbSchema.connection[INDICES_QUERY, table_name.to_s].
|
154
|
+
indices_data = DbSchema.connection[INDICES_QUERY, table_name.to_s].to_a
|
155
|
+
expressions_data = index_expressions_data(indices_data)
|
156
|
+
|
157
|
+
indices_data.map do |index|
|
146
158
|
positions = index[:column_positions].split(' ').map(&:to_i)
|
147
159
|
options = index[:index_options].split(' ').map(&:to_i)
|
148
160
|
|
149
|
-
|
161
|
+
columns = positions.zip(options).map do |column_position, column_order_options|
|
150
162
|
options = case column_order_options
|
151
163
|
when 0
|
152
164
|
{}
|
@@ -158,12 +170,17 @@ SELECT extname
|
|
158
170
|
{ order: :desc, nulls: :last }
|
159
171
|
end
|
160
172
|
|
161
|
-
|
173
|
+
if column_position.zero?
|
174
|
+
expression = expressions_data.fetch(index[:index_oid]).shift
|
175
|
+
DbSchema::Definitions::Index::Expression.new(expression, **options)
|
176
|
+
else
|
177
|
+
DbSchema::Definitions::Index::TableField.new(column_names.fetch(column_position), **options)
|
178
|
+
end
|
162
179
|
end
|
163
180
|
|
164
181
|
{
|
165
182
|
name: index[:name].to_sym,
|
166
|
-
|
183
|
+
columns: columns,
|
167
184
|
unique: index[:unique],
|
168
185
|
type: index[:index_type].to_sym,
|
169
186
|
condition: index[:condition]
|
@@ -172,6 +189,29 @@ SELECT extname
|
|
172
189
|
end
|
173
190
|
|
174
191
|
private
|
192
|
+
def index_expressions_data(indices_data)
|
193
|
+
expressions_stats = indices_data.each_with_object(ids: [], max: 0) do |index_data, stats|
|
194
|
+
expressions_count = index_data[:column_positions].split(' ').count('0')
|
195
|
+
|
196
|
+
if expressions_count > 0
|
197
|
+
stats[:ids] << index_data[:index_oid]
|
198
|
+
stats[:max] = [stats[:max], expressions_count].max
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
if expressions_stats[:max] > 0
|
203
|
+
DbSchema.connection[
|
204
|
+
EXPRESSION_INDICES_QUERY,
|
205
|
+
Sequel.pg_array(expressions_stats[:ids]),
|
206
|
+
Sequel.pg_array((1..expressions_stats[:max]).to_a)
|
207
|
+
].each_with_object({}) do |index_data, indices_data|
|
208
|
+
indices_data[index_data[:index_id]] = index_data[:definitions]
|
209
|
+
end
|
210
|
+
else
|
211
|
+
{}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
175
215
|
def build_field(data, primary_key: false)
|
176
216
|
type = data[:type].to_sym.downcase
|
177
217
|
if type == :'user-defined'
|
data/lib/db_schema/runner.rb
CHANGED
@@ -70,14 +70,8 @@ module DbSchema
|
|
70
70
|
end
|
71
71
|
|
72
72
|
change.indices.each do |index|
|
73
|
-
fields = if index.btree?
|
74
|
-
index.fields.map(&:to_sequel)
|
75
|
-
else
|
76
|
-
index.fields.map(&:name)
|
77
|
-
end
|
78
|
-
|
79
73
|
index(
|
80
|
-
|
74
|
+
index.columns_to_sequel,
|
81
75
|
name: index.name,
|
82
76
|
unique: index.unique?,
|
83
77
|
type: index.type,
|
@@ -141,14 +135,8 @@ module DbSchema
|
|
141
135
|
when Changes::AlterColumnDefault
|
142
136
|
set_column_default(element.name, element.new_default)
|
143
137
|
when Changes::CreateIndex
|
144
|
-
fields = if element.btree?
|
145
|
-
element.fields.map(&:to_sequel)
|
146
|
-
else
|
147
|
-
element.fields.map(&:name)
|
148
|
-
end
|
149
|
-
|
150
138
|
add_index(
|
151
|
-
|
139
|
+
element.columns_to_sequel,
|
152
140
|
name: element.name,
|
153
141
|
unique: element.unique?,
|
154
142
|
type: element.type,
|
data/lib/db_schema/validator.rb
CHANGED
@@ -24,7 +24,7 @@ module DbSchema
|
|
24
24
|
field_names = table.fields.map(&:name)
|
25
25
|
|
26
26
|
table.indices.each do |index|
|
27
|
-
index.
|
27
|
+
index.columns.reject(&:expression?).map(&:name).each do |field_name|
|
28
28
|
unless field_names.include?(field_name)
|
29
29
|
error_message = %(Index "#{index.name}" refers to a missing field "#{table.name}.#{field_name}")
|
30
30
|
errors << error_message
|
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.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vsevolod Romashov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|