|
7 | 7 | #include <access/attmap.h> |
8 | 8 | #include <access/attnum.h> |
9 | 9 | #include <access/detoast.h> |
| 10 | +#include <access/htup_details.h> |
10 | 11 | #include <access/skey.h> |
11 | 12 | #include <access/tupdesc.h> |
12 | 13 | #include <catalog/heap.h> |
@@ -2765,6 +2766,129 @@ tsl_compressed_data_decompress_reverse(PG_FUNCTION_ARGS) |
2765 | 2766 | ; |
2766 | 2767 | } |
2767 | 2768 |
|
| 2769 | +/* |
| 2770 | + * decompress_batch(compressed_tuple record) RETURNS SETOF record |
| 2771 | + * |
| 2772 | + * Decompresses a single compressed batch (one row of a compressed chunk) into |
| 2773 | + * the individual rows it represents. The shape of the input compressed tuple |
| 2774 | + * is taken from the record's own type info (typeId/typmod in the header). |
| 2775 | + * The shape of the output rows is taken from the call site's column |
| 2776 | + * definition list (the AS t(...) clause). |
| 2777 | + */ |
| 2778 | +typedef struct DecompressBatchSRFContext |
| 2779 | +{ |
| 2780 | + RowDecompressor decompressor; |
| 2781 | + int next_row; |
| 2782 | + int total_rows; |
| 2783 | +} DecompressBatchSRFContext; |
| 2784 | + |
| 2785 | +Datum |
| 2786 | +tsl_decompress_batch(PG_FUNCTION_ARGS) |
| 2787 | +{ |
| 2788 | + FuncCallContext *funcctx; |
| 2789 | + DecompressBatchSRFContext *decompress_ctx; |
| 2790 | + |
| 2791 | + if (SRF_IS_FIRSTCALL()) |
| 2792 | + { |
| 2793 | + funcctx = SRF_FIRSTCALL_INIT(); |
| 2794 | + MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
| 2795 | + |
| 2796 | + TupleDesc out_desc; |
| 2797 | + if (get_call_result_type(fcinfo, NULL, &out_desc) != TYPEFUNC_COMPOSITE) |
| 2798 | + { |
| 2799 | + ereport(ERROR, |
| 2800 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 2801 | + errmsg("function returning record called in context " |
| 2802 | + "that cannot accept type record"))); |
| 2803 | + } |
| 2804 | + BlessTupleDesc(out_desc); |
| 2805 | + |
| 2806 | + HeapTupleHeader td = PG_GETARG_HEAPTUPLEHEADER(0); |
| 2807 | + TupleDesc in_desc = |
| 2808 | + lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td), HeapTupleHeaderGetTypMod(td)); |
| 2809 | + |
| 2810 | + /* |
| 2811 | + * Verify that the input record actually looks like a compressed-chunk |
| 2812 | + * row before handing it to the decompressor. The decompressor identifies |
| 2813 | + * the batch row count through the "_ts_meta_count" metadata column and |
| 2814 | + * indexes into the compressed datums using its position; a record that |
| 2815 | + * lacks this column would otherwise lead to an out-of-bounds access. The |
| 2816 | + * record is fully caller-controlled, so this must be a runtime check |
| 2817 | + * rather than an assertion. |
| 2818 | + */ |
| 2819 | + bool has_count_column = false; |
| 2820 | + for (int i = 0; i < in_desc->natts; i++) |
| 2821 | + { |
| 2822 | + Form_pg_attribute attr = TupleDescAttr(in_desc, i); |
| 2823 | + |
| 2824 | + if (attr->attisdropped) |
| 2825 | + continue; |
| 2826 | + |
| 2827 | + if (strcmp(NameStr(attr->attname), COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0) |
| 2828 | + { |
| 2829 | + if (attr->atttypid != INT4OID) |
| 2830 | + ereport(ERROR, |
| 2831 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 2832 | + errmsg("input record is not a compressed batch"), |
| 2833 | + errdetail("Column \"%s\" must have type integer.", |
| 2834 | + COMPRESSION_COLUMN_METADATA_COUNT_NAME))); |
| 2835 | + |
| 2836 | + has_count_column = true; |
| 2837 | + break; |
| 2838 | + } |
| 2839 | + } |
| 2840 | + |
| 2841 | + if (!has_count_column) |
| 2842 | + { |
| 2843 | + ReleaseTupleDesc(in_desc); |
| 2844 | + ereport(ERROR, |
| 2845 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 2846 | + errmsg("input record is not a compressed batch"), |
| 2847 | + errdetail("A compressed batch must have a \"%s\" metadata column.", |
| 2848 | + COMPRESSION_COLUMN_METADATA_COUNT_NAME))); |
| 2849 | + } |
| 2850 | + |
| 2851 | + decompress_ctx = palloc0(sizeof(DecompressBatchSRFContext)); |
| 2852 | + decompress_ctx->decompressor = |
| 2853 | + build_decompressor(in_desc, out_desc, InvalidOid, InvalidOid); |
| 2854 | + |
| 2855 | + HeapTupleData tmp; |
| 2856 | + tmp.t_len = HeapTupleHeaderGetDatumLength(td); |
| 2857 | + ItemPointerSetInvalid(&tmp.t_self); |
| 2858 | + tmp.t_tableOid = InvalidOid; |
| 2859 | + tmp.t_data = td; |
| 2860 | + |
| 2861 | + heap_deform_tuple(&tmp, |
| 2862 | + in_desc, |
| 2863 | + decompress_ctx->decompressor.compressed_datums, |
| 2864 | + decompress_ctx->decompressor.compressed_is_nulls); |
| 2865 | + |
| 2866 | + ReleaseTupleDesc(in_desc); |
| 2867 | + |
| 2868 | + decompress_ctx->total_rows = decompress_batch(&decompress_ctx->decompressor); |
| 2869 | + decompress_ctx->next_row = 0; |
| 2870 | + |
| 2871 | + funcctx->user_fctx = decompress_ctx; |
| 2872 | + funcctx->tuple_desc = decompress_ctx->decompressor.out_desc; |
| 2873 | + MemoryContextSwitchTo(oldcontext); |
| 2874 | + } |
| 2875 | + |
| 2876 | + funcctx = SRF_PERCALL_SETUP(); |
| 2877 | + decompress_ctx = (DecompressBatchSRFContext *) funcctx->user_fctx; |
| 2878 | + |
| 2879 | + if (decompress_ctx->next_row >= decompress_ctx->total_rows) |
| 2880 | + { |
| 2881 | + row_decompressor_close(&decompress_ctx->decompressor); |
| 2882 | + SRF_RETURN_DONE(funcctx); |
| 2883 | + } |
| 2884 | + |
| 2885 | + TupleTableSlot *slot = |
| 2886 | + decompress_ctx->decompressor.decompressed_slots[decompress_ctx->next_row++]; |
| 2887 | + bool should_free; |
| 2888 | + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free); |
| 2889 | + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); |
| 2890 | +} |
| 2891 | + |
2768 | 2892 | /* |
2769 | 2893 | * compressed_data_to_array(compressed_data, element_type) -> anyarray |
2770 | 2894 | */ |
|
0 commit comments