dynamic_migrations 3.6.15 → 3.7.0

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/lib/dynamic_migrations/active_record/migrators/primary_key.rb +21 -0
  4. data/lib/dynamic_migrations/active_record/migrators.rb +1 -0
  5. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +32 -0
  6. data/lib/dynamic_migrations/postgres/generator/table.rb +20 -4
  7. data/lib/dynamic_migrations/postgres/generator/table_migration.rb +1 -1
  8. data/lib/dynamic_migrations/postgres/generator/validation.rb +0 -4
  9. data/lib/dynamic_migrations/postgres/generator/validation_template_base.rb +5 -3
  10. data/lib/dynamic_migrations/postgres/generator.rb +4 -5
  11. data/lib/dynamic_migrations/postgres/server/database/connection.rb +3 -1
  12. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +1 -1
  13. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +2 -2
  14. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +2 -2
  15. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +2 -2
  16. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +2 -2
  17. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +2 -2
  18. data/lib/dynamic_migrations/postgres/server/database/differences.rb +3 -3
  19. data/lib/dynamic_migrations/postgres/server/database/enums_loader.rb +1 -1
  20. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +8 -0
  21. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +51 -2
  22. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +29 -2
  23. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +4 -7
  24. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rb +5 -0
  25. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +72 -6
  26. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +95 -12
  27. data/lib/dynamic_migrations/postgres/server/database/schema/table/validations.rb +1 -1
  28. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +2 -0
  29. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +9 -1
  30. data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +4 -3
  31. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +42 -29
  32. data/lib/dynamic_migrations/postgres/server/database.rb +6 -0
  33. data/lib/dynamic_migrations/version.rb +1 -1
  34. data/lib/dynamic_migrations.rb +1 -0
  35. data/sig/dynamic_migrations/active_record/migrators/primary_key.rbs +18 -0
  36. data/sig/dynamic_migrations/postgres/generator/primary_key.rbs +2 -0
  37. data/sig/dynamic_migrations/postgres/generator/table.rbs +2 -0
  38. data/sig/dynamic_migrations/postgres/generator/validation_template_base.rbs +7 -7
  39. data/sig/dynamic_migrations/postgres/generator.rbs +1 -1
  40. data/sig/dynamic_migrations/postgres/server/database/connection.rbs +1 -1
  41. data/sig/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rbs +1 -0
  42. data/sig/dynamic_migrations/postgres/server/database/schema/enum.rbs +2 -2
  43. data/sig/dynamic_migrations/postgres/server/database/schema/enums.rbs +1 -1
  44. data/sig/dynamic_migrations/postgres/server/database/schema/function.rbs +9 -0
  45. data/sig/dynamic_migrations/postgres/server/database/schema/table/column.rbs +4 -0
  46. data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rbs +0 -3
  47. data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rbs +1 -0
  48. data/sig/dynamic_migrations/postgres/server/database/schema/table/trigger.rbs +9 -0
  49. data/sig/dynamic_migrations/postgres/server/database/schema/table/validation.rbs +9 -1
  50. data/sig/dynamic_migrations/postgres/server/database/schema/table/validations.rbs +1 -1
  51. data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +1 -0
  52. data/sig/dynamic_migrations/postgres/server/database/structure_loader.rbs +1 -0
  53. data/sig/dynamic_migrations/postgres/server/database/validations_loader.rbs +1 -0
  54. data/sig/dynamic_migrations/postgres/server/database.rbs +1 -0
  55. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b1f731ee879fc2da123605b8cb10c5dee47ff16a065dd043be4ec4424ceba90
4
- data.tar.gz: ee8925da339476c44a54440240d4736b443ae671075d6f9791033052ef542da3
3
+ metadata.gz: 22fd29f59924e820da26858d1cf6caf04b03985ecc2fafd5f49205f5a0c2b34e
4
+ data.tar.gz: a8164cec22385ede8954df04e86c866bdb9d1ae4d420ba9107353f96e1a525e8
5
5
  SHA512:
6
- metadata.gz: 841a16d865fc252e0c7ea85b589d0de86703c1d718ba05fd695852daff12117e6dbbfd7ccb22e7f29659253e8193054dccca635423ccd9ea0dc7875482606cb8
7
- data.tar.gz: 16ffa1cffc44cf979247738466a5884db216042f82b945b4936e0f1beca4d04b4544f71b4912e65a1b2e5581d8ffb5c4b2d288a55198eaf6dfdcad04372b10b6
6
+ metadata.gz: ec4eb2339d1ce110393eae8747c9e2aabee426d58a4e3caca8e01b7dd48ddfe99291ab903c9c7ba2e474a8b509a7185850132c1934f512c8ebe710e763243ba9
7
+ data.tar.gz: 50076bbed1405f301476ac49f0886207f9fde401c47af062fafa4095139cd3287699b8054cac4583b97422180f8fb3be935d94ac87b1ccd071a293972811eec7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.16...v3.7.0) (2023-09-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * providing access to foreign key constraints from both sides of the association ([48dcf1c](https://github.com/craigulliott/dynamic_migrations/commit/48dcf1cd4cdb23bc37da3e47b00f8007b8bc0f8a))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * allowing foreign keys to the same table because they are valid and sometimes useful ([0566384](https://github.com/craigulliott/dynamic_migrations/commit/0566384a4cfeade757d9806059035477f7c41ee2))
14
+ * lazy loading column names for validations when they were configured with a nil value for columns ([439200e](https://github.com/craigulliott/dynamic_migrations/commit/439200efdedf74dddc852bf9bd35e81c1f6b4336))
15
+ * providing convenience method to retrieve columns base data type from array columns ([cd3d9bf](https://github.com/craigulliott/dynamic_migrations/commit/cd3d9bf06682335a890d9b11133d397d7bcd50af))
16
+ * semi colon at the end of function definitions is now optional ([ca9b3aa](https://github.com/craigulliott/dynamic_migrations/commit/ca9b3aa04da23605ff61b2ec14baed927145d2c3))
17
+ * structure loader was not identifying enums properly ([95276d3](https://github.com/craigulliott/dynamic_migrations/commit/95276d33f98f41eca9a9467bb6abeb458a37f16b))
18
+
19
+ ## [3.6.16](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.15...v3.6.16) (2023-09-16)
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * adding convenience method to call all three cache rebuild methods at once ([287a73d](https://github.com/craigulliott/dynamic_migrations/commit/287a73d96c4bfa303150e3c1f65cb514569c03b6))
25
+ * allowing functions and triggers to be in different schemas ([6df3377](https://github.com/craigulliott/dynamic_migrations/commit/6df337780a9a6b6815776a1f65afd3c8325a00fd))
26
+ * always sorting column names on validations so that they match the structure loader ([3e7e646](https://github.com/craigulliott/dynamic_migrations/commit/3e7e646e834140bcce74e357e6e84c3996b9525e))
27
+ * corrected bug where we passed nil as trigger for updating trigger comment ([c7a3677](https://github.com/craigulliott/dynamic_migrations/commit/c7a3677694130125d581f8f68e56865644d5c442))
28
+ * enum values should be strings not symbols ([080e5da](https://github.com/craigulliott/dynamic_migrations/commit/080e5daaeefa1504912e2cd4f3482af82890ee08))
29
+ * fixed bug where constraint was duplicated due to pg check_constraints table allowing duplicates ([471b6f8](https://github.com/craigulliott/dynamic_migrations/commit/471b6f82fc01770e63f657a12d01d18011c69f07))
30
+ * fixing whitespace with create table migrator syntax ([5bf1a7f](https://github.com/craigulliott/dynamic_migrations/commit/5bf1a7f9f337883ca677b1a2015dd0c41a27442c))
31
+ * generating materialized views automatically when refreshing them but they don't yet exist ([471b6f8](https://github.com/craigulliott/dynamic_migrations/commit/471b6f82fc01770e63f657a12d01d18011c69f07))
32
+ * handling default comments via templates ([3e7e646](https://github.com/craigulliott/dynamic_migrations/commit/3e7e646e834140bcce74e357e6e84c3996b9525e))
33
+ * methods to refresh caches because this needs to be performed before and after migrations are generated and run ([ab72670](https://github.com/craigulliott/dynamic_migrations/commit/ab72670e5fc7d379aed86cf72be956cdc39ed620))
34
+ * more strictly validating column types data types which use enums ([3e7e646](https://github.com/craigulliott/dynamic_migrations/commit/3e7e646e834140bcce74e357e6e84c3996b9525e))
35
+ * removed code which was stripping empty lines from migrations, but inadvertently removing empty lines from within SQL statements ([3e7e646](https://github.com/craigulliott/dynamic_migrations/commit/3e7e646e834140bcce74e357e6e84c3996b9525e))
36
+ * updating action order so that its sequential based on the event manipulation type (update, insert etc.) ([3e7e646](https://github.com/craigulliott/dynamic_migrations/commit/3e7e646e834140bcce74e357e6e84c3996b9525e))
37
+ * validations should not end with a semicolon ([9e2cc8e](https://github.com/craigulliott/dynamic_migrations/commit/9e2cc8ebf1c6da8c4741c5257c6567fc9f3668a7))
38
+ * various fixes after running a variety of real migrations from platformer ([301db01](https://github.com/craigulliott/dynamic_migrations/commit/301db01397f1cb16c6c0e08e9e8c69e1f3ec799e))
39
+
3
40
  ## [3.6.15](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.14...v3.6.15) (2023-09-14)
4
41
 
5
42
 
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module PrimaryKey
5
+ # add a comment to the primary_key
6
+ def set_primary_key_comment table_name, primary_key_name, comment
7
+ execute <<~SQL
8
+ COMMENT ON CONSTRAINT #{primary_key_name} ON #{schema_name}.#{table_name} IS #{quote comment};
9
+ SQL
10
+ end
11
+
12
+ # remove a primary_key comment
13
+ def remove_primary_key_comment table_name, primary_key_name
14
+ execute <<~SQL
15
+ COMMENT ON CONSTRAINT #{primary_key_name} ON #{schema_name}.#{table_name} IS NULL;
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -30,6 +30,7 @@ module DynamicMigrations
30
30
  include UniqueConstraint
31
31
  include Trigger
32
32
  include Enum
33
+ include PrimaryKey
33
34
 
34
35
  def self.included(base)
35
36
  base.extend(ClassMethods)
@@ -59,6 +59,38 @@ module DynamicMigrations
59
59
  # return the new fragments (the main reason to return them here is for the specs)
60
60
  [removal_fragment, recreation_fragment]
61
61
  end
62
+
63
+ # add a comment to a primary_key
64
+ def set_primary_key_comment primary_key, code_comment = nil
65
+ description = primary_key.description
66
+
67
+ if description.nil?
68
+ raise MissingDescriptionError, "Missing required description for primary_key `#{primary_key.name}` in table `#{primary_key.table.schema.name}.#{primary_key.table.name}`"
69
+ end
70
+
71
+ add_fragment schema: primary_key.table.schema,
72
+ table: primary_key.table,
73
+ migration_method: :set_primary_key_comment,
74
+ object: primary_key,
75
+ code_comment: code_comment,
76
+ migration: <<~RUBY
77
+ set_primary_key_comment :#{primary_key.table.name}, :#{primary_key.name}, <<~COMMENT
78
+ #{indent description}
79
+ COMMENT
80
+ RUBY
81
+ end
82
+
83
+ # remove the comment from a primary_key
84
+ def remove_primary_key_comment primary_key, code_comment = nil
85
+ add_fragment schema: primary_key.table.schema,
86
+ table: primary_key.table,
87
+ migration_method: :remove_primary_key_comment,
88
+ object: primary_key,
89
+ code_comment: code_comment,
90
+ migration: <<~RUBY
91
+ remove_primary_key_comment :#{primary_key.table.name}, :#{primary_key.name}
92
+ RUBY
93
+ end
62
94
  end
63
95
  end
64
96
  end
@@ -93,10 +93,20 @@ module DynamicMigrations
93
93
  lines = []
94
94
  timestamps = []
95
95
  columns.each do |column|
96
- # skip the :id, as it is handled by the table_options method
97
- next if column.name == :id
98
- # skip the :created_at and :updated_at as they are handled below
96
+ # skip creating the :id column as it is handled by the table_options
97
+ # method, but add the comment if there is one
98
+ if column.name == :id
99
+ unless column.description.nil?
100
+ set_column_comment column
101
+ end
102
+ next
103
+ end
104
+ # skip creating the :created_at and :updated_at column as it is handled
105
+ # by the table_options method, but add the comments
99
106
  if column.name == :created_at || column.name == :updated_at
107
+ unless column.description.nil?
108
+ set_column_comment column
109
+ end
100
110
  timestamps << column.name
101
111
  next
102
112
  end
@@ -115,7 +125,7 @@ module DynamicMigrations
115
125
  if column.description.nil?
116
126
  raise NoTableColumnCommentError, "Refusing to generate create_table migration, no description was provided for `#{column.table.schema.name}`.`#{column.table.name}` column `#{column.name}`"
117
127
  end
118
- options[:comment] = <<~RUBY
128
+ options[:comment] = <<~RUBY.strip
119
129
  <<~COMMENT
120
130
  #{indent column.description}
121
131
  COMMENT
@@ -163,6 +173,12 @@ module DynamicMigrations
163
173
  elsif pk_column_names.count > 1
164
174
  options << "primary_key: [:#{pk_column_names.join(", :")}]"
165
175
  end
176
+
177
+ # if the primary key has a description, then add it seperately
178
+ if table.primary_key.description
179
+ set_primary_key_comment table.primary_key
180
+ end
181
+
166
182
  end
167
183
 
168
184
  options << "comment: table_comment"
@@ -17,7 +17,7 @@ module DynamicMigrations
17
17
  add_structure_template [:remove_table_comment, :set_table_comment], "Tables"
18
18
  add_structure_template [:add_column], "Additional Columns"
19
19
  add_structure_template [:change_column, :remove_column_comment, :set_column_comment], "Update Columns"
20
- add_structure_template [:add_primary_key], "Primary Key"
20
+ add_structure_template [:add_primary_key, :set_primary_key_comment, :remove_primary_key_comment], "Primary Key"
21
21
  add_structure_template [:add_index, :set_index_comment], "Indexes"
22
22
  add_structure_template [:add_foreign_key, :set_foreign_key_constraint_comment, :remove_foreign_key_constraint_comment], "Foreign Keys"
23
23
  add_structure_template [:add_validation, :add_unique_constraint, :set_validation_comment, :remove_validation_comment, :set_unique_constraint_comment, :remove_unique_constraint_comment], "Validations"
@@ -57,10 +57,6 @@ module DynamicMigrations
57
57
  options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
58
58
 
59
59
  validation_sql = validation.check_clause.strip
60
- # ensure that the validation ends with a semicolon
61
- unless validation_sql.end_with? ";"
62
- validation_sql << ";"
63
- end
64
60
 
65
61
  add_fragment schema: validation.table.schema,
66
62
  table: validation.table,
@@ -46,14 +46,16 @@ module DynamicMigrations
46
46
  matches[:value]
47
47
  end
48
48
 
49
- def name_and_description_options_string default_name
49
+ def name_and_description_options_string default_name, default_comment = nil
50
50
  options = {}
51
51
  # we only need to provide a name if it is different than the default
52
52
  unless @validation.name == default_name
53
53
  options[:name] = @validation.name
54
54
  end
55
- # only provide a comment if it is not nil
56
- unless @validation.description.nil?
55
+ # only provide a comment if it is not nil and not equal to the provided
56
+ # default_comment, if it is the same as the default then we wont want to show
57
+ # it in the migration files
58
+ unless @validation.description.nil? || @validation.description == default_comment
57
59
  options[:comment] = <<~RUBY
58
60
  <<~COMMENT
59
61
  #{indent @validation.description || ""}
@@ -243,9 +243,8 @@ module DynamicMigrations
243
243
  # This method is called from within the various modules which are included to this class.
244
244
  # It locally stores all the fragments which will later be organized into different migrations.
245
245
  def add_fragment migration_method:, object:, migration:, schema: nil, table: nil, code_comment: nil, dependent_table: nil, dependent_function: nil, dependent_enum: nil
246
- # Remove any empty lines and whitespace from the beginning or the end of the migration and then
247
- # strip any empty lines witin the migration (remove the whitespace from them, not delete them).
248
- final_migration = strip_empty_lines(migration).strip
246
+ # Remove any empty lines and whitespace from the beginning or the end of the migration
247
+ final_migration = trim_lines migration
249
248
  fragment = Fragment.new(schema&.name, table&.name, migration_method, object.name, code_comment, final_migration)
250
249
 
251
250
  if dependent_table
@@ -272,8 +271,8 @@ module DynamicMigrations
272
271
  multi_line_string.gsub("\n", "\n#{spaces}")
273
272
  end
274
273
 
275
- def strip_empty_lines multi_line_string
276
- multi_line_string.gsub(/^\s*\n/, "")
274
+ def trim_lines string
275
+ string.split("\n").map(&:rstrip).join("\n")
277
276
  end
278
277
  end
279
278
  end
@@ -42,10 +42,12 @@ module DynamicMigrations
42
42
  # perform work with the connection
43
43
  # todo: `yield connection` would have been preferred, but rbs/steep doesnt understand that syntax
44
44
  if block.is_a? Proc
45
- block.call connection
45
+ result = block.call connection
46
46
  end
47
47
  # close the connection
48
48
  disconnect
49
+ # return whever was returned from within the block
50
+ result
49
51
  end
50
52
  end
51
53
  end
@@ -33,7 +33,7 @@ module DynamicMigrations
33
33
 
34
34
  # If the function exists in both the configuration and database representations
35
35
  # but the definition is different then we need to update the definition.
36
- elsif configuration_function[:definition][:matches] == false
36
+ elsif configuration_function[:normalized_definition][:matches] == false
37
37
  function = @database.configured_schema(schema_name).function(function_name)
38
38
  @generator.update_function function
39
39
  # does the description also need to be updated
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  if configuration_foreign_key_constraint[:description][:matches] == false
45
45
  # if the description was removed
46
46
  if configuration_foreign_key_constraint[:description].nil?
47
- @generator.remove_foreign_key_constraint_comment foreign_key_constraint
47
+ @generator.remove_foreign_key_constraint_comment updated_foreign_key_constraint
48
48
  else
49
- @generator.set_foreign_key_constraint_comment foreign_key_constraint
49
+ @generator.set_foreign_key_constraint_comment updated_foreign_key_constraint
50
50
  end
51
51
  end
52
52
 
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  if configuration_index[:description][:matches] == false
45
45
  # if the description was removed
46
46
  if configuration_index[:description].nil?
47
- @generator.remove_index_comment index
47
+ @generator.remove_index_comment updated_index
48
48
  else
49
- @generator.set_index_comment index
49
+ @generator.set_index_comment updated_index
50
50
  end
51
51
  end
52
52
 
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  if configuration_trigger[:description][:matches] == false
45
45
  # if the description was removed
46
46
  if configuration_trigger[:description].nil?
47
- @generator.remove_trigger_comment trigger
47
+ @generator.remove_trigger_comment updated_trigger
48
48
  else
49
- @generator.set_trigger_comment trigger
49
+ @generator.set_trigger_comment updated_trigger
50
50
  end
51
51
  end
52
52
 
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  if configuration_unique_constraint[:description][:matches] == false
45
45
  # if the description was removed
46
46
  if configuration_unique_constraint[:description].nil?
47
- @generator.remove_unique_constraint_comment unique_constraint
47
+ @generator.remove_unique_constraint_comment updated_unique_constraint
48
48
  else
49
- @generator.set_unique_constraint_comment unique_constraint
49
+ @generator.set_unique_constraint_comment updated_unique_constraint
50
50
  end
51
51
  end
52
52
 
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  if configuration_validation[:description][:matches] == false
45
45
  # if the description was removed
46
46
  if configuration_validation[:description].nil?
47
- @generator.remove_validation_comment validation
47
+ @generator.remove_validation_comment updated_validation
48
48
  else
49
- @generator.set_validation_comment validation
49
+ @generator.set_validation_comment updated_validation
50
50
  end
51
51
  end
52
52
 
@@ -172,7 +172,7 @@ module DynamicMigrations
172
172
  # the base functions
173
173
  functions.each do |function_name, function|
174
174
  result[function_name] = compare_record function, comparison_functions[function_name], [
175
- :definition,
175
+ :normalized_definition,
176
176
  :description
177
177
  ]
178
178
  end
@@ -249,7 +249,7 @@ module DynamicMigrations
249
249
  :action_timing,
250
250
  :event_manipulation,
251
251
  :action_order,
252
- :action_condition,
252
+ :normalized_action_condition,
253
253
  :parameters,
254
254
  :action_orientation,
255
255
  :action_reference_old_table,
@@ -332,7 +332,7 @@ module DynamicMigrations
332
332
  validations.each do |name, validation|
333
333
  # compare this validation to the equivilent in the comparison list
334
334
  result[name] = compare_record validation, comparison_validations[name], [
335
- :check_clause,
335
+ :normalized_check_clause,
336
336
  :column_names,
337
337
  :description,
338
338
  :deferrable,
@@ -22,7 +22,7 @@ module DynamicMigrations
22
22
  rows.each do |row|
23
23
  schema_name = row["schema_name"].to_sym
24
24
  enum_name = row["enum_name"].to_sym
25
- enum_value = row["enum_value"].to_sym
25
+ enum_value = row["enum_value"]
26
26
 
27
27
  schema = schemas[schema_name] ||= {}
28
28
  enum = schema[enum_name] ||= {
@@ -99,6 +99,14 @@ module DynamicMigrations
99
99
  SQL
100
100
  end
101
101
 
102
+ def refresh_database_keys_and_unique_constraints_cache
103
+ connection.exec(<<~SQL)
104
+ REFRESH MATERIALIZED VIEW public.dynamic_migrations_keys_and_unique_constraints_cache
105
+ SQL
106
+ rescue PG::UndefinedTable
107
+ create_database_keys_and_unique_constraints_cache
108
+ end
109
+
102
110
  # fetch all required data from the database and build and return a
103
111
  # useful hash representing the keys and indexes of your database
104
112
  def fetch_keys_and_unique_constraints
@@ -13,6 +13,9 @@ module DynamicMigrations
13
13
  class ExpectedDefinitionError < StandardError
14
14
  end
15
15
 
16
+ class UnnormalizableDefinitionError < StandardError
17
+ end
18
+
16
19
  attr_reader :schema
17
20
  attr_reader :name
18
21
  attr_reader :definition
@@ -31,7 +34,7 @@ module DynamicMigrations
31
34
  raise ExpectedSymbolError, name unless name.is_a? Symbol
32
35
  @name = name
33
36
 
34
- unless definition.is_a?(String) && definition.strip != "" && definition.strip.end_with?("END;")
37
+ unless definition.is_a?(String) && definition.strip != "" && definition.strip.end_with?("END;", "END")
35
38
  raise ExpectedDefinitionError, "Definition must be a string, and end with `END;`. Definition provided:\n#{definition}"
36
39
  end
37
40
  @definition = definition.strip
@@ -59,9 +62,55 @@ module DynamicMigrations
59
62
 
60
63
  def differences_descriptions other_function
61
64
  method_differences_descriptions other_function, [
62
- :definition
65
+ :normalized_definition
63
66
  ]
64
67
  end
68
+
69
+ # temporarily create a function in postgres and fetch the actual
70
+ # normalized definition directly from the database
71
+ def normalized_definition
72
+ # no need to normalize definitions which originated from the database
73
+ if from_database?
74
+ definition
75
+ else
76
+ @normalized_definition ||= fetch_normalized_definition
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def fetch_normalized_definition
83
+ fn_def = schema.database.with_connection do |connection|
84
+ # wrapped in a transaction just in case something here fails, because
85
+ # we don't want the function to be persisted
86
+ connection.exec("BEGIN")
87
+
88
+ # create a temporary function, from which we will fetch the normalized definition
89
+ connection.exec(<<~SQL)
90
+ CREATE OR REPLACE FUNCTION normalized_definition_temp_fn() returns trigger language plpgsql AS
91
+ $$#{definition}$$;
92
+ SQL
93
+
94
+ # get the normalzed version of the definition
95
+ rows = connection.exec(<<~SQL)
96
+ SELECT prosrc AS function_definition
97
+ FROM pg_proc
98
+ WHERE proname = 'normalized_definition_temp_fn';
99
+ SQL
100
+
101
+ # delete the temp table and close the transaction
102
+ connection.exec("ROLLBACK")
103
+
104
+ # return the normalized function definition
105
+ rows.first["function_definition"]
106
+ end
107
+
108
+ if fn_def.nil?
109
+ raise UnnormalizableDefinitionError, "Failed to nomalize action condition `#{definition}`"
110
+ end
111
+
112
+ fn_def
113
+ end
65
114
  end
66
115
  end
67
116
  end
@@ -36,7 +36,11 @@ module DynamicMigrations
36
36
  @data_type = data_type
37
37
 
38
38
  @null = null
39
- @default = default
39
+
40
+ unless default.nil?
41
+ raise ExpectedStringError, default unless default.is_a? String
42
+ @default = default
43
+ end
40
44
 
41
45
  unless description.nil?
42
46
  raise ExpectedStringError, description unless description.is_a? String
@@ -50,7 +54,7 @@ module DynamicMigrations
50
54
  unless enum.is_a? Enum
51
55
  raise UnexpectedEnumError, "#{enum} is not a valid enum"
52
56
  end
53
- unless @data_type == enum.full_name || @data_type == "#{enum.full_name}[]"
57
+ if (array? && @data_type != :"#{enum.full_name}[]") || (!array? && @data_type != enum.full_name)
54
58
  raise UnexpectedEnumError, "enum `#{enum.full_name}` does not match this column's data type `#{@data_type}`"
55
59
  end
56
60
  @enum = enum
@@ -68,6 +72,29 @@ module DynamicMigrations
68
72
  def array?
69
73
  @data_type.end_with? "[]"
70
74
  end
75
+
76
+ def enum?
77
+ !@enum.nil?
78
+ end
79
+
80
+ # for arrays returns the column type without the array brackets, for non arrays
81
+ # jsut returnms the column type
82
+ def base_data_type
83
+ array? ? @data_type[0..-3]&.to_sym : @data_type
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
71
98
  end
72
99
  end
73
100
  end
@@ -14,9 +14,6 @@ module DynamicMigrations
14
14
  class ExpectedArrayOfColumnsError < StandardError
15
15
  end
16
16
 
17
- class ExpectedDifferentTablesError < StandardError
18
- end
19
-
20
17
  class DuplicateColumnError < StandardError
21
18
  end
22
19
 
@@ -49,14 +46,14 @@ module DynamicMigrations
49
46
  raise ExpectedArrayOfColumnsError
50
47
  end
51
48
 
52
- if table.name == foreign_table.name && table.schema.name == foreign_table.schema.name
53
- raise ExpectedDifferentTablesError
54
- end
55
-
56
49
  # tables must be set before the columns are added
57
50
  @table = table
58
51
  @foreign_table = foreign_table
59
52
 
53
+ # add this foreign_key_constraint to the remote table (so we can always find
54
+ # these from both sides of the association)
55
+ @foreign_table.add_remote_foreign_key_constraint self
56
+
60
57
  @columns = {}
61
58
  columns.each do |column|
62
59
  add_column column
@@ -61,6 +61,11 @@ module DynamicMigrations
61
61
  # return the new foreign_key_constraint
62
62
  new_foreign_key_constraint
63
63
  end
64
+
65
+ # called automatically from the other side of the foreign key constraint to keep track of the foreign key from both sides
66
+ def add_remote_foreign_key_constraint foreign_key_constraint
67
+ @remote_foreign_key_constraints << foreign_key_constraint
68
+ end
64
69
  end
65
70
  end
66
71
  end