LCOV - code coverage report
Current view: top level - contrib/hstore - hstore_compat.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 5 98 5.1 %
Date: 2024-04-19 14:10:55 Functions: 2 5 40.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * contrib/hstore/hstore_compat.c
       3             :  *
       4             :  * Notes on old/new hstore format disambiguation.
       5             :  *
       6             :  * There are three formats to consider:
       7             :  * 1) old contrib/hstore (referred to as hstore-old)
       8             :  * 2) prerelease pgfoundry hstore
       9             :  * 3) new contrib/hstore
      10             :  *
      11             :  * (2) and (3) are identical except for the HS_FLAG_NEWVERSION
      12             :  * bit, which is set in (3) but not (2).
      13             :  *
      14             :  * Values that are already in format (3), or which are
      15             :  * unambiguously in format (2), are handled by the first
      16             :  * "return immediately" test in hstoreUpgrade().
      17             :  *
      18             :  * To stress a point: we ONLY get here with possibly-ambiguous
      19             :  * values if we're doing some sort of in-place migration from an
      20             :  * old prerelease pgfoundry hstore-new; and we explicitly don't
      21             :  * support that without fixing up any potentially padded values
      22             :  * first. Most of the code here is serious overkill, but the
      23             :  * performance penalty isn't serious (especially compared to the
      24             :  * palloc() that we have to do anyway) and the belt-and-braces
      25             :  * validity checks provide some reassurance. (If for some reason
      26             :  * we get a value that would have worked on the old code, but
      27             :  * which would be botched by the conversion code, the validity
      28             :  * checks will fail it first so we get an error rather than bad
      29             :  * data.)
      30             :  *
      31             :  * Note also that empty hstores are the same in (2) and (3), so
      32             :  * there are some special-case paths for them.
      33             :  *
      34             :  * We tell the difference between formats (2) and (3) as follows (but
      35             :  * note that there are some edge cases where we can't tell; see
      36             :  * comments in hstoreUpgrade):
      37             :  *
      38             :  * First, since there must be at least one entry, we look at
      39             :  * how the bits line up. The new format looks like:
      40             :  *
      41             :  * 10kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk  (k..k = keylen)
      42             :  * 0nvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv  (v..v = keylen+vallen)
      43             :  *
      44             :  * The old format looks like one of these, depending on endianness
      45             :  * and bitfield layout: (k..k = keylen, v..v = vallen, p..p = pos,
      46             :  * n = isnull)
      47             :  *
      48             :  * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
      49             :  * nppppppppppppppppppppppppppppppp
      50             :  *
      51             :  * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
      52             :  * pppppppppppppppppppppppppppppppn
      53             :  *
      54             :  * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
      55             :  * nppppppppppppppppppppppppppppppp
      56             :  *
      57             :  * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
      58             :  * pppppppppppppppppppppppppppppppn   (usual i386 format)
      59             :  *
      60             :  * If the entry is in old format, for the first entry "pos" must be 0.
      61             :  * We can obviously see that either keylen or vallen must be >32768
      62             :  * for there to be any ambiguity (which is why lengths less than that
      63             :  * are fasttracked in hstore.h) Since "pos"==0, the "v" field in the
      64             :  * new-format interpretation can only be 0 or 1, which constrains all
      65             :  * but three bits of the old-format's k and v fields. But in addition
      66             :  * to all of this, the data length implied by the keylen and vallen
      67             :  * must fit in the varlena size. So the only ambiguous edge case for
      68             :  * hstores with only one entry occurs between a new-format entry with
      69             :  * an excess (~32k) of padding, and an old-format entry. But we know
      70             :  * which format to use in that case based on how we were compiled, so
      71             :  * no actual data corruption can occur.
      72             :  *
      73             :  * If there is more than one entry, the requirement that keys do not
      74             :  * decrease in length, and that positions increase contiguously, and
      75             :  * that the end of the data not be beyond the end of the varlena
      76             :  * itself, disambiguates in almost all other cases. There is a small
      77             :  * set of ambiguous cases which could occur if the old-format value
      78             :  * has a large excess of padding and just the right pattern of key
      79             :  * sizes, but these are also handled based on how we were compiled.
      80             :  *
      81             :  * The otherwise undocumented function hstore_version_diag is provided
      82             :  * for testing purposes.
      83             :  */
      84             : #include "postgres.h"
      85             : 
      86             : 
      87             : #include "hstore.h"
      88             : 
      89             : /*
      90             :  * This is the structure used for entries in the old contrib/hstore
      91             :  * implementation. Notice that this is the same size as the new entry
      92             :  * (two 32-bit words per key/value pair) and that the header is the
      93             :  * same, so the old and new versions of ARRPTR, STRPTR, CALCDATASIZE
      94             :  * etc. are compatible.
      95             :  *
      96             :  * If the above statement isn't true on some bizarre platform, we're
      97             :  * a bit hosed (see StaticAssertStmt in hstoreValidOldFormat).
      98             :  */
      99             : typedef struct
     100             : {
     101             :     uint16      keylen;
     102             :     uint16      vallen;
     103             :     uint32
     104             :                 valisnull:1,
     105             :                 pos:31;
     106             : } HOldEntry;
     107             : 
     108             : static int  hstoreValidNewFormat(HStore *hs);
     109             : static int  hstoreValidOldFormat(HStore *hs);
     110             : 
     111             : 
     112             : /*
     113             :  * Validity test for a new-format hstore.
     114             :  *  0 = not valid
     115             :  *  1 = valid but with "slop" in the length
     116             :  *  2 = exactly valid
     117             :  */
     118             : static int
     119           0 : hstoreValidNewFormat(HStore *hs)
     120             : {
     121           0 :     int         count = HS_COUNT(hs);
     122           0 :     HEntry     *entries = ARRPTR(hs);
     123           0 :     int         buflen = (count) ? HSE_ENDPOS(entries[2 * (count) - 1]) : 0;
     124           0 :     int         vsize = CALCDATASIZE(count, buflen);
     125             :     int         i;
     126             : 
     127           0 :     if (hs->size_ & HS_FLAG_NEWVERSION)
     128           0 :         return 2;
     129             : 
     130           0 :     if (count == 0)
     131           0 :         return 2;
     132             : 
     133           0 :     if (!HSE_ISFIRST(entries[0]))
     134           0 :         return 0;
     135             : 
     136           0 :     if (vsize > VARSIZE(hs))
     137           0 :         return 0;
     138             : 
     139             :     /* entry position must be nondecreasing */
     140             : 
     141           0 :     for (i = 1; i < 2 * count; ++i)
     142             :     {
     143           0 :         if (HSE_ISFIRST(entries[i])
     144           0 :             || (HSE_ENDPOS(entries[i]) < HSE_ENDPOS(entries[i - 1])))
     145           0 :             return 0;
     146             :     }
     147             : 
     148             :     /* key length must be nondecreasing and keys must not be null */
     149             : 
     150           0 :     for (i = 1; i < count; ++i)
     151             :     {
     152           0 :         if (HSTORE_KEYLEN(entries, i) < HSTORE_KEYLEN(entries, i - 1))
     153           0 :             return 0;
     154           0 :         if (HSE_ISNULL(entries[2 * i]))
     155           0 :             return 0;
     156             :     }
     157             : 
     158           0 :     if (vsize != VARSIZE(hs))
     159           0 :         return 1;
     160             : 
     161           0 :     return 2;
     162             : }
     163             : 
     164             : /*
     165             :  * Validity test for an old-format hstore.
     166             :  *  0 = not valid
     167             :  *  1 = valid but with "slop" in the length
     168             :  *  2 = exactly valid
     169             :  */
     170             : static int
     171           0 : hstoreValidOldFormat(HStore *hs)
     172             : {
     173           0 :     int         count = hs->size_;
     174           0 :     HOldEntry  *entries = (HOldEntry *) ARRPTR(hs);
     175             :     int         vsize;
     176           0 :     int         lastpos = 0;
     177             :     int         i;
     178             : 
     179           0 :     if (hs->size_ & HS_FLAG_NEWVERSION)
     180           0 :         return 0;
     181             : 
     182             :     /* New format uses an HEntry for key and another for value */
     183             :     StaticAssertStmt(sizeof(HOldEntry) == 2 * sizeof(HEntry),
     184             :                      "old hstore format is not upward-compatible");
     185             : 
     186           0 :     if (count == 0)
     187           0 :         return 2;
     188             : 
     189           0 :     if (count > 0xFFFFFFF)
     190           0 :         return 0;
     191             : 
     192           0 :     if (CALCDATASIZE(count, 0) > VARSIZE(hs))
     193           0 :         return 0;
     194             : 
     195           0 :     if (entries[0].pos != 0)
     196           0 :         return 0;
     197             : 
     198             :     /* key length must be nondecreasing */
     199             : 
     200           0 :     for (i = 1; i < count; ++i)
     201             :     {
     202           0 :         if (entries[i].keylen < entries[i - 1].keylen)
     203           0 :             return 0;
     204             :     }
     205             : 
     206             :     /*
     207             :      * entry position must be strictly increasing, except for the first entry
     208             :      * (which can be ""=>"" and thus zero-length); and all entries must be
     209             :      * properly contiguous
     210             :      */
     211             : 
     212           0 :     for (i = 0; i < count; ++i)
     213             :     {
     214           0 :         if (entries[i].pos != lastpos)
     215           0 :             return 0;
     216           0 :         lastpos += (entries[i].keylen
     217           0 :                     + ((entries[i].valisnull) ? 0 : entries[i].vallen));
     218             :     }
     219             : 
     220           0 :     vsize = CALCDATASIZE(count, lastpos);
     221             : 
     222           0 :     if (vsize > VARSIZE(hs))
     223           0 :         return 0;
     224             : 
     225           0 :     if (vsize != VARSIZE(hs))
     226           0 :         return 1;
     227             : 
     228           0 :     return 2;
     229             : }
     230             : 
     231             : 
     232             : /*
     233             :  * hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores
     234             :  */
     235             : HStore *
     236      219536 : hstoreUpgrade(Datum orig)
     237             : {
     238      219536 :     HStore     *hs = (HStore *) PG_DETOAST_DATUM(orig);
     239             :     int         valid_new;
     240             :     int         valid_old;
     241             : 
     242             :     /* Return immediately if no conversion needed */
     243      219536 :     if (hs->size_ & HS_FLAG_NEWVERSION)
     244      219536 :         return hs;
     245             : 
     246             :     /* Do we have a writable copy? If not, make one. */
     247           0 :     if ((void *) hs == (void *) DatumGetPointer(orig))
     248           0 :         hs = (HStore *) PG_DETOAST_DATUM_COPY(orig);
     249             : 
     250           0 :     if (hs->size_ == 0 ||
     251           0 :         (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0]))))
     252             :     {
     253           0 :         HS_SETCOUNT(hs, HS_COUNT(hs));
     254           0 :         HS_FIXSIZE(hs, HS_COUNT(hs));
     255           0 :         return hs;
     256             :     }
     257             : 
     258           0 :     valid_new = hstoreValidNewFormat(hs);
     259           0 :     valid_old = hstoreValidOldFormat(hs);
     260             : 
     261           0 :     if (!valid_old || hs->size_ == 0)
     262             :     {
     263           0 :         if (valid_new)
     264             :         {
     265             :             /*
     266             :              * force the "new version" flag and the correct varlena length.
     267             :              */
     268           0 :             HS_SETCOUNT(hs, HS_COUNT(hs));
     269           0 :             HS_FIXSIZE(hs, HS_COUNT(hs));
     270           0 :             return hs;
     271             :         }
     272             :         else
     273             :         {
     274           0 :             elog(ERROR, "invalid hstore value found");
     275             :         }
     276             :     }
     277             : 
     278             :     /*
     279             :      * this is the tricky edge case. It is only possible in some quite extreme
     280             :      * cases (the hstore must have had a lot of wasted padding space at the
     281             :      * end). But the only way a "new" hstore value could get here is if we're
     282             :      * upgrading in place from a pre-release version of hstore-new (NOT
     283             :      * contrib/hstore), so we work off the following assumptions: 1. If you're
     284             :      * moving from old contrib/hstore to hstore-new, you're required to fix up
     285             :      * any potential conflicts first, e.g. by running ALTER TABLE ... USING
     286             :      * col::text::hstore; on all hstore columns before upgrading. 2. If you're
     287             :      * moving from old contrib/hstore to new contrib/hstore, then "new" values
     288             :      * are impossible here 3. If you're moving from pre-release hstore-new to
     289             :      * hstore-new, then "old" values are impossible here 4. If you're moving
     290             :      * from pre-release hstore-new to new contrib/hstore, you're not doing so
     291             :      * as an in-place upgrade, so there is no issue So the upshot of all this
     292             :      * is that we can treat all the edge cases as "new" if we're being built
     293             :      * as hstore-new, and "old" if we're being built as contrib/hstore.
     294             :      *
     295             :      * XXX the WARNING can probably be downgraded to DEBUG1 once this has been
     296             :      * beta-tested. But for now, it would be very useful to know if anyone can
     297             :      * actually reach this case in a non-contrived setting.
     298             :      */
     299             : 
     300           0 :     if (valid_new)
     301             :     {
     302             : #ifdef HSTORE_IS_HSTORE_NEW
     303             :         elog(WARNING, "ambiguous hstore value resolved as hstore-new");
     304             : 
     305             :         /*
     306             :          * force the "new version" flag and the correct varlena length.
     307             :          */
     308             :         HS_SETCOUNT(hs, HS_COUNT(hs));
     309             :         HS_FIXSIZE(hs, HS_COUNT(hs));
     310             :         return hs;
     311             : #else
     312           0 :         elog(WARNING, "ambiguous hstore value resolved as hstore-old");
     313             : #endif
     314             :     }
     315             : 
     316             :     /*
     317             :      * must have an old-style value. Overwrite it in place as a new-style one.
     318             :      */
     319             :     {
     320           0 :         int         count = hs->size_;
     321           0 :         HEntry     *new_entries = ARRPTR(hs);
     322           0 :         HOldEntry  *old_entries = (HOldEntry *) ARRPTR(hs);
     323             :         int         i;
     324             : 
     325           0 :         for (i = 0; i < count; ++i)
     326             :         {
     327           0 :             uint32      pos = old_entries[i].pos;
     328           0 :             uint32      keylen = old_entries[i].keylen;
     329           0 :             uint32      vallen = old_entries[i].vallen;
     330           0 :             bool        isnull = old_entries[i].valisnull;
     331             : 
     332           0 :             if (isnull)
     333           0 :                 vallen = 0;
     334             : 
     335           0 :             new_entries[2 * i].entry = (pos + keylen) & HENTRY_POSMASK;
     336           0 :             new_entries[2 * i + 1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK)
     337           0 :                                             | ((isnull) ? HENTRY_ISNULL : 0));
     338             :         }
     339             : 
     340           0 :         if (count)
     341           0 :             new_entries[0].entry |= HENTRY_ISFIRST;
     342           0 :         HS_SETCOUNT(hs, count);
     343           0 :         HS_FIXSIZE(hs, count);
     344             :     }
     345             : 
     346           0 :     return hs;
     347             : }
     348             : 
     349             : 
     350          14 : PG_FUNCTION_INFO_V1(hstore_version_diag);
     351             : Datum
     352           0 : hstore_version_diag(PG_FUNCTION_ARGS)
     353             : {
     354           0 :     HStore     *hs = (HStore *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
     355           0 :     int         valid_new = hstoreValidNewFormat(hs);
     356           0 :     int         valid_old = hstoreValidOldFormat(hs);
     357             : 
     358           0 :     PG_RETURN_INT32(valid_old * 10 + valid_new);
     359             : }

Generated by: LCOV version 1.14