dynamic_migrations 3.6.16 → 3.8.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 +30 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb +3 -1
- data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +6 -6
- data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +3 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/enums.rb +3 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +1 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +4 -7
- data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rb +5 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +2 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +52 -24
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validations.rb +1 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +2 -0
- data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +36 -13
- data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +6 -6
- data/lib/dynamic_migrations/postgres.rb +27 -2
- data/lib/dynamic_migrations/version.rb +1 -1
- data/lib/dynamic_migrations.rb +2 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/column.rbs +2 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rbs +0 -3
- data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rbs +1 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/validation.rbs +5 -3
- data/sig/dynamic_migrations/postgres/server/database/schema/table/validations.rbs +1 -1
- data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +1 -0
- data/sig/dynamic_migrations/postgres.rbs +8 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 437a84189a939e185f89db2de8395bc1fb8dc9f159b3948a97dcc5b04c4fedb7
|
4
|
+
data.tar.gz: 6e1df320ac06c0b0ca9d6aeb3abf684e47b9daac4ee1def93180de801f73125b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2ce93a8d273abfb0c2ede80b7564216a8409bcd8a43b8fe6c85043eed83d750f8a2b2e53996afdc241e13db9f6cdf2c3f52034a5e93965663684bc121237b54
|
7
|
+
data.tar.gz: bb3378cc475e8e3c16dd2d3a9a5754484da539d3ba1feb4839d2312c1f24cb147ea11534ba3a9bfbcda827bee4bc5ae18cc2c7a2b23bdd67c84a23503fc4fcab
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.8.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.7.0...v3.8.0) (2023-10-04)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* configuration option to skip removing unused extensions ([5d27e36](https://github.com/craigulliott/dynamic_migrations/commit/5d27e36363dcec552ed015a196fbcf660e6b038c))
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* adding a setting to allow customizing where the schema where materialized view structure caches are created ([f3a81f8](https://github.com/craigulliott/dynamic_migrations/commit/f3a81f850dacc40ee434de77bb1d3f3c14ea0872))
|
14
|
+
* handling arrays of enums properly ([5d27e36](https://github.com/craigulliott/dynamic_migrations/commit/5d27e36363dcec552ed015a196fbcf660e6b038c))
|
15
|
+
* skipping views when loading database tables ([5d27e36](https://github.com/craigulliott/dynamic_migrations/commit/5d27e36363dcec552ed015a196fbcf660e6b038c))
|
16
|
+
|
17
|
+
## [3.7.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.16...v3.7.0) (2023-09-27)
|
18
|
+
|
19
|
+
|
20
|
+
### Features
|
21
|
+
|
22
|
+
* providing access to foreign key constraints from both sides of the association ([48dcf1c](https://github.com/craigulliott/dynamic_migrations/commit/48dcf1cd4cdb23bc37da3e47b00f8007b8bc0f8a))
|
23
|
+
|
24
|
+
|
25
|
+
### Bug Fixes
|
26
|
+
|
27
|
+
* allowing foreign keys to the same table because they are valid and sometimes useful ([0566384](https://github.com/craigulliott/dynamic_migrations/commit/0566384a4cfeade757d9806059035477f7c41ee2))
|
28
|
+
* lazy loading column names for validations when they were configured with a nil value for columns ([439200e](https://github.com/craigulliott/dynamic_migrations/commit/439200efdedf74dddc852bf9bd35e81c1f6b4336))
|
29
|
+
* providing convenience method to retrieve columns base data type from array columns ([cd3d9bf](https://github.com/craigulliott/dynamic_migrations/commit/cd3d9bf06682335a890d9b11133d397d7bcd50af))
|
30
|
+
* semi colon at the end of function definitions is now optional ([ca9b3aa](https://github.com/craigulliott/dynamic_migrations/commit/ca9b3aa04da23605ff61b2ec14baed927145d2c3))
|
31
|
+
* structure loader was not identifying enums properly ([95276d3](https://github.com/craigulliott/dynamic_migrations/commit/95276d33f98f41eca9a9467bb6abeb458a37f16b))
|
32
|
+
|
3
33
|
## [3.6.16](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.15...v3.6.16) (2023-09-16)
|
4
34
|
|
5
35
|
|
data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb
CHANGED
@@ -18,7 +18,9 @@ module DynamicMigrations
|
|
18
18
|
# then we need to delete it
|
19
19
|
elsif database_extension[:exists] == true && !configuration_extension[:exists]
|
20
20
|
# a migration to drop the extension
|
21
|
-
|
21
|
+
if Postgres.remove_unused_extensions?
|
22
|
+
@generator.disable_extension extension_name
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -7,7 +7,7 @@ module DynamicMigrations
|
|
7
7
|
module KeysAndUniqueConstraintsLoader
|
8
8
|
def create_database_keys_and_unique_constraints_cache
|
9
9
|
connection.exec(<<~SQL)
|
10
|
-
CREATE MATERIALIZED VIEW
|
10
|
+
CREATE MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache as
|
11
11
|
SELECT
|
12
12
|
c.conname AS constraint_name,
|
13
13
|
pg_get_constraintdef(c.oid, true) as constraint_definition,
|
@@ -92,16 +92,16 @@ module DynamicMigrations
|
|
92
92
|
ORDER BY schema_name, table_name;
|
93
93
|
SQL
|
94
94
|
connection.exec(<<~SQL)
|
95
|
-
CREATE UNIQUE INDEX dynamic_migrations_keys_and_unique_constraints_cache_index ON
|
95
|
+
CREATE UNIQUE INDEX dynamic_migrations_keys_and_unique_constraints_cache_index ON #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache (schema_name, table_name, constraint_name);
|
96
96
|
SQL
|
97
97
|
connection.exec(<<~SQL)
|
98
|
-
COMMENT ON MATERIALIZED VIEW
|
98
|
+
COMMENT ON MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache IS 'A cached representation of the database constraints. This is used by the dynamic migrations library and is created automatically and updated automatically after migrations have run.';
|
99
99
|
SQL
|
100
100
|
end
|
101
101
|
|
102
102
|
def refresh_database_keys_and_unique_constraints_cache
|
103
103
|
connection.exec(<<~SQL)
|
104
|
-
REFRESH MATERIALIZED VIEW
|
104
|
+
REFRESH MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache
|
105
105
|
SQL
|
106
106
|
rescue PG::UndefinedTable
|
107
107
|
create_database_keys_and_unique_constraints_cache
|
@@ -112,12 +112,12 @@ module DynamicMigrations
|
|
112
112
|
def fetch_keys_and_unique_constraints
|
113
113
|
begin
|
114
114
|
rows = connection.exec(<<~SQL)
|
115
|
-
SELECT * FROM
|
115
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache
|
116
116
|
SQL
|
117
117
|
rescue PG::UndefinedTable
|
118
118
|
create_database_keys_and_unique_constraints_cache
|
119
119
|
rows = connection.exec(<<~SQL)
|
120
|
-
SELECT * FROM
|
120
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_keys_and_unique_constraints_cache
|
121
121
|
SQL
|
122
122
|
end
|
123
123
|
|
@@ -42,7 +42,9 @@ module DynamicMigrations
|
|
42
42
|
# add each table column
|
43
43
|
table_definition[:columns].each do |column_name, column_definition|
|
44
44
|
if column_definition[:is_enum]
|
45
|
-
|
45
|
+
data_type = column_definition[:data_type].to_s
|
46
|
+
enum_full_name = column_definition[:is_array] ? data_type[0..-3] : data_type
|
47
|
+
enum_schema, enum_name = enum_full_name.split(".")
|
46
48
|
enum = table.schema.database.loaded_schema(enum_schema.to_sym).enum(enum_name.to_sym)
|
47
49
|
else
|
48
50
|
enum = nil
|
@@ -37,7 +37,9 @@ module DynamicMigrations
|
|
37
37
|
# return a enum by its name, raises an error if the enum does not exist
|
38
38
|
def enum enum_name
|
39
39
|
raise ExpectedSymbolError, enum_name unless enum_name.is_a? Symbol
|
40
|
-
|
40
|
+
unless has_enum? enum_name
|
41
|
+
raise EnumDoesNotExistError, "Enum `#{enum_name}` does not exist"
|
42
|
+
end
|
41
43
|
@enums[enum_name]
|
42
44
|
end
|
43
45
|
|
@@ -34,7 +34,7 @@ module DynamicMigrations
|
|
34
34
|
raise ExpectedSymbolError, name unless name.is_a? Symbol
|
35
35
|
@name = name
|
36
36
|
|
37
|
-
unless definition.is_a?(String) && definition.strip != "" && definition.strip.end_with?("END;")
|
37
|
+
unless definition.is_a?(String) && definition.strip != "" && definition.strip.end_with?("END;", "END")
|
38
38
|
raise ExpectedDefinitionError, "Definition must be a string, and end with `END;`. Definition provided:\n#{definition}"
|
39
39
|
end
|
40
40
|
@definition = definition.strip
|
@@ -77,6 +77,12 @@ module DynamicMigrations
|
|
77
77
|
!@enum.nil?
|
78
78
|
end
|
79
79
|
|
80
|
+
# for arrays returns the column type without the array brackets, for non arrays
|
81
|
+
# jsut returnms the column type
|
82
|
+
def base_data_type
|
83
|
+
array? ? @data_type[0..-3]&.to_sym : @data_type
|
84
|
+
end
|
85
|
+
|
80
86
|
# sometimes this system makes temporary tables in order to fetch the normalized
|
81
87
|
# version of constraint check clauses, function definitions or trigger action conditions
|
82
88
|
# because certain data types might not yet exist, we need to use alternative types
|
@@ -14,9 +14,6 @@ module DynamicMigrations
|
|
14
14
|
class ExpectedArrayOfColumnsError < StandardError
|
15
15
|
end
|
16
16
|
|
17
|
-
class ExpectedDifferentTablesError < StandardError
|
18
|
-
end
|
19
|
-
|
20
17
|
class DuplicateColumnError < StandardError
|
21
18
|
end
|
22
19
|
|
@@ -49,14 +46,14 @@ module DynamicMigrations
|
|
49
46
|
raise ExpectedArrayOfColumnsError
|
50
47
|
end
|
51
48
|
|
52
|
-
if table.name == foreign_table.name && table.schema.name == foreign_table.schema.name
|
53
|
-
raise ExpectedDifferentTablesError
|
54
|
-
end
|
55
|
-
|
56
49
|
# tables must be set before the columns are added
|
57
50
|
@table = table
|
58
51
|
@foreign_table = foreign_table
|
59
52
|
|
53
|
+
# add this foreign_key_constraint to the remote table (so we can always find
|
54
|
+
# these from both sides of the association)
|
55
|
+
@foreign_table.add_remote_foreign_key_constraint self
|
56
|
+
|
60
57
|
@columns = {}
|
61
58
|
columns.each do |column|
|
62
59
|
add_column column
|
data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rb
CHANGED
@@ -61,6 +61,11 @@ module DynamicMigrations
|
|
61
61
|
# return the new foreign_key_constraint
|
62
62
|
new_foreign_key_constraint
|
63
63
|
end
|
64
|
+
|
65
|
+
# called automatically from the other side of the foreign key constraint to keep track of the foreign key from both sides
|
66
|
+
def add_remote_foreign_key_constraint foreign_key_constraint
|
67
|
+
@remote_foreign_key_constraints << foreign_key_constraint
|
68
|
+
end
|
64
69
|
end
|
65
70
|
end
|
66
71
|
end
|
@@ -54,7 +54,7 @@ module DynamicMigrations
|
|
54
54
|
attr_reader :description
|
55
55
|
attr_reader :template
|
56
56
|
|
57
|
-
# initialize a new object to represent a
|
57
|
+
# initialize a new object to represent a trigger in a postgres table
|
58
58
|
def initialize source, table, name, action_timing:, event_manipulation:, parameters:, action_orientation:, function:, action_order: nil, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, description: nil, template: nil
|
59
59
|
super source
|
60
60
|
|
@@ -201,7 +201,7 @@ module DynamicMigrations
|
|
201
201
|
descriptions
|
202
202
|
end
|
203
203
|
|
204
|
-
# create a temporary table in postgres to represent this
|
204
|
+
# create a temporary table in postgres to represent this trigger and fetch
|
205
205
|
# the actual normalized check constraint directly from the database
|
206
206
|
def normalized_action_condition
|
207
207
|
if action_condition.nil?
|
@@ -37,22 +37,25 @@ module DynamicMigrations
|
|
37
37
|
raise ExpectedTableError, table unless table.is_a? Table
|
38
38
|
@table = table
|
39
39
|
|
40
|
-
# assert that the provided columns is an array
|
41
|
-
unless columns.is_a?(Array) && columns.count > 0
|
42
|
-
raise ExpectedArrayOfColumnsError
|
43
|
-
end
|
44
|
-
|
45
|
-
@columns = {}
|
46
|
-
columns.each do |column|
|
47
|
-
add_column column
|
48
|
-
end
|
49
|
-
|
50
40
|
raise ExpectedSymbolError, name unless name.is_a? Symbol
|
51
41
|
@name = name
|
52
42
|
|
53
43
|
raise ExpectedStringError, check_clause unless check_clause.is_a? String
|
54
44
|
@check_clause = check_clause.strip
|
55
45
|
|
46
|
+
# if this validation is created via configuration (apposed to being loaded) then they can be lazy loaded
|
47
|
+
unless from_configuration? && columns.nil?
|
48
|
+
# assert that the provided columns is an array
|
49
|
+
unless columns.is_a?(Array) && columns.count > 0
|
50
|
+
raise ExpectedArrayOfColumnsError
|
51
|
+
end
|
52
|
+
|
53
|
+
@columns = {}
|
54
|
+
columns.each do |column|
|
55
|
+
add_column column
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
56
59
|
unless description.nil?
|
57
60
|
raise ExpectedStringError, description unless description.is_a? String
|
58
61
|
@description = description.strip
|
@@ -80,11 +83,17 @@ module DynamicMigrations
|
|
80
83
|
|
81
84
|
# return an array of this validations columns
|
82
85
|
def columns
|
86
|
+
if @columns.nil?
|
87
|
+
@columns = {}
|
88
|
+
normalized_check_clause_and_column_names[:column_names].each do |column_name|
|
89
|
+
add_column table.column(column_name)
|
90
|
+
end
|
91
|
+
end
|
83
92
|
@columns.values
|
84
93
|
end
|
85
94
|
|
86
95
|
def column_names
|
87
|
-
|
96
|
+
columns.map(&:name)
|
88
97
|
end
|
89
98
|
|
90
99
|
def differences_descriptions other_validation
|
@@ -102,15 +111,19 @@ module DynamicMigrations
|
|
102
111
|
if from_database?
|
103
112
|
check_clause
|
104
113
|
else
|
105
|
-
|
114
|
+
normalized_check_clause_and_column_names[:check_clause]
|
106
115
|
end
|
107
116
|
end
|
108
117
|
|
109
118
|
private
|
110
119
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
120
|
+
def normalized_check_clause_and_column_names
|
121
|
+
@normalized_check_clause_and_column_names ||= fetch_normalized_check_clause_and_column_names
|
122
|
+
end
|
123
|
+
|
124
|
+
def fetch_normalized_check_clause_and_column_names
|
125
|
+
result = table.schema.database.with_connection do |connection|
|
126
|
+
# wrapped in a transaction just in case something here fails, because
|
114
127
|
# we don't want the temporary table to be persisted
|
115
128
|
connection.exec("BEGIN")
|
116
129
|
|
@@ -122,31 +135,46 @@ module DynamicMigrations
|
|
122
135
|
);
|
123
136
|
SQL
|
124
137
|
|
125
|
-
# get the
|
138
|
+
# get the normalized version of the constraint
|
126
139
|
rows = connection.exec(<<~SQL)
|
127
|
-
SELECT
|
140
|
+
SELECT
|
141
|
+
pg_get_constraintdef(pg_constraint.oid) AS check_clause,
|
142
|
+
ARRAY_AGG(col.attname ORDER BY u.attposition) AS column_names
|
128
143
|
FROM pg_constraint
|
129
|
-
|
144
|
+
LEFT JOIN LATERAL UNNEST(pg_constraint.conkey)
|
145
|
+
WITH ORDINALITY AS u(attnum, attposition)
|
146
|
+
ON TRUE
|
147
|
+
LEFT JOIN pg_attribute col
|
148
|
+
ON
|
149
|
+
(col.attrelid = pg_constraint.conrelid
|
150
|
+
AND col.attnum = u.attnum)
|
151
|
+
WHERE conrelid = 'validation_normalized_check_clause_temp_table'::regclass
|
152
|
+
GROUP BY pg_constraint.oid;
|
130
153
|
SQL
|
131
154
|
|
132
155
|
# delete the temp table and close the transaction
|
133
156
|
connection.exec("ROLLBACK")
|
134
157
|
|
135
|
-
|
136
|
-
rows.first["check_clause"]
|
158
|
+
rows.first
|
137
159
|
end
|
138
160
|
|
139
|
-
if
|
161
|
+
if result["check_clause"].nil?
|
140
162
|
raise UnnormalizableCheckClauseError, "Failed to nomalize check clause `#{check_clause}`"
|
141
163
|
end
|
142
164
|
|
143
165
|
# extract the check clause from the result "CHECK(%check_clause%)"
|
144
|
-
matches =
|
166
|
+
matches = result["check_clause"].match(/\ACHECK \((?<inner_clause>.*)\)\z/)
|
145
167
|
if matches.nil?
|
146
|
-
raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{
|
168
|
+
raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{result["check_clause"]}"
|
147
169
|
end
|
148
170
|
|
149
|
-
|
171
|
+
normalized_column_names = result["column_names"].gsub(/\A\{/, "").gsub(/\}\Z/, "").split(",").map { |column_name| column_name.to_sym }
|
172
|
+
|
173
|
+
# return the normalized check clause
|
174
|
+
{
|
175
|
+
check_clause: matches[:inner_clause],
|
176
|
+
column_names: normalized_column_names
|
177
|
+
}
|
150
178
|
end
|
151
179
|
|
152
180
|
# used internally to set the columns from this objects initialize method
|
@@ -42,7 +42,7 @@ module DynamicMigrations
|
|
42
42
|
if has_validation? name
|
43
43
|
raise(ValidationAlreadyExistsError, "Validation #{name} already exists")
|
44
44
|
end
|
45
|
-
columns = column_names
|
45
|
+
columns = column_names&.map { |column_name| column column_name }
|
46
46
|
included_target = self
|
47
47
|
if included_target.is_a? Table
|
48
48
|
new_validation = @validations[name] = Validation.new source, included_target, columns, name, check_clause, **validation_options
|
@@ -26,6 +26,7 @@ module DynamicMigrations
|
|
26
26
|
attr_reader :schema
|
27
27
|
attr_reader :name
|
28
28
|
attr_reader :description
|
29
|
+
attr_reader :remote_foreign_key_constraints
|
29
30
|
|
30
31
|
# initialize a new object to represent a postgres table
|
31
32
|
def initialize source, schema, name, description: nil
|
@@ -47,6 +48,7 @@ module DynamicMigrations
|
|
47
48
|
@validations = {}
|
48
49
|
@indexes = {}
|
49
50
|
@foreign_key_constraints = {}
|
51
|
+
@remote_foreign_key_constraints = []
|
50
52
|
@triggers = {}
|
51
53
|
@unique_constraints = {}
|
52
54
|
end
|
@@ -7,12 +7,14 @@ module DynamicMigrations
|
|
7
7
|
module StructureLoader
|
8
8
|
def create_database_structure_cache
|
9
9
|
connection.exec(<<~SQL)
|
10
|
-
CREATE MATERIALIZED VIEW
|
10
|
+
CREATE MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_structure_cache AS
|
11
11
|
SELECT
|
12
12
|
-- Name of the schema containing the table
|
13
13
|
schemata.schema_name,
|
14
14
|
-- Name of the table
|
15
15
|
tables.table_name,
|
16
|
+
-- is this a real table or a view
|
17
|
+
tables.table_type,
|
16
18
|
-- The comment which has been added to the table (if any)
|
17
19
|
table_description.description AS table_description,
|
18
20
|
-- Name of the column
|
@@ -24,6 +26,8 @@ module DynamicMigrations
|
|
24
26
|
-- YES if the column is possibly nullable, NO if
|
25
27
|
-- it is known not nullable
|
26
28
|
columns.is_nullable,
|
29
|
+
-- Is this column an array
|
30
|
+
columns.data_type = 'ARRAY' AS is_array,
|
27
31
|
-- The formatted data type (such as integer, char(5) or numeric(12,2)[])
|
28
32
|
CASE
|
29
33
|
WHEN tables.table_name IS NOT NULL THEN
|
@@ -34,12 +38,26 @@ module DynamicMigrations
|
|
34
38
|
)
|
35
39
|
END AS data_type,
|
36
40
|
-- is this an emum
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
CASE
|
42
|
+
WHEN columns.data_type = 'ARRAY' OR columns.data_type = 'USER-DEFINED' THEN
|
43
|
+
(
|
44
|
+
SELECT EXISTS (
|
45
|
+
SELECT 1
|
46
|
+
FROM pg_type
|
47
|
+
INNER JOIN pg_enum
|
48
|
+
ON pg_type.oid = pg_enum.enumtypid
|
49
|
+
INNER JOIN pg_namespace
|
50
|
+
ON pg_namespace.oid = pg_type.typnamespace
|
51
|
+
WHERE
|
52
|
+
-- when the column is an array, the udt_name is the name of the enum prefixed with an underscore
|
53
|
+
(columns.data_type = 'ARRAY' AND concat('_', pg_type.typname) = columns.udt_name AND pg_namespace.nspname = columns.udt_schema)
|
54
|
+
-- when the column is not an array, the udt_name is the same name as the enum
|
55
|
+
OR (columns.data_type = 'USER-DEFINED' AND pg_type.typname = columns.udt_name AND pg_namespace.nspname = columns.udt_schema)
|
56
|
+
)
|
57
|
+
)
|
58
|
+
ELSE FALSE
|
59
|
+
END
|
60
|
+
AS is_enum,
|
43
61
|
-- If data_type identifies an interval type, this column contains
|
44
62
|
-- the specification which fields the intervals include for this
|
45
63
|
-- column, e.g., YEAR TO MONTH, DAY TO SECOND, etc. If no field
|
@@ -55,20 +73,24 @@ module DynamicMigrations
|
|
55
73
|
LEFT JOIN pg_catalog.pg_description table_description ON table_description.objoid = pg_statio_all_tables.relid AND table_description.objsubid = 0
|
56
74
|
-- required for the column description/comment
|
57
75
|
LEFT JOIN pg_catalog.pg_description column_description ON column_description.objoid = pg_statio_all_tables.relid AND column_description.objsubid = columns.ordinal_position
|
58
|
-
WHERE
|
76
|
+
WHERE
|
77
|
+
-- skip internal postgres schemas
|
78
|
+
schemata.schema_name != 'information_schema'
|
59
79
|
AND schemata.schema_name != 'postgis'
|
60
80
|
AND left(schemata.schema_name, 3) != 'pg_'
|
81
|
+
-- only base tables (skip views), the null check is required for the left join as the schema might be empty
|
82
|
+
AND (tables IS NULL OR tables.table_type = 'BASE TABLE')
|
61
83
|
-- order by the schema and table names alphabetically, then by the column position in the table
|
62
84
|
ORDER BY schemata.schema_name, tables.table_schema, columns.ordinal_position
|
63
85
|
SQL
|
64
86
|
connection.exec(<<~SQL)
|
65
|
-
COMMENT ON MATERIALIZED VIEW
|
87
|
+
COMMENT ON MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_structure_cache IS 'A cached representation of the database structure. This is used by the dynamic migrations library and is created automatically and updated automatically after migrations have run.';
|
66
88
|
SQL
|
67
89
|
end
|
68
90
|
|
69
91
|
def refresh_database_structure_cache
|
70
92
|
connection.exec(<<~SQL)
|
71
|
-
REFRESH MATERIALIZED VIEW
|
93
|
+
REFRESH MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_structure_cache
|
72
94
|
SQL
|
73
95
|
rescue PG::UndefinedTable
|
74
96
|
create_database_structure_cache
|
@@ -79,12 +101,12 @@ module DynamicMigrations
|
|
79
101
|
def fetch_structure
|
80
102
|
begin
|
81
103
|
rows = connection.exec(<<~SQL)
|
82
|
-
SELECT * FROM
|
104
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_structure_cache
|
83
105
|
SQL
|
84
106
|
rescue PG::UndefinedTable
|
85
107
|
create_database_structure_cache
|
86
108
|
rows = connection.exec(<<~SQL)
|
87
|
-
SELECT * FROM
|
109
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_structure_cache
|
88
110
|
SQL
|
89
111
|
end
|
90
112
|
|
@@ -108,7 +130,8 @@ module DynamicMigrations
|
|
108
130
|
|
109
131
|
column[:data_type] = row["data_type"].to_sym
|
110
132
|
column[:null] = row["is_nullable"] == "YES"
|
111
|
-
column[:is_enum] = row["is_enum"] == "
|
133
|
+
column[:is_enum] = row["is_enum"] == "t"
|
134
|
+
column[:is_array] = row["is_array"] == "t"
|
112
135
|
column[:default] = row["column_default"]
|
113
136
|
column[:description] = row["column_description"]
|
114
137
|
column[:interval_type] = row["interval_type"].nil? ? nil : row["interval_type"].to_sym
|
@@ -7,7 +7,7 @@ module DynamicMigrations
|
|
7
7
|
module ValidationsLoader
|
8
8
|
def create_database_validations_cache
|
9
9
|
connection.exec(<<~SQL)
|
10
|
-
CREATE MATERIALIZED VIEW
|
10
|
+
CREATE MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache AS
|
11
11
|
SELECT
|
12
12
|
nspname AS schema_name,
|
13
13
|
pg_constraint_class.relname AS table_name,
|
@@ -38,16 +38,16 @@ module DynamicMigrations
|
|
38
38
|
conname;
|
39
39
|
SQL
|
40
40
|
connection.exec(<<~SQL)
|
41
|
-
CREATE UNIQUE INDEX dynamic_migrations_validations_cache_index ON
|
41
|
+
CREATE UNIQUE INDEX dynamic_migrations_validations_cache_index ON #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache (schema_name, table_name, validation_name);
|
42
42
|
SQL
|
43
43
|
connection.exec(<<~SQL)
|
44
|
-
COMMENT ON MATERIALIZED VIEW
|
44
|
+
COMMENT ON MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache IS 'A cached representation of the database validations. This is used by the dynamic migrations library and is created automatically and updated automatically after migrations have run.';
|
45
45
|
SQL
|
46
46
|
end
|
47
47
|
|
48
48
|
def refresh_database_validations_cache
|
49
49
|
connection.exec(<<~SQL)
|
50
|
-
REFRESH MATERIALIZED VIEW
|
50
|
+
REFRESH MATERIALIZED VIEW #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache
|
51
51
|
SQL
|
52
52
|
rescue PG::UndefinedTable
|
53
53
|
create_database_validations_cache
|
@@ -58,12 +58,12 @@ module DynamicMigrations
|
|
58
58
|
def fetch_validations
|
59
59
|
begin
|
60
60
|
rows = connection.exec(<<~SQL)
|
61
|
-
SELECT * FROM
|
61
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache
|
62
62
|
SQL
|
63
63
|
rescue PG::UndefinedTable
|
64
64
|
create_database_validations_cache
|
65
65
|
rows = connection.exec(<<~SQL)
|
66
|
-
SELECT * FROM
|
66
|
+
SELECT * FROM #{Postgres.cache_schema_name}.dynamic_migrations_validations_cache
|
67
67
|
SQL
|
68
68
|
end
|
69
69
|
|
@@ -1,8 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DynamicMigrations
|
4
|
-
# This module exists only to namespace Postgres functionality and
|
5
|
-
# make it possible to add other database/storage types in the future.
|
6
4
|
module Postgres
|
5
|
+
# The default behaviour of dynamic migrations is to generate migrations
|
6
|
+
# which remove any unused extensions.
|
7
|
+
# People don't always have control over which extensions are running on the
|
8
|
+
# database, so this behaviour can be disabled by setting
|
9
|
+
# `DynamicMigrations::Postgres.remove_unused_extensions = false`
|
10
|
+
def self.remove_unused_extensions= value
|
11
|
+
@remove_unused_extensions = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# defaults to true, but can be set to false to disable the removal of unused
|
15
|
+
# extensions
|
16
|
+
def self.remove_unused_extensions?
|
17
|
+
(@remove_unused_extensions.nil? || @remove_unused_extensions) ? true : false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Dynamic Migrations creates a materialized view to store a cache representation
|
21
|
+
# of various parts of the database structure, by default this is created in the
|
22
|
+
# public schema, but this can be changed by setting the otion below.
|
23
|
+
def self.cache_schema_name= value
|
24
|
+
@cache_schema_name = value
|
25
|
+
end
|
26
|
+
|
27
|
+
# defaults to true, but can be set to false to disable the removal of unused
|
28
|
+
# extensions
|
29
|
+
def self.cache_schema_name
|
30
|
+
@cache_schema_name || :public
|
31
|
+
end
|
7
32
|
end
|
8
33
|
end
|
data/lib/dynamic_migrations.rb
CHANGED
@@ -10,6 +10,8 @@ require "dynamic_migrations/expected_integer_error"
|
|
10
10
|
require "dynamic_migrations/expected_boolean_error"
|
11
11
|
require "dynamic_migrations/module_included_into_unexpected_target_error"
|
12
12
|
|
13
|
+
require "dynamic_migrations/postgres"
|
14
|
+
|
13
15
|
require "dynamic_migrations/postgres/server/database/connection"
|
14
16
|
require "dynamic_migrations/postgres/server/database/structure_loader"
|
15
17
|
require "dynamic_migrations/postgres/server/database/validations_loader"
|
@@ -20,6 +20,8 @@ module DynamicMigrations
|
|
20
20
|
def array?: -> bool
|
21
21
|
def enum?: -> bool
|
22
22
|
def temp_table_data_type: -> Symbol
|
23
|
+
# untyped because we cant specify this logic in rbs yet (compiler is concerned this might be nil)
|
24
|
+
def base_data_type: -> untyped
|
23
25
|
|
24
26
|
class ExpectedTableError < StandardError
|
25
27
|
end
|
data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rbs
CHANGED
@@ -12,6 +12,7 @@ module DynamicMigrations
|
|
12
12
|
def foreign_key_constraints: -> Array[ForeignKeyConstraint]
|
13
13
|
def foreign_key_constraints_hash: -> Hash[Symbol, ForeignKeyConstraint]
|
14
14
|
def add_foreign_key_constraint: (Symbol name, Array[Symbol] column_names, Symbol foreign_schema_name, Symbol foreign_table_name, Array[Symbol] foreign_column_names, **untyped) -> untyped
|
15
|
+
def add_remote_foreign_key_constraint: (ForeignKeyConstraint foreign_key_constraint) -> untyped
|
15
16
|
|
16
17
|
# these come from the table object (which this module is included into)
|
17
18
|
def source: -> database_or_configuration
|
@@ -6,7 +6,7 @@ module DynamicMigrations
|
|
6
6
|
class Table
|
7
7
|
class Validation < Source
|
8
8
|
@columns: Hash[Symbol, Column]
|
9
|
-
@
|
9
|
+
@normalized_check_clause_and_column_names: {check_clause: String, column_names: Array[Symbol]}?
|
10
10
|
|
11
11
|
attr_reader table: Table
|
12
12
|
attr_reader name: Symbol
|
@@ -16,7 +16,7 @@ module DynamicMigrations
|
|
16
16
|
attr_reader description: String?
|
17
17
|
attr_reader template: Symbol?
|
18
18
|
|
19
|
-
def initialize: (database_or_configuration source, Table table, Array[Column] columns, Symbol name, String check_clause, ?deferrable: bool, ?initially_deferred: bool, ?description: String?, ?template: Symbol?) -> void
|
19
|
+
def initialize: (database_or_configuration source, Table table, Array[Column]? columns, Symbol name, String check_clause, ?deferrable: bool, ?initially_deferred: bool, ?description: String?, ?template: Symbol?) -> void
|
20
20
|
def columns: -> Array[Column]
|
21
21
|
def column_names: -> Array[Symbol]
|
22
22
|
def has_description?: -> bool
|
@@ -24,7 +24,9 @@ module DynamicMigrations
|
|
24
24
|
def normalized_check_clause: -> String
|
25
25
|
|
26
26
|
private
|
27
|
-
|
27
|
+
|
28
|
+
def normalized_check_clause_and_column_names: -> {check_clause: String, column_names: Array[Symbol]}
|
29
|
+
def fetch_normalized_check_clause_and_column_names: -> {check_clause: String, column_names: Array[Symbol]}
|
28
30
|
|
29
31
|
def add_column: (Column column) -> void
|
30
32
|
|
@@ -11,7 +11,7 @@ module DynamicMigrations
|
|
11
11
|
def has_validation?: (Symbol name) -> bool
|
12
12
|
def validations: -> Array[Validation]
|
13
13
|
def validations_hash: -> Hash[Symbol, Validation]
|
14
|
-
def add_validation: (Symbol name, Array[Symbol] column_names, String check_clause, **untyped) -> untyped
|
14
|
+
def add_validation: (Symbol name, Array[Symbol]? column_names, String check_clause, **untyped) -> untyped
|
15
15
|
|
16
16
|
# these come from the table object (which this module is included into)
|
17
17
|
def source: -> database_or_configuration
|
@@ -16,6 +16,7 @@ module DynamicMigrations
|
|
16
16
|
attr_reader schema: Schema
|
17
17
|
attr_reader name: Symbol
|
18
18
|
attr_reader description: String?
|
19
|
+
attr_reader remote_foreign_key_constraints: Array[ForeignKeyConstraint]
|
19
20
|
def initialize: (database_or_configuration source, Schema schema, Symbol name, ?description: String?) -> void
|
20
21
|
def has_description?: -> bool
|
21
22
|
def add_primary_key: (Symbol name, Array[Symbol] column_names, **untyped) -> untyped
|
@@ -1,4 +1,12 @@
|
|
1
1
|
module DynamicMigrations
|
2
2
|
module Postgres
|
3
|
+
self.@remove_unused_extensions: bool?
|
4
|
+
self.@cache_schema_name: Symbol?
|
5
|
+
|
6
|
+
def self.remove_unused_extensions=: (bool value) -> bool
|
7
|
+
def self.remove_unused_extensions?: -> bool
|
8
|
+
|
9
|
+
def self.cache_schema_name=: (Symbol value) -> Symbol
|
10
|
+
def self.cache_schema_name: -> Symbol
|
3
11
|
end
|
4
12
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamic_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Craig Ulliott
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|