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