77#include <access/attmap.h>
88#include <access/attnum.h>
99#include <access/detoast.h>
10+ #include <access/htup_details.h>
1011#include <access/skey.h>
1112#include <access/tupdesc.h>
1213#include <catalog/heap.h>
@@ -146,7 +147,7 @@ static void row_compressor_process_ordered_slot(RowCompressor *row_compressor, T
146147static void row_compressor_update_group (RowCompressor * row_compressor , TupleTableSlot * row );
147148static bool row_compressor_new_row_is_in_new_group (RowCompressor * row_compressor ,
148149 TupleTableSlot * row );
149- static void create_per_compressed_column (RowDecompressor * decompressor );
150+ static void create_per_compressed_column (RowDecompressor * decompressor , bool internal_error );
150151static void row_compressor_append_row (RowCompressor * row_compressor , TupleTableSlot * row );
151152static void row_compressor_flush (RowCompressor * row_compressor , BulkWriter * writer ,
152153 bool changed_groups );
@@ -2022,8 +2023,9 @@ bulk_writer_close(BulkWriter *writer)
20222023 ** decompress_chunk **
20232024 **********************/
20242025
2025- RowDecompressor
2026- build_decompressor (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid , Oid out_oid )
2026+ static inline RowDecompressor
2027+ build_decompressor_common (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid ,
2028+ Oid out_oid , bool internal_error )
20272029{
20282030 AttrNumber count_meta_attnum = InvalidAttrNumber ;
20292031 AttrMap * attrmap = build_decompress_attrmap (out_desc , in_desc , & count_meta_attnum );
@@ -2054,7 +2056,7 @@ build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc, Oid in_oid
20542056 .attrmap = attrmap ,
20552057 };
20562058
2057- create_per_compressed_column (& decompressor );
2059+ create_per_compressed_column (& decompressor , internal_error );
20582060
20592061 /*
20602062 * We need to make sure decompressed_is_nulls is in a defined state. While this
@@ -2071,6 +2073,12 @@ build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc, Oid in_oid
20712073 return decompressor ;
20722074}
20732075
2076+ RowDecompressor
2077+ build_decompressor (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid , Oid out_oid )
2078+ {
2079+ return build_decompressor_common (in_desc , out_desc , in_oid , out_oid , true);
2080+ }
2081+
20742082void
20752083row_decompressor_init_stats (RowDecompressor * decompressor , Oid compressed_relid ,
20762084 Oid uncompressed_relid , CmdType cmd_type )
@@ -2190,7 +2198,7 @@ decompress_chunk(Oid in_table, Oid out_table)
21902198}
21912199
21922200static void
2193- create_per_compressed_column (RowDecompressor * decompressor )
2201+ create_per_compressed_column (RowDecompressor * decompressor , bool internal_error )
21942202{
21952203 Oid compressed_data_type_oid = ts_custom_type_cache_get (CUSTOM_TYPE_COMPRESSED_DATA )-> type_oid ;
21962204 Assert (OidIsValid (compressed_data_type_oid ));
@@ -2234,12 +2242,14 @@ create_per_compressed_column(RowDecompressor *decompressor)
22342242 is_compressed = compressed_attr -> atttypid == compressed_data_type_oid ;
22352243 if (!is_compressed && compressed_attr -> atttypid != decompressed_type )
22362244 {
2237- elog (ERROR ,
2238- "compressed table type '%s' does not match decompressed table type '%s' for "
2239- "segment-by column \"%s\"" ,
2240- format_type_be (compressed_attr -> atttypid ),
2241- format_type_be (decompressed_type ),
2242- col_name );
2245+ ereport (ERROR ,
2246+ (errcode (internal_error ? ERRCODE_INTERNAL_ERROR :
2247+ ERRCODE_INVALID_PARAMETER_VALUE ),
2248+ errmsg ("compressed table type '%s' does not match decompressed "
2249+ "table type '%s' for segment-by column \"%s\"" ,
2250+ format_type_be (compressed_attr -> atttypid ),
2251+ format_type_be (decompressed_type ),
2252+ col_name )));
22432253 }
22442254
22452255 * per_compressed_col = (PerCompressedColumn ){
@@ -2767,6 +2777,133 @@ tsl_compressed_data_decompress_reverse(PG_FUNCTION_ARGS)
27672777 ;
27682778}
27692779
2780+ /*
2781+ * decompress_batch(compressed_tuple record) RETURNS SETOF record
2782+ *
2783+ * Decompresses a single compressed batch (one row of a compressed chunk) into
2784+ * the individual rows it represents. The shape of the input compressed tuple
2785+ * is taken from the record's own type info (typeId/typmod in the header).
2786+ * The shape of the output rows is taken from the call site's column
2787+ * definition list (the AS t(...) clause).
2788+ */
2789+ typedef struct DecompressBatchSRFContext
2790+ {
2791+ RowDecompressor decompressor ;
2792+ int next_row ;
2793+ int total_rows ;
2794+ } DecompressBatchSRFContext ;
2795+
2796+ Datum
2797+ tsl_decompress_batch (PG_FUNCTION_ARGS )
2798+ {
2799+ FuncCallContext * funcctx ;
2800+ DecompressBatchSRFContext * decompress_ctx ;
2801+
2802+ if (SRF_IS_FIRSTCALL ())
2803+ {
2804+ funcctx = SRF_FIRSTCALL_INIT ();
2805+ MemoryContext oldcontext = MemoryContextSwitchTo (funcctx -> multi_call_memory_ctx );
2806+
2807+ TupleDesc out_desc ;
2808+ if (get_call_result_type (fcinfo , NULL , & out_desc ) != TYPEFUNC_COMPOSITE )
2809+ {
2810+ ereport (ERROR ,
2811+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
2812+ errmsg ("function returning record called in context "
2813+ "that cannot accept type record" )));
2814+ }
2815+ BlessTupleDesc (out_desc );
2816+
2817+ HeapTupleHeader td = PG_GETARG_HEAPTUPLEHEADER (0 );
2818+ TupleDesc in_desc =
2819+ lookup_rowtype_tupdesc (HeapTupleHeaderGetTypeId (td ), HeapTupleHeaderGetTypMod (td ));
2820+
2821+ /*
2822+ * Verify that the input record actually looks like a compressed-chunk
2823+ * row before handing it to the decompressor. The decompressor identifies
2824+ * the batch row count through the "_ts_meta_count" metadata column and
2825+ * indexes into the compressed datums using its position; a record that
2826+ * lacks this column would otherwise lead to an out-of-bounds access. The
2827+ * record is fully caller-controlled, so this must be a runtime check
2828+ * rather than an assertion.
2829+ */
2830+ bool has_count_column = false;
2831+ for (int i = 0 ; i < in_desc -> natts ; i ++ )
2832+ {
2833+ Form_pg_attribute attr = TupleDescAttr (in_desc , i );
2834+
2835+ if (attr -> attisdropped )
2836+ {
2837+ continue ;
2838+ }
2839+
2840+ if (strcmp (NameStr (attr -> attname ), COMPRESSION_COLUMN_METADATA_COUNT_NAME ) == 0 )
2841+ {
2842+ if (attr -> atttypid != INT4OID )
2843+ {
2844+ ereport (ERROR ,
2845+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
2846+ errmsg ("input record is not a compressed batch" ),
2847+ errdetail ("Column \"%s\" must have type integer." ,
2848+ COMPRESSION_COLUMN_METADATA_COUNT_NAME )));
2849+ }
2850+
2851+ has_count_column = true;
2852+ break ;
2853+ }
2854+ }
2855+
2856+ if (!has_count_column )
2857+ {
2858+ ReleaseTupleDesc (in_desc );
2859+ ereport (ERROR ,
2860+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
2861+ errmsg ("input record is not a compressed batch" ),
2862+ errdetail ("A compressed batch must have a \"%s\" metadata column." ,
2863+ COMPRESSION_COLUMN_METADATA_COUNT_NAME )));
2864+ }
2865+
2866+ decompress_ctx = palloc0 (sizeof (DecompressBatchSRFContext ));
2867+ decompress_ctx -> decompressor =
2868+ build_decompressor_common (in_desc , out_desc , InvalidOid , InvalidOid , false);
2869+
2870+ HeapTupleData tmp ;
2871+ tmp .t_len = HeapTupleHeaderGetDatumLength (td );
2872+ ItemPointerSetInvalid (& tmp .t_self );
2873+ tmp .t_tableOid = InvalidOid ;
2874+ tmp .t_data = td ;
2875+
2876+ heap_deform_tuple (& tmp ,
2877+ in_desc ,
2878+ decompress_ctx -> decompressor .compressed_datums ,
2879+ decompress_ctx -> decompressor .compressed_is_nulls );
2880+
2881+ ReleaseTupleDesc (in_desc );
2882+
2883+ decompress_ctx -> total_rows = decompress_batch (& decompress_ctx -> decompressor );
2884+ decompress_ctx -> next_row = 0 ;
2885+
2886+ funcctx -> user_fctx = decompress_ctx ;
2887+ funcctx -> tuple_desc = decompress_ctx -> decompressor .out_desc ;
2888+ MemoryContextSwitchTo (oldcontext );
2889+ }
2890+
2891+ funcctx = SRF_PERCALL_SETUP ();
2892+ decompress_ctx = (DecompressBatchSRFContext * ) funcctx -> user_fctx ;
2893+
2894+ if (decompress_ctx -> next_row >= decompress_ctx -> total_rows )
2895+ {
2896+ row_decompressor_close (& decompress_ctx -> decompressor );
2897+ SRF_RETURN_DONE (funcctx );
2898+ }
2899+
2900+ TupleTableSlot * slot =
2901+ decompress_ctx -> decompressor .decompressed_slots [decompress_ctx -> next_row ++ ];
2902+ bool should_free ;
2903+ HeapTuple tuple = ExecFetchSlotHeapTuple (slot , false, & should_free );
2904+ SRF_RETURN_NEXT (funcctx , HeapTupleGetDatum (tuple ));
2905+ }
2906+
27702907/*
27712908 * compressed_data_to_array(compressed_data, element_type) -> anyarray
27722909 */
0 commit comments