Disallow dropping type cascade which is used in partition column

pull/6393/head
naisila 2022-11-02 13:29:44 +03:00
parent 402a30a2b7
commit 47669fb14f
5 changed files with 226 additions and 1 deletions

View File

@ -1134,7 +1134,7 @@ static DistributeObjectOps View_AlterView = {
static DistributeObjectOps Type_Drop = {
.deparse = DeparseDropTypeStmt,
.qualify = NULL,
.preprocess = PreprocessDropDistributedObjectStmt,
.preprocess = PreprocessDropTypeStmt,
.postprocess = NULL,
.operationType = DIST_OPS_DROP,
.address = NULL,

View File

@ -101,6 +101,7 @@ static CompositeTypeStmt * RecreateCompositeTypeStmt(Oid typeOid);
static List * CompositeTypeColumnDefList(Oid typeOid);
static CreateEnumStmt * RecreateEnumStmt(Oid typeOid);
static List * EnumValsList(Oid typeOid);
static ObjectAddress * typeDependentDistributionColumn(Oid typeId);
/*
* PreprocessRenameTypeAttributeStmt is called for changes of attribute names for composite
@ -142,6 +143,63 @@ PreprocessRenameTypeAttributeStmt(Node *node, const char *queryString,
}
/*
* PreprocessDropTypeStmt is called to check whether this is a
* DROP TYPE ... CASCADE statement. If yes, it checks whether there are any
* columns part of distributed tables included. If yes, it errors out.
* After that it calls the general PreprocessDropDistributedObjectStmt
*/
List *
PreprocessDropTypeStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_TYPE);
if (stmt->behavior == DROP_CASCADE)
{
ListCell *cell;
foreach(cell, stmt->objects)
{
Node *object = lfirst(cell);
Relation relation = NULL;
/* Get an ObjectAddress for the type. */
ObjectAddress typeAddress = get_object_address(OBJECT_TYPE,
object,
&relation,
AccessExclusiveLock,
stmt->missing_ok);
ObjectAddress *distTableAndColumn = typeDependentDistributionColumn(
typeAddress.objectId);
if (distTableAndColumn != NULL)
{
Oid distTableId = distTableAndColumn->objectId;
int32 attNum = distTableAndColumn->objectSubId;
HeapTuple attTuple = SearchSysCacheAttNum(distTableId, attNum);
Form_pg_attribute targetAttr = (Form_pg_attribute) GETSTRUCT(attTuple);
char *columnName = NameStr(targetAttr->attname);
ReleaseSysCache(attTuple);
TypeName *typeName = castNode(TypeName, object);
Oid typeOid = LookupTypeNameOid(NULL, typeName, false);
const char *identifier = format_type_be_qualified(typeOid);
ereport(ERROR,
(errmsg("Can't drop type \"%s\" "
"because it is used in the partition column \"%s\" "
"of Citus table \"%s\"",
identifier, columnName, get_rel_name(distTableId))));
}
}
}
return PreprocessDropDistributedObjectStmt(node, queryString, processUtilityContext);
}
/*
* CreateTypeStmtByObjectAddress returns a parsetree for the CREATE TYPE statement to
* recreate the type by its object address.
@ -669,3 +727,57 @@ LookupNonAssociatedArrayTypeNameOid(ParseState *pstate, const TypeName *typeName
return typeOid;
}
/*
* typeDependentDistributionColumn checks whether the given type has a dependency
* to a Citus table as the type of the table's distribution column
* If there exists at least one, it returns the first object seen in the list.
*/
static ObjectAddress *
typeDependentDistributionColumn(Oid typeId)
{
ObjectAddress *distTableAndColumn = NULL;
ScanKeyData key[2];
Relation depRel = table_open(DependRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(TypeRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(typeId));
SysScanDesc scan = systable_beginscan(depRel, DependReferenceIndexId, true,
NULL, 2, key);
HeapTuple tup;
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_depend pgDependEntry = (Form_pg_depend) GETSTRUCT(tup);
if (pgDependEntry->classid == RelationRelationId &&
get_rel_relkind(pgDependEntry->objid) == RELKIND_RELATION &&
IsCitusTable(pgDependEntry->objid))
{
Var *partitionColumn = DistPartitionKey(pgDependEntry->objid);
if (partitionColumn != NULL &&
partitionColumn->varattno == pgDependEntry->objsubid)
{
distTableAndColumn = palloc0(sizeof(ObjectAddress));
ObjectAddressSubSet(*distTableAndColumn, RelationRelationId,
pgDependEntry->objid, pgDependEntry->objsubid);
break;
}
}
}
systable_endscan(scan);
table_close(depRel, AccessShareLock);
return distTableAndColumn;
}

View File

@ -607,6 +607,8 @@ extern void PreprocessTruncateStatement(TruncateStmt *truncateStatement);
extern List * PreprocessRenameTypeAttributeStmt(Node *stmt, const char *queryString,
ProcessUtilityContext
processUtilityContext);
extern List * PreprocessDropTypeStmt(Node *stmt, const char *queryString,
ProcessUtilityContext processUtilityContext);
extern Node * CreateTypeStmtByObjectAddress(const ObjectAddress *address);
extern List * CompositeTypeStmtObjectAddress(Node *stmt, bool missing_ok, bool
isPostprocess);

View File

@ -578,6 +578,68 @@ DETAIL: "type temp_type" will be created only locally
CREATE TYPE pg_temp.temp_enum AS ENUM ('one', 'two', 'three');
WARNING: "type temp_enum" has dependency on unsupported object "schema pg_temp_xxx"
DETAIL: "type temp_enum" will be created only locally
-- verify that dropping a type which is used in the distribution column
-- of a distributed table fails
-- create a custom type...
CREATE TYPE my_type AS (
i integer,
i2 integer
);
CREATE FUNCTION equal_my_type_function(my_type, my_type) RETURNS boolean
LANGUAGE 'internal'
AS 'record_eq'
IMMUTABLE
RETURNS NULL ON NULL INPUT;
-- ... use that function to create a custom equality operator...
CREATE OPERATOR = (
LEFTARG = my_type,
RIGHTARG = my_type,
PROCEDURE = equal_my_type_function,
HASHES
);
-- ... create a test HASH function. Though it is a poor hash function,
-- it is acceptable for our tests
CREATE FUNCTION my_type_hash(my_type) RETURNS int
AS 'SELECT hashtext( ($1.i + $1.i2)::text);'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
-- ... and create a custom operator family for hash indexes...
CREATE OPERATOR FAMILY cats_op_fam USING hash;
-- We need to define a default operator classes for my_type
-- that uses HASH
CREATE OPERATOR CLASS cats_op_fam_class
DEFAULT FOR TYPE my_type USING HASH AS
OPERATOR 1 = (my_type, my_type),
FUNCTION 1 my_type_hash(my_type);
CREATE TABLE tbl (a my_type);
SELECT create_distributed_table('tbl', 'a');
create_distributed_table
---------------------------------------------------------------------
(1 row)
DROP TYPE my_type CASCADE;
ERROR: Can't drop type "type_tests.my_type" because it is used in the partition column "a" of Citus table "tbl"
ALTER TABLE tbl DROP COLUMN a;
ERROR: cannot execute ALTER TABLE command involving partition column
SELECT undistribute_table('tbl');
NOTICE: creating a new table for type_tests.tbl
NOTICE: moving the data of type_tests.tbl
NOTICE: dropping the old type_tests.tbl
NOTICE: renaming the new table to type_tests.tbl
undistribute_table
---------------------------------------------------------------------
(1 row)
DROP TYPE my_type CASCADE;
NOTICE: drop cascades to 5 other objects
DETAIL: drop cascades to function equal_my_type_function(my_type,my_type)
drop cascades to operator =(my_type,my_type)
drop cascades to function my_type_hash(my_type)
drop cascades to operator class cats_op_fam_class for access method hash
drop cascades to column a of table tbl
-- clear objects
SET client_min_messages TO error; -- suppress cascading objects dropping
DROP SCHEMA type_tests CASCADE;

View File

@ -350,6 +350,55 @@ SELECT create_distributed_table('table_text_local_def','id');
CREATE TYPE pg_temp.temp_type AS (int_field int);
CREATE TYPE pg_temp.temp_enum AS ENUM ('one', 'two', 'three');
-- verify that dropping a type which is used in the distribution column
-- of a distributed table fails
-- create a custom type...
CREATE TYPE my_type AS (
i integer,
i2 integer
);
CREATE FUNCTION equal_my_type_function(my_type, my_type) RETURNS boolean
LANGUAGE 'internal'
AS 'record_eq'
IMMUTABLE
RETURNS NULL ON NULL INPUT;
-- ... use that function to create a custom equality operator...
CREATE OPERATOR = (
LEFTARG = my_type,
RIGHTARG = my_type,
PROCEDURE = equal_my_type_function,
HASHES
);
-- ... create a test HASH function. Though it is a poor hash function,
-- it is acceptable for our tests
CREATE FUNCTION my_type_hash(my_type) RETURNS int
AS 'SELECT hashtext( ($1.i + $1.i2)::text);'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
-- ... and create a custom operator family for hash indexes...
CREATE OPERATOR FAMILY cats_op_fam USING hash;
-- We need to define a default operator classes for my_type
-- that uses HASH
CREATE OPERATOR CLASS cats_op_fam_class
DEFAULT FOR TYPE my_type USING HASH AS
OPERATOR 1 = (my_type, my_type),
FUNCTION 1 my_type_hash(my_type);
CREATE TABLE tbl (a my_type);
SELECT create_distributed_table('tbl', 'a');
DROP TYPE my_type CASCADE;
ALTER TABLE tbl DROP COLUMN a;
SELECT undistribute_table('tbl');
DROP TYPE my_type CASCADE;
-- clear objects
SET client_min_messages TO error; -- suppress cascading objects dropping
DROP SCHEMA type_tests CASCADE;