mirror of https://github.com/citusdata/citus.git
Disallow dropping type cascade which is used in partition column
parent
402a30a2b7
commit
47669fb14f
|
@ -1134,7 +1134,7 @@ static DistributeObjectOps View_AlterView = {
|
||||||
static DistributeObjectOps Type_Drop = {
|
static DistributeObjectOps Type_Drop = {
|
||||||
.deparse = DeparseDropTypeStmt,
|
.deparse = DeparseDropTypeStmt,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
.preprocess = PreprocessDropDistributedObjectStmt,
|
.preprocess = PreprocessDropTypeStmt,
|
||||||
.postprocess = NULL,
|
.postprocess = NULL,
|
||||||
.operationType = DIST_OPS_DROP,
|
.operationType = DIST_OPS_DROP,
|
||||||
.address = NULL,
|
.address = NULL,
|
||||||
|
|
|
@ -101,6 +101,7 @@ static CompositeTypeStmt * RecreateCompositeTypeStmt(Oid typeOid);
|
||||||
static List * CompositeTypeColumnDefList(Oid typeOid);
|
static List * CompositeTypeColumnDefList(Oid typeOid);
|
||||||
static CreateEnumStmt * RecreateEnumStmt(Oid typeOid);
|
static CreateEnumStmt * RecreateEnumStmt(Oid typeOid);
|
||||||
static List * EnumValsList(Oid typeOid);
|
static List * EnumValsList(Oid typeOid);
|
||||||
|
static ObjectAddress * typeDependentDistributionColumn(Oid typeId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PreprocessRenameTypeAttributeStmt is called for changes of attribute names for composite
|
* 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
|
* CreateTypeStmtByObjectAddress returns a parsetree for the CREATE TYPE statement to
|
||||||
* recreate the type by its object address.
|
* recreate the type by its object address.
|
||||||
|
@ -669,3 +727,57 @@ LookupNonAssociatedArrayTypeNameOid(ParseState *pstate, const TypeName *typeName
|
||||||
|
|
||||||
return typeOid;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -607,6 +607,8 @@ extern void PreprocessTruncateStatement(TruncateStmt *truncateStatement);
|
||||||
extern List * PreprocessRenameTypeAttributeStmt(Node *stmt, const char *queryString,
|
extern List * PreprocessRenameTypeAttributeStmt(Node *stmt, const char *queryString,
|
||||||
ProcessUtilityContext
|
ProcessUtilityContext
|
||||||
processUtilityContext);
|
processUtilityContext);
|
||||||
|
extern List * PreprocessDropTypeStmt(Node *stmt, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext);
|
||||||
extern Node * CreateTypeStmtByObjectAddress(const ObjectAddress *address);
|
extern Node * CreateTypeStmtByObjectAddress(const ObjectAddress *address);
|
||||||
extern List * CompositeTypeStmtObjectAddress(Node *stmt, bool missing_ok, bool
|
extern List * CompositeTypeStmtObjectAddress(Node *stmt, bool missing_ok, bool
|
||||||
isPostprocess);
|
isPostprocess);
|
||||||
|
|
|
@ -578,6 +578,68 @@ DETAIL: "type temp_type" will be created only locally
|
||||||
CREATE TYPE pg_temp.temp_enum AS ENUM ('one', 'two', 'three');
|
CREATE TYPE pg_temp.temp_enum AS ENUM ('one', 'two', 'three');
|
||||||
WARNING: "type temp_enum" has dependency on unsupported object "schema pg_temp_xxx"
|
WARNING: "type temp_enum" has dependency on unsupported object "schema pg_temp_xxx"
|
||||||
DETAIL: "type temp_enum" will be created only locally
|
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
|
-- clear objects
|
||||||
SET client_min_messages TO error; -- suppress cascading objects dropping
|
SET client_min_messages TO error; -- suppress cascading objects dropping
|
||||||
DROP SCHEMA type_tests CASCADE;
|
DROP SCHEMA type_tests CASCADE;
|
||||||
|
|
|
@ -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_type AS (int_field int);
|
||||||
CREATE TYPE pg_temp.temp_enum AS ENUM ('one', 'two', 'three');
|
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
|
-- clear objects
|
||||||
SET client_min_messages TO error; -- suppress cascading objects dropping
|
SET client_min_messages TO error; -- suppress cascading objects dropping
|
||||||
DROP SCHEMA type_tests CASCADE;
|
DROP SCHEMA type_tests CASCADE;
|
||||||
|
|
Loading…
Reference in New Issue