dynamic_migrations 3.6.16 → 3.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 166ee1f91202640e9ad50fd80c0cb4fade2993ba6ca873ff4bdbf207d939ce81
4
- data.tar.gz: e23bc180aa98886aecd575a4b869d595d08f4d90136a59e7de46b7f42b6cbe11
3
+ metadata.gz: 22fd29f59924e820da26858d1cf6caf04b03985ecc2fafd5f49205f5a0c2b34e
4
+ data.tar.gz: a8164cec22385ede8954df04e86c866bdb9d1ae4d420ba9107353f96e1a525e8
5
5
  SHA512:
6
- metadata.gz: e3b9df7db4f4eed952f7144c52eda7a46447d66cdb83e44fff32f97f335cfb78e03ebd87d0e000f781b8c596a21615ccff085933032c8e2534d900864b242f36
7
- data.tar.gz: e0ce6d30f2d346dbbb69ae506674aef00dc49eefe04c48d7cf7c8e81e06fea11fe5afb33e7822e87302c1d514d8be5da16070ecbc622829e51bfecc87f650885
6
+ metadata.gz: ec4eb2339d1ce110393eae8747c9e2aabee426d58a4e3caca8e01b7dd48ddfe99291ab903c9c7ba2e474a8b509a7185850132c1934f512c8ebe710e763243ba9
7
+ data.tar.gz: 50076bbed1405f301476ac49f0886207f9fde401c47af062fafa4095139cd3287699b8054cac4583b97422180f8fb3be935d94ac87b1ccd071a293972811eec7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.0](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.16...v3.7.0) (2023-09-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * providing access to foreign key constraints from both sides of the association ([48dcf1c](https://github.com/craigulliott/dynamic_migrations/commit/48dcf1cd4cdb23bc37da3e47b00f8007b8bc0f8a))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * allowing foreign keys to the same table because they are valid and sometimes useful ([0566384](https://github.com/craigulliott/dynamic_migrations/commit/0566384a4cfeade757d9806059035477f7c41ee2))
14
+ * 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))
15
+ * providing convenience method to retrieve columns base data type from array columns ([cd3d9bf](https://github.com/craigulliott/dynamic_migrations/commit/cd3d9bf06682335a890d9b11133d397d7bcd50af))
16
+ * semi colon at the end of function definitions is now optional ([ca9b3aa](https://github.com/craigulliott/dynamic_migrations/commit/ca9b3aa04da23605ff61b2ec14baed927145d2c3))
17
+ * structure loader was not identifying enums properly ([95276d3](https://github.com/craigulliott/dynamic_migrations/commit/95276d33f98f41eca9a9467bb6abeb458a37f16b))
18
+
3
19
  ## [3.6.16](https://github.com/craigulliott/dynamic_migrations/compare/v3.6.15...v3.6.16) (2023-09-16)
4
20
 
5
21
 
@@ -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
@@ -108,7 +108,7 @@ module DynamicMigrations
108
108
 
109
109
  column[:data_type] = row["data_type"].to_sym
110
110
  column[:null] = row["is_nullable"] == "YES"
111
- column[:is_enum] = row["is_enum"] == "TRUE"
111
+ column[:is_enum] = row["is_enum"] == "t"
112
112
  column[:default] = row["column_default"]
113
113
  column[:description] = row["column_description"]
114
114
  column[:interval_type] = row["interval_type"].nil? ? nil : row["interval_type"].to_sym
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamicMigrations
4
- VERSION = "3.6.16"
4
+ VERSION = "3.7.0"
5
5
  end
@@ -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
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.7.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-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg