mirror of
https://github.com/postgres/postgres.git
synced 2025-09-08 00:47:37 +03:00
Use the right memory context for partkey's FmgrInfo
We were using CurrentMemoryContext to put the partsupfunc fmgr_info into, which isn't right, because we want the PartitionKey as a whole to be in the isolated Relation->rd_partkeycxt context. This can cause a crash with user-defined support functions in the operator classes used by partitioning keys. (Maybe this can cause problems with core-supplied opclasses too, not sure.) This is demonstrably broken in Postgres 10, too, but the initial proposed fix runs afoul of a problem discussed back when8a0596cb65
("Get rid of copy_partition_key") reorganized that code: namely that it is possible to jump out of RelationBuildPartitionKey because of some error and leave a dangling memory context child of CacheMemoryContext. Also, while reviewing this I noticed that the removed-in-pg11 copy_partition_key was doing something wrong, unfixed in pg10, namely doing memcpy() on the FmgrInfo, which is bogus (should be doing fmgr_info_copy). Therefore, in branch pg10, the sane fix seems to be to backpatch both the aforementioned8a0596cb65
and its followupbe2343221f
("Protect against hypothetical memory leaks in RelationGetPartitionKey"), so do that, then apply the fmgr_info memcxt bugfix on top. Add a test case exercising btree-based custom operator classes, which causes a crash prior to this fix. This is not a security problem, because in order to create an operator class you need superuser privileges anyway. Authors: Álvaro Herrera and Amit Langote Reported and diagnosed by: Amit Langote Discussion: https://postgr.es/m/3041e853-b1dd-a0c6-ff21-7cc5633bffd0@lab.ntt.co.jp
This commit is contained in:
109
src/backend/utils/cache/relcache.c
vendored
109
src/backend/utils/cache/relcache.c
vendored
@@ -266,7 +266,6 @@ static Relation AllocateRelationDesc(Form_pg_class relp);
|
|||||||
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
|
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
|
||||||
static void RelationBuildTupleDesc(Relation relation);
|
static void RelationBuildTupleDesc(Relation relation);
|
||||||
static void RelationBuildPartitionKey(Relation relation);
|
static void RelationBuildPartitionKey(Relation relation);
|
||||||
static PartitionKey copy_partition_key(PartitionKey fromkey);
|
|
||||||
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
|
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
|
||||||
static void RelationInitPhysicalAddr(Relation relation);
|
static void RelationInitPhysicalAddr(Relation relation);
|
||||||
static void load_critical_index(Oid indexoid, Oid heapoid);
|
static void load_critical_index(Oid indexoid, Oid heapoid);
|
||||||
@@ -811,17 +810,16 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
* RelationBuildPartitionKey
|
* RelationBuildPartitionKey
|
||||||
* Build and attach to relcache partition key data of relation
|
* Build and attach to relcache partition key data of relation
|
||||||
*
|
*
|
||||||
* Partitioning key data is stored in CacheMemoryContext to ensure it survives
|
* Partitioning key data is a complex structure; to avoid complicated logic to
|
||||||
* as long as the relcache. To avoid leaking memory in that context in case
|
* free individual elements whenever the relcache entry is flushed, we give it
|
||||||
* of an error partway through this function, we build the structure in the
|
* its own memory context, child of CacheMemoryContext, which can easily be
|
||||||
* working context (which must be short-lived) and copy the completed
|
* deleted on its own. To avoid leaking memory in that context in case of an
|
||||||
* structure into the cache memory.
|
* error partway through this function, the context is initially created as a
|
||||||
*
|
* child of CurTransactionContext and only re-parented to CacheMemoryContext
|
||||||
* Also, since the structure being created here is sufficiently complex, we
|
* at the end, when no further errors are possible. Also, we don't make this
|
||||||
* make a private child context of CacheMemoryContext for each relation that
|
* context the current context except in very brief code sections, out of fear
|
||||||
* has associated partition key information. That means no complicated logic
|
* that some of our callees allocate memory on their own which would be leaked
|
||||||
* to free individual elements whenever the relcache entry is flushed - just
|
* permanently.
|
||||||
* delete the context.
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
RelationBuildPartitionKey(Relation relation)
|
RelationBuildPartitionKey(Relation relation)
|
||||||
@@ -849,7 +847,12 @@ RelationBuildPartitionKey(Relation relation)
|
|||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
|
partkeycxt = AllocSetContextCreate(CurTransactionContext,
|
||||||
|
RelationGetRelationName(relation),
|
||||||
|
ALLOCSET_SMALL_SIZES);
|
||||||
|
|
||||||
|
key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
|
||||||
|
sizeof(PartitionKeyData));
|
||||||
|
|
||||||
/* Fixed-length attributes */
|
/* Fixed-length attributes */
|
||||||
form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
|
form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
|
||||||
@@ -896,13 +899,15 @@ RelationBuildPartitionKey(Relation relation)
|
|||||||
* expressions should be in canonical form already (ie, no need for
|
* expressions should be in canonical form already (ie, no need for
|
||||||
* OR-merging or constant elimination).
|
* OR-merging or constant elimination).
|
||||||
*/
|
*/
|
||||||
expr = eval_const_expressions(NULL, (Node *) expr);
|
expr = eval_const_expressions(NULL, expr);
|
||||||
|
fix_opfuncids(expr);
|
||||||
|
|
||||||
/* May as well fix opfuncids too */
|
oldcxt = MemoryContextSwitchTo(partkeycxt);
|
||||||
fix_opfuncids((Node *) expr);
|
key->partexprs = (List *) copyObject(expr);
|
||||||
key->partexprs = (List *) expr;
|
MemoryContextSwitchTo(oldcxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldcxt = MemoryContextSwitchTo(partkeycxt);
|
||||||
key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
|
key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
|
||||||
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||||
key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||||
@@ -917,6 +922,7 @@ RelationBuildPartitionKey(Relation relation)
|
|||||||
key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
|
key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
|
||||||
key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
|
key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
|
||||||
key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||||
|
MemoryContextSwitchTo(oldcxt);
|
||||||
|
|
||||||
/* Copy partattrs and fill other per-attribute info */
|
/* Copy partattrs and fill other per-attribute info */
|
||||||
memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
|
memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
|
||||||
@@ -951,7 +957,7 @@ RelationBuildPartitionKey(Relation relation)
|
|||||||
BTORDER_PROC, opclassform->opcintype, opclassform->opcintype,
|
BTORDER_PROC, opclassform->opcintype, opclassform->opcintype,
|
||||||
opclassform->opcfamily);
|
opclassform->opcfamily);
|
||||||
|
|
||||||
fmgr_info(funcid, &key->partsupfunc[i]);
|
fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
|
||||||
|
|
||||||
/* Collation */
|
/* Collation */
|
||||||
key->partcollation[i] = collation->values[i];
|
key->partcollation[i] = collation->values[i];
|
||||||
@@ -984,68 +990,13 @@ RelationBuildPartitionKey(Relation relation)
|
|||||||
|
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
/* Success --- now copy to the cache memory */
|
/*
|
||||||
partkeycxt = AllocSetContextCreate(CacheMemoryContext,
|
* Success --- reparent our context and make the relcache point to the
|
||||||
RelationGetRelationName(relation),
|
* newly constructed key
|
||||||
ALLOCSET_SMALL_SIZES);
|
*/
|
||||||
|
MemoryContextSetParent(partkeycxt, CacheMemoryContext);
|
||||||
relation->rd_partkeycxt = partkeycxt;
|
relation->rd_partkeycxt = partkeycxt;
|
||||||
oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
|
relation->rd_partkey = key;
|
||||||
relation->rd_partkey = copy_partition_key(key);
|
|
||||||
MemoryContextSwitchTo(oldcxt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* copy_partition_key
|
|
||||||
*
|
|
||||||
* The copy is allocated in the current memory context.
|
|
||||||
*/
|
|
||||||
static PartitionKey
|
|
||||||
copy_partition_key(PartitionKey fromkey)
|
|
||||||
{
|
|
||||||
PartitionKey newkey;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
|
|
||||||
|
|
||||||
newkey->strategy = fromkey->strategy;
|
|
||||||
newkey->partnatts = n = fromkey->partnatts;
|
|
||||||
|
|
||||||
newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
|
|
||||||
memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
|
|
||||||
|
|
||||||
newkey->partexprs = copyObject(fromkey->partexprs);
|
|
||||||
|
|
||||||
newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
|
|
||||||
memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
|
|
||||||
|
|
||||||
newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
|
|
||||||
memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
|
|
||||||
|
|
||||||
newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
|
|
||||||
memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
|
|
||||||
|
|
||||||
newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
|
|
||||||
memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
|
|
||||||
|
|
||||||
newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
|
|
||||||
memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
|
|
||||||
|
|
||||||
newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
|
|
||||||
memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
|
|
||||||
|
|
||||||
newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
|
|
||||||
memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
|
|
||||||
|
|
||||||
newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
|
|
||||||
memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
|
|
||||||
|
|
||||||
newkey->parttypalign = (char *) palloc(n * sizeof(bool));
|
|
||||||
memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
|
|
||||||
|
|
||||||
newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
|
|
||||||
memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
|
|
||||||
|
|
||||||
return newkey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -290,7 +290,7 @@ typedef HashMetaPageData *HashMetaPage;
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* When a new operator class is declared, we require that the user supply
|
* When a new operator class is declared, we require that the user supply
|
||||||
* us with an amproc procudure for hashing a key of the new type.
|
* us with an amproc procedure for hashing a key of the new type.
|
||||||
* Since we only have one such proc in amproc, it's number 1.
|
* Since we only have one such proc in amproc, it's number 1.
|
||||||
*/
|
*/
|
||||||
#define HASHPROC 1
|
#define HASHPROC 1
|
||||||
|
@@ -764,8 +764,22 @@ Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MA
|
|||||||
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
|
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
|
||||||
|
|
||||||
DROP TABLE range_parted4;
|
DROP TABLE range_parted4;
|
||||||
|
-- user-defined operator class in partition key
|
||||||
|
CREATE FUNCTION my_int4_sort(int4,int4) RETURNS int LANGUAGE sql
|
||||||
|
AS $$ SELECT case WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; $$;
|
||||||
|
CREATE OPERATOR CLASS test_int4_ops FOR TYPE int4 USING btree AS
|
||||||
|
OPERATOR 1 < (int4,int4), OPERATOR 2 <= (int4,int4),
|
||||||
|
OPERATOR 3 = (int4,int4), OPERATOR 4 >= (int4,int4),
|
||||||
|
OPERATOR 5 > (int4,int4), FUNCTION 1 my_int4_sort(int4,int4);
|
||||||
|
CREATE TABLE partkey_t (a int4) PARTITION BY RANGE (a test_int4_ops);
|
||||||
|
CREATE TABLE partkey_t_1 PARTITION OF partkey_t FOR VALUES FROM (0) TO (1000);
|
||||||
|
INSERT INTO partkey_t VALUES (100);
|
||||||
|
INSERT INTO partkey_t VALUES (200);
|
||||||
-- cleanup
|
-- cleanup
|
||||||
DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
|
DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
|
||||||
|
DROP TABLE partkey_t;
|
||||||
|
DROP OPERATOR CLASS test_int4_ops USING btree;
|
||||||
|
DROP FUNCTION my_int4_sort(int4,int4);
|
||||||
-- comments on partitioned tables columns
|
-- comments on partitioned tables columns
|
||||||
CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
|
CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
|
||||||
COMMENT ON TABLE parted_col_comment IS 'Am partitioned table';
|
COMMENT ON TABLE parted_col_comment IS 'Am partitioned table';
|
||||||
|
@@ -637,8 +637,23 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
|
|||||||
\d+ range_parted4_3
|
\d+ range_parted4_3
|
||||||
DROP TABLE range_parted4;
|
DROP TABLE range_parted4;
|
||||||
|
|
||||||
|
-- user-defined operator class in partition key
|
||||||
|
CREATE FUNCTION my_int4_sort(int4,int4) RETURNS int LANGUAGE sql
|
||||||
|
AS $$ SELECT case WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; $$;
|
||||||
|
CREATE OPERATOR CLASS test_int4_ops FOR TYPE int4 USING btree AS
|
||||||
|
OPERATOR 1 < (int4,int4), OPERATOR 2 <= (int4,int4),
|
||||||
|
OPERATOR 3 = (int4,int4), OPERATOR 4 >= (int4,int4),
|
||||||
|
OPERATOR 5 > (int4,int4), FUNCTION 1 my_int4_sort(int4,int4);
|
||||||
|
CREATE TABLE partkey_t (a int4) PARTITION BY RANGE (a test_int4_ops);
|
||||||
|
CREATE TABLE partkey_t_1 PARTITION OF partkey_t FOR VALUES FROM (0) TO (1000);
|
||||||
|
INSERT INTO partkey_t VALUES (100);
|
||||||
|
INSERT INTO partkey_t VALUES (200);
|
||||||
|
|
||||||
-- cleanup
|
-- cleanup
|
||||||
DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
|
DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3;
|
||||||
|
DROP TABLE partkey_t;
|
||||||
|
DROP OPERATOR CLASS test_int4_ops USING btree;
|
||||||
|
DROP FUNCTION my_int4_sort(int4,int4);
|
||||||
|
|
||||||
-- comments on partitioned tables columns
|
-- comments on partitioned tables columns
|
||||||
CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
|
CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
|
||||||
|
Reference in New Issue
Block a user