dynamic_migrations 3.8.4 → 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 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