dynamic_migrations 3.8.3 → 3.8.5
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/CHANGELOG.md +15 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/enum.rb +22 -3
- data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +0 -13
- data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +4 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +12 -8
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +31 -25
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +50 -0
- data/lib/dynamic_migrations/version.rb +1 -1
- data/sig/dynamic_migrations/postgres/server/database/schema/enum.rbs +7 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/column.rbs +0 -1
- data/sig/dynamic_migrations/postgres/server/database/schema/table/columns.rbs +1 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0237e61916864ca25f8f7ba1c8882ee7cd8ba687eb2ead948f7dc602cc11e58d
|
4
|
+
data.tar.gz: 8e883bcf30a9b2bf549b7e52062e1cd0576aecaedbe24eb20314522b7e6a6ac8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c026e79bd2af5e73e7cc994fa0a3a28ed480c99f79e151377ae1f01a4953696875d9b3b3df8e53e8e4b6d0e463cadb784ceeed49e0e37b6b6fe9929261f030c5
|
7
|
+
data.tar.gz: 4de30c0cdc9586519da3790fd073eaa4e039214fff6448a064b1fc603a694f3264885ffa3258f542f588e22523173eec55b26c9596753d03a72bbebc5145e7a6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.8.5](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.4...v3.8.5) (2023-10-08)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* missing error class ([761e478](https://github.com/craigulliott/dynamic_migrations/commit/761e478fbd9234014751d853da3dde35d2043a7e))
|
9
|
+
* normalized validation check constraint and trigger action condition now work with enum columns ([a657dd6](https://github.com/craigulliott/dynamic_migrations/commit/a657dd66f3bb3e140672db89224b0b562bb91ef0))
|
10
|
+
|
11
|
+
## [3.8.4](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.3...v3.8.4) (2023-10-06)
|
12
|
+
|
13
|
+
|
14
|
+
### Bug Fixes
|
15
|
+
|
16
|
+
* asserting that enum values must be unique strings, and adding ability to add additional enum values ([5cf6093](https://github.com/craigulliott/dynamic_migrations/commit/5cf6093eed96387042f70987c3999b40e4041b76))
|
17
|
+
|
3
18
|
## [3.8.3](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.2...v3.8.3) (2023-10-06)
|
4
19
|
|
5
20
|
|
@@ -13,6 +13,12 @@ module DynamicMigrations
|
|
13
13
|
class ExpectedValuesError < StandardError
|
14
14
|
end
|
15
15
|
|
16
|
+
class ValueAlreadyExistsError < StandardError
|
17
|
+
end
|
18
|
+
|
19
|
+
class ValueMustBeStringError < StandardError
|
20
|
+
end
|
21
|
+
|
16
22
|
attr_reader :schema
|
17
23
|
attr_reader :name
|
18
24
|
attr_reader :values
|
@@ -25,8 +31,6 @@ module DynamicMigrations
|
|
25
31
|
|
26
32
|
@columns = []
|
27
33
|
|
28
|
-
@values = []
|
29
|
-
|
30
34
|
raise ExpectedSchemaError, schema unless schema.is_a? Schema
|
31
35
|
@schema = schema
|
32
36
|
|
@@ -36,7 +40,10 @@ module DynamicMigrations
|
|
36
40
|
unless values.is_a?(Array) && values.count > 0
|
37
41
|
raise ExpectedValuesError, "Values are required for enums"
|
38
42
|
end
|
39
|
-
@values =
|
43
|
+
@values = []
|
44
|
+
values.each do |value|
|
45
|
+
add_value value
|
46
|
+
end
|
40
47
|
|
41
48
|
unless description.nil?
|
42
49
|
raise ExpectedStringError, description unless description.is_a? String
|
@@ -45,6 +52,18 @@ module DynamicMigrations
|
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
55
|
+
def add_value value
|
56
|
+
unless value.is_a? String
|
57
|
+
raise ValueMustBeStringError, "Value `#{value}` must be a string"
|
58
|
+
end
|
59
|
+
|
60
|
+
if @values.include? value
|
61
|
+
raise ValueAlreadyExistsError, "Value `#{value}` already exists in enum `#{name}`"
|
62
|
+
end
|
63
|
+
|
64
|
+
@values << value
|
65
|
+
end
|
66
|
+
|
48
67
|
# returns true if this enum has a description, otehrwise false
|
49
68
|
def has_description?
|
50
69
|
!@description.nil?
|
@@ -82,19 +82,6 @@ module DynamicMigrations
|
|
82
82
|
def base_data_type
|
83
83
|
array? ? @data_type[0..-3]&.to_sym : @data_type
|
84
84
|
end
|
85
|
-
|
86
|
-
# sometimes this system makes temporary tables in order to fetch the normalized
|
87
|
-
# version of constraint check clauses, function definitions or trigger action conditions
|
88
|
-
# because certain data types might not yet exist, we need to use alternative types
|
89
|
-
def temp_table_data_type
|
90
|
-
if enum
|
91
|
-
:text
|
92
|
-
elsif @data_type == :citext || @data_type == :"citext[]"
|
93
|
-
:text
|
94
|
-
else
|
95
|
-
@data_type
|
96
|
-
end
|
97
|
-
end
|
98
85
|
end
|
99
86
|
end
|
100
87
|
end
|
@@ -18,7 +18,9 @@ module DynamicMigrations
|
|
18
18
|
# error if the column does not exist
|
19
19
|
def column name
|
20
20
|
raise ExpectedSymbolError, name unless name.is_a? Symbol
|
21
|
-
|
21
|
+
unless has_column? name
|
22
|
+
raise ColumnDoesNotExistError, "column `#{name}` does not exist within table `#{self.name}`"
|
23
|
+
end
|
22
24
|
@columns[name]
|
23
25
|
end
|
24
26
|
|
@@ -40,7 +42,7 @@ module DynamicMigrations
|
|
40
42
|
# adds a new column to this table, and returns it
|
41
43
|
def add_column name, data_type, **column_options
|
42
44
|
if has_column? name
|
43
|
-
raise
|
45
|
+
raise DuplicateColumnError, "Column `#{name}` already exists"
|
44
46
|
end
|
45
47
|
included_target = self
|
46
48
|
if included_target.is_a? Table
|
@@ -202,7 +202,7 @@ module DynamicMigrations
|
|
202
202
|
end
|
203
203
|
|
204
204
|
# create a temporary table in postgres to represent this trigger and fetch
|
205
|
-
# the actual normalized
|
205
|
+
# the actual normalized action_condition directly from the database
|
206
206
|
def normalized_action_condition
|
207
207
|
if action_condition.nil?
|
208
208
|
nil
|
@@ -215,12 +215,8 @@ module DynamicMigrations
|
|
215
215
|
# we don't want the function, temporary table or trigger to be persisted
|
216
216
|
connection.exec("BEGIN")
|
217
217
|
|
218
|
-
# create the temp table and add the expected columns
|
219
|
-
|
220
|
-
CREATE TEMP TABLE trigger_normalized_action_condition_temp_table (
|
221
|
-
#{table.columns.map { |column| '"' + column.name.to_s + '" ' + column.temp_table_data_type.to_s }.join(", ")}
|
222
|
-
);
|
223
|
-
SQL
|
218
|
+
# create the temp table and add the expected columns
|
219
|
+
temp_enums = table.create_temp_table(connection, "trigger_normalized_action_condition_temp_table")
|
224
220
|
|
225
221
|
# create a temporary function to trigger (triggers require a function)
|
226
222
|
connection.exec(<<~SQL)
|
@@ -253,7 +249,15 @@ module DynamicMigrations
|
|
253
249
|
connection.exec("ROLLBACK")
|
254
250
|
|
255
251
|
# return the normalized action condition
|
256
|
-
rows.first["action_condition"]
|
252
|
+
action_condition_result = rows.first["action_condition"]
|
253
|
+
|
254
|
+
# string replace any enum names with their real enum names
|
255
|
+
temp_enums.each do |temp_enum_name, enum|
|
256
|
+
real_enum_name = (enum.schema == table.schema) ? enum.name : enum.full_name
|
257
|
+
action_condition_result.gsub!("::#{temp_enum_name}", "::#{real_enum_name}")
|
258
|
+
end
|
259
|
+
|
260
|
+
action_condition_result
|
257
261
|
end
|
258
262
|
|
259
263
|
if ac.nil?
|
@@ -128,17 +128,15 @@ module DynamicMigrations
|
|
128
128
|
if table.columns.empty?
|
129
129
|
raise ExpectedTableColumnsError, "Can not normalize check clause or validation columnns because the table has no columns"
|
130
130
|
end
|
131
|
-
|
132
|
-
# wrapped in a transaction
|
133
|
-
# we don't want the temporary table to be persisted
|
131
|
+
table.schema.database.with_connection do |connection|
|
132
|
+
# wrapped in a transaction so we can rollback the creation of the table and any enums
|
134
133
|
connection.exec("BEGIN")
|
135
134
|
|
136
|
-
|
135
|
+
temp_enums = table.create_temp_table(connection, "validation_normalized_check_clause_temp_table")
|
136
|
+
|
137
137
|
connection.exec(<<~SQL)
|
138
|
-
|
139
|
-
#{
|
140
|
-
CONSTRAINT #{name} CHECK (#{check_clause})
|
141
|
-
);
|
138
|
+
ALTER TABLE validation_normalized_check_clause_temp_table
|
139
|
+
ADD CONSTRAINT #{name} CHECK (#{check_clause})
|
142
140
|
SQL
|
143
141
|
|
144
142
|
# get the normalized version of the constraint
|
@@ -158,29 +156,37 @@ module DynamicMigrations
|
|
158
156
|
GROUP BY pg_constraint.oid;
|
159
157
|
SQL
|
160
158
|
|
161
|
-
# delete the
|
159
|
+
# delete the table and any temporary enums
|
162
160
|
connection.exec("ROLLBACK")
|
163
161
|
|
164
|
-
rows.first
|
165
|
-
|
162
|
+
check_clause_result = rows.first["check_clause"]
|
163
|
+
column_names_string = rows.first["column_names"]
|
166
164
|
|
167
|
-
|
168
|
-
|
169
|
-
|
165
|
+
if check_clause_result.nil?
|
166
|
+
raise UnnormalizableCheckClauseError, "Failed to nomalize check clause `#{check_clause_result}`"
|
167
|
+
end
|
170
168
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
169
|
+
# extract the check clause from the result "CHECK(%check_clause%)"
|
170
|
+
matches = check_clause_result.match(/\ACHECK \((?<inner_clause>.*)\)\z/)
|
171
|
+
if matches.nil?
|
172
|
+
raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{check_clause_result}"
|
173
|
+
end
|
174
|
+
check_clause_result = matches[:inner_clause]
|
175
|
+
|
176
|
+
# string replace any enum names with their real enum names
|
177
|
+
temp_enums.each do |temp_enum_name, enum|
|
178
|
+
real_enum_name = (enum.schema == table.schema) ? enum.name : enum.full_name
|
179
|
+
check_clause_result.gsub!("::#{temp_enum_name}", "::#{real_enum_name}")
|
180
|
+
end
|
176
181
|
|
177
|
-
|
182
|
+
column_names_result = column_names_string.gsub(/\A\{/, "").gsub(/\}\Z/, "").split(",").map { |column_name| column_name.to_sym }
|
178
183
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
+
# return the normalized check clause
|
185
|
+
{
|
186
|
+
check_clause: check_clause_result,
|
187
|
+
column_names: column_names_result
|
188
|
+
}
|
189
|
+
end
|
184
190
|
end
|
185
191
|
|
186
192
|
# used internally to set the columns from this objects initialize method
|
@@ -78,6 +78,56 @@ module DynamicMigrations
|
|
78
78
|
end
|
79
79
|
pk
|
80
80
|
end
|
81
|
+
|
82
|
+
# Used within validations and triggers when normalizing check clauses and other
|
83
|
+
# SQL statements which require a table to process the SQL.
|
84
|
+
#
|
85
|
+
# This method returns a hash representation of any temporary enums created to satisfy
|
86
|
+
# the columns in the table
|
87
|
+
def create_temp_table connection, temp_table_name
|
88
|
+
# create the temp table and add the expected columns
|
89
|
+
|
90
|
+
# if any of the columns are enums, then we need to create a temporary enum type for them.
|
91
|
+
# we cant just create temporary columns as text fields because postgres may automatically
|
92
|
+
# add casts to those columns, which would result in a different normalized check clause
|
93
|
+
temp_enums = {}
|
94
|
+
|
95
|
+
# an array of sql column definitions for within the create table SQL
|
96
|
+
# we process each column individually like this so that we can create temporary enums for
|
97
|
+
# any enum columns
|
98
|
+
columns_sql = columns.map do |column|
|
99
|
+
enum = column.enum
|
100
|
+
if enum
|
101
|
+
# create the temporary enum type
|
102
|
+
temp_enum_name = "#{temp_table_name}_enum_#{temp_enums.count}"
|
103
|
+
connection.exec(<<~SQL)
|
104
|
+
CREATE TYPE #{temp_enum_name} as ENUM ('#{enum.values.join("','")}');
|
105
|
+
SQL
|
106
|
+
temp_enums[temp_enum_name] = enum
|
107
|
+
|
108
|
+
# return the column definition used within the CREATE TABLE SQL
|
109
|
+
data_type = column.array? ? "#{temp_enum_name}[]" : temp_enum_name
|
110
|
+
"\"#{column.name}\" #{data_type}"
|
111
|
+
|
112
|
+
else
|
113
|
+
# return the column definition used within the CREATE TABLE SQL
|
114
|
+
"\"#{column.name}\" #{column.data_type}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# in case any of the columnbs are citext columns
|
119
|
+
connection.exec("CREATE EXTENSION IF NOT EXISTS citext;")
|
120
|
+
|
121
|
+
# note, this is not actually a TEMP TABLE, it is created within a transaction
|
122
|
+
# and rolled back.
|
123
|
+
connection.exec(<<~SQL)
|
124
|
+
CREATE TABLE #{temp_table_name} (
|
125
|
+
#{columns_sql.join(", ")}
|
126
|
+
);
|
127
|
+
SQL
|
128
|
+
|
129
|
+
temp_enums
|
130
|
+
end
|
81
131
|
end
|
82
132
|
end
|
83
133
|
end
|
@@ -16,6 +16,7 @@ module DynamicMigrations
|
|
16
16
|
def full_name: -> Symbol
|
17
17
|
def has_description?: -> bool
|
18
18
|
def add_column: (Schema::Table::Column column) -> void
|
19
|
+
def add_value: (String value) -> void
|
19
20
|
def differences_descriptions: (Enum other_enum) -> Array[String]
|
20
21
|
|
21
22
|
class ExpectedSchemaError < StandardError
|
@@ -23,6 +24,12 @@ module DynamicMigrations
|
|
23
24
|
|
24
25
|
class ExpectedValuesError < StandardError
|
25
26
|
end
|
27
|
+
|
28
|
+
class ValueAlreadyExistsError < StandardError
|
29
|
+
end
|
30
|
+
|
31
|
+
class ValueMustBeStringError < StandardError
|
32
|
+
end
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -19,7 +19,6 @@ module DynamicMigrations
|
|
19
19
|
def has_description?: -> bool
|
20
20
|
def array?: -> bool
|
21
21
|
def enum?: -> bool
|
22
|
-
def temp_table_data_type: -> Symbol
|
23
22
|
# untyped because we cant specify this logic in rbs yet (compiler is concerned this might be nil)
|
24
23
|
def base_data_type: -> untyped
|
25
24
|
|
@@ -22,7 +22,7 @@ module DynamicMigrations
|
|
22
22
|
def add_primary_key: (Symbol name, Array[Symbol] column_names, **untyped) -> untyped
|
23
23
|
def has_primary_key?: -> bool
|
24
24
|
def primary_key: -> PrimaryKey
|
25
|
-
|
25
|
+
def create_temp_table: (PG::Connection connection, String table_name) -> Hash[String, Enum]
|
26
26
|
class ExpectedSchemaError < StandardError
|
27
27
|
end
|
28
28
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamic_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.8.
|
4
|
+
version: 3.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Craig Ulliott
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|