dynamic_migrations 3.8.4 → 3.8.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/enum.rb +3 -0
- 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 +19 -8
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +38 -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/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: 00405e194209e083433ebb55aa305922a16e24290ead4f240420602c9465bc90
|
4
|
+
data.tar.gz: 57b7136d7eb1f7efc5382101cf24a4dc5ed4852e66a5f67faf368294e05632c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc594cb118caa67c0e9982f839c762e074b789b083328a1b69b3261297bd34055a53739f51cabb6f6e5685ea76f80eb4574b45649fd202bf213a579713842fac
|
7
|
+
data.tar.gz: 8d42ee7274c26e25086eae4e9c2646f958f930a18fd03eb9c6262f734cf55af380499763e3508a05156a6f1baad4c7bc30007e9d242674d9594bf83e75a7bf1f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.8.6](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.5...v3.8.6) (2023-10-08)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* replacing enum casts with the temporary enums when normalizing check clause and action conditions ([0cb67fc](https://github.com/craigulliott/dynamic_migrations/commit/0cb67fcff4d7833ec1dd60453526b1bc3dbe45ee))
|
9
|
+
|
10
|
+
## [3.8.5](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.4...v3.8.5) (2023-10-08)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* missing error class ([761e478](https://github.com/craigulliott/dynamic_migrations/commit/761e478fbd9234014751d853da3dde35d2043a7e))
|
16
|
+
* normalized validation check constraint and trigger action condition now work with enum columns ([a657dd6](https://github.com/craigulliott/dynamic_migrations/commit/a657dd66f3bb3e140672db89224b0b562bb91ef0))
|
17
|
+
|
3
18
|
## [3.8.4](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.3...v3.8.4) (2023-10-06)
|
4
19
|
|
5
20
|
|
@@ -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)
|
@@ -228,6 +224,13 @@ module DynamicMigrations
|
|
228
224
|
$$ BEGIN END $$;
|
229
225
|
SQL
|
230
226
|
|
227
|
+
temp_action_condition = action_condition
|
228
|
+
# string replace any real enum names with their temp enum names
|
229
|
+
temp_enums.each do |temp_enum_name, enum|
|
230
|
+
temp_action_condition.gsub!("::#{enum.name}", "::#{temp_enum_name}")
|
231
|
+
temp_action_condition.gsub!("::#{enum.full_name}", "::#{temp_enum_name}")
|
232
|
+
end
|
233
|
+
|
231
234
|
# create a temporary trigger, from which we will fetch the normalized action condition
|
232
235
|
connection.exec(<<~SQL)
|
233
236
|
CREATE TRIGGER trigger_normalized_action_condition_temp_trigger
|
@@ -253,7 +256,15 @@ module DynamicMigrations
|
|
253
256
|
connection.exec("ROLLBACK")
|
254
257
|
|
255
258
|
# return the normalized action condition
|
256
|
-
rows.first["action_condition"]
|
259
|
+
action_condition_result = rows.first["action_condition"]
|
260
|
+
|
261
|
+
# string replace any enum names with their real enum names
|
262
|
+
temp_enums.each do |temp_enum_name, enum|
|
263
|
+
real_enum_name = (enum.schema == table.schema) ? enum.name : enum.full_name
|
264
|
+
action_condition_result.gsub!("::#{temp_enum_name}", "::#{real_enum_name}")
|
265
|
+
end
|
266
|
+
|
267
|
+
action_condition_result
|
257
268
|
end
|
258
269
|
|
259
270
|
if ac.nil?
|
@@ -128,17 +128,22 @@ 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
|
+
temp_check_clause = check_clause
|
138
|
+
# string replace any real enum names with their temp enum names
|
139
|
+
temp_enums.each do |temp_enum_name, enum|
|
140
|
+
temp_check_clause.gsub!("::#{enum.name}", "::#{temp_enum_name}")
|
141
|
+
temp_check_clause.gsub!("::#{enum.full_name}", "::#{temp_enum_name}")
|
142
|
+
end
|
143
|
+
|
137
144
|
connection.exec(<<~SQL)
|
138
|
-
|
139
|
-
#{
|
140
|
-
CONSTRAINT #{name} CHECK (#{check_clause})
|
141
|
-
);
|
145
|
+
ALTER TABLE validation_normalized_check_clause_temp_table
|
146
|
+
ADD CONSTRAINT #{name} CHECK (#{temp_check_clause})
|
142
147
|
SQL
|
143
148
|
|
144
149
|
# get the normalized version of the constraint
|
@@ -158,29 +163,37 @@ module DynamicMigrations
|
|
158
163
|
GROUP BY pg_constraint.oid;
|
159
164
|
SQL
|
160
165
|
|
161
|
-
# delete the
|
166
|
+
# delete the table and any temporary enums
|
162
167
|
connection.exec("ROLLBACK")
|
163
168
|
|
164
|
-
rows.first
|
165
|
-
|
169
|
+
check_clause_result = rows.first["check_clause"]
|
170
|
+
column_names_string = rows.first["column_names"]
|
166
171
|
|
167
|
-
|
168
|
-
|
169
|
-
|
172
|
+
if check_clause_result.nil?
|
173
|
+
raise UnnormalizableCheckClauseError, "Failed to nomalize check clause `#{check_clause_result}`"
|
174
|
+
end
|
170
175
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
+
# extract the check clause from the result "CHECK(%check_clause%)"
|
177
|
+
matches = check_clause_result.match(/\ACHECK \((?<inner_clause>.*)\)\z/)
|
178
|
+
if matches.nil?
|
179
|
+
raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{check_clause_result}"
|
180
|
+
end
|
181
|
+
check_clause_result = matches[:inner_clause]
|
176
182
|
|
177
|
-
|
183
|
+
# string replace any enum names with their real enum names
|
184
|
+
temp_enums.each do |temp_enum_name, enum|
|
185
|
+
real_enum_name = (enum.schema == table.schema) ? enum.name : enum.full_name
|
186
|
+
check_clause_result.gsub!("::#{temp_enum_name}", "::#{real_enum_name}")
|
187
|
+
end
|
178
188
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
189
|
+
column_names_result = column_names_string.gsub(/\A\{/, "").gsub(/\}\Z/, "").split(",").map { |column_name| column_name.to_sym }
|
190
|
+
|
191
|
+
# return the normalized check clause
|
192
|
+
{
|
193
|
+
check_clause: check_clause_result,
|
194
|
+
column_names: column_names_result
|
195
|
+
}
|
196
|
+
end
|
184
197
|
end
|
185
198
|
|
186
199
|
# 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
|
@@ -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.6
|
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
|