dynamic_migrations 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
- data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
- data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
- data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
- data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
- data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
- data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
- data/lib/dynamic_migrations/name_helper.rb +13 -0
- data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
- data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
- data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
- data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
- data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
- data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
- data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
- data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
- data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
- data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
- data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
- data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
- data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
- data/lib/dynamic_migrations/postgres/generator.rb +359 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
- data/lib/dynamic_migrations/postgres/server/database/differences.rb +75 -6
- data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
- data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +49 -8
- data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +4 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
- data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
- data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
- data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
- data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
- data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
- data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
- data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +6 -6
- data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
- data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
- data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
- data/lib/dynamic_migrations/postgres/server.rb +6 -0
- data/lib/dynamic_migrations/postgres.rb +1 -1
- data/lib/dynamic_migrations/version.rb +1 -1
- data/lib/dynamic_migrations.rb +47 -3
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 389dc33f29bc39c47961125ad36f5faa377d8609d6d64c2f008517c81bbeb44c
|
4
|
+
data.tar.gz: 27bb1d8d55727f8467b52b7240373bfaf397262d88adfa07db2a04a5d3b52e65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf62099155ca8274632d52fbe7328e66eed962a73cbafd6c53cd056ca8fcea00851ddec52263f47d82ea8486756ea89bf79d03d8b2d2fa489399c7f7e60fb3c0
|
7
|
+
data.tar.gz: 0ea7b26061538ae65d1b1c9cddf91dd8afa3a8c2ac3ff41416926235ccb5e0b2ad6cad304129b038d39939cab32492d00c289a85331cc7724a24678b0e202821
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.0.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.2.0...v3.0.0) (2023-08-11)
|
4
|
+
|
5
|
+
|
6
|
+
### ⚠ BREAKING CHANGES
|
7
|
+
|
8
|
+
* functions and triggers, removed unusable index_type from primary key and unique constraints, added active record migrations and some general refactoring
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* adding migration generators and active record migrators ([80bf481](https://github.com/craigulliott/dynamic_migrations/commit/80bf481bfd33941b8380e7deb010b171898c03df))
|
13
|
+
* 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))
|
14
|
+
* now generating migrations from all database structure object types ([871ab04](https://github.com/craigulliott/dynamic_migrations/commit/871ab048efb6247bc7fadb6e49bb15f6933c5586))
|
15
|
+
* various improvements to triggers and functions, and other general improvements ([e7dd9ab](https://github.com/craigulliott/dynamic_migrations/commit/e7dd9abeee736ea2791c192d3f797497f54773b4))
|
16
|
+
* work in progress on the migrations generator ([3e8deb9](https://github.com/craigulliott/dynamic_migrations/commit/3e8deb954913be291ad8e6e7d1c09af619378e4d))
|
17
|
+
* work in progress on the migrations generator ([7a999ee](https://github.com/craigulliott/dynamic_migrations/commit/7a999ee8467527164e79f2c6da704245e78cf0cd))
|
18
|
+
|
3
19
|
## [2.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.1.0...v2.2.0) (2023-07-31)
|
4
20
|
|
5
21
|
|
@@ -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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# include this module at the top of your migration to get access to the
|
2
|
+
# custom migration methods. For example:
|
3
|
+
#
|
4
|
+
# class CreateFooBars < ActiveRecord::Migration[7.0]
|
5
|
+
# include DynamicMigrations::ActiveRecord::Migrators
|
6
|
+
#
|
7
|
+
# def change
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
module DynamicMigrations
|
13
|
+
module ActiveRecord
|
14
|
+
module Migrators
|
15
|
+
class SchemaNameNotSetError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class DeferrableOptionsError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
class MissingFunctionBlockError < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
include Schema
|
25
|
+
include Validation
|
26
|
+
include ForeignKeyConstraint
|
27
|
+
include Table
|
28
|
+
include Index
|
29
|
+
include Column
|
30
|
+
include Function
|
31
|
+
include UniqueConstraint
|
32
|
+
include Trigger
|
33
|
+
|
34
|
+
# The schema name should be set before the migrations for
|
35
|
+
# each schema's migrations are run. This is done by:
|
36
|
+
# DynamicMigrations::ActiveRecord::Migrators.set_schema_name :schema_name
|
37
|
+
def self.schema_name
|
38
|
+
@current_schema
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.set_schema_name schema_name
|
42
|
+
@current_schema = schema_name.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clear_schema_name
|
46
|
+
@current_schema = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def quote string
|
50
|
+
connection.quote string
|
51
|
+
end
|
52
|
+
|
53
|
+
# this method is made available on the final migration class
|
54
|
+
def schema_name
|
55
|
+
sn = Migrators.schema_name
|
56
|
+
if sn.nil?
|
57
|
+
raise SchemaNameNotSetError
|
58
|
+
end
|
59
|
+
# return the schema name
|
60
|
+
sn
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DynamicMigrations
|
2
|
+
module NameHelper
|
3
|
+
# shortens a table name like 'invoice_subscription_prepayments' to 'inv_sub_pre'
|
4
|
+
warn "no unit tests"
|
5
|
+
def abbreviate_table_name table_name
|
6
|
+
table_name_without_schema = table_name.to_s.split(".").last
|
7
|
+
if table_name_without_schema.nil?
|
8
|
+
raise "no table name provided"
|
9
|
+
end
|
10
|
+
table_name_without_schema.split("_").map { |v| v[0..2] }.join("_")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|