/****************************************************************
 *								*
 * Copyright (c) 2010-2020 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_string.h"

#include "gdsroot.h"
#include "gdsbt.h"
#include "gtm_facility.h"
#include "fileinfo.h"
#include "gdsfhead.h"
#include <rtnhdr.h>		/* for rtn_tabent in gv_trigger.h */
#include "gv_trigger.h"
#include "gtm_trigger.h"
#include "error.h"
#include "filestruct.h"
#include "gdscc.h"
#include "gdskill.h"
#include "jnl.h"
#include "buddy_list.h"		/* needed for tp.h */
#include "tp.h"
#include "targ_alloc.h"
#include "hashtab_mname.h"	/* for COMPUTE_HASH_MNAME */
#include "tp_set_sgm.h"
#include "t_begin.h"
#include "t_retry.h"
#include "tp_restart.h"
#include "gvcst_protos.h"
#include "mv_stent.h"		/* for COPY_SUBS_TO_GVCURRKEY macro & PUSH_MV_STENT macros */
#include "gvsub2str.h"		/* for COPY_SUBS_TO_GVCURRKEY macro (gvsub2str prototype) */
#include "format_targ_key.h"	/* for COPY_SUBS_TO_GVCURRKEY macro (format_targ_key prototype) */
#include "mvalconv.h"		/* for mval2i */
#include "op.h"			/* for op_tstart */
#include "toktyp.h"
#include "patcode.h"
#include "compiler.h"		/* for MAX_SRCLINE */
#include "min_max.h"
#include "stringpool.h"
#include "subscript.h"
#include "op_tcommit.h"		/* for op_tcommit prototype */
#include "cdb_sc.h"
#include "gv_trigger_protos.h"
#include "trigger_fill_xecute_buffer.h"
#include "strpiecediff.h"
#include "gtm_utf8.h"		/* for CHSET_M_STR and CHSET_UTF8_STR */
#include "trigger.h"		/* for MAX_TRIGNAME_LEN */
#include "wbox_test_init.h"
#include "have_crit.h"
#include "gtm_trigger_trc.h"
#include "gvt_inline.h"

#ifdef GTM_TRIGGER
GBLREF	boolean_t		is_dollar_incr;
GBLREF	boolean_t		skip_dbtriggers;	/* see gbldefs.c for description of this global */
GBLREF	gd_region		*gv_cur_region;
GBLREF	gv_key			*gv_altkey;
GBLREF	gv_key			*gv_currkey;
GBLREF	gv_namehead		*gv_target;
GBLREF	gv_namehead		*reset_gv_target;
GBLREF	int4			gtm_trigger_depth;
GBLREF	int4			tstart_trigger_depth;
GBLREF	uint4			update_trans;
GBLREF	sgm_info		*first_sgm_info;
GBLREF	sgm_info		*sgm_info_ptr;
GBLREF	sgmnt_addrs		*cs_addrs;
GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	uint4			dollar_tlevel;
GBLREF	uint4			t_err;
GBLREF	int			tprestart_state;
GBLREF	unsigned int		t_tries;
GBLREF	unsigned char		t_fail_hist[CDB_MAX_TRIES];
GBLREF	pid_t			process_id;
GBLREF	trans_num		local_tn;
#ifdef DEBUG
GBLREF	uint4			dollar_trestart;
GBLREF	boolean_t		donot_INVOKE_MUMTSTART;
#endif

error_def(ERR_DBROLLEDBACK);
error_def(ERR_GVKILLFAIL);
error_def(ERR_GVPUTFAIL);
error_def(ERR_GVZTRIGFAIL);
error_def(ERR_NEEDTRIGUPGRD);
error_def(ERR_INDRMAXLEN);
error_def(ERR_PATMAXLEN);
error_def(ERR_TEXT);
error_def(ERR_TPRETRY);
error_def(ERR_TRIGDEFBAD);
error_def(ERR_TRIGINVCHSET);
error_def(ERR_TRIGIS);
error_def(ERR_TRIGSUBSCRANGE);

LITREF	char	ctypetab[NUM_CHARS];
LITREF	int4	gvtr_cmd_mask[GVTR_CMDTYPES];
LITREF	mval	gvtr_cmd_mval[GVTR_CMDTYPES];
LITREF	mval	literal_zero;
LITREF	mval	literal_hashcount;
LITREF	mval	literal_hashcycle;
LITREF	mval	literal_hashlabel;
LITREF	mval	literal_null;

#define TRIGGER_SUBSDEF(SUBSTYPE, SUBSNAME, LITMVALNAME, TRIGFILEQUAL, PARTOFHASH)	LITREF mval LITMVALNAME;
#include "trigger_subs_def.h"
#undef TRIGGER_SUBSDEF

#define	GVTR_PARSE_POINT	1
#define	GVTR_PARSE_LEFT		2
#define	GVTR_PARSE_RIGHT	3

#define SAVE_RTN_NAME(SAVE_RTN_NAME, SAVE_RTN_NAME_LEN, TRIGDSC)			\
{											\
	SAVE_RTN_NAME_LEN = MIN(MAX_TRIGNAME_LEN, TRIGDSC->rtn_desc.rt_name.len);	\
	assert(SAVE_RTN_NAME_LEN <= SIZEOF(SAVE_RTN_NAME));				\
	memcpy(SAVE_RTN_NAME, TRIGDSC->rtn_desc.rt_name.addr, SAVE_RTN_NAME_LEN);	\
}

#define SAVE_VAR_NAME(SAVE_VAR_NAME, SAVE_VAR_NAME_LEN, GVT)				\
{											\
	SAVE_VAR_NAME_LEN = MIN(MAX_MIDENT_LEN, GVT->gvname.var_name.len);		\
	assert(SAVE_VAR_NAME_LEN <= SIZEOF(SAVE_VAR_NAME));				\
	memcpy(SAVE_VAR_NAME, GVT->gvname.var_name.addr, SAVE_VAR_NAME_LEN);		\
}

/* GVTR_HASHTGBL_READ_CLEANUP
 * Now that ^#t has been read/referenced, make sure this gvt is added to the gvt_tp_list, even if the gvt has no
 * triggers associated with it.
 *
 * Callers gvcst_put and gvcst_kill perform database operations on gvt and an accompanying tp_hist() automatically
 * ensures that gvt is added to gvt_tp_list. However, there is a window in time between gvtr_db_read_hasht() and
 * tp_hist() where a restart could occur. If we did not add this gvt to the gvt_tp_list, a restart would not undo this
 * gvt's trigger related cycles (in TP_INVALIDATE_TRIGGER_CYCLES_IF_NEEDED).
 *
 * If the caller is op_ztrigger, it is possible only the ^#t global gvtarget gets added as part of the
 * "gvtr_get_hasht_gblsubs" calls below and the triggering global does not get referenced anywhere else in the TP
 * transaction. Since ZTRIGGER command does no db operations on the triggering global, it is possible gvt does not get
 * added to the gvt_tp_list which means if a trollback/tprestart occurs we would not undo this gvt's trigger related
 * cycles. To avoid this issue, we add this gvt to the gvt_tp_list always. The macro anyways does nothing if this gvt
 * has already been added so we should be fine correctness and performance wise.
 */
#define	GVTR_HASHTGBL_READ_CLEANUP(do_gvtr_cleanup)									\
{															\
	/* Restore gv_target, gv_currkey & gv_altkey */									\
	gv_target = save_gvtarget;											\
	/* This ADD_TO_GVT_TP_LIST could potentially happen BEFORE a gvcst_search of this gvt occurred in this		\
	 * transaction.  This means if gvt->clue.end is non-zero, gvcst_search would not get a chance to clear the	\
	 * first_tp_srch_status fields (which it does using the GVT_CLEAR_FIRST_TP_SRCH_STATUS macro) because		\
	 * gvt->read_local_tn would be set to local_tn as part of the ADD_TO_GVT_TP_LIST macro invocation. We		\
	 * therefore pass the second parameter indicating that first_tp_srch_status needs to be cleared too if		\
	 * gvt->read_local_tn gets synced to local_tn. All other callers of ADD_TO_GVT_TP_LIST (as of this writing)	\
	 * happen AFTER a gvcst_search of this gvt occurred in this TP transaction.  Therefore this is currently the	\
	 * only place which uses TRUE for the second parameter.								\
	 */														\
	ADD_TO_GVT_TP_LIST(gv_target, RESET_FIRST_TP_SRCH_STATUS_TRUE);							\
	if (do_gvtr_cleanup)												\
		gvtr_free(gv_target);											\
	/* check no keysize expansion occurred inside gvcst_root_search */						\
	assert(gv_currkey->top == save_gv_currkey->top);								\
	COPY_KEY(gv_currkey, save_gv_currkey);										\
	/* check no keysize expansion occurred inside gvcst_root_search */						\
	assert(gv_altkey->top == save_gv_altkey->top);									\
	COPY_KEY(gv_altkey, save_gv_altkey);										\
	TREF(gv_last_subsc_null) = save_gv_last_subsc_null;								\
	TREF(gv_some_subsc_null) = save_gv_some_subsc_null;								\
}

#define	GVTR_POOL2BUDDYLIST(GVT_TRIGGER, DST_MSTR)									\
{															\
	int4	len;													\
	char	*addr;													\
	mstr	*dst_mstr;												\
															\
	dst_mstr = DST_MSTR;												\
	addr = dst_mstr->addr;												\
	len = dst_mstr->len;												\
	dst_mstr->addr = (char *)get_new_element(GVT_TRIGGER->gv_trig_list, DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));	\
	memcpy(dst_mstr->addr, addr, len);										\
}

#define	GVTR_PROCESS_GVSUBS(PTR, END, SUBSDSC, COLON_IMBALANCE, GVT, SUBSCSTRLEN, SUBSCSTR)			\
{														\
	uint4		status, rt_name_len, save_var_name_len;							\
	gv_trigger_t	*lcl_trigdsc;										\
	char		rt_name[MAX_TRIGNAME_LEN], save_var_name[MAX_MIDENT_LEN];				\
														\
	status = gvtr_process_gvsubs(PTR, END, SUBSDSC, COLON_IMBALANCE, GVT);					\
	if (status)												\
	{													\
		lcl_trigdsc = GVT->gvt_trigger->gv_trig_array;							\
		assert(NULL != lcl_trigdsc);									\
		assert((NULL != lcl_trigdsc->rtn_desc.rt_name.addr)						\
			&& (0 != lcl_trigdsc->rtn_desc.rt_name.len));						\
		SAVE_VAR_NAME(save_var_name, save_var_name_len, GVT);						\
		SAVE_RTN_NAME(rt_name, rt_name_len, lcl_trigdsc);						\
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);								\
		if (ERR_PATMAXLEN == status) 									\
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(6) status, 0, ERR_TRIGIS, 2, rt_name_len,		\
					rt_name);								\
		else if (ERR_TRIGSUBSCRANGE == status)								\
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(10) status, 4, save_var_name_len, save_var_name,	\
					SUBSCSTRLEN, SUBSCSTR, ERR_TRIGIS, 2, rt_name_len, rt_name);		\
		else	/* error return from patstr */								\
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) status);					\
	}													\
	/* End of a range (in a set of ranges) or a subscript itself.						\
	 * Either case, colon_imbalance can be safely reset.							\
	 */													\
	COLON_IMBALANCE = FALSE;										\
}

#define	KEYSUB_S2POOL_IF_NEEDED(KEYSUB_MVAL, KEYSUB, THISSUB)				\
{											\
	unsigned char		str_buff[MAX_ZWR_KEY_SZ], *str_end;			\
	mstr			opstr;							\
											\
	KEYSUB_MVAL = lvvalarray[KEYSUB];						\
	if (NULL == KEYSUB_MVAL)							\
	{										\
		PUSH_MV_STENT(MVST_MVAL);						\
		KEYSUB_MVAL = &mv_chain->mv_st_cont.mvs_mval;				\
		KEYSUB_MVAL->mvtype = 0;						\
		lvvalarray[KEYSUB] = KEYSUB_MVAL;					\
		THISSUB = keysub_start[KEYSUB];						\
		opstr.addr = (char *)str_buff;						\
		opstr.len = MAX_ZWR_KEY_SZ;						\
		str_end = gvsub2str((unsigned char *)THISSUB, &opstr, FALSE);		\
		KEYSUB_MVAL->str.addr = (char *)str_buff;				\
		KEYSUB_MVAL->str.len = INTCAST(str_end - str_buff);			\
		KEYSUB_MVAL->mvtype = MV_STR;						\
		s2pool(&KEYSUB_MVAL->str);						\
	}										\
}

#define PUT_TRIGGER_ON_CMD_TYPE_QUEUE(trigdsc, gvt_trigger, type)						\
{	/* Add to type queue in gvt_trigger */									\
	if (NULL == (gvt_trigger)->type##_triglist)								\
	{	/* Initialize circular list of one element */							\
		(gvt_trigger)->type##_triglist = (trigdsc);							\
		(trigdsc)->next_##type = trigdsc;								\
	} else													\
	{	/* Add element to list - Note this is a simplistic add algorithm that doesn't make		\
		 * any attempt to keep elements sorted. Since triggers are supposed to fire in random		\
		 * order, that's a good thing. Adding elements A,B,C will end up with order A,C,B.		\
		 */												\
		(trigdsc)->next_##type = (gvt_trigger)->type##_triglist->next_##type;				\
		(gvt_trigger)->type##_triglist->next_##type = (trigdsc);					\
	}													\
}

#define SELECT_AND_RANDOMIZE_TRIGGER_CHAIN(gvt_trigger, trigstart, trig_list_offset, type)				\
{															\
	trigstart = (gvt_trigger)->type##_triglist;									\
	if (NULL != trigstart)												\
	{	/* Use arbitrary bit from sum of hodgepodge fields that change over time to see if should		\
		 * rotate the trigger chain by 1 or 2 entries to increase pseudo-randomization.				\
		 */													\
		gvt_trigger->type##_triglist = trigstart = trigstart->next_##type;					\
		if (0 != (0x10 & (process_id + (INTPTR_T)stringpool.free + (INTPTR_T)&trigstart))) /* BYPASSOK */	\
			/* Do a 2nd rotation.. */									\
			gvt_trigger->type##_triglist = trigstart = trigstart->next_##type;				\
	}														\
	trig_list_offset = OFFSETOF(gv_trigger_t, next_##type);								\
}

/* This error macro is used for all definition errors where the target is ^#t(GVN,<index>,<required subscript>) */
#define GVTR_HASHT_GVN_DEFINITION_RETRY_OR_ERROR(INDEX,SUBSCRIPT,CSA)				\
{												\
	if (UPDATE_CAN_RETRY(t_tries, t_fail_hist[t_tries]))					\
	{											\
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);						\
		t_retry(cdb_sc_triggermod);							\
	} else											\
	{											\
		assert(WBTEST_HELPOUT_TRIGDEFBAD == gtm_white_box_test_case_number);		\
		SAVE_VAR_NAME(save_var_name, save_var_name_len, gvt);				\
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);						\
		/* format "INDEX,SUBSCRIPT" of ^#t(GVN,INDEX,SUBSCRIPT) in the error message */	\
		SET_PARAM_STRING(util_buff, util_len, INDEX, SUBSCRIPT);			\
		rts_error_csa(CSA_ARG(CSA) VARLSTCNT(8) ERR_TRIGDEFBAD, 6, save_var_name_len,	\
				save_var_name, save_var_name_len, save_var_name, util_len,	\
				util_buff);							\
	}											\
}

/* This error macro is used for all definition errors where the target is ^#t(GVN,<#COUNT|#CYCLE>) */
#define HASHT_DEFINITION_RETRY_OR_ERROR(SUBSCRIPT,MOREINFO,CSA)	\
{								\
	if (UPDATE_CAN_RETRY(t_tries, t_fail_hist[t_tries]))	\
	{							\
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);		\
		t_retry(cdb_sc_triggermod);			\
	} else							\
	{							\
		HASHT_DEFINITION_ERROR(SUBSCRIPT,MOREINFO,CSA);	\
	}							\
}

#define HASHT_DEFINITION_ERROR(SUBSCRIPT,MOREINFO,CSA)					\
{											\
	assert(WBTEST_HELPOUT_TRIGDEFBAD == gtm_white_box_test_case_number);		\
	SAVE_VAR_NAME(save_var_name, save_var_name_len, gvt);				\
	GVTR_HASHTGBL_READ_CLEANUP(TRUE);						\
	rts_error_csa(CSA_ARG(CSA) VARLSTCNT(12) ERR_TRIGDEFBAD, 6, save_var_name_len,	\
			save_var_name, save_var_name_len, save_var_name,		\
			LEN_AND_LIT(SUBSCRIPT), ERR_TEXT, 2, RTS_ERROR_TEXT(MOREINFO));	\
}

/* This code is modeled around "updproc_ch" in updproc.c */
CONDITION_HANDLER(gvtr_tpwrap_ch)
{
	int	rc;

	START_CH(TRUE);
	if ((int)ERR_TPRETRY == SIGNAL)
	{
		assert(TPRESTART_STATE_NORMAL == tprestart_state);
		tprestart_state = TPRESTART_STATE_NORMAL;
		assert(NULL != first_sgm_info);
		/* This only happens at the outer-most layer so state should be normal now */
		rc = tp_restart(1, !TP_RESTART_HANDLES_ERRORS);
		assert(0 == rc);
		assert(TPRESTART_STATE_NORMAL == tprestart_state);	/* No rethrows possible */
		/* gvtr* code could be called by gvcst_put which plays with "reset_gv_target". But the only gvcst* functions
		 * called by the gvtr* code is "gvcst_get" which is guaranteed not to play with it. Therefore we should NOT
		 * be tampering with it (i.e. trying to reset it like what "trigger_item_tpwrap_ch" does) as the caller expects
		 * it to be untouched throughout the gvtr* activity.
		 */
		UNWIND(NULL, NULL);
	}
	NEXTCH;
}

STATICFNDEF void gvtr_db_tpwrap_helper(sgmnt_addrs *csa, int err_code, boolean_t root_srch_needed)
{
	enum cdb_sc		status, failure;
#	ifdef DEBUG
	gv_namehead		*save_gv_target;
#	endif

	ESTABLISH(gvtr_tpwrap_ch);
	assert(dollar_tlevel);
	assert(gv_target != csa->hasht_tree);
	assert((ERR_GVPUTFAIL == err_code) || (ERR_GVKILLFAIL == err_code) || (ERR_GVZTRIGFAIL == err_code));
	if (root_srch_needed)
	{
		assert(0 < t_tries);
		assert(ERR_GVPUTFAIL != err_code); /* for SET, gvcst_put does the root search anyways. So, don't do it here */
		ASSERT_BEGIN_OF_FRESH_TP_TRANS; /* ensures gvcst_root_search is the first thing done in the restarted transaction */
		GVCST_ROOT_SEARCH; /* any t_retry from gvcst_root_search will transfer control back to gvtr_tpwrap_ch */
		root_srch_needed = FALSE; /* just to be safe */
	}
	gvtr_db_read_hasht(csa);
	DEBUG_ONLY(save_gv_target = gv_target;)
	assert(save_gv_target == gv_target); /* ensure gv_target was not inadvertently modified by gvtr_db_read_hasht */
	/* If no triggers are defined for this global and we came in as non-TP and did op_tstart in GVTR_INIT_AND_TPWRAP_IF_NEEDED
	 * do the op_tcommit. However, do it only for t_tries = 0 for two reasons:
	 *
	 * (a) If in final retry of a non-TP set, we will be holding crit. op_tcommit releases crit which is an out-of-design
	 * situation as that could cause restarts in final retry of the non-TP set. So, complete the non-TP update as a TP update
	 * by deferring the op_tcommit until we reach GVTR_OP_TCOMMIT in gvcst_put/gvcst_kill.
	 *
	 * (b) Assume t_tries = 1 or 2 and we came in as non-TP. If we found no triggers defined and invoked op_tcommit, to resume
	 * the non-TP set, we will invoke t_begin to reset global variables which also resets t_tries. Now, with t_tries = 0,
	 * if a restart happens and we come back here (due to concurrent $ZTRIGGER and MUPIP TRIGGER) and find no triggers defined
	 * for this global, we will again invoke op_tcommit. This can happen in a spinlock thereby not letting the final retry
	 * optimistic -> pessimistic concurrency scheme kick in at all. One workaround would be to not call t_begin
	 * after doing an op_tcommit. But, this would mean examining the various global variables that t_begin resets and
	 * making sure it's okay NOT to do the t_begin. Instead, complete the non-TP update as a TP update by deferring the
	 * op_tcommit until we reach GVTR_OP_TCOMMIT in gvcst_put/gvcst_kill.
	 *
	 * Note: Not doing op_tcommit will cause this non-TP SET to be completed as a TP SET even though no triggers were defined.
	 * This would mean, a non-TP set (in a concurrent environment with trigger loads happening frequently) could end up
	 * writing a TSET record in the corresponding regions' journal file. In a non-trigger environment, this will NOT
	 * be an issue since we come here only if process' trigger cycle is different from database trigger cycle.
	 *
	 * The exception to the above case is when we are processing a ZTRIGGER command (denoted by the err_code passed
	 * in being set to ERR_GVTRIGFAIL). In that case, we will be journaling a TZTRIG record of some form and is ok
	 * even if the global does not exist. So we don't want to prematurely terminate the transaction in that case.
	 */
	if ((NULL == gv_target->gvt_trigger) && (0 == t_tries) && (ERR_GVZTRIGFAIL != err_code))
	{	/* No triggers exist for this global. TP wrap not needed any more for this transaction */
		status = op_tcommit();
		assert(cdb_sc_normal == status); /* if retry, an rts_error should have been issued and we should not reach here */
	}
	REVERT;
}

/* Helper function used by "gvtr_db_read_hasht" and "trigger_upgrade" functions */
boolean_t	gvtr_get_hasht_gblsubs(mval *subs_mval, mval *ret_mval)
{
	uint4		curend;
	boolean_t	was_null = FALSE, is_null = FALSE, is_defined;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	is_defined = FALSE;
	curend = gv_currkey->end; /* note down gv_currkey->end before changing it so we can restore it before function returns */
	assert(KEY_DELIMITER == gv_currkey->base[curend]);
	assert(gv_target->gd_csa == cs_addrs);
	COPY_SUBS_TO_GVCURRKEY(subs_mval, gv_cur_region, gv_currkey, was_null, is_null); /* updates gv_currkey */
	is_defined = gvcst_get(ret_mval);
	assert(!is_defined || (MV_STR & ret_mval->mvtype));	/* assert that gvcst_get() sets type of mval to MV_STR */
	gv_currkey->end = curend;	/* reset gv_currkey->end to what it was at function entry */
	gv_currkey->base[curend] = KEY_DELIMITER;    /* restore terminator for entire key so key is well-formed */
	return is_defined;
}

STATICFNDEF uint4	gvtr_process_range(gv_namehead *gvt, gvtr_subs_t *subsdsc, int type, char *start, char *end)
{
	mval		tmpmval;
	gv_namehead	*save_gvt;
	char		keybuff[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key		*out_key;
	uint4		len, len1, min, nelems;
	gvt_trigger_t	*gvt_trigger;
	char		*dststart, *dstptr, *srcptr, ch;
	boolean_t	ret;
	int		cmpres;

	/* This function has its input string pointing to the stringpool. It does some processing (mval2subsc etc.)
	 * and expects the input string to be untouched during all of this so it sets the global to TRUE
	 * to ensure no one else touches the stringpool in the meantime.
	 */
	DEBUG_ONLY(len = (uint4)(end - start));
	assert(IS_IN_STRINGPOOL(start, len));
	DBG_MARK_STRINGPOOL_UNUSABLE;
	if ('"' == *start)
	{	/* Remove surrounding double-quotes as well as transform TWO CONSECUTIVE intermediate double-quotes into ONE.
		 * We need to construct the transformed string. Since we cannot modify the input string and yet want to avoid
		 * malloc/frees within this routine, we use the buddy_list allocate/free functions for this purpose.
		 */
		assert('"' == *(end - 1));
		start++;
		end--;
	}
	len = UINTCAST(end - start);
	if (len || (GVTR_PARSE_POINT == type))
	{
		gvt_trigger = gvt->gvt_trigger;
		if (len == 0)
		{
			tmpmval.mvtype = MV_STR;
			tmpmval.str.addr = "";
			tmpmval.str.len = 0;
		} else
		{
			nelems = DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE);
			dststart = (char *)get_new_element(gvt_trigger->gv_trig_list, nelems);
			dstptr = dststart;
			for (srcptr = start; srcptr < end; srcptr++)
			{
				ch = *srcptr;
				if ('"' == ch)
				{	/* A double-quote in the middle of the string better be TWO consecutive double-quotes */
					assert(((srcptr + 1) < end) && ('"' == srcptr[1]));
					srcptr++;
				}
				*dstptr++ = ch;
			}
			assert((dstptr - dststart) <= len);
			tmpmval.mvtype = MV_STR;
			tmpmval.str.addr = dststart;
			tmpmval.str.len = INTCAST(dstptr - dststart);
		}
		/* switch gv_target for mval2subsc */
		save_gvt = gv_target;
		gv_target = gvt;
		out_key = (gv_key *)keybuff;
		out_key->end = 0;
		out_key->top = DBKEYSIZE(MAX_KEY_SZ);
		mval2subsc(&tmpmval, out_key, gv_cur_region->std_null_coll);
		gv_target = save_gvt;
		if (0 < len)
		{	/* Now that mval2subsc is done, free up the allocated dststart buffer */
			ret = free_last_n_elements(gvt_trigger->gv_trig_list, nelems);
			assert(ret);
		}
	}
	/* else it means an open range (where left or right side of range is unspecified)
	 * null subscript on the right side of a range is treated as negative infinity and
	 * on the left side of a range is treated as positive infinity.
	 */
	switch(type)
	{
		case GVTR_PARSE_POINT:
			assert(&subsdsc->gvtr_subs_type == &subsdsc->gvtr_subs_point.gvtr_subs_type);
			len = out_key->end;	/* keep trailing 0 */
			subsdsc->gvtr_subs_point.len = len;
			dststart = (char *)get_new_element(gvt_trigger->gv_trig_list, DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));
			memcpy(dststart, out_key->base, len);
			subsdsc->gvtr_subs_point.subs_key = dststart;
			subsdsc->gvtr_subs_point.next_range = NULL;
			subsdsc->gvtr_subs_type = GVTR_SUBS_POINT;
			break;
		case GVTR_PARSE_LEFT:
			assert(&subsdsc->gvtr_subs_type == &subsdsc->gvtr_subs_range.gvtr_subs_type);
			if (len)
			{
				len = out_key->end;	/* keep trailing 0 */
				assert(len);
				dststart = (char *)get_new_element(gvt_trigger->gv_trig_list,
									DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));
				memcpy(dststart, out_key->base, len);
				subsdsc->gvtr_subs_range.subs_key1 = dststart;
				subsdsc->gvtr_subs_range.len1 = len;
			} else
				subsdsc->gvtr_subs_range.len1 = GVTR_RANGE_OPEN_LEN;
			subsdsc->gvtr_subs_type = GVTR_SUBS_RANGE;
			break;
		case GVTR_PARSE_RIGHT:
			assert(&subsdsc->gvtr_subs_type == &subsdsc->gvtr_subs_range.gvtr_subs_type);
			assert(GVTR_SUBS_RANGE == subsdsc->gvtr_subs_type);
			if (len)
			{
				len = out_key->end;	/* keep trailing 0 */
				assert(len);
				dststart = (char *)get_new_element(gvt_trigger->gv_trig_list,
									DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));
				memcpy(dststart, out_key->base, len);
				subsdsc->gvtr_subs_range.subs_key2 = dststart;
				subsdsc->gvtr_subs_range.len2 = len;
				len1 = subsdsc->gvtr_subs_range.len1;
				if (GVTR_RANGE_OPEN_LEN != len1)
				{	/* Since both ends of the range are not open, do range check of post-collated subscripts */
					min = MIN(len1, len);
					cmpres = memcmp(subsdsc->gvtr_subs_range.subs_key1,
								subsdsc->gvtr_subs_range.subs_key2, min);
					if ((0 < cmpres) || (0 == cmpres) && (len1 > len))
					{	/* Invalid range. Issue error */
						DBG_MARK_STRINGPOOL_USABLE;
						return ERR_TRIGSUBSCRANGE;
					}
				}
			} else
			{	/* right side of the range is open, check if left side is open too, if so reduce it to a "*" */
				if (GVTR_RANGE_OPEN_LEN == subsdsc->gvtr_subs_range.len1)
					subsdsc->gvtr_subs_type = GVTR_SUBS_STAR;
				else
					subsdsc->gvtr_subs_range.len2 = GVTR_RANGE_OPEN_LEN;
			}
			break;
		default:
			assert(FALSE);
			break;
	}
	assert(&subsdsc->gvtr_subs_range.next_range == &subsdsc->gvtr_subs_point.next_range);
	subsdsc->gvtr_subs_range.next_range = NULL;
	DBG_MARK_STRINGPOOL_USABLE;
	return 0;
}

STATICFNDEF uint4	gvtr_process_pattern(char *ptr, uint4 len, gvtr_subs_t *subsdsc, gvt_trigger_t *gvt_trigger)
{	/* subscript is a pattern */
	char		source_buffer[MAX_SRCLINE];
	mstr		instr;
	ptstr		pat_ptstr;
	uint4		status;

	assert('?' == *ptr);
	ptr++;
	len--;
	assert(0 < len);
	if (ARRAYSIZE(source_buffer) <= len)
	{
		assert(FALSE);
		return ERR_PATMAXLEN;
	}
	memcpy(source_buffer, ptr, len);
	instr.addr = source_buffer;
	instr.len = len;
	if (status = patstr(&instr, &pat_ptstr, NULL))
		return status;
	assert(pat_ptstr.len <= MAX_PATOBJ_LENGTH);
	len = pat_ptstr.len * SIZEOF(uint4);
	ptr = (char *)get_new_element(gvt_trigger->gv_trig_list, DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));
	memcpy(ptr, pat_ptstr.buff, len);
	assert(&subsdsc->gvtr_subs_type == &subsdsc->gvtr_subs_pattern.gvtr_subs_type);
	subsdsc->gvtr_subs_type = GVTR_SUBS_PATTERN;
	subsdsc->gvtr_subs_pattern.pat_mval.mvtype = MV_STR;
	subsdsc->gvtr_subs_pattern.pat_mval.str.addr = ptr;
	subsdsc->gvtr_subs_pattern.pat_mval.str.len = len;
	subsdsc->gvtr_subs_pattern.next_range = NULL;
	return 0;
}

STATICFNDEF uint4 gvtr_process_gvsubs(char *start, char *end, gvtr_subs_t *subsdsc, boolean_t colon_imbalance, gv_namehead *gvt)
{
	uint4		status;

	if ('?' == start[0])
	{
		assert(!colon_imbalance);
		if (status = gvtr_process_pattern(start, UINTCAST(end - start), subsdsc, gvt->gvt_trigger))
			return status;
	} else if ('*' == start[0])
	{	/* subscript is a "*" */
		assert(end == start + 1);
		assert(&subsdsc->gvtr_subs_type == &subsdsc->gvtr_subs_star.gvtr_subs_type);
		subsdsc->gvtr_subs_type = GVTR_SUBS_STAR; /* allow ANY value for this subscript */
		subsdsc->gvtr_subs_star.next_range = NULL;
	} else
	{
		if (status = gvtr_process_range(gvt, subsdsc, colon_imbalance ? GVTR_PARSE_RIGHT : GVTR_PARSE_POINT, start, end))
		{
			/* As of now we expect only ERR_TRIGSUBSCRANGE (which is triggered only for GVTR_PARSE_RIGHT) */
			assert((ERR_TRIGSUBSCRANGE == status) && colon_imbalance);
			return status;
		}
	}
	return 0;
}

void	gvtr_db_read_hasht(sgmnt_addrs *csa)
{
	gv_namehead		*save_gvtarget, *gvt;
	gv_key_buf		save_currkey, save_altkey;
	unsigned char		util_buff[MAX_TRIG_UTIL_LEN];
	gv_key			*save_gv_currkey;
	gv_key			*save_gv_altkey;
	mval			tmpmval, *ret_mval;
	boolean_t		is_defined, was_null = FALSE, is_null = FALSE, zdelim_defined, delim_defined;
	boolean_t		save_gv_last_subsc_null, save_gv_some_subsc_null;
	int4			tmpint4, util_len;
	gvt_trigger_t		*gvt_trigger;
	uint4			trigidx, num_gv_triggers, num_pieces, len, cmdtype, index, minpiece, maxpiece;
	gv_trigger_t		*gv_trig_array, *trigdsc, *trigtop;
	uint4			currkey_end, cycle, numsubs, cursub, numlvsubs, curlvsub;
	char			ch, *ptr, *ptr_top, *ptr_start;
	boolean_t		quote_imbalance, colon_imbalance, end_of_subscript;
	uint4			paren_imbalance;
	gvtr_subs_t		*subsdsc;
	mname_entry		*lvnamedsc;
	uint4			*lvindexdsc, status;
	int			ctype;
	char			save_rtn_name[MAX_TRIGNAME_LEN], save_var_name[MAX_MIDENT_LEN];
	uint4			save_rtn_name_len, save_var_name_len;
#	ifdef DEBUG
	int			cntset, icntset, cntkill, icntkill, cntztrig, icntztrig;
	gv_trigger_t		*trigstart;
#	endif
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(dollar_tlevel);		/* the code below is not designed to work in non-TP */
	save_gvtarget = gv_target;
	SET_GVTARGET_TO_HASHT_GBL(csa);
	/* Save gv_currkey and gv_altkey */
	assert(NULL != gv_currkey);
	assert((SIZEOF(gv_key) + gv_currkey->end + 1) <= SIZEOF(save_currkey));
	save_gv_currkey = (gv_key *)&save_currkey.key;
	MEMCPY_KEY(save_gv_currkey, gv_currkey);
	assert(NULL != gv_altkey);
	assert((SIZEOF(gv_key) + gv_altkey->end + 1) <= SIZEOF(save_altkey));
	save_gv_altkey = (gv_key *)&save_altkey.key;
	MEMCPY_KEY(save_gv_altkey, gv_altkey);
	save_gv_last_subsc_null = TREF(gv_last_subsc_null);
	save_gv_some_subsc_null = TREF(gv_some_subsc_null);
	DBGTRIGR((stderr, "gvtr_db_read_hasht: begin\n"));
	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	gv_currkey->prev = 0;
	if (0 == gv_target->root)
	{	/* ^#t global does not exist. Return right away. */
		DBGTRIGR((stderr, "gvtr_db_read_hasht: no triggers\n"));
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);
		csa->hdr->hasht_upgrade_needed = FALSE;	/* Reset now that we know there is no ^#t global in this db.
							 * Note: It is safe to do so even if we dont hold crit.
							 */
		return;
	}
	if (csa->hdr->hasht_upgrade_needed)
	{	/* ^#t needs to be upgraded first before reading it. Cannot proceed. */
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);
		rts_error_csa(CSA_ARG(csa) VARLSTCNT(4) ERR_NEEDTRIGUPGRD, 2, DB_LEN_STR(gv_cur_region));
	}
	/* ^#t global exists.
	 * Initialize gv_target->gvt_trigger from ^#t(<gbl>,...) where <gbl> is the global corresponding to save_gvtarget.
	 *
	 * In order to follow the code below, it would help to have the following ^#t global layout as an example.
	 * The following example assumes the following ^#t global layout corresponding to triggers for the ^GBL global
	 * In the following code, wherever "GBL" is mentioned in the comment, <gbl> is meant for the general case.
	 *
	 *	Input file
	 *	-----------
	 *	^GBL(acn=:,1) -zdelim="|" -pieces="2:5;4:6;8" -commands=Set,Kill,ZTKill -xecute="do ^XNAMEinGBL" -options=NOI,NOC
	 *
	 *	^#t global structure created by MUPIP TRIGGER
	 *	----------------------------------------------
	 *	^#t("GBL","#LABEL")="3"                     # Format of the ^#t global for GBL. Currently set to 3.
	 *						    # Will get bumped up in the future when the ^#t global format changes.
	 *	^#t("GBL","#CYCLE")=<32-bit-decimal-number> # incremented every time any trigger changes happen in ^GBL global
	 *	^#t("GBL","#COUNT")=1                       # indicating there is 1 trigger currently defined for ^GBL global
	 *	^#t("GBL",1,"CMD")="S,K,ZTK"
	 *	^#t("GBL",1,"GVSUBS")="acn=:,1"
	 *	^#t("GBL",1,"OPTIONS")="NOI,NOC"
	 *	^#t("GBL",1,"DELIM")=<undefined>	    # undefined in this case because -zdelim was specified
	 *	^#t("GBL",1,"ZDELIM")="|"		    # would have been undefined in case -delim was specified
	 *	^#t("GBL",1,"PIECES")="2:6;8"
	 *	^#t("GBL",1,"TRIGNAME")="GBL#1"		    # routine name trigger will have
	 *	^#t("GBL",1,"XECUTE")="do ^XNAMEinGBL"
	 *	^#t("GBL",1,"CHSET")="UTF-8"                # assuming the MUPIP TRIGGER was done with $ZCHSET of "UTF-8"
	 */
	/* -----------------------------------------------------------------------------
	 *          Read ^#t("GBL","#LABEL")
	 * -----------------------------------------------------------------------------
	 */
	/* Check value of ^#t("GBL","#LABEL"). We expect it to indicate the current format of the ^#t global.
	 * If not, it is an integ error in the ^#t global so issue an appropriate error.
	 */
	gvt = save_gvtarget;	/* use smaller variable name as it is going to be used in lots of places below */
	/* First add "GBL" subscript to gv_currkey i.e. ^#t("GBL") */
	tmpmval.mvtype = MV_STR;
	tmpmval.str = gvt->gvname.var_name;	/* copy gvname from gvt */
	ret_mval = &tmpmval;
	COPY_SUBS_TO_GVCURRKEY(ret_mval, gv_cur_region, gv_currkey, was_null, is_null); /* updates gv_currkey */
	/* At this point, gv_currkey points to ^#t("GBL") */
	/* Now check for ^#t("CIF","#LABEL") to determine what format ^#t("GBL") is stored in (if at all it exists) */
	is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_hashlabel, ret_mval);
	if (!is_defined)	/* No triggers exist for "^GBL". Return */
	{
		DBGTRIGR((stderr, "gvtr_db_read_hasht: no triggers for ^GBL\n"));
		GVTR_HASHTGBL_READ_CLEANUP(TRUE);
		return;
	}
	if ((STR_LIT_LEN(HASHT_GBL_CURLABEL) != ret_mval->str.len) || MEMCMP_LIT(ret_mval->str.addr, HASHT_GBL_CURLABEL))
		HASHT_DEFINITION_RETRY_OR_ERROR("\"#LABEL\"","#LABEL field is not " HASHT_GBL_CURLABEL, csa);
	/* So we can go ahead and read other ^#t("GBL") records */
	/* -----------------------------------------------------------------------------
	 *          Now read ^#t("GBL","#CYCLE")
	 * -----------------------------------------------------------------------------
	 */
	is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_hashcycle, ret_mval);
	if (!is_defined)
		HASHT_DEFINITION_RETRY_OR_ERROR("\"#CYCLE\"","#CYCLE field is missing", csa);
	tmpint4 = mval2i(ret_mval);	/* decimal values are truncated by mval2i so we will accept a #CYCLE of 1.5 as 1 */
	if (0 >= tmpint4) /* ^#t("GBL","#CYCLE") is not a positive integer. Error out */
		HASHT_DEFINITION_ERROR("\"#CYCLE\"","#CYCLE field is negative", csa);
	cycle = (uint4)tmpint4;
	/* Check if ^#t("GBL") has previously been read from the db. If so, check if cycle is same as ^#t("GBL","#CYCLE"). */
	gvt_trigger = gvt->gvt_trigger;
	if (NULL != gvt_trigger)
	{
		DBGTRIGR((stderr, "gvtr_db_read_hasht: gvt_trigger->gv_trigger_cycle = %d \n",gvt_trigger->gv_trigger_cycle));
		if (gvt_trigger->gv_trigger_cycle == cycle)
		{	/* Since cycle is same, no need to reinitialize any triggers for "GBL". Can return safely.
			 * Pass "FALSE" to GVTR_HASHTGBL_READ_CLEANUP macro to ensure gvt->gvt_trigger is NOT freed.
			 */
			DBGTRIGR((stderr, "gvtr_db_read_hasht: trigger has been read before\n"));
			GVTR_HASHTGBL_READ_CLEANUP(FALSE);
			return;
		}
		DBGTRIGR((stderr, "gvtr_db_read_hasht: cycle mismatch, re-initialize triggers\n"));
		/* Now that we have to reinitialize triggers for "GBL", free up previously allocated structure first */
		gvtr_free(gvt);
		assert(NULL == gvt->gvt_trigger);
	}
	/* Force a cycle mismatch if a restart occurs during trigger content read */
	gvt->db_trigger_cycle = gvt->gd_csa->db_trigger_cycle - 1;
	gvt_trigger = (gvt_trigger_t *)malloc(SIZEOF(gvt_trigger_t));
	gvt_trigger->gv_trigger_cycle = 0;
	gvt_trigger->gv_trig_array = NULL;
	gvt_trigger->gv_trig_list = NULL;
	/* Set gvt->gvt_trigger to this malloced memory (after gv_trig_array has been initialized to NULL to avoid garbage
	 * values). If we encounter an error below, we will remember to free this up the next time we are in this function
	 */
	gvt->gvt_trigger = gvt_trigger;
	/* -----------------------------------------------------------------------------
	 *          Now read ^#t("GBL","#COUNT")
	 * -----------------------------------------------------------------------------
	 */
	is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_hashcount, ret_mval);
	if (!is_defined)
		HASHT_DEFINITION_RETRY_OR_ERROR("\"#COUNT\"","#COUNT field is missing", csa);
	tmpint4 = mval2i(ret_mval);	/* decimal values are truncated by mval2i so we will accept a #COUNT of 1.5 as 1 */
	if (0 >= tmpint4) /* ^#t("GBL","#COUNT") is not a positive integer. Error out */
		HASHT_DEFINITION_ERROR("\"#COUNT\"","#COUNT field is negative", csa);
	num_gv_triggers = (uint4)tmpint4;
	gvt_trigger->num_gv_triggers = num_gv_triggers;
	/* We want a memory store for all the values that are going to be read in from the database. We dont know upfront
	 * how much memory is needed so we use a buddy list (that expands to fit the needed memory). We need to create a
	 * buddy_list with element size of 1 byte and ask for n-elements if we want n-bytes of contiguous memory.
	 * Minimum value of elemSize for buddy_list is 8 due to alignment requirements of the returned memory location.
	 * Therefore, we set elemSize to 8 bytes instead and will convert as much bytes as we need into as many
	 * 8-byte multiple segments below. We anticipate 256 bytes for each trigger so we allocate space for "num_gv_triggers"
	 * as the start of the buddy_list (to avoid repeated expansions of the buddy list in most cases).
	 */
	gvt_trigger->gv_trig_list = (buddy_list *)malloc(SIZEOF(buddy_list));
	initialize_list(gvt_trigger->gv_trig_list, GVTR_LIST_ELE_SIZE,
					DIVIDE_ROUND_UP(GV_TRIG_LIST_INIT_ALLOC * num_gv_triggers, GVTR_LIST_ELE_SIZE));
	trigdsc = (gv_trigger_t *)malloc(num_gv_triggers * SIZEOF(gv_trigger_t));
	memset(trigdsc, 0, num_gv_triggers * SIZEOF(gv_trigger_t));
	gvt_trigger->gv_trig_array = trigdsc; /* Now that everything is null-initialized, set gvt_trigger->gv_trig_array */
	gvt_trigger->gv_target = gvt;
	/* From this point onwards, we assume the integrity of the ^#t global i.e. MUPIP TRIGGER would have
	 * set ^#t fields to their appropriate value. So we dont do any more checks in case taking the wrong path.
	 */
	/* ------------------------------------------------------------------------------------
	 *          Now read ^#t("GBL",<num>,...) for <num> going from 1 to num_gv_triggers
	 * ------------------------------------------------------------------------------------
	 */
	currkey_end = gv_currkey->end; /* note down gv_currkey->end before changing it so we can restore it before next iteration */
	assert(KEY_DELIMITER == gv_currkey->base[currkey_end]);
	for (trigidx = 1; trigidx <= num_gv_triggers; trigidx++, trigdsc++)
	{	/* All records to be read in this loop are of the form ^#t("GBL",1,...) so add "1" as a subscript to gv_currkey */
		i2mval(&tmpmval, trigidx);
		assert(ret_mval == &tmpmval);
		COPY_SUBS_TO_GVCURRKEY(ret_mval, gv_cur_region, gv_currkey, was_null, is_null); /* updates gv_currkey */
		/* Read in ^#t("GBL",1,"TRIGNAME")="GBL#1" */
		is_defined =  gvtr_get_hasht_gblsubs((mval *)&literal_trigname, ret_mval);
		if (!is_defined)
			GVTR_HASHT_GVN_DEFINITION_RETRY_OR_ERROR(trigidx,",\"TRIGNAME\"", csa);
		trigdsc->rtn_desc.rt_name = ret_mval->str;	/* Copy trigger name mident */
		trigdsc->gvt_trigger = gvt_trigger;		/* Save ptr to our main gvt_trigger struct for this trigger. With
								 * this and given a gv_trigger_t, we can get to the gvt_trigger_t
								 * block containing the gv_target pointer and thus get the trigger
								 * csa, region and index for loading trigger source later without
								 * having to look anything up again.
								 */
		/* Reserve extra space when triggername is placed in buddy list. This space is used by gtm_trigger() if
		 * the trigger name is not unique within the process. One or two of the chars are appended to the trigger
		 * name until an unused name is found.
		 */
		trigdsc->rtn_desc.rt_name.len += TRIGGER_NAME_RESERVED_SPACE;
		GVTR_POOL2BUDDYLIST(gvt_trigger, &trigdsc->rtn_desc.rt_name);
		trigdsc->rtn_desc.rt_name.len -= TRIGGER_NAME_RESERVED_SPACE;	/* Not using the space yet */
		/* Read in ^#t("GBL",1,"CMD")="S,K,ZK,ZTK,ZTR" */
		is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_cmd, ret_mval);
		if (!is_defined)
			GVTR_HASHT_GVN_DEFINITION_RETRY_OR_ERROR(trigidx,",\"CMD\"", csa);
		/* Initialize trigdsc->cmdmask */
		ptr = ret_mval->str.addr;
		ptr_top = ptr + ret_mval->str.len;
		assert(0 == trigdsc->cmdmask);
		for (ptr_start = ptr; ptr <= ptr_top; ptr++)
		{
			if ((ptr == ptr_top) || (',' == *ptr))
			{
				len = UINTCAST(ptr - ptr_start);
				for (cmdtype = 0; cmdtype < GVTR_CMDTYPES; cmdtype++)
				{
					if ((len == gvtr_cmd_mval[cmdtype].str.len)
					    && (0 ==memcmp(ptr_start, gvtr_cmd_mval[cmdtype].str.addr, len)))
					{
						trigdsc->cmdmask |= gvtr_cmd_mask[cmdtype];
						break;
					}
				}
				assert(GVTR_CMDTYPES > cmdtype);
				ptr_start = ptr + 1;
			}
		}
		assert(0 != trigdsc->cmdmask);
		/* Read in ^#t("GBL",1,"GVSUBS")="acn=:,1" */
		is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_gvsubs, ret_mval);
		if (is_defined)
		{	/* At this point, we expect MUPIP TRIGGER to have ensured ret_mval is NOT the empty string "" */
			numsubs = 0;
			numlvsubs = 0;
			ptr_start = ret_mval->str.addr;
			ptr_top = ptr_start + ret_mval->str.len;
			quote_imbalance = FALSE;
			paren_imbalance = 0;
			end_of_subscript = FALSE;
			for (ptr = ptr_start; ptr <= ptr_top; ptr++)
			{
				if (ptr == ptr_top)
					end_of_subscript = TRUE;
				else if ('"' == (ch = *ptr))
				{
					if (!quote_imbalance)
						quote_imbalance = TRUE;	/* start of double-quoted string */
					else if (((ptr + 1) == ptr_top) || ('"' != ptr[1]))
						quote_imbalance = FALSE;
					else
						ptr++;	/* skip past nested double-quote (TWO consecutive double-quotes) */
				} else if (quote_imbalance)
					continue;
				else if ('(' == ch)
					paren_imbalance++;	/* parens can be inside pattern match alternation expressions */
				else if (')' == ch)
				{
					assert(paren_imbalance);	/* should never go negative */
					paren_imbalance--;
				} else if (paren_imbalance)
					continue;
				else if (',' == ch)
					end_of_subscript = TRUE;
				else if ('=' == ch)
					numlvsubs++;
				if (end_of_subscript)
				{	/* End of current subscript */
					assert(!quote_imbalance);
					assert(!paren_imbalance);
					numsubs++;
					end_of_subscript = FALSE;
				}
			}
			/* Initialize trigdsc->numsubs */
			assert(numsubs);
			assert(MAX_GVSUBSCRIPTS > numsubs);
			trigdsc->numsubs = numsubs;
			/* Allocate trigdsc->subsarray */
			trigdsc->subsarray = (gvtr_subs_t *)get_new_element(gvt_trigger->gv_trig_list,
							DIVIDE_ROUND_UP((numsubs * SIZEOF(gvtr_subs_t)), GVTR_LIST_ELE_SIZE));
			cursub = 0;
			subsdsc = trigdsc->subsarray;
			/* Initialize trigdsc->numlvsubs */
			assert(numlvsubs <= numsubs);
			trigdsc->numlvsubs = numlvsubs;
			if (numlvsubs)
			{
				curlvsub = 0;
				/* Allocate trigdsc->lvnamearray */
				trigdsc->lvnamearray = (mname_entry *)get_new_element(gvt_trigger->gv_trig_list,
							DIVIDE_ROUND_UP((numlvsubs * SIZEOF(mname_entry)), GVTR_LIST_ELE_SIZE));
				lvnamedsc = trigdsc->lvnamearray;
				/* Allocate trigdsc->lvindexarray */
				trigdsc->lvindexarray = (uint4 *)get_new_element(gvt_trigger->gv_trig_list,
							DIVIDE_ROUND_UP((numlvsubs * SIZEOF(uint4)), GVTR_LIST_ELE_SIZE));
				lvindexdsc = trigdsc->lvindexarray;
			}
			/* Initialize trigdsc->subsarray, trigdsc->lvindexarray & trigdsc->lvnamearray */
			quote_imbalance = FALSE;
			colon_imbalance = FALSE;
			paren_imbalance = 0;
			end_of_subscript = FALSE;
			for (ptr = ptr_start; ptr <= ptr_top; ptr++)
			{
				assert(ptr_start <= ptr);
				if (ptr == ptr_top)
					end_of_subscript = TRUE;
				else if ('"' == (ch = *ptr))
				{
					if (!quote_imbalance)
						quote_imbalance = TRUE;	/* start of double-quoted string */
					else if (((ptr + 1) == ptr_top) || ('"' != ptr[1]))
						quote_imbalance = FALSE;
					else
						ptr++;	/* skip past nested double-quote (TWO consecutive double-quotes) */
				} else if (quote_imbalance)
					continue;
				else if ('(' == ch)
					paren_imbalance++;	/* parens can be inside pattern match alternation expressions */
				else if (')' == ch)
				{
					assert(paren_imbalance);	/* should never go negative */
					paren_imbalance--;
				} else if (paren_imbalance)
					continue;
				else if (',' == ch)
					end_of_subscript = TRUE;
				else if ('=' == ch)
				{	/* a local variable name has been specified for a subscript */
					assert(curlvsub < numlvsubs);
					*lvindexdsc++ = cursub;
					len = UINTCAST(ptr - ptr_start);
					assert(len);
#					ifdef DEBUG
					/* Check validity of lvname */
					ctype = ctypetab[ptr_start[0]];
					assert((TK_PERCENT == ctype) || (TK_LOWER == ctype) || (TK_UPPER == ctype));
					for (index = 1; index < len; index++)
					{
						ctype = ctypetab[ptr_start[index]];
						assert((TK_LOWER == ctype) || (TK_UPPER == ctype) || (TK_DIGIT == ctype));
					}
#					endif
					lvnamedsc->var_name.len = len;
					lvnamedsc->var_name.addr = (char *)get_new_element(gvt_trigger->gv_trig_list,
										DIVIDE_ROUND_UP(len, GVTR_LIST_ELE_SIZE));
					memcpy(lvnamedsc->var_name.addr, ptr_start, len);
					lvnamedsc->marked = FALSE;
					COMPUTE_HASH_MNAME(lvnamedsc);
					lvnamedsc++;
					curlvsub++;
					assert(lvnamedsc == &trigdsc->lvnamearray[curlvsub]);
					assert(lvindexdsc == &trigdsc->lvindexarray[curlvsub]);
					ptr_start = &ptr[1];
				} else if (';' == ch)
				{	/* End of current range and beginning of new range within same subscript */
					GVTR_PROCESS_GVSUBS(ptr_start, ptr, subsdsc, colon_imbalance, gvt, ret_mval->str.len,
						ret_mval->str.addr);
					/* Assert that irrespective of the type of the subscript, all of them have the
					 * "next_range" member at the same offset so it is safe for us to use any one below.
					 */
					assert(&subsdsc->gvtr_subs_range.next_range == &subsdsc->gvtr_subs_point.next_range);
					assert(&subsdsc->gvtr_subs_pattern.next_range == &subsdsc->gvtr_subs_point.next_range);
					assert(&subsdsc->gvtr_subs_star.next_range == &subsdsc->gvtr_subs_point.next_range);
					subsdsc->gvtr_subs_range.next_range =
							(gvtr_subs_t *)get_new_element(gvt_trigger->gv_trig_list,
									DIVIDE_ROUND_UP(SIZEOF(gvtr_subs_t), GVTR_LIST_ELE_SIZE));
					subsdsc = subsdsc->gvtr_subs_range.next_range;
					ptr_start = &ptr[1];
				} else if (':' == ch)
				{	/* Left side of range */
					status = gvtr_process_range(gvt, subsdsc, GVTR_PARSE_LEFT, ptr_start, ptr);
					assert(0 == status); /* ERR_TRIGSUBSCRANGE is issued only in case of GVTR_PARSE_RIGHT */
					assert(!colon_imbalance);
					colon_imbalance = TRUE;
					ptr_start = &ptr[1];
				}
				if (end_of_subscript)
				{
					assert(!quote_imbalance);
					assert(!paren_imbalance);
					GVTR_PROCESS_GVSUBS(ptr_start, ptr, subsdsc, colon_imbalance, gvt, ret_mval->str.len,
						ret_mval->str.addr);
					cursub++;
					/* We cannot do subsdsc++ because subsdsc could at this point have followed a chain of
					 * "next_range" pointers so could be different from what we had at the start of the loop.
					 */
					subsdsc = &trigdsc->subsarray[cursub];
					assert(subsdsc == &trigdsc->subsarray[cursub]);
					ptr_start = &ptr[1];
					end_of_subscript = FALSE;
				}
			}
			assert(cursub == numsubs);
			assert(!numlvsubs || (curlvsub == numlvsubs));
		}
		/* Else ^#t("GBL","#COUNT") is set to 1 but ^#t("GBL",1,"GVSUBS") is undefined.
		 * Possible if the trigger is defined for the entire ^GBL (i.e. NO subscripts)
		 * All related 5 trigdsc fields are already initialized to either 0 or NULL (by the memset above).
		 */
		/* Read in ^#t("GBL",1,"OPTIONS")="NOI,NOC"	*/
		is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_options, ret_mval);
		if (is_defined)
		{
			trigdsc->options = ret_mval->str;
			GVTR_POOL2BUDDYLIST(gvt_trigger, &trigdsc->options);
		}
		/* Read in ^#t("GBL",1,"DELIM")=<undefined>	*/
		delim_defined = gvtr_get_hasht_gblsubs((mval *)&literal_delim, ret_mval);
		if (delim_defined)
			trigdsc->is_zdelim = FALSE;
		else
		{	/* Read in ^#t("GBL",1,"ZDELIM")="|"		*/
			zdelim_defined = gvtr_get_hasht_gblsubs((mval *)&literal_zdelim, ret_mval);
			if (zdelim_defined)
			{
				assert(!delim_defined); /* DELIM and ZDELIM should NOT have been specified at same time */
				trigdsc->is_zdelim = TRUE;
			}
		}
		if (delim_defined || zdelim_defined)	/* order of || is important since latter is set only if former is FALSE */
		{
			/* Initialize trigdsc->delimiter */
			trigdsc->delimiter = *ret_mval;
			GVTR_POOL2BUDDYLIST(gvt_trigger, &trigdsc->delimiter.str);
		}
		/* Read in ^#t("GBL",1,"PIECES")="2:6;8"	*/
		is_defined = gvtr_get_hasht_gblsubs((mval *)&literal_pieces, ret_mval);
		if (is_defined)
		{	/* First determine # of ';' separated piece-ranges */
			num_pieces = 1;	/* for last piece that does NOT have a ';' */
			ptr_start = ret_mval->str.addr;
			ptr_top = ptr_start + ret_mval->str.len;
			for (ptr = ptr_start; ptr < ptr_top; ptr++)
			{
				if (';' == *ptr)
					num_pieces++;
			}
			trigdsc->numpieces = num_pieces;
			trigdsc->piecearray = (gvtr_piece_t *)get_new_element(gvt_trigger->gv_trig_list,
							DIVIDE_ROUND_UP((num_pieces * SIZEOF(gvtr_piece_t)), GVTR_LIST_ELE_SIZE));
			index = 0;
			minpiece = 0;
			for (ptr = ptr_start; ptr <= ptr_top; ptr++)
			{
				if ((ptr == ptr_top) || (';' == *ptr))
				{
					maxpiece = (uint4)asc2i((uchar_ptr_t)ptr_start, INTCAST(ptr - ptr_start));
					if (!minpiece)
						minpiece = maxpiece;
					ptr_start = ptr + 1;
					trigdsc->piecearray[index].min = minpiece;
					trigdsc->piecearray[index].max = maxpiece;
					minpiece = 0;
					index++;
				} else if (':' == *ptr)
				{
					minpiece = (uint4)asc2i((uchar_ptr_t)ptr_start, INTCAST(ptr - ptr_start));
					ptr_start = ptr + 1;
				}
			}
			assert(index == num_pieces);
		}
		/* Read in ^#t("GBL",1,"CHSET")="UTF-8". If CHSET does not match gtm_chset issue error. */
		is_defined =  gvtr_get_hasht_gblsubs((mval *)&literal_chset, ret_mval);
		if (!is_defined)
			GVTR_HASHT_GVN_DEFINITION_RETRY_OR_ERROR(trigidx,",\"CHSET\"", csa);
		if ((!gtm_utf8_mode && ((STR_LIT_LEN(CHSET_M_STR) != ret_mval->str.len)
						|| memcmp(ret_mval->str.addr, CHSET_M_STR, STR_LIT_LEN(CHSET_M_STR))))
			|| (gtm_utf8_mode && ((STR_LIT_LEN(CHSET_UTF8_STR) != ret_mval->str.len)
						|| memcmp(ret_mval->str.addr, CHSET_UTF8_STR, STR_LIT_LEN(CHSET_UTF8_STR)))))
		{	/* CHSET mismatch */
			SAVE_VAR_NAME(save_var_name, save_var_name_len, gvt);
			SAVE_RTN_NAME(save_rtn_name, save_rtn_name_len, trigdsc);
			GVTR_HASHTGBL_READ_CLEANUP(TRUE);
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(8) ERR_TRIGINVCHSET, 6, save_rtn_name_len, save_rtn_name,
				save_var_name_len, save_var_name, ret_mval->str.len, ret_mval->str.addr);
		}
		/* Defer loading xecute string until time to compile it */
		/* trigdsc->rtn_desc is already NULL-initialized as part of the memset above */
		gv_currkey->end = currkey_end;	/* remove the current <trigidx> and make way for the next <trigidx> in gv_currkey */
		gv_currkey->base[currkey_end] = KEY_DELIMITER;    /* restore terminator for entire key */
	}
	/* Now that ALL triggers for this global have been read, separate the triggers into types. There is a link for each command
	 * type in each trigger and an anchor for the list in gvt_trigger. Each list is circular for that type. Since triggers can
	 * be for more than one command type (e.g. SET, KILL, etc), each trigger can be on more than one queue. Later when we need
	 * to run the triggers of a given type, we can just pick the queue and run it.
	 */
	gv_trig_array = gvt_trigger->gv_trig_array;
	assert(NULL != gv_trig_array);
	gvt_trigger->set_triglist = gvt_trigger->kill_triglist = gvt_trigger->ztrig_triglist = NULL;
	DEBUG_ONLY(icntset = icntkill = icntztrig = 0);
	for (trigdsc = gv_trig_array, trigtop = trigdsc + num_gv_triggers; trigdsc < trigtop; trigdsc++)
	{
		if (0 != (trigdsc->cmdmask & gvtr_cmd_mask[GVTR_CMDTYPE_SET]))
		{
			PUT_TRIGGER_ON_CMD_TYPE_QUEUE(trigdsc, gvt_trigger, set);
			DEBUG_ONLY(++icntset);
		}
		if ((0 != (trigdsc->cmdmask & gvtr_cmd_mask[GVTR_CMDTYPE_KILL]))
			   || (0 != (trigdsc->cmdmask & gvtr_cmd_mask[GVTR_CMDTYPE_ZKILL]))
			   || (0 != (trigdsc->cmdmask & gvtr_cmd_mask[GVTR_CMDTYPE_ZTKILL])))
		{
			PUT_TRIGGER_ON_CMD_TYPE_QUEUE(trigdsc, gvt_trigger, kill);
			DEBUG_ONLY(++icntkill);
		}
		if (0 != (trigdsc->cmdmask & gvtr_cmd_mask[GVTR_CMDTYPE_ZTRIGGER]))
		{
			PUT_TRIGGER_ON_CMD_TYPE_QUEUE(trigdsc, gvt_trigger, ztrig);
			DEBUG_ONLY(++icntztrig);
		}
	}
	gvt_trigger->gv_trig_top = trigdsc;	/* Very top of the array */
#	ifdef DEBUG	/* Verify that the queues are well built */
	{
		cntset = cntkill = cntztrig = 0;
		trigstart = gvt_trigger->set_triglist;
		trigtop = NULL;
		for (trigdsc = trigstart; (NULL != trigdsc) && (trigdsc != trigtop); trigdsc = trigdsc->next_set)
		{
			trigtop = trigstart;	/* Stop when we get back here */
			cntset++;
		}

		trigstart = gvt_trigger->kill_triglist;
		trigtop = NULL;
		for (trigdsc = trigstart; (NULL != trigdsc) && (trigdsc != trigtop); trigdsc = trigdsc->next_kill)
		{
			trigtop = trigstart;	/* Stop when we get back here */
			cntkill++;
		}

		trigstart = gvt_trigger->ztrig_triglist;
		trigtop = NULL;
		for (trigdsc = trigstart; (NULL != trigdsc) && (trigdsc != trigtop); trigdsc = trigdsc->next_ztrig)
		{
			trigtop = trigstart;	/* Stop when we get back here */
			cntztrig++;
		}
		assert(cntset == icntset);
		assert(cntkill == icntkill);
		assert(cntztrig == cntztrig);
	}
#	endif
	GVTR_HASHTGBL_READ_CLEANUP(FALSE);	/* do NOT free gvt->gvt_trigger so pass FALSE */
	DBGTRIGR((stderr, "gvtr_db_read_hasht: gvt_trigger->gv_trigger_cycle = cycle\n"));
	/* Now that ^#t has been read, we update "cycle" to the higher value. In case this transaction restarts,
	 * we cannot be sure of the correctness of whatever we read so we need to undo the "cycle" update.
	 * We take care of this by setting "gvt_triggers_read_this_tn" to TRUE and use this in "tp_clean_up".
	 * Set gvt->trig_read_tn as well so this gvt is part of the list of gvts whose cycle gets restored in tp_clean_up.
	 */
	gvt_trigger->gv_trigger_cycle = cycle;
	TREF(gvt_triggers_read_this_tn) = TRUE;
	gvt->trig_read_tn = local_tn;
	return;
}

/* Determine if a given trigger matches the input key.
 * Expects key to be parsed into an array of pointers to the individual subscripts.
 * Also expects caller to invoke this function only if the # of subscripts in key is equal to that in the trigger.
 * Uses the function "gvsub2str" (in some cases) which in turn expects gv_target to be set appropriately.
 */
STATICFNDEF	boolean_t	gvtr_is_key_a_match(char *keysub_start[], gv_trigger_t *trigdsc, mval *lvvalarray[])
{
	uint4		numsubs, keysub;
	gvtr_subs_t	*subsdsc, *substop, *subsdsc1;
	uint4		subs_type;
	char		*thissub;
	mval		*keysub_mval;
	uint4		thissublen, len1, len2, min;
	boolean_t	thissub_matched, left_side_matched, right_side_matched;
	int		cmpres;

	numsubs = trigdsc->numsubs;
	assert(numsubs);
	subsdsc = trigdsc->subsarray;
	for (keysub = 0; keysub < numsubs; keysub++, subsdsc++)
	{
		thissub_matched = FALSE;
		/* For each key subscript, check against the trigger subscript (or chain of subscripts in case of SETs of ranges) */
		subsdsc1 = subsdsc; /* note down separately since we might need to follow chain of "next_range" within this subsc */
		do
		{
			subs_type = subsdsc1->gvtr_subs_type;
			switch(subs_type)
			{
				case GVTR_SUBS_STAR:
					/* easiest case, * matches any subscript so skip to the next subscript */
					thissub_matched = TRUE;
					break;
				case GVTR_SUBS_PATTERN:
					/* Determine match by reverse transforming subscript to string format and invoking
					 * do_pattern. Before that check if transformation has already been done. If so use
					 * that instead of redoing it. If not, do transformation and store it in M-stack
					 * (to protect it from garbage collection) and also update lvvalarray to help with
					 * preventing future recomputations of the same.
					 */
					KEYSUB_S2POOL_IF_NEEDED(keysub_mval, keysub, thissub);
					if (do_pattern(keysub_mval, &subsdsc1->gvtr_subs_pattern.pat_mval))
						thissub_matched = TRUE;
					break;
				case GVTR_SUBS_POINT:
					thissub = keysub_start[keysub];
					thissublen = UINTCAST(keysub_start[keysub + 1] - thissub);
					assert(0 < (int4)thissublen);	/* ensure it is not negative (i.e. huge positive value) */
					if ((thissublen == subsdsc1->gvtr_subs_point.len)
							&& (0 == memcmp(subsdsc1->gvtr_subs_point.subs_key, thissub, thissublen)))
						thissub_matched = TRUE;
					break;
				case GVTR_SUBS_RANGE:
					thissub = keysub_start[keysub];
					thissublen = UINTCAST(keysub_start[keysub + 1] - thissub);
					assert(0 < (int4)thissublen);	/* ensure it is not negative (i.e. huge positive value) */
					/* Check left side of range */
					len1 = subsdsc1->gvtr_subs_range.len1;
					left_side_matched = TRUE;
					if (GVTR_RANGE_OPEN_LEN != len1)
					{
						min = MIN(len1, thissublen);
						cmpres = memcmp(subsdsc1->gvtr_subs_range.subs_key1, thissub, min);
						if ((0 < cmpres) || (0 == cmpres) && (thissublen < len1))
							left_side_matched = FALSE;
					}
					/* Check right side of range */
					len2 = subsdsc1->gvtr_subs_range.len2;
					if (left_side_matched)
					{
						right_side_matched = TRUE;
						if (GVTR_RANGE_OPEN_LEN != len2)
						{
							min = MIN(len2, thissublen);
							cmpres = memcmp(subsdsc1->gvtr_subs_range.subs_key2, thissub, min);
							if ((0 > cmpres) || (0 == cmpres) && (thissublen > len2))
								right_side_matched = FALSE;
						}
						if (right_side_matched)
							thissub_matched = TRUE;
					}
					break;
				default:
					assert(FALSE);
					break;
			}
			if (thissub_matched)
				break;
			assert(&subsdsc1->gvtr_subs_range.next_range == &subsdsc1->gvtr_subs_point.next_range);
			assert(&subsdsc1->gvtr_subs_range.next_range == &subsdsc1->gvtr_subs_pattern.next_range);
			assert(&subsdsc1->gvtr_subs_range.next_range == &subsdsc1->gvtr_subs_star.next_range);
			subsdsc1 = subsdsc1->gvtr_subs_range.next_range;
			if (NULL == subsdsc1)	/* this key subscript did NOT match with trigger */
				return FALSE;
		} while (TRUE);
	}
	return TRUE;
}

void	gvtr_free(gv_namehead *gvt)
{
	gvt_trigger_t		*gvt_trigger;
	gv_trigger_t		*gv_trig_array, *trigdsc, *trigtop;
	buddy_list		*gv_trig_list;
	uint4			num_gv_triggers;

	gvt_trigger = gvt->gvt_trigger;
	if (NULL == gvt_trigger)
		return;
	gv_trig_array = gvt_trigger->gv_trig_array;
	if (NULL != gv_trig_array)
	{
		num_gv_triggers = gvt_trigger->num_gv_triggers;
		assert(0 < num_gv_triggers);
		for (trigdsc = gv_trig_array, trigtop = trigdsc + num_gv_triggers; trigdsc < trigtop; trigdsc++)
		{
			if (NULL != trigdsc->rtn_desc.rt_adr)
				gtm_trigger_cleanup(trigdsc);
		}
		free(gv_trig_array);
		gvt_trigger->gv_trig_array = NULL;
	}
	gv_trig_list = gvt_trigger->gv_trig_list;
	if (NULL != gv_trig_list) /* free up intermediate mallocs */
	{
		cleanup_list(gv_trig_list);
		free(gv_trig_list);
		gvt_trigger->gv_trig_list = NULL;
	}
	free(gvt_trigger);
	gvt->gvt_trigger = NULL;
	gvt->db_trigger_cycle = 0;	/* Force triggers to be reloaded implicitly if not explicitly done by caller */
}

/* Initializes triggers for global variable "gvt" from ^#t global. */
void	gvtr_init(gv_namehead *gvt, uint4 cycle, boolean_t tp_is_implicit, int err_code)
{
	sgmnt_addrs		*csa;
	sgmnt_data_ptr_t	csd;
	uint4			lcl_t_tries, save_t_tries, loopcnt;
	uint4			cycle_start;
	boolean_t		root_srch_needed;
	enum cdb_sc		failure;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(dollar_tlevel);		/* A TP wrap should have been done by the caller if needed */
	assert(!skip_dbtriggers);	/* should not come here if triggers are not supposed to be invoked */
	assert(gv_target == gvt);
	save_t_tries = t_tries; /* note down the value of t_tries at the entry to this function */
	csa = gvt->gd_csa;
	assert(NULL != csa);	/* database for this gvt better be opened at this point */
	/* The directory tree corresponds to a non-existent global which does not have any triggers so in this case
	 * return right away. No trigger initialization needed.
	 */
	if (gvt == csa->dir_tree)
		return;
	/* We know there are no triggers for the ^#t global so return right away in that case too.
	 * In fact it is necessary to do this if "trigger_upgrade" is caller as we dont want a NEEDTRIGUPGRD error there.
	 */
	if (gvt == csa->hasht_tree)
		return;
	csd = csa->hdr;
	assert((gvt->db_trigger_cycle != cycle) || (gvt->db_dztrigger_cycle != csa->db_dztrigger_cycle));
	root_srch_needed = FALSE;
	/* Check if TP was in turn an implicit TP (e.g. created by the GVTR_INIT_AND_TPWRAP_IF_NEEDED macro). */
	if (tp_is_implicit)
	{	/* If implicit TP, we need to go through gvtr_db_tpwrap_helper since we don't want any t_retry calls to end up
		 * invoking MUM_TSTART. This way any restarts signaled during ^#t global reads will be handled internally in
		 * gvtr_init without disturbing the caller gvcst_put/gvcst_kill in any manner. But this assumes that the ^#t
		 * global is the FIRST global reference in this TP transaction. Otherwise the TP restart would have to transfer
		 * control back to wherever that global reference occurred instead of this ^#t global read. Assert that below.
		 */
		cycle_start = csa->db_trigger_cycle;
		ASSERT_BEGIN_OF_FRESH_TP_TRANS;
		lcl_t_tries = t_tries;
		t_fail_hist[lcl_t_tries] = cdb_sc_normal;
		assert(donot_INVOKE_MUMTSTART);
		for (loopcnt = 0; ; loopcnt++)
		{
			/* In case of restart in gvtr_db_read_hasht (handled by gvtr_tpwrap_ch), tp_restart will call tp_clean_up
			 * which will reset sgm_info_ptr to NULL and csa->sgm_info_ptr->tp_set_sgm_done to FALSE. Call tp_set_sgm
			 * to set sgm_info_ptr and first_sgm_info as gvcst_get relies on this being set. Also re-inits
			 * csa->db_trigger_cycle to the latest copy from csd->db_trigger_cycle which is important to detect if
			 * triggers have changed.
			 */
			tp_set_sgm();
			gvtr_db_tpwrap_helper(csa, err_code, root_srch_needed);
			root_srch_needed = FALSE;
			assert(t_tries >= lcl_t_tries);
			if (!dollar_tlevel)
			{	/* Came in as non-tp. Did op_tstart in the caller (GVTR_INIT_AND_TPWRAP_IF_NEEDED).
				 * op_tcommit was completed by gvtr_db_tpwrap_helper as no triggers were defined.
				 * Can break out of the loop.
				 */
				assert(ERR_GVZTRIGFAIL != err_code);
				assert((0 == t_tries) && (NULL == gvt->gvt_trigger));
				break;
			} else
			{
				failure = t_fail_hist[lcl_t_tries];
				if ((lcl_t_tries == t_tries) && (cdb_sc_normal == failure))
				{	/* Read of ^#t completed successfully as there is no change in t_tries.
					 * Safe to break out of the loop.
					 */
					break;
				}
				/* else we encountered a TP restart (which would have triggered a call to t_retry
				 * which in turn would have done a rts_error(TPRETRY) which would have been caught	{BYPASSOK}
				 * by gvtr_tpwrap_ch which would in turn have unwound the C-stack upto the point
				 * where the ESTABLISH is done in gvtr_tpwrap_helper and then returned from there).
				 * In this case we have to keep retrying the read until there are no tp restarts or
				 * an op_tcommit is done by gvtr_db_tpwrap_helper (in the event that no triggers
				 * are defined).
				 */
				assert(dollar_trestart);
				/* Before restarting, check if the restart is due to online rollback. If so, based on whether the
				 * rollback took the database back to a different logical state or not, we need to either issue
				 * an error OR redo the root search of the original global as online rollback related restart
				 * resets root block of all gv_targets to zero.
				 */
				assert(((cdb_sc_onln_rlbk1 != failure) && (cdb_sc_onln_rlbk2 != failure)) || !gv_target->root);
				assert((cdb_sc_onln_rlbk2 != failure) || TREF(dollar_zonlnrlbk));
				if (cdb_sc_onln_rlbk1 == failure)
				{
					root_srch_needed = (ERR_GVPUTFAIL != err_code);
				} else if (cdb_sc_onln_rlbk2 == failure)
				{
					assert(tstart_trigger_depth == gtm_trigger_depth);
					rts_error_csa(CSA_ARG(csa) VARLSTCNT(1) ERR_DBROLLEDBACK);
				}
				/* update lcl_t_tries to reflect the fact that a restart happened */
				lcl_t_tries = t_tries;
				t_fail_hist[lcl_t_tries] = cdb_sc_normal;
			}
			/* We expect the above function to return with either op_tcommit or a tp_restart invoked.
			 * In the case of op_tcommit, we expect dollar_tlevel to be 0 and if so we break out of the loop.
			 * In the tp_restart case, we expect a maximum of 4 tries/retries and much lesser usually.
			 * Additionally we also want to avoid an infinite loop so limit the loop to what is considered
			 * a huge iteration count and assertpro if that is reached as it suggests an out-of-design situation.
			 */
			assertpro(TPWRAP_HELPER_MAX_ATTEMPTS >= loopcnt);
		}
		/* It is possible we have restarted one or more times. If so, it is possible for csa->db_trigger_cycle
		 * to have also been updated one or more times by t_retry() or tp_set_sgm but "cycle" would not have been
		 * refreshed. Since we snapshoted cycle into start_cycle, we can check if csa->db_trigger_cycle has changed.
		 * If it has, we update "cycle" so the correct value gets set into gvt->db_trigger_cycle below.
		 */
		if (cycle_start != csa->db_trigger_cycle)
			cycle = csa->db_trigger_cycle;
	} else
		gvtr_db_read_hasht(csa);
	/* Note that only gvt->db_trigger_cycle (and not CSA->db_trigger_cycle) should be touched here.
	 * CSA->db_trigger_cycle should be left untouched and updated only in t_end/tp_tend in crit.
	 * This way we are protected from the following situation which would arise if we incremented CSA here.
	 * Let us say a TP transaction does TWO updates SET ^X=1,^Y=1 and then goes to commit. Let us say
	 * before the ^X=1 set, csd had a cycle of 10 so that would have been copied over to X's gvt and to
	 * csa. Let us say just before the ^Y=1 set, csd cycle got incremented to 11 (due to a concurrent MUPIP
	 * TRIGGER which reloaded triggers for both ^X and ^Y). Now ^Y's gvt, csa and csd would all have a
	 * cycle of 11.  At commit time, we would check if csa and csd have the same cycle and they do so we
	 * will assume no restart needed when actually a restart is needed because our view of ^X's triggers
	 * is stale even though our view of ^Y's triggers is uptodate. Not touching csa's cycle here saves us
	 * from this situation as csa's cycle will be 10 at commit time which will be different from csd's
	 * cycle of 11 and in turn would cause a restart. This avoids us from otherwise having to go through
	 * all the gvts that participated in this transaction and comparing their cycle with csd.
	 */
	gvt->db_trigger_cycle = cycle;
	gvt->db_dztrigger_cycle = csa->db_dztrigger_cycle; /* No more trigger reads for this global until next $ZTRIGGER() */
	DBGTRIGR((stderr, "gvtr_init(): GVT->db_trigger_cycle=%d ->db_dztrigger_cycle=%d\n",
				gvt->db_trigger_cycle, gvt->db_dztrigger_cycle));
}

/* Determines matching triggers for a given gv_currkey and uses "gtm_trigger" to invokes them with appropriate parameters.
 * Returns 0 for success and ERR_TPRETRY in case a retry is detected.
 */
int	gvtr_match_n_invoke(gtm_trigger_parms *trigparms, gvtr_invoke_parms_t *gvtr_parms)
{
	mident			gbl;
	gvtr_cmd_type_t		gvtr_cmd;
	gvt_trigger_t		*gvt_trigger;
	boolean_t		is_set_trigger, is_ztrig_trigger, ok_to_invoke_trigger;
	char			*key_ptr, *key_start, *key_end;
	char			*keysub_start[MAX_KEY_SZ + 1];
	gv_key			*save_gv_currkey;
	gv_key_buf		save_currkey;
	gv_trigger_t		*trigdsc, *trigstop, *trigstart;
	int			gtm_trig_status, tfxb_status, num_triggers_invoked, trigmax, trig_list_offset;
	mstr			*ztupd_mstr;
	mval			*keysub_mval;
	mval			*lvvalarray[MAX_GVSUBSCRIPTS + 1];
	mval			*ztupd_mval, dummy_mval;
	uint4			*lvindexarray;
	uint4			keysubs, keylen, numlvsubs, curlvsub, lvvalindex, cursub;
	char			*thissub;
	mval			*ret_mval;
	mval			tmpmval;
	unsigned char		util_buff[MAX_TRIG_UTIL_LEN];
	int4			util_len;
#	ifdef DEBUG
	sgmnt_addrs		*csa;
	sgmnt_data_ptr_t	csd;
	sgm_info		*si;
	gv_namehead		*save_targ;
	int			lcl_gtm_trigger_depth;
#	endif

	DEBUG_ONLY(csa = cs_addrs);
	DEBUG_ONLY(csd = cs_data);
	DEBUG_ONLY(si = sgm_info_ptr);
	DEBUG_ONLY(save_targ = gv_target);
	assert(gv_target != csa->dir_tree);
	assert(dollar_tlevel);
	/* Initialize trigger parms that dont depend on the context of the matching trigger */
	gvtr_cmd = gvtr_parms->gvtr_cmd;
	gvt_trigger = gvtr_parms->gvt_trigger;
	trigparms->ztriggerop_new = &gvtr_cmd_mval[gvtr_cmd];
	is_set_trigger = (GVTR_CMDTYPE_SET == gvtr_cmd);
	is_ztrig_trigger = (GVTR_CMDTYPE_ZTRIGGER == gvtr_cmd);
	if (is_set_trigger)
	{
		/* Check that minimal pre-filling of trigparms has already occurred in the caller */
		assert(NULL != trigparms->ztoldval_new);
		assert(NULL != trigparms->ztvalue_new);
		assert(NULL != trigparms->ztdata_new);
		PUSH_MV_STENT(MVST_MVAL);	/* protect $ztupdate from stp_gcol */
		ztupd_mval = &mv_chain->mv_st_cont.mvs_mval;
		ztupd_mval->str.len = 0;
		ztupd_mval->mvtype = 0; /* keep mvtype set to 0 until mval is fully initialized (later below) */
		ztupd_mstr = &ztupd_mval->str;
		trigparms->ztupdate_new = ztupd_mval;
	} else
	{	/* KILL or ZTRIGGER type command */
		ztupd_mval = &dummy_mval;
		trigparms->ztupdate_new = (mval *)&literal_zero;
	}
	trigparms->lvvalarray = lvvalarray;
	trigparms->ztvalue_changed = FALSE;
	/* Parse gv_currkey into array of subscripts to facilitate trigger matching.
	 * Save contents of gv_currkey into local array as the global variable gv_currkey could change
	 * in between invocations of the "gtm_trigger" function in the for loop below. Not just that,
	 * gv_currkey could be even freed and remalloced as a bigger array (if a db with a bigger keysize
	 * gets opened) inside any of the "gtm_trigger" invocations. So we should maintain pointers only
	 * to the local copy (not the global gv_currkey) for use inside all iterations of the for loop.
	 */
	save_gv_currkey = (gv_key *)&save_currkey.key;
	assert(((char *)save_gv_currkey + SIZEOF(gv_key) + gv_currkey->end + 1) < (char *)(&save_currkey + 1));
	MEMCPY_KEY(save_gv_currkey, gv_currkey);
	key_ptr = (char *)save_gv_currkey->base;
	DEBUG_ONLY(key_start = key_ptr;)
	key_end = key_ptr + save_gv_currkey->end;
	assert(KEY_DELIMITER == *key_end);
	assert(KEY_DELIMITER == *(key_end - 1));
	keysubs = 0;
	keysub_start[0] = key_ptr;
	for ( ; key_ptr < key_end; key_ptr++)
	{
		if (KEY_DELIMITER == *key_ptr)
		{
			assert(ARRAYSIZE(keysub_start) > keysubs);
			assert(keysubs < ARRAYSIZE(lvvalarray));
			lvvalarray[keysubs] = NULL;
			keysub_start[keysubs++] = key_ptr + 1;
		}
	}
	assert(keysubs);
	keysubs--;	/* do not count global name as subscript */
	assert(keysub_start[keysubs] == key_end);
	assert(NULL == lvvalarray[keysubs]);
	DEBUG_ONLY(keylen = INTCAST(key_end - key_start));
	assert(!keysubs || keylen);
	/* Match & Invoke triggers. Take care to ensure they are invoked in an UNPREDICTABLE order.
	 * Current implementation is to invoke triggers in a rotating order. For example, each command
	 * type is in a circular queue - say A, B, C. Each time we come here to drive triggers for this
	 * node, bump the gvt_trigger list pointer to the next element in the queue we are processing. So
	 * if triggers A, B, C are on the SET queue, first time we will process A,B,C, next time B,C,A,
	 * and the third, C,A,B.
	 */
	num_triggers_invoked = 0;
	if (is_set_trigger)
	{	/* Chose the set command list to process */
		SELECT_AND_RANDOMIZE_TRIGGER_CHAIN(gvt_trigger, trigstart, trig_list_offset, set);
	} else if (is_ztrig_trigger)
	{	/* Chose the ztrig command list to process */
		SELECT_AND_RANDOMIZE_TRIGGER_CHAIN(gvt_trigger, trigstart, trig_list_offset, ztrig);

	} else
	{	/* Chose the kill command list to process */
		SELECT_AND_RANDOMIZE_TRIGGER_CHAIN(gvt_trigger, trigstart, trig_list_offset, kill);
	}

	trigmax = gvt_trigger->num_gv_triggers;
	trigstop = NULL;			/* So we can get through the first iteration */
	for (trigdsc = trigstart;
	     (NULL != trigdsc) && (trigdsc != trigstop);
	     --trigmax, trigdsc = *(gv_trigger_t **)((char *)trigdsc + trig_list_offset))	/* Follow the designated list */
	{
		DBGTRIGR((stderr, "gvtr_match_n_invoke: top of trigr scan loop (%d)\n", trigmax));
		trigstop = trigstart;		/* Stop when we get back to where we started */
		assertpro(0 <= trigmax);		/* Loop ender "just in case" */
		assert(trigdsc >= gvt_trigger->gv_trig_array);
		assert(trigdsc < gvt_trigger->gv_trig_top);
		assert((trigdsc->cmdmask & gvtr_cmd_mask[gvtr_cmd]) || !is_set_trigger);
		if (!is_set_trigger && !is_ztrig_trigger && !(trigdsc->cmdmask & gvtr_cmd_mask[gvtr_cmd]))
			continue; /* Trigger is for different command. Currently only possible for KILL/ZKILL (asserted above) */
		/* Check that global variables which could have been modified inside gvcst_put/gvcst_kill have been
		 * reset to their default values before going into trigger code as that could cause a nested call to
		 * gvcst_put/gvcst_kill and we dont want any non-default value of this global variable from the parent
		 * gvcst_put/gvcst_kill to be reset by the nested invocation.
		 */
		assert(INVALID_GV_TARGET == reset_gv_target);
		if ((keysubs == trigdsc->numsubs) && (!keysubs || gvtr_is_key_a_match(keysub_start, trigdsc, lvvalarray)))
		{
			/* Note: lvvalarray could be updated above in case any trigger patterns
			 * needed to have been checked for a key-match. Before invoking the trigger,
			 * check if it specified any local variables for subscripts. If so,
			 * initialize any that are not yet already done.
			 */
			numlvsubs = trigdsc->numlvsubs;
			if (numlvsubs)
			{
				lvindexarray = trigdsc->lvindexarray;
				for (curlvsub = 0; curlvsub < numlvsubs; curlvsub++)
				{
					lvvalindex = lvindexarray[curlvsub];
					assert(lvvalindex < keysubs);
					/* lvval not already computed. Do so by reverse transforming subscript to
					 * string format. Store mval containing string in M-stack (to protect it
					 * from garbage collection). Also update lvvalarray to prevent future
					 * recomputations of the same across the many triggers to be checked for
					 * the given key.
					 */
					keysub_mval = lvvalarray[lvvalindex];
					KEYSUB_S2POOL_IF_NEEDED(keysub_mval, lvvalindex, thissub);
				}
			}
			/* Check if trigger specifies a piece delimiter and pieces of interest.
			 * If so, check if any of those pieces are different. If not, trigger should NOT be invoked.
			 */
			ok_to_invoke_trigger = TRUE;
			trigparms->ztdelim_new = (mval *)&literal_null;
			if (is_set_trigger)
			{
				assert(0 == ztupd_mval->mvtype);
				if (trigdsc->delimiter.str.len)
				{
					trigparms->ztdelim_new = (mval *)&trigdsc->delimiter;
					strpiecediff(&trigparms->ztoldval_new->str, &trigparms->ztvalue_new->str,
						&trigdsc->delimiter.str, trigdsc->numpieces, trigdsc->piecearray,
						!trigdsc->is_zdelim && gtm_utf8_mode, ztupd_mstr);
					if (!ztupd_mstr->len)
					{	/* No pieces of interest changed. So dont invoke trigger. */
						DBGTRIGR((stderr, "gvtr_match_n_invoke: Turning off ok_to_invoke_trigger #1\n"));
						ok_to_invoke_trigger = FALSE;
					} else
					{	/* The string containing list of updated pieces is pointing
						 * to a buffer allocated inside "strpiecediff". Since this
						 * function could be called again by a nested trigger, move
						 * this buffer to the stringpool.
						 */
						s2pool(ztupd_mstr);
						ztupd_mval->mvtype = MV_STR;
							/* now ztupd_mval->str is protected from stp_gcol */
					}
				}
			}
			if (ok_to_invoke_trigger)
			{
				DBGTRIGR((stderr, "gvtr_match_n_invoke: Inside trigger drive block\n"));
				DEBUG_ONLY(lcl_gtm_trigger_depth = gtm_trigger_depth);
				if (NULL == trigdsc->rtn_desc.rt_adr)
				{	/* Trigger xecute string not compiled yet, so load it */
					/* Read in ^#t("GBL",1,"XECUTE")="do ^XNAMEinGBL"	*/
					DBGTRIGR((stderr, "gvtr_match_n_invoke: Fetching trigger source\n"));
					tfxb_status = trigger_fill_xecute_buffer(trigdsc);
					if (0 != tfxb_status)
					{
						assert(ERR_TPRETRY == tfxb_status);
						ztupd_mval->mvtype = 0;	/* so stp_gcol - if invoked somehow - can free up any space
									 * currently occupied by this no-longer-necessary mval */
						gvtr_parms->num_triggers_invoked = num_triggers_invoked;
						return tfxb_status;
					}
					assert(NULL != trigdsc->xecute_str.str.addr);
					if (MAX_XECUTE_LEN <= trigdsc->xecute_str.str.len)
					{
						assert(FALSE);
						rts_error_csa(CSA_ARG(NULL) VARLSTCNT(3) ERR_INDRMAXLEN, 1, MAX_XECUTE_LEN);
					}
				}
				gtm_trig_status = gtm_trigger(trigdsc, trigparms);
				/* note: the above call may update trigparms->ztvalue_new for SET type triggers */
				assert(lcl_gtm_trigger_depth == gtm_trigger_depth);
				num_triggers_invoked++;
				ztupd_mval->mvtype = 0;	/* so stp_gcol -if invoked somehow - can free up any space
							 * currently occupied by this no-longer-necessary mval */
				assert((0 == gtm_trig_status) || (ERR_TPRETRY == gtm_trig_status));
				if (0 != gtm_trig_status)
				{
					gvtr_parms->num_triggers_invoked = num_triggers_invoked;
					return gtm_trig_status;
				}
			}
			/* At this time, gv_cur_region, cs_addrs, gv_target, gv_currkey could all be
			 * different from whatever they were JUST before the gtm_trigger() call. All
			 * of them would be restored to what they were after the gtm_trigger_fini call.
			 * It is ok to be in this out-of-sync situation since we dont rely on these
			 * global variables until the "gtm_trigger_fini" call. Any interrupt that
			 * happens before then will anyways know to save/restore those variables in
			 * this that are pertinent in its processing.
			 */
		}
	}
	assert(INVALID_GV_TARGET == reset_gv_target);
	assert(0 <= num_triggers_invoked);
	if (num_triggers_invoked)
		gtm_trigger_fini(FALSE, FALSE);
	/* Verify that gtm_trigger_fini restored gv_cur_region/cs_addrs/cs_data/gv_target/gv_currkey
	 * properly (gtm_trigger could have changed these values depending on the M code that was invoked).
	 */
	assert(csa == cs_addrs);
	assert(csd == cs_data);
	assert(si == sgm_info_ptr);
	assert(gv_target == save_targ);
	assert(0 == memcmp(save_gv_currkey, gv_currkey, OFFSETOF(gv_key, base[0]) + gv_currkey->end));
	DBG_CHECK_GVTARGET_GVCURRKEY_IN_SYNC(CHECK_CSA_TRUE);
	gvtr_parms->num_triggers_invoked = num_triggers_invoked;
	return 0;
}
#endif /* GTM_TRIGGER */
