LCOV - code coverage report
Current view: top level - src/backend/access/table - toast_helper.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 97.4 % 115 112
Test Date: 2026-02-17 17:20:33 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * toast_helper.c
       4              :  *    Helper functions for table AMs implementing compressed or
       5              :  *    out-of-line storage of varlena attributes.
       6              :  *
       7              :  * Copyright (c) 2000-2026, PostgreSQL Global Development Group
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/access/table/toast_helper.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : 
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/detoast.h"
      18              : #include "access/toast_helper.h"
      19              : #include "access/toast_internals.h"
      20              : #include "catalog/pg_type_d.h"
      21              : #include "varatt.h"
      22              : 
      23              : 
      24              : /*
      25              :  * Prepare to TOAST a tuple.
      26              :  *
      27              :  * tupleDesc, toast_values, and toast_isnull are required parameters; they
      28              :  * provide the necessary details about the tuple to be toasted.
      29              :  *
      30              :  * toast_oldvalues and toast_oldisnull should be NULL for a newly-inserted
      31              :  * tuple; for an update, they should describe the existing tuple.
      32              :  *
      33              :  * All of these arrays should have a length equal to tupleDesc->natts.
      34              :  *
      35              :  * On return, toast_flags and toast_attr will have been initialized.
      36              :  * toast_flags is just a single uint8, but toast_attr is a caller-provided
      37              :  * array with a length equal to tupleDesc->natts.  The caller need not
      38              :  * perform any initialization of the array before calling this function.
      39              :  */
      40              : void
      41        20550 : toast_tuple_init(ToastTupleContext *ttc)
      42              : {
      43        20550 :     TupleDesc   tupleDesc = ttc->ttc_rel->rd_att;
      44        20550 :     int         numAttrs = tupleDesc->natts;
      45              :     int         i;
      46              : 
      47        20550 :     ttc->ttc_flags = 0;
      48              : 
      49       208901 :     for (i = 0; i < numAttrs; i++)
      50              :     {
      51       188351 :         Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
      52              :         varlena    *old_value;
      53              :         varlena    *new_value;
      54              : 
      55       188351 :         ttc->ttc_attr[i].tai_colflags = 0;
      56       188351 :         ttc->ttc_attr[i].tai_oldexternal = NULL;
      57       188351 :         ttc->ttc_attr[i].tai_compression = att->attcompression;
      58              : 
      59       188351 :         if (ttc->ttc_oldvalues != NULL)
      60              :         {
      61              :             /*
      62              :              * For UPDATE get the old and new values of this attribute
      63              :              */
      64              :             old_value =
      65        46517 :                 (varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]);
      66              :             new_value =
      67        46517 :                 (varlena *) DatumGetPointer(ttc->ttc_values[i]);
      68              : 
      69              :             /*
      70              :              * If the old value is stored on disk, check if it has changed so
      71              :              * we have to delete it later.
      72              :              */
      73        51909 :             if (att->attlen == -1 && !ttc->ttc_oldisnull[i] &&
      74         5392 :                 VARATT_IS_EXTERNAL_ONDISK(old_value))
      75              :             {
      76          464 :                 if (ttc->ttc_isnull[i] ||
      77          457 :                     !VARATT_IS_EXTERNAL_ONDISK(new_value) ||
      78           49 :                     memcmp(old_value, new_value,
      79              :                            VARSIZE_EXTERNAL(old_value)) != 0)
      80              :                 {
      81              :                     /*
      82              :                      * The old external stored value isn't needed any more
      83              :                      * after the update
      84              :                      */
      85          416 :                     ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD;
      86          416 :                     ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD;
      87              :                 }
      88              :                 else
      89              :                 {
      90              :                     /*
      91              :                      * This attribute isn't changed by this update so we reuse
      92              :                      * the original reference to the old value in the new
      93              :                      * tuple.
      94              :                      */
      95           48 :                     ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
      96           48 :                     continue;
      97              :                 }
      98              :             }
      99              :         }
     100              :         else
     101              :         {
     102              :             /*
     103              :              * For INSERT simply get the new value
     104              :              */
     105       141834 :             new_value = (varlena *) DatumGetPointer(ttc->ttc_values[i]);
     106              :         }
     107              : 
     108              :         /*
     109              :          * Handle NULL attributes
     110              :          */
     111       188303 :         if (ttc->ttc_isnull[i])
     112              :         {
     113        20517 :             ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
     114        20517 :             ttc->ttc_flags |= TOAST_HAS_NULLS;
     115        20517 :             continue;
     116              :         }
     117              : 
     118              :         /*
     119              :          * Now look at varlena attributes
     120              :          */
     121       167786 :         if (att->attlen == -1)
     122              :         {
     123              :             /*
     124              :              * If the table's attribute says PLAIN always, force it so.
     125              :              */
     126        38666 :             if (att->attstorage == TYPSTORAGE_PLAIN)
     127         1188 :                 ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
     128              : 
     129              :             /*
     130              :              * We took care of UPDATE above, so any external value we find
     131              :              * still in the tuple must be someone else's that we cannot reuse
     132              :              * (this includes the case of an out-of-line in-memory datum).
     133              :              * Fetch it back (without decompression, unless we are forcing
     134              :              * PLAIN storage).  If necessary, we'll push it out as a new
     135              :              * external value below.
     136              :              */
     137        38666 :             if (VARATT_IS_EXTERNAL(new_value))
     138              :             {
     139          473 :                 ttc->ttc_attr[i].tai_oldexternal = new_value;
     140          473 :                 if (att->attstorage == TYPSTORAGE_PLAIN)
     141            0 :                     new_value = detoast_attr(new_value);
     142              :                 else
     143          473 :                     new_value = detoast_external_attr(new_value);
     144          473 :                 ttc->ttc_values[i] = PointerGetDatum(new_value);
     145          473 :                 ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE;
     146          473 :                 ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
     147              :             }
     148              : 
     149              :             /*
     150              :              * Remember the size of this attribute
     151              :              */
     152        38666 :             ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value);
     153              :         }
     154              :         else
     155              :         {
     156              :             /*
     157              :              * Not a varlena attribute, plain storage always
     158              :              */
     159       129120 :             ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE;
     160              :         }
     161              :     }
     162        20550 : }
     163              : 
     164              : /*
     165              :  * Find the largest varlena attribute that satisfies certain criteria.
     166              :  *
     167              :  * The relevant column must not be marked TOASTCOL_IGNORE, and if the
     168              :  * for_compression flag is passed as true, it must also not be marked
     169              :  * TOASTCOL_INCOMPRESSIBLE.
     170              :  *
     171              :  * The column must have attstorage EXTERNAL or EXTENDED if check_main is
     172              :  * false, and must have attstorage MAIN if check_main is true.
     173              :  *
     174              :  * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE);
     175              :  * if not, no benefit is to be expected by compressing it.
     176              :  *
     177              :  * The return value is the index of the biggest suitable column, or
     178              :  * -1 if there is none.
     179              :  */
     180              : int
     181        24152 : toast_tuple_find_biggest_attribute(ToastTupleContext *ttc,
     182              :                                    bool for_compression, bool check_main)
     183              : {
     184        24152 :     TupleDesc   tupleDesc = ttc->ttc_rel->rd_att;
     185        24152 :     int         numAttrs = tupleDesc->natts;
     186        24152 :     int         biggest_attno = -1;
     187        24152 :     int32       biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
     188        24152 :     int32       skip_colflags = TOASTCOL_IGNORE;
     189              :     int         i;
     190              : 
     191        24152 :     if (for_compression)
     192        23349 :         skip_colflags |= TOASTCOL_INCOMPRESSIBLE;
     193              : 
     194       311021 :     for (i = 0; i < numAttrs; i++)
     195              :     {
     196       286869 :         Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
     197              : 
     198       286869 :         if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0)
     199       236310 :             continue;
     200        50559 :         if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i])))
     201            0 :             continue;           /* can't happen, toast_action would be PLAIN */
     202        98620 :         if (for_compression &&
     203        48061 :             VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i])))
     204         4466 :             continue;
     205        46093 :         if (check_main && att->attstorage != TYPSTORAGE_MAIN)
     206            0 :             continue;
     207        46093 :         if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED &&
     208         2995 :             att->attstorage != TYPSTORAGE_EXTERNAL)
     209           68 :             continue;
     210              : 
     211        46025 :         if (ttc->ttc_attr[i].tai_size > biggest_size)
     212              :         {
     213        30592 :             biggest_attno = i;
     214        30592 :             biggest_size = ttc->ttc_attr[i].tai_size;
     215              :         }
     216              :     }
     217              : 
     218        24152 :     return biggest_attno;
     219              : }
     220              : 
     221              : /*
     222              :  * Try compression for an attribute.
     223              :  *
     224              :  * If we find that the attribute is not compressible, mark it so.
     225              :  */
     226              : void
     227        19606 : toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
     228              : {
     229        19606 :     Datum      *value = &ttc->ttc_values[attribute];
     230              :     Datum       new_value;
     231        19606 :     ToastAttrInfo *attr = &ttc->ttc_attr[attribute];
     232              : 
     233        19606 :     new_value = toast_compress_datum(*value, attr->tai_compression);
     234              : 
     235        19606 :     if (DatumGetPointer(new_value) != NULL)
     236              :     {
     237              :         /* successful compression */
     238        18617 :         if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
     239           61 :             pfree(DatumGetPointer(*value));
     240        18617 :         *value = new_value;
     241        18617 :         attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
     242        18617 :         attr->tai_size = VARSIZE(DatumGetPointer(*value));
     243        18617 :         ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
     244              :     }
     245              :     else
     246              :     {
     247              :         /* incompressible, ignore on subsequent compression passes */
     248          989 :         attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE;
     249              :     }
     250        19606 : }
     251              : 
     252              : /*
     253              :  * Move an attribute to external storage.
     254              :  */
     255              : void
     256         8542 : toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options)
     257              : {
     258         8542 :     Datum      *value = &ttc->ttc_values[attribute];
     259         8542 :     Datum       old_value = *value;
     260         8542 :     ToastAttrInfo *attr = &ttc->ttc_attr[attribute];
     261              : 
     262         8542 :     attr->tai_colflags |= TOASTCOL_IGNORE;
     263         8542 :     *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal,
     264              :                               options);
     265         8542 :     if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
     266         5479 :         pfree(DatumGetPointer(old_value));
     267         8542 :     attr->tai_colflags |= TOASTCOL_NEEDS_FREE;
     268         8542 :     ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE);
     269         8542 : }
     270              : 
     271              : /*
     272              :  * Perform appropriate cleanup after one tuple has been subjected to TOAST.
     273              :  */
     274              : void
     275        20550 : toast_tuple_cleanup(ToastTupleContext *ttc)
     276              : {
     277        20550 :     TupleDesc   tupleDesc = ttc->ttc_rel->rd_att;
     278        20550 :     int         numAttrs = tupleDesc->natts;
     279              : 
     280              :     /*
     281              :      * Free allocated temp values
     282              :      */
     283        20550 :     if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0)
     284              :     {
     285              :         int         i;
     286              : 
     287       208016 :         for (i = 0; i < numAttrs; i++)
     288              :         {
     289       187549 :             ToastAttrInfo *attr = &ttc->ttc_attr[i];
     290              : 
     291       187549 :             if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0)
     292        22092 :                 pfree(DatumGetPointer(ttc->ttc_values[i]));
     293              :         }
     294              :     }
     295              : 
     296              :     /*
     297              :      * Delete external values from the old tuple
     298              :      */
     299        20550 :     if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0)
     300              :     {
     301              :         int         i;
     302              : 
     303        10060 :         for (i = 0; i < numAttrs; i++)
     304              :         {
     305         9690 :             ToastAttrInfo *attr = &ttc->ttc_attr[i];
     306              : 
     307         9690 :             if ((attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0)
     308          416 :                 toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false);
     309              :         }
     310              :     }
     311        20550 : }
     312              : 
     313              : /*
     314              :  * Check for external stored attributes and delete them from the secondary
     315              :  * relation.
     316              :  */
     317              : void
     318          295 : toast_delete_external(Relation rel, const Datum *values, const bool *isnull,
     319              :                       bool is_speculative)
     320              : {
     321          295 :     TupleDesc   tupleDesc = rel->rd_att;
     322          295 :     int         numAttrs = tupleDesc->natts;
     323              :     int         i;
     324              : 
     325         2416 :     for (i = 0; i < numAttrs; i++)
     326              :     {
     327         2121 :         if (TupleDescCompactAttr(tupleDesc, i)->attlen == -1)
     328              :         {
     329          710 :             Datum       value = values[i];
     330              : 
     331          710 :             if (isnull[i])
     332          197 :                 continue;
     333          513 :             else if (VARATT_IS_EXTERNAL_ONDISK(DatumGetPointer(value)))
     334          305 :                 toast_delete_datum(rel, value, is_speculative);
     335              :         }
     336              :     }
     337          295 : }
        

Generated by: LCOV version 2.0-1