Skip to content

Commit 716e7ad

Browse files
committed
Support ADD COLUMN on continuous aggregates
Allow ALTER MATERIALIZED VIEW <cagg> ADD COLUMN <name> <type> GENERATED ALWAYS AS (<aggregate>) STORED. The new column is added to the materialization hypertable and surfaced through the partial, direct, and user views. AccessExclusiveLock's are taken on each of them at the beginning. New columns will have NULL values on already existing rows from the CAgg, new rows inserted after the ADD COLUMN statement will have appropriate values. To backfill previous rows, forced refresh is required.
1 parent 14583bd commit 716e7ad

33 files changed

Lines changed: 3460 additions & 27 deletions

.unreleased/pr_9825

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9825 Support ADD COLUMN on continuous aggregates

sql/pre_install/tables.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ CREATE TABLE _timescaledb_catalog.continuous_agg (
341341
direct_view_schema name NOT NULL,
342342
direct_view_name name NOT NULL,
343343
materialized_only bool NOT NULL DEFAULT FALSE,
344+
schema_change_timestamp bigint,
344345
-- table constraints
345346
CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),
346347
CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),

sql/updates/latest-dev.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,7 @@ ALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_constraint_
158158
DROP TABLE _timescaledb_catalog.chunk_constraint;
159159
DROP SEQUENCE _timescaledb_catalog.chunk_constraint_name;
160160

161+
-- Track the watermark at which a column was added to a continuous aggregate via
162+
-- ALTER ... ADD COLUMN, so the planner does not rewrite
163+
ALTER TABLE _timescaledb_catalog.continuous_agg ADD COLUMN schema_change_timestamp bigint;
164+

sql/updates/reverse-dev.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,5 @@ SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_
183183
GRANT SELECT ON _timescaledb_catalog.dimension_slice TO PUBLIC;
184184
GRANT SELECT ON _timescaledb_catalog.dimension_slice_id_seq TO PUBLIC;
185185
-- end rebuild _timescaledb_catalog.dimension_slice table --
186+
ALTER TABLE _timescaledb_catalog.continuous_agg DROP COLUMN schema_change_timestamp;
186187

src/cross_module_fn.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ continuous_agg_update_options_default(ContinuousAgg *cagg, WithClauseResult *wit
229229
pg_unreachable();
230230
}
231231

232+
static void
233+
continuous_agg_add_column_default(ContinuousAgg *cagg, AlterTableStmt *stmt)
234+
{
235+
error_no_default_fn_community();
236+
pg_unreachable();
237+
}
238+
232239
static void
233240
continuous_agg_invalidate_raw_ht_all_default(const Hypertable *raw_ht, int64 start, int64 end)
234241
{
@@ -337,6 +344,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {
337344
.continuous_agg_invalidate_mat_ht = continuous_agg_invalidate_mat_ht_all_default,
338345
.continuous_agg_dml_invalidate = continuous_agg_dml_invalidate_default,
339346
.continuous_agg_update_options = continuous_agg_update_options_default,
347+
.continuous_agg_add_column = continuous_agg_add_column_default,
340348
.continuous_agg_apply_rewrites_tsl = NULL,
341349
.continuous_agg_validate_query = error_no_default_fn_pg_community,
342350
.continuous_agg_get_bucket_function = error_no_default_fn_pg_community,

src/cross_module_fn.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ typedef struct CrossModuleFunctions
105105
bool update);
106106
void (*continuous_agg_update_options)(ContinuousAgg *cagg,
107107
WithClauseResult *with_clause_options);
108+
void (*continuous_agg_add_column)(ContinuousAgg *cagg, AlterTableStmt *stmt);
108109
Query *(*continuous_agg_apply_rewrites_tsl)(Query *parse);
109110
PGFunction continuous_agg_validate_query;
110111
PGFunction continuous_agg_get_bucket_function;

src/process_utility.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5080,6 +5080,40 @@ process_altertable_start_matview(ProcessUtilityArgs *args)
50805080

50815081
continuous_agg_with_clause_perm_check(cagg, view_relid);
50825082

5083+
/*
5084+
* CAgg add column handler.
5085+
* Split `stmt->cmds` into the GENERATED ALWAYS AS and everything else
5086+
* (handled by the switch below). Process all ADD COLUMNs at once since
5087+
* they require a rewrite of the view, and doing them one by one would
5088+
* be inefficient.
5089+
*/
5090+
{
5091+
List *addcol_cmds = NIL;
5092+
List *other_cmds = NIL;
5093+
foreach (lc, stmt->cmds)
5094+
{
5095+
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);
5096+
if (cmd->subtype == AT_AddColumn)
5097+
{
5098+
addcol_cmds = lappend(addcol_cmds, cmd);
5099+
}
5100+
else
5101+
{
5102+
other_cmds = lappend(other_cmds, cmd);
5103+
}
5104+
}
5105+
5106+
if (addcol_cmds != NIL)
5107+
{
5108+
AlterTableStmt addcol_stmt = *stmt;
5109+
addcol_stmt.cmds = addcol_cmds;
5110+
ts_cm_functions->continuous_agg_add_column(cagg, &addcol_stmt);
5111+
CommandCounterIncrement();
5112+
5113+
stmt->cmds = other_cmds;
5114+
}
5115+
}
5116+
50835117
foreach (lc, stmt->cmds)
50845118
{
50855119
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);

src/ts_catalog/catalog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,7 @@ typedef enum Anum_continuous_agg
815815
Anum_continuous_agg_direct_view_schema,
816816
Anum_continuous_agg_direct_view_name,
817817
Anum_continuous_agg_materialize_only,
818+
Anum_continuous_agg_schema_change_timestamp,
818819
_Anum_continuous_agg_max,
819820
} Anum_continuous_agg;
820821

@@ -832,6 +833,7 @@ typedef struct FormData_continuous_agg
832833
NameData direct_view_schema;
833834
NameData direct_view_name;
834835
bool materialized_only;
836+
int64 schema_change_timestamp;
835837
} FormData_continuous_agg;
836838

837839
typedef FormData_continuous_agg *Form_continuous_agg;

src/ts_catalog/continuous_agg.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ continuous_agg_formdata_make_tuple(const FormData_continuous_agg *fd, TupleDesc
263263
values[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)] =
264264
BoolGetDatum(fd->materialized_only);
265265

266+
if (fd->schema_change_timestamp == PG_INT64_MIN)
267+
{
268+
nulls[AttrNumberGetAttrOffset(Anum_continuous_agg_schema_change_timestamp)] = true;
269+
}
270+
else
271+
{
272+
values[AttrNumberGetAttrOffset(Anum_continuous_agg_schema_change_timestamp)] =
273+
Int64GetDatum(fd->schema_change_timestamp);
274+
}
275+
266276
return heap_form_tuple(desc, values, nulls);
267277
}
268278

@@ -315,6 +325,17 @@ continuous_agg_formdata_fill(FormData_continuous_agg *fd, const TupleInfo *ti)
315325

316326
fd->materialized_only =
317327
DatumGetBool(values[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)]);
328+
329+
if (nulls[AttrNumberGetAttrOffset(Anum_continuous_agg_schema_change_timestamp)])
330+
{
331+
fd->schema_change_timestamp = PG_INT64_MIN;
332+
}
333+
else
334+
{
335+
fd->schema_change_timestamp = DatumGetInt64(
336+
values[AttrNumberGetAttrOffset(Anum_continuous_agg_schema_change_timestamp)]);
337+
}
338+
318339
if (should_free)
319340
{
320341
heap_freetuple(tuple);
@@ -627,6 +648,42 @@ ts_continuous_agg_find_by_mat_hypertable_id(int32 mat_hypertable_id, bool missin
627648
return ca;
628649
}
629650

651+
/*
652+
* Record the threshold below which a newly added column is not yet materialized,
653+
* so the planner rewrite can avoid serving for the cagg. The threshold only ever moves
654+
* forward, so we keep the highest value across multiple ADD COLUMN operations.
655+
*/
656+
void
657+
ts_continuous_agg_set_schema_change_timestamp(int32 mat_hypertable_id, int64 threshold)
658+
{
659+
ScanIterator iterator =
660+
ts_scan_iterator_create(CONTINUOUS_AGG, RowExclusiveLock, CurrentMemoryContext);
661+
CatalogSecurityContext sec_ctx;
662+
663+
init_scan_by_mat_hypertable_id(&iterator, mat_hypertable_id);
664+
ts_scanner_foreach(&iterator)
665+
{
666+
TupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);
667+
FormData_continuous_agg form;
668+
669+
continuous_agg_formdata_fill(&form, ti);
670+
671+
if (form.schema_change_timestamp == PG_INT64_MIN ||
672+
threshold > form.schema_change_timestamp)
673+
{
674+
form.schema_change_timestamp = threshold;
675+
676+
HeapTuple new_tuple =
677+
continuous_agg_formdata_make_tuple(&form, ts_scanner_get_tupledesc(ti));
678+
ts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);
679+
ts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);
680+
ts_catalog_restore_user(&sec_ctx);
681+
heap_freetuple(new_tuple);
682+
}
683+
}
684+
ts_scan_iterator_close(&iterator);
685+
}
686+
630687
static bool
631688
continuous_agg_find_by_name(const char *schema, const char *name, ContinuousAggViewType type,
632689
FormData_continuous_agg *fd)

src/ts_catalog/continuous_agg.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ extern TSDLLEXPORT ContinuousAggInfo ts_continuous_agg_get_all_caggs_info(int32
151151
extern TSDLLEXPORT ContinuousAgg *
152152
ts_continuous_agg_find_by_mat_hypertable_id(int32 mat_hypertable_id, bool missing_ok);
153153

154+
extern TSDLLEXPORT void ts_continuous_agg_set_schema_change_timestamp(int32 mat_hypertable_id,
155+
int64 threshold);
156+
154157
extern TSDLLEXPORT ContinuousAggHypertableStatus
155158
ts_continuous_agg_hypertable_status(int32 hypertable_id);
156159
extern TSDLLEXPORT List *ts_continuous_aggs_find_by_raw_table_id(int32 raw_hypertable_id);

0 commit comments

Comments
 (0)