@@ -987,10 +987,64 @@ foreach_chunk_multitransaction(Oid relid, MemoryContext mctx, mt_process_chunk_t
987987
988988typedef struct VacuumCtx
989989{
990+ bool is_vacuumfull ;
990991 VacuumRelation * ht_vacuum_rel ;
991992 List * chunk_rels ;
993+ List * recompress_chunk_oids ;
992994} VacuumCtx ;
993995
996+ static bool
997+ chunk_has_missing_attrs (Chunk * chunk )
998+ {
999+ bool has_missing_attrs = false;
1000+ Relation ht_rel = relation_open (chunk -> hypertable_relid , AccessShareLock );
1001+ Relation chunk_rel = relation_open (chunk -> table_id , AccessShareLock );
1002+ TupleDesc tupdesc = RelationGetDescr (chunk_rel );
1003+
1004+ for (int i = 0 ; i < tupdesc -> natts ; i ++ )
1005+ {
1006+ Form_pg_attribute att = TupleDescAttr (tupdesc , i );
1007+
1008+ if (att -> atthasmissing )
1009+ {
1010+ has_missing_attrs = true;
1011+ break ;
1012+ }
1013+ }
1014+
1015+ relation_close (chunk_rel , AccessShareLock );
1016+ relation_close (ht_rel , AccessShareLock );
1017+ return has_missing_attrs ;
1018+ }
1019+
1020+ static void
1021+ check_chunk_needs_recompression (Oid chunk_relid , VacuumCtx * ctx )
1022+ {
1023+ /* Only VACUUM FULL does a complete table rewrite */
1024+ if (!ctx -> is_vacuumfull )
1025+ return ;
1026+
1027+ Chunk * chunk = ts_chunk_get_by_relid (chunk_relid , false);
1028+ if (!chunk )
1029+ return ;
1030+
1031+ /*
1032+ * VACUUM FULL will materialize missing attributes. Recompress chunks to propagate
1033+ * these changes to compressed data.
1034+ */
1035+ if (chunk_has_missing_attrs (chunk ))
1036+ {
1037+ /*
1038+ * Frozen chunks are not allowed to add columns with defaults
1039+ */
1040+ Ensure (!ts_chunk_is_frozen (chunk ),
1041+ "chunk \"%s.%s\" was altered unsafely after it was frozen" ,
1042+ NameStr (chunk -> fd .schema_name ),
1043+ NameStr (chunk -> fd .table_name ));
1044+ ctx -> recompress_chunk_oids = lappend_oid (ctx -> recompress_chunk_oids , chunk_relid );
1045+ }
1046+ }
1047+
9941048/* Adds a chunk to the list of tables to be vacuumed */
9951049static void
9961050add_chunk_to_vacuum (Hypertable * ht , Oid chunk_relid , void * arg )
@@ -1018,6 +1072,8 @@ add_chunk_to_vacuum(Hypertable *ht, Oid chunk_relid, void *arg)
10181072 ctx -> chunk_rels = lappend (ctx -> chunk_rels , chunk_vacuum_rel );
10191073 }
10201074 }
1075+
1076+ check_chunk_needs_recompression (chunk_relid , ctx );
10211077}
10221078
10231079/*
@@ -1026,7 +1082,7 @@ add_chunk_to_vacuum(Hypertable *ht, Oid chunk_relid, void *arg)
10261082 * from vacuum.c.
10271083 */
10281084static List *
1029- ts_get_all_vacuum_rels (bool is_vacuumcmd )
1085+ ts_get_all_vacuum_rels (bool is_vacuumcmd , VacuumCtx * ctx )
10301086{
10311087 List * vacrels = NIL ;
10321088 Relation pgclass ;
@@ -1066,6 +1122,8 @@ ts_get_all_vacuum_rels(bool is_vacuumcmd)
10661122 * about failure to open one of these relations later.
10671123 */
10681124 vacrels = lappend (vacrels , makeVacuumRelation (NULL , relid , NIL ));
1125+
1126+ check_chunk_needs_recompression (relid , ctx );
10691127 }
10701128
10711129 table_endscan (scan );
@@ -1082,8 +1140,10 @@ process_vacuum(ProcessUtilityArgs *args)
10821140 VacuumStmt * stmt = (VacuumStmt * ) args -> parsetree ;
10831141 bool is_toplevel = (args -> context == PROCESS_UTILITY_TOPLEVEL );
10841142 VacuumCtx ctx = {
1143+ .is_vacuumfull = false,
10851144 .ht_vacuum_rel = NULL ,
10861145 .chunk_rels = NIL ,
1146+ .recompress_chunk_oids = NIL ,
10871147 };
10881148 ListCell * lc ;
10891149 Hypertable * ht ;
@@ -1094,24 +1154,22 @@ process_vacuum(ProcessUtilityArgs *args)
10941154
10951155 is_vacuumcmd = stmt -> is_vacuumcmd ;
10961156
1097- #if PG16_GE
1098- if ( is_vacuumcmd )
1157+ /* Look for new option ONLY_DATABASE_STATS and FULL */
1158+ foreach ( lc , stmt -> options )
10991159 {
1100- /* Look for new option ONLY_DATABASE_STATS */
1101- foreach (lc , stmt -> options )
1102- {
1103- DefElem * opt = (DefElem * ) lfirst (lc );
1104-
1105- /* if "only_database_stats" is defined then don't execute our custom code and return to
1106- * the postgres execution for the proper validations */
1107- if (strcmp (opt -> defname , "only_database_stats" ) == 0 )
1108- return DDL_CONTINUE ;
1109- }
1110- }
1160+ DefElem * opt = (DefElem * ) lfirst (lc );
1161+ #if PG16_GE
1162+ /* if "only_database_stats" is defined then don't execute our custom code and return to
1163+ * the postgres execution for the proper validations */
1164+ if (is_vacuumcmd && strcmp (opt -> defname , "only_database_stats" ) == 0 )
1165+ return DDL_CONTINUE ;
11111166#endif
1167+ if (strcmp (opt -> defname , "full" ) == 0 )
1168+ ctx .is_vacuumfull = defGetBoolean (opt );
1169+ }
11121170
11131171 if (stmt -> rels == NIL )
1114- vacuum_rels = ts_get_all_vacuum_rels (is_vacuumcmd );
1172+ vacuum_rels = ts_get_all_vacuum_rels (is_vacuumcmd , & ctx );
11151173 else
11161174 {
11171175 Cache * hcache = ts_hypertable_cache_pin ();
@@ -1147,6 +1205,20 @@ process_vacuum(ProcessUtilityArgs *args)
11471205 {
11481206 PreventCommandDuringRecovery (is_vacuumcmd ? "VACUUM" : "ANALYZE" );
11491207
1208+ if (list_length (ctx .recompress_chunk_oids ) > 0 )
1209+ {
1210+ /* there are chunks that need recompression */
1211+ ListCell * lc ;
1212+ foreach (lc , ctx .recompress_chunk_oids )
1213+ {
1214+ /* TODO: needs to handle partial compression */
1215+ Oid chunk_relid = lfirst_oid (lc );
1216+ (void ) DirectFunctionCall3 (ts_cm_functions -> compress_chunk ,
1217+ ObjectIdGetDatum (chunk_relid ),
1218+ BoolGetDatum (true),
1219+ BoolGetDatum (true)); /* recompress */
1220+ }
1221+ }
11501222 /* ACL permission checks inside vacuum_rel and analyze_rel called by this ExecVacuum */
11511223 ExecVacuum (args -> parse_state , stmt , is_toplevel );
11521224 }
0 commit comments