LCOV - code coverage report
Current view: top level - contrib/hstore - hstore_compat.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 116 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 5 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.
      98              :  */
      99              : typedef struct
     100              : {
     101              :         uint16          keylen;
     102              :         uint16          vallen;
     103              :         uint32
     104              :                                 valisnull:1,
     105              :                                 pos:31;
     106              : } HOldEntry;
     107              : 
     108              : StaticAssertDecl(sizeof(HOldEntry) == 2 * sizeof(HEntry),
     109              :                                  "old hstore format is not upward-compatible");
     110              : 
     111              : static int      hstoreValidNewFormat(HStore *hs);
     112              : static int      hstoreValidOldFormat(HStore *hs);
     113              : 
     114              : 
     115              : /*
     116              :  * Validity test for a new-format hstore.
     117              :  *      0 = not valid
     118              :  *      1 = valid but with "slop" in the length
     119              :  *      2 = exactly valid
     120              :  */
     121              : static int
     122            0 : hstoreValidNewFormat(HStore *hs)
     123              : {
     124            0 :         int                     count = HS_COUNT(hs);
     125            0 :         HEntry     *entries = ARRPTR(hs);
     126            0 :         int                     buflen = (count) ? HSE_ENDPOS(entries[2 * (count) - 1]) : 0;
     127            0 :         int                     vsize = CALCDATASIZE(count, buflen);
     128            0 :         int                     i;
     129              : 
     130            0 :         if (hs->size_ & HS_FLAG_NEWVERSION)
     131            0 :                 return 2;
     132              : 
     133            0 :         if (count == 0)
     134            0 :                 return 2;
     135              : 
     136            0 :         if (!HSE_ISFIRST(entries[0]))
     137            0 :                 return 0;
     138              : 
     139            0 :         if (vsize > VARSIZE(hs))
     140            0 :                 return 0;
     141              : 
     142              :         /* entry position must be nondecreasing */
     143              : 
     144            0 :         for (i = 1; i < 2 * count; ++i)
     145              :         {
     146            0 :                 if (HSE_ISFIRST(entries[i])
     147            0 :                         || (HSE_ENDPOS(entries[i]) < HSE_ENDPOS(entries[i - 1])))
     148            0 :                         return 0;
     149            0 :         }
     150              : 
     151              :         /* key length must be nondecreasing and keys must not be null */
     152              : 
     153            0 :         for (i = 1; i < count; ++i)
     154              :         {
     155            0 :                 if (HSTORE_KEYLEN(entries, i) < HSTORE_KEYLEN(entries, i - 1))
     156            0 :                         return 0;
     157            0 :                 if (HSE_ISNULL(entries[2 * i]))
     158            0 :                         return 0;
     159            0 :         }
     160              : 
     161            0 :         if (vsize != VARSIZE(hs))
     162            0 :                 return 1;
     163              : 
     164            0 :         return 2;
     165            0 : }
     166              : 
     167              : /*
     168              :  * Validity test for an old-format hstore.
     169              :  *      0 = not valid
     170              :  *      1 = valid but with "slop" in the length
     171              :  *      2 = exactly valid
     172              :  */
     173              : static int
     174            0 : hstoreValidOldFormat(HStore *hs)
     175              : {
     176            0 :         int                     count = hs->size_;
     177            0 :         HOldEntry  *entries = (HOldEntry *) ARRPTR(hs);
     178            0 :         int                     vsize;
     179            0 :         int                     lastpos = 0;
     180            0 :         int                     i;
     181              : 
     182            0 :         if (hs->size_ & HS_FLAG_NEWVERSION)
     183            0 :                 return 0;
     184              : 
     185            0 :         if (count == 0)
     186            0 :                 return 2;
     187              : 
     188            0 :         if (count > 0xFFFFFFF)
     189            0 :                 return 0;
     190              : 
     191            0 :         if (CALCDATASIZE(count, 0) > VARSIZE(hs))
     192            0 :                 return 0;
     193              : 
     194            0 :         if (entries[0].pos != 0)
     195            0 :                 return 0;
     196              : 
     197              :         /* key length must be nondecreasing */
     198              : 
     199            0 :         for (i = 1; i < count; ++i)
     200              :         {
     201            0 :                 if (entries[i].keylen < entries[i - 1].keylen)
     202            0 :                         return 0;
     203            0 :         }
     204              : 
     205              :         /*
     206              :          * entry position must be strictly increasing, except for the first entry
     207              :          * (which can be ""=>"" and thus zero-length); and all entries must be
     208              :          * properly contiguous
     209              :          */
     210              : 
     211            0 :         for (i = 0; i < count; ++i)
     212              :         {
     213            0 :                 if (entries[i].pos != lastpos)
     214            0 :                         return 0;
     215            0 :                 lastpos += (entries[i].keylen
     216            0 :                                         + ((entries[i].valisnull) ? 0 : entries[i].vallen));
     217            0 :         }
     218              : 
     219            0 :         vsize = CALCDATASIZE(count, lastpos);
     220              : 
     221            0 :         if (vsize > VARSIZE(hs))
     222            0 :                 return 0;
     223              : 
     224            0 :         if (vsize != VARSIZE(hs))
     225            0 :                 return 1;
     226              : 
     227            0 :         return 2;
     228            0 : }
     229              : 
     230              : 
     231              : /*
     232              :  * hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores
     233              :  */
     234              : HStore *
     235            0 : hstoreUpgrade(Datum orig)
     236              : {
     237            0 :         HStore     *hs = (HStore *) PG_DETOAST_DATUM(orig);
     238            0 :         int                     valid_new;
     239            0 :         int                     valid_old;
     240              : 
     241              :         /* Return immediately if no conversion needed */
     242            0 :         if (hs->size_ & HS_FLAG_NEWVERSION)
     243            0 :                 return hs;
     244              : 
     245              :         /* Do we have a writable copy? If not, make one. */
     246            0 :         if ((void *) hs == (void *) DatumGetPointer(orig))
     247            0 :                 hs = (HStore *) PG_DETOAST_DATUM_COPY(orig);
     248              : 
     249            0 :         if (hs->size_ == 0 ||
     250            0 :                 (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0]))))
     251              :         {
     252            0 :                 HS_SETCOUNT(hs, HS_COUNT(hs));
     253            0 :                 HS_FIXSIZE(hs, HS_COUNT(hs));
     254            0 :                 return hs;
     255              :         }
     256              : 
     257            0 :         valid_new = hstoreValidNewFormat(hs);
     258            0 :         valid_old = hstoreValidOldFormat(hs);
     259              : 
     260            0 :         if (!valid_old || hs->size_ == 0)
     261              :         {
     262            0 :                 if (valid_new)
     263              :                 {
     264              :                         /*
     265              :                          * force the "new version" flag and the correct varlena length.
     266              :                          */
     267            0 :                         HS_SETCOUNT(hs, HS_COUNT(hs));
     268            0 :                         HS_FIXSIZE(hs, HS_COUNT(hs));
     269            0 :                         return hs;
     270              :                 }
     271              :                 else
     272              :                 {
     273            0 :                         elog(ERROR, "invalid hstore value found");
     274              :                 }
     275            0 :         }
     276              : 
     277              :         /*
     278              :          * this is the tricky edge case. It is only possible in some quite extreme
     279              :          * cases (the hstore must have had a lot of wasted padding space at the
     280              :          * end). But the only way a "new" hstore value could get here is if we're
     281              :          * upgrading in place from a pre-release version of hstore-new (NOT
     282              :          * contrib/hstore), so we work off the following assumptions: 1. If you're
     283              :          * moving from old contrib/hstore to hstore-new, you're required to fix up
     284              :          * any potential conflicts first, e.g. by running ALTER TABLE ... USING
     285              :          * col::text::hstore; on all hstore columns before upgrading. 2. If you're
     286              :          * moving from old contrib/hstore to new contrib/hstore, then "new" values
     287              :          * are impossible here 3. If you're moving from pre-release hstore-new to
     288              :          * hstore-new, then "old" values are impossible here 4. If you're moving
     289              :          * from pre-release hstore-new to new contrib/hstore, you're not doing so
     290              :          * as an in-place upgrade, so there is no issue So the upshot of all this
     291              :          * is that we can treat all the edge cases as "new" if we're being built
     292              :          * as hstore-new, and "old" if we're being built as contrib/hstore.
     293              :          *
     294              :          * XXX the WARNING can probably be downgraded to DEBUG1 once this has been
     295              :          * beta-tested. But for now, it would be very useful to know if anyone can
     296              :          * actually reach this case in a non-contrived setting.
     297              :          */
     298              : 
     299            0 :         if (valid_new)
     300              :         {
     301              : #ifdef HSTORE_IS_HSTORE_NEW
     302              :                 elog(WARNING, "ambiguous hstore value resolved as hstore-new");
     303              : 
     304              :                 /*
     305              :                  * force the "new version" flag and the correct varlena length.
     306              :                  */
     307              :                 HS_SETCOUNT(hs, HS_COUNT(hs));
     308              :                 HS_FIXSIZE(hs, HS_COUNT(hs));
     309              :                 return hs;
     310              : #else
     311            0 :                 elog(WARNING, "ambiguous hstore value resolved as hstore-old");
     312              : #endif
     313            0 :         }
     314              : 
     315              :         /*
     316              :          * must have an old-style value. Overwrite it in place as a new-style one.
     317              :          */
     318              :         {
     319            0 :                 int                     count = hs->size_;
     320            0 :                 HEntry     *new_entries = ARRPTR(hs);
     321            0 :                 HOldEntry  *old_entries = (HOldEntry *) ARRPTR(hs);
     322            0 :                 int                     i;
     323              : 
     324            0 :                 for (i = 0; i < count; ++i)
     325              :                 {
     326            0 :                         uint32          pos = old_entries[i].pos;
     327            0 :                         uint32          keylen = old_entries[i].keylen;
     328            0 :                         uint32          vallen = old_entries[i].vallen;
     329            0 :                         bool            isnull = old_entries[i].valisnull;
     330              : 
     331            0 :                         if (isnull)
     332            0 :                                 vallen = 0;
     333              : 
     334            0 :                         new_entries[2 * i].entry = (pos + keylen) & HENTRY_POSMASK;
     335            0 :                         new_entries[2 * i + 1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK)
     336            0 :                                                                                         | ((isnull) ? HENTRY_ISNULL : 0));
     337            0 :                 }
     338              : 
     339            0 :                 if (count)
     340            0 :                         new_entries[0].entry |= HENTRY_ISFIRST;
     341            0 :                 HS_SETCOUNT(hs, count);
     342            0 :                 HS_FIXSIZE(hs, count);
     343            0 :         }
     344              : 
     345            0 :         return hs;
     346            0 : }
     347              : 
     348              : 
     349            0 : PG_FUNCTION_INFO_V1(hstore_version_diag);
     350              : Datum
     351            0 : hstore_version_diag(PG_FUNCTION_ARGS)
     352              : {
     353            0 :         HStore     *hs = (HStore *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
     354            0 :         int                     valid_new = hstoreValidNewFormat(hs);
     355            0 :         int                     valid_old = hstoreValidOldFormat(hs);
     356              : 
     357            0 :         PG_RETURN_INT32(valid_old * 10 + valid_new);
     358            0 : }
        

Generated by: LCOV version 2.3.2-1