dynamic_migrations 2.2.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -4
  3. data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
  4. data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
  5. data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
  6. data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
  7. data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
  8. data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
  9. data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
  10. data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
  11. data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
  12. data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
  13. data/lib/dynamic_migrations/name_helper.rb +13 -0
  14. data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
  15. data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
  16. data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
  17. data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
  18. data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
  19. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
  20. data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
  21. data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
  22. data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
  23. data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
  24. data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
  25. data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
  26. data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
  27. data/lib/dynamic_migrations/postgres/generator.rb +359 -0
  28. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
  29. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
  30. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
  31. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
  32. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
  33. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
  34. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
  35. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
  36. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
  37. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
  38. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
  39. data/lib/dynamic_migrations/postgres/server/database/differences.rb +81 -6
  40. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
  41. data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +49 -8
  42. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
  43. data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
  44. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +4 -0
  45. data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
  46. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
  47. data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
  48. data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
  49. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
  50. data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
  51. data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
  52. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
  53. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
  54. data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
  55. data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
  56. data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
  57. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +6 -6
  58. data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
  59. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
  60. data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
  61. data/lib/dynamic_migrations/postgres/server.rb +6 -0
  62. data/lib/dynamic_migrations/postgres.rb +1 -1
  63. data/lib/dynamic_migrations/version.rb +1 -1
  64. data/lib/dynamic_migrations.rb +47 -3
  65. metadata +44 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b27309525226b33bbcff1b08b3361347e93cff1d182f5620debbe3195b5c1d88
4
- data.tar.gz: 670213a55b129e2518e793bc5314fe522f72645da50d5b32355cacfec5cff8fb
3
+ metadata.gz: f112c797c02407f6c2b090fcd5dabd05861a864069f6aba51a96467be7a093f2
4
+ data.tar.gz: 78bb49e74e722db75f9a6366d6034b947632a41326241f24e51ea6eba4879720
5
5
  SHA512:
6
- metadata.gz: 4227acc9480ac7c9022a0e1f9473627d3b7ab11611d164582b9edf9049928a4a36c1292ce5f43f9f627f9c2c4968e6b8bb4da1ed0dde11b41cd431d6d61022a6
7
- data.tar.gz: ef666cf1f797e262469fc080524726d3e70e1f1a52a4dfdb08bc9b9fbc5925671b25cead8013555618ade984bf603e86812ea8689cba2e59a5dd1a8d411664dc
6
+ metadata.gz: 9ed20eadb8e64389d12665938cff6702d19c2ec5d8c8e9a8b52cc0a7cf715c76596eb9a868ae0411c3f417e39425708a96ee8b890ea40b4f6403ce67dc5daa92
7
+ data.tar.gz: 49aa26f15e57e2d4f18c5dc850e35946781881b63fe52182eb7d707605b9615f4c1d9c6ac1c4807782577646712207032d0a775614a4552ad6fbef7d83d68157
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.0.0...v3.1.0) (2023-08-14)
4
+
5
+
6
+ ### Features
7
+
8
+ * adding a convenience method `to_migrations` on the differences class ([efe94be](https://github.com/craigulliott/dynamic_migrations/commit/efe94be9c620fc44d1cdba54f6cd365292ae0f34))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * updating to_migrations method signature in differences class ([e4e502b](https://github.com/craigulliott/dynamic_migrations/commit/e4e502b7ec44add3d2b6727869a4ad90bc7531b7))
14
+
15
+ ## [3.0.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.2.0...v3.0.0) (2023-08-11)
16
+
17
+
18
+ ### ⚠ BREAKING CHANGES
19
+
20
+ * functions and triggers, removed unusable index_type from primary key and unique constraints, added active record migrations and some general refactoring
21
+
22
+ ### Features
23
+
24
+ * adding migration generators and active record migrators ([80bf481](https://github.com/craigulliott/dynamic_migrations/commit/80bf481bfd33941b8380e7deb010b171898c03df))
25
+ * functions and triggers, removed unusable index_type from primary key and unique constraints, added active record migrations and some general refactoring ([45fcb7c](https://github.com/craigulliott/dynamic_migrations/commit/45fcb7ca3b6724625a4868198a9e75aefb1ea964))
26
+ * now generating migrations from all database structure object types ([871ab04](https://github.com/craigulliott/dynamic_migrations/commit/871ab048efb6247bc7fadb6e49bb15f6933c5586))
27
+ * various improvements to triggers and functions, and other general improvements ([e7dd9ab](https://github.com/craigulliott/dynamic_migrations/commit/e7dd9abeee736ea2791c192d3f797497f54773b4))
28
+
3
29
  ## [2.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.1.0...v2.2.0) (2023-07-31)
4
30
 
5
31
 
@@ -19,11 +45,11 @@
19
45
 
20
46
  ### ⚠ BREAKING CHANGES
21
47
 
22
- * changing all name related methods from %object%_name to just name (i.e. `table.table_name` is now just `table.name`)
48
+ * changing all name related methods from `%object%_name` to just `name` (i.e. `table.table_name` is now just `table.name`)
23
49
 
24
50
  ### Features
25
51
 
26
- * changing all name related methods from %object%_name to just name (i.e. `table.table_name` is now just `table.name`) ([77f18ae](https://github.com/craigulliott/dynamic_migrations/commit/77f18ae168c2449fa437fa7692ff9339931f9076))
52
+ * changing all name related methods from `%object%_name` to just `name` (i.e. `table.table_name` is now just `table.name`) ([77f18ae](https://github.com/craigulliott/dynamic_migrations/commit/77f18ae168c2449fa437fa7692ff9339931f9076))
27
53
 
28
54
  ## [1.1.1](https://github.com/craigulliott/dynamic_migrations/compare/v1.1.0...v1.1.1) (2023-07-17)
29
55
 
@@ -31,8 +57,6 @@
31
57
  ### Bug Fixes
32
58
 
33
59
  * validating that new databases don't already exist and removing a debug print statement ([73bed2d](https://github.com/craigulliott/dynamic_migrations/commit/73bed2d6ab928c7a8f53b2aa17a188a77ed369e9))
34
- * validating that new databases don't already exist and removing a debug print statement ([73bed2d](https://github.com/craigulliott/dynamic_migrations/commit/73bed2d6ab928c7a8f53b2aa17a188a77ed369e9))
35
- * validating that new databases don't already exist and removing a debug print statement ([f4cfb1e](https://github.com/craigulliott/dynamic_migrations/commit/f4cfb1e81252ed967ad1a6d864d495c182ca908f))
36
60
 
37
61
  ## [1.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v1.0.0...v1.1.0) (2023-07-10)
38
62
 
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Column
5
+ # add a comment to the column
6
+ def set_column_comment table_name, column_name, comment
7
+ execute <<~SQL
8
+ COMMENT ON COLUMN #{schema_name}.#{table_name}.#{column_name} IS '#{quote comment}';
9
+ SQL
10
+ end
11
+
12
+ # remove a column comment
13
+ def remove_column_comment table_name, column_name
14
+ execute <<~SQL
15
+ COMMENT ON COLUMN #{schema_name}.#{table_name}.#{column_name} IS NULL;
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,112 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module ForeignKeyConstraint
5
+ class ForeignKeyOnDeleteOptionsError < StandardError
6
+ end
7
+
8
+ class UnexpectedReferentialActionError < StandardError
9
+ end
10
+
11
+ # because rails migrations don't support composite (multiple column) foreign keys
12
+ # column_names and foreign_column_names can be a single column name or
13
+ # an array of column names
14
+ def add_foreign_key table_name, column_names, foreign_schema_name, foreign_table_name, foreign_column_names, name:, initially_deferred: false, deferrable: false, on_delete: :no_action, on_update: :no_action, comment: nil
15
+ if initially_deferred == true && deferrable == false
16
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
17
+ end
18
+
19
+ # convert single column names into arrays, this simplifies the logic below
20
+ column_names = column_names.is_a?(Array) ? column_names : [column_names]
21
+ foreign_column_names = foreign_column_names.is_a?(Array) ? foreign_column_names : [foreign_column_names]
22
+
23
+ # allow it to be deferred, and defer it by default
24
+ deferrable_sql = if initially_deferred
25
+ "DEFERRABLE INITIALLY DEFERRED"
26
+
27
+ # allow it to be deferred, but do not deferr by default
28
+ elsif deferrable
29
+ "DEFERRABLE INITIALLY IMMEDIATE"
30
+
31
+ # it can not be deferred (this is the default)
32
+ else
33
+ "NOT DEFERRABLE"
34
+ end
35
+
36
+ execute <<~SQL
37
+ ALTER TABLE #{table_name}
38
+ ADD CONSTRAINT #{name}
39
+ FOREIGN KEY (#{column_names.join(", ")})
40
+ REFERENCES #{foreign_schema_name}.#{foreign_table_name} (#{foreign_column_names.join(", ")})
41
+ ON DELETE #{referential_action_to_sql on_delete}
42
+ ON UPDATE #{referential_action_to_sql on_update}
43
+ #{deferrable_sql};
44
+ SQL
45
+
46
+ if comment.is_a? String
47
+ set_foreign_key_comment table_name, name, comment
48
+ end
49
+ end
50
+
51
+ def remove_foreign_key table_name, name
52
+ execute <<~SQL
53
+ ALTER TABLE #{table_name}
54
+ DROP CONSTRAINT #{name};
55
+ SQL
56
+ end
57
+
58
+ # add a comment to the foreign_key
59
+ def set_foreign_key_comment table_name, foreign_key_name, comment
60
+ execute <<~SQL
61
+ COMMENT ON CONSTRAINT #{foreign_key_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
62
+ SQL
63
+ end
64
+
65
+ # remove a foreign_key comment
66
+ def remove_foreign_key_comment table_name, foreign_key_name
67
+ execute <<~SQL
68
+ COMMENT ON CONSTRAINT #{foreign_key_name} ON #{schema_name}.#{table_name} IS NULL;
69
+ SQL
70
+ end
71
+
72
+ private
73
+
74
+ def referential_action_to_sql referential_action
75
+ case referential_action
76
+ # Produce an error indicating that the deletion or update would create a
77
+ # foreign key constraint violation. If the constraint is deferred, this
78
+ # error will be produced at constraint check time if there still exist
79
+ # any referencing rows. This is the default action.
80
+ when :no_action
81
+ "NO ACTION"
82
+
83
+ # Produce an error indicating that the deletion or update would create a
84
+ # foreign key constraint violation. This is the same as NO ACTION except
85
+ # that the check is not deferrable.
86
+ when :restrict
87
+ "RESTRICT"
88
+
89
+ # Delete any rows referencing the deleted row, or update the values of
90
+ # the referencing column(s) to the new values of the referenced columns,
91
+ # respectively.
92
+ when :cascade
93
+ "CASCADE"
94
+
95
+ # Set all of the referencing columns, or a specified subset of the
96
+ # referencing columns, to null.
97
+ when :set_null
98
+ "SET NULL"
99
+
100
+ # Set all of the referencing columns, or a specified subset of the
101
+ # referencing columns, to their default values.
102
+ when :set_default
103
+ "SET DEFAULT"
104
+
105
+ else
106
+ raise UnexpectedReferentialActionError, referential_action
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,108 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Function
5
+ class FunctionDoesNotExistError < StandardError
6
+ end
7
+
8
+ class MissingFunctionBlockError < StandardError
9
+ end
10
+
11
+ # create a postgres function
12
+ def create_function table_name, function_name, comment: nil, &block
13
+ unless block
14
+ raise MissingFunctionBlockError, "create_function requires a block"
15
+ end
16
+ # todo - remove this once steep/rbs can better handle blocks
17
+ unless block.is_a? NilClass
18
+ fn_sql = block.call.strip
19
+ end
20
+
21
+ # ensure that the function ends with a semicolon
22
+ unless fn_sql.end_with? ";"
23
+ fn_sql << ";"
24
+ end
25
+
26
+ # schema_name was not provided to this method, it comes from the migration class
27
+ execute <<~SQL
28
+ CREATE FUNCTION #{schema_name}.#{function_name}() returns trigger language plpgsql AS
29
+ $$BEGIN #{fn_sql}
30
+ RETURN NEW;
31
+ END$$;
32
+ SQL
33
+
34
+ if comment.is_a? String
35
+ set_function_comment function_name, comment
36
+ end
37
+ end
38
+
39
+ # update a postgres function
40
+ def update_function table_name, function_name, comment: nil, &block
41
+ unless block
42
+ raise MissingFunctionBlockError, "create_function requires a block"
43
+ end
44
+ # todo - remove this once steep/rbs can better handle blocks
45
+ unless block.is_a? NilClass
46
+ fn_sql = block.call.strip
47
+ end
48
+
49
+ # ensure that the function ends with a semicolon
50
+ unless fn_sql.end_with? ";"
51
+ fn_sql << ";"
52
+ end
53
+
54
+ # schema_name was not provided to this method, it comes from the migration class
55
+ # assert it already exists
56
+ exists_result = execute <<~SQL
57
+ SELECT TRUE as exists
58
+ FROM pg_proc p
59
+ INNER JOIN pg_namespace p_n
60
+ ON p_n.oid = p.pronamespace
61
+ WHERE
62
+ p.proname = #{function_name}
63
+ AND p_n.nspname = #{schema_name}
64
+ -- arguments (defaulting to none for now)
65
+ AND pg_get_function_identity_arguments(p.oid) = ''
66
+ SQL
67
+
68
+ unless exists_result.to_a.first["exists"]
69
+ raise FunctionDoesNotExistError, "Can not update Function. Function #{schema_name}.#{function_name} does not exist."
70
+ end
71
+
72
+ # create or replace will update the function
73
+ execute <<~SQL
74
+ CREATE OR REPLACE FUNCTION #{schema_name}.#{function_name}() returns trigger language plpgsql AS
75
+ $$BEGIN #{fn_sql}
76
+ RETURN NEW;
77
+ END$$;
78
+ SQL
79
+
80
+ if comment.is_a? String
81
+ set_function_comment function_name, comment
82
+ end
83
+ end
84
+
85
+ # remove a function from the schema
86
+ def drop_function function_name
87
+ execute <<~SQL
88
+ DROP FUNCTION #{schema_name}.#{function_name}();
89
+ SQL
90
+ end
91
+
92
+ # add a comment to a function
93
+ def set_function_comment function_name, comment
94
+ execute <<~SQL
95
+ COMMENT ON FUNCTION #{schema_name}.#{function_name} IS '#{quote comment}';
96
+ SQL
97
+ end
98
+
99
+ # remove the comment from a function
100
+ def remove_function_comment function_name
101
+ execute <<~SQL
102
+ COMMENT ON FUNCTION #{schema_name}.#{function_name} IS null;
103
+ SQL
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Index
5
+ # add a comment to the index
6
+ # table name is not needed, but is included here for consistency with the other
7
+ # rils migrations methods, and to make it easier to understand what table this
8
+ # index is relate to
9
+ def set_index_comment table_name, index_name, comment
10
+ execute <<~SQL
11
+ COMMENT ON INDEX #{index_name} IS '#{quote comment}';
12
+ SQL
13
+ end
14
+
15
+ # remove a index comment
16
+ # table name is not needed, but is included here for consistency with the other
17
+ # rils migrations methods, and to make it easier to understand what table this
18
+ # index is relate to
19
+ def remove_index_comment table_name, index_name
20
+ execute <<~SQL
21
+ COMMENT ON INDEX #{index_name} IS NULL;
22
+ SQL
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Schema
5
+ def create_schema
6
+ # schema_name was not provided to this method, it comes from the migration class
7
+ execute <<~SQL
8
+ CREATE SCHEMA #{schema_name};
9
+ SQL
10
+ end
11
+
12
+ def drop_schema
13
+ # schema_name was not provided to this method, it comes from the migration class
14
+ execute <<~SQL
15
+ DROP SCHEMA #{schema_name};
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Table
5
+ # add a comment to the table
6
+ def set_table_comment table_name, comment
7
+ execute <<~SQL
8
+ COMMENT ON TABLE #{schema_name}.#{table_name} IS '#{quote comment}';
9
+ SQL
10
+ end
11
+
12
+ # remove a table comment
13
+ def remove_table_comment table_name
14
+ execute <<~SQL
15
+ COMMENT ON TABLE #{schema_name}.#{table_name} IS NULL;
16
+ SQL
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Trigger
5
+ # create a trigger
6
+ class UnexpectedEventManipulationError < StandardError
7
+ end
8
+
9
+ class UnexpectedActionOrientationError < StandardError
10
+ end
11
+
12
+ class UnexpectedActionTimingError < StandardError
13
+ end
14
+
15
+ class UnexpectedConditionsError < StandardError
16
+ end
17
+
18
+ # create a postgres trigger
19
+ def add_trigger table_name, name:, action_timing:, event_manipulation:, action_orientation:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
20
+ unless [:insert, :delete, :update].include? event_manipulation
21
+ raise UnexpectedEventManipulationError, event_manipulation
22
+ end
23
+
24
+ unless action_condition.nil? || action_condition.is_a?(String)
25
+ raise UnexpectedConditionsError, "expected String but got `#{action_condition}`"
26
+ end
27
+
28
+ unless [:row, :statement].include? action_orientation
29
+ raise UnexpectedActionOrientationError, action_orientation
30
+ end
31
+
32
+ unless [:before, :after, :instead_of].include? action_timing
33
+ raise UnexpectedActionTimingError, action_timing
34
+ end
35
+
36
+ # "INSTEAD OF/BEFORE/AFTER" "INSERT/UPDATE/DELETE"
37
+ timing_sql = "#{action_timing.to_s.sub("_", " ")} #{event_manipulation}".upcase
38
+
39
+ condition_sql = action_condition.nil? ? "" : "WHEN (#{action_condition})"
40
+
41
+ temp_tables = []
42
+ unless action_reference_old_table.nil?
43
+ temp_tables << "OLD TABLE AS #{action_reference_old_table}"
44
+ end
45
+ unless action_reference_new_table.nil?
46
+ temp_tables << "NEW TABLE AS #{action_reference_new_table}"
47
+ end
48
+ temp_tables_sql = temp_tables.any? ? "REFERENCING #{temp_tables.join(" ")}" : ""
49
+
50
+ # schema_name was not provided to this method, it comes from the migration class
51
+ execute <<~SQL
52
+ CREATE TRIGGER #{name}
53
+ #{timing_sql} ON #{schema_name}.#{table_name} #{temp_tables_sql}
54
+ FOR EACH #{action_orientation}
55
+ #{condition_sql}
56
+ EXECUTE FUNCTION #{function_schema_name}.#{function_name}();
57
+ SQL
58
+
59
+ if comment.is_a? String
60
+ set_trigger_comment table_name, name, comment
61
+ end
62
+ end
63
+
64
+ # wrappers for add_trigger which provide more friendly syntax
65
+ def before_insert table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
66
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :insert, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
67
+ end
68
+
69
+ def before_update table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
70
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :update, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
71
+ end
72
+
73
+ def before_delete table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
74
+ add_trigger table_name, name: name, action_timing: :before, event_manipulation: :delete, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
75
+ end
76
+
77
+ def after_insert table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
78
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :insert, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
79
+ end
80
+
81
+ def after_update table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
82
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :update, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
83
+ end
84
+
85
+ def after_delete table_name, name:, function_schema_name:, function_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, comment: nil
86
+ add_trigger table_name, name: name, action_timing: :after, event_manipulation: :delete, action_orientation: :row, function_schema_name: function_schema_name, function_name: function_name, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, comment: comment
87
+ end
88
+
89
+ def remove_trigger table_name, trigger_name
90
+ execute <<~SQL
91
+ DROP TRIGGER #{trigger_name} ON #{schema_name}.#{table_name};
92
+ SQL
93
+ end
94
+
95
+ def set_trigger_comment table_name, trigger_name, comment
96
+ execute <<~SQL
97
+ COMMENT ON TRIGGER #{trigger_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
98
+ SQL
99
+ end
100
+
101
+ def remove_trigger_comment table_name, trigger_name
102
+ execute <<~SQL
103
+ COMMENT ON TRIGGER #{trigger_name} ON #{schema_name}.#{table_name} IS NULL;
104
+ SQL
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,63 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module UniqueConstraint
5
+ # because rails migrations don't support composite (multiple column) foreign keys
6
+ # column_names can be a single column name or an array of column names
7
+ def add_unique_constraint table_name, column_names, name:, deferrable: false, initially_deferred: false, comment: nil
8
+ if initially_deferred == true && deferrable == false
9
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
10
+ end
11
+
12
+ # convert single column names into arrays, this simplifies the logic below
13
+ column_names = column_names.is_a?(Array) ? column_names : [column_names]
14
+
15
+ # allow it to be deferred, and defer it by default
16
+ deferrable_sql = if initially_deferred
17
+ "DEFERRABLE INITIALLY DEFERRED"
18
+
19
+ # allow it to be deferred, but do not deferr by default
20
+ elsif deferrable
21
+ "DEFERRABLE INITIALLY IMMEDIATE"
22
+
23
+ # it can not be deferred (this is the default)
24
+ else
25
+ "NOT DEFERRABLE"
26
+ end
27
+
28
+ execute <<~SQL
29
+ ALTER TABLE #{table_name}
30
+ ADD CONSTRAINT #{name}
31
+ UNIQUE (#{column_names.join(", ")})
32
+ #{deferrable_sql};
33
+ SQL
34
+
35
+ if comment.is_a? String
36
+ set_unique_constraint_comment table_name, name, comment
37
+ end
38
+ end
39
+
40
+ def remove_unique_constraint table_name, name
41
+ execute <<~SQL
42
+ ALTER TABLE #{table_name}
43
+ DROP CONSTRAINT #{name};
44
+ SQL
45
+ end
46
+
47
+ # add a comment to the unique_constraint
48
+ def set_unique_constraint_comment table_name, unique_constraint_name, comment
49
+ execute <<~SQL
50
+ COMMENT ON CONSTRAINT #{unique_constraint_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
51
+ SQL
52
+ end
53
+
54
+ # remove a unique_constraint comment
55
+ def remove_unique_constraint_comment table_name, unique_constraint_name
56
+ execute <<~SQL
57
+ COMMENT ON CONSTRAINT #{unique_constraint_name} ON #{schema_name}.#{table_name} IS NULL;
58
+ SQL
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ module DynamicMigrations
2
+ module ActiveRecord
3
+ module Migrators
4
+ module Validation
5
+ # this exists because because the standard rails migration does not support deffered constraints
6
+ def add_validation table_name, name:, initially_deferred: false, deferrable: false, comment: nil, &block
7
+ unless block
8
+ raise MissingFunctionBlockError, "create_function requires a block"
9
+ end
10
+ # todo - remove this once steep/rbs can better handle blocks
11
+ unless block.is_a? NilClass
12
+ sql = block.call.strip
13
+ end
14
+
15
+ if initially_deferred == true && deferrable == false
16
+ raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
17
+ end
18
+
19
+ # allow it to be deferred, and defer it by default
20
+ deferrable_sql = if initially_deferred
21
+ "DEFERRABLE INITIALLY DEFERRED"
22
+
23
+ # allow it to be deferred, but do not deferr by default
24
+ elsif deferrable
25
+ "DEFERRABLE INITIALLY IMMEDIATE"
26
+
27
+ # it can not be deferred (this is the default)
28
+ else
29
+ "NOT DEFERRABLE"
30
+ end
31
+
32
+ execute <<~SQL
33
+ ALTER TABLE #{table_name}
34
+ ADD CONSTRAINT #{name}
35
+ CHECK (#{sql})
36
+ #{deferrable_sql};
37
+ SQL
38
+
39
+ if comment.is_a? String
40
+ set_validation_comment table_name, name, comment
41
+ end
42
+ end
43
+
44
+ def remove_validation table_name, name
45
+ execute <<~SQL
46
+ ALTER TABLE #{table_name}
47
+ DROP CONSTRAINT #{name};
48
+ SQL
49
+ end
50
+
51
+ # add a comment to the validation
52
+ def set_validation_comment table_name, validation_name, comment
53
+ execute <<~SQL
54
+ COMMENT ON CONSTRAINT #{validation_name} ON #{schema_name}.#{table_name} IS '#{quote comment}';
55
+ SQL
56
+ end
57
+
58
+ # remove a validation comment
59
+ def remove_validation_comment table_name, validation_name
60
+ execute <<~SQL
61
+ COMMENT ON CONSTRAINT #{validation_name} ON #{schema_name}.#{table_name} IS NULL;
62
+ SQL
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end