RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
{
	TriggerData *trigdata = (TriggerData *) fcinfo->context;
	const RI_ConstraintInfo *riinfo;
	Relation	fk_rel;
	Relation	pk_rel;
	HeapTuple	new_row;
	HeapTuple	old_row;
	RI_QueryKey qkey;
	SPIPlanPtr	qplan;
	int			i;
	int			j;

	/*
	 * Check that this is a valid trigger call on the right time and event.
	 */
	ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);

	/*
	 * Get arguments.
	 */
	riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
									trigdata->tg_relation, true);

	/*
	 * Get the relation descriptors of the FK and PK tables and the new and
	 * old tuple.
	 *
	 * fk_rel is opened in RowExclusiveLock mode since that's what our
	 * eventual UPDATE will get on it.
	 */
	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
	pk_rel = trigdata->tg_relation;
	new_row = trigdata->tg_newtuple;
	old_row = trigdata->tg_trigtuple;

	switch (riinfo->confmatchtype)
	{
			/* ----------
			 * SQL:2008 15.17 <Execution of referential actions>
			 *	General rules 10) a) i):
			 *		MATCH SIMPLE/FULL
			 *			... ON UPDATE CASCADE
			 * ----------
			 */
		case FKCONSTR_MATCH_SIMPLE:
		case FKCONSTR_MATCH_FULL:
			switch (ri_NullCheck(old_row, riinfo, true))
			{
				case RI_KEYS_ALL_NULL:
				case RI_KEYS_SOME_NULL:

					/*
					 * No check needed - there cannot be any reference to old
					 * key if it contains a NULL
					 */
					heap_close(fk_rel, RowExclusiveLock);
					return PointerGetDatum(NULL);

				case RI_KEYS_NONE_NULL:

					/*
					 * Have a full qualified key - continue below
					 */
					break;
			}

			/*
			 * No need to do anything if old and new keys are equal
			 */
			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
			{
				heap_close(fk_rel, RowExclusiveLock);
				return PointerGetDatum(NULL);
			}

			if (SPI_connect() != SPI_OK_CONNECT)
				elog(ERROR, "SPI_connect failed");

			/*
			 * Fetch or prepare a saved plan for the cascaded update
			 */
			ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);

			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
			{
				StringInfoData querybuf;
				StringInfoData qualbuf;
				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
				char		attname[MAX_QUOTED_NAME_LEN];
				char		paramname[16];
				const char *querysep;
				const char *qualsep;
				Oid			queryoids[RI_MAX_NUMKEYS * 2];

				/* ----------
				 * The query string built is
				 *	UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
				 *			WHERE $n = fkatt1 [AND ...]
				 * The type id's for the $ parameters are those of the
				 * corresponding PK attributes.  Note that we are assuming
				 * there is an assignment cast from the PK to the FK type;
				 * else the parser will fail.
				 * ----------
				 */
				initStringInfo(&querybuf);
				initStringInfo(&qualbuf);
				quoteRelationName(fkrelname, fk_rel);
				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
				querysep = "";
				qualsep = "WHERE";
				for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
				{
					Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
					Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);

					quoteOneName(attname,
								 RIAttName(fk_rel, riinfo->fk_attnums[i]));
					appendStringInfo(&querybuf,
									 "%s %s = $%d",
									 querysep, attname, i + 1);
					sprintf(paramname, "$%d", j + 1);
					ri_GenerateQual(&qualbuf, qualsep,
									paramname, pk_type,
									riinfo->pf_eq_oprs[i],
									attname, fk_type);
					querysep = ",";
					qualsep = "AND";
					queryoids[i] = pk_type;
					queryoids[j] = pk_type;
				}
				appendStringInfoString(&querybuf, qualbuf.data);

				/* Prepare and save the plan */
				qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids,
									 &qkey, fk_rel, pk_rel, true);
			}

			/*
			 * We have a plan now. Run it to update the existing references.
			 */
			ri_PerformCheck(riinfo, &qkey, qplan,
							fk_rel, pk_rel,
							old_row, new_row,
							true,		/* must detect new rows */
							SPI_OK_UPDATE);

			if (SPI_finish() != SPI_OK_FINISH)
				elog(ERROR, "SPI_finish failed");

			heap_close(fk_rel, RowExclusiveLock);

			return PointerGetDatum(NULL);

			/*
			 * Handle MATCH PARTIAL cascade update.
			 */
		case FKCONSTR_MATCH_PARTIAL:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("MATCH PARTIAL not yet implemented")));
			return PointerGetDatum(NULL);

		default:
			elog(ERROR, "unrecognized confmatchtype: %d",
				 riinfo->confmatchtype);
			break;
	}

	/* Never reached */
	return PointerGetDatum(NULL);
}