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

Generated by: LCOV version 1.14