LCOV - code coverage report
Current view: top level - contrib/tsm_system_rows - tsm_system_rows.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 96.7 % 92 89
Test Date: 2026-07-17 08:15:34 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 78.3 % 46 36

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * tsm_system_rows.c
       4                 :             :  *    support routines for SYSTEM_ROWS tablesample method
       5                 :             :  *
       6                 :             :  * The desire here is to produce a random sample with a given number of rows
       7                 :             :  * (or the whole relation, if that is fewer rows).  We use a block-sampling
       8                 :             :  * approach.  To ensure that the whole relation will be visited if necessary,
       9                 :             :  * we start at a randomly chosen block and then advance with a stride that
      10                 :             :  * is randomly chosen but is relatively prime to the relation's nblocks.
      11                 :             :  *
      12                 :             :  * Because of the dependence on nblocks, this method cannot be repeatable
      13                 :             :  * across queries.  (Even if the user hasn't explicitly changed the relation,
      14                 :             :  * maintenance activities such as autovacuum might change nblocks.)  However,
      15                 :             :  * we can at least make it repeatable across scans, by determining the
      16                 :             :  * sampling pattern only once on the first scan.  This means that rescans
      17                 :             :  * won't visit blocks added after the first scan, but that is fine since
      18                 :             :  * such blocks shouldn't contain any visible tuples anyway.
      19                 :             :  *
      20                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      21                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
      22                 :             :  *
      23                 :             :  * IDENTIFICATION
      24                 :             :  *    contrib/tsm_system_rows/tsm_system_rows.c
      25                 :             :  *
      26                 :             :  *-------------------------------------------------------------------------
      27                 :             :  */
      28                 :             : 
      29                 :             : #include "postgres.h"
      30                 :             : 
      31                 :             : #include "access/tsmapi.h"
      32                 :             : #include "catalog/pg_type.h"
      33                 :             : #include "miscadmin.h"
      34                 :             : #include "optimizer/optimizer.h"
      35                 :             : #include "utils/sampling.h"
      36                 :             : 
      37                 :           1 : PG_MODULE_MAGIC_EXT(
      38                 :             :                     .name = "tsm_system_rows",
      39                 :             :                     .version = PG_VERSION
      40                 :             : );
      41                 :             : 
      42                 :           2 : PG_FUNCTION_INFO_V1(tsm_system_rows_handler);
      43                 :             : 
      44                 :             : 
      45                 :             : /* Private state */
      46                 :             : typedef struct
      47                 :             : {
      48                 :             :     uint32      seed;           /* random seed */
      49                 :             :     int64       ntuples;        /* number of tuples to return */
      50                 :             :     OffsetNumber lt;            /* last tuple returned from current block */
      51                 :             :     BlockNumber doneblocks;     /* number of already-scanned blocks */
      52                 :             :     BlockNumber lb;             /* last block visited */
      53                 :             :     /* these three values are not changed during a rescan: */
      54                 :             :     BlockNumber nblocks;        /* number of blocks in relation */
      55                 :             :     BlockNumber firstblock;     /* first block to sample from */
      56                 :             :     BlockNumber step;           /* step size, or 0 if not set yet */
      57                 :             : } SystemRowsSamplerData;
      58                 :             : 
      59                 :             : static void system_rows_samplescangetsamplesize(PlannerInfo *root,
      60                 :             :                                                 RelOptInfo *baserel,
      61                 :             :                                                 List *paramexprs,
      62                 :             :                                                 BlockNumber *pages,
      63                 :             :                                                 double *tuples);
      64                 :             : static void system_rows_initsamplescan(SampleScanState *node,
      65                 :             :                                        int eflags);
      66                 :             : static void system_rows_beginsamplescan(SampleScanState *node,
      67                 :             :                                         Datum *params,
      68                 :             :                                         int nparams,
      69                 :             :                                         uint32 seed);
      70                 :             : static BlockNumber system_rows_nextsampleblock(SampleScanState *node, BlockNumber nblocks);
      71                 :             : static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
      72                 :             :                                                 BlockNumber blockno,
      73                 :             :                                                 OffsetNumber maxoffset);
      74                 :             : static uint32 random_relative_prime(uint32 n, pg_prng_state *randstate);
      75                 :             : 
      76                 :             : 
      77                 :             : /*
      78                 :             :  * Create a TsmRoutine descriptor for the SYSTEM_ROWS method.
      79                 :             :  */
      80                 :             : Datum
      81                 :          40 : tsm_system_rows_handler(PG_FUNCTION_ARGS)
      82                 :             : {
      83                 :          40 :     TsmRoutine *tsm = makeNode(TsmRoutine);
      84                 :             : 
      85                 :          40 :     tsm->parameterTypes = list_make1_oid(INT8OID);
      86                 :             : 
      87                 :             :     /* See notes at head of file */
      88                 :          40 :     tsm->repeatable_across_queries = false;
      89                 :          40 :     tsm->repeatable_across_scans = true;
      90                 :             : 
      91                 :          40 :     tsm->SampleScanGetSampleSize = system_rows_samplescangetsamplesize;
      92                 :          40 :     tsm->InitSampleScan = system_rows_initsamplescan;
      93                 :          40 :     tsm->BeginSampleScan = system_rows_beginsamplescan;
      94                 :          40 :     tsm->NextSampleBlock = system_rows_nextsampleblock;
      95                 :          40 :     tsm->NextSampleTuple = system_rows_nextsampletuple;
      96                 :          40 :     tsm->EndSampleScan = NULL;
      97                 :             : 
      98                 :          40 :     PG_RETURN_POINTER(tsm);
      99                 :             : }
     100                 :             : 
     101                 :             : /*
     102                 :             :  * Sample size estimation.
     103                 :             :  */
     104                 :             : static void
     105                 :           9 : system_rows_samplescangetsamplesize(PlannerInfo *root,
     106                 :             :                                     RelOptInfo *baserel,
     107                 :             :                                     List *paramexprs,
     108                 :             :                                     BlockNumber *pages,
     109                 :             :                                     double *tuples)
     110                 :             : {
     111                 :             :     Node       *limitnode;
     112                 :             :     int64       ntuples;
     113                 :             :     double      npages;
     114                 :             : 
     115                 :             :     /* Try to extract an estimate for the limit rowcount */
     116                 :           9 :     limitnode = (Node *) linitial(paramexprs);
     117                 :           9 :     limitnode = estimate_expression_value(root, limitnode);
     118                 :             : 
     119         [ +  + ]:           9 :     if (IsA(limitnode, Const) &&
     120         [ +  - ]:           7 :         !((Const *) limitnode)->constisnull)
     121                 :             :     {
     122                 :           7 :         ntuples = DatumGetInt64(((Const *) limitnode)->constvalue);
     123         [ +  + ]:           7 :         if (ntuples < 0)
     124                 :             :         {
     125                 :             :             /* Default ntuples if the value is bogus */
     126                 :           2 :             ntuples = 1000;
     127                 :             :         }
     128                 :             :     }
     129                 :             :     else
     130                 :             :     {
     131                 :             :         /* Default ntuples if we didn't obtain a non-null Const */
     132                 :           2 :         ntuples = 1000;
     133                 :             :     }
     134                 :             : 
     135                 :             :     /* Clamp to the estimated relation size */
     136         [ +  + ]:           9 :     if (ntuples > baserel->tuples)
     137                 :           5 :         ntuples = (int64) baserel->tuples;
     138                 :           9 :     ntuples = clamp_row_est(ntuples);
     139                 :             : 
     140   [ +  -  +  - ]:           9 :     if (baserel->tuples > 0 && baserel->pages > 0)
     141                 :           9 :     {
     142                 :             :         /* Estimate number of pages visited based on tuple density */
     143                 :           9 :         double      density = baserel->tuples / (double) baserel->pages;
     144                 :             : 
     145                 :           9 :         npages = ntuples / density;
     146                 :             :     }
     147                 :             :     else
     148                 :             :     {
     149                 :             :         /* For lack of data, assume one tuple per page */
     150                 :           0 :         npages = ntuples;
     151                 :             :     }
     152                 :             : 
     153                 :             :     /* Clamp to sane value */
     154         [ -  + ]:           9 :     npages = clamp_row_est(Min((double) baserel->pages, npages));
     155                 :             : 
     156                 :           9 :     *pages = npages;
     157                 :           9 :     *tuples = ntuples;
     158                 :           9 : }
     159                 :             : 
     160                 :             : /*
     161                 :             :  * Initialize during executor setup.
     162                 :             :  */
     163                 :             : static void
     164                 :           9 : system_rows_initsamplescan(SampleScanState *node, int eflags)
     165                 :             : {
     166                 :           9 :     node->tsm_state = palloc0_object(SystemRowsSamplerData);
     167                 :             :     /* Note the above leaves tsm_state->step equal to zero */
     168                 :           9 : }
     169                 :             : 
     170                 :             : /*
     171                 :             :  * Examine parameters and prepare for a sample scan.
     172                 :             :  */
     173                 :             : static void
     174                 :           9 : system_rows_beginsamplescan(SampleScanState *node,
     175                 :             :                             Datum *params,
     176                 :             :                             int nparams,
     177                 :             :                             uint32 seed)
     178                 :             : {
     179                 :           9 :     SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
     180                 :           9 :     int64       ntuples = DatumGetInt64(params[0]);
     181                 :             : 
     182         [ +  + ]:           9 :     if (ntuples < 0)
     183         [ +  - ]:           1 :         ereport(ERROR,
     184                 :             :                 (errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
     185                 :             :                  errmsg("sample size must not be negative")));
     186                 :             : 
     187                 :           8 :     sampler->seed = seed;
     188                 :           8 :     sampler->ntuples = ntuples;
     189                 :           8 :     sampler->lt = InvalidOffsetNumber;
     190                 :           8 :     sampler->doneblocks = 0;
     191                 :             :     /* lb will be initialized during first NextSampleBlock call */
     192                 :             :     /* we intentionally do not change nblocks/firstblock/step here */
     193                 :             : 
     194                 :             :     /*
     195                 :             :      * We *must* use pagemode visibility checking in this module, so force
     196                 :             :      * that even though it's currently default.
     197                 :             :      */
     198                 :           8 :     node->use_pagemode = true;
     199                 :           8 : }
     200                 :             : 
     201                 :             : /*
     202                 :             :  * Select next block to sample.
     203                 :             :  *
     204                 :             :  * Uses linear probing algorithm for picking next block.
     205                 :             :  */
     206                 :             : static BlockNumber
     207                 :          34 : system_rows_nextsampleblock(SampleScanState *node, BlockNumber nblocks)
     208                 :             : {
     209                 :          34 :     SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
     210                 :             : 
     211                 :             :     /* First call within scan? */
     212         [ +  + ]:          34 :     if (sampler->doneblocks == 0)
     213                 :             :     {
     214                 :             :         /* First scan within query? */
     215         [ +  + ]:           8 :         if (sampler->step == 0)
     216                 :             :         {
     217                 :             :             /* Initialize now that we have scan descriptor */
     218                 :             :             pg_prng_state randstate;
     219                 :             : 
     220                 :             :             /* If relation is empty, there's nothing to scan */
     221         [ -  + ]:           6 :             if (nblocks == 0)
     222                 :           0 :                 return InvalidBlockNumber;
     223                 :             : 
     224                 :             :             /* We only need an RNG during this setup step */
     225                 :           6 :             sampler_random_init_state(sampler->seed, &randstate);
     226                 :             : 
     227                 :             :             /* Compute nblocks/firstblock/step only once per query */
     228                 :           6 :             sampler->nblocks = nblocks;
     229                 :             : 
     230                 :             :             /* Choose random starting block within the relation */
     231                 :             :             /* (Actually this is the predecessor of the first block visited) */
     232                 :           6 :             sampler->firstblock = sampler_random_fract(&randstate) *
     233                 :           6 :                 sampler->nblocks;
     234                 :             : 
     235                 :             :             /* Find relative prime as step size for linear probing */
     236                 :           6 :             sampler->step = random_relative_prime(sampler->nblocks, &randstate);
     237                 :             :         }
     238                 :             : 
     239                 :             :         /* Reinitialize lb */
     240                 :           8 :         sampler->lb = sampler->firstblock;
     241                 :             :     }
     242                 :             : 
     243                 :             :     /* If we've read all blocks or returned all needed tuples, we're done */
     244         [ +  + ]:          34 :     if (++sampler->doneblocks > sampler->nblocks ||
     245         [ +  + ]:          32 :         node->donetuples >= sampler->ntuples)
     246                 :           8 :         return InvalidBlockNumber;
     247                 :             : 
     248                 :             :     /*
     249                 :             :      * It's probably impossible for scan->rs_nblocks to decrease between scans
     250                 :             :      * within a query; but just in case, loop until we select a block number
     251                 :             :      * less than scan->rs_nblocks.  We don't care if scan->rs_nblocks has
     252                 :             :      * increased since the first scan.
     253                 :             :      */
     254                 :             :     do
     255                 :             :     {
     256                 :             :         /* Advance lb, using uint64 arithmetic to forestall overflow */
     257                 :          26 :         sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
     258         [ -  + ]:          26 :     } while (sampler->lb >= nblocks);
     259                 :             : 
     260                 :          26 :     return sampler->lb;
     261                 :             : }
     262                 :             : 
     263                 :             : /*
     264                 :             :  * Select next sampled tuple in current block.
     265                 :             :  *
     266                 :             :  * In block sampling, we just want to sample all the tuples in each selected
     267                 :             :  * block.
     268                 :             :  *
     269                 :             :  * When we reach end of the block, return InvalidOffsetNumber which tells
     270                 :             :  * SampleScan to go to next block.
     271                 :             :  */
     272                 :             : static OffsetNumber
     273                 :         129 : system_rows_nextsampletuple(SampleScanState *node,
     274                 :             :                             BlockNumber blockno,
     275                 :             :                             OffsetNumber maxoffset)
     276                 :             : {
     277                 :         129 :     SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
     278                 :         129 :     OffsetNumber tupoffset = sampler->lt;
     279                 :             : 
     280                 :             :     /* Quit if we've returned all needed tuples */
     281         [ +  + ]:         129 :     if (node->donetuples >= sampler->ntuples)
     282                 :           4 :         return InvalidOffsetNumber;
     283                 :             : 
     284                 :             :     /* Advance to next possible offset on page */
     285         [ +  + ]:         125 :     if (tupoffset == InvalidOffsetNumber)
     286                 :          26 :         tupoffset = FirstOffsetNumber;
     287                 :             :     else
     288                 :          99 :         tupoffset++;
     289                 :             : 
     290                 :             :     /* Done? */
     291         [ +  + ]:         125 :     if (tupoffset > maxoffset)
     292                 :          22 :         tupoffset = InvalidOffsetNumber;
     293                 :             : 
     294                 :         125 :     sampler->lt = tupoffset;
     295                 :             : 
     296                 :         125 :     return tupoffset;
     297                 :             : }
     298                 :             : 
     299                 :             : /*
     300                 :             :  * Compute greatest common divisor of two uint32's.
     301                 :             :  */
     302                 :             : static uint32
     303                 :           6 : gcd(uint32 a, uint32 b)
     304                 :             : {
     305                 :             :     uint32      c;
     306                 :             : 
     307         [ +  + ]:          17 :     while (a != 0)
     308                 :             :     {
     309                 :          11 :         c = a;
     310                 :          11 :         a = b % a;
     311                 :          11 :         b = c;
     312                 :             :     }
     313                 :             : 
     314                 :           6 :     return b;
     315                 :             : }
     316                 :             : 
     317                 :             : /*
     318                 :             :  * Pick a random value less than and relatively prime to n, if possible
     319                 :             :  * (else return 1).
     320                 :             :  */
     321                 :             : static uint32
     322                 :           6 : random_relative_prime(uint32 n, pg_prng_state *randstate)
     323                 :             : {
     324                 :             :     uint32      r;
     325                 :             : 
     326                 :             :     /* Safety check to avoid infinite loop or zero result for small n. */
     327         [ -  + ]:           6 :     if (n <= 1)
     328                 :           0 :         return 1;
     329                 :             : 
     330                 :             :     /*
     331                 :             :      * This should only take 2 or 3 iterations as the probability of 2 numbers
     332                 :             :      * being relatively prime is ~61%; but just in case, we'll include a
     333                 :             :      * CHECK_FOR_INTERRUPTS in the loop.
     334                 :             :      */
     335                 :             :     do
     336                 :             :     {
     337         [ -  + ]:           8 :         CHECK_FOR_INTERRUPTS();
     338                 :           8 :         r = (uint32) (sampler_random_fract(randstate) * n);
     339   [ +  +  -  + ]:           8 :     } while (r == 0 || gcd(r, n) > 1);
     340                 :             : 
     341                 :           6 :     return r;
     342                 :             : }
        

Generated by: LCOV version 2.0-1