LCOV - code coverage report
Current view: top level - src/test/modules/spgist_name_ops - spgist_name_ops.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 84.8 % 231 196
Test Date: 2026-03-04 07:14:52 Functions: 100.0 % 14 14
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            1 : PG_MODULE_MAGIC;
      30              : 
      31              : 
      32            2 : PG_FUNCTION_INFO_V1(spgist_name_config);
      33              : Datum
      34            7 : spgist_name_config(PG_FUNCTION_ARGS)
      35              : {
      36              : #ifdef NOT_USED
      37              :     spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0);
      38              : #endif
      39            7 :     spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
      40              : 
      41            7 :     cfg->prefixType = TEXTOID;
      42            7 :     cfg->labelType = INT2OID;
      43            7 :     cfg->leafType = TEXTOID;
      44            7 :     cfg->canReturnData = true;
      45            7 :     cfg->longValuesOK = true;    /* suffixing will shorten long values */
      46            7 :     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        18373 : formTextDatum(const char *data, int datalen)
      55              : {
      56              :     char       *p;
      57              : 
      58        18373 :     p = (char *) palloc(datalen + VARHDRSZ);
      59              : 
      60        18373 :     if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
      61              :     {
      62        18373 :         SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
      63        18373 :         if (datalen)
      64        18330 :             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        18373 :     return PointerGetDatum(p);
      73              : }
      74              : 
      75              : /*
      76              :  * Find the length of the common prefix of a and b
      77              :  */
      78              : static int
      79          992 : commonPrefix(const char *a, const char *b, int lena, int lenb)
      80              : {
      81          992 :     int         i = 0;
      82              : 
      83         2370 :     while (i < lena && i < lenb && *a == *b)
      84              :     {
      85         1378 :         a++;
      86         1378 :         b++;
      87         1378 :         i++;
      88              :     }
      89              : 
      90          992 :     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        11563 : searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
     100              : {
     101        11563 :     int         StopLow = 0,
     102        11563 :                 StopHigh = nNodes;
     103              : 
     104        37016 :     while (StopLow < StopHigh)
     105              :     {
     106        36904 :         int         StopMiddle = (StopLow + StopHigh) >> 1;
     107        36904 :         int16       middle = DatumGetInt16(nodeLabels[StopMiddle]);
     108              : 
     109        36904 :         if (c < middle)
     110        14879 :             StopHigh = StopMiddle;
     111        22025 :         else if (c > middle)
     112        10574 :             StopLow = StopMiddle + 1;
     113              :         else
     114              :         {
     115        11451 :             *i = StopMiddle;
     116        11451 :             return true;
     117              :         }
     118              :     }
     119              : 
     120          112 :     *i = StopHigh;
     121          112 :     return false;
     122              : }
     123              : 
     124            2 : PG_FUNCTION_INFO_V1(spgist_name_choose);
     125              : Datum
     126        11569 : spgist_name_choose(PG_FUNCTION_ARGS)
     127              : {
     128        11569 :     spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
     129        11569 :     spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
     130        11569 :     Name        inName = DatumGetName(in->datum);
     131        11569 :     char       *inStr = NameStr(*inName);
     132        11569 :     int         inSize = strlen(inStr);
     133        11569 :     char       *prefixStr = NULL;
     134        11569 :     int         prefixSize = 0;
     135        11569 :     int         commonLen = 0;
     136        11569 :     int16       nodeChar = 0;
     137        11569 :     int         i = 0;
     138              : 
     139              :     /* Check for prefix match, set nodeChar to first byte after prefix */
     140        11569 :     if (in->hasPrefix)
     141              :     {
     142          992 :         text       *prefixText = DatumGetTextPP(in->prefixDatum);
     143              : 
     144          992 :         prefixStr = VARDATA_ANY(prefixText);
     145          992 :         prefixSize = VARSIZE_ANY_EXHDR(prefixText);
     146              : 
     147          992 :         commonLen = commonPrefix(inStr + in->level,
     148              :                                  prefixStr,
     149          992 :                                  inSize - in->level,
     150              :                                  prefixSize);
     151              : 
     152          992 :         if (commonLen == prefixSize)
     153              :         {
     154          986 :             if (inSize - in->level > commonLen)
     155          984 :                 nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
     156              :             else
     157            2 :                 nodeChar = -1;
     158              :         }
     159              :         else
     160              :         {
     161              :             /* Must split tuple because incoming value doesn't match prefix */
     162            6 :             out->resultType = spgSplitTuple;
     163              : 
     164            6 :             if (commonLen == 0)
     165              :             {
     166            2 :                 out->result.splitTuple.prefixHasPrefix = false;
     167              :             }
     168              :             else
     169              :             {
     170            4 :                 out->result.splitTuple.prefixHasPrefix = true;
     171            4 :                 out->result.splitTuple.prefixPrefixDatum =
     172            4 :                     formTextDatum(prefixStr, commonLen);
     173              :             }
     174            6 :             out->result.splitTuple.prefixNNodes = 1;
     175            6 :             out->result.splitTuple.prefixNodeLabels =
     176            6 :                 palloc_object(Datum);
     177           12 :             out->result.splitTuple.prefixNodeLabels[0] =
     178            6 :                 Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
     179              : 
     180            6 :             out->result.splitTuple.childNodeN = 0;
     181              : 
     182            6 :             if (prefixSize - commonLen == 1)
     183              :             {
     184            4 :                 out->result.splitTuple.postfixHasPrefix = false;
     185              :             }
     186              :             else
     187              :             {
     188            2 :                 out->result.splitTuple.postfixHasPrefix = true;
     189            2 :                 out->result.splitTuple.postfixPrefixDatum =
     190            2 :                     formTextDatum(prefixStr + commonLen + 1,
     191            2 :                                   prefixSize - commonLen - 1);
     192              :             }
     193              : 
     194            6 :             PG_RETURN_VOID();
     195              :         }
     196              :     }
     197        10577 :     else if (inSize > in->level)
     198              :     {
     199        10557 :         nodeChar = *(unsigned char *) (inStr + in->level);
     200              :     }
     201              :     else
     202              :     {
     203           20 :         nodeChar = -1;
     204              :     }
     205              : 
     206              :     /* Look up nodeChar in the node label array */
     207        11563 :     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        11451 :         out->resultType = spgMatchNode;
     218        11451 :         out->result.matchNode.nodeN = i;
     219        11451 :         levelAdd = commonLen;
     220        11451 :         if (nodeChar >= 0)
     221        11429 :             levelAdd++;
     222        11451 :         out->result.matchNode.levelAdd = levelAdd;
     223        11451 :         if (inSize - in->level - levelAdd > 0)
     224        11408 :             out->result.matchNode.restDatum =
     225        11408 :                 formTextDatum(inStr + in->level + levelAdd,
     226        11408 :                               inSize - in->level - levelAdd);
     227              :         else
     228           43 :             out->result.matchNode.restDatum =
     229           43 :                 formTextDatum(NULL, 0);
     230              :     }
     231          112 :     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          112 :         out->resultType = spgAddNode;
     257          112 :         out->result.addNode.nodeLabel = Int16GetDatum(nodeChar);
     258          112 :         out->result.addNode.nodeN = i;
     259              :     }
     260              : 
     261        11563 :     PG_RETURN_VOID();
     262              : }
     263              : 
     264              : /* The picksplit function is identical to the core opclass, so just use that */
     265              : 
     266            2 : PG_FUNCTION_INFO_V1(spgist_name_inner_consistent);
     267              : Datum
     268            4 : spgist_name_inner_consistent(PG_FUNCTION_ARGS)
     269              : {
     270            4 :     spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
     271            4 :     spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
     272              :     text       *reconstructedValue;
     273              :     text       *reconstrText;
     274              :     int         maxReconstrLen;
     275            4 :     text       *prefixText = NULL;
     276            4 :     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            4 :     reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
     294              :     Assert(reconstructedValue == NULL ? in->level == 0 :
     295              :            VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
     296              : 
     297            4 :     maxReconstrLen = in->level + 1;
     298            4 :     if (in->hasPrefix)
     299              :     {
     300            0 :         prefixText = DatumGetTextPP(in->prefixDatum);
     301            0 :         prefixSize = VARSIZE_ANY_EXHDR(prefixText);
     302            0 :         maxReconstrLen += prefixSize;
     303              :     }
     304              : 
     305            4 :     reconstrText = palloc(VARHDRSZ + maxReconstrLen);
     306            4 :     SET_VARSIZE(reconstrText, VARHDRSZ + maxReconstrLen);
     307              : 
     308            4 :     if (in->level)
     309            2 :         memcpy(VARDATA(reconstrText),
     310            2 :                VARDATA(reconstructedValue),
     311            2 :                in->level);
     312            4 :     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            4 :     out->nodeNumbers = palloc_array(int, in->nNodes);
     324            4 :     out->levelAdds = palloc_array(int, in->nNodes);
     325            4 :     out->reconstructedValues = palloc_array(Datum, in->nNodes);
     326            4 :     out->nNodes = 0;
     327              : 
     328           70 :     for (i = 0; i < in->nNodes; i++)
     329              :     {
     330           66 :         int16       nodeChar = DatumGetInt16(in->nodeLabels[i]);
     331              :         int         thisLen;
     332           66 :         bool        res = true;
     333              :         int         j;
     334              : 
     335              :         /* If nodeChar is a dummy value, don't include it in data */
     336           66 :         if (nodeChar <= 0)
     337            0 :             thisLen = maxReconstrLen - 1;
     338              :         else
     339              :         {
     340           66 :             ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
     341           66 :             thisLen = maxReconstrLen;
     342              :         }
     343              : 
     344          128 :         for (j = 0; j < in->nkeys; j++)
     345              :         {
     346          124 :             StrategyNumber strategy = in->scankeys[j].sk_strategy;
     347              :             Name        inName;
     348              :             char       *inStr;
     349              :             int         inSize;
     350              :             int         r;
     351              : 
     352          124 :             inName = DatumGetName(in->scankeys[j].sk_argument);
     353          124 :             inStr = NameStr(*inName);
     354          124 :             inSize = strlen(inStr);
     355              : 
     356          248 :             r = memcmp(VARDATA(reconstrText), inStr,
     357          124 :                        Min(inSize, thisLen));
     358              : 
     359          124 :             switch (strategy)
     360              :             {
     361           58 :                 case BTLessStrategyNumber:
     362              :                 case BTLessEqualStrategyNumber:
     363           58 :                     if (r > 0)
     364           54 :                         res = false;
     365           58 :                     break;
     366            0 :                 case BTEqualStrategyNumber:
     367            0 :                     if (r != 0 || inSize < thisLen)
     368            0 :                         res = false;
     369            0 :                     break;
     370           66 :                 case BTGreaterEqualStrategyNumber:
     371              :                 case BTGreaterStrategyNumber:
     372           66 :                     if (r < 0)
     373            8 :                         res = false;
     374           66 :                     break;
     375            0 :                 default:
     376            0 :                     elog(ERROR, "unrecognized strategy number: %d",
     377              :                          in->scankeys[j].sk_strategy);
     378              :                     break;
     379              :             }
     380              : 
     381          124 :             if (!res)
     382           62 :                 break;          /* no need to consider remaining conditions */
     383              :         }
     384              : 
     385           66 :         if (res)
     386              :         {
     387            4 :             out->nodeNumbers[out->nNodes] = i;
     388            4 :             out->levelAdds[out->nNodes] = thisLen - in->level;
     389            4 :             SET_VARSIZE(reconstrText, VARHDRSZ + thisLen);
     390            8 :             out->reconstructedValues[out->nNodes] =
     391            4 :                 datumCopy(PointerGetDatum(reconstrText), false, -1);
     392            4 :             out->nNodes++;
     393              :         }
     394              :     }
     395              : 
     396            4 :     PG_RETURN_VOID();
     397              : }
     398              : 
     399            2 : PG_FUNCTION_INFO_V1(spgist_name_leaf_consistent);
     400              : Datum
     401          126 : spgist_name_leaf_consistent(PG_FUNCTION_ARGS)
     402              : {
     403          126 :     spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
     404          126 :     spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
     405          126 :     int         level = in->level;
     406              :     text       *leafValue,
     407          126 :                *reconstrValue = NULL;
     408              :     char       *fullValue;
     409              :     int         fullLen;
     410              :     bool        res;
     411              :     int         j;
     412              : 
     413              :     /* all tests are exact */
     414          126 :     out->recheck = false;
     415              : 
     416          126 :     leafValue = DatumGetTextPP(in->leafDatum);
     417              : 
     418              :     /* As above, in->reconstructedValue isn't toasted or short. */
     419          126 :     if (DatumGetPointer(in->reconstructedValue))
     420          126 :         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          126 :     fullValue = palloc0(NAMEDATALEN);
     427          126 :     fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
     428              :     Assert(fullLen < NAMEDATALEN);
     429          126 :     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          126 :         if (level)
     437          126 :             memcpy(fullValue, VARDATA(reconstrValue), level);
     438          126 :         if (VARSIZE_ANY_EXHDR(leafValue) > 0)
     439          126 :             memcpy(fullValue + level, VARDATA_ANY(leafValue),
     440              :                    VARSIZE_ANY_EXHDR(leafValue));
     441              :     }
     442          126 :     out->leafValue = PointerGetDatum(fullValue);
     443              : 
     444              :     /* Perform the required comparison(s) */
     445          126 :     res = true;
     446          260 :     for (j = 0; j < in->nkeys; j++)
     447              :     {
     448          234 :         StrategyNumber strategy = in->scankeys[j].sk_strategy;
     449          234 :         Name        queryName = DatumGetName(in->scankeys[j].sk_argument);
     450          234 :         char       *queryStr = NameStr(*queryName);
     451          234 :         int         queryLen = strlen(queryStr);
     452              :         int         r;
     453              : 
     454              :         /* Non-collation-aware comparison */
     455          234 :         r = memcmp(fullValue, queryStr, Min(queryLen, fullLen));
     456              : 
     457          234 :         if (r == 0)
     458              :         {
     459           26 :             if (queryLen > fullLen)
     460            0 :                 r = -1;
     461           26 :             else if (queryLen < fullLen)
     462           26 :                 r = 1;
     463              :         }
     464              : 
     465          234 :         switch (strategy)
     466              :         {
     467          108 :             case BTLessStrategyNumber:
     468          108 :                 res = (r < 0);
     469          108 :                 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          126 :             case BTGreaterStrategyNumber:
     480          126 :                 res = (r > 0);
     481          126 :                 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          234 :         if (!res)
     490          100 :             break;              /* no need to consider remaining conditions */
     491              :     }
     492              : 
     493          126 :     PG_RETURN_BOOL(res);
     494              : }
     495              : 
     496            2 : PG_FUNCTION_INFO_V1(spgist_name_compress);
     497              : Datum
     498         6916 : spgist_name_compress(PG_FUNCTION_ARGS)
     499              : {
     500         6916 :     Name        inName = PG_GETARG_NAME(0);
     501         6916 :     char       *inStr = NameStr(*inName);
     502              : 
     503         6916 :     PG_RETURN_DATUM(formTextDatum(inStr, strlen(inStr)));
     504              : }
        

Generated by: LCOV version 2.0-1