LCOV - code coverage report
Current view: top level - src/test/modules/spgist_name_ops - spgist_name_ops.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 196 231 84.8 %
Date: 2026-02-09 11:17:40 Functions: 14 14 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*--------------------------------------------------------------------------
       2             :  *
       3             :  * spgist_name_ops.c
       4             :  *      Test opclass for SP-GiST
       5             :  *
       6             :  * This indexes input values of type "name", but the index storage is "text",
       7             :  * with the same choices as made in the core SP-GiST text_ops opclass.
       8             :  * Much of the code is identical to src/backend/access/spgist/spgtextproc.c,
       9             :  * which see for a more detailed header comment.
      10             :  *
      11             :  * Unlike spgtextproc.c, we don't bother with collation-aware logic.
      12             :  *
      13             :  *
      14             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      15             :  * Portions Copyright (c) 1994, Regents of the University of California
      16             :  *
      17             :  * IDENTIFICATION
      18             :  *      src/test/modules/spgist_name_ops/spgist_name_ops.c
      19             :  *
      20             :  * -------------------------------------------------------------------------
      21             :  */
      22             : #include "postgres.h"
      23             : 
      24             : #include "access/spgist.h"
      25             : #include "catalog/pg_type.h"
      26             : #include "utils/datum.h"
      27             : #include "varatt.h"
      28             : 
      29           2 : PG_MODULE_MAGIC;
      30             : 
      31             : 
      32           4 : PG_FUNCTION_INFO_V1(spgist_name_config);
      33             : Datum
      34          14 : spgist_name_config(PG_FUNCTION_ARGS)
      35             : {
      36             : #ifdef NOT_USED
      37             :     spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0);
      38             : #endif
      39          14 :     spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
      40             : 
      41          14 :     cfg->prefixType = TEXTOID;
      42          14 :     cfg->labelType = INT2OID;
      43          14 :     cfg->leafType = TEXTOID;
      44          14 :     cfg->canReturnData = true;
      45          14 :     cfg->longValuesOK = true;    /* suffixing will shorten long values */
      46          14 :     PG_RETURN_VOID();
      47             : }
      48             : 
      49             : /*
      50             :  * Form a text datum from the given not-necessarily-null-terminated string,
      51             :  * using short varlena header format if possible
      52             :  */
      53             : static Datum
      54       36704 : formTextDatum(const char *data, int datalen)
      55             : {
      56             :     char       *p;
      57             : 
      58       36704 :     p = (char *) palloc(datalen + VARHDRSZ);
      59             : 
      60       36704 :     if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
      61             :     {
      62       36704 :         SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
      63       36704 :         if (datalen)
      64       36618 :             memcpy(p + VARHDRSZ_SHORT, data, datalen);
      65             :     }
      66             :     else
      67             :     {
      68           0 :         SET_VARSIZE(p, datalen + VARHDRSZ);
      69           0 :         memcpy(p + VARHDRSZ, data, datalen);
      70             :     }
      71             : 
      72       36704 :     return PointerGetDatum(p);
      73             : }
      74             : 
      75             : /*
      76             :  * Find the length of the common prefix of a and b
      77             :  */
      78             : static int
      79        1984 : commonPrefix(const char *a, const char *b, int lena, int lenb)
      80             : {
      81        1984 :     int         i = 0;
      82             : 
      83        4740 :     while (i < lena && i < lenb && *a == *b)
      84             :     {
      85        2756 :         a++;
      86        2756 :         b++;
      87        2756 :         i++;
      88             :     }
      89             : 
      90        1984 :     return i;
      91             : }
      92             : 
      93             : /*
      94             :  * Binary search an array of int16 datums for a match to c
      95             :  *
      96             :  * On success, *i gets the match location; on failure, it gets where to insert
      97             :  */
      98             : static bool
      99       23098 : searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
     100             : {
     101       23098 :     int         StopLow = 0,
     102       23098 :                 StopHigh = nNodes;
     103             : 
     104       74148 :     while (StopLow < StopHigh)
     105             :     {
     106       73910 :         int         StopMiddle = (StopLow + StopHigh) >> 1;
     107       73910 :         int16       middle = DatumGetInt16(nodeLabels[StopMiddle]);
     108             : 
     109       73910 :         if (c < middle)
     110       29824 :             StopHigh = StopMiddle;
     111       44086 :         else if (c > middle)
     112       21226 :             StopLow = StopMiddle + 1;
     113             :         else
     114             :         {
     115       22860 :             *i = StopMiddle;
     116       22860 :             return true;
     117             :         }
     118             :     }
     119             : 
     120         238 :     *i = StopHigh;
     121         238 :     return false;
     122             : }
     123             : 
     124           4 : PG_FUNCTION_INFO_V1(spgist_name_choose);
     125             : Datum
     126       23110 : spgist_name_choose(PG_FUNCTION_ARGS)
     127             : {
     128       23110 :     spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
     129       23110 :     spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
     130       23110 :     Name        inName = DatumGetName(in->datum);
     131       23110 :     char       *inStr = NameStr(*inName);
     132       23110 :     int         inSize = strlen(inStr);
     133       23110 :     char       *prefixStr = NULL;
     134       23110 :     int         prefixSize = 0;
     135       23110 :     int         commonLen = 0;
     136       23110 :     int16       nodeChar = 0;
     137       23110 :     int         i = 0;
     138             : 
     139             :     /* Check for prefix match, set nodeChar to first byte after prefix */
     140       23110 :     if (in->hasPrefix)
     141             :     {
     142        1984 :         text       *prefixText = DatumGetTextPP(in->prefixDatum);
     143             : 
     144        1984 :         prefixStr = VARDATA_ANY(prefixText);
     145        1984 :         prefixSize = VARSIZE_ANY_EXHDR(prefixText);
     146             : 
     147        1984 :         commonLen = commonPrefix(inStr + in->level,
     148             :                                  prefixStr,
     149        1984 :                                  inSize - in->level,
     150             :                                  prefixSize);
     151             : 
     152        1984 :         if (commonLen == prefixSize)
     153             :         {
     154        1972 :             if (inSize - in->level > commonLen)
     155        1968 :                 nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
     156             :             else
     157           4 :                 nodeChar = -1;
     158             :         }
     159             :         else
     160             :         {
     161             :             /* Must split tuple because incoming value doesn't match prefix */
     162          12 :             out->resultType = spgSplitTuple;
     163             : 
     164          12 :             if (commonLen == 0)
     165             :             {
     166           4 :                 out->result.splitTuple.prefixHasPrefix = false;
     167             :             }
     168             :             else
     169             :             {
     170           8 :                 out->result.splitTuple.prefixHasPrefix = true;
     171           8 :                 out->result.splitTuple.prefixPrefixDatum =
     172           8 :                     formTextDatum(prefixStr, commonLen);
     173             :             }
     174          12 :             out->result.splitTuple.prefixNNodes = 1;
     175          12 :             out->result.splitTuple.prefixNodeLabels =
     176          12 :                 palloc_object(Datum);
     177          24 :             out->result.splitTuple.prefixNodeLabels[0] =
     178          12 :                 Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
     179             : 
     180          12 :             out->result.splitTuple.childNodeN = 0;
     181             : 
     182          12 :             if (prefixSize - commonLen == 1)
     183             :             {
     184           8 :                 out->result.splitTuple.postfixHasPrefix = false;
     185             :             }
     186             :             else
     187             :             {
     188           4 :                 out->result.splitTuple.postfixHasPrefix = true;
     189           4 :                 out->result.splitTuple.postfixPrefixDatum =
     190           4 :                     formTextDatum(prefixStr + commonLen + 1,
     191           4 :                                   prefixSize - commonLen - 1);
     192             :             }
     193             : 
     194          12 :             PG_RETURN_VOID();
     195             :         }
     196             :     }
     197       21126 :     else if (inSize > in->level)
     198             :     {
     199       21086 :         nodeChar = *(unsigned char *) (inStr + in->level);
     200             :     }
     201             :     else
     202             :     {
     203          40 :         nodeChar = -1;
     204             :     }
     205             : 
     206             :     /* Look up nodeChar in the node label array */
     207       23098 :     if (searchChar(in->nodeLabels, in->nNodes, nodeChar, &i))
     208             :     {
     209             :         /*
     210             :          * Descend to existing node.  (If in->allTheSame, the core code will
     211             :          * ignore our nodeN specification here, but that's OK.  We still have
     212             :          * to provide the correct levelAdd and restDatum values, and those are
     213             :          * the same regardless of which node gets chosen by core.)
     214             :          */
     215             :         int         levelAdd;
     216             : 
     217       22860 :         out->resultType = spgMatchNode;
     218       22860 :         out->result.matchNode.nodeN = i;
     219       22860 :         levelAdd = commonLen;
     220       22860 :         if (nodeChar >= 0)
     221       22816 :             levelAdd++;
     222       22860 :         out->result.matchNode.levelAdd = levelAdd;
     223       22860 :         if (inSize - in->level - levelAdd > 0)
     224       22774 :             out->result.matchNode.restDatum =
     225       22774 :                 formTextDatum(inStr + in->level + levelAdd,
     226       22774 :                               inSize - in->level - levelAdd);
     227             :         else
     228          86 :             out->result.matchNode.restDatum =
     229          86 :                 formTextDatum(NULL, 0);
     230             :     }
     231         238 :     else if (in->allTheSame)
     232             :     {
     233             :         /*
     234             :          * Can't use AddNode action, so split the tuple.  The upper tuple has
     235             :          * the same prefix as before and uses a dummy node label -2 for the
     236             :          * lower tuple.  The lower tuple has no prefix and the same node
     237             :          * labels as the original tuple.
     238             :          *
     239             :          * Note: it might seem tempting to shorten the upper tuple's prefix,
     240             :          * if it has one, then use its last byte as label for the lower tuple.
     241             :          * But that doesn't win since we know the incoming value matches the
     242             :          * whole prefix: we'd just end up splitting the lower tuple again.
     243             :          */
     244           0 :         out->resultType = spgSplitTuple;
     245           0 :         out->result.splitTuple.prefixHasPrefix = in->hasPrefix;
     246           0 :         out->result.splitTuple.prefixPrefixDatum = in->prefixDatum;
     247           0 :         out->result.splitTuple.prefixNNodes = 1;
     248           0 :         out->result.splitTuple.prefixNodeLabels = palloc_object(Datum);
     249           0 :         out->result.splitTuple.prefixNodeLabels[0] = Int16GetDatum(-2);
     250           0 :         out->result.splitTuple.childNodeN = 0;
     251           0 :         out->result.splitTuple.postfixHasPrefix = false;
     252             :     }
     253             :     else
     254             :     {
     255             :         /* Add a node for the not-previously-seen nodeChar value */
     256         238 :         out->resultType = spgAddNode;
     257         238 :         out->result.addNode.nodeLabel = Int16GetDatum(nodeChar);
     258         238 :         out->result.addNode.nodeN = i;
     259             :     }
     260             : 
     261       23098 :     PG_RETURN_VOID();
     262             : }
     263             : 
     264             : /* The picksplit function is identical to the core opclass, so just use that */
     265             : 
     266           4 : PG_FUNCTION_INFO_V1(spgist_name_inner_consistent);
     267             : Datum
     268           8 : spgist_name_inner_consistent(PG_FUNCTION_ARGS)
     269             : {
     270           8 :     spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
     271           8 :     spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
     272             :     text       *reconstructedValue;
     273             :     text       *reconstrText;
     274             :     int         maxReconstrLen;
     275           8 :     text       *prefixText = NULL;
     276           8 :     int         prefixSize = 0;
     277             :     int         i;
     278             : 
     279             :     /*
     280             :      * Reconstruct values represented at this tuple, including parent data,
     281             :      * prefix of this tuple if any, and the node label if it's non-dummy.
     282             :      * in->level should be the length of the previously reconstructed value,
     283             :      * and the number of bytes added here is prefixSize or prefixSize + 1.
     284             :      *
     285             :      * Recall that reconstructedValues are assumed to be the same type as leaf
     286             :      * datums, so we must use "text" not "name" for them.
     287             :      *
     288             :      * Note: we assume that in->reconstructedValue isn't toasted and doesn't
     289             :      * have a short varlena header.  This is okay because it must have been
     290             :      * created by a previous invocation of this routine, and we always emit
     291             :      * long-format reconstructed values.
     292             :      */
     293           8 :     reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
     294             :     Assert(reconstructedValue == NULL ? in->level == 0 :
     295             :            VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
     296             : 
     297           8 :     maxReconstrLen = in->level + 1;
     298           8 :     if (in->hasPrefix)
     299             :     {
     300           0 :         prefixText = DatumGetTextPP(in->prefixDatum);
     301           0 :         prefixSize = VARSIZE_ANY_EXHDR(prefixText);
     302           0 :         maxReconstrLen += prefixSize;
     303             :     }
     304             : 
     305           8 :     reconstrText = palloc(VARHDRSZ + maxReconstrLen);
     306           8 :     SET_VARSIZE(reconstrText, VARHDRSZ + maxReconstrLen);
     307             : 
     308           8 :     if (in->level)
     309           4 :         memcpy(VARDATA(reconstrText),
     310           4 :                VARDATA(reconstructedValue),
     311           4 :                in->level);
     312           8 :     if (prefixSize)
     313           0 :         memcpy(((char *) VARDATA(reconstrText)) + in->level,
     314           0 :                VARDATA_ANY(prefixText),
     315             :                prefixSize);
     316             :     /* last byte of reconstrText will be filled in below */
     317             : 
     318             :     /*
     319             :      * Scan the child nodes.  For each one, complete the reconstructed value
     320             :      * and see if it's consistent with the query.  If so, emit an entry into
     321             :      * the output arrays.
     322             :      */
     323           8 :     out->nodeNumbers = palloc_array(int, in->nNodes);
     324           8 :     out->levelAdds = palloc_array(int, in->nNodes);
     325           8 :     out->reconstructedValues = palloc_array(Datum, in->nNodes);
     326           8 :     out->nNodes = 0;
     327             : 
     328         140 :     for (i = 0; i < in->nNodes; i++)
     329             :     {
     330         132 :         int16       nodeChar = DatumGetInt16(in->nodeLabels[i]);
     331             :         int         thisLen;
     332         132 :         bool        res = true;
     333             :         int         j;
     334             : 
     335             :         /* If nodeChar is a dummy value, don't include it in data */
     336         132 :         if (nodeChar <= 0)
     337           0 :             thisLen = maxReconstrLen - 1;
     338             :         else
     339             :         {
     340         132 :             ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
     341         132 :             thisLen = maxReconstrLen;
     342             :         }
     343             : 
     344         256 :         for (j = 0; j < in->nkeys; j++)
     345             :         {
     346         248 :             StrategyNumber strategy = in->scankeys[j].sk_strategy;
     347             :             Name        inName;
     348             :             char       *inStr;
     349             :             int         inSize;
     350             :             int         r;
     351             : 
     352         248 :             inName = DatumGetName(in->scankeys[j].sk_argument);
     353         248 :             inStr = NameStr(*inName);
     354         248 :             inSize = strlen(inStr);
     355             : 
     356         496 :             r = memcmp(VARDATA(reconstrText), inStr,
     357         248 :                        Min(inSize, thisLen));
     358             : 
     359         248 :             switch (strategy)
     360             :             {
     361         116 :                 case BTLessStrategyNumber:
     362             :                 case BTLessEqualStrategyNumber:
     363         116 :                     if (r > 0)
     364         108 :                         res = false;
     365         116 :                     break;
     366           0 :                 case BTEqualStrategyNumber:
     367           0 :                     if (r != 0 || inSize < thisLen)
     368           0 :                         res = false;
     369           0 :                     break;
     370         132 :                 case BTGreaterEqualStrategyNumber:
     371             :                 case BTGreaterStrategyNumber:
     372         132 :                     if (r < 0)
     373          16 :                         res = false;
     374         132 :                     break;
     375           0 :                 default:
     376           0 :                     elog(ERROR, "unrecognized strategy number: %d",
     377             :                          in->scankeys[j].sk_strategy);
     378             :                     break;
     379             :             }
     380             : 
     381         248 :             if (!res)
     382         124 :                 break;          /* no need to consider remaining conditions */
     383             :         }
     384             : 
     385         132 :         if (res)
     386             :         {
     387           8 :             out->nodeNumbers[out->nNodes] = i;
     388           8 :             out->levelAdds[out->nNodes] = thisLen - in->level;
     389           8 :             SET_VARSIZE(reconstrText, VARHDRSZ + thisLen);
     390          16 :             out->reconstructedValues[out->nNodes] =
     391           8 :                 datumCopy(PointerGetDatum(reconstrText), false, -1);
     392           8 :             out->nNodes++;
     393             :         }
     394             :     }
     395             : 
     396           8 :     PG_RETURN_VOID();
     397             : }
     398             : 
     399           4 : PG_FUNCTION_INFO_V1(spgist_name_leaf_consistent);
     400             : Datum
     401         252 : spgist_name_leaf_consistent(PG_FUNCTION_ARGS)
     402             : {
     403         252 :     spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
     404         252 :     spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
     405         252 :     int         level = in->level;
     406             :     text       *leafValue,
     407         252 :                *reconstrValue = NULL;
     408             :     char       *fullValue;
     409             :     int         fullLen;
     410             :     bool        res;
     411             :     int         j;
     412             : 
     413             :     /* all tests are exact */
     414         252 :     out->recheck = false;
     415             : 
     416         252 :     leafValue = DatumGetTextPP(in->leafDatum);
     417             : 
     418             :     /* As above, in->reconstructedValue isn't toasted or short. */
     419         252 :     if (DatumGetPointer(in->reconstructedValue))
     420         252 :         reconstrValue = (text *) DatumGetPointer(in->reconstructedValue);
     421             : 
     422             :     Assert(reconstrValue == NULL ? level == 0 :
     423             :            VARSIZE_ANY_EXHDR(reconstrValue) == level);
     424             : 
     425             :     /* Reconstruct the Name represented by this leaf tuple */
     426         252 :     fullValue = palloc0(NAMEDATALEN);
     427         252 :     fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
     428             :     Assert(fullLen < NAMEDATALEN);
     429         252 :     if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
     430             :     {
     431           0 :         memcpy(fullValue, VARDATA(reconstrValue),
     432             :                VARSIZE_ANY_EXHDR(reconstrValue));
     433             :     }
     434             :     else
     435             :     {
     436         252 :         if (level)
     437         252 :             memcpy(fullValue, VARDATA(reconstrValue), level);
     438         252 :         if (VARSIZE_ANY_EXHDR(leafValue) > 0)
     439         252 :             memcpy(fullValue + level, VARDATA_ANY(leafValue),
     440             :                    VARSIZE_ANY_EXHDR(leafValue));
     441             :     }
     442         252 :     out->leafValue = PointerGetDatum(fullValue);
     443             : 
     444             :     /* Perform the required comparison(s) */
     445         252 :     res = true;
     446         520 :     for (j = 0; j < in->nkeys; j++)
     447             :     {
     448         468 :         StrategyNumber strategy = in->scankeys[j].sk_strategy;
     449         468 :         Name        queryName = DatumGetName(in->scankeys[j].sk_argument);
     450         468 :         char       *queryStr = NameStr(*queryName);
     451         468 :         int         queryLen = strlen(queryStr);
     452             :         int         r;
     453             : 
     454             :         /* Non-collation-aware comparison */
     455         468 :         r = memcmp(fullValue, queryStr, Min(queryLen, fullLen));
     456             : 
     457         468 :         if (r == 0)
     458             :         {
     459          52 :             if (queryLen > fullLen)
     460           0 :                 r = -1;
     461          52 :             else if (queryLen < fullLen)
     462          52 :                 r = 1;
     463             :         }
     464             : 
     465         468 :         switch (strategy)
     466             :         {
     467         216 :             case BTLessStrategyNumber:
     468         216 :                 res = (r < 0);
     469         216 :                 break;
     470           0 :             case BTLessEqualStrategyNumber:
     471           0 :                 res = (r <= 0);
     472           0 :                 break;
     473           0 :             case BTEqualStrategyNumber:
     474           0 :                 res = (r == 0);
     475           0 :                 break;
     476           0 :             case BTGreaterEqualStrategyNumber:
     477           0 :                 res = (r >= 0);
     478           0 :                 break;
     479         252 :             case BTGreaterStrategyNumber:
     480         252 :                 res = (r > 0);
     481         252 :                 break;
     482           0 :             default:
     483           0 :                 elog(ERROR, "unrecognized strategy number: %d",
     484             :                      in->scankeys[j].sk_strategy);
     485             :                 res = false;
     486             :                 break;
     487             :         }
     488             : 
     489         468 :         if (!res)
     490         200 :             break;              /* no need to consider remaining conditions */
     491             :     }
     492             : 
     493         252 :     PG_RETURN_BOOL(res);
     494             : }
     495             : 
     496           4 : PG_FUNCTION_INFO_V1(spgist_name_compress);
     497             : Datum
     498       13832 : spgist_name_compress(PG_FUNCTION_ARGS)
     499             : {
     500       13832 :     Name        inName = PG_GETARG_NAME(0);
     501       13832 :     char       *inStr = NameStr(*inName);
     502             : 
     503       13832 :     PG_RETURN_DATUM(formTextDatum(inStr, strlen(inStr)));
     504             : }

Generated by: LCOV version 1.16