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.
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