dynamic_migrations 3.6.15 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
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