@ -152,7 +152,9 @@ static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
*
* When called on partitioned tables , this function recurses to create the
* trigger on all the partitions , except if isInternal is true , in which
* case caller is expected to execute recursion on its own .
* case caller is expected to execute recursion on its own . in_partition
* indicates such a recursive call ; outside callers should pass " false "
* ( but see CloneRowTriggersToPartition ) .
*/
ObjectAddress
CreateTrigger ( CreateTrigStmt * stmt , const char * queryString ,
@ -171,12 +173,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Relation rel ;
AclResult aclresult ;
Relation tgrel ;
SysScanDesc tgscan ;
ScanKeyData key ;
Relation pgrel ;
HeapTuple tuple ;
HeapTuple tuple = NULL ;
Oid funcrettype ;
Oid trigoid ;
Oid trigoid = InvalidOid ;
char internaltrigname [ NAMEDATALEN ] ;
char * trigname ;
Oid constrrelid = InvalidOid ;
@ -185,6 +185,9 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
char * oldtablename = NULL ;
char * newtablename = NULL ;
bool partition_recurse ;
bool trigger_exists = false ;
Oid existing_constraint_oid = InvalidOid ;
bool existing_isInternal = false ;
if ( OidIsValid ( relOid ) )
rel = table_open ( relOid , ShareRowExclusiveLock ) ;
@ -688,6 +691,100 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg ( " function %s must return type %s " ,
NameListToString ( stmt - > funcname ) , " trigger " ) ) ) ;
/*
* Scan pg_trigger to see if there is already a trigger of the same name .
* Skip this for internally generated triggers , since we ' ll modify the
* name to be unique below .
*
* NOTE that this is cool only because we have ShareRowExclusiveLock on
* the relation , so the trigger set won ' t be changing underneath us .
*/
tgrel = table_open ( TriggerRelationId , RowExclusiveLock ) ;
if ( ! isInternal )
{
ScanKeyData skeys [ 2 ] ;
SysScanDesc tgscan ;
ScanKeyInit ( & skeys [ 0 ] ,
Anum_pg_trigger_tgrelid ,
BTEqualStrategyNumber , F_OIDEQ ,
ObjectIdGetDatum ( RelationGetRelid ( rel ) ) ) ;
ScanKeyInit ( & skeys [ 1 ] ,
Anum_pg_trigger_tgname ,
BTEqualStrategyNumber , F_NAMEEQ ,
CStringGetDatum ( stmt - > trigname ) ) ;
tgscan = systable_beginscan ( tgrel , TriggerRelidNameIndexId , true ,
NULL , 2 , skeys ) ;
/* There should be at most one matching tuple */
if ( HeapTupleIsValid ( tuple = systable_getnext ( tgscan ) ) )
{
Form_pg_trigger oldtrigger = ( Form_pg_trigger ) GETSTRUCT ( tuple ) ;
trigoid = oldtrigger - > oid ;
existing_constraint_oid = oldtrigger - > tgconstraint ;
existing_isInternal = oldtrigger - > tgisinternal ;
trigger_exists = true ;
/* copy the tuple to use in CatalogTupleUpdate() */
tuple = heap_copytuple ( tuple ) ;
}
systable_endscan ( tgscan ) ;
}
if ( ! trigger_exists )
{
/* Generate the OID for the new trigger. */
trigoid = GetNewOidWithIndex ( tgrel , TriggerOidIndexId ,
Anum_pg_trigger_oid ) ;
}
else
{
/*
* If OR REPLACE was specified , we ' ll replace the old trigger ;
* otherwise complain about the duplicate name .
*/
if ( ! stmt - > replace )
ereport ( ERROR ,
( errcode ( ERRCODE_DUPLICATE_OBJECT ) ,
errmsg ( " trigger \" %s \" for relation \" %s \" already exists " ,
stmt - > trigname , RelationGetRelationName ( rel ) ) ) ) ;
/*
* An internal trigger cannot be replaced by a user - defined trigger .
* However , skip this test when in_partition , because then we ' re
* recursing from a partitioned table and the check was made at the
* parent level . Child triggers will always be marked " internal " ( so
* this test does protect us from the user trying to replace a child
* trigger directly ) .
*/
if ( existing_isInternal & & ! isInternal & & ! in_partition )
ereport ( ERROR ,
( errcode ( ERRCODE_DUPLICATE_OBJECT ) ,
errmsg ( " trigger \" %s \" for relation \" %s \" is an internal trigger " ,
stmt - > trigname , RelationGetRelationName ( rel ) ) ) ) ;
/*
* It is not allowed to replace with a constraint trigger ; gram . y
* should have enforced this already .
*/
Assert ( ! stmt - > isconstraint ) ;
/*
* It is not allowed to replace an existing constraint trigger ,
* either . ( The reason for these restrictions is partly that it seems
* difficult to deal with pending trigger events in such cases , and
* partly that the command might imply changing the constraint ' s
* properties as well , which doesn ' t seem nice . )
*/
if ( OidIsValid ( existing_constraint_oid ) )
ereport ( ERROR ,
( errcode ( ERRCODE_DUPLICATE_OBJECT ) ,
errmsg ( " trigger \" %s \" for relation \" %s \" is a constraint trigger " ,
stmt - > trigname , RelationGetRelationName ( rel ) ) ) ) ;
}
/*
* If it ' s a user - entered CREATE CONSTRAINT TRIGGER command , make a
* corresponding pg_constraint entry .
@ -727,15 +824,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
isInternal ) ; /* is_internal */
}
/*
* Generate the trigger ' s OID now , so that we can use it in the name if
* needed .
*/
tgrel = table_open ( TriggerRelationId , RowExclusiveLock ) ;
trigoid = GetNewOidWithIndex ( tgrel , TriggerOidIndexId ,
Anum_pg_trigger_oid ) ;
/*
* If trigger is internally generated , modify the provided trigger name to
* ensure uniqueness by appending the trigger OID . ( Callers will usually
@ -753,37 +841,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
trigname = stmt - > trigname ;
}
/*
* Scan pg_trigger for existing triggers on relation . We do this only to
* give a nice error message if there ' s already a trigger of the same
* name . ( The unique index on tgrelid / tgname would complain anyway . ) We
* can skip this for internally generated triggers , since the name
* modification above should be sufficient .
*
* NOTE that this is cool only because we have ShareRowExclusiveLock on
* the relation , so the trigger set won ' t be changing underneath us .
*/
if ( ! isInternal )
{
ScanKeyInit ( & key ,
Anum_pg_trigger_tgrelid ,
BTEqualStrategyNumber , F_OIDEQ ,
ObjectIdGetDatum ( RelationGetRelid ( rel ) ) ) ;
tgscan = systable_beginscan ( tgrel , TriggerRelidNameIndexId , true ,
NULL , 1 , & key ) ;
while ( HeapTupleIsValid ( tuple = systable_getnext ( tgscan ) ) )
{
Form_pg_trigger pg_trigger = ( Form_pg_trigger ) GETSTRUCT ( tuple ) ;
if ( namestrcmp ( & ( pg_trigger - > tgname ) , trigname ) = = 0 )
ereport ( ERROR ,
( errcode ( ERRCODE_DUPLICATE_OBJECT ) ,
errmsg ( " trigger \" %s \" for relation \" %s \" already exists " ,
trigname , RelationGetRelationName ( rel ) ) ) ) ;
}
systable_endscan ( tgscan ) ;
}
/*
* Build the new pg_trigger tuple .
*
@ -910,14 +967,24 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
else
nulls [ Anum_pg_trigger_tgnewtable - 1 ] = true ;
tuple = heap_form_tuple ( tgrel - > rd_att , values , nulls ) ;
/*
* Insert tuple into pg_trigger .
* Insert or replace tuple in pg_trigger .
*/
CatalogTupleInsert ( tgrel , tuple ) ;
if ( ! trigger_exists )
{
tuple = heap_form_tuple ( tgrel - > rd_att , values , nulls ) ;
CatalogTupleInsert ( tgrel , tuple ) ;
}
else
{
HeapTuple newtup ;
heap_freetuple ( tuple ) ;
newtup = heap_form_tuple ( tgrel - > rd_att , values , nulls ) ;
CatalogTupleUpdate ( tgrel , & tuple - > t_self , newtup ) ;
heap_freetuple ( newtup ) ;
}
heap_freetuple ( tuple ) ; /* free either original or new tuple */
table_close ( tgrel , RowExclusiveLock ) ;
pfree ( DatumGetPointer ( values [ Anum_pg_trigger_tgname - 1 ] ) ) ;
@ -952,6 +1019,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
heap_freetuple ( tuple ) ;
table_close ( pgrel , RowExclusiveLock ) ;
/*
* If we ' re replacing a trigger , flush all the old dependencies before
* recording new ones .
*/
if ( trigger_exists )
deleteDependencyRecordsFor ( TriggerRelationId , trigoid , true ) ;
/*
* Record dependencies for trigger . Always place a normal dependency on
* the function .