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); }
/* * 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; bool writable; /* Return immediately if no conversion needed */ if ((hs->size_ & HS_FLAG_NEWVERSION) || hs->size_ == 0 || (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0])))) return hs; valid_new = hstoreValidNewFormat(hs); valid_old = hstoreValidOldFormat(hs); /* Do we have a writable copy? */ writable = ((void *) hs != (void *) DatumGetPointer(orig)); if (!valid_old || hs->size_ == 0) { if (valid_new) { /* * force the "new version" flag and the correct varlena length, * but only if we have a writable copy already (which we almost * always will, since short new-format values won't come through * here) */ if (writable) { 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) { #if HSTORE_IS_HSTORE_NEW elog(WARNING, "ambiguous hstore value resolved as hstore-new"); /* * force the "new version" flag and the correct varlena length, but * only if we have a writable copy already (which we almost always * will, since short new-format values won't come through here) */ if (writable) { 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 } /* * must have an old-style value. Overwrite it in place as a new-style one, * making sure we have a writable copy first. */ if (!writable) hs = (HStore *) PG_DETOAST_DATUM_COPY(orig); { int count = hs->size_; HEntry *new_entries = ARRPTR(hs); HOldEntry *old_entries = (HOldEntry *) ARRPTR(hs); int i; 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; }