Repository: timescale/timescaledb
Pull request #8138: Refactor compressor and decompressor
Author: login: erimatnor
Labels: name: refactor
name: released-2.21.0
description: Released in 2.21.0
Created: 2025-05-15T13:08:31Z | Merged: 2025-05-21T07:48:48Z

This is a multi-commit PR:

Clean up RowCompressor and RowDecompressor

Remove unnecessary fields in the RowCompressor and RowDecompressor
structs. Some of these fields were unused or duplicated information
that could be retrieved by other means.

Break out write state from RowDecompressor

The RowDecompressor expects two relations: one reading compressed data
and one where decompressed data is written. However, some
(de)compression don't follow this pattern; for example, in case of
splitting a chunk it is necessary to split compressed segments into
multiple new segments and write back compressed to the new compressed
relations. Another is memory decompression, which has no output
relation.

To better support such use cases, move the relations and write state
out of the RowDecompressor and introduce a BulkWriter that takes care
of writing the data to the desired output relation if needed. This
removes the dependency on specific relations and makes the write state
optional.

Break out the write state from RowCompressor

Similar to the RowDecompressor, the RowCompressor is constructed from
output and input relations. However, sometimes when compressing this
one-to-one relation mapping does not fit. For example, the input could
also be a compressed relation (e.g., when recompressing or splitting
chunks), so it makes sense to move the relations and write state out
of the RowCompressor.

A previous refactor did the similar thing for RowDecompressor and
introduced a BulkWriter. In line with that change, remove the write
state from the RowCompressor and use the BulkWriter instead.


Disable-check: force-changelog-file
Disable-check: commit-count


Diff:
diff --git a/tsl/src/compression/compression.c b/tsl/src/compression/compression.c
index 9679c67867c..9e58831e2df 100644
--- a/tsl/src/compression/compression.c
+++ b/tsl/src/compression/compression.c
@@ -4,15 +4,21 @@
  * LICENSE-TIMESCALE for a copy of the license.
  */
 #include <postgres.h>
+#include <access/attmap.h>
+#include <access/attnum.h>
 #include <access/skey.h>
+#include <access/tupdesc.h>
 #include <catalog/heap.h>
 #include <catalog/indexing.h>
 #include <catalog/pg_am.h>
 #include <common/base64.h>
+#include <funcapi.h>
 #include <libpq/pqformat.h>
 #include <storage/predicate.h>
 #include <utils/datum.h>
 #include <utils/elog.h>
+#include <utils/palloc.h>
+#include <utils/rel.h>
 #include <utils/snapmgr.h>
 #include <utils/syscache.h>
 #include <utils/typcache.h>
@@ -26,7 +32,6 @@
 #include "algorithms/gorilla.h"
 #include "algorithms/null.h"
 #include "batch_metadata_builder.h"
-#include "chunk.h"
 #include "compression.h"
 #include "create.h"
 #include "custom_type_cache.h"
@@ -34,10 +39,8 @@
 #include "debug_point.h"
 #include "guc.h"
 #include "hypercore/hypercore_handler.h"
-#include "nodes/chunk_dispatch/chunk_insert_state.h"
 #include "ts_catalog/array_utils.h"
 #include "ts_catalog/catalog.h"
-#include "ts_catalog/compression_chunk_size.h"
 #include "ts_catalog/compression_settings.h"
 #include <nodes/decompress_chunk/vector_quals.h>
 
@@ -119,16 +122,15 @@ tsl_get_decompress_all_function(CompressionAlgorithm algorithm, Oid type)
 
 static Tuplesortstate *compress_chunk_sort_relation(CompressionSettings *settings, Relation in_rel);
 static void row_compressor_process_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot,
-												CommandId mycid);
+												BulkWriter *writer);
 static void row_compressor_update_group(RowCompressor *row_compressor, TupleTableSlot *row);
 static bool row_compressor_new_row_is_in_new_group(RowCompressor *row_compressor,
 												   TupleTableSlot *row);
+static void create_per_compressed_column(RowDecompressor *decompressor);
 static void row_compressor_append_row(RowCompressor *row_compressor, TupleTableSlot *row);
-static void row_compressor_flush(RowCompressor *row_compressor, CommandId mycid,
+static void row_compressor_flush(RowCompressor *row_compressor, BulkWriter *writer,
 								 bool changed_groups);
 
-static void create_per_compressed_column(RowDecompressor *decompressor);
-
 /********************
  ** compress_chunk **
  ********************/
@@ -245,13 +247,13 @@ delete_relation_rows(Oid table_oid)
  */
 #define RELTUPLES_REPORT_DEFAULT 100000
 static int64
-calculate_reltuples_to_report(Relation rel)
+calculate_reltuples_to_report(float4 reltuples)
 {
 	int64 report_reltuples = RELTUPLES_REPORT_DEFAULT;
 
-	if (rel != NULL && rel->rd_rel->reltuples > 0)
+	if (reltuples > 0)
 	{
-		report_reltuples = (int64) (0.1 * rel->rd_rel->reltuples);
+		report_reltuples = (int64) (0.1 * reltuples);
 		/* either analyze has not been done or table doesn't have a lot of rows */
 		if (report_reltuples < RELTUPLES_REPORT_DEFAULT)
 			report_reltuples = RELTUPLES_REPORT_DEFAULT;
@@ -269,7 +271,6 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 	Relation matched_index_rel = NULL;
 	TupleTableSlot *slot;
 	IndexScanDesc index_scan;
-	CommandId mycid = GetCurrentCommandId(true);
 	HeapTuple in_table_tp = NULL, index_tp = NULL;
 	Form_pg_attribute in_table_attr_tp, index_attr_tp;
 	CompressionStats cstat;
@@ -297,13 +298,13 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 		out_rel_lockmode = RowExclusiveLock;
 	}
 	Relation out_rel = relation_open(out_table, out_rel_lockmode);
+	BulkWriter writer = bulk_writer_build(out_rel, insert_options);
 
 	/* Sanity check we are dealing with relations */
 	Ensure(in_rel->rd_rel->relkind == RELKIND_RELATION, "compress_chunk called on non-relation");
 	Ensure(out_rel->rd_rel->relkind == RELKIND_RELATION, "compress_chunk called on non-relation");
 
 	TupleDesc in_desc = RelationGetDescr(in_rel);
-	TupleDesc out_desc = RelationGetDescr(out_rel);
 
 	/* Before calling row compressor relation should be segmented and sorted as configured
 	 * by compress_segmentby and compress_orderby.
@@ -458,13 +459,12 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 	}
 
 	RowCompressor row_compressor;
-	row_compressor_init(settings,
-						&row_compressor,
-						in_rel,
-						out_rel,
-						out_desc->natts,
-						true /*need_bistate*/,
-						insert_options);
+
+	Assert(settings->fd.compress_relid == RelationGetRelid(out_rel));
+	row_compressor_init(&row_compressor,
+						settings,
+						RelationGetDescr(in_rel),
+						RelationGetDescr(out_rel));
 
 	if (matched_index_rel != NULL)
 	{
@@ -478,10 +478,10 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 		index_scan = index_beginscan(in_rel, matched_index_rel, GetTransactionSnapshot(), 0, 0);
 		slot = table_slot_create(in_rel, NULL);
 		index_rescan(index_scan, NULL, 0, NULL, 0);
-		report_reltuples = calculate_reltuples_to_report(in_rel);
+		report_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);
 		while (index_getnext_slot(index_scan, indexscan_direction, slot))
 		{
-			row_compressor_process_ordered_slot(&row_compressor, slot, mycid);
+			row_compressor_process_ordered_slot(&row_compressor, slot, &writer);
 			if ((++nrows_processed % report_reltuples) == 0)
 				elog(DEBUG2,
 					 "converted " INT64_FORMAT " rows to columnstore from \"%s\"",
@@ -490,7 +490,7 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 		}
 
 		if (row_compressor.rows_compressed_into_current_value > 0)
-			row_compressor_flush(&row_compressor, mycid, true);
+			row_compressor_flush(&row_compressor, &writer, true);
 
 		elog(DEBUG1,
 			 "finished converting " INT64_FORMAT " rows to columnstore from \"%s\"",
@@ -508,11 +508,12 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
 			 RelationGetRelationName(in_rel));
 
 		Tuplesortstate *sorted_rel = compress_chunk_sort_relation(settings, in_rel);
-		row_compressor_append_sorted_rows(&row_compressor, sorted_rel, in_desc, in_rel);
+		row_compressor_append_sorted_rows(&row_compressor, sorted_rel, in_desc, in_rel, &writer);
 		tuplesort_end(sorted_rel);
 	}
 
 	row_compressor_close(&row_compressor);
+	bulk_writer_close(&writer);
 
 	if (ts_guc_enable_delete_after_compression)
 	{
@@ -763,12 +764,10 @@ get_compressed_chunk_index(ResultRelInfo *resultRelInfo, const CompressionSettin
 }
 
 static void
-build_column_map(const CompressionSettings *settings, Relation uncompressed_table,
-				 Relation compressed_table, PerColumn **pcolumns, int16 **pmap)
+build_column_map(const CompressionSettings *settings, const TupleDesc in_desc,
+				 const TupleDesc out_desc, PerColumn **pcolumns, int16 **pmap)
 {
 	Oid compressed_data_type_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;
-	TupleDesc out_desc = RelationGetDescr(compressed_table);
-	TupleDesc in_desc = RelationGetDescr(uncompressed_table);
 
 	PerColumn *columns = palloc0(sizeof(PerColumn) * in_desc->natts);
 	int16 *map = palloc0(sizeof(int16) * in_desc->natts);
@@ -781,7 +780,8 @@ build_column_map(const CompressionSettings *settings, Relation uncompressed_tabl
 			continue;
 
 		PerColumn *column = &columns[AttrNumberGetAttrOffset(attr->attnum)];
-		AttrNumber compressed_colnum = get_attnum(compressed_table->rd_id, NameStr(attr->attname));
+		AttrNumber compressed_colnum =
+			get_attnum(settings->fd.compress_relid, NameStr(attr->attname));
 		Form_pg_attribute compressed_column_attr =
 			TupleDescAttr(out_desc, AttrNumberGetAttrOffset(compressed_colnum));
 		map[AttrNumberGetAttrOffset(attr->attnum)] = AttrNumberGetAttrOffset(compressed_colnum);
@@ -798,15 +798,15 @@ build_column_map(const CompressionSettings *settings, Relation uncompressed_tabl
 
 			AttrNumber segment_min_attr_number =
 				compressed_column_metadata_attno(settings,
-												 uncompressed_table->rd_id,
+												 settings->fd.relid,
 												 attr->attnum,
-												 compressed_table->rd_id,
+												 settings->fd.compress_relid,
 												 "min");
 			AttrNumber segment_max_attr_number =
 				compressed_column_metadata_attno(settings,
-												 uncompressed_table->rd_id,
+												 settings->fd.relid,
 												 attr->attnum,
-												 compressed_table->rd_id,
+												 settings->fd.compress_relid,
 												 "max");
 			int16 segment_min_attr_offset = segment_min_attr_number - 1;
 			int16 segment_max_attr_offset = segment_max_attr_number - 1;
@@ -831,9 +831,9 @@ build_column_map(const CompressionSettings *settings, Relation uncompressed_tabl
 
 			const AttrNumber bloom_attr_number =
 				compressed_column_metadata_attno(settings,
-												 uncompressed_table->rd_id,
+												 settings->fd.relid,
 												 attr->attnum,
-												 compressed_table->rd_id,
+												 settings->fd.compress_relid,
 												 "bloom1");
 			if (AttributeNumberIsValid(bloom_attr_number))
 			{
@@ -882,14 +882,14 @@ check_for_limited_size_compressors(PerColumn *pcolumns, int16 natts)
  ** row_compressor **
  ********************/
 void
-row_compressor_init(const CompressionSettings *settings, RowCompressor *row_compressor,
-					Relation uncompressed_table, Relation compressed_table,
-					int16 num_columns_in_compressed_table, bool need_bistate, int insert_options)
+row_compressor_init(RowCompressor *row_compressor, const CompressionSettings *settings,
+					const TupleDesc noncompressed_tupdesc, const TupleDesc compressed_tupdesc)
 {
 	Name count_metadata_name = DatumGetName(
 		DirectFunctionCall1(namein, CStringGetDatum(COMPRESSION_COLUMN_METADATA_COUNT_NAME)));
 	AttrNumber count_metadata_column_num =
-		get_attnum(compressed_table->rd_id, NameStr(*count_metadata_name));
+		get_attnum(settings->fd.compress_relid, NameStr(*count_metadata_name));
+
 	if (count_metadata_column_num == InvalidAttrNumber)
 		elog(ERROR,
 			 "missing metadata column '%s' in columnstore table",
@@ -899,25 +899,22 @@ row_compressor_init(const CompressionSettings *settings, RowCompressor *row_comp
 		.per_row_ctx = AllocSetContextCreate(CurrentMemoryContext,
 											 "compress chunk per-row",
 											 ALLOCSET_DEFAULT_SIZES),
-		.compressed_table = compressed_table,
-		.bistate = need_bistate ? GetBulkInsertState() : NULL,
-		.resultRelInfo = CatalogOpenIndexes(compressed_table),
-		.n_input_columns = RelationGetDescr(uncompressed_table)->natts,
+		.out_desc = CreateTupleDescCopyConstr(compressed_tupdesc),
+		.n_input_columns = noncompressed_tupdesc->natts,
 		.count_metadata_column_offset = AttrNumberGetAttrOffset(count_metadata_column_num),
-		.compressed_values = palloc(sizeof(Datum) * num_columns_in_compressed_table),
-		.compressed_is_null = palloc(sizeof(bool) * num_columns_in_compressed_table),
+		.compressed_values = palloc(sizeof(Datum) * compressed_tupdesc->natts),
+		.compressed_is_null = palloc(sizeof(bool) * compressed_tupdesc->natts),
 		.rows_compressed_into_current_value = 0,
 		.rowcnt_pre_compression = 0,
 		.num_compressed_rows = 0,
 		.first_iteration = true,
-		.insert_options = insert_options,
 	};
 
-	memset(row_compressor->compressed_is_null, 1, sizeof(bool) * num_columns_in_compressed_table);
+	memset(row_compressor->compressed_is_null, 1, sizeof(bool) * compressed_tupdesc->natts);
 
 	build_column_map(settings,
-					 uncompressed_table,
-					 compressed_table,
+					 noncompressed_tupdesc,
+					 compressed_tupdesc,
 					 &row_compressor->per_column,
 					 &row_compressor->uncompressed_col_to_compressed_col);
 
@@ -926,21 +923,18 @@ row_compressor_init(const CompressionSettings *settings, RowCompressor *row_comp
 	row_compressor->needs_fullness_check =
 		check_for_limited_size_compressors(row_compressor->per_column,
 										   row_compressor->n_input_columns);
-
-	row_compressor->index_oid = get_compressed_chunk_index(row_compressor->resultRelInfo, settings);
 }
 
 void
 row_compressor_append_sorted_rows(RowCompressor *row_compressor, Tuplesortstate *sorted_rel,
-								  TupleDesc sorted_desc, Relation in_rel)
+								  TupleDesc sorted_desc, Relation in_rel, BulkWriter *writer)
 {
-	CommandId mycid = GetCurrentCommandId(true);
 	TupleTableSlot *slot = MakeTupleTableSlot(sorted_desc, &TTSOpsMinimalTuple);
 	bool got_tuple;
 	int64 nrows_processed = 0;
 	int64 report_reltuples;
 
-	report_reltuples = calculate_reltuples_to_report(in_rel);
+	report_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);
 
 	for (got_tuple = tuplesort_gettupleslot(sorted_rel,
 											true /*=forward*/,
@@ -954,7 +948,7 @@ row_compressor_append_sorted_rows(RowCompressor *row_compressor, Tuplesortstate
 											slot,
 											NULL /*=abbrev*/))
 	{
-		row_compressor_process_ordered_slot(row_compressor, slot, mycid);
+		row_compressor_process_ordered_slot(row_compressor, slot, writer);
 		if ((++nrows_processed % report_reltuples) == 0)
 			elog(DEBUG2,
 				 "compressed " INT64_FORMAT " rows from \"%s\"",
@@ -963,7 +957,7 @@ row_compressor_append_sorted_rows(RowCompressor *row_compressor, Tuplesortstate
 	}
 
 	if (row_compressor->rows_compressed_into_current_value > 0)
-		row_compressor_flush(row_compressor, mycid, true);
+		row_compressor_flush(row_compressor, writer, true);
 	elog(DEBUG1,
 		 "finished compressing " INT64_FORMAT " rows from \"%s\"",
 		 nrows_processed,
@@ -1010,7 +1004,7 @@ row_compressor_is_full(RowCompressor *row_compressor, TupleTableSlot *row)
 
 static void
 row_compressor_process_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot,
-									CommandId mycid)
+									BulkWriter *writer)
 {
 	MemoryContext old_ctx;
 	slot_getallattrs(slot);
@@ -1025,7 +1019,7 @@ row_compressor_process_ordered_slot(RowCompressor *row_compressor, TupleTableSlo
 	if (compressed_row_is_full || changed_groups)
 	{
 		if (row_compressor->rows_compressed_into_current_value > 0)
-			row_compressor_flush(row_compressor, mycid, changed_groups);
+			row_compressor_flush(row_compressor, writer, changed_groups);
 		if (changed_groups)
 			row_compressor_update_group(row_compressor, slot);
 	}
@@ -1090,7 +1084,7 @@ row_compressor_new_row_is_in_new_group(RowCompressor *row_compressor, TupleTable
 	return false;
 }
 
-static void
+void
 row_compressor_append_row(RowCompressor *row_compressor, TupleTableSlot *row)
 {
 	int col;
@@ -1130,13 +1124,10 @@ row_compressor_append_row(RowCompressor *row_compressor, TupleTableSlot *row)
 	row_compressor->rows_compressed_into_current_value += 1;
 }
 
-static void
-row_compressor_flush(RowCompressor *row_compressor, CommandId mycid, bool changed_groups)
+HeapTuple
+row_compressor_build_tuple(RowCompressor *row_compressor)
 {
-	MemoryContext old_ctx;
-	old_ctx = MemoryContextSwitchTo(row_compressor->per_row_ctx);
-
-	HeapTuple compressed_tuple;
+	MemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);
 
 	for (int col = 0; col < row_compressor->n_input_columns; col++)
 	{
@@ -1186,21 +1177,19 @@ row_compressor_flush(RowCompressor *row_compressor, CommandId mycid, bool change
 		Int32GetDatum(row_compressor->rows_compressed_into_current_value);
 	row_compressor->compressed_is_null[row_compressor->count_metadata_column_offset] = false;
 
-	compressed_tuple = heap_form_tuple(RelationGetDescr(row_compressor->compressed_table),
-									   row_compressor->compressed_values,
-									   row_compressor->compressed_is_null);
-	Assert(row_compressor->bistate != NULL);
-	heap_insert(row_compressor->compressed_table,
-				compressed_tuple,
-				mycid,
-				row_compressor->insert_options /*=options*/,
-				row_compressor->bistate);
-	if (row_compressor->resultRelInfo->ri_NumIndices > 0)
-	{
-		ts_catalog_index_insert(row_compressor->resultRelInfo, compressed_tuple);
-	}
+	HeapTuple tuple = heap_form_tuple(row_compressor->out_desc,
+									  row_compressor->compressed_values,
+									  row_compressor->compressed_is_null);
 
-	heap_freetuple(compressed_tuple);
+	MemoryContextSwitchTo(old_cxt);
+
+	return tuple;
+}
+
+void
+row_compressor_clear_batch(RowCompressor *row_compressor, bool changed_groups)
+{
+	MemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);
 
 	/* free the compressed values now that we're done with them (the old compressor is freed in
 	 * finish()) */
@@ -1232,16 +1221,39 @@ row_compressor_flush(RowCompressor *row_compressor, CommandId mycid, bool change
 		row_compressor->compressed_is_null[compressed_col] = true;
 	}
 
-	if (NULL != row_compressor->on_flush)
-		row_compressor->on_flush(row_compressor,
-								 row_compressor->rows_compressed_into_current_value);
-
 	row_compressor->rowcnt_pre_compression += row_compressor->rows_compressed_into_current_value;
 	row_compressor->num_compressed_rows++;
 	row_compressor->rows_compressed_into_current_value = 0;
 
+	MemoryContextSwitchTo(old_cxt);
 	MemoryContextReset(row_compressor->per_row_ctx);
-	MemoryContextSwitchTo(old_ctx);
+}
+
+static void
+row_compressor_flush(RowCompressor *row_compressor, BulkWriter *writer, bool changed_groups)
+{
+	HeapTuple compressed_tuple = row_compressor_build_tuple(row_compressor);
+	MemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);
+
+	Assert(writer->bistate != NULL);
+	heap_insert(writer->out_rel,
+				compressed_tuple,
+				writer->mycid,
+				writer->insert_options /*=options*/,
+				writer->bistate);
+	if (writer->indexstate->ri_NumIndices > 0)
+	{
+		ts_catalog_index_insert(writer->indexstate, compressed_tuple);
+	}
+
+	heap_freetuple(compressed_tuple);
+
+	if (NULL != row_compressor->on_flush)
+		row_compressor->on_flush(row_compressor,
+								 row_compressor->rows_compressed_into_current_value);
+
+	MemoryContextSwitchTo(old_cxt);
+	row_compressor_clear_batch(row_compressor, changed_groups);
 }
 
 void
@@ -1253,9 +1265,11 @@ row_compressor_reset(RowCompressor *row_compressor)
 void
 row_compressor_close(RowCompressor *row_compressor)
 {
-	if (row_compressor->bistate)
-		FreeBulkInsertState(row_compressor->bistate);
-	CatalogCloseIndexes(row_compressor->resultRelInfo);
+	pfree(row_compressor->compressed_is_null);
+	pfree(row_compressor->compressed_values);
+	pfree(row_compressor->per_column);
+	pfree(row_compressor->uncompressed_col_to_compressed_col);
+	FreeTupleDesc(row_compressor->out_desc);
 }
 
 /******************
@@ -1329,43 +1343,124 @@ segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_n
 	return DatumGetBool(data_is_eq);
 }
 
-/**********************
- ** decompress_chunk **
- **********************/
-
-RowDecompressor
-build_decompressor(Relation in_rel, Relation out_rel)
+/*
+ * Build a map from compressed attribute numbers to non-compressed attribute
+ * numbers.
+ */
+static AttrMap *
+build_decompress_attrmap(const TupleDesc noncompressed_desc, const TupleDesc compressed_desc,
+						 AttrNumber *count_meta_attnum)
 {
-	TupleDesc in_desc = RelationGetDescr(in_rel);
-	TupleDesc out_desc = CreateTupleDescCopyConstr(RelationGetDescr(out_rel));
+	AttrMap *attrMap;
+	int outnatts;
+	int innatts;
+	int i;
+	int nextindesc = -1;
 
-	RowDecompressor decompressor = {
-		.num_compressed_columns = in_desc->natts,
+	outnatts = compressed_desc->natts;
+	innatts = noncompressed_desc->natts;
 
-		.in_desc = in_desc,
-		.in_rel = in_rel,
+	attrMap = make_attrmap(outnatts);
+	for (i = 0; i < outnatts; i++)
+	{
+		Form_pg_attribute outatt = TupleDescAttr(compressed_desc, i);
+		char *attname;
+		int j;
+
+		if (outatt->attisdropped)
+			continue;
+
+		attname = NameStr(outatt->attname);
+
+		if (strcmp(attname, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)
+		{
+			*count_meta_attnum = outatt->attnum;
+			/* No point in mapping this attribute since meta columns are not
+			 * present in the non-compressed relation and will not be found
+			 * below anyway. */
+			continue;
+		}
+		else if (strncmp(attname,
+						 COMPRESSION_COLUMN_METADATA_PREFIX,
+						 strlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)
+		{
+			/* We can skip other meta attributes as well */
+			continue;
+		}
+
+		for (j = 0; j < innatts; j++)
+		{
+			Form_pg_attribute inatt;
 
-		.out_desc = out_desc,
+			nextindesc++;
+			if (nextindesc >= innatts)
+				nextindesc = 0;
+
+			inatt = TupleDescAttr(noncompressed_desc, nextindesc);
+			if (inatt->attisdropped)
+				continue;
+			if (strcmp(attname, NameStr(inatt->attname)) == 0)
+			{
+				attrMap->attnums[i] = inatt->attnum;
+				break;
+			}
+		}
+	}
+	return attrMap;
+}
+
+BulkWriter
+bulk_writer_build(Relation out_rel, int insert_options)
+{
+	BulkWriter writer = {
 		.out_rel = out_rel,
 		.indexstate = CatalogOpenIndexes(out_rel),
-
 		.mycid = GetCurrentCommandId(true),
 		.bistate = GetBulkInsertState(),
+		.estate = CreateExecutorState(),
+		.insert_options = insert_options,
+	};
+
+	return writer;
+}
+
+void
+bulk_writer_close(BulkWriter *writer)
+{
+	FreeBulkInsertState(writer->bistate);
+	if (writer->indexstate)
+		CatalogCloseIndexes(writer->indexstate);
+	FreeExecutorState(writer->estate);
+}
 
+/**********************
+ ** decompress_chunk **
+ **********************/
+
+RowDecompressor
+build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc)
+{
+	AttrNumber count_meta_attnum = InvalidAttrNumber;
+	AttrMap *attrmap = build_decompress_attrmap(out_desc, in_desc, &count_meta_attnum);
+
+	Assert(AttributeNumberIsValid(count_meta_attnum));
+
+	RowDecompressor decompressor = {
+		.count_compressed_attindex = AttrNumberGetAttrOffset(count_meta_attnum),
+		.in_desc = CreateTupleDescCopyConstr(in_desc),
+		.out_desc = CreateTupleDescCopyConstr(out_desc),
 		.compressed_datums = palloc(sizeof(Datum) * in_desc->natts),
 		.compressed_is_nulls = palloc(sizeof(bool) * in_desc->natts),
 
 		/* cache memory used to store the decompressed datums/is_null for form_tuple */
 		.decompressed_datums = palloc(sizeof(Datum) * out_desc->natts),
 		.decompressed_is_nulls = palloc(sizeof(bool) * out_desc->natts),
-
 		.per_compressed_row_ctx = AllocSetContextCreate(CurrentMemoryContext,
 														"decompress chunk per-compressed row",
 														ALLOCSET_DEFAULT_SIZES),
-		.estate = CreateExecutorState(),
-
 		.decompressed_slots =
 			(TupleTableSlot **) palloc0(sizeof(void *) * TARGET_COMPRESSED_BATCH_SIZE),
+		.attrmap = attrmap,
 	};
 
 	create_per_compressed_column(&decompressor);
@@ -1395,11 +1490,17 @@ row_decompressor_reset(RowDecompressor *decompressor)
 void
 row_decompressor_close(RowDecompressor *decompressor)
 {
-	FreeBulkInsertState(decompressor->bistate);
 	MemoryContextDelete(decompressor->per_compressed_row_ctx);
-	CatalogCloseIndexes(decompressor->indexstate);
-	FreeExecutorState(decompressor->estate);
 	detoaster_close(&decompressor->detoaster);
+	free_attrmap(decompressor->attrmap);
+	FreeTupleDesc(decompressor->in_desc);
+	FreeTupleDesc(decompressor->out_desc);
+	pfree(decompressor->compressed_datums);
+	pfree(decompressor->compressed_is_nulls);
+	pfree(decompressor->decompressed_datums);
+	pfree(decompressor->decompressed_is_nulls);
+	pfree(decompressor->decompressed_slots);
+	pfree(decompressor->per_compressed_cols);
 }
 
 void
@@ -1421,10 +1522,12 @@ decompress_chunk(Oid in_table, Oid out_table)
 	Relation in_rel = table_open(in_table, ExclusiveLock);
 	int64 nrows_processed = 0;
 
-	RowDecompressor decompressor = build_decompressor(in_rel, out_rel);
+	BulkWriter writer = bulk_writer_build(out_rel, 0);
+	RowDecompressor decompressor =
+		build_decompressor(RelationGetDescr(in_rel), RelationGetDescr(out_rel));
 	TupleTableSlot *slot = table_slot_create(in_rel, NULL);
 	TableScanDesc scan = table_beginscan(in_rel, GetLatestSnapshot(), 0, (ScanKey) NULL);
-	int64 report_reltuples = calculate_reltuples_to_report(in_rel);
+	int64 report_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);
 
 	while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
 	{
@@ -1439,7 +1542,7 @@ decompress_chunk(Oid in_table, Oid out_table)
 		if (should_free)
 			heap_freetuple(tuple);
 
-		row_decompressor_decompress_row_to_table(&decompressor);
+		row_decompressor_decompress_row_to_table(&decompressor, &writer);
 
 		if ((++nrows_processed % report_reltuples) == 0)
 			elog(DEBUG2,
@@ -1455,6 +1558,7 @@ decompress_chunk(Oid in_table, Oid out_table)
 	table_endscan(scan);
 	ExecDropSingleTupleTableSlot(slot);
 	row_decompressor_close(&decompressor);
+	bulk_writer_close(&writer);
 
 	table_close(out_rel, NoLock);
 	table_close(in_rel, NoLock);
@@ -1479,10 +1583,6 @@ create_per_compressed_column(RowDecompressor *decompressor)
 		PerCompressedColumn *per_compressed_col = &decompressor->per_compressed_cols[col];
 		Form_pg_attribute compressed_attr = TupleDescAttr(decompressor->in_desc, col);
 		char *col_name = NameStr(compressed_attr->attname);
-		if (strcmp(col_name, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)
-		{
-			decompressor->count_compressed_attindex = col;
-		}
 
 		/* find the mapping from compressed column to uncompressed column, setting
 		 * the index of columns that don't have an uncompressed version
@@ -1490,7 +1590,8 @@ create_per_compressed_column(RowDecompressor *decompressor)
 		 * Assumption: column names are the same on compressed and
 		 *       uncompressed chunk.
 		 */
-		AttrNumber decompressed_colnum = get_attnum(decompressor->out_rel->rd_id, col_name);
+		AttrNumber decompressed_colnum = decompressor->attrmap->attnums[col];
+
 		if (!AttributeNumberIsValid(decompressed_colnum))
 		{
 			*per_compressed_col = (PerCompressedColumn){
@@ -1525,7 +1626,7 @@ create_per_compressed_column(RowDecompressor *decompressor)
 static void
 init_iterator(RowDecompressor *decompressor, CompressedDataHeader *header, int input_column)
 {
-	Assert(decompressor->num_compressed_columns > input_column);
+	Assert(decompressor->in_desc->natts > input_column);
 	PerCompressedColumn *column_info = &decompressor->per_compressed_cols[input_column];
 
 	/* Special compression block with the NULL compression algorithm,
@@ -1550,7 +1651,7 @@ init_batch(RowDecompressor *decompressor, AttrNumber *attnos, int num_attnos)
 	/*
 	 * Set segmentbys and compressed columns with default value.
 	 */
-	for (int input_column = 0; input_column < decompressor->num_compressed_columns; input_column++)
+	for (int input_column = 0; input_column < decompressor->in_desc->natts; input_column++)
 	{
 		PerCompressedColumn *column_info = &decompressor->per_compressed_cols[input_column];
 		const int output_index = column_info->decompressed_column_offset;
@@ -1639,7 +1740,7 @@ decompress_batch(RowDecompressor *decompressor)
 	 */
 	for (int current_row = 0; current_row < n_batch_rows; current_row++)
 	{
-		for (int16 col = 0; col < decompressor->num_compressed_columns; col++)
+		for (int col = 0; col < decompressor->in_desc->natts; col++)
 		{
 			PerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];
 			if (column_info->iterator == NULL)
@@ -1676,7 +1777,6 @@ decompress_batch(RowDecompressor *decompressor)
 		HeapTuple decompressed_tuple = heap_form_tuple(decompressor->out_desc,
 													   decompressor->decompressed_datums,
 													   decompressor->decompressed_is_nulls);
-		decompressed_tuple->t_tableOid = decompressor->out_rel->rd_id;
 
 		ExecStoreHeapTuple(decompressed_tuple, decompressed_slot, /* should_free = */ false);
 	}
@@ -1685,7 +1785,7 @@ decompress_batch(RowDecompressor *decompressor)
 	 * Verify that all other columns have ended, i.e. their length is consistent
 	 * with the count metadata column.
 	 */
-	for (int16 col = 0; col < decompressor->num_compressed_columns; col++)
+	for (int col = 0; col < decompressor->in_desc->natts; col++)
 	{
 		PerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];
 		if (column_info->iterator == NULL)
@@ -1741,7 +1841,7 @@ decompress_batch_next_row(RowDecompressor *decompressor, AttrNumber *attnos, int
 		CheckCompressedData(decompressor->unprocessed_tuples <= GLOBAL_MAX_ROWS_PER_COMPRESSION);
 	}
 
-	for (int16 col = 0; col < decompressor->num_compressed_columns; col++)
+	for (int col = 0; col < decompressor->in_desc->natts; col++)
 	{
 		PerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];
 		if (column_info->iterator == NULL)
@@ -1771,7 +1871,7 @@ decompress_single_column(RowDecompressor *decompressor, AttrNumber attno, bool *
 	int16 target_col = -1;
 	PerCompressedColumn *column_info = NULL;
 
-	for (int16 col = 0; col < decompressor->num_compressed_columns; col++)
+	for (int col = 0; col < decompressor->in_desc->natts; col++)
 	{
 		column_info = &decompressor->per_compressed_cols[col];
 		if (!column_info->is_compressed)
@@ -1820,19 +1920,19 @@ decompress_single_column(RowDecompressor *decompressor, AttrNumber attno, bool *
 }
 
 int
-row_decompressor_decompress_row_to_table(RowDecompressor *decompressor)
+row_decompressor_decompress_row_to_table(RowDecompressor *decompressor, BulkWriter *writer)
 {
 	const int n_batch_rows = decompress_batch(decompressor);
 
 	MemoryContext old_ctx = MemoryContextSwitchTo(decompressor->per_compressed_row_ctx);
 
 	/* Insert all decompressed rows into table using the bulk insert API. */
-	table_multi_insert(decompressor->out_rel,
+	table_multi_insert(writer->out_rel,
 					   decompressor->decompressed_slots,
 					   n_batch_rows,
-					   decompressor->mycid,
+					   writer->mycid,
 					   /* options = */ 0,
-					   decompressor->bistate);
+					   writer->bistate);
 
 	/*
 	 * Now, update the indexes. If we have several indexes, we want to first
@@ -1845,22 +1945,22 @@ row_decompressor_decompress_row_to_table(RowDecompressor *decompressor)
 	 * set it to this temporary ResultRelInfo, and insert all rows into this
 	 * single index.
 	 */
-	if (decompressor->indexstate->ri_NumIndices > 0)
+	if (writer->indexstate->ri_NumIndices > 0)
 	{
-		ResultRelInfo indexstate_copy = *decompressor->indexstate;
+		ResultRelInfo indexstate_copy = *writer->indexstate;
 		Relation single_index_relation;
 		IndexInfo *single_index_info;
 		indexstate_copy.ri_NumIndices = 1;
 		indexstate_copy.ri_IndexRelationDescs = &single_index_relation;
 		indexstate_copy.ri_IndexRelationInfo = &single_index_info;
-		for (int i = 0; i < decompressor->indexstate->ri_NumIndices; i++)
+		for (int i = 0; i < writer->indexstate->ri_NumIndices; i++)
 		{
-			single_index_relation = decompressor->indexstate->ri_IndexRelationDescs[i];
-			single_index_info = decompressor->indexstate->ri_IndexRelationInfo[i];
+			single_index_relation = writer->indexstate->ri_IndexRelationDescs[i];
+			single_index_info = writer->indexstate->ri_IndexRelationInfo[i];
 			for (int row = 0; row < n_batch_rows; row++)
 			{
 				TupleTableSlot *decompressed_slot = decompressor->decompressed_slots[row];
-				EState *estate = decompressor->estate;
+				EState *estate = writer->estate;
 				ExprContext *econtext = GetPerTupleExprContext(estate);
 
 				/* Arrange for econtext's scan tuple to be the tuple under test */
diff --git a/tsl/src/compression/compression.h b/tsl/src/compression/compression.h
index 6c813ced87c..fe6a40b5f14 100644
--- a/tsl/src/compression/compression.h
+++ b/tsl/src/compression/compression.h
@@ -6,6 +6,7 @@
 #pragma once
 
 #include <postgres.h>
+#include <access/attnum.h>
 #include <catalog/indexing.h>
 #include <executor/tuptable.h>
 #include <fmgr.h>
@@ -15,7 +16,6 @@
 
 typedef struct BulkInsertStateData *BulkInsertState;
 
-#include "compat/compat.h"
 #include "batch_metadata_builder_minmax.h"
 #include "hypertable.h"
 #include "nodes/decompress_chunk/detoaster.h"
@@ -121,25 +121,24 @@ typedef struct PerCompressedColumn
 	int16 decompressed_column_offset;
 } PerCompressedColumn;
 
-typedef struct RowDecompressor
+typedef struct BulkWriter
 {
-	PerCompressedColumn *per_compressed_cols;
-	int16 num_compressed_columns;
-	int16 count_compressed_attindex;
-
-	TupleDesc in_desc;
-	Relation in_rel;
-
-	TupleDesc out_desc;
 	Relation out_rel;
 	CatalogIndexState indexstate;
 	EState *estate;
-
 	CommandId mycid;
 	BulkInsertState bistate;
+	int insert_options; /* heap insert options */
+} BulkWriter;
+
+typedef struct RowDecompressor
+{
+	PerCompressedColumn *per_compressed_cols;
+	int16 count_compressed_attindex;
 
-	bool delete_only;
+	TupleDesc in_desc;
 
+	TupleDesc out_desc;
 	Datum *compressed_datums;
 	bool *compressed_is_nulls;
 
@@ -149,10 +148,10 @@ typedef struct RowDecompressor
 	MemoryContext per_compressed_row_ctx;
 	int64 batches_decompressed;
 	int64 tuples_decompressed;
-	int64 batches_deleted;
 
 	TupleTableSlot **decompressed_slots;
 	int unprocessed_tuples;
+	AttrMap *attrmap;
 
 	Detoaster detoaster;
 } RowDecompressor;
@@ -230,13 +229,8 @@ typedef struct RowCompressor
 	/* memory context reset per-row is stored */
 	MemoryContext per_row_ctx;
 
-	/* the table we're writing the compressed data to */
-	Relation compressed_table;
-	BulkInsertState bistate;
-	/* segment by index Oid if any */
-	Oid index_oid;
-	/* relation info necessary to update indexes on compressed table */
-	ResultRelInfo *resultRelInfo;
+	/* The descriptor of the compressed tuple we're generating */
+	TupleDesc out_desc;
 
 	/* in theory we could have more input columns than outputted ones, so we
 	   store the number of inputs/compressors separately */
@@ -267,8 +261,6 @@ typedef struct RowCompressor
 	int64 num_compressed_rows;
 	/* flag for checking if we are working on the first tuple */
 	bool first_iteration;
-	/* the heap insert options */
-	int insert_options;
 
 	/* Callback called on every flush. The ntuples argument is the number of
 	 * tuples flushed. Typically used for progress reporting. */
@@ -360,7 +352,8 @@ extern bool segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datu
 extern TupleTableSlot *compress_row_exec(CompressSingleRowState *cr, TupleTableSlot *slot);
 extern void compress_row_end(CompressSingleRowState *cr);
 extern void compress_row_destroy(CompressSingleRowState *cr);
-extern int row_decompressor_decompress_row_to_table(RowDecompressor *row_decompressor);
+extern int row_decompressor_decompress_row_to_table(RowDecompressor *row_decompressor,
+													BulkWriter *writer);
 extern void row_decompressor_decompress_row_to_tuplesort(RowDecompressor *row_decompressor,
 														 Tuplesortstate *tuplesortstate);
 extern void compress_chunk_populate_sort_info_for_column(const CompressionSettings *settings,
@@ -369,21 +362,24 @@ extern void compress_chunk_populate_sort_info_for_column(const CompressionSettin
 														 Oid *collation, bool *nulls_first);
 extern Tuplesortstate *compression_create_tuplesort_state(CompressionSettings *settings,
 														  Relation rel);
-extern void row_compressor_init(const CompressionSettings *settings, RowCompressor *row_compressor,
-								Relation uncompressed_table, Relation compressed_table,
-								int16 num_columns_in_compressed_table, bool need_bistate,
-								int insert_options);
+extern void row_compressor_init(RowCompressor *row_compressor, const CompressionSettings *settings,
+								const TupleDesc noncompressed_tupdesc,
+								const TupleDesc compressed_tupdesc);
 extern void row_compressor_reset(RowCompressor *row_compressor);
 extern void row_compressor_close(RowCompressor *row_compressor);
+extern HeapTuple row_compressor_build_tuple(RowCompressor *row_compressor);
+extern void row_compressor_clear_batch(RowCompressor *row_compressor, bool changed_groups);
 extern void row_compressor_append_sorted_rows(RowCompressor *row_compressor,
 											  Tuplesortstate *sorted_rel, TupleDesc sorted_desc,
-											  Relation in_rel);
+											  Relation in_rel, BulkWriter *writer);
 extern Oid get_compressed_chunk_index(ResultRelInfo *resultRelInfo,
 									  const CompressionSettings *settings);
 
 extern void segment_info_update(SegmentInfo *segment_info, Datum val, bool is_null);
 
-extern RowDecompressor build_decompressor(Relation in_rel, Relation out_rel);
+extern BulkWriter bulk_writer_build(Relation out_rel, int insert_options);
+extern void bulk_writer_close(BulkWriter *writer);
+extern RowDecompressor build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc);
 
 extern void row_decompressor_reset(RowDecompressor *decompressor);
 extern void row_decompressor_close(RowDecompressor *decompressor);
diff --git a/tsl/src/compression/compression_dml.c b/tsl/src/compression/compression_dml.c
index 2dbb9c0d0d2..a9174e31fea 100644
--- a/tsl/src/compression/compression_dml.c
+++ b/tsl/src/compression/compression_dml.c
@@ -57,8 +57,6 @@ get_batch_keys_for_unique_constraints(const ChunkInsertState *cis, Relation rela
 static BatchFilter *make_batchfilter(char *column_name, StrategyNumber strategy, Oid collation,
 									 RegProcedure opcode, Const *value, bool is_null_check,
 									 bool is_null, bool is_array_op);
-static inline TM_Result delete_compressed_tuple(RowDecompressor *decompressor, Snapshot snapshot,
-												HeapTuple compressed_tuple);
 static void report_error(TM_Result result);
 
 static bool key_column_is_null(tuple_filtering_constraints *constraints, Relation chunk_rel,
@@ -78,6 +76,7 @@ TupleDescGetAttrNumber(TupleDesc desc, const char *name)
 		if (strcmp(name, NameStr(desc->attrs[i].attname)) == 0)
 			return desc->attrs[i].attnum;
 	}
+
 	return InvalidAttrNumber;
 }
 
@@ -427,6 +426,7 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 						bool delete_only, Bitmapset *null_columns, List *is_nulls)
 {
 	HeapTuple compressed_tuple;
+	BulkWriter writer;
 	RowDecompressor decompressor;
 	bool decompressor_initialized = false;
 	bool valid = false;
@@ -521,9 +521,9 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 
 		if (!decompressor_initialized)
 		{
-			decompressor = build_decompressor(in_rel, out_rel);
-			decompressor.delete_only = delete_only;
+			decompressor = build_decompressor(RelationGetDescr(in_rel), RelationGetDescr(out_rel));
 			decompressor_initialized = true;
+			writer = bulk_writer_build(out_rel, 0);
 			meta_count_attno = TupleDescGetAttrNumber(decompressor.in_desc,
 													  COMPRESSION_COLUMN_METADATA_COUNT_NAME);
 			Assert(meta_count_attno != InvalidAttrNumber);
@@ -550,12 +550,23 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 		if (skip_current_tuple && *skip_current_tuple)
 		{
 			row_decompressor_close(&decompressor);
+			bulk_writer_close(&writer);
 			decompress_batch_endscan(scan);
 			ExecDropSingleTupleTableSlot(slot);
 			return stats;
 		}
 		write_logical_replication_msg_decompression_start();
-		result = delete_compressed_tuple(&decompressor, snapshot, compressed_tuple);
+
+		TM_FailureData tmfd;
+		result = table_tuple_delete(in_rel,
+									&compressed_tuple->t_self,
+									GetCurrentCommandId(true),
+									snapshot,
+									InvalidSnapshot,
+									true,
+									&tmfd,
+									false);
+
 		/* skip reporting error if isolation level is < Repeatable Read
 		 * since somebody decompressed the data concurrently, we need to take
 		 * that data into account as well when in Read Committed level
@@ -570,11 +581,12 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 		{
 			write_logical_replication_msg_decompression_end();
 			row_decompressor_close(&decompressor);
+			bulk_writer_close(&writer);
 			decompress_batch_endscan(scan);
 			report_error(result);
 			return stats;
 		}
-		if (decompressor.delete_only)
+		if (delete_only)
 		{
 			stats.batches_deleted++;
 			stats.tuples_deleted += DatumGetInt32(
@@ -582,7 +594,8 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 		}
 		else
 		{
-			stats.tuples_decompressed += row_decompressor_decompress_row_to_table(&decompressor);
+			stats.tuples_decompressed +=
+				row_decompressor_decompress_row_to_table(&decompressor, &writer);
 			stats.batches_decompressed++;
 		}
 		write_logical_replication_msg_decompression_end();
@@ -592,6 +605,7 @@ decompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, S
 	if (decompressor_initialized)
 	{
 		row_decompressor_close(&decompressor);
+		bulk_writer_close(&writer);
 	}
 
 	if (ts_guc_debug_compression_path_info)
@@ -1499,23 +1513,6 @@ find_matching_index(Relation comp_chunk_rel, List **index_filters, List **heap_f
 	return result_rel;
 }
 
-static inline TM_Result
-delete_compressed_tuple(RowDecompressor *decompressor, Snapshot snapshot,
-						HeapTuple compressed_tuple)
-{
-	TM_FailureData tmfd;
-	TM_Result result;
-	result = table_tuple_delete(decompressor->in_rel,
-								&compressed_tuple->t_self,
-								decompressor->mycid,
-								snapshot,
-								InvalidSnapshot,
-								true,
-								&tmfd,
-								false);
-	return result;
-}
-
 static void
 report_error(TM_Result result)
 {
diff --git a/tsl/src/compression/recompress.c b/tsl/src/compression/recompress.c
index 57f0f93c3d9..196c40cbdfd 100644
--- a/tsl/src/compression/recompress.c
+++ b/tsl/src/compression/recompress.c
@@ -58,7 +58,7 @@ static enum Batch_match_result match_tuple_batch(TupleTableSlot *compressed_slot
 static bool check_changed_group(CompressedSegmentInfo *current_segment, TupleTableSlot *slot,
 								int nsegmentby_cols);
 static void recompress_segment(Tuplesortstate *tuplesortstate, Relation compressed_chunk_rel,
-							   RowCompressor *row_compressor);
+							   RowCompressor *row_compressor, BulkWriter *writer);
 static void try_updating_chunk_status(Chunk *uncompressed_chunk, Relation uncompressed_chunk_rel);
 
 /*
@@ -251,27 +251,30 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 
 	/******************** row decompressor **************/
 
-	RowDecompressor decompressor = build_decompressor(compressed_chunk_rel, uncompressed_chunk_rel);
+	RowDecompressor decompressor = build_decompressor(RelationGetDescr(compressed_chunk_rel),
+													  RelationGetDescr(uncompressed_chunk_rel));
+
 	/********** row compressor *******************/
 	RowCompressor row_compressor;
-	row_compressor_init(settings,
-						&row_compressor,
-						uncompressed_chunk_rel,
-						compressed_chunk_rel,
-						compressed_rel_tupdesc->natts,
-						true /*need_bistate*/,
-						0 /*insert options*/);
+	Assert(settings->fd.compress_relid == RelationGetRelid(compressed_chunk_rel));
+	row_compressor_init(&row_compressor,
+						settings,
+						RelationGetDescr(uncompressed_chunk_rel),
+						RelationGetDescr(compressed_chunk_rel));
+
+	BulkWriter writer = bulk_writer_build(compressed_chunk_rel, 0);
+	Oid index_oid = get_compressed_chunk_index(writer.indexstate, settings);
 
 	/* For chunks with no segmentby settings, we can still do segmentwise recompression
 	 * The entire chunk is treated as a single segment
 	 */
 	elog(ts_guc_debug_compression_path_info ? INFO : DEBUG1,
 		 "Using index \"%s\" for recompression",
-		 get_rel_name(row_compressor.index_oid));
+		 get_rel_name(index_oid));
 
 	LOCKMODE index_lockmode =
 		ts_guc_enable_exclusive_locking_recompression ? ExclusiveLock : RowExclusiveLock;
-	Relation index_rel = index_open(row_compressor.index_oid, index_lockmode);
+	Relation index_rel = index_open(index_oid, index_lockmode);
 	ereport(DEBUG1,
 			(errmsg("locks acquired for recompression: \"%s.%s\"",
 					NameStr(uncompressed_chunk->fd.schema_name),
@@ -413,7 +416,8 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 				tuples_for_recompression = false;
 				recompress_segment(recompress_tuplesortstate,
 								   uncompressed_chunk_rel,
-								   &row_compressor);
+								   &row_compressor,
+								   &writer);
 				break;
 			}
 
@@ -460,7 +464,8 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 				tuples_for_recompression = false;
 				recompress_segment(recompress_tuplesortstate,
 								   uncompressed_chunk_rel,
-								   &row_compressor);
+								   &row_compressor,
+								   &writer);
 			}
 		}
 
@@ -494,7 +499,8 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 				tuples_for_recompression = false;
 				recompress_segment(recompress_tuplesortstate,
 								   uncompressed_chunk_rel,
-								   &row_compressor);
+								   &row_compressor,
+								   &writer);
 				break;
 			}
 
@@ -503,12 +509,16 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 
 		if (tuples_for_recompression)
 		{
-			recompress_segment(recompress_tuplesortstate, uncompressed_chunk_rel, &row_compressor);
+			recompress_segment(recompress_tuplesortstate,
+							   uncompressed_chunk_rel,
+							   &row_compressor,
+							   &writer);
 		}
 	}
 
 finish:
 	row_compressor_close(&row_compressor);
+	bulk_writer_close(&writer);
 	ExecDropSingleTupleTableSlot(uncompressed_slot);
 	ExecDropSingleTupleTableSlot(compressed_slot);
 	index_endscan(index_scan);
@@ -702,14 +712,15 @@ fetch_uncompressed_chunk_into_tuplesort(Tuplesortstate *tuplesortstate,
 /* Sort the tuples and recompress them */
 static void
 recompress_segment(Tuplesortstate *tuplesortstate, Relation compressed_chunk_rel,
-				   RowCompressor *row_compressor)
+				   RowCompressor *row_compressor, BulkWriter *writer)
 {
 	tuplesort_performsort(tuplesortstate);
 	row_compressor_reset(row_compressor);
 	row_compressor_append_sorted_rows(row_compressor,
 									  tuplesortstate,
 									  RelationGetDescr(compressed_chunk_rel),
-									  compressed_chunk_rel);
+									  compressed_chunk_rel,
+									  writer);
 	tuplesort_reset(tuplesortstate);
 	CommandCounterIncrement();
 }
diff --git a/tsl/src/hypercore/hypercore_handler.c b/tsl/src/hypercore/hypercore_handler.c
index c57f5327e8a..474919ce04d 100644
--- a/tsl/src/hypercore/hypercore_handler.c
+++ b/tsl/src/hypercore/hypercore_handler.c
@@ -1928,6 +1928,7 @@ hypercore_decompress_update_segment(Relation relation, const ItemPointer ctid, T
 	int n_batch_rows = 0;
 	uint16 tuple_index;
 	bool should_free;
+	CommandId cid = GetCurrentCommandId(true);
 
 	/* Nothing to do if this is not a compressed segment */
 	if (!is_compressed_tid(ctid))
@@ -1942,7 +1943,10 @@ hypercore_decompress_update_segment(Relation relation, const ItemPointer ctid, T
 	cslot = arrow_slot_get_compressed_slot(slot, NULL);
 	HeapTuple tuple = ExecFetchSlotHeapTuple(cslot, false, &should_free);
 
-	RowDecompressor decompressor = build_decompressor(crel, relation);
+	RowDecompressor decompressor =
+		build_decompressor(RelationGetDescr(crel), RelationGetDescr(relation));
+	BulkWriter writer = bulk_writer_build(relation, 0);
+
 	heap_deform_tuple(tuple,
 					  RelationGetDescr(crel),
 					  decompressor.compressed_datums,
@@ -1950,9 +1954,9 @@ hypercore_decompress_update_segment(Relation relation, const ItemPointer ctid, T
 
 	/* Must delete the segment before calling the decompression function below
 	 * or otherwise index updates will lead to conflicts */
-	result = table_tuple_delete(decompressor.in_rel,
+	result = table_tuple_delete(crel,
 								&cslot->tts_tid,
-								decompressor.mycid,
+								cid,
 								snapshot,
 								InvalidSnapshot,
 								true,
@@ -1961,7 +1965,7 @@ hypercore_decompress_update_segment(Relation relation, const ItemPointer ctid, T
 
 	Ensure(result == TM_Ok, "could not delete compressed segment, result: %u", result);
 
-	n_batch_rows = row_decompressor_decompress_row_to_table(&decompressor);
+	n_batch_rows = row_decompressor_decompress_row_to_table(&decompressor, &writer);
 	/* Return the TID of the decompressed conflicting tuple. Tuple index is
 	 * 1-indexed, so subtract 1. */
 	slot = decompressor.decompressed_slots[tuple_index - 1];
@@ -1970,6 +1974,7 @@ hypercore_decompress_update_segment(Relation relation, const ItemPointer ctid, T
 	/* Need to make decompressed (and deleted segment) visible */
 	CommandCounterIncrement();
 	row_decompressor_close(&decompressor);
+	bulk_writer_close(&writer);
 	table_close(crel, NoLock);
 
 	return n_batch_rows;
@@ -2180,23 +2185,28 @@ compress_and_swap_heap(Relation rel, Tuplesortstate *tuplesort, TransactionId *x
 											 AccessExclusiveLock);
 	Relation new_compressed_rel = table_open(new_compressed_relid, AccessExclusiveLock);
 	RowCompressor row_compressor;
+	BulkWriter writer;
 	double reltuples;
 	int32 relpages;
 
 	/* Initialize the compressor. */
-	row_compressor_init(settings,
-						&row_compressor,
-						rel,
-						new_compressed_rel,
-						RelationGetDescr(old_compressed_rel)->natts,
-						true /*need_bistate*/,
-						HEAP_INSERT_FROZEN);
+	Assert(settings->fd.relid == RelationGetRelid(rel));
+	row_compressor_init(&row_compressor,
+						settings,
+						RelationGetDescr(rel),
+						RelationGetDescr(new_compressed_rel));
 
+	writer = bulk_writer_build(new_compressed_rel, HEAP_INSERT_FROZEN);
 	row_compressor.on_flush = on_compression_progress;
-	row_compressor_append_sorted_rows(&row_compressor, tuplesort, tupdesc, old_compressed_rel);
+	row_compressor_append_sorted_rows(&row_compressor,
+									  tuplesort,
+									  tupdesc,
+									  old_compressed_rel,
+									  &writer);
 	reltuples = row_compressor.num_compressed_rows;
 	relpages = RelationGetNumberOfBlocks(new_compressed_rel);
 	row_compressor_close(&row_compressor);
+	bulk_writer_close(&writer);
 
 	table_close(new_compressed_rel, NoLock);
 	table_close(old_compressed_rel, NoLock);
@@ -3783,20 +3793,23 @@ convert_to_hypercore_finish(Oid relid)
 	const CompressionSettings *settings = ts_compression_settings_get(conversionstate->relid);
 	RowCompressor row_compressor;
 
-	row_compressor_init(settings,
-						&row_compressor,
-						relation,
-						compressed_rel,
-						RelationGetDescr(compressed_rel)->natts,
-						true /*need_bistate*/,
-						HEAP_INSERT_FROZEN);
+	Assert(settings->fd.relid == RelationGetRelid(relation));
+	Assert(settings->fd.compress_relid == RelationGetRelid(compressed_rel));
+
+	row_compressor_init(&row_compressor,
+						settings,
+						RelationGetDescr(relation),
+						RelationGetDescr(compressed_rel));
+	BulkWriter writer = bulk_writer_build(compressed_rel, HEAP_INSERT_FROZEN);
 
 	row_compressor_append_sorted_rows(&row_compressor,
 									  conversionstate->tuplesortstate,
 									  tupdesc,
-									  compressed_rel);
+									  compressed_rel,
+									  &writer);
 
 	row_compressor_close(&row_compressor);
+	bulk_writer_close(&writer);
 	tuplesort_end(conversionstate->tuplesortstate);
 	conversionstate->tuplesortstate = NULL;
 


Reviews:
user: login: mkindahl
body: Quite straightforward: checked the the replaced code was a proper replace.

You are switching from one field to another one inside `row_compressor_init`, and it looks correct, but adding assertions for these cases to make sure that nothing breaks can help avoid a potential bug. Added suggestions for those cases.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T13:44:31Z
user: login: antekresic
body: One small nit otherwise lgtm
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-19T09:53:52Z
user: login: erimatnor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:43:38Z
user: login: erimatnor
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:46:58Z

Review comments:
diff_hunk: @@ -882,14 +879,14 @@ check_for_limited_size_compressors(PerColumn *pcolumns, int16 natts)
  ** row_compressor **
  ********************/
 void
-row_compressor_init(const CompressionSettings *settings, RowCompressor *row_compressor,
-					Relation uncompressed_table, Relation compressed_table,
-					int16 num_columns_in_compressed_table, bool need_bistate, int insert_options)
+row_compressor_init(RowCompressor *row_compressor, const CompressionSettings *settings,
+					const TupleDesc noncompressed_tupdesc, const TupleDesc compressed_tupdesc)
 {
 	Name count_metadata_name = DatumGetName(
 		DirectFunctionCall1(namein, CStringGetDatum(COMPRESSION_COLUMN_METADATA_COUNT_NAME)));
 	AttrNumber count_metadata_column_num =
-		get_attnum(compressed_table->rd_id, NameStr(*count_metadata_name));
+		get_attnum(settings->fd.compress_relid, NameStr(*count_metadata_name));
+
path: tsl/src/compression/compression.c
user: login: mkindahl
body: I looked over the calls for this one, and they look correct, but since you remove the `compressed_table` from the function parameter, you might want to add an assertion (at least temporarily) before calling `row_compressor_init` making sure that the previous `compressed_table->rd_id` is identical to `settings->fd.compress_relid`. See suggestions.
created_at: 2025-05-16T13:27:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -251,27 +251,29 @@ recompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)
 
 	/******************** row decompressor **************/
 
-	RowDecompressor decompressor = build_decompressor(compressed_chunk_rel, uncompressed_chunk_rel);
+	RowDecompressor decompressor = build_decompressor(RelationGetDescr(compressed_chunk_rel),
+													  RelationGetDescr(uncompressed_chunk_rel));
+
 	/********** row compressor *******************/
 	RowCompressor row_compressor;
-	row_compressor_init(settings,
-						&row_compressor,
-						uncompressed_chunk_rel,
-						compressed_chunk_rel,
-						compressed_rel_tupdesc->natts,
-						true /*need_bistate*/,
-						0 /*insert options*/);
+	row_compressor_init(&row_compressor,
+						settings,
+						RelationGetDescr(uncompressed_chunk_rel),
+						RelationGetDescr(compressed_chunk_rel));
path: tsl/src/compression/recompress.c
user: login: mkindahl
body: ```suggestion
        Assert(compressed_chunk_rel->rd_id == settings->fd.compress_relid);
	row_compressor_init(&row_compressor,
						settings,
						RelationGetDescr(uncompressed_chunk_rel),
						RelationGetDescr(compressed_chunk_rel));
```
created_at: 2025-05-16T13:29:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2180,23 +2185,27 @@ compress_and_swap_heap(Relation rel, Tuplesortstate *tuplesort, TransactionId *x
 											 AccessExclusiveLock);
 	Relation new_compressed_rel = table_open(new_compressed_relid, AccessExclusiveLock);
 	RowCompressor row_compressor;
+	BulkWriter writer;
 	double reltuples;
 	int32 relpages;
 
 	/* Initialize the compressor. */
-	row_compressor_init(settings,
-						&row_compressor,
-						rel,
-						new_compressed_rel,
-						RelationGetDescr(old_compressed_rel)->natts,
-						true /*need_bistate*/,
-						HEAP_INSERT_FROZEN);
+	row_compressor_init(&row_compressor,
path: tsl/src/hypercore/hypercore_handler.c
user: login: mkindahl
body: ```suggestion
        Assert(new_compressed_rel->rd_id == settings->fd.compress_relid);
	row_compressor_init(&row_compressor,
```
created_at: 2025-05-16T13:30:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3783,20 +3792,20 @@ convert_to_hypercore_finish(Oid relid)
 	const CompressionSettings *settings = ts_compression_settings_get(conversionstate->relid);
 	RowCompressor row_compressor;
 
-	row_compressor_init(settings,
-						&row_compressor,
-						relation,
-						compressed_rel,
-						RelationGetDescr(compressed_rel)->natts,
-						true /*need_bistate*/,
-						HEAP_INSERT_FROZEN);
+	row_compressor_init(&row_compressor,
path: tsl/src/hypercore/hypercore_handler.c
user: login: mkindahl
body: ```suggestion
        Assert(compressed_rel->rd_id == settings->fd.compress_relid);
	row_compressor_init(&row_compressor,
```
created_at: 2025-05-16T13:31:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1329,43 +1340,115 @@ segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_n
 	return DatumGetBool(data_is_eq);
 }
 
-/**********************
- ** decompress_chunk **
- **********************/
-
-RowDecompressor
-build_decompressor(Relation in_rel, Relation out_rel)
+/*
+ *
+ */
+static AttrMap *
+build_decompress_attrmap(const TupleDesc noncompressed_desc, const TupleDesc compressed_desc,
+						 AttrNumber *count_meta_attnum)
 {
-	TupleDesc in_desc = RelationGetDescr(in_rel);
-	TupleDesc out_desc = CreateTupleDescCopyConstr(RelationGetDescr(out_rel));
+	AttrMap *attrMap;
+	int outnatts;
+	int innatts;
+	int i;
+	int nextindesc = -1;
 
-	RowDecompressor decompressor = {
-		.num_compressed_columns = in_desc->natts,
+	outnatts = compressed_desc->natts;
+	innatts = noncompressed_desc->natts;
+
+	attrMap = make_attrmap(outnatts);
+	for (i = 0; i < outnatts; i++)
+	{
+		Form_pg_attribute outatt = TupleDescAttr(compressed_desc, i);
+		char *attname;
+		int j;
 
-		.in_desc = in_desc,
-		.in_rel = in_rel,
+		if (outatt->attisdropped)
+			continue;
 
-		.out_desc = out_desc,
+		attname = NameStr(outatt->attname);
+
+		if (strcmp(attname, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)
+		{
+			*count_meta_attnum = outatt->attnum;
+			/* No point in mapping this attribute since meta columns are not
path: tsl/src/compression/compression.c
user: login: antekresic
body: We are just skipping the count metadata column, I guess we should include rest of the metadata columns as per the comment here.
created_at: 2025-05-19T09:48:07Z
author_association: MEMBER
diff_hunk: @@ -1329,43 +1340,115 @@ segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_n
 	return DatumGetBool(data_is_eq);
 }
 
-/**********************
- ** decompress_chunk **
- **********************/
-
-RowDecompressor
-build_decompressor(Relation in_rel, Relation out_rel)
+/*
+ *
+ */
+static AttrMap *
+build_decompress_attrmap(const TupleDesc noncompressed_desc, const TupleDesc compressed_desc,
+						 AttrNumber *count_meta_attnum)
 {
-	TupleDesc in_desc = RelationGetDescr(in_rel);
-	TupleDesc out_desc = CreateTupleDescCopyConstr(RelationGetDescr(out_rel));
+	AttrMap *attrMap;
+	int outnatts;
+	int innatts;
+	int i;
+	int nextindesc = -1;
 
-	RowDecompressor decompressor = {
-		.num_compressed_columns = in_desc->natts,
+	outnatts = compressed_desc->natts;
+	innatts = noncompressed_desc->natts;
+
+	attrMap = make_attrmap(outnatts);
+	for (i = 0; i < outnatts; i++)
+	{
+		Form_pg_attribute outatt = TupleDescAttr(compressed_desc, i);
+		char *attname;
+		int j;
 
-		.in_desc = in_desc,
-		.in_rel = in_rel,
+		if (outatt->attisdropped)
+			continue;
 
-		.out_desc = out_desc,
+		attname = NameStr(outatt->attname);
+
+		if (strcmp(attname, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)
+		{
+			*count_meta_attnum = outatt->attnum;
+			/* No point in mapping this attribute since meta columns are not
path: tsl/src/compression/compression.c
user: login: erimatnor
body: They will be skipped because they are not found when searched for. The reason I am doing a special handling of this column is because I want to return the attnum of the column since it is used later.
created_at: 2025-05-20T14:43:38Z
author_association: MEMBER
diff_hunk: @@ -1329,43 +1340,115 @@ segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_n
 	return DatumGetBool(data_is_eq);
 }
 
-/**********************
- ** decompress_chunk **
- **********************/
-
-RowDecompressor
-build_decompressor(Relation in_rel, Relation out_rel)
+/*
+ *
+ */
+static AttrMap *
+build_decompress_attrmap(const TupleDesc noncompressed_desc, const TupleDesc compressed_desc,
+						 AttrNumber *count_meta_attnum)
 {
-	TupleDesc in_desc = RelationGetDescr(in_rel);
-	TupleDesc out_desc = CreateTupleDescCopyConstr(RelationGetDescr(out_rel));
+	AttrMap *attrMap;
+	int outnatts;
+	int innatts;
+	int i;
+	int nextindesc = -1;
 
-	RowDecompressor decompressor = {
-		.num_compressed_columns = in_desc->natts,
+	outnatts = compressed_desc->natts;
+	innatts = noncompressed_desc->natts;
+
+	attrMap = make_attrmap(outnatts);
+	for (i = 0; i < outnatts; i++)
+	{
+		Form_pg_attribute outatt = TupleDescAttr(compressed_desc, i);
+		char *attname;
+		int j;
 
-		.in_desc = in_desc,
-		.in_rel = in_rel,
+		if (outatt->attisdropped)
+			continue;
 
-		.out_desc = out_desc,
+		attname = NameStr(outatt->attname);
+
+		if (strcmp(attname, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)
+		{
+			*count_meta_attnum = outatt->attnum;
+			/* No point in mapping this attribute since meta columns are not
path: tsl/src/compression/compression.c
user: login: erimatnor
body: I can add a check for the meta prefix. It won't make much of a difference, but will avoid an unnecessary loop over the non-compressed attributes.
created_at: 2025-05-20T14:46:58Z
author_association: MEMBER

PR comments:
user: login: erimatnor
created_at: 2025-05-15T16:04:01Z
body: For context: This change is needed for splitting compressed chunks.
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-05-15T16:17:34Z
body: ## [Codecov](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) Report
Attention: Patch coverage is `92.72727%` with `12 lines` in your changes missing coverage. Please review.
> Project coverage is 82.35%. Comparing base [(`09513b0`)](https://app.codecov.io/gh/timescale/timescaledb/commit/09513b064bc823c5525d82c242d9affe0a308e17?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) to head [(`00242e5`)](https://app.codecov.io/gh/timescale/timescaledb/commit/00242e5690bf66c36add9b78ae345f857c5e265c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale).
> Report is 3 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) | Patch % | Lines |
|---|---|---|
| [tsl/src/compression/compression.c](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&filepath=tsl%2Fsrc%2Fcompression%2Fcompression.c&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale#diff-dHNsL3NyYy9jb21wcmVzc2lvbi9jb21wcmVzc2lvbi5j) | 93.93% | [0 Missing and 8 partials :warning: ](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) |
| [tsl/src/hypercore/hypercore\_handler.c](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&filepath=tsl%2Fsrc%2Fhypercore%2Fhypercore_handler.c&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale#diff-dHNsL3NyYy9oeXBlcmNvcmUvaHlwZXJjb3JlX2hhbmRsZXIuYw==) | 81.25% | [0 Missing and 3 partials :warning: ](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) |
| [tsl/src/compression/recompress.c](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&filepath=tsl%2Fsrc%2Fcompression%2Frecompress.c&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale#diff-dHNsL3NyYy9jb21wcmVzc2lvbi9yZWNvbXByZXNzLmM=) | 87.50% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #8138      +/-   ##
==========================================
+ Coverage   82.30%   82.35%   +0.05%     
==========================================
  Files         255      255              
  Lines       47744    47784      +40     
  Branches    12038    12047       +9     
==========================================
+ Hits        39295    39353      +58     
- Misses       3635     3661      +26     
+ Partials     4814     4770      -44     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/timescale/timescaledb/pull/8138?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=timescale).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE