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 +4 -4
- data/README.md +7 -3
- data/lib/code0/zero_track/database/migration_helpers/table_partitioning.rb +5 -3
- data/lib/code0/zero_track/database/partitioning/partition_manager.rb +69 -0
- data/lib/code0/zero_track/database/partitioning/partitioned_table.rb +10 -0
- data/lib/code0/zero_track/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7bc699d8f0bb3f1237eeb8ff75493624930bc8cce4b97ba1666709c494d4d04
|
|
4
|
+
data.tar.gz: aac0a33b9d880e0fd9f5a6297b76f0dd32c5f42f6beccad0afeea412caa78bd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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(
|
|
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
|