Skip to content

Commit 89fc5b4

Browse files
committed
Reset attislocal on chunks attached to a hypertable
When attaching a chunk to a hypertable we would mark the relation as inherited but leave the columns marked as local so a later DROP COLUMN on the hypertable would not propagate to the chunk. Clear the local flag on the inherited columns when a chunk is attached so column changes on the hypertable propagate to it. Also add a migration that fixes chunks already left in this state by an earlier detach and attach round-trip.
1 parent 0078f49 commit 89fc5b4

5 files changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #10352 Reset attislocal on chunks during attach_chunk

sql/updates/latest-dev.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Reset attislocal on chunk columns left marked local by a
2+
-- detach_chunk/attach_chunk round-trip so a later hypertable DROP COLUMN
3+
-- propagates to them. Restricted to chunk relations via the catalog join.
4+
UPDATE pg_catalog.pg_attribute a
5+
SET attislocal = false
6+
FROM _timescaledb_catalog.chunk c
7+
WHERE a.attrelid = c.relid
8+
AND a.attnum > 0
9+
AND NOT a.attisdropped
10+
AND a.attislocal
11+
AND a.attinhcount > 0;

src/chunk.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <access/xact.h>
1414
#include <catalog/indexing.h>
1515
#include <catalog/namespace.h>
16+
#include <catalog/pg_attribute.h>
1617
#include <catalog/pg_class.h>
1718
#include <catalog/pg_constraint.h>
1819
#include <catalog/pg_inherits.h>
@@ -1116,6 +1117,47 @@ chunk_create_from_hypercube_after_lock(const Hypertable *ht, Hypercube *cube,
11161117
return chunk;
11171118
}
11181119

1120+
/*
1121+
* A chunk should have all columns inherited and none marked as local, so clear
1122+
* the attislocal flag that ALTER TABLE ... INHERIT leaves set when attaching
1123+
* a pre-existing table. Otherwise a later DROP COLUMN on the hypertable would
1124+
* not propagate to the chunk.
1125+
*/
1126+
static void
1127+
chunk_reset_attislocal(Oid chunk_relid)
1128+
{
1129+
Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
1130+
Relation chunkrel = table_open(chunk_relid, AccessShareLock);
1131+
TupleDesc tupdesc = RelationGetDescr(chunkrel);
1132+
1133+
for (int i = 0; i < tupdesc->natts; i++)
1134+
{
1135+
Form_pg_attribute att = TupleDescAttr(tupdesc, i);
1136+
1137+
/* Only touch inherited user columns that are still marked local */
1138+
if (att->attnum <= 0 || att->attisdropped || !att->attislocal || att->attinhcount == 0)
1139+
{
1140+
continue;
1141+
}
1142+
1143+
HeapTuple tuple = SearchSysCacheCopyAttNum(chunk_relid, att->attnum);
1144+
if (!HeapTupleIsValid(tuple))
1145+
{
1146+
elog(ERROR,
1147+
"cache lookup failed for attribute %d of relation %u",
1148+
att->attnum,
1149+
chunk_relid);
1150+
}
1151+
1152+
((Form_pg_attribute) GETSTRUCT(tuple))->attislocal = false;
1153+
CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
1154+
heap_freetuple(tuple);
1155+
}
1156+
1157+
table_close(chunkrel, NoLock);
1158+
table_close(attrel, RowExclusiveLock);
1159+
}
1160+
11191161
/*
11201162
* Make a chunk table inherit a hypertable.
11211163
*
@@ -1149,6 +1191,8 @@ chunk_add_inheritance(Chunk *chunk, const Hypertable *ht)
11491191
};
11501192

11511193
AlterTable(&alterstmt, lockmode, &atcontext);
1194+
1195+
chunk_reset_attislocal(atcontext.relid);
11521196
}
11531197

11541198
static Chunk *

tsl/test/expected/attach_chunk.out

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,38 @@ DROP TABLE regular_table_to_attach;
235235
DROP TABLE attach_test_ref;
236236
DROP TABLE attach_test;
237237
DROP TABLE devices CASCADE;
238+
-- Test dropping columns after detach/attach
239+
CREATE TABLE drop_after_attach(time timestamptz NOT NULL, x int);
240+
SELECT create_hypertable('drop_after_attach', 'time', chunk_time_interval => interval '1 day');
241+
create_hypertable
242+
--------------------------------
243+
(3,public,drop_after_attach,t)
244+
245+
INSERT INTO drop_after_attach VALUES ('2026-01-01', 1), ('2026-01-05', 1);
246+
SELECT schema_name || '.' || table_name AS "ROUNDTRIP_CHUNK", slices AS "ROUNDTRIP_SLICES"
247+
FROM _timescaledb_functions.show_chunk((SELECT show_chunks('drop_after_attach', older_than => '2026-01-02') LIMIT 1)); \gset
248+
ROUNDTRIP_CHUNK | ROUNDTRIP_SLICES
249+
-----------------------------------------+------------------------------------------------
250+
_timescaledb_internal._hyper_3_15_chunk | {"time": [1767225600000000, 1767312000000000]}
251+
252+
CALL detach_chunk(:'ROUNDTRIP_CHUNK');
253+
CALL attach_chunk('drop_after_attach', :'ROUNDTRIP_CHUNK', :'ROUNDTRIP_SLICES');
254+
-- The re-attached chunk inherits without any locally-defined columns.
255+
SELECT attname, attislocal, attinhcount
256+
FROM pg_attribute
257+
WHERE attrelid = :'ROUNDTRIP_CHUNK'::regclass AND attnum > 0 AND NOT attisdropped
258+
ORDER BY attnum;
259+
attname | attislocal | attinhcount
260+
---------+------------+-------------
261+
time | f | 1
262+
x | f | 1
263+
264+
ALTER TABLE drop_after_attach DROP COLUMN x;
265+
-- The column is gone from the round-tripped chunk, not left orphaned.
266+
SELECT attname, attislocal, attinhcount
267+
FROM pg_attribute
268+
WHERE attrelid = :'ROUNDTRIP_CHUNK'::regclass AND attname = 'x' AND NOT attisdropped;
269+
attname | attislocal | attinhcount
270+
---------+------------+-------------
271+
272+
DROP TABLE drop_after_attach;

tsl/test/sql/attach_chunk.sql

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,29 @@ DROP TABLE regular_table_to_attach;
186186
DROP TABLE attach_test_ref;
187187
DROP TABLE attach_test;
188188
DROP TABLE devices CASCADE;
189+
190+
-- Test dropping columns after detach/attach
191+
CREATE TABLE drop_after_attach(time timestamptz NOT NULL, x int);
192+
SELECT create_hypertable('drop_after_attach', 'time', chunk_time_interval => interval '1 day');
193+
INSERT INTO drop_after_attach VALUES ('2026-01-01', 1), ('2026-01-05', 1);
194+
195+
SELECT schema_name || '.' || table_name AS "ROUNDTRIP_CHUNK", slices AS "ROUNDTRIP_SLICES"
196+
FROM _timescaledb_functions.show_chunk((SELECT show_chunks('drop_after_attach', older_than => '2026-01-02') LIMIT 1)); \gset
197+
198+
CALL detach_chunk(:'ROUNDTRIP_CHUNK');
199+
CALL attach_chunk('drop_after_attach', :'ROUNDTRIP_CHUNK', :'ROUNDTRIP_SLICES');
200+
201+
-- The re-attached chunk inherits without any locally-defined columns.
202+
SELECT attname, attislocal, attinhcount
203+
FROM pg_attribute
204+
WHERE attrelid = :'ROUNDTRIP_CHUNK'::regclass AND attnum > 0 AND NOT attisdropped
205+
ORDER BY attnum;
206+
207+
ALTER TABLE drop_after_attach DROP COLUMN x;
208+
209+
-- The column is gone from the round-tripped chunk, not left orphaned.
210+
SELECT attname, attislocal, attinhcount
211+
FROM pg_attribute
212+
WHERE attrelid = :'ROUNDTRIP_CHUNK'::regclass AND attname = 'x' AND NOT attisdropped;
213+
214+
DROP TABLE drop_after_attach;

0 commit comments

Comments
 (0)