db_schema 0.2.5 → 0.3.rc1
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/.travis.yml +5 -3
- data/README.md +2 -2
- data/lib/db_schema.rb +81 -38
- data/lib/db_schema/awesome_print.rb +37 -36
- data/lib/db_schema/changes.rb +68 -217
- data/lib/db_schema/configuration.rb +34 -15
- data/lib/db_schema/definitions.rb +7 -252
- data/lib/db_schema/definitions/check_constraint.rb +17 -0
- data/lib/db_schema/definitions/enum.rb +21 -0
- data/lib/db_schema/definitions/extension.rb +12 -0
- data/lib/db_schema/definitions/field/base.rb +6 -0
- data/lib/db_schema/definitions/foreign_key.rb +41 -0
- data/lib/db_schema/definitions/index.rb +56 -0
- data/lib/db_schema/definitions/index/column.rb +32 -0
- data/lib/db_schema/definitions/index/expression.rb +19 -0
- data/lib/db_schema/definitions/index/table_field.rb +19 -0
- data/lib/db_schema/definitions/schema.rb +36 -0
- data/lib/db_schema/definitions/table.rb +115 -0
- data/lib/db_schema/dsl.rb +96 -76
- data/lib/db_schema/dsl/migration.rb +24 -0
- data/lib/db_schema/migration.rb +12 -0
- data/lib/db_schema/migrator.rb +177 -0
- data/lib/db_schema/normalizer.rb +19 -17
- data/lib/db_schema/operations.rb +211 -0
- data/lib/db_schema/reader.rb +46 -36
- data/lib/db_schema/runner.rb +147 -171
- data/lib/db_schema/version.rb +1 -1
- metadata +18 -4
@@ -0,0 +1,32 @@
|
|
1
|
+
module DbSchema
|
2
|
+
module Definitions
|
3
|
+
class Index
|
4
|
+
class Column
|
5
|
+
include Dry::Equalizer(:name, :order, :nulls)
|
6
|
+
attr_reader :name, :order, :nulls
|
7
|
+
|
8
|
+
def initialize(name, order: :asc, nulls: order == :asc ? :last : :first)
|
9
|
+
@name = name
|
10
|
+
@order = order
|
11
|
+
@nulls = nulls
|
12
|
+
end
|
13
|
+
|
14
|
+
def asc?
|
15
|
+
@order == :asc
|
16
|
+
end
|
17
|
+
|
18
|
+
def desc?
|
19
|
+
@order == :desc
|
20
|
+
end
|
21
|
+
|
22
|
+
def ordered_expression
|
23
|
+
if asc?
|
24
|
+
Sequel.asc(to_sequel, nulls: nulls)
|
25
|
+
else
|
26
|
+
Sequel.desc(to_sequel, nulls: nulls)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DbSchema
|
2
|
+
module Definitions
|
3
|
+
class Index
|
4
|
+
class Expression < Column
|
5
|
+
def expression?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def index_name_segment
|
10
|
+
name.scan(/\b[A-Za-z0-9_]+\b/).join('_')
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_sequel
|
14
|
+
Sequel.lit("(#{name})")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DbSchema
|
2
|
+
module Definitions
|
3
|
+
class Schema
|
4
|
+
include Dry::Equalizer(:tables, :enums, :extensions)
|
5
|
+
attr_reader :tables, :enums, :extensions
|
6
|
+
attr_writer :tables
|
7
|
+
|
8
|
+
def initialize(tables: [], enums: [], extensions: [])
|
9
|
+
@tables = tables
|
10
|
+
@enums = enums
|
11
|
+
@extensions = extensions
|
12
|
+
end
|
13
|
+
|
14
|
+
def table(table_name)
|
15
|
+
tables.find { |table| table.name == table_name } || NullTable.new
|
16
|
+
end
|
17
|
+
alias_method :[], :table
|
18
|
+
|
19
|
+
def has_table?(table_name)
|
20
|
+
!table(table_name).is_a?(NullTable)
|
21
|
+
end
|
22
|
+
|
23
|
+
def enum(enum_name)
|
24
|
+
enums.find { |enum| enum.name == enum_name } || NullEnum.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_enum?(enum_name)
|
28
|
+
!enum(enum_name).is_a?(NullEnum)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_extension?(extension_name)
|
32
|
+
extensions.any? { |extension| extension.name == extension_name }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module DbSchema
|
2
|
+
module Definitions
|
3
|
+
class Table
|
4
|
+
include Dry::Equalizer(:name, :fields, :indices, :checks, :foreign_keys)
|
5
|
+
attr_reader :name, :fields, :indices, :checks, :foreign_keys
|
6
|
+
|
7
|
+
def initialize(name, fields: [], indices: [], checks: [], foreign_keys: [])
|
8
|
+
@name = name.to_sym
|
9
|
+
@fields = fields
|
10
|
+
@indices = indices
|
11
|
+
@checks = checks
|
12
|
+
@foreign_keys = foreign_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_expressions?
|
16
|
+
fields.any?(&:default_is_expression?) ||
|
17
|
+
indices.any?(&:has_expressions?) ||
|
18
|
+
checks.any?
|
19
|
+
end
|
20
|
+
|
21
|
+
def field(field_name)
|
22
|
+
fields.find { |field| field.name == field_name } || NullField.new
|
23
|
+
end
|
24
|
+
alias_method :[], :field
|
25
|
+
|
26
|
+
def has_field?(field_name)
|
27
|
+
!field(field_name).is_a?(NullField)
|
28
|
+
end
|
29
|
+
|
30
|
+
def index(index_name)
|
31
|
+
indices.find { |index| index.name == index_name } || NullIndex.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_index?(index_name)
|
35
|
+
!index(index_name).is_a?(NullIndex)
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_index_on?(*field_names)
|
39
|
+
indices.any? do |index|
|
40
|
+
index.columns.none?(&:expression?) && index.columns.map(&:name) == field_names
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_unique_index_on?(*field_names)
|
45
|
+
indices.any? do |index|
|
46
|
+
index.unique? && index.columns.none?(&:expression?) && index.columns.map(&:name) == field_names
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def check(check_name)
|
51
|
+
checks.find { |check| check.name == check_name } || NullCheckConstraint.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_check?(check_name)
|
55
|
+
!check(check_name).is_a?(NullCheckConstraint)
|
56
|
+
end
|
57
|
+
|
58
|
+
def foreign_key(fkey_name)
|
59
|
+
foreign_keys.find { |fkey| fkey.name == fkey_name } || NullForeignKey.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_foreign_key?(fkey_name)
|
63
|
+
!foreign_key(fkey_name).is_a?(NullForeignKey)
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_foreign_key_to?(other_table_name)
|
67
|
+
foreign_keys.any? { |fkey| fkey.table == other_table_name }
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_name(new_name)
|
71
|
+
Table.new(
|
72
|
+
new_name,
|
73
|
+
fields: fields,
|
74
|
+
indices: indices,
|
75
|
+
checks: checks,
|
76
|
+
foreign_keys: foreign_keys
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def with_fields(new_fields)
|
81
|
+
Table.new(
|
82
|
+
name,
|
83
|
+
fields: new_fields,
|
84
|
+
indices: indices,
|
85
|
+
checks: checks,
|
86
|
+
foreign_keys: foreign_keys
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_indices(new_indices)
|
91
|
+
Table.new(
|
92
|
+
name,
|
93
|
+
fields: fields,
|
94
|
+
indices: new_indices,
|
95
|
+
checks: checks,
|
96
|
+
foreign_keys: foreign_keys
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def with_foreign_keys(new_foreign_keys)
|
101
|
+
Table.new(
|
102
|
+
name,
|
103
|
+
fields: fields,
|
104
|
+
indices: indices,
|
105
|
+
checks: checks,
|
106
|
+
foreign_keys: new_foreign_keys
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class NullTable < Table
|
112
|
+
def initialize; end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/db_schema/dsl.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
|
+
require_relative 'dsl/migration'
|
2
|
+
|
1
3
|
module DbSchema
|
2
4
|
class DSL
|
3
|
-
attr_reader :
|
5
|
+
attr_reader :schema, :migrations
|
4
6
|
|
5
7
|
def initialize(block)
|
6
|
-
@
|
7
|
-
@
|
8
|
-
end
|
8
|
+
@schema = Definitions::Schema.new
|
9
|
+
@migrations = []
|
9
10
|
|
10
|
-
def schema
|
11
11
|
block.call(self)
|
12
|
-
|
13
|
-
@schema
|
14
12
|
end
|
15
13
|
|
16
14
|
def table(name, &block)
|
@@ -33,6 +31,10 @@ module DbSchema
|
|
33
31
|
@schema.extensions << Definitions::Extension.new(name.to_sym)
|
34
32
|
end
|
35
33
|
|
34
|
+
def migrate(name, &block)
|
35
|
+
migrations << Migration.new(name, block).migration
|
36
|
+
end
|
37
|
+
|
36
38
|
class TableYielder
|
37
39
|
attr_reader :table_name
|
38
40
|
|
@@ -61,47 +63,11 @@ module DbSchema
|
|
61
63
|
field(name, :integer, primary_key: true)
|
62
64
|
end
|
63
65
|
|
64
|
-
def index(*columns,
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
ordered_expressions = {}
|
70
|
-
end
|
71
|
-
|
72
|
-
columns_data = ascending_columns.each_with_object({}) do |column_name, columns|
|
73
|
-
columns[column_name] = :asc
|
74
|
-
end.merge(ordered_fields).merge(ordered_expressions)
|
75
|
-
|
76
|
-
index_columns = columns_data.map do |column_name, column_order_options|
|
77
|
-
options = case column_order_options
|
78
|
-
when :asc
|
79
|
-
{}
|
80
|
-
when :desc
|
81
|
-
{ order: :desc }
|
82
|
-
when :asc_nulls_first
|
83
|
-
{ nulls: :first }
|
84
|
-
when :desc_nulls_last
|
85
|
-
{ order: :desc, nulls: :last }
|
86
|
-
else
|
87
|
-
raise ArgumentError, 'Only :asc, :desc, :asc_nulls_first and :desc_nulls_last options are supported.'
|
88
|
-
end
|
89
|
-
|
90
|
-
if column_name.is_a?(String)
|
91
|
-
Definitions::Index::Expression.new(column_name, **options)
|
92
|
-
else
|
93
|
-
Definitions::Index::TableField.new(column_name, **options)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
index_name = name || "#{table_name}_#{index_columns.map(&:index_name_segment).join('_')}_index"
|
98
|
-
|
99
|
-
indices << Definitions::Index.new(
|
100
|
-
name: index_name,
|
101
|
-
columns: index_columns,
|
102
|
-
unique: unique,
|
103
|
-
type: using,
|
104
|
-
condition: where
|
66
|
+
def index(*columns, **index_options)
|
67
|
+
indices << TableYielder.build_index(
|
68
|
+
columns,
|
69
|
+
table_name: table_name,
|
70
|
+
**index_options
|
105
71
|
)
|
106
72
|
end
|
107
73
|
|
@@ -109,36 +75,15 @@ module DbSchema
|
|
109
75
|
checks << Definitions::CheckConstraint.new(name: name, condition: condition)
|
110
76
|
end
|
111
77
|
|
112
|
-
def foreign_key(*fkey_fields,
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
foreign_keys << Definitions::ForeignKey.new(
|
120
|
-
name: fkey_name,
|
121
|
-
fields: fkey_fields,
|
122
|
-
table: referenced_table,
|
123
|
-
keys: referenced_keys,
|
124
|
-
on_delete: on_delete,
|
125
|
-
on_update: on_update,
|
126
|
-
deferrable: deferrable
|
127
|
-
)
|
128
|
-
else
|
129
|
-
# :table
|
130
|
-
foreign_keys << Definitions::ForeignKey.new(
|
131
|
-
name: fkey_name,
|
132
|
-
fields: fkey_fields,
|
133
|
-
table: references,
|
134
|
-
on_delete: on_delete,
|
135
|
-
on_update: on_update,
|
136
|
-
deferrable: deferrable
|
137
|
-
)
|
138
|
-
end
|
78
|
+
def foreign_key(*fkey_fields, **fkey_options)
|
79
|
+
foreign_keys << TableYielder.build_foreign_key(
|
80
|
+
fkey_fields,
|
81
|
+
table_name: table_name,
|
82
|
+
**fkey_options
|
83
|
+
)
|
139
84
|
end
|
140
85
|
|
141
|
-
def field(name, type,
|
86
|
+
def field(name, type, unique: false, index: false, references: nil, check: nil, **options)
|
142
87
|
fields << Definitions::Field.build(name, type, options)
|
143
88
|
|
144
89
|
if unique
|
@@ -171,6 +116,81 @@ module DbSchema
|
|
171
116
|
def foreign_keys
|
172
117
|
@foreign_keys ||= []
|
173
118
|
end
|
119
|
+
|
120
|
+
class << self
|
121
|
+
def build_index(columns, table_name:, name: nil, unique: false, using: :btree, where: nil, **ordered_fields)
|
122
|
+
if columns.last.is_a?(Hash)
|
123
|
+
*ascending_columns, ordered_expressions = columns
|
124
|
+
else
|
125
|
+
ascending_columns = columns
|
126
|
+
ordered_expressions = {}
|
127
|
+
end
|
128
|
+
|
129
|
+
columns_data = ascending_columns.each_with_object({}) do |column_name, columns|
|
130
|
+
columns[column_name] = :asc
|
131
|
+
end.merge(ordered_fields).merge(ordered_expressions)
|
132
|
+
|
133
|
+
index_columns = columns_data.map do |column_name, column_order_options|
|
134
|
+
options = case column_order_options
|
135
|
+
when :asc
|
136
|
+
{}
|
137
|
+
when :desc
|
138
|
+
{ order: :desc }
|
139
|
+
when :asc_nulls_first
|
140
|
+
{ nulls: :first }
|
141
|
+
when :desc_nulls_last
|
142
|
+
{ order: :desc, nulls: :last }
|
143
|
+
else
|
144
|
+
raise ArgumentError, 'Only :asc, :desc, :asc_nulls_first and :desc_nulls_last options are supported.'
|
145
|
+
end
|
146
|
+
|
147
|
+
if column_name.is_a?(String)
|
148
|
+
Definitions::Index::Expression.new(column_name, **options)
|
149
|
+
else
|
150
|
+
Definitions::Index::TableField.new(column_name, **options)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
index_name = name || "#{table_name}_#{index_columns.map(&:index_name_segment).join('_')}_index"
|
155
|
+
|
156
|
+
Definitions::Index.new(
|
157
|
+
name: index_name,
|
158
|
+
columns: index_columns,
|
159
|
+
unique: unique,
|
160
|
+
type: using,
|
161
|
+
condition: where
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_foreign_key(fkey_fields, table_name:, references:, name: nil, on_update: :no_action, on_delete: :no_action, deferrable: false)
|
166
|
+
fkey_name = name || :"#{table_name}_#{fkey_fields.first}_fkey"
|
167
|
+
|
168
|
+
if references.is_a?(Array)
|
169
|
+
# [:table, :field]
|
170
|
+
referenced_table, *referenced_keys = references
|
171
|
+
|
172
|
+
Definitions::ForeignKey.new(
|
173
|
+
name: fkey_name,
|
174
|
+
fields: fkey_fields,
|
175
|
+
table: referenced_table,
|
176
|
+
keys: referenced_keys,
|
177
|
+
on_delete: on_delete,
|
178
|
+
on_update: on_update,
|
179
|
+
deferrable: deferrable
|
180
|
+
)
|
181
|
+
else
|
182
|
+
# :table
|
183
|
+
Definitions::ForeignKey.new(
|
184
|
+
name: fkey_name,
|
185
|
+
fields: fkey_fields,
|
186
|
+
table: references,
|
187
|
+
on_delete: on_delete,
|
188
|
+
on_update: on_update,
|
189
|
+
deferrable: deferrable
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
174
194
|
end
|
175
195
|
end
|
176
196
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DbSchema
|
2
|
+
class DSL
|
3
|
+
class Migration
|
4
|
+
attr_reader :migration
|
5
|
+
|
6
|
+
def initialize(name, block)
|
7
|
+
@migration = DbSchema::Migration.new(name)
|
8
|
+
block.call(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply_if(&block)
|
12
|
+
migration.conditions[:apply] << block
|
13
|
+
end
|
14
|
+
|
15
|
+
def skip_if(&block)
|
16
|
+
migration.conditions[:skip] << block
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(&block)
|
20
|
+
migration.body = block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|