1717 *
1818 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
1919 *
20- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.39 2002/06/21 02:59:38 momjian Exp $
20+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.40 2002/07/30 16:33:21 momjian Exp $
2121 *
2222 * ----------
2323 */
@@ -130,19 +130,24 @@ static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
130130 int32 constr_queryno ,
131131 Relation fk_rel , Relation pk_rel ,
132132 int argc , char * * argv );
133+ static void ri_BuildQueryKeyPkCheck (RI_QueryKey * key , Oid constr_id ,
134+ int32 constr_queryno ,
135+ Relation pk_rel ,
136+ int argc , char * * argv );
133137static bool ri_KeysEqual (Relation rel , HeapTuple oldtup , HeapTuple newtup ,
134138 RI_QueryKey * key , int pairidx );
135139static bool ri_AllKeysUnequal (Relation rel , HeapTuple oldtup , HeapTuple newtup ,
136140 RI_QueryKey * key , int pairidx );
137141static bool ri_OneKeyEqual (Relation rel , int column , HeapTuple oldtup ,
138142 HeapTuple newtup , RI_QueryKey * key , int pairidx );
139143static bool ri_AttributesEqual (Oid typeid , Datum oldvalue , Datum newvalue );
144+ static bool ri_Check_Pk_Match (Relation pk_rel , HeapTuple old_row ,
145+ Oid tgoid , int match_type , int tgnargs , char * * tgargs );
140146
141147static void ri_InitHashTables (void );
142148static void * ri_FetchPreparedPlan (RI_QueryKey * key );
143149static void ri_HashPreparedPlan (RI_QueryKey * key , void * plan );
144150
145-
146151/* ----------
147152 * RI_FKey_check -
148153 *
@@ -385,6 +390,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
385390 if (SPI_connect () != SPI_OK_CONNECT )
386391 elog (WARNING , "SPI_connect() failed in RI_FKey_check()" );
387392
393+
388394 /*
389395 * Fetch or prepare a saved plan for the real check
390396 */
@@ -512,6 +518,161 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
512518}
513519
514520
521+ /* ----------
522+ * ri_Check_Pk_Match
523+ *
524+ * Check for matching value of old pk row in current state for
525+ * noaction triggers. Returns false if no row was found and a fk row
526+ * could potentially be referencing this row, true otherwise.
527+ * ----------
528+ */
529+ static bool
530+ ri_Check_Pk_Match (Relation pk_rel , HeapTuple old_row , Oid tgoid , int match_type , int tgnargs , char * * tgargs ) {
531+ void * qplan ;
532+ RI_QueryKey qkey ;
533+ bool isnull ;
534+ Datum check_values [RI_MAX_NUMKEYS ];
535+ char check_nulls [RI_MAX_NUMKEYS + 1 ];
536+ int i ;
537+ Oid save_uid ;
538+ bool result ;
539+ save_uid = GetUserId ();
540+
541+ ri_BuildQueryKeyPkCheck (& qkey , tgoid ,
542+ RI_PLAN_CHECK_LOOKUPPK , pk_rel ,
543+ tgnargs , tgargs );
544+
545+ switch (ri_NullCheck (pk_rel , old_row , & qkey , RI_KEYPAIR_PK_IDX ))
546+ {
547+ case RI_KEYS_ALL_NULL :
548+ /*
549+ * No check - nothing could have been referencing this row anyway.
550+ */
551+ return true;
552+
553+ case RI_KEYS_SOME_NULL :
554+
555+ /*
556+ * This is the only case that differs between the three kinds
557+ * of MATCH.
558+ */
559+ switch (match_type )
560+ {
561+ case RI_MATCH_TYPE_FULL :
562+ case RI_MATCH_TYPE_UNSPECIFIED :
563+
564+ /*
565+ * MATCH <unspecified>/FULL - if ANY column is null, we
566+ * can't be matching to this row already.
567+ */
568+ return true;
569+
570+ case RI_MATCH_TYPE_PARTIAL :
571+
572+ /*
573+ * MATCH PARTIAL - all non-null columns must match.
574+ * (not implemented, can be done by modifying the
575+ * query below to only include non-null columns, or by
576+ * writing a special version here)
577+ */
578+ elog (ERROR , "MATCH PARTIAL not yet implemented" );
579+ break ;
580+ }
581+
582+ case RI_KEYS_NONE_NULL :
583+
584+ /*
585+ * Have a full qualified key - continue below for all three
586+ * kinds of MATCH.
587+ */
588+ break ;
589+ }
590+
591+ if (SPI_connect () != SPI_OK_CONNECT )
592+ elog (WARNING , "SPI_connect() failed in RI_FKey_check()" );
593+
594+
595+ /*
596+ * Fetch or prepare a saved plan for the real check
597+ */
598+ if ((qplan = ri_FetchPreparedPlan (& qkey )) == NULL )
599+ {
600+ char querystr [MAX_QUOTED_REL_NAME_LEN + 100 +
601+ (MAX_QUOTED_NAME_LEN + 32 ) * RI_MAX_NUMKEYS ];
602+ char pkrelname [MAX_QUOTED_REL_NAME_LEN ];
603+ char attname [MAX_QUOTED_NAME_LEN ];
604+ const char * querysep ;
605+ Oid queryoids [RI_MAX_NUMKEYS ];
606+
607+ /* ----------
608+ * The query string built is
609+ * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
610+ * The type id's for the $ parameters are those of the
611+ * corresponding FK attributes. Thus, SPI_prepare could
612+ * eventually fail if the parser cannot identify some way
613+ * how to compare these two types by '='.
614+ * ----------
615+ */
616+ quoteRelationName (pkrelname , pk_rel );
617+ sprintf (querystr , "SELECT 1 FROM ONLY %s x" , pkrelname );
618+ querysep = "WHERE" ;
619+ for (i = 0 ; i < qkey .nkeypairs ; i ++ )
620+ {
621+ quoteOneName (attname ,
622+ tgargs [RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX ]);
623+ sprintf (querystr + strlen (querystr ), " %s %s = $%d" ,
624+ querysep , attname , i + 1 );
625+ querysep = "AND" ;
626+ queryoids [i ] = SPI_gettypeid (pk_rel -> rd_att ,
627+ qkey .keypair [i ][RI_KEYPAIR_PK_IDX ]);
628+ }
629+ strcat (querystr , " FOR UPDATE OF x" );
630+
631+ /*
632+ * Prepare, save and remember the new plan.
633+ */
634+ qplan = SPI_prepare (querystr , qkey .nkeypairs , queryoids );
635+ qplan = SPI_saveplan (qplan );
636+ ri_HashPreparedPlan (& qkey , qplan );
637+ }
638+
639+ /*
640+ * We have a plan now. Build up the arguments for SPI_execp() from the
641+ * key values in the new FK tuple.
642+ */
643+ for (i = 0 ; i < qkey .nkeypairs ; i ++ )
644+ {
645+ check_values [i ] = SPI_getbinval (old_row ,
646+ pk_rel -> rd_att ,
647+ qkey .keypair [i ][RI_KEYPAIR_PK_IDX ],
648+ & isnull );
649+ if (isnull )
650+ check_nulls [i ] = 'n' ;
651+ else
652+ check_nulls [i ] = ' ' ;
653+ }
654+ check_nulls [i ] = '\0' ;
655+
656+ /*
657+ * Now check that foreign key exists in PK table
658+ */
659+
660+ SetUserId (RelationGetForm (pk_rel )-> relowner );
661+
662+ if (SPI_execp (qplan , check_values , check_nulls , 1 ) != SPI_OK_SELECT )
663+ elog (ERROR , "SPI_execp() failed in ri_Check_Pk_Match()" );
664+
665+ SetUserId (save_uid );
666+
667+ result = (SPI_processed != 0 );
668+
669+ if (SPI_finish () != SPI_OK_FINISH )
670+ elog (WARNING , "SPI_finish() failed in ri_Check_Pk_Match()" );
671+
672+ return result ;
673+ }
674+
675+
515676/* ----------
516677 * RI_FKey_noaction_del -
517678 *
@@ -535,6 +696,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
535696 char del_nulls [RI_MAX_NUMKEYS + 1 ];
536697 bool isnull ;
537698 int i ;
699+ int match_type ;
538700 Oid save_uid ;
539701
540702 save_uid = GetUserId ();
@@ -581,7 +743,18 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
581743 pk_rel = trigdata -> tg_relation ;
582744 old_row = trigdata -> tg_trigtuple ;
583745
584- switch (ri_DetermineMatchType (tgargs [RI_MATCH_TYPE_ARGNO ]))
746+ match_type = ri_DetermineMatchType (tgargs [RI_MATCH_TYPE_ARGNO ]);
747+ if (ri_Check_Pk_Match (pk_rel , old_row , trigdata -> tg_trigger -> tgoid ,
748+ match_type , tgnargs , tgargs )) {
749+ /*
750+ * There's either another row, or no row could match this
751+ * one. In either case, we don't need to do the check.
752+ */
753+ heap_close (fk_rel , RowShareLock );
754+ return PointerGetDatum (NULL );
755+ }
756+
757+ switch (match_type )
585758 {
586759 /* ----------
587760 * SQL3 11.9 <referential constraint definition>
@@ -746,6 +919,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
746919 char upd_nulls [RI_MAX_NUMKEYS + 1 ];
747920 bool isnull ;
748921 int i ;
922+ int match_type ;
749923 Oid save_uid ;
750924
751925 save_uid = GetUserId ();
@@ -793,7 +967,18 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
793967 new_row = trigdata -> tg_newtuple ;
794968 old_row = trigdata -> tg_trigtuple ;
795969
796- switch (ri_DetermineMatchType (tgargs [RI_MATCH_TYPE_ARGNO ]))
970+ match_type = ri_DetermineMatchType (tgargs [RI_MATCH_TYPE_ARGNO ]);
971+ if (ri_Check_Pk_Match (pk_rel , old_row , trigdata -> tg_trigger -> tgoid ,
972+ match_type , tgnargs , tgargs )) {
973+ /*
974+ * There's either another row, or no row could match this
975+ * one. In either case, we don't need to do the check.
976+ */
977+ heap_close (fk_rel , RowShareLock );
978+ return PointerGetDatum (NULL );
979+ }
980+
981+ switch (match_type )
797982 {
798983 /* ----------
799984 * SQL3 11.9 <referential constraint definition>
@@ -3042,6 +3227,59 @@ ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
30423227 }
30433228}
30443229
3230+ /* ----------
3231+ * ri_BuildQueryKeyPkCheck -
3232+ *
3233+ * Build up a new hashtable key for a prepared SPI plan of a
3234+ * check for PK rows in noaction triggers.
3235+ *
3236+ * constr_type is FULL
3237+ * constr_id is the OID of the pg_trigger row that invoked us
3238+ * constr_queryno is an internal number of the query inside the proc
3239+ * pk_relid is the OID of referenced relation
3240+ * nkeypairs is the number of keypairs
3241+ * following are the attribute number keypairs of the trigger invocation
3242+ *
3243+ * At least for MATCH FULL this builds a unique key per plan.
3244+ * ----------
3245+ */
3246+ static void
3247+ ri_BuildQueryKeyPkCheck (RI_QueryKey * key , Oid constr_id , int32 constr_queryno ,
3248+ Relation pk_rel ,
3249+ int argc , char * * argv )
3250+ {
3251+ int i ;
3252+ int j ;
3253+ int fno ;
3254+
3255+ /*
3256+ * Initialize the key and fill in type, oid's and number of keypairs
3257+ */
3258+ memset ((void * ) key , 0 , sizeof (RI_QueryKey ));
3259+ key -> constr_type = RI_MATCH_TYPE_FULL ;
3260+ key -> constr_id = constr_id ;
3261+ key -> constr_queryno = constr_queryno ;
3262+ key -> fk_relid = 0 ;
3263+ key -> pk_relid = pk_rel -> rd_id ;
3264+ key -> nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO ) / 2 ;
3265+
3266+ /*
3267+ * Lookup the attribute numbers of the arguments to the trigger call
3268+ * and fill in the keypairs.
3269+ */
3270+ for (i = 0 , j = RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX ; j < argc ; i ++ , j += 2 )
3271+ {
3272+ fno = SPI_fnumber (pk_rel -> rd_att , argv [j ]);
3273+ if (fno == SPI_ERROR_NOATTRIBUTE )
3274+ elog (ERROR , "constraint %s: table %s does not have an attribute %s" ,
3275+ argv [RI_CONSTRAINT_NAME_ARGNO ],
3276+ RelationGetRelationName (pk_rel ),
3277+ argv [j + 1 ]);
3278+ key -> keypair [i ][RI_KEYPAIR_PK_IDX ] = fno ;
3279+ key -> keypair [i ][RI_KEYPAIR_FK_IDX ] = 0 ;
3280+ }
3281+ }
3282+
30453283
30463284/* ----------
30473285 * ri_NullCheck -
@@ -3378,3 +3616,4 @@ ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
33783616 return DatumGetBool (FunctionCall2 (& (entry -> oprfmgrinfo ),
33793617 oldvalue , newvalue ));
33803618}
3619+
0 commit comments