postgres/contrib/hstore/hstore_compat.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

360 lines
10 KiB
C
Raw Normal View History

/*
2010-09-21 00:08:53 +04:00
* contrib/hstore/hstore_compat.c
*
* Notes on old/new hstore format disambiguation.
*
* There are three formats to consider:
* 1) old contrib/hstore (referred to as hstore-old)
* 2) prerelease pgfoundry hstore
* 3) new contrib/hstore
*
* (2) and (3) are identical except for the HS_FLAG_NEWVERSION
* bit, which is set in (3) but not (2).
*
* Values that are already in format (3), or which are
* unambiguously in format (2), are handled by the first
* "return immediately" test in hstoreUpgrade().
*
* To stress a point: we ONLY get here with possibly-ambiguous
* values if we're doing some sort of in-place migration from an
* old prerelease pgfoundry hstore-new; and we explicitly don't
* support that without fixing up any potentially padded values
* first. Most of the code here is serious overkill, but the
* performance penalty isn't serious (especially compared to the
* palloc() that we have to do anyway) and the belt-and-braces
* validity checks provide some reassurance. (If for some reason
* we get a value that would have worked on the old code, but
* which would be botched by the conversion code, the validity
* checks will fail it first so we get an error rather than bad
* data.)
*
* Note also that empty hstores are the same in (2) and (3), so
* there are some special-case paths for them.
*
* We tell the difference between formats (2) and (3) as follows (but
* note that there are some edge cases where we can't tell; see
* comments in hstoreUpgrade):
*
* First, since there must be at least one entry, we look at
* how the bits line up. The new format looks like:
*
* 10kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk (k..k = keylen)
* 0nvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv (v..v = keylen+vallen)
*
* The old format looks like one of these, depending on endianness
* and bitfield layout: (k..k = keylen, v..v = vallen, p..p = pos,
* n = isnull)
*
* kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
* nppppppppppppppppppppppppppppppp
*
* kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv
* pppppppppppppppppppppppppppppppn
*
* vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
* nppppppppppppppppppppppppppppppp
*
* vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk
* pppppppppppppppppppppppppppppppn (usual i386 format)
*
* If the entry is in old format, for the first entry "pos" must be 0.
* We can obviously see that either keylen or vallen must be >32768
* for there to be any ambiguity (which is why lengths less than that
* are fasttracked in hstore.h) Since "pos"==0, the "v" field in the
* new-format interpretation can only be 0 or 1, which constrains all
* but three bits of the old-format's k and v fields. But in addition
* to all of this, the data length implied by the keylen and vallen
* must fit in the varlena size. So the only ambiguous edge case for
* hstores with only one entry occurs between a new-format entry with
* an excess (~32k) of padding, and an old-format entry. But we know
* which format to use in that case based on how we were compiled, so
* no actual data corruption can occur.
*
* If there is more than one entry, the requirement that keys do not
* decrease in length, and that positions increase contiguously, and
* that the end of the data not be beyond the end of the varlena
* itself, disambiguates in almost all other cases. There is a small
* set of ambiguous cases which could occur if the old-format value
* has a large excess of padding and just the right pattern of key
* sizes, but these are also handled based on how we were compiled.
*
* The otherwise undocumented function hstore_version_diag is provided
* for testing purposes.
*/
#include "postgres.h"
#include "hstore.h"
/*
* This is the structure used for entries in the old contrib/hstore
* implementation. Notice that this is the same size as the new entry
* (two 32-bit words per key/value pair) and that the header is the
* same, so the old and new versions of ARRPTR, STRPTR, CALCDATASIZE
* etc. are compatible.
*
* If the above statement isn't true on some bizarre platform, we're
2012-10-01 06:46:29 +04:00
* a bit hosed (see StaticAssertStmt in hstoreValidOldFormat).
*/
typedef struct
{
uint16 keylen;
uint16 vallen;
uint32
valisnull:1,
pos:31;
} HOldEntry;
static int hstoreValidNewFormat(HStore *hs);
static int hstoreValidOldFormat(HStore *hs);
/*
* Validity test for a new-format hstore.
* 0 = not valid
* 1 = valid but with "slop" in the length
* 2 = exactly valid
*/
static int
hstoreValidNewFormat(HStore *hs)
{
int count = HS_COUNT(hs);
HEntry *entries = ARRPTR(hs);
int buflen = (count) ? HSE_ENDPOS(entries[2 * (count) - 1]) : 0;
int vsize = CALCDATASIZE(count, buflen);
int i;
if (hs->size_ & HS_FLAG_NEWVERSION)
return 2;
if (count == 0)
return 2;
if (!HSE_ISFIRST(entries[0]))
return 0;
if (vsize > VARSIZE(hs))
return 0;
/* entry position must be nondecreasing */
for (i = 1; i < 2 * count; ++i)
{
if (HSE_ISFIRST(entries[i])
|| (HSE_ENDPOS(entries[i]) < HSE_ENDPOS(entries[i - 1])))
return 0;
}
/* key length must be nondecreasing and keys must not be null */
for (i = 1; i < count; ++i)
{
if (HSTORE_KEYLEN(entries, i) < HSTORE_KEYLEN(entries, i - 1))
return 0;
if (HSE_ISNULL(entries[2 * i]))
return 0;
}
if (vsize != VARSIZE(hs))
return 1;
return 2;
}
/*
* Validity test for an old-format hstore.
* 0 = not valid
* 1 = valid but with "slop" in the length
* 2 = exactly valid
*/
static int
hstoreValidOldFormat(HStore *hs)
{
int count = hs->size_;
HOldEntry *entries = (HOldEntry *) ARRPTR(hs);
int vsize;
int lastpos = 0;
int i;
if (hs->size_ & HS_FLAG_NEWVERSION)
return 0;
/* New format uses an HEntry for key and another for value */
2012-10-01 06:46:29 +04:00
StaticAssertStmt(sizeof(HOldEntry) == 2 * sizeof(HEntry),
"old hstore format is not upward-compatible");
if (count == 0)
return 2;
if (count > 0xFFFFFFF)
return 0;
if (CALCDATASIZE(count, 0) > VARSIZE(hs))
return 0;
if (entries[0].pos != 0)
return 0;
/* key length must be nondecreasing */
for (i = 1; i < count; ++i)
{
if (entries[i].keylen < entries[i - 1].keylen)
return 0;
}
/*
* entry position must be strictly increasing, except for the first entry
* (which can be ""=>"" and thus zero-length); and all entries must be
* properly contiguous
*/
for (i = 0; i < count; ++i)
{
if (entries[i].pos != lastpos)
return 0;
lastpos += (entries[i].keylen
+ ((entries[i].valisnull) ? 0 : entries[i].vallen));
}
vsize = CALCDATASIZE(count, lastpos);
if (vsize > VARSIZE(hs))
return 0;
if (vsize != VARSIZE(hs))
return 1;
return 2;
}
/*
* hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores
*/
HStore *
hstoreUpgrade(Datum orig)
{
HStore *hs = (HStore *) PG_DETOAST_DATUM(orig);
int valid_new;
int valid_old;
/* Return immediately if no conversion needed */
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
if (hs->size_ & HS_FLAG_NEWVERSION)
return hs;
/* Do we have a writable copy? If not, make one. */
if ((void *) hs == (void *) DatumGetPointer(orig))
hs = (HStore *) PG_DETOAST_DATUM_COPY(orig);
if (hs->size_ == 0 ||
(VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0]))))
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
{
HS_SETCOUNT(hs, HS_COUNT(hs));
HS_FIXSIZE(hs, HS_COUNT(hs));
return hs;
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
}
valid_new = hstoreValidNewFormat(hs);
valid_old = hstoreValidOldFormat(hs);
if (!valid_old || hs->size_ == 0)
{
if (valid_new)
{
/*
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
* force the "new version" flag and the correct varlena length.
*/
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
HS_SETCOUNT(hs, HS_COUNT(hs));
HS_FIXSIZE(hs, HS_COUNT(hs));
return hs;
}
else
{
elog(ERROR, "invalid hstore value found");
}
}
/*
* this is the tricky edge case. It is only possible in some quite extreme
* cases (the hstore must have had a lot of wasted padding space at the
* end). But the only way a "new" hstore value could get here is if we're
* upgrading in place from a pre-release version of hstore-new (NOT
* contrib/hstore), so we work off the following assumptions: 1. If you're
* moving from old contrib/hstore to hstore-new, you're required to fix up
* any potential conflicts first, e.g. by running ALTER TABLE ... USING
* col::text::hstore; on all hstore columns before upgrading. 2. If you're
* moving from old contrib/hstore to new contrib/hstore, then "new" values
* are impossible here 3. If you're moving from pre-release hstore-new to
* hstore-new, then "old" values are impossible here 4. If you're moving
* from pre-release hstore-new to new contrib/hstore, you're not doing so
* as an in-place upgrade, so there is no issue So the upshot of all this
* is that we can treat all the edge cases as "new" if we're being built
* as hstore-new, and "old" if we're being built as contrib/hstore.
*
* XXX the WARNING can probably be downgraded to DEBUG1 once this has been
* beta-tested. But for now, it would be very useful to know if anyone can
* actually reach this case in a non-contrived setting.
*/
if (valid_new)
{
#ifdef HSTORE_IS_HSTORE_NEW
elog(WARNING, "ambiguous hstore value resolved as hstore-new");
/*
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
* force the "new version" flag and the correct varlena length.
*/
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
HS_SETCOUNT(hs, HS_COUNT(hs));
HS_FIXSIZE(hs, HS_COUNT(hs));
return hs;
#else
elog(WARNING, "ambiguous hstore value resolved as hstore-old");
#endif
}
/*
Fix hstore hash function for empty hstores upgraded from 8.4. Hstore data generated on pg 8.4 and pg_upgraded to current versions remains in its original on-disk format unless modified. The same goes for values generated by the addon hstore-new module on pre-9.0 versions. (The hstoreUpgrade function converts old values on the fly when read in, but the on-disk value is not modified by this.) Since old-format empty hstores (and hstore-new hstores) have representations compatible with the new format, hstoreUpgrade thought it could get away without modifying such values; but this breaks hstore_hash (and the new hstore_hash_extended) which assumes bit-perfect matching between semantically identical hstore values. Only one bit actually differs (the "new version" flag in the count field) but that of course is enough to break the hash. Fix by making hstoreUpgrade unconditionally convert all old values to new format. Backpatch all the way, even though this changes a hash value in some cases, because in those cases the hash value is already failing - for example, a hash join between old- and new-format empty hstores will be failing to match, or a hash index on an hstore column containing an old-format empty value will be failing to find the value since it will be searching for a hash derived from a new-format datum. (There are no known field reports of this happening, probably because hashing of hstores has only been useful in limited circumstances and there probably isn't much upgraded data being used this way.) Per concerns arising from discussion of commit eb6f29141be. Original bug is my fault. Discussion: https://postgr.es/m/60b1fd3b-7332-40f0-7e7f-f2f04f777747%402ndquadrant.com
2018-11-24 12:59:49 +03:00
* must have an old-style value. Overwrite it in place as a new-style one.
*/
{
int count = hs->size_;
HEntry *new_entries = ARRPTR(hs);
HOldEntry *old_entries = (HOldEntry *) ARRPTR(hs);
int i;
2010-02-26 05:01:40 +03:00
for (i = 0; i < count; ++i)
{
uint32 pos = old_entries[i].pos;
uint32 keylen = old_entries[i].keylen;
uint32 vallen = old_entries[i].vallen;
bool isnull = old_entries[i].valisnull;
if (isnull)
vallen = 0;
new_entries[2 * i].entry = (pos + keylen) & HENTRY_POSMASK;
new_entries[2 * i + 1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK)
| ((isnull) ? HENTRY_ISNULL : 0));
}
if (count)
new_entries[0].entry |= HENTRY_ISFIRST;
HS_SETCOUNT(hs, count);
HS_FIXSIZE(hs, count);
}
return hs;
}
PG_FUNCTION_INFO_V1(hstore_version_diag);
Datum
hstore_version_diag(PG_FUNCTION_ARGS)
{
HStore *hs = (HStore *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
int valid_new = hstoreValidNewFormat(hs);
int valid_old = hstoreValidOldFormat(hs);
PG_RETURN_INT32(valid_old * 10 + valid_new);
}