dynamic_migrations 3.8.4 → 3.8.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a9d25190ae4b67f3154feae64f9aa7a11d6b6effc9a6422cce43cde28cbcc68
4
- data.tar.gz: 6a7a23a1d7793155f383899ec83f3477ec18581cf94a762a0729466e6d791ac4
3
+ metadata.gz: 0237e61916864ca25f8f7ba1c8882ee7cd8ba687eb2ead948f7dc602cc11e58d
4
+ data.tar.gz: 8e883bcf30a9b2bf549b7e52062e1cd0576aecaedbe24eb20314522b7e6a6ac8
5
5
  SHA512:
6
- metadata.gz: 84c9ed288329dbb5cc687335820feb293dd4aced564d9ebd8c65e0a84ab12368f8a2b774bef93af9fb9c845fb889eb8daadb9f8c60229e00cc8a78ce768ff3cd
7
- data.tar.gz: e2b639cc38df40ed02de870e97707a71a1305819ce3eeac3d61384cdda75c764a3793616149187bf2eed3a40443a3c30f2b19d32ca3a13cf1da2a5f010e01571
6
+ metadata.gz: c026e79bd2af5e73e7cc994fa0a3a28ed480c99f79e151377ae1f01a4953696875d9b3b3df8e53e8e4b6d0e463cadb784ceeed49e0e37b6b6fe9929261f030c5
7
+ data.tar.gz: 4de30c0cdc9586519da3790fd073eaa4e039214fff6448a064b1fc603a694f3264885ffa3258f542f588e22523173eec55b26c9596753d03a72bbebc5145e7a6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## [3.8.4](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.3...v3.8.4) (2023-10-06)
4
12
 
5
13
 
@@ -16,6 +16,9 @@ module DynamicMigrations
16
16
  class ValueAlreadyExistsError < StandardError
17
17
  end
18
18
 
19
+ class ValueMustBeStringError < StandardError
20
+ end
21
+
19
22
  attr_reader :schema
20
23
  attr_reader :name
21
24
  attr_reader :values
@@ -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
- raise ColumnDoesNotExistError, name unless has_column? name
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(DuplicateColumnError, "Column #{name} already exists")
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 check constraint directly from the database
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 and constraint
219
- connection.exec(<<~SQL)
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
- result = table.schema.database.with_connection do |connection|
132
- # wrapped in a transaction just in case something here fails, because
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
- # create the temp table and add the expected columns and constraint
135
+ temp_enums = table.create_temp_table(connection, "validation_normalized_check_clause_temp_table")
136
+
137
137
  connection.exec(<<~SQL)
138
- CREATE TEMP TABLE validation_normalized_check_clause_temp_table (
139
- #{table.columns.map { |column| '"' + column.name.to_s + '" ' + column.temp_table_data_type.to_s }.join(", ")},
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 temp table and close the transaction
159
+ # delete the table and any temporary enums
162
160
  connection.exec("ROLLBACK")
163
161
 
164
- rows.first
165
- end
162
+ check_clause_result = rows.first["check_clause"]
163
+ column_names_string = rows.first["column_names"]
166
164
 
167
- if result["check_clause"].nil?
168
- raise UnnormalizableCheckClauseError, "Failed to nomalize check clause `#{check_clause}`"
169
- end
165
+ if check_clause_result.nil?
166
+ raise UnnormalizableCheckClauseError, "Failed to nomalize check clause `#{check_clause_result}`"
167
+ end
170
168
 
171
- # extract the check clause from the result "CHECK(%check_clause%)"
172
- matches = result["check_clause"].match(/\ACHECK \((?<inner_clause>.*)\)\z/)
173
- if matches.nil?
174
- raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{result["check_clause"]}"
175
- end
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
- normalized_column_names = result["column_names"].gsub(/\A\{/, "").gsub(/\}\Z/, "").split(",").map { |column_name| column_name.to_sym }
182
+ column_names_result = column_names_string.gsub(/\A\{/, "").gsub(/\}\Z/, "").split(",").map { |column_name| column_name.to_sym }
178
183
 
179
- # return the normalized check clause
180
- {
181
- check_clause: matches[:inner_clause],
182
- column_names: normalized_column_names
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamicMigrations
4
- VERSION = "3.8.4"
4
+ VERSION = "3.8.5"
5
5
  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
 
@@ -15,6 +15,7 @@ module DynamicMigrations
15
15
 
16
16
  # these come from the table object (which this module is included into)
17
17
  def source: -> database_or_configuration
18
+ def name: -> String
18
19
 
19
20
  class ColumnDoesNotExistError < StandardError
20
21
  end
@@ -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
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-06 00:00:00.000000000 Z
11
+ date: 2023-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg