db_schema 0.2.5 → 0.3.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,19 @@
1
+ module DbSchema
2
+ module Definitions
3
+ class Index
4
+ class TableField < Column
5
+ def expression?
6
+ false
7
+ end
8
+
9
+ def index_name_segment
10
+ name
11
+ end
12
+
13
+ def to_sequel
14
+ 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
@@ -1,16 +1,14 @@
1
+ require_relative 'dsl/migration'
2
+
1
3
  module DbSchema
2
4
  class DSL
3
- attr_reader :block
5
+ attr_reader :schema, :migrations
4
6
 
5
7
  def initialize(block)
6
- @block = block
7
- @schema = Definitions::Schema.new
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, name: nil, unique: false, using: :btree, where: nil, **ordered_fields)
65
- if columns.last.is_a?(Hash)
66
- *ascending_columns, ordered_expressions = columns
67
- else
68
- ascending_columns = columns
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, references:, name: nil, on_update: :no_action, on_delete: :no_action, deferrable: false)
113
- fkey_name = name || :"#{table_name}_#{fkey_fields.first}_fkey"
114
-
115
- if references.is_a?(Array)
116
- # [:table, :field]
117
- referenced_table, *referenced_keys = references
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, custom: false, unique: false, index: false, references: nil, check: nil, **options)
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