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 );
@@ -2024,8 +2025,9 @@ bulk_writer_close(BulkWriter *writer)
20242025 ** decompress_chunk **
20252026 **********************/
20262027
2027- RowDecompressor
2028- build_decompressor (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid , Oid out_oid )
2028+ static inline RowDecompressor
2029+ build_decompressor_common (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid ,
2030+ Oid out_oid , bool internal_error )
20292031{
20302032 AttrNumber count_meta_attnum = InvalidAttrNumber ;
20312033 AttrMap * attrmap = build_decompress_attrmap (out_desc , in_desc , & count_meta_attnum );
@@ -2056,7 +2058,7 @@ build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc, Oid in_oid
20562058 .attrmap = attrmap ,
20572059 };
20582060
2059- create_per_compressed_column (& decompressor );
2061+ create_per_compressed_column (& decompressor , internal_error );
20602062
20612063 /*
20622064 * We need to make sure decompressed_is_nulls is in a defined state. While this
@@ -2073,6 +2075,13 @@ build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc, Oid in_oid
20732075 return decompressor ;
20742076}
20752077
2078+ RowDecompressor
2079+ build_decompressor (const TupleDesc in_desc , const TupleDesc out_desc , Oid in_oid , Oid out_oid )
2080+ {
2081+ return build_decompressor_common (in_desc , out_desc , in_oid , out_oid , true);
2082+ }
2083+
2084+
20762085void
20772086row_decompressor_init_stats (RowDecompressor * decompressor , Oid compressed_relid ,
20782087 Oid uncompressed_relid , CmdType cmd_type )
@@ -2192,7 +2201,7 @@ decompress_chunk(Oid in_table, Oid out_table)
21922201}
21932202
21942203static void
2195- create_per_compressed_column (RowDecompressor * decompressor )
2204+ create_per_compressed_column (RowDecompressor * decompressor , bool internal_error )
21962205{
21972206 Oid compressed_data_type_oid = ts_custom_type_cache_get (CUSTOM_TYPE_COMPRESSED_DATA )-> type_oid ;
21982207 Assert (OidIsValid (compressed_data_type_oid ));
@@ -2236,12 +2245,15 @@ create_per_compressed_column(RowDecompressor *decompressor)
22362245 is_compressed = compressed_attr -> atttypid == compressed_data_type_oid ;
22372246 if (!is_compressed && compressed_attr -> atttypid != decompressed_type )
22382247 {
2239- elog (ERROR ,
2240- "compressed table type '%s' does not match decompressed table type '%s' for "
2241- "segment-by column \"%s\"" ,
2242- format_type_be (compressed_attr -> atttypid ),
2243- format_type_be (decompressed_type ),
2244- col_name );
2248+
2249+ ereport (ERROR ,
2250+ (errcode (internal_error ? ERRCODE_INTERNAL_ERROR :
2251+ ERRCODE_INVALID_PARAMETER_VALUE ),
2252+ errmsg ("compressed table type '%s' does not match decompressed "
2253+ "table type '%s' for segment-by column \"%s\"" ,
2254+ format_type_be (compressed_attr -> atttypid ),
2255+ format_type_be (decompressed_type ),
2256+ col_name )));
22452257 }
22462258
22472259 * per_compressed_col = (PerCompressedColumn ){
@@ -2769,6 +2781,133 @@ tsl_compressed_data_decompress_reverse(PG_FUNCTION_ARGS)
27692781 ;
27702782}
27712783
2784+ /*
2785+ * decompress_batch(compressed_tuple record) RETURNS SETOF record
2786+ *
2787+ * Decompresses a single compressed batch (one row of a compressed chunk) into
2788+ * the individual rows it represents. The shape of the input compressed tuple
2789+ * is taken from the record's own type info (typeId/typmod in the header).
2790+ * The shape of the output rows is taken from the call site's column
2791+ * definition list (the AS t(...) clause).
2792+ */
2793+ typedef struct DecompressBatchSRFContext
2794+ {
2795+ RowDecompressor decompressor ;
2796+ int next_row ;
2797+ int total_rows ;
2798+ } DecompressBatchSRFContext ;
2799+
2800+ Datum
2801+ tsl_decompress_batch (PG_FUNCTION_ARGS )
2802+ {
2803+ FuncCallContext * funcctx ;
2804+ DecompressBatchSRFContext * decompress_ctx ;
2805+
2806+ if (SRF_IS_FIRSTCALL ())
2807+ {
2808+ funcctx = SRF_FIRSTCALL_INIT ();
2809+ MemoryContext oldcontext = MemoryContextSwitchTo (funcctx -> multi_call_memory_ctx );
2810+
2811+ TupleDesc out_desc ;
2812+ if (get_call_result_type (fcinfo , NULL , & out_desc ) != TYPEFUNC_COMPOSITE )
2813+ {
2814+ ereport (ERROR ,
2815+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
2816+ errmsg ("function returning record called in context "
2817+ "that cannot accept type record" )));
2818+ }
2819+ BlessTupleDesc (out_desc );
2820+
2821+ HeapTupleHeader td = PG_GETARG_HEAPTUPLEHEADER (0 );
2822+ TupleDesc in_desc =
2823+ lookup_rowtype_tupdesc (HeapTupleHeaderGetTypeId (td ), HeapTupleHeaderGetTypMod (td ));
2824+
2825+ /*
2826+ * Verify that the input record actually looks like a compressed-chunk
2827+ * row before handing it to the decompressor. The decompressor identifies
2828+ * the batch row count through the "_ts_meta_count" metadata column and
2829+ * indexes into the compressed datums using its position; a record that
2830+ * lacks this column would otherwise lead to an out-of-bounds access. The
2831+ * record is fully caller-controlled, so this must be a runtime check
2832+ * rather than an assertion.
2833+ */
2834+ bool has_count_column = false;
2835+ for (int i = 0 ; i < in_desc -> natts ; i ++ )
2836+ {
2837+ Form_pg_attribute attr = TupleDescAttr (in_desc , i );
2838+
2839+ if (attr -> attisdropped )
2840+ {
2841+ continue ;
2842+ }
2843+
2844+ if (strcmp (NameStr (attr -> attname ), COMPRESSION_COLUMN_METADATA_COUNT_NAME ) == 0 )
2845+ {
2846+ if (attr -> atttypid != INT4OID )
2847+ {
2848+ ereport (ERROR ,
2849+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
2850+ errmsg ("input record is not a compressed batch" ),
2851+ errdetail ("Column \"%s\" must have type integer." ,
2852+ COMPRESSION_COLUMN_METADATA_COUNT_NAME )));
2853+ }
2854+
2855+ has_count_column = true;
2856+ break ;
2857+ }
2858+ }
2859+
2860+ if (!has_count_column )
2861+ {
2862+ ReleaseTupleDesc (in_desc );
2863+ ereport (ERROR ,
2864+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
2865+ errmsg ("input record is not a compressed batch" ),
2866+ errdetail ("A compressed batch must have a \"%s\" metadata column." ,
2867+ COMPRESSION_COLUMN_METADATA_COUNT_NAME )));
2868+ }
2869+
2870+ decompress_ctx = palloc0 (sizeof (DecompressBatchSRFContext ));
2871+ decompress_ctx -> decompressor =
2872+ build_decompressor_common (in_desc , out_desc , InvalidOid , InvalidOid , false);
2873+
2874+ HeapTupleData tmp ;
2875+ tmp .t_len = HeapTupleHeaderGetDatumLength (td );
2876+ ItemPointerSetInvalid (& tmp .t_self );
2877+ tmp .t_tableOid = InvalidOid ;
2878+ tmp .t_data = td ;
2879+
2880+ heap_deform_tuple (& tmp ,
2881+ in_desc ,
2882+ decompress_ctx -> decompressor .compressed_datums ,
2883+ decompress_ctx -> decompressor .compressed_is_nulls );
2884+
2885+ ReleaseTupleDesc (in_desc );
2886+
2887+ decompress_ctx -> total_rows = decompress_batch (& decompress_ctx -> decompressor );
2888+ decompress_ctx -> next_row = 0 ;
2889+
2890+ funcctx -> user_fctx = decompress_ctx ;
2891+ funcctx -> tuple_desc = decompress_ctx -> decompressor .out_desc ;
2892+ MemoryContextSwitchTo (oldcontext );
2893+ }
2894+
2895+ funcctx = SRF_PERCALL_SETUP ();
2896+ decompress_ctx = (DecompressBatchSRFContext * ) funcctx -> user_fctx ;
2897+
2898+ if (decompress_ctx -> next_row >= decompress_ctx -> total_rows )
2899+ {
2900+ row_decompressor_close (& decompress_ctx -> decompressor );
2901+ SRF_RETURN_DONE (funcctx );
2902+ }
2903+
2904+ TupleTableSlot * slot =
2905+ decompress_ctx -> decompressor .decompressed_slots [decompress_ctx -> next_row ++ ];
2906+ bool should_free ;
2907+ HeapTuple tuple = ExecFetchSlotHeapTuple (slot , false, & should_free );
2908+ SRF_RETURN_NEXT (funcctx , HeapTupleGetDatum (tuple ));
2909+ }
2910+
27722911/*
27732912 * compressed_data_to_array(compressed_data, element_type) -> anyarray
27742913 */
0 commit comments