dynamic_migrations 3.6.16 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb +3 -1
  4. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +6 -6
  5. data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +3 -1
  6. data/lib/dynamic_migrations/postgres/server/database/schema/enums.rb +3 -1
  7. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +1 -1
  8. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -0
  9. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +4 -7
  10. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rb +5 -0
  11. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +2 -2
  12. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +52 -24
  13. data/lib/dynamic_migrations/postgres/server/database/schema/table/validations.rb +1 -1
  14. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +2 -0
  15. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +36 -13
  16. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +6 -6
  17. data/lib/dynamic_migrations/postgres.rb +27 -2
  18. data/lib/dynamic_migrations/version.rb +1 -1
  19. data/lib/dynamic_migrations.rb +2 -0
  20. data/sig/dynamic_migrations/postgres/server/database/schema/table/column.rbs +2 -0
  21. data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rbs +0 -3
  22. data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rbs +1 -0
  23. data/sig/dynamic_migrations/postgres/server/database/schema/table/validation.rbs +5 -3
  24. data/sig/dynamic_migrations/postgres/server/database/schema/table/validations.rbs +1 -1
  25. data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +1 -0
  26. data/sig/dynamic_migrations/postgres.rbs +8 -0
  27. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 166ee1f91202640e9ad50fd80c0cb4fade2993ba6ca873ff4bdbf207d939ce81
4
- data.tar.gz: e23bc180aa98886aecd575a4b869d595d08f4d90136a59e7de46b7f42b6cbe11
3
+ metadata.gz: 437a84189a939e185f89db2de8395bc1fb8dc9f159b3948a97dcc5b04c4fedb7
4
+ data.tar.gz: 6e1df320ac06c0b0ca9d6aeb3abf684e47b9daac4ee1def93180de801f73125b
5
5
  SHA512:
6
- metadata.gz: e3b9df7db4f4eed952f7144c52eda7a46447d66cdb83e44fff32f97f335cfb78e03ebd87d0e000f781b8c596a21615ccff085933032c8e2534d900864b242f36
7
- data.tar.gz: e0ce6d30f2d346dbbb69ae506674aef00dc49eefe04c48d7cf7c8e81e06fea11fe5afb33e7822e87302c1d514d8be5da16070ecbc622829e51bfecc87f650885
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
 
@@ -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
- @generator.disable_extension extension_name
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 public.dynamic_migrations_keys_and_unique_constraints_cache as
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 public.dynamic_migrations_keys_and_unique_constraints_cache (schema_name, table_name, constraint_name);
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 public.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.';
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 public.dynamic_migrations_keys_and_unique_constraints_cache
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 public.dynamic_migrations_keys_and_unique_constraints_cache
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 public.dynamic_migrations_keys_and_unique_constraints_cache
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
- enum_schema, enum_name = column_definition[:data_type].to_s.split(".")
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
- raise EnumDoesNotExistError unless has_enum? enum_name
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
@@ -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 validation in a postgres table
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 validation and fetch
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
- @columns.keys.sort
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
- @normalized_check_clause ||= fetch_normalized_check_clause
114
+ normalized_check_clause_and_column_names[:check_clause]
106
115
  end
107
116
  end
108
117
 
109
118
  private
110
119
 
111
- def fetch_normalized_check_clause
112
- ncc = table.schema.database.with_connection do |connection|
113
- # wrapped in a transaction jsut in case something here fails, because
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 normalzed version of the constraint
138
+ # get the normalized version of the constraint
126
139
  rows = connection.exec(<<~SQL)
127
- SELECT pg_get_constraintdef(pg_constraint.oid) AS check_clause
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
- WHERE conrelid = 'validation_normalized_check_clause_temp_table'::regclass;
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
- # return the normalized check clause
136
- rows.first["check_clause"]
158
+ rows.first
137
159
  end
138
160
 
139
- if ncc.nil?
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 = ncc.match(/\ACHECK \((?<inner_clause>.*)\)\z/)
166
+ matches = result["check_clause"].match(/\ACHECK \((?<inner_clause>.*)\)\z/)
145
167
  if matches.nil?
146
- raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{ncc}"
168
+ raise UnnormalizableCheckClauseError, "Unparsable normalized check_clause #{result["check_clause"]}"
147
169
  end
148
170
 
149
- matches[:inner_clause]
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.map { |column_name| column column_name }
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 public.dynamic_migrations_structure_cache AS
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
- EXISTS (
38
- SELECT 1
39
- FROM pg_type typ
40
- INNER JOIN pg_enum enu ON typ.oid = enu.enumtypid
41
- WHERE typ.typname = columns.udt_name
42
- ) AS is_enum,
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 schemata.schema_name != 'information_schema'
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 public.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.';
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 public.dynamic_migrations_structure_cache
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 public.dynamic_migrations_structure_cache
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 public.dynamic_migrations_structure_cache
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"] == "TRUE"
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 public.dynamic_migrations_validations_cache AS
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 public.dynamic_migrations_validations_cache (schema_name, table_name, validation_name);
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 public.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.';
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 public.dynamic_migrations_validations_cache
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 public.dynamic_migrations_validations_cache
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 public.dynamic_migrations_validations_cache
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamicMigrations
4
- VERSION = "3.6.16"
4
+ VERSION = "3.8.0"
5
5
  end
@@ -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
@@ -38,9 +38,6 @@ module DynamicMigrations
38
38
  class ExpectedArrayOfColumnsError < StandardError
39
39
  end
40
40
 
41
- class ExpectedDifferentTablesError < StandardError
42
- end
43
-
44
41
  class DuplicateColumnError < StandardError
45
42
  end
46
43
 
@@ -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
- @normalized_check_clause: String?
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
- def fetch_normalized_check_clause: -> String
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.6.16
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-09-16 00:00:00.000000000 Z
11
+ date: 2023-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg