LCOV - code coverage report
Current view: top level - src/backend/catalog - toasting.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 95.1 % 164 156
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 7 7
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 50.0 % 70 35

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * toasting.c
       4                 :             :  *        This file contains routines to support creation of toast tables
       5                 :             :  *
       6                 :             :  *
       7                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *        src/backend/catalog/toasting.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "access/heapam.h"
      18                 :             : #include "access/toast_compression.h"
      19                 :             : #include "access/xact.h"
      20                 :             : #include "catalog/binary_upgrade.h"
      21                 :             : #include "catalog/catalog.h"
      22                 :             : #include "catalog/dependency.h"
      23                 :             : #include "catalog/heap.h"
      24                 :             : #include "catalog/index.h"
      25                 :             : #include "catalog/namespace.h"
      26                 :             : #include "catalog/pg_am.h"
      27                 :             : #include "catalog/pg_namespace.h"
      28                 :             : #include "catalog/pg_opclass.h"
      29                 :             : #include "catalog/toasting.h"
      30                 :             : #include "miscadmin.h"
      31                 :             : #include "nodes/makefuncs.h"
      32                 :             : #include "utils/fmgroids.h"
      33                 :             : #include "utils/rel.h"
      34                 :             : #include "utils/syscache.h"
      35                 :             : 
      36                 :             : static void CheckAndCreateToastTable(Oid relOid, Datum reloptions,
      37                 :             :                                                                          LOCKMODE lockmode, bool check,
      38                 :             :                                                                          Oid OIDOldToast);
      39                 :             : static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
      40                 :             :                                                            Datum reloptions, LOCKMODE lockmode, bool check,
      41                 :             :                                                            Oid OIDOldToast);
      42                 :             : static bool needs_toast_table(Relation rel);
      43                 :             : 
      44                 :             : 
      45                 :             : /*
      46                 :             :  * CreateToastTable variants
      47                 :             :  *              If the table needs a toast table, and doesn't already have one,
      48                 :             :  *              then create a toast table for it.
      49                 :             :  *
      50                 :             :  * reloptions for the toast table can be passed, too.  Pass (Datum) 0
      51                 :             :  * for default reloptions.
      52                 :             :  *
      53                 :             :  * We expect the caller to have verified that the relation is a table and have
      54                 :             :  * already done any necessary permission checks.  Callers expect this function
      55                 :             :  * to end with CommandCounterIncrement if it makes any changes.
      56                 :             :  */
      57                 :             : void
      58                 :        2432 : AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
      59                 :             : {
      60                 :        2432 :         CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
      61                 :        2432 : }
      62                 :             : 
      63                 :             : void
      64                 :         101 : NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
      65                 :             :                                                 Oid OIDOldToast)
      66                 :             : {
      67                 :         101 :         CheckAndCreateToastTable(relOid, reloptions, lockmode, false, OIDOldToast);
      68                 :         101 : }
      69                 :             : 
      70                 :             : void
      71                 :        4393 : NewRelationCreateToastTable(Oid relOid, Datum reloptions)
      72                 :             : {
      73                 :        4393 :         CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
      74                 :             :                                                          InvalidOid);
      75                 :        4393 : }
      76                 :             : 
      77                 :             : static void
      78                 :        6926 : CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
      79                 :             :                                                  bool check, Oid OIDOldToast)
      80                 :             : {
      81                 :        6926 :         Relation        rel;
      82                 :             : 
      83                 :        6926 :         rel = table_open(relOid, lockmode);
      84                 :             : 
      85                 :             :         /* create_toast_table does all the work */
      86                 :       13852 :         (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode,
      87                 :        6926 :                                                           check, OIDOldToast);
      88                 :             : 
      89                 :        6926 :         table_close(rel, NoLock);
      90                 :        6926 : }
      91                 :             : 
      92                 :             : /*
      93                 :             :  * Create a toast table during bootstrap
      94                 :             :  *
      95                 :             :  * Here we need to prespecify the OIDs of the toast table and its index
      96                 :             :  */
      97                 :             : void
      98                 :          35 : BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
      99                 :             : {
     100                 :          35 :         Relation        rel;
     101                 :             : 
     102                 :          35 :         rel = table_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
     103                 :             : 
     104   [ -  +  #  # ]:          35 :         if (rel->rd_rel->relkind != RELKIND_RELATION &&
     105                 :           0 :                 rel->rd_rel->relkind != RELKIND_MATVIEW)
     106   [ #  #  #  # ]:           0 :                 elog(ERROR, "\"%s\" is not a table or materialized view",
     107                 :             :                          relName);
     108                 :             : 
     109                 :             :         /* create_toast_table does all the work */
     110         [ +  - ]:          35 :         if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
     111                 :             :                                                         AccessExclusiveLock, false, InvalidOid))
     112   [ #  #  #  # ]:           0 :                 elog(ERROR, "\"%s\" does not require a toast table",
     113                 :             :                          relName);
     114                 :             : 
     115                 :          35 :         table_close(rel, NoLock);
     116                 :          35 : }
     117                 :             : 
     118                 :             : 
     119                 :             : /*
     120                 :             :  * create_toast_table --- internal workhorse
     121                 :             :  *
     122                 :             :  * rel is already opened and locked
     123                 :             :  * toastOid and toastIndexOid are normally InvalidOid, but during
     124                 :             :  * bootstrap they can be nonzero to specify hand-assigned OIDs
     125                 :             :  */
     126                 :             : static bool
     127                 :        6961 : create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
     128                 :             :                                    Datum reloptions, LOCKMODE lockmode, bool check,
     129                 :             :                                    Oid OIDOldToast)
     130                 :             : {
     131                 :        6961 :         Oid                     relOid = RelationGetRelid(rel);
     132                 :        6961 :         HeapTuple       reltup;
     133                 :        6961 :         TupleDesc       tupdesc;
     134                 :        6961 :         bool            shared_relation;
     135                 :        6961 :         bool            mapped_relation;
     136                 :        6961 :         Relation        toast_rel;
     137                 :        6961 :         Relation        class_rel;
     138                 :        6961 :         Oid                     toast_relid;
     139                 :        6961 :         Oid                     namespaceid;
     140                 :        6961 :         char            toast_relname[NAMEDATALEN];
     141                 :        6961 :         char            toast_idxname[NAMEDATALEN];
     142                 :        6961 :         IndexInfo  *indexInfo;
     143                 :        6961 :         Oid                     collationIds[2];
     144                 :        6961 :         Oid                     opclassIds[2];
     145                 :        6961 :         int16           coloptions[2];
     146                 :        6961 :         ObjectAddress baseobject,
     147                 :             :                                 toastobject;
     148                 :             : 
     149                 :             :         /*
     150                 :             :          * Is it already toasted?
     151                 :             :          */
     152         [ +  + ]:        6961 :         if (rel->rd_rel->reltoastrelid != InvalidOid)
     153                 :         672 :                 return false;
     154                 :             : 
     155                 :             :         /*
     156                 :             :          * Check to see whether the table actually needs a TOAST table.
     157                 :             :          */
     158         [ -  + ]:        6289 :         if (!IsBinaryUpgrade)
     159                 :             :         {
     160                 :             :                 /* Normal mode, normal check */
     161         [ +  + ]:        6289 :                 if (!needs_toast_table(rel))
     162                 :        4790 :                         return false;
     163                 :        1499 :         }
     164                 :             :         else
     165                 :             :         {
     166                 :             :                 /*
     167                 :             :                  * In binary-upgrade mode, create a TOAST table if and only if
     168                 :             :                  * pg_upgrade told us to (ie, a TOAST table OID has been provided).
     169                 :             :                  *
     170                 :             :                  * This indicates that the old cluster had a TOAST table for the
     171                 :             :                  * current table.  We must create a TOAST table to receive the old
     172                 :             :                  * TOAST file, even if the table seems not to need one.
     173                 :             :                  *
     174                 :             :                  * Contrariwise, if the old cluster did not have a TOAST table, we
     175                 :             :                  * should be able to get along without one even if the new version's
     176                 :             :                  * needs_toast_table rules suggest we should have one.  There is a lot
     177                 :             :                  * of daylight between where we will create a TOAST table and where
     178                 :             :                  * one is really necessary to avoid failures, so small cross-version
     179                 :             :                  * differences in the when-to-create heuristic shouldn't be a problem.
     180                 :             :                  * If we tried to create a TOAST table anyway, we would have the
     181                 :             :                  * problem that it might take up an OID that will conflict with some
     182                 :             :                  * old-cluster table we haven't seen yet.
     183                 :             :                  */
     184         [ #  # ]:           0 :                 if (!OidIsValid(binary_upgrade_next_toast_pg_class_oid))
     185                 :           0 :                         return false;
     186                 :             :         }
     187                 :             : 
     188                 :             :         /*
     189                 :             :          * If requested check lockmode is sufficient. This is a cross check in
     190                 :             :          * case of errors or conflicting decisions in earlier code.
     191                 :             :          */
     192   [ +  +  +  - ]:        1499 :         if (check && lockmode != AccessExclusiveLock)
     193   [ #  #  #  # ]:           0 :                 elog(ERROR, "AccessExclusiveLock required to add toast table.");
     194                 :             : 
     195                 :             :         /*
     196                 :             :          * Create the toast table and its index
     197                 :             :          */
     198                 :        2998 :         snprintf(toast_relname, sizeof(toast_relname),
     199                 :        1499 :                          "pg_toast_%u", relOid);
     200                 :        2998 :         snprintf(toast_idxname, sizeof(toast_idxname),
     201                 :        1499 :                          "pg_toast_%u_index", relOid);
     202                 :             : 
     203                 :             :         /* this is pretty painful...  need a tuple descriptor */
     204                 :        1499 :         tupdesc = CreateTemplateTupleDesc(3);
     205                 :        1499 :         TupleDescInitEntry(tupdesc, (AttrNumber) 1,
     206                 :             :                                            "chunk_id",
     207                 :             :                                            OIDOID,
     208                 :             :                                            -1, 0);
     209                 :        1499 :         TupleDescInitEntry(tupdesc, (AttrNumber) 2,
     210                 :             :                                            "chunk_seq",
     211                 :             :                                            INT4OID,
     212                 :             :                                            -1, 0);
     213                 :        1499 :         TupleDescInitEntry(tupdesc, (AttrNumber) 3,
     214                 :             :                                            "chunk_data",
     215                 :             :                                            BYTEAOID,
     216                 :             :                                            -1, 0);
     217                 :             : 
     218                 :             :         /*
     219                 :             :          * Ensure that the toast table doesn't itself get toasted, or we'll be
     220                 :             :          * toast :-(.  This is essential for chunk_data because type bytea is
     221                 :             :          * toastable; hit the other two just to be sure.
     222                 :             :          */
     223                 :        1499 :         TupleDescAttr(tupdesc, 0)->attstorage = TYPSTORAGE_PLAIN;
     224                 :        1499 :         TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN;
     225                 :        1499 :         TupleDescAttr(tupdesc, 2)->attstorage = TYPSTORAGE_PLAIN;
     226                 :             : 
     227                 :             :         /* Toast field should not be compressed */
     228                 :        1499 :         TupleDescAttr(tupdesc, 0)->attcompression = InvalidCompressionMethod;
     229                 :        1499 :         TupleDescAttr(tupdesc, 1)->attcompression = InvalidCompressionMethod;
     230                 :        1499 :         TupleDescAttr(tupdesc, 2)->attcompression = InvalidCompressionMethod;
     231                 :             : 
     232                 :             :         /*
     233                 :             :          * Toast tables for regular relations go in pg_toast; those for temp
     234                 :             :          * relations go into the per-backend temp-toast-table namespace.
     235                 :             :          */
     236         [ +  + ]:        1499 :         if (isTempOrTempToastNamespace(rel->rd_rel->relnamespace))
     237                 :         145 :                 namespaceid = GetTempToastNamespace();
     238                 :             :         else
     239                 :        1354 :                 namespaceid = PG_TOAST_NAMESPACE;
     240                 :             : 
     241                 :             :         /* Toast table is shared if and only if its parent is. */
     242                 :        1499 :         shared_relation = rel->rd_rel->relisshared;
     243                 :             : 
     244                 :             :         /* It's mapped if and only if its parent is, too */
     245   [ +  +  +  -  :        1499 :         mapped_relation = RelationIsMapped(rel);
          +  -  +  -  +  
                      - ]
     246                 :             : 
     247                 :        2998 :         toast_relid = heap_create_with_catalog(toast_relname,
     248                 :        1499 :                                                                                    namespaceid,
     249                 :        1499 :                                                                                    rel->rd_rel->reltablespace,
     250                 :        1499 :                                                                                    toastOid,
     251                 :             :                                                                                    InvalidOid,
     252                 :             :                                                                                    InvalidOid,
     253                 :        1499 :                                                                                    rel->rd_rel->relowner,
     254                 :        1499 :                                                                                    table_relation_toast_am(rel),
     255                 :        1499 :                                                                                    tupdesc,
     256                 :             :                                                                                    NIL,
     257                 :             :                                                                                    RELKIND_TOASTVALUE,
     258                 :        1499 :                                                                                    rel->rd_rel->relpersistence,
     259                 :        1499 :                                                                                    shared_relation,
     260                 :        1499 :                                                                                    mapped_relation,
     261                 :             :                                                                                    ONCOMMIT_NOOP,
     262                 :        1499 :                                                                                    reloptions,
     263                 :             :                                                                                    false,
     264                 :             :                                                                                    true,
     265                 :             :                                                                                    true,
     266                 :        1499 :                                                                                    OIDOldToast,
     267                 :             :                                                                                    NULL);
     268         [ +  - ]:        1499 :         Assert(toast_relid != InvalidOid);
     269                 :             : 
     270                 :             :         /* make the toast relation visible, else table_open will fail */
     271                 :        1499 :         CommandCounterIncrement();
     272                 :             : 
     273                 :             :         /* ShareLock is not really needed here, but take it anyway */
     274                 :        1499 :         toast_rel = table_open(toast_relid, ShareLock);
     275                 :             : 
     276                 :             :         /*
     277                 :             :          * Create unique index on chunk_id, chunk_seq.
     278                 :             :          *
     279                 :             :          * NOTE: the normal TOAST access routines could actually function with a
     280                 :             :          * single-column index on chunk_id only. However, the slice access
     281                 :             :          * routines use both columns for faster access to an individual chunk. In
     282                 :             :          * addition, we want it to be unique as a check against the possibility of
     283                 :             :          * duplicate TOAST chunk OIDs. The index might also be a little more
     284                 :             :          * efficient this way, since btree isn't all that happy with large numbers
     285                 :             :          * of equal keys.
     286                 :             :          */
     287                 :             : 
     288                 :        1499 :         indexInfo = makeNode(IndexInfo);
     289                 :        1499 :         indexInfo->ii_NumIndexAttrs = 2;
     290                 :        1499 :         indexInfo->ii_NumIndexKeyAttrs = 2;
     291                 :        1499 :         indexInfo->ii_IndexAttrNumbers[0] = 1;
     292                 :        1499 :         indexInfo->ii_IndexAttrNumbers[1] = 2;
     293                 :        1499 :         indexInfo->ii_Expressions = NIL;
     294                 :        1499 :         indexInfo->ii_ExpressionsState = NIL;
     295                 :        1499 :         indexInfo->ii_Predicate = NIL;
     296                 :        1499 :         indexInfo->ii_PredicateState = NULL;
     297                 :        1499 :         indexInfo->ii_ExclusionOps = NULL;
     298                 :        1499 :         indexInfo->ii_ExclusionProcs = NULL;
     299                 :        1499 :         indexInfo->ii_ExclusionStrats = NULL;
     300                 :        1499 :         indexInfo->ii_Unique = true;
     301                 :        1499 :         indexInfo->ii_NullsNotDistinct = false;
     302                 :        1499 :         indexInfo->ii_ReadyForInserts = true;
     303                 :        1499 :         indexInfo->ii_CheckedUnchanged = false;
     304                 :        1499 :         indexInfo->ii_IndexUnchanged = false;
     305                 :        1499 :         indexInfo->ii_Concurrent = false;
     306                 :        1499 :         indexInfo->ii_BrokenHotChain = false;
     307                 :        1499 :         indexInfo->ii_ParallelWorkers = 0;
     308                 :        1499 :         indexInfo->ii_Am = BTREE_AM_OID;
     309                 :        1499 :         indexInfo->ii_AmCache = NULL;
     310                 :        1499 :         indexInfo->ii_Context = CurrentMemoryContext;
     311                 :             : 
     312                 :        1499 :         collationIds[0] = InvalidOid;
     313                 :        1499 :         collationIds[1] = InvalidOid;
     314                 :             : 
     315                 :        1499 :         opclassIds[0] = OID_BTREE_OPS_OID;
     316                 :        1499 :         opclassIds[1] = INT4_BTREE_OPS_OID;
     317                 :             : 
     318                 :        1499 :         coloptions[0] = 0;
     319                 :        1499 :         coloptions[1] = 0;
     320                 :             : 
     321                 :        2998 :         index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
     322                 :             :                                  InvalidOid, InvalidOid,
     323                 :        1499 :                                  indexInfo,
     324                 :        1499 :                                  list_make2("chunk_id", "chunk_seq"),
     325                 :             :                                  BTREE_AM_OID,
     326                 :        1499 :                                  rel->rd_rel->reltablespace,
     327                 :        1499 :                                  collationIds, opclassIds, NULL, coloptions, NULL, (Datum) 0,
     328                 :             :                                  INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
     329                 :             : 
     330                 :        1499 :         table_close(toast_rel, NoLock);
     331                 :             : 
     332                 :             :         /*
     333                 :             :          * Store the toast table's OID in the parent relation's pg_class row
     334                 :             :          */
     335                 :        1499 :         class_rel = table_open(RelationRelationId, RowExclusiveLock);
     336                 :             : 
     337         [ +  + ]:        1499 :         if (!IsBootstrapProcessingMode())
     338                 :             :         {
     339                 :             :                 /* normal case, use a transactional update */
     340                 :        1464 :                 reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
     341         [ +  - ]:        1464 :                 if (!HeapTupleIsValid(reltup))
     342   [ #  #  #  # ]:           0 :                         elog(ERROR, "cache lookup failed for relation %u", relOid);
     343                 :             : 
     344                 :        1464 :                 ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
     345                 :             : 
     346                 :        1464 :                 CatalogTupleUpdate(class_rel, &reltup->t_self, reltup);
     347                 :        1464 :         }
     348                 :             :         else
     349                 :             :         {
     350                 :             :                 /* While bootstrapping, we cannot UPDATE, so overwrite in-place */
     351                 :             : 
     352                 :          35 :                 ScanKeyData key[1];
     353                 :          35 :                 void       *state;
     354                 :             : 
     355                 :          70 :                 ScanKeyInit(&key[0],
     356                 :             :                                         Anum_pg_class_oid,
     357                 :             :                                         BTEqualStrategyNumber, F_OIDEQ,
     358                 :          35 :                                         ObjectIdGetDatum(relOid));
     359                 :          70 :                 systable_inplace_update_begin(class_rel, ClassOidIndexId, true,
     360                 :          35 :                                                                           NULL, 1, key, &reltup, &state);
     361         [ +  - ]:          35 :                 if (!HeapTupleIsValid(reltup))
     362   [ #  #  #  # ]:           0 :                         elog(ERROR, "cache lookup failed for relation %u", relOid);
     363                 :             : 
     364                 :          35 :                 ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
     365                 :             : 
     366                 :          35 :                 systable_inplace_update_finish(state, reltup);
     367                 :          35 :         }
     368                 :             : 
     369                 :        1499 :         heap_freetuple(reltup);
     370                 :             : 
     371                 :        1499 :         table_close(class_rel, RowExclusiveLock);
     372                 :             : 
     373                 :             :         /*
     374                 :             :          * Register dependency from the toast table to the main, so that the toast
     375                 :             :          * table will be deleted if the main is.  Skip this in bootstrap mode.
     376                 :             :          */
     377         [ +  + ]:        1499 :         if (!IsBootstrapProcessingMode())
     378                 :             :         {
     379                 :        1464 :                 baseobject.classId = RelationRelationId;
     380                 :        1464 :                 baseobject.objectId = relOid;
     381                 :        1464 :                 baseobject.objectSubId = 0;
     382                 :        1464 :                 toastobject.classId = RelationRelationId;
     383                 :        1464 :                 toastobject.objectId = toast_relid;
     384                 :        1464 :                 toastobject.objectSubId = 0;
     385                 :             : 
     386                 :        1464 :                 recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
     387                 :        1464 :         }
     388                 :             : 
     389                 :             :         /*
     390                 :             :          * Make changes visible
     391                 :             :          */
     392                 :        1499 :         CommandCounterIncrement();
     393                 :             : 
     394                 :        1499 :         return true;
     395                 :        6961 : }
     396                 :             : 
     397                 :             : /*
     398                 :             :  * Check to see whether the table needs a TOAST table.
     399                 :             :  */
     400                 :             : static bool
     401                 :        6289 : needs_toast_table(Relation rel)
     402                 :             : {
     403                 :             :         /*
     404                 :             :          * No need to create a TOAST table for partitioned tables.
     405                 :             :          */
     406         [ +  + ]:        6289 :         if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
     407                 :        1284 :                 return false;
     408                 :             : 
     409                 :             :         /*
     410                 :             :          * We cannot allow toasting a shared relation after initdb (because
     411                 :             :          * there's no way to mark it toasted in other databases' pg_class).
     412                 :             :          */
     413   [ +  +  +  + ]:        5005 :         if (rel->rd_rel->relisshared && !IsBootstrapProcessingMode())
     414                 :           7 :                 return false;
     415                 :             : 
     416                 :             :         /*
     417                 :             :          * Ignore attempts to create toast tables on catalog tables after initdb.
     418                 :             :          * Which catalogs get toast tables is explicitly chosen in catalog/pg_*.h.
     419                 :             :          * (We could get here via some ALTER TABLE command if the catalog doesn't
     420                 :             :          * have a toast table.)
     421                 :             :          */
     422   [ +  +  +  + ]:        4998 :         if (IsCatalogRelation(rel) && !IsBootstrapProcessingMode())
     423                 :          45 :                 return false;
     424                 :             : 
     425                 :             :         /* Otherwise, let the AM decide. */
     426                 :        4953 :         return table_relation_needs_toast_table(rel);
     427                 :        6289 : }
        

Generated by: LCOV version 2.3.2-1