dynamic_migrations 2.1.0 → 3.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -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 +76 -16
- 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 +50 -26
- 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 +6 -44
- 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 +22 -112
- 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 -4
- metadata +44 -3
- data/lib/dynamic_migrations/postgres/data_types.rb +0 -320
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,28 @@
|
|
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
|
+
|
19
|
+
## [2.2.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.1.0...v2.2.0) (2023-07-31)
|
20
|
+
|
21
|
+
|
22
|
+
### Features
|
23
|
+
|
24
|
+
* using postgres shorthand names such as `numeric(12,2)` for types, and removing the additional column metadata ([86d0113](https://github.com/craigulliott/dynamic_migrations/commit/86d0113aabd4350f278164d18e3c0a611fcf5595))
|
25
|
+
|
3
26
|
## [2.1.0](https://github.com/craigulliott/dynamic_migrations/compare/v2.0.0...v2.1.0) (2023-07-31)
|
4
27
|
|
5
28
|
|
@@ -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
|