partition_gardener 0.3.3 → 0.3.4
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/CHANGELOG.md +4 -0
- data/SECURITY.md +10 -12
- data/docs/THIRD_PARTY_LICENSE_MANIFEST.tsv +3 -3
- data/docs/audit_reference.md +2 -0
- data/docs/configuration.md +4 -0
- data/docs/schemas/partition_garden.schema.json +4 -0
- data/lib/partition_gardener/audit.rb +16 -0
- data/lib/partition_gardener/child_column_align.rb +165 -0
- data/lib/partition_gardener/config_document.rb +9 -0
- data/lib/partition_gardener/configuration.rb +2 -0
- data/lib/partition_gardener/executor.rb +20 -2
- data/lib/partition_gardener/templates.rb +6 -0
- data/lib/partition_gardener/version.rb +1 -1
- data/lib/partition_gardener.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4be8b78232ba13211d0baad009dea43f6320b96dd9b82398c8a4e86ed6008b3d
|
|
4
|
+
data.tar.gz: acb6c02d0192c07e3b8c9f959b6ada44e44014f9f5c144906e6649ebb1309e87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f387a844418342ef9f4ff644faf1ca2431f8259e3f26e9220bab9ce225ae5f1606ceb4651a94cb39f0e7929a7a0b7c2eae779c6aa7d079a8ad4355901f39a63f
|
|
7
|
+
data.tar.gz: a659acdb2ed364d72f44a9ecb0aab7ac925b27b77f8365e9da36582416145c9db9bbe6a1e9da279be036eab0cb4eda3d4a865d5a86a22741564bbcbf87145d32
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.4 (2026-09-02)
|
|
6
|
+
|
|
7
|
+
- Align LIKE and detached children with additive parent columns before `ATTACH PARTITION`, preserving type, collation, default, and `NOT NULL` atomically. Audit warns when a managed child still lags. Set `align_child_columns: false` to fail fast instead.
|
|
8
|
+
|
|
5
9
|
## 0.3.3 (2026-08-13)
|
|
6
10
|
|
|
7
11
|
- Roll the sliding-window current partition before attaching the prior month's archive so a month rollover detaches the old current span and attaches that month as its own child
|
data/SECURITY.md
CHANGED
|
@@ -2,26 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Reporting a Vulnerability
|
|
4
4
|
|
|
5
|
-
Do not open a public issue for security vulnerabilities.
|
|
6
|
-
|
|
7
5
|
Email security details to: security@kiskolabs.com
|
|
8
6
|
|
|
9
|
-
Include
|
|
7
|
+
Send the report only to that address. Include description, steps to reproduce, potential impact, and a suggested fix when you have one.
|
|
10
8
|
|
|
11
9
|
### Response Timeline
|
|
12
10
|
|
|
13
|
-
- We will acknowledge receipt of your report
|
|
14
|
-
- We will provide an initial assessment
|
|
15
|
-
- We will keep you informed of our progress and resolution timeline
|
|
11
|
+
- We will acknowledge receipt of your report.
|
|
12
|
+
- We will provide an initial assessment.
|
|
13
|
+
- We will keep you informed of our progress and resolution timeline.
|
|
16
14
|
|
|
17
15
|
### Disclosure Policy
|
|
18
16
|
|
|
19
|
-
- We will work with you to understand and resolve the issue
|
|
20
|
-
- We will credit you for the discovery
|
|
21
|
-
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
-
- We will coordinate public disclosure with you
|
|
17
|
+
- We will work with you to understand and resolve the issue.
|
|
18
|
+
- We will credit you for the discovery unless you prefer to remain anonymous.
|
|
19
|
+
- We will publish a security advisory after the vulnerability is patched.
|
|
20
|
+
- We will coordinate public disclosure with you.
|
|
23
21
|
|
|
24
22
|
## Automation Security
|
|
25
23
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
24
|
+
- Keep production credentials, API keys, and personal data out of prompts sent to third-party models or automation services.
|
|
25
|
+
- Verify automated dependencies.
|
|
@@ -19,16 +19,16 @@ root logger 1.7.0 Ruby|BSD-2-Clause
|
|
|
19
19
|
root minitest 6.0.6 MIT
|
|
20
20
|
root parallel 2.1.0 MIT
|
|
21
21
|
root parser 3.3.12.0 MIT
|
|
22
|
-
root partition_gardener 0.3.
|
|
22
|
+
root partition_gardener 0.3.4 MIT
|
|
23
23
|
root pg 1.6.3 BSD-2-Clause
|
|
24
|
-
root polyrun 2.2.
|
|
24
|
+
root polyrun 2.2.3 MIT
|
|
25
25
|
root prism 1.9.0 MIT
|
|
26
26
|
root prosopite 2.2.0 Apache-2.0
|
|
27
27
|
root racc 1.8.1 Ruby|BSD-2-Clause
|
|
28
28
|
root rack 3.2.6 MIT
|
|
29
29
|
root rainbow 3.1.1 MIT
|
|
30
30
|
root rake 13.4.2 MIT
|
|
31
|
-
root rbs 4.
|
|
31
|
+
root rbs 4.2.0 BSD-2-Clause|Ruby
|
|
32
32
|
root regexp_parser 2.12.0 MIT
|
|
33
33
|
root rspec 3.13.2 MIT
|
|
34
34
|
root rspec-core 3.13.6 MIT
|
data/docs/audit_reference.md
CHANGED
|
@@ -36,6 +36,8 @@ Horizon warning threshold: 30 days (`Audit::HORIZON_WARNING_DAYS`). High child c
|
|
|
36
36
|
|
|
37
37
|
`partition gap: no attached tail partition extends to MAXVALUE` — missing future/open tail. `apply` to attach `_future`.
|
|
38
38
|
|
|
39
|
+
`child {name} is missing column {columns} from parent {table}` — a managed LIKE or detached child lacks ordinary columns added on the parent later. `apply` adds those columns atomically then attaches (default). Set `align_child_columns: false` to fail fast. Built-in naming schemes exclude unrelated prefixed tables; custom partition name formatters retain prefix discovery.
|
|
40
|
+
|
|
39
41
|
Gap objects include `range_start`, `range_end`, and `message`.
|
|
40
42
|
|
|
41
43
|
## Plan report
|
data/docs/configuration.md
CHANGED
|
@@ -53,6 +53,8 @@ When the gem loads inside Rails, the railtie sets `connection_resolver` and `tod
|
|
|
53
53
|
|
|
54
54
|
`retention_detach_concurrently` (default: `false`) — detach dropped archive partitions with `CONCURRENTLY` (global default).
|
|
55
55
|
|
|
56
|
+
`align_child_columns` (default: `true`) — before `ATTACH PARTITION`, add ordinary parent columns that a LIKE or detached child is missing. Audit warns when a child still lags. Set `false` to fail fast when a child is missing those columns.
|
|
57
|
+
|
|
56
58
|
### Advisory locks
|
|
57
59
|
|
|
58
60
|
Each table run acquires `pg_advisory_lock(hashtext('partition_gardener'), hashtext(table_name))` (or the transaction-scoped variant).
|
|
@@ -164,6 +166,8 @@ All templates accept shared options passed through to the registry hash:
|
|
|
164
166
|
|
|
165
167
|
`retention_detach_concurrently` (default: global default) — per-table override for concurrent detach.
|
|
166
168
|
|
|
169
|
+
`align_child_columns` (default: global default) — per-table override. Additive only: add missing ordinary columns in one statement (skip identity and generated), preserving type, non-default collation, default, and `NOT NULL`. If existing rows cannot satisfy `NOT NULL`, PostgreSQL rejects the complete alignment and leaves the child unchanged. Existing types, extra child columns, and other constraints stay as they are.
|
|
170
|
+
|
|
167
171
|
`maintenance_backend` (default: `:gardener`) — `:gardener`, `:pg_partman`, or `:hybrid_layout_only`.
|
|
168
172
|
|
|
169
173
|
`incremental_rebalance` (default: global default) — per-table override.
|
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
"retention_detach_concurrently": {
|
|
93
93
|
"type": "boolean"
|
|
94
94
|
},
|
|
95
|
+
"align_child_columns": {
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"description": "When true (default), atomically add missing ordinary parent columns with their collation, default, and NOT NULL contract on a LIKE or detached child before ATTACH PARTITION"
|
|
98
|
+
},
|
|
95
99
|
"hash_modulus": {
|
|
96
100
|
"type": "integer",
|
|
97
101
|
"minimum": 2,
|
|
@@ -61,6 +61,10 @@ module PartitionGardener
|
|
|
61
61
|
warnings << "partition gap: #{gap.message}"
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
warning_options = {}
|
|
65
|
+
warning_options[:candidate_table] = method(:managed_child_table?) if @config
|
|
66
|
+
warnings.concat(ChildColumnAlign.lagging_child_warnings(@table_name, **warning_options))
|
|
67
|
+
|
|
64
68
|
AuditResult.new(
|
|
65
69
|
table_name: @table_name,
|
|
66
70
|
partitioned: true,
|
|
@@ -74,6 +78,18 @@ module PartitionGardener
|
|
|
74
78
|
|
|
75
79
|
private
|
|
76
80
|
|
|
81
|
+
def managed_child_table?(child_table)
|
|
82
|
+
return true if @config[:partition_name_format_is_custom] || @config[:archive_partition_name_format]
|
|
83
|
+
return true if child_table == Naming.rebalance_staging_partition_name(@table_name)
|
|
84
|
+
|
|
85
|
+
strategy = (@strategy ||= Strategy.for(@config))
|
|
86
|
+
return true if strategy.respond_to?(:tail_slot_name?) && strategy.tail_slot_name?(child_table)
|
|
87
|
+
|
|
88
|
+
!strategy.archive_bucket_from_partition_name(child_table).nil?
|
|
89
|
+
rescue Date::Error
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
77
93
|
def compute_horizon_days(partitions)
|
|
78
94
|
today = PartitionGardener.configuration.today
|
|
79
95
|
finite_ends = partitions.filter_map do |partition|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
module PartitionGardener
|
|
2
|
+
class ChildColumnAlign
|
|
3
|
+
Column = Data.define(
|
|
4
|
+
:name,
|
|
5
|
+
:type,
|
|
6
|
+
:not_null,
|
|
7
|
+
:default_expression,
|
|
8
|
+
:collation_schema,
|
|
9
|
+
:collation_name
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def self.lagging_child_warnings(parent_table, connection: Connection.connection, candidate_table: nil)
|
|
13
|
+
new(connection: connection).lagging_child_warnings(parent_table, candidate_table: candidate_table)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(connection: Connection.connection)
|
|
17
|
+
@connection = connection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def align!(parent_table, child_table)
|
|
21
|
+
missing = missing_ordinary_columns(parent_table, child_table)
|
|
22
|
+
add_columns!(child_table, missing) if missing.any?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def lagging_child_warnings(parent_table, candidate_table: nil)
|
|
26
|
+
child_tables = unattached_child_tables(parent_table)
|
|
27
|
+
child_tables.select!(&candidate_table) if candidate_table
|
|
28
|
+
columns_by_table = ordinary_columns_by_table([parent_table, *child_tables])
|
|
29
|
+
parent_columns = columns_by_table.fetch(parent_table, [])
|
|
30
|
+
|
|
31
|
+
child_tables.filter_map do |child_table|
|
|
32
|
+
missing = missing_columns(parent_columns, columns_by_table.fetch(child_table, []))
|
|
33
|
+
next if missing.empty?
|
|
34
|
+
|
|
35
|
+
missing_columns_warning(child_table, parent_table, missing)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def missing_ordinary_columns(parent_table, child_table)
|
|
40
|
+
columns_by_table = ordinary_columns_by_table([parent_table, child_table])
|
|
41
|
+
missing_columns(
|
|
42
|
+
columns_by_table.fetch(parent_table, []),
|
|
43
|
+
columns_by_table.fetch(child_table, [])
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def add_columns!(child_table, columns)
|
|
50
|
+
actions = columns.map { |column| add_column_action(column) }.join(",\n ")
|
|
51
|
+
@connection.execute(<<~SQL)
|
|
52
|
+
ALTER TABLE #{quoted_table(child_table)}
|
|
53
|
+
#{actions}
|
|
54
|
+
SQL
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_column_action(column)
|
|
58
|
+
clauses = ["ADD COLUMN #{quoted_column(column.name)} #{column.type}"]
|
|
59
|
+
if column.collation_name
|
|
60
|
+
clauses << "COLLATE #{quoted_column(column.collation_schema)}.#{quoted_column(column.collation_name)}"
|
|
61
|
+
end
|
|
62
|
+
clauses << "DEFAULT #{column.default_expression}" if usable_default?(column.default_expression)
|
|
63
|
+
clauses << "NOT NULL" if column.not_null
|
|
64
|
+
clauses.join(" ")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ordinary_columns_by_table(table_names)
|
|
68
|
+
names = table_names.uniq
|
|
69
|
+
quoted_names = names.map { |table_name| @connection.quote(table_name) }.join(", ")
|
|
70
|
+
sql = <<~SQL
|
|
71
|
+
SELECT
|
|
72
|
+
table_class.relname AS table_name,
|
|
73
|
+
attribute.attname AS name,
|
|
74
|
+
pg_catalog.format_type(attribute.atttypid, attribute.atttypmod) AS type,
|
|
75
|
+
attribute.attnotnull AS not_null,
|
|
76
|
+
pg_get_expr(column_default.adbin, column_default.adrelid) AS default_expression,
|
|
77
|
+
attribute.attidentity AS identity,
|
|
78
|
+
attribute.attgenerated AS generated,
|
|
79
|
+
collation_namespace.nspname AS collation_schema,
|
|
80
|
+
column_collation.collname AS collation_name
|
|
81
|
+
FROM pg_attribute attribute
|
|
82
|
+
LEFT JOIN pg_attrdef column_default
|
|
83
|
+
ON column_default.adrelid = attribute.attrelid
|
|
84
|
+
AND column_default.adnum = attribute.attnum
|
|
85
|
+
JOIN pg_class table_class ON table_class.oid = attribute.attrelid
|
|
86
|
+
JOIN pg_namespace table_namespace ON table_namespace.oid = table_class.relnamespace
|
|
87
|
+
JOIN pg_type attribute_type ON attribute_type.oid = attribute.atttypid
|
|
88
|
+
LEFT JOIN pg_collation column_collation
|
|
89
|
+
ON column_collation.oid = attribute.attcollation
|
|
90
|
+
AND attribute.attcollation <> attribute_type.typcollation
|
|
91
|
+
LEFT JOIN pg_namespace collation_namespace
|
|
92
|
+
ON collation_namespace.oid = column_collation.collnamespace
|
|
93
|
+
WHERE table_namespace.nspname = #{@connection.quote(schema_name)}
|
|
94
|
+
AND table_class.relname IN (#{quoted_names})
|
|
95
|
+
AND attribute.attnum > 0
|
|
96
|
+
AND NOT attribute.attisdropped
|
|
97
|
+
ORDER BY table_class.relname, attribute.attnum
|
|
98
|
+
SQL
|
|
99
|
+
@connection.execute(sql).each_with_object({}) do |row, columns_by_table|
|
|
100
|
+
next if Blank.present?(row["identity"]) || Blank.present?(row["generated"])
|
|
101
|
+
|
|
102
|
+
column = Column.new(
|
|
103
|
+
name: row["name"],
|
|
104
|
+
type: row["type"],
|
|
105
|
+
not_null: flag?(row["not_null"]),
|
|
106
|
+
default_expression: row["default_expression"],
|
|
107
|
+
collation_schema: row["collation_schema"],
|
|
108
|
+
collation_name: row["collation_name"]
|
|
109
|
+
)
|
|
110
|
+
(columns_by_table[row["table_name"]] ||= []) << column
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def missing_columns(parent_columns, child_columns)
|
|
115
|
+
child_names = child_columns.to_h { |column| [column.name, true] }
|
|
116
|
+
parent_columns.reject { |column| child_names.key?(column.name) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def unattached_child_tables(parent_table)
|
|
120
|
+
sql = <<~SQL
|
|
121
|
+
SELECT child.relname
|
|
122
|
+
FROM pg_class child
|
|
123
|
+
JOIN pg_namespace child_namespace ON child_namespace.oid = child.relnamespace
|
|
124
|
+
WHERE child_namespace.nspname = #{@connection.quote(schema_name)}
|
|
125
|
+
AND child.relkind = 'r'
|
|
126
|
+
AND starts_with(child.relname, #{@connection.quote("#{parent_table}_")})
|
|
127
|
+
AND NOT EXISTS (
|
|
128
|
+
SELECT 1
|
|
129
|
+
FROM pg_inherits inheritance
|
|
130
|
+
WHERE inheritance.inhrelid = child.oid
|
|
131
|
+
)
|
|
132
|
+
ORDER BY child.relname
|
|
133
|
+
SQL
|
|
134
|
+
@connection.execute(sql).map { |row| row["relname"] }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def missing_columns_warning(child_table, parent_table, missing)
|
|
138
|
+
names = missing.map(&:name).join(", ")
|
|
139
|
+
noun = (missing.size == 1) ? "column" : "columns"
|
|
140
|
+
"child #{child_table} is missing #{noun} #{names} from parent #{parent_table}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def usable_default?(expression)
|
|
144
|
+
return false if Blank.blank?(expression)
|
|
145
|
+
|
|
146
|
+
!expression.strip.match?(/\ANULL(::[\w.]+)?\z/i)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def flag?(value)
|
|
150
|
+
value == true || value.to_s == "t" || value.to_s == "true"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def quoted_table(name)
|
|
154
|
+
@connection.quote_table_name(name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def quoted_column(name)
|
|
158
|
+
@connection.quote_column_name(name)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def schema_name
|
|
162
|
+
PartitionGardener.configuration.schema_name
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -22,6 +22,7 @@ module PartitionGardener
|
|
|
22
22
|
retention_apply
|
|
23
23
|
retention_keep_table
|
|
24
24
|
retention_detach_concurrently
|
|
25
|
+
align_child_columns
|
|
25
26
|
hash_modulus
|
|
26
27
|
maintenance_backend
|
|
27
28
|
incremental_rebalance
|
|
@@ -173,6 +174,7 @@ module PartitionGardener
|
|
|
173
174
|
validate_registry_conflict_key!(document)
|
|
174
175
|
validate_registry_optional_bucket!(document)
|
|
175
176
|
validate_registry_optional_maintenance_backend!(document)
|
|
177
|
+
validate_registry_optional_boolean!(document, "align_child_columns")
|
|
176
178
|
document
|
|
177
179
|
end
|
|
178
180
|
|
|
@@ -233,6 +235,13 @@ module PartitionGardener
|
|
|
233
235
|
raise ArgumentError, "maintenance_backend must be gardener, pg_partman, or hybrid_layout_only"
|
|
234
236
|
end
|
|
235
237
|
|
|
238
|
+
def validate_registry_optional_boolean!(document, key)
|
|
239
|
+
return unless document.key?(key)
|
|
240
|
+
return if document.fetch(key) == true || document.fetch(key) == false
|
|
241
|
+
|
|
242
|
+
raise ArgumentError, "#{key} must be true or false"
|
|
243
|
+
end
|
|
244
|
+
|
|
236
245
|
def register_validated_document!(document)
|
|
237
246
|
Registry.register(from_hash(validate_registry_document!(document)))
|
|
238
247
|
end
|
|
@@ -11,6 +11,7 @@ module PartitionGardener
|
|
|
11
11
|
:incremental_rebalance,
|
|
12
12
|
:run_record_enabled,
|
|
13
13
|
:retention_detach_concurrently,
|
|
14
|
+
:align_child_columns,
|
|
14
15
|
:strict_maintenance_backend_validation,
|
|
15
16
|
:current_run_metrics
|
|
16
17
|
|
|
@@ -29,6 +30,7 @@ module PartitionGardener
|
|
|
29
30
|
@run_record_enabled = true
|
|
30
31
|
@run_record_store = nil
|
|
31
32
|
@retention_detach_concurrently = false
|
|
33
|
+
@align_child_columns = true
|
|
32
34
|
@strict_maintenance_backend_validation = false
|
|
33
35
|
@current_run_metrics = nil
|
|
34
36
|
end
|
|
@@ -5,16 +5,26 @@ module PartitionGardener
|
|
|
5
5
|
def self.for_config(config, connection: Connection.connection)
|
|
6
6
|
new(
|
|
7
7
|
connection: connection,
|
|
8
|
-
batch_size: config.fetch(:move_batch_size, MOVE_BATCH_SIZE)
|
|
8
|
+
batch_size: config.fetch(:move_batch_size, MOVE_BATCH_SIZE),
|
|
9
|
+
align_child_columns: config.fetch(
|
|
10
|
+
:align_child_columns,
|
|
11
|
+
PartitionGardener.configuration.align_child_columns
|
|
12
|
+
)
|
|
9
13
|
)
|
|
10
14
|
end
|
|
11
15
|
|
|
12
|
-
def initialize(
|
|
16
|
+
def initialize(
|
|
17
|
+
connection: Connection.connection,
|
|
18
|
+
batch_size: MOVE_BATCH_SIZE,
|
|
19
|
+
align_child_columns: PartitionGardener.configuration.align_child_columns
|
|
20
|
+
)
|
|
13
21
|
@connection = connection
|
|
14
22
|
@batch_size = batch_size
|
|
23
|
+
@align_child_columns = align_child_columns
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
def attach_partition(table_name, partition_name, for_values_clause)
|
|
27
|
+
align_child_columns!(table_name, partition_name)
|
|
18
28
|
sql = <<~SQL
|
|
19
29
|
ALTER TABLE #{quoted_table(table_name)} ATTACH PARTITION #{quoted_table(partition_name)}
|
|
20
30
|
FOR VALUES #{for_values_clause}
|
|
@@ -23,6 +33,7 @@ module PartitionGardener
|
|
|
23
33
|
end
|
|
24
34
|
|
|
25
35
|
def attach_default_partition(table_name, partition_name)
|
|
36
|
+
align_child_columns!(table_name, partition_name)
|
|
26
37
|
sql = <<~SQL
|
|
27
38
|
ALTER TABLE #{quoted_table(table_name)} ATTACH PARTITION #{quoted_table(partition_name)} DEFAULT
|
|
28
39
|
SQL
|
|
@@ -59,6 +70,7 @@ module PartitionGardener
|
|
|
59
70
|
)
|
|
60
71
|
SQL
|
|
61
72
|
@connection.execute(sql)
|
|
73
|
+
align_child_columns!(table_name, partition_name)
|
|
62
74
|
|
|
63
75
|
conflict_columns = conflict_key.map { |column| @connection.quote_column_name(column) }.join(", ")
|
|
64
76
|
sql = <<~SQL
|
|
@@ -141,6 +153,12 @@ module PartitionGardener
|
|
|
141
153
|
|
|
142
154
|
private
|
|
143
155
|
|
|
156
|
+
def align_child_columns!(parent_table, child_table)
|
|
157
|
+
return unless @align_child_columns
|
|
158
|
+
|
|
159
|
+
ChildColumnAlign.new(connection: @connection).align!(parent_table, child_table)
|
|
160
|
+
end
|
|
161
|
+
|
|
144
162
|
def quoted_table(name)
|
|
145
163
|
@connection.quote_table_name(name)
|
|
146
164
|
end
|
|
@@ -4,6 +4,9 @@ module PartitionGardener
|
|
|
4
4
|
|
|
5
5
|
def normalize(config)
|
|
6
6
|
config = config.dup
|
|
7
|
+
unless config.key?(:partition_name_format_is_custom)
|
|
8
|
+
config[:partition_name_format_is_custom] = config.key?(:partition_name_format)
|
|
9
|
+
end
|
|
7
10
|
config[:layout] ||= :sliding_window
|
|
8
11
|
config[:bucket] ||= :month if date_layout?(config[:layout])
|
|
9
12
|
config
|
|
@@ -106,6 +109,7 @@ module PartitionGardener
|
|
|
106
109
|
conflict_key: conflict_key,
|
|
107
110
|
premake_months: premake_months,
|
|
108
111
|
maintenance_backend: options.fetch(:maintenance_backend, :gardener),
|
|
112
|
+
partition_name_format_is_custom: options.key?(:partition_name_format),
|
|
109
113
|
partition_name_format: options.fetch(:partition_name_format) {
|
|
110
114
|
->(identifier) { DateBucket.partition_name(table_name, identifier, :month) }
|
|
111
115
|
},
|
|
@@ -128,6 +132,7 @@ module PartitionGardener
|
|
|
128
132
|
partition_key_column: partition_key_column,
|
|
129
133
|
conflict_key: conflict_key,
|
|
130
134
|
active_years: active_years,
|
|
135
|
+
partition_name_format_is_custom: options.key?(:partition_name_format),
|
|
131
136
|
partition_name_format: options.fetch(:partition_name_format) {
|
|
132
137
|
->(identifier) { DateBucket.partition_name(table_name, identifier, :year) }
|
|
133
138
|
},
|
|
@@ -301,6 +306,7 @@ module PartitionGardener
|
|
|
301
306
|
:partition_key_column => partition_key_column,
|
|
302
307
|
:conflict_key => conflict_key,
|
|
303
308
|
active_key => active_value,
|
|
309
|
+
:partition_name_format_is_custom => options.key?(:partition_name_format),
|
|
304
310
|
:partition_name_format => options.fetch(:partition_name_format) {
|
|
305
311
|
->(identifier) { DateBucket.partition_name(table_name, identifier, bucket) }
|
|
306
312
|
},
|
data/lib/partition_gardener.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "partition_gardener/configuration"
|
|
|
14
14
|
require_relative "partition_gardener/naming"
|
|
15
15
|
require_relative "partition_gardener/plan"
|
|
16
16
|
require_relative "partition_gardener/connection"
|
|
17
|
+
require_relative "partition_gardener/child_column_align"
|
|
17
18
|
require_relative "partition_gardener/executor"
|
|
18
19
|
require_relative "partition_gardener/default_partition"
|
|
19
20
|
require_relative "partition_gardener/strategy/requires_default_partition"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: partition_gardener
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -303,6 +303,7 @@ files:
|
|
|
303
303
|
- lib/partition_gardener/archive_retention.rb
|
|
304
304
|
- lib/partition_gardener/audit.rb
|
|
305
305
|
- lib/partition_gardener/blank.rb
|
|
306
|
+
- lib/partition_gardener/child_column_align.rb
|
|
306
307
|
- lib/partition_gardener/cli.rb
|
|
307
308
|
- lib/partition_gardener/config_document.rb
|
|
308
309
|
- lib/partition_gardener/configuration.rb
|