dynamic_migrations 3.8.3 → 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: '08737568ccf7605e0a4d404063b7e1708e67c0ed96ab9f091bb72db627c0d50b'
4
- data.tar.gz: 404f7bd4816def6c379a9277ec9c06ad4f58d188d3911f4ca9e9c1a38cf301f8
3
+ metadata.gz: 0237e61916864ca25f8f7ba1c8882ee7cd8ba687eb2ead948f7dc602cc11e58d
4
+ data.tar.gz: 8e883bcf30a9b2bf549b7e52062e1cd0576aecaedbe24eb20314522b7e6a6ac8
5
5
  SHA512:
6
- metadata.gz: a403e1da441c1625e89034f07141d48c15fa07953404638795c2c11d58c79e26468f7b4b9070a37d18d6d62a91f069950b4d9ed824c6404071cee59fd3627d35
7
- data.tar.gz: 3df74c476b5d84f8b98f774a69223934823670201649bf35f3aa5a6620738c0fba046bb56ac2e8139364a39ac5a6c4cbdcea547b2cf9625bf45f570261038b80
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 = 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
- 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.3"
4
+ VERSION = "3.8.5"
5
5
  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
 
@@ -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.3
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