code0-zero_track 0.0.8 → 0.0.10

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: d6150c745af141848ee0848ec9beda4db5c1809ece022f90b9006cf69df88f10
4
- data.tar.gz: 73128d1d5ab33dec7887b195831c8a1b591eb46999c5cb4e20eb79387d5d138e
3
+ metadata.gz: b7bc699d8f0bb3f1237eeb8ff75493624930bc8cce4b97ba1666709c494d4d04
4
+ data.tar.gz: aac0a33b9d880e0fd9f5a6297b76f0dd32c5f42f6beccad0afeea412caa78bd9
5
5
  SHA512:
6
- metadata.gz: 129416f538b904432c213a42a22d95244220fb9c35548b089d9a9ef4836b11b1b3fd0a93c910ab748dfd51d024d56d00ac525e7fd22855330916462b848a3824
7
- data.tar.gz: 78bba0f4d37e6a08b2d5b02a46478060e7ea944ee8a40c407a4f63c819c450ae41e86b33df9b47a28472ebb202dad28a49c2c95fa221de041d672de1164e8a6d
6
+ metadata.gz: 60edef2bb7d288aeb3127068d6d58099cb409b659a5401a62db2844317cd4aa7048f7b8146ae59234f0e2c3c20e4af1a2e94ef91ac0561c62987f9977193e1c6
7
+ data.tar.gz: 02f209d8121437c829735e11bb9896c0991f2927d484da5c5f8f1f52c1cb7dd6d2363581f4d9886f285052339685ae942972666d497db6e03e6f25a0ed5b0c86
data/README.md CHANGED
@@ -87,9 +87,10 @@ config.zero_track.db_partitioning.base_ar_class = 'ActiveRecord::Base' # default
87
87
  Include the migration helpers by inheriting from `Code0::ZeroTrack::Database::Migration[1.0]` (or the
88
88
  appropriate version). The following methods become available:
89
89
 
90
- `create_partition_by_date_table(table_name, partition_column:, **options, &block)` creates a table
91
- partitioned by range on the given column. It automatically sets up a composite primary key
92
- of `(id, partition_column)`.
90
+ `create_partition_by_date_table(table_name, partition_column:, primary_key: nil, **options, &block)` creates a table
91
+ partitioned by range on the given column. By default, it adds a `bigserial` id column and sets up a composite
92
+ primary key of `(id, partition_column)`. If `primary_key` is provided (e.g. `primary_key: %i[date other_column]`),
93
+ the id column is omitted and the given columns are used as the primary key instead.
93
94
 
94
95
  `create_dynamic_partition_schema` / `drop_dynamic_partition_schema` creates or drops the schema
95
96
  used for storing dynamic partitions.
@@ -188,6 +189,9 @@ When tables have foreign key relationships, registration order and retention con
188
189
  - A child table's `retain_for` must be less than or equal to the parent table's `retain_for`.
189
190
  If a child retains partitions longer than its parent, dropping the parent partition will
190
191
  fail because the child's foreign key still references it.
192
+ - Foreign keys between the partitioned tables must be declared with `drop_foreign_keys_on_detach`
193
+ on the model. Otherwise, the foreign key of a detached partition will block detaching partitions
194
+ of the referenced table.
191
195
 
192
196
  #### Schema Cleaner Integration
193
197
 
@@ -5,21 +5,23 @@ module Code0
5
5
  module Database
6
6
  module MigrationHelpers
7
7
  module TablePartitioning
8
- def create_partition_by_date_table(table_name, partition_column:, **options, &block)
8
+ def create_partition_by_date_table(table_name, partition_column:, primary_key: nil, **options, &block)
9
9
  options[:options] = "PARTITION BY RANGE (#{quote_column_name(partition_column)})"
10
10
  options[:id] = false
11
11
 
12
12
  create_table(table_name, **options) do |t|
13
- t.bigserial :id, null: false
13
+ t.bigserial :id, null: false unless primary_key
14
14
 
15
15
  block.call(t)
16
16
  end
17
17
 
18
+ pk_columns = primary_key || [:id, partition_column]
19
+
18
20
  reversible do |dir|
19
21
  dir.up do
20
22
  execute <<~SQL.squish
21
23
  ALTER TABLE #{quote_table_name(table_name)}
22
- ADD PRIMARY KEY (#{quote_column_name(:id)}, #{quote_column_name(partition_column)})
24
+ ADD PRIMARY KEY (#{pk_columns.map { |c| quote_column_name(c) }.join(', ')})
23
25
  SQL
24
26
  end
25
27
  end
@@ -64,6 +64,8 @@ module Code0
64
64
  model.partitioning_strategy.partitions_to_detach.each do |partition|
65
65
  detach_partition(partition, connection)
66
66
  end
67
+
68
+ drop_declared_foreign_keys_from_detached_partitions(connection)
67
69
  end
68
70
  end
69
71
 
@@ -109,6 +111,73 @@ module Code0
109
111
  )
110
112
  end
111
113
 
114
+ def drop_declared_foreign_keys_from_detached_partitions(connection)
115
+ constraint_names = model.foreign_keys_to_drop_on_detach || []
116
+ return if constraint_names.empty?
117
+
118
+ warn_about_unknown_foreign_keys(constraint_names, connection)
119
+
120
+ detached_foreign_keys(constraint_names, connection).each do |qualified_table, constraint_name|
121
+ connection.execute(
122
+ "ALTER TABLE #{qualified_table} " \
123
+ "DROP CONSTRAINT #{connection.quote_column_name(constraint_name)}"
124
+ )
125
+
126
+ logger.info(
127
+ message: 'Dropped foreign key from detached partition',
128
+ table_name: model.table_name,
129
+ partition_name: qualified_table,
130
+ constraint_name: constraint_name
131
+ )
132
+ end
133
+ end
134
+
135
+ # We check the parent table because we drop the FK from the partition
136
+ # when detaching. A missing FK on the detached partition is an expected state
137
+ def warn_about_unknown_foreign_keys(constraint_names, connection)
138
+ existing = parent_foreign_key_names(connection)
139
+ unknown = constraint_names - existing
140
+ return if unknown.empty?
141
+
142
+ logger.warn(
143
+ message: 'Configured foreign keys to drop on detach do not exist on the partitioned table',
144
+ table_name: model.table_name,
145
+ constraint_names: unknown
146
+ )
147
+ end
148
+
149
+ def parent_foreign_key_names(connection)
150
+ connection.select_values(<<~SQL.squish)
151
+ SELECT con.conname
152
+ FROM pg_catalog.pg_constraint con
153
+ WHERE con.contype = 'f'
154
+ AND con.conrelid = #{connection.quote(model.table_name)}::regclass
155
+ SQL
156
+ end
157
+
158
+ def detached_foreign_keys(constraint_names, connection)
159
+ schema = Rails.application.config.zero_track.db_partitioning.dynamic_partition_schema
160
+ quoted_names = constraint_names.map { |name| connection.quote(name) }.join(', ')
161
+
162
+ rows = connection.select_rows(<<~SQL.squish)
163
+ SELECT (quote_ident(n.nspname) || '.' || quote_ident(c.relname)) AS qualified_table,
164
+ con.conname AS constraint_name
165
+ FROM pg_catalog.pg_constraint con
166
+ JOIN pg_catalog.pg_class c ON c.oid = con.conrelid
167
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
168
+ WHERE con.contype = 'f'
169
+ AND con.conname IN (#{quoted_names})
170
+ AND n.nspname = #{connection.quote(schema)}
171
+ AND c.relkind = 'r'
172
+ AND NOT c.relispartition
173
+ AND NOT EXISTS (
174
+ SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid
175
+ )
176
+ SQL
177
+
178
+ rows.map { |qualified_table, constraint_name| [qualified_table, constraint_name] }
179
+ end
180
+
112
181
  def drop_partition(detached_partition, connection)
113
182
  schema_name = connection.quote_table_name(detached_partition.schema)
114
183
  partition_name = connection.quote_table_name(detached_partition.name)
@@ -25,6 +25,16 @@ module Code0
25
25
 
26
26
  @partitioning_strategy = strategy_class.new(self, column, **kwargs)
27
27
  end
28
+
29
+ def drop_foreign_keys_on_detach(*constraint_names)
30
+ @foreign_keys_to_drop_on_detach ||= []
31
+ @foreign_keys_to_drop_on_detach.concat(constraint_names.flatten.map(&:to_s))
32
+ @foreign_keys_to_drop_on_detach
33
+ end
34
+
35
+ def foreign_keys_to_drop_on_detach
36
+ @foreign_keys_to_drop_on_detach || []
37
+ end
28
38
  end
29
39
  end
30
40
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Code0
4
4
  module ZeroTrack
5
- VERSION = '0.0.8'
5
+ VERSION = '0.0.10'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code0-zero_track
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Niklas van Schrick