Skip to content

Commit bfe738d

Browse files
committed
Fix pg_extension_config_dump() to handle update cases more sanely.
If pg_extension_config_dump() is executed again for a table already listed in the extension's extconfig, the code was blindly making a new array entry. This does not seem useful. Fix it to replace the existing array entry instead, so that it's possible for extension update scripts to alter the filter conditions for configuration tables. In addition, teach ALTER EXTENSION DROP TABLE to check for an extconfig entry for the target table, and remove it if present. This is not a 100% solution because it's allowed for an extension update script to just summarily DROP a member table, and that code path doesn't go through ExecAlterExtensionContentsStmt. We could probably make that case clean things up if we had to, but it would involve sticking a very ugly wart somewhere in the guts of dependency.c. Since on the whole it seems quite unlikely that extension updates would want to remove pre-existing configuration tables, making the case possible with an explicit command seems sufficient. Per bug #7756 from Regina Obe. Back-patch to 9.1 where extensions were introduced.
1 parent 3881aed commit bfe738d

File tree

2 files changed

+239
-10
lines changed

2 files changed

+239
-10
lines changed

doc/src/sgml/extend.sgml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,10 @@ SET LOCAL search_path TO @extschema@;
665665
and reload.
666666
</para>
667667

668+
<indexterm>
669+
<primary>pg_extension_config_dump</primary>
670+
</indexterm>
671+
668672
<para>
669673
To solve this problem, an extension's script file can mark a table
670674
it has created as a configuration table, which will cause
@@ -703,6 +707,14 @@ SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entr
703707
be modified by users, can be handled by creating triggers on the
704708
configuration table to ensure that modified rows are marked correctly.
705709
</para>
710+
711+
<para>
712+
You can alter the filter condition associated with a configuration table
713+
by calling <function>pg_extension_config_dump</> again. (This would
714+
typically be useful in an extension update script.) The only way to mark
715+
a table as no longer a configuration table is to dissociate it from the
716+
extension with <command>ALTER EXTENSION ... DROP TABLE</>.
717+
</para>
706718
</sect2>
707719

708720
<sect2>

src/backend/commands/extension.c

Lines changed: 227 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,6 +2047,7 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
20472047
HeapTuple extTup;
20482048
Datum arrayDatum;
20492049
Datum elementDatum;
2050+
int arrayLength;
20502051
int arrayIndex;
20512052
bool isnull;
20522053
Datum repl_val[Natts_pg_extension];
@@ -2066,8 +2067,8 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
20662067

20672068
/*
20682069
* Check that the table exists and is a member of the extension being
2069-
* created. This ensures that we don't need to register a dependency to
2070-
* protect the extconfig entry.
2070+
* created. This ensures that we don't need to register an additional
2071+
* dependency to protect the extconfig entry.
20712072
*/
20722073
tablename = get_rel_name(tableoid);
20732074
if (tablename == NULL)
@@ -2084,6 +2085,9 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
20842085
/*
20852086
* Add the table OID and WHERE condition to the extension's extconfig and
20862087
* extcondition arrays.
2088+
*
2089+
* If the table is already in extconfig, treat this as an update of the
2090+
* WHERE condition.
20872091
*/
20882092

20892093
/* Find the pg_extension tuple */
@@ -2114,18 +2118,41 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
21142118
RelationGetDescr(extRel), &isnull);
21152119
if (isnull)
21162120
{
2121+
/* Previously empty extconfig, so build 1-element array */
2122+
arrayLength = 0;
2123+
arrayIndex = 1;
2124+
21172125
a = construct_array(&elementDatum, 1,
21182126
OIDOID,
21192127
sizeof(Oid), true, 'i');
21202128
}
21212129
else
21222130
{
2131+
/* Modify or extend existing extconfig array */
2132+
Oid *arrayData;
2133+
int i;
2134+
21232135
a = DatumGetArrayTypeP(arrayDatum);
2124-
Assert(ARR_ELEMTYPE(a) == OIDOID);
2125-
Assert(ARR_NDIM(a) == 1);
2126-
Assert(ARR_LBOUND(a)[0] == 1);
21272136

2128-
arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */
2137+
arrayLength = ARR_DIMS(a)[0];
2138+
if (ARR_NDIM(a) != 1 ||
2139+
ARR_LBOUND(a)[0] != 1 ||
2140+
arrayLength < 0 ||
2141+
ARR_HASNULL(a) ||
2142+
ARR_ELEMTYPE(a) != OIDOID)
2143+
elog(ERROR, "extconfig is not a 1-D Oid array");
2144+
arrayData = (Oid *) ARR_DATA_PTR(a);
2145+
2146+
arrayIndex = arrayLength + 1; /* set up to add after end */
2147+
2148+
for (i = 0; i < arrayLength; i++)
2149+
{
2150+
if (arrayData[i] == tableoid)
2151+
{
2152+
arrayIndex = i + 1; /* replace this element instead */
2153+
break;
2154+
}
2155+
}
21292156

21302157
a = array_set(a, 1, &arrayIndex,
21312158
elementDatum,
@@ -2145,19 +2172,26 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
21452172
RelationGetDescr(extRel), &isnull);
21462173
if (isnull)
21472174
{
2175+
if (arrayLength != 0)
2176+
elog(ERROR, "extconfig and extcondition arrays do not match");
2177+
21482178
a = construct_array(&elementDatum, 1,
21492179
TEXTOID,
21502180
-1, false, 'i');
21512181
}
21522182
else
21532183
{
21542184
a = DatumGetArrayTypeP(arrayDatum);
2155-
Assert(ARR_ELEMTYPE(a) == TEXTOID);
2156-
Assert(ARR_NDIM(a) == 1);
2157-
Assert(ARR_LBOUND(a)[0] == 1);
21582185

2159-
arrayIndex = ARR_DIMS(a)[0] + 1; /* add after end */
2186+
if (ARR_NDIM(a) != 1 ||
2187+
ARR_LBOUND(a)[0] != 1 ||
2188+
ARR_HASNULL(a) ||
2189+
ARR_ELEMTYPE(a) != TEXTOID)
2190+
elog(ERROR, "extcondition is not a 1-D text array");
2191+
if (ARR_DIMS(a)[0] != arrayLength)
2192+
elog(ERROR, "extconfig and extcondition arrays do not match");
21602193

2194+
/* Add or replace at same index as in extconfig */
21612195
a = array_set(a, 1, &arrayIndex,
21622196
elementDatum,
21632197
false,
@@ -2182,6 +2216,182 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
21822216
PG_RETURN_VOID();
21832217
}
21842218

2219+
/*
2220+
* extension_config_remove
2221+
*
2222+
* Remove the specified table OID from extension's extconfig, if present.
2223+
* This is not currently exposed as a function, but it could be;
2224+
* for now, we just invoke it from ALTER EXTENSION DROP.
2225+
*/
2226+
static void
2227+
extension_config_remove(Oid extensionoid, Oid tableoid)
2228+
{
2229+
Relation extRel;
2230+
ScanKeyData key[1];
2231+
SysScanDesc extScan;
2232+
HeapTuple extTup;
2233+
Datum arrayDatum;
2234+
int arrayLength;
2235+
int arrayIndex;
2236+
bool isnull;
2237+
Datum repl_val[Natts_pg_extension];
2238+
bool repl_null[Natts_pg_extension];
2239+
bool repl_repl[Natts_pg_extension];
2240+
ArrayType *a;
2241+
2242+
/* Find the pg_extension tuple */
2243+
extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
2244+
2245+
ScanKeyInit(&key[0],
2246+
ObjectIdAttributeNumber,
2247+
BTEqualStrategyNumber, F_OIDEQ,
2248+
ObjectIdGetDatum(extensionoid));
2249+
2250+
extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
2251+
SnapshotNow, 1, key);
2252+
2253+
extTup = systable_getnext(extScan);
2254+
2255+
if (!HeapTupleIsValid(extTup)) /* should not happen */
2256+
elog(ERROR, "extension with oid %u does not exist",
2257+
extensionoid);
2258+
2259+
/* Search extconfig for the tableoid */
2260+
arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig,
2261+
RelationGetDescr(extRel), &isnull);
2262+
if (isnull)
2263+
{
2264+
/* nothing to do */
2265+
a = NULL;
2266+
arrayLength = 0;
2267+
arrayIndex = -1;
2268+
}
2269+
else
2270+
{
2271+
Oid *arrayData;
2272+
int i;
2273+
2274+
a = DatumGetArrayTypeP(arrayDatum);
2275+
2276+
arrayLength = ARR_DIMS(a)[0];
2277+
if (ARR_NDIM(a) != 1 ||
2278+
ARR_LBOUND(a)[0] != 1 ||
2279+
arrayLength < 0 ||
2280+
ARR_HASNULL(a) ||
2281+
ARR_ELEMTYPE(a) != OIDOID)
2282+
elog(ERROR, "extconfig is not a 1-D Oid array");
2283+
arrayData = (Oid *) ARR_DATA_PTR(a);
2284+
2285+
arrayIndex = -1; /* flag for no deletion needed */
2286+
2287+
for (i = 0; i < arrayLength; i++)
2288+
{
2289+
if (arrayData[i] == tableoid)
2290+
{
2291+
arrayIndex = i; /* index to remove */
2292+
break;
2293+
}
2294+
}
2295+
}
2296+
2297+
/* If tableoid is not in extconfig, nothing to do */
2298+
if (arrayIndex < 0)
2299+
{
2300+
systable_endscan(extScan);
2301+
heap_close(extRel, RowExclusiveLock);
2302+
return;
2303+
}
2304+
2305+
/* Modify or delete the extconfig value */
2306+
memset(repl_val, 0, sizeof(repl_val));
2307+
memset(repl_null, false, sizeof(repl_null));
2308+
memset(repl_repl, false, sizeof(repl_repl));
2309+
2310+
if (arrayLength <= 1)
2311+
{
2312+
/* removing only element, just set array to null */
2313+
repl_null[Anum_pg_extension_extconfig - 1] = true;
2314+
}
2315+
else
2316+
{
2317+
/* squeeze out the target element */
2318+
Datum *dvalues;
2319+
bool *dnulls;
2320+
int nelems;
2321+
int i;
2322+
2323+
deconstruct_array(a, OIDOID, sizeof(Oid), true, 'i',
2324+
&dvalues, &dnulls, &nelems);
2325+
2326+
/* We already checked there are no nulls, so ignore dnulls */
2327+
for (i = arrayIndex; i < arrayLength - 1; i++)
2328+
dvalues[i] = dvalues[i + 1];
2329+
2330+
a = construct_array(dvalues, arrayLength - 1,
2331+
OIDOID, sizeof(Oid), true, 'i');
2332+
2333+
repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a);
2334+
}
2335+
repl_repl[Anum_pg_extension_extconfig - 1] = true;
2336+
2337+
/* Modify or delete the extcondition value */
2338+
arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition,
2339+
RelationGetDescr(extRel), &isnull);
2340+
if (isnull)
2341+
{
2342+
elog(ERROR, "extconfig and extcondition arrays do not match");
2343+
}
2344+
else
2345+
{
2346+
a = DatumGetArrayTypeP(arrayDatum);
2347+
2348+
if (ARR_NDIM(a) != 1 ||
2349+
ARR_LBOUND(a)[0] != 1 ||
2350+
ARR_HASNULL(a) ||
2351+
ARR_ELEMTYPE(a) != TEXTOID)
2352+
elog(ERROR, "extcondition is not a 1-D text array");
2353+
if (ARR_DIMS(a)[0] != arrayLength)
2354+
elog(ERROR, "extconfig and extcondition arrays do not match");
2355+
}
2356+
2357+
if (arrayLength <= 1)
2358+
{
2359+
/* removing only element, just set array to null */
2360+
repl_null[Anum_pg_extension_extcondition - 1] = true;
2361+
}
2362+
else
2363+
{
2364+
/* squeeze out the target element */
2365+
Datum *dvalues;
2366+
bool *dnulls;
2367+
int nelems;
2368+
int i;
2369+
2370+
deconstruct_array(a, TEXTOID, -1, false, 'i',
2371+
&dvalues, &dnulls, &nelems);
2372+
2373+
/* We already checked there are no nulls, so ignore dnulls */
2374+
for (i = arrayIndex; i < arrayLength - 1; i++)
2375+
dvalues[i] = dvalues[i + 1];
2376+
2377+
a = construct_array(dvalues, arrayLength - 1,
2378+
TEXTOID, -1, false, 'i');
2379+
2380+
repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a);
2381+
}
2382+
repl_repl[Anum_pg_extension_extcondition - 1] = true;
2383+
2384+
extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
2385+
repl_val, repl_null, repl_repl);
2386+
2387+
simple_heap_update(extRel, &extTup->t_self, extTup);
2388+
CatalogUpdateIndexes(extRel, extTup);
2389+
2390+
systable_endscan(extScan);
2391+
2392+
heap_close(extRel, RowExclusiveLock);
2393+
}
2394+
21852395
/*
21862396
* Execute ALTER EXTENSION SET SCHEMA
21872397
*/
@@ -2742,6 +2952,13 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt)
27422952
ExtensionRelationId,
27432953
DEPENDENCY_EXTENSION) != 1)
27442954
elog(ERROR, "unexpected number of extension dependency records");
2955+
2956+
/*
2957+
* If it's a relation, it might have an entry in the extension's
2958+
* extconfig array, which we must remove.
2959+
*/
2960+
if (object.classId == RelationRelationId)
2961+
extension_config_remove(extension.objectId, object.objectId);
27452962
}
27462963

27472964
/*

0 commit comments

Comments
 (0)