/****************************************************************
 *								*
 *	Copyright 2010, 2012 Fidelity Information Services, Inc	*
 *								*
 *	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"

#ifdef GTM_TRIGGER
#include "gdsroot.h"			/* for gdsfhead.h */
#include "gdsbt.h"			/* for gdsfhead.h */
#include "gdsfhead.h"			/* For gvcst_protos.h */
#include "dpgbldir.h"
#include "gvcst_protos.h"
#include <rtnhdr.h>
#include "io.h"
#include "iormdef.h"
#include "hashtab_str.h"
#include "wbox_test_init.h"
#include "gv_trigger.h"
#include "trigger_delete_protos.h"
#include "trigger.h"
#include "trigger_incr_cycle.h"
#include "trigger_parse_protos.h"
#include "trigger_update_protos.h"
#include "trigger_compare_protos.h"
#include "trigger_user_name.h"
#include "gtm_string.h"
#include "mv_stent.h"			/* for COPY_SUBS_TO_GVCURRKEY macro */
#include "gvsub2str.h"			/* for COPY_SUBS_TO_GVCURRKEY */
#include "format_targ_key.h"		/* for COPY_SUBS_TO_GVCURRKEY */
#include "targ_alloc.h"			/* for SETUP_TRIGGER_GLOBAL & SWITCH_TO_DEFAULT_REGION */
#include "gdsblk.h"
#include "gdscc.h"			/* needed for tp.h */
#include "gdskill.h"			/* needed for tp.h */
#include "buddy_list.h"			/* needed for tp.h */
#include "hashtab_int4.h"		/* needed for tp.h */
#include "filestruct.h"			/* needed for jnl.h */
#include "jnl.h"			/* needed for tp.h */
#include "tp.h"
#include "min_max.h"			/* Needed for MIN */
#include "mvalconv.h"			/* Needed for MV_FORCE_* */
#include "op.h"
#include "util.h"
#include "op_tcommit.h"
#include "tp_restart.h"
#include "error.h"
#include "file_input.h"
#include "stack_frame.h"
#include "tp_frame.h"
#include "t_retry.h"
#include "gtmimagename.h"

GBLREF	sgmnt_data_ptr_t	cs_data;
GBLREF	sgm_info		*first_sgm_info;
GBLREF	gd_region		*gv_cur_region;
GBLREF	gv_key			*gv_currkey;
GBLREF	gd_addr			*gd_header;
GBLREF	io_pair			io_curr_device;
GBLREF	sgm_info		*sgm_info_ptr;
GBLREF	int			tprestart_state;
GBLREF	gv_namehead		*reset_gv_target;
GBLREF	uint4			dollar_tlevel;
GBLREF	boolean_t		dollar_ztrigger_invoked;
GBLREF	trans_num		local_tn;
GBLREF	unsigned int		t_tries;
GBLREF	unsigned char		t_fail_hist[CDB_MAX_TRIES];
#ifdef DEBUG
GBLREF	boolean_t		donot_INVOKE_MUMTSTART;
#endif

error_def(ERR_DBROLLEDBACK);
error_def(ERR_TEXT);
error_def(ERR_TPRETRY);
error_def(ERR_TPRETRY);
error_def(ERR_TRIGDEFBAD);
error_def(ERR_TRIGMODINTP);
error_def(ERR_TRIGMODREGNOTRW);

LITREF	mval			gvtr_cmd_mval[GVTR_CMDTYPES];
LITREF	int4			gvtr_cmd_mask[GVTR_CMDTYPES];
LITREF	mval			literal_hasht;
LITREF	mval			literal_one;
LITREF	char 			*trigger_subs[];

#define	MAX_COMMANDS_LEN	32		/* Need room for S,K,ZK,ZTK + room for expansion */
#define	MAX_OPTIONS_LEN		32		/* Need room for NOI,NOC + room for expansion */
#define	MAX_TRIGNAME_SEQ_NUM	999999
#define	LITERAL_M		"M"
#define	OPTIONS_I		1
#define	OPTIONS_NOI		2
#define	OPTIONS_C		4
#define	OPTIONS_NOC		8

#define	ADD_UPDATE_NOCHANGE	0x00
#define	ADD_UPDATE_NAME		0x01
#define	ADD_UPDATE_CMDS		0x02
#define	ADD_UPDATE_OPTIONS	0x04
#define	SUB_UPDATE_NAME		0x10
#define	SUB_UPDATE_CMDS		0x20
#define	SUB_UPDATE_OPTIONS	0x40
#define	SUB_UPDATE_NOCHANGE	0x00
#define	DELETE_REC		0x80

#define	BUILD_COMMAND_BITMAP(BITMAP, COMMANDS)									\
{														\
	char		lcl_cmds[MAX_COMMANDS_LEN + 1];								\
	char		*lcl_ptr;										\
														\
	memcpy(lcl_cmds, COMMANDS, STRLEN(COMMANDS) + 1);							\
	BITMAP = 0;												\
	lcl_ptr = strtok(lcl_cmds, ",");									\
	do													\
	{													\
		switch (*lcl_ptr)										\
		{												\
			case 'S':										\
				BITMAP |= gvtr_cmd_mask[GVTR_CMDTYPE_SET];					\
				break;										\
			case 'K':										\
				BITMAP |= gvtr_cmd_mask[GVTR_CMDTYPE_KILL];					\
				break;										\
			case 'Z':										\
				switch (*(lcl_ptr + 1))								\
				{										\
					case 'K':								\
						BITMAP |= gvtr_cmd_mask[GVTR_CMDTYPE_ZKILL];			\
						break;								\
					case 'T':								\
						switch(*(lcl_ptr + 2))						\
						{								\
							case 'K':						\
								BITMAP |= gvtr_cmd_mask[GVTR_CMDTYPE_ZTKILL];	\
							        break;						\
							case 'R':						\
								BITMAP |= gvtr_cmd_mask[GVTR_CMDTYPE_ZTRIGGER];	\
								break;						\
							default:						\
							        GTMASSERT;					\
								break;						\
						}								\
						break;								\
					default:								\
						GTMASSERT;	/* Parsing should have found invalid command */ \
						break;								\
				}										\
				break;										\
			default:										\
				GTMASSERT;	/* Parsing should have found invalid command */			\
				break;										\
		}												\
	} while (lcl_ptr = strtok(NULL, ","));									\
}

#define	COMMAND_BITMAP_TO_STR(COMMANDS, BITMAP, LEN)								\
{														\
	int		count, cmdtype, lcl_len;								\
	char		*lcl_ptr;										\
														\
	count = 0;												\
	lcl_ptr = COMMANDS;											\
	lcl_len = LEN;												\
	for (cmdtype = 0; cmdtype < GVTR_CMDTYPES; cmdtype++)							\
	{													\
		if (gvtr_cmd_mask[cmdtype] & (BITMAP))								\
		{												\
			ADD_COMMA_IF_NEEDED(count, lcl_ptr, lcl_len);						\
			ADD_STRING(count, lcl_ptr, gvtr_cmd_mval[cmdtype].str.len, gvtr_cmd_mval[cmdtype].str.addr, lcl_len); \
		}												\
	}													\
	*lcl_ptr = '\0';											\
	LEN = STRLEN(COMMANDS);											\
}

#define	BUILD_OPTION_BITMAP(BITMAP, OPTIONS)									\
{														\
	char		lcl_options[MAX_OPTIONS_LEN + 1];							\
	char		*lcl_ptr;										\
														\
	memcpy(lcl_options, OPTIONS, STRLEN(OPTIONS) + 1);							\
	BITMAP = 0;												\
	lcl_ptr = strtok(lcl_options, ",");									\
	if (NULL != lcl_ptr)											\
		do												\
		{												\
			switch (*lcl_ptr)									\
			{											\
				case 'C':									\
					BITMAP |= OPTIONS_C;							\
					break;									\
				case 'I':									\
					BITMAP |= OPTIONS_I;							\
					break;									\
				case 'N':									\
					assert('O' == *(lcl_ptr + 1));						\
					switch (*(lcl_ptr + 2))							\
					{									\
						case 'C':							\
							BITMAP |= OPTIONS_NOC;					\
							break;							\
						case 'I':							\
							BITMAP |= OPTIONS_NOI;					\
							break;							\
						default:							\
							GTMASSERT;	/* Parsing should have found invalid command */ \
							break;							\
					}									\
					break;									\
				default:									\
					GTMASSERT;	/* Parsing should have found invalid command */		\
					break;									\
			}											\
		} while (lcl_ptr = strtok(NULL, ","));								\
}

#define	OPTION_BITMAP_TO_STR(OPTIONS, BITMAP, LEN)								\
{														\
	int		count, optiontype, lcl_len;								\
	char		*lcl_ptr;										\
														\
	count = 0;												\
	lcl_len = LEN;												\
	lcl_ptr = OPTIONS;											\
	if (OPTIONS_I & BITMAP)											\
	{													\
		ADD_COMMA_IF_NEEDED(count, lcl_ptr, lcl_len);							\
		ADD_STRING(count, lcl_ptr, STRLEN(HASHT_OPT_ISOLATION), HASHT_OPT_ISOLATION, lcl_len);		\
	}													\
	if (OPTIONS_NOI & BITMAP)										\
	{													\
		ADD_COMMA_IF_NEEDED(count, lcl_ptr, lcl_len);							\
		ADD_STRING(count, lcl_ptr, STRLEN(HASHT_OPT_NOISOLATION), HASHT_OPT_NOISOLATION, lcl_len);	\
	}													\
	if (OPTIONS_C & BITMAP)											\
	{													\
		ADD_COMMA_IF_NEEDED(count, lcl_ptr, lcl_len);							\
		ADD_STRING(count, lcl_ptr, STRLEN(HASHT_OPT_CONSISTENCY), HASHT_OPT_CONSISTENCY, lcl_len); 	\
	}													\
	if (OPTIONS_NOC & BITMAP)										\
	{													\
		ADD_COMMA_IF_NEEDED(count, lcl_ptr, lcl_len);							\
		ADD_STRING(count, lcl_ptr, STRLEN(HASHT_OPT_NOCONSISTENCY), HASHT_OPT_NOCONSISTENCY, lcl_len);	\
	}													\
	*lcl_ptr = '\0';											\
	LEN = STRLEN(OPTIONS);											\
}

#define TOO_LONG_REC_KEY_ERROR_MSG										\
{														\
	trig_stats[STATS_ERROR]++;										\
	if (KEY_TOO_LONG == result)										\
		util_out_print_gtmio("^!AD trigger - key larger than max key size", FLUSH, trigvn_len, trigvn);	\
	else													\
		util_out_print_gtmio("^!AD trigger - value larger than max record size", FLUSH, trigvn_len, trigvn);	\
}

#define IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(RESULT)						\
{														\
	if (PUT_SUCCESS != RESULT)										\
	{													\
		TOO_LONG_REC_KEY_ERROR_MSG;									\
		return TRIG_FAILURE;										\
	}													\
}

#define TRIGGER_SAME_NAME_EXISTS_ERROR												\
{																\
	trig_stats[STATS_ERROR]++;												\
	util_out_print_gtmio("a trigger named !AD already exists", FLUSH, value_len[TRIGNAME_SUB], values[TRIGNAME_SUB]);	\
	return TRIG_FAILURE;													\
}

/* This error macro is used for all definition errors where the target is ^#t(GVN,<index>,<required subscript>) */
#define HASHT_GVN_DEFINITION_RETRY_OR_ERROR(INDEX,SUBSCRIPT)					\
{												\
	if (CDB_STAGNATE > t_tries)								\
		t_retry(cdb_sc_triggermod);							\
	else											\
	{											\
		assert(WBTEST_HELPOUT_TRIGDEFBAD == gtm_white_box_test_case_number);		\
		/* format "INDEX,SUBSCRIPT" of ^#t(GVN,INDEX,SUBSCRIPT) in the error message */	\
		SET_PARAM_STRING(util_buff, util_len, INDEX, SUBSCRIPT);			\
		rts_error(VARLSTCNT(8) ERR_TRIGDEFBAD, 6, trigvn_len, trigvn,			\
			trigvn_len, trigvn, util_len, util_buff);				\
	}											\
}

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

#define HASHT_DEFINITION_ERROR(SUBSCRIPT,MOREINFO)					\
{											\
	assert(WBTEST_HELPOUT_TRIGDEFBAD == gtm_white_box_test_case_number);		\
	rts_error(VARLSTCNT(12) ERR_TRIGDEFBAD, 6, trigvn_len, trigvn,		\
		trigvn_len, trigvn, LEN_AND_LIT(SUBSCRIPT),				\
		ERR_TEXT, 2, RTS_ERROR_TEXT(MOREINFO));					\
}

STATICFNDEF boolean_t validate_label(char *trigvn, int trigvn_len)
{
	mval			trigger_label;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	BUILD_HASHT_SUB_SUB_CURRKEY(trigvn, trigvn_len, LITERAL_HASHLABEL, STRLEN(LITERAL_HASHLABEL));
	if (!gvcst_get(&trigger_label)) /* There has to be a #LABEL */
		HASHT_DEFINITION_RETRY_OR_ERROR("\"#LABEL\"","#LABEL was not found")
	return ((trigger_label.str.len == STRLEN(HASHT_GBL_CURLABEL))
		&& (0 == memcmp(trigger_label.str.addr, HASHT_GBL_CURLABEL, trigger_label.str.len)));
}

STATICFNDEF int4 update_commands(char *trigvn, int trigvn_len, int trigger_index, char *new_trig_cmds, char *orig_db_cmds)
{
	mval			mv_trig_indx;
	uint4			orig_cmd_bm, new_cmd_bm;
	int4			result;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	if (!validate_label(trigvn, trigvn_len))
		return INVALID_LABEL;
	BUILD_COMMAND_BITMAP(orig_cmd_bm, orig_db_cmds);
	BUILD_COMMAND_BITMAP(new_cmd_bm, new_trig_cmds);
	i2mval(&mv_trig_indx, trigger_index);
	SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[CMD_SUB], STRLEN(trigger_subs[CMD_SUB]),
		new_trig_cmds, STRLEN(new_trig_cmds), result);
	if (PUT_SUCCESS != result)
		return result;
	if ((0 != (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & orig_cmd_bm)) && (0 == (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & new_cmd_bm)))
	{	/* SET was removed from the commands, so delete the SET specific attributes */
		BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, mv_trig_indx, LITERAL_DELIM, LITERAL_DELIM_LEN);
		gvcst_kill(TRUE);
		BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, mv_trig_indx, LITERAL_ZDELIM, LITERAL_ZDELIM_LEN);
		gvcst_kill(TRUE);
		BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, mv_trig_indx, LITERAL_PIECES, LITERAL_PIECES_LEN);
		gvcst_kill(TRUE);
	}
	trigger_incr_cycle(trigvn, trigvn_len);
	return SUB_UPDATE_CMDS;
}

STATICFNDEF int4 update_options(char *trigvn, int trigvn_len, int trigger_index, char *trig_options, char *option_value)
{
	mval			mv_trig_indx;
	int4			result;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	if (!validate_label(trigvn, trigvn_len))
		return INVALID_LABEL;
	i2mval(&mv_trig_indx, trigger_index);
	SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[OPTIONS_SUB],
		STRLEN(trigger_subs[OPTIONS_SUB]), trig_options, STRLEN(trig_options), result);
	if (PUT_SUCCESS != result)
		return result;
	trigger_incr_cycle(trigvn, trigvn_len);
	return SUB_UPDATE_OPTIONS;
}

STATICFNDEF int4 update_trigger_name(char *trigvn, int trigvn_len, int trigger_index, char *db_trig_name, char *tf_trig_name,
				     uint4 tf_trig_name_len)
{
	mval			mv_trig_indx;
	int4			result;
	uint4			retval;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	retval = NO_NAME_CHANGE;
	if ((0 != tf_trig_name_len) && (tf_trig_name_len != STRLEN(db_trig_name) - 1)
		|| (0 != memcmp(tf_trig_name, db_trig_name, tf_trig_name_len)))
	{
		if (!validate_label(trigvn, trigvn_len))
			return INVALID_LABEL;
		i2mval(&mv_trig_indx, trigger_index);
		tf_trig_name[tf_trig_name_len++] = TRIGNAME_SEQ_DELIM;
		SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, LITERAL_TRIGNAME, STRLEN(LITERAL_TRIGNAME),
			tf_trig_name, tf_trig_name_len, result);
		if (PUT_SUCCESS != result)
			return result;
		cleanup_trigger_name(trigvn, trigvn_len, db_trig_name, STRLEN(db_trig_name));
		trigger_incr_cycle(trigvn, trigvn_len);
		retval = ADD_UPDATE_NAME;
	}
	return retval;
}

STATICFNDEF boolean_t check_unique_trigger_name(char *trigvn, int trigvn_len, char *trigger_name, uint4 trigger_name_len)
{
	sgmnt_addrs		*csa;
	char			save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key			*save_gv_currkey;
	gd_region		*save_gv_cur_region;
	gv_namehead		*save_gv_target;
	sgm_info		*save_sgm_info_ptr;
	boolean_t		status;
	mval			val;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	/* We only check user supplied names for uniqueness (since autogenerated names are unique). */
	if (0 == trigger_name_len)
		return TRUE;
	SAVE_TRIGGER_REGION_INFO;
	SWITCH_TO_DEFAULT_REGION;
	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	if (0 == gv_target->root)
	{
		RESTORE_TRIGGER_REGION_INFO;
		return TRUE;
	}
	assert((MAX_HASH_INDEX_LEN + 1 + MAX_DIGITS_IN_INT) > gv_cur_region->max_rec_size);
	/* $get(^#t("#TNAME",trigger_name) */
	BUILD_HASHT_SUB_SUB_CURRKEY(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), trigger_name, trigger_name_len);
	status = !gvcst_get(&val);
	RESTORE_TRIGGER_REGION_INFO;
	return status;
}

STATICFNDEF int4 add_trigger_hash_entry(char *trigvn, int trigvn_len, char *cmd_value, int trigindx, boolean_t add_kill_hash,
		stringkey *kill_hash, stringkey *set_hash)
{
	sgmnt_addrs		*csa;
	int			hash_indx;
	char			indx_str[MAX_DIGITS_IN_INT];
	uint4			len;
	mval			mv_hash;
	mval			mv_indx, *mv_indx_ptr;
	char			name_and_index[MAX_MIDENT_LEN + 1 + MAX_DIGITS_IN_INT];
	int			num_len;
	char			*ptr;
	int4			result;
	char			save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key			*save_gv_currkey;
	gd_region		*save_gv_cur_region;
	gv_namehead		*save_gv_target;
	sgm_info		*save_sgm_info_ptr;
	boolean_t		set_cmp;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	SAVE_TRIGGER_REGION_INFO;
	SWITCH_TO_DEFAULT_REGION;
	if (gv_cur_region->read_only)
		rts_error(VARLSTCNT(4) ERR_TRIGMODREGNOTRW, 2, REG_LEN_STR(gv_cur_region));
 	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	set_cmp = (NULL != strchr(cmd_value, 'S'));
	mv_indx_ptr = &mv_indx;
	num_len = 0;
	I2A(indx_str, num_len, trigindx);
	assert(MAX_MIDENT_LEN >= trigvn_len);
	memcpy(name_and_index, trigvn, trigvn_len);
	ptr = name_and_index + trigvn_len;
	*ptr++ = '\0';
	memcpy(ptr, indx_str, num_len);
	len = trigvn_len + 1 + num_len;
	if (set_cmp)
	{
		MV_FORCE_UMVAL(&mv_hash, set_hash->hash_code);
		if (0 != gv_target->root)
		{
			BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(LITERAL_HASHTRHASH, STRLEN(LITERAL_HASHTRHASH), mv_hash, "", 0);
			op_zprevious(&mv_indx);
			hash_indx = (0 == mv_indx.str.len) ? 1 : (mval2i(mv_indx_ptr) + 1);
		} else
			hash_indx = 1;
		i2mval(&mv_indx, hash_indx);
		SET_TRIGGER_GLOBAL_SUB_MSUB_MSUB_STR(LITERAL_HASHTRHASH, STRLEN(LITERAL_HASHTRHASH), mv_hash, mv_indx,
			name_and_index,	len, result);
		if (PUT_SUCCESS != result)
		{
			RESTORE_TRIGGER_REGION_INFO;
			return result;
		}
	} else
		set_hash->hash_code = 0;
	if (add_kill_hash)
	{
		MV_FORCE_UMVAL(&mv_hash, kill_hash->hash_code);
		if (0 != gv_target->root)
		{
			BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(LITERAL_HASHTRHASH, STRLEN(LITERAL_HASHTRHASH), mv_hash, "", 0);
			op_zprevious(&mv_indx);
			hash_indx = (0 == mv_indx.str.len) ? 1 : (mval2i(mv_indx_ptr) + 1);
		} else
			hash_indx = 1;
		i2mval(&mv_indx, hash_indx);
		SET_TRIGGER_GLOBAL_SUB_MSUB_MSUB_STR(LITERAL_HASHTRHASH, STRLEN(LITERAL_HASHTRHASH), mv_hash, mv_indx,
			name_and_index,	len, result);
		if (PUT_SUCCESS != result)
		{
			RESTORE_TRIGGER_REGION_INFO;
			return result;
		}
	} else
		kill_hash->hash_code = 0;
	RESTORE_TRIGGER_REGION_INFO;
	return PUT_SUCCESS;
}

STATICFNDEF boolean_t trigger_already_exists(char *trigvn, int trigvn_len, char **values, uint4 *value_len, int *set_index,
					     int *kill_index, boolean_t *set_cmp_result, boolean_t *kill_cmp_result,
					     boolean_t *full_match, stringkey *set_trigger_hash, stringkey *kill_trigger_hash,
					     mval *setname, mval *killname)
{
	sgmnt_addrs		*csa;
	boolean_t		db_has_K;
	boolean_t		db_has_S;
	char			*ptr;
	int			hash_indx;
	boolean_t		kill_cmp, kill_found;
	int			kill_indx;
	boolean_t		name_match;
	char			save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key			*save_gv_currkey;
	gd_region		*save_gv_cur_region;
	gv_namehead		*save_gv_target;
	sgm_info		*save_sgm_info_ptr;
	boolean_t		set_cmp, set_found, set_name_match, kill_name_match;
	int			set_indx;
	mval			trigindx;
	unsigned char		util_buff[MAX_TRIG_UTIL_LEN];
	int4			util_len;
	mval			val;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	SAVE_TRIGGER_REGION_INFO;
	SWITCH_TO_DEFAULT_REGION;
	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	/* test with SET and/or KILL hash */
	set_cmp = (NULL != strchr(values[CMD_SUB], 'S'));
	kill_cmp = ((NULL != strchr(values[CMD_SUB], 'K')) || (NULL != strchr(values[CMD_SUB], 'R')));
	set_found = kill_found = set_name_match = kill_name_match = FALSE;
	if (set_cmp)
	{ /* test for SET hash match if SET command specified */
		set_found = search_triggers(trigvn, trigvn_len, values, value_len, set_trigger_hash, &hash_indx, &set_indx, 0,
				TRUE);
		if (set_found)
		{
			RESTORE_TRIGGER_REGION_INFO;
			i2mval(&trigindx, set_indx);
			BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[TRIGNAME_SUB],
					STRLEN(trigger_subs[TRIGNAME_SUB]));
			if (!gvcst_get(setname)) /* There has to be a name value */
				HASHT_GVN_DEFINITION_RETRY_OR_ERROR(set_indx, ",\"TRIGNAME\"");
			set_name_match = ((value_len[TRIGNAME_SUB] == (setname->str.len - 1))
				&& (0 == memcmp(setname->str.addr, values[TRIGNAME_SUB], value_len[TRIGNAME_SUB])));
		}
	}
	*set_cmp_result = set_found;
	if (kill_cmp || !set_found)
	{ /* if SET is not found OR KILL is specified in commands, test for KILL hash match */
		kill_found = search_triggers(trigvn, trigvn_len, values, value_len, kill_trigger_hash, &hash_indx, &kill_indx, 0,
				FALSE);
		if (kill_found)
		{
			RESTORE_TRIGGER_REGION_INFO;
			i2mval(&trigindx, kill_indx);
			BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[CMD_SUB],
							 STRLEN(trigger_subs[CMD_SUB]));
			if (!gvcst_get(&val))	/* There has to be a command string */
				HASHT_GVN_DEFINITION_RETRY_OR_ERROR(kill_indx, ",\"CMD\"");
			db_has_S = (NULL != memchr(val.str.addr, 'S', val.str.len));
			db_has_K = ((NULL != memchr(val.str.addr, 'K', val.str.len)) ||
				    (NULL != memchr(val.str.addr, 'R', val.str.len)));
			/* Below means
			 * NOT ( Matched trigger has SET && New trigger has SET &&
			 * 	NOT ( Matched trigger has SET + KILL && New trigger has SET + KILL )  )
			 *
			 * KILL is found if:
			 * The matched trigger does not have a SET || The new trigger does not have a SET
			 * But not if the matched trigger has a SET or KILL && the new trigger does not have a SET or KILL
			 */
			kill_found = !(db_has_S && set_cmp && !(db_has_S && db_has_K && set_cmp && kill_cmp));
			/* $get(^#t(trigvn,trigindx,"TRIGNAME") */
			BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[TRIGNAME_SUB],
							 STRLEN(trigger_subs[TRIGNAME_SUB]));
			if (!gvcst_get(killname)) /* There has to be a name string */
				HASHT_GVN_DEFINITION_RETRY_OR_ERROR(kill_indx, ",\"TRIGNAME\"");
			kill_name_match = ((value_len[TRIGNAME_SUB] == (killname->str.len - 1))
				&& (0 == memcmp(killname->str.addr, values[TRIGNAME_SUB], value_len[TRIGNAME_SUB])));
		}
	}
	/* Starting from the beginning:
	 *    Matching both set and kill, but for different records -- don't update the kill record, hence the FALSE
	 *    Matching a set implies matching a kill -- hence the ||
	 */
	if (set_found && kill_found && (set_indx != kill_indx))
	{
		*kill_cmp_result = FALSE;
		*kill_index = kill_indx;
	} else
	{
		*kill_cmp_result = (kill_found || set_found);
		if (!set_found)
		{
			setname->mvtype = MV_STR;
			setname->str.addr = killname->str.addr;
			setname->str.len = killname->str.len;
		}
	}
	*set_index = (set_found) ? set_indx : (kill_found) ? kill_indx : 0;
	/* If there is both a set and a kill and the set components don't match, there is no name match no matter if the kill
	 * components match or not.  If there is no set, then the name match is only based on the kill components.
	 */
	*full_match = (set_cmp) ? set_name_match : kill_name_match;
	RESTORE_TRIGGER_REGION_INFO;
	return (set_found || kill_found);
}

STATICFNDEF int4 add_trigger_cmd_attributes(char *trigvn, int trigvn_len,  int trigger_index, char *trig_cmds, char **values,
			uint4 *value_len, boolean_t set_compare, boolean_t kill_compare, stringkey *kill_hash, stringkey *set_hash)
{
	char			cmd_str[MAX_COMMANDS_LEN];
	int			cmd_str_len;
	uint4			db_cmd_bm;
	mval			mv_hash;
	mval			mv_trig_indx;
	int4			result;
	uint4			tf_cmd_bm;
	uint4			tmp_bm;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	if (!validate_label(trigvn, trigvn_len))
		return INVALID_LABEL;
	BUILD_COMMAND_BITMAP(db_cmd_bm, trig_cmds);
	BUILD_COMMAND_BITMAP(tf_cmd_bm, values[CMD_SUB]);
	/* If the trigger file command string is contained in the database command and either
	 *   1. the trigger file command has no SET components or
	 *   2. the trigger file command matched a database SET component or
	 * then the trigger file command is already in the database, so return.
	 */
	if ((tf_cmd_bm == (db_cmd_bm & tf_cmd_bm))
			&& ((0 == (tf_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_SET])) || set_compare))
		return CMDS_PRESENT;
	/* If the database command string is contained in the trigger file command and the database is only a "SET"
	 * and the trigger file SET matched the database, but not the KILL (which doesn't make sense until you realize that
	 * trigger_already_exists() returns kill_compare as FALSE when the trigger file record matches both SET and KILL, but
	 * the matches are with two different triggers, then the trigger file command is already in the database so return.
	 */
	if ((db_cmd_bm == (db_cmd_bm & tf_cmd_bm))
			&& ((db_cmd_bm == (db_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_SET])) && set_compare && !kill_compare))
		return CMDS_PRESENT;
	/* If merge would combine K and ZTK, it's an error */
	if (((0 != (db_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_KILL])) && (0 != (tf_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_ZTKILL])))
	    || ((0 != (db_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_ZTKILL])) && (0 != (tf_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_KILL]))))
		return K_ZTK_CONFLICT;
	if (!set_compare && kill_compare
		&& (0 != (tf_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_SET])) && (0 != (db_cmd_bm & gvtr_cmd_mask[GVTR_CMDTYPE_SET])))
	{	/* Subtract common (between triggerfile and DB) "non-S" from tf_cmd_bm */
		tmp_bm = gvtr_cmd_mask[GVTR_CMDTYPE_SET];
		COMMAND_BITMAP_TO_STR(values[CMD_SUB], tmp_bm, value_len[CMD_SUB]);
		/* since the KILL matches, update the corresponding trigger's KILLs */
		tmp_bm = db_cmd_bm | (tf_cmd_bm ^ (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & tf_cmd_bm));
		cmd_str_len = ARRAYSIZE(cmd_str);
		COMMAND_BITMAP_TO_STR(cmd_str, tmp_bm, cmd_str_len);
		i2mval(&mv_trig_indx, trigger_index);
		SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[CMD_SUB],
						    STRLEN(trigger_subs[CMD_SUB]), cmd_str, cmd_str_len, result);
		assert(result == PUT_SUCCESS);
		return (result == PUT_SUCCESS) ? ADD_NEW_TRIGGER : result;
	}
	cmd_str_len = ARRAYSIZE(cmd_str);
	COMMAND_BITMAP_TO_STR(cmd_str, db_cmd_bm | tf_cmd_bm, cmd_str_len);
	i2mval(&mv_trig_indx, trigger_index);
	SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[CMD_SUB], STRLEN(trigger_subs[CMD_SUB]),
					    cmd_str, cmd_str_len, result);
	if (PUT_SUCCESS != result)
		return result;
	strcpy(trig_cmds, cmd_str);
	if ((0 != (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & tf_cmd_bm)) && (0 == (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & db_cmd_bm)))
	{	/* need to add SET attributes */
		if (0 < value_len[DELIM_SUB])
		{
			SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[DELIM_SUB],
				STRLEN(trigger_subs[DELIM_SUB]), values[DELIM_SUB], value_len[DELIM_SUB], result);
			if (PUT_SUCCESS != result)
				return result;
		}
		if (0 < value_len[ZDELIM_SUB])
		{
			SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[ZDELIM_SUB],
				STRLEN(trigger_subs[ZDELIM_SUB]), values[ZDELIM_SUB], value_len[ZDELIM_SUB], result);
			if (PUT_SUCCESS != result)
				return result;
		}
		if (0 < value_len[PIECES_SUB])
		{
			SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[PIECES_SUB],
				STRLEN(trigger_subs[PIECES_SUB]), values[PIECES_SUB], value_len[PIECES_SUB], result);
			if (PUT_SUCCESS != result)
				return result;
		}
		if ((0 == (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & db_cmd_bm))
			&& (0 != (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & tf_cmd_bm)))
		{	/* We gained an "S" so we need to add the set hash value */
			result = add_trigger_hash_entry(trigvn, trigvn_len, values[CMD_SUB], trigger_index, FALSE, kill_hash,
					set_hash);
			if (PUT_SUCCESS != result)
				return result;
			MV_FORCE_UMVAL(&mv_hash, set_hash->hash_code);
			SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_MVAL(trigvn, trigvn_len, mv_trig_indx, trigger_subs[BHASH_SUB],
							     STRLEN(trigger_subs[BHASH_SUB]), mv_hash, result);
			if (PUT_SUCCESS != result)
				return result;
		}
	}
	trigger_incr_cycle(trigvn, trigvn_len);
	return ADD_UPDATE_CMDS;
}

STATICFNDEF int4 add_trigger_options_attributes(char *trigvn, int trigvn_len, int trigger_index, char *trig_options, char **values,
						uint4 *value_len)
{
	uint4			db_option_bm;
	mval			mv_trig_indx;
	char			option_str[MAX_OPTIONS_LEN];
	int			option_str_len;
	int4			result;
	uint4			tf_option_bm;
	uint4			tmp_bm;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	BUILD_OPTION_BITMAP(db_option_bm, trig_options);
	BUILD_OPTION_BITMAP(tf_option_bm, values[OPTIONS_SUB]);
	if (tf_option_bm == (db_option_bm & tf_option_bm))
		/* If trigger file OPTIONS is contained in the DB OPTIONS, then trigger file entry is already in DB, just return */
		return OPTIONS_PRESENT;
	tmp_bm = db_option_bm | tf_option_bm;
	if (((0 != (OPTIONS_C & tmp_bm)) && (0 != (OPTIONS_NOC & tmp_bm)))
			|| ((0 != (OPTIONS_I & tmp_bm)) && (0 != (OPTIONS_NOI & tmp_bm))))
		/* Can't combine incompatible options, so triggers are different */
		return OPTION_CONFLICT;
	if (!validate_label(trigvn, trigvn_len))
		return INVALID_LABEL;
	option_str_len = ARRAYSIZE(option_str);
	OPTION_BITMAP_TO_STR(option_str, tmp_bm, option_str_len);
	i2mval(&mv_trig_indx, trigger_index);
	SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, mv_trig_indx, trigger_subs[OPTIONS_SUB],
		STRLEN(trigger_subs[OPTIONS_SUB]), option_str, option_str_len, result);
	if (PUT_SUCCESS != result)
		return result;
	strcpy(trig_options, option_str);
	trigger_incr_cycle(trigvn, trigvn_len);
	return ADD_UPDATE_OPTIONS;
}

STATICFNDEF boolean_t subtract_trigger_cmd_attributes(char *trigvn, int trigvn_len, char *trig_cmds, char **values,
		uint4 *value_len, boolean_t set_cmp, stringkey *kill_hash, stringkey *set_hash)
{
	char			cmd_str[MAX_COMMANDS_LEN];
	int			cmd_str_len;
	uint4			db_cmd_bm;
	uint4			len;
	uint4			tf_cmd_bm;
	uint4			restore_set = 0;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	BUILD_COMMAND_BITMAP(db_cmd_bm, trig_cmds);
	BUILD_COMMAND_BITMAP(tf_cmd_bm, values[CMD_SUB]);
	if (!set_cmp && (0 != (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & tf_cmd_bm)))
	{ /* If the set compare failed, we don't want to consider the SET */
		restore_set = gvtr_cmd_mask[GVTR_CMDTYPE_SET];
		tf_cmd_bm &= ~restore_set;
	}
	if (0 == (db_cmd_bm & tf_cmd_bm))
		/* If trigger file CMD does NOT overlap with the DB CMD, then no match. So no delete.  Just return */
		return 0;
	cmd_str_len = ARRAYSIZE(cmd_str);
	if (db_cmd_bm != (db_cmd_bm & tf_cmd_bm))
	{	/* combine cmds - subtract trigger file attributes from db attributes */
		COMMAND_BITMAP_TO_STR(cmd_str, (db_cmd_bm & tf_cmd_bm) ^ db_cmd_bm, cmd_str_len);
		strcpy(trig_cmds, cmd_str);
		if ((0 != (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & db_cmd_bm))
				&& (0 == (gvtr_cmd_mask[GVTR_CMDTYPE_SET] & ((db_cmd_bm & tf_cmd_bm) ^ db_cmd_bm))))
			/* We lost the "S" so we need to delete the set hash value */
			cleanup_trigger_hash(trigvn, trigvn_len, values, value_len, set_hash, kill_hash, FALSE, 0);
	} else
	{	/* Both cmds are the same - candidate for delete */
		trig_cmds[0] = '\0';
		db_cmd_bm |= restore_set;
		COMMAND_BITMAP_TO_STR(cmd_str, db_cmd_bm, cmd_str_len);
		value_len[CMD_SUB] = cmd_str_len;
		strcpy(values[CMD_SUB], cmd_str);

	}
	return SUB_UPDATE_CMDS;
}

STATICFNDEF boolean_t subtract_trigger_options_attributes(char *trigvn, int trigvn_len, char *trig_options, char *option_value)
{
	uint4			db_option_bm;
	char			option_str[MAX_OPTIONS_LEN];
	int			option_str_len;
	uint4			tf_option_bm;
	uint4			tmp_bm;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	BUILD_OPTION_BITMAP(db_option_bm, trig_options);
	BUILD_OPTION_BITMAP(tf_option_bm, option_value);
	if (((0 != db_option_bm) && (0 != tf_option_bm)) && (0 == (db_option_bm & tf_option_bm)))
		/* If trigger file OPTIONS does NOT overlap with the DB OPTIONS, then no match. So no delete.  Just return */
		return 0;
	if (db_option_bm != (db_option_bm & tf_option_bm))
	{
		/* combine options - subtract trigger file attributes from db attributes */
		tmp_bm = (db_option_bm & tf_option_bm) ^ db_option_bm;
		assert((0 == (OPTIONS_C & tmp_bm)) || (0 == (OPTIONS_NOC & tmp_bm)));
		assert((0 == (OPTIONS_I & tmp_bm)) || (0 == (OPTIONS_NOI & tmp_bm)));
		option_str_len = ARRAYSIZE(option_str);
		OPTION_BITMAP_TO_STR(option_str, tmp_bm, option_str_len);
		strcpy(trig_options, option_str);
	} else
		/* Both options are the same - candidate to delete */
		trig_options[0] = '\0';
	return SUB_UPDATE_OPTIONS;
}

STATICFNDEF int4 modify_record(char *trigvn, int trigvn_len, char add_delete, int trigger_index, char **values, uint4 *value_len,
		mval *trigger_count, boolean_t set_compare, boolean_t kill_compare, stringkey *kill_hash, stringkey *set_hash)
{
	char			db_cmds[MAX_COMMANDS_LEN + 1];
	boolean_t		name_matches;
	int4			result;
	uint4			retval;
	mval			trigindx;
	char			trig_cmds[MAX_COMMANDS_LEN + 1];
	char			trig_name[MAX_USER_TRIGNAME_LEN + 2];	/* One spot for # delimiter and one for trailing 0 */
	char			trig_options[MAX_OPTIONS_LEN + 1];
	unsigned char		util_buff[MAX_TRIG_UTIL_LEN];
	int4			util_len;
	mval			val;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	retval = 0;
	i2mval(&trigindx, trigger_index);
	BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[CMD_SUB], STRLEN(trigger_subs[CMD_SUB]));
	if (!gvcst_get(&val)) /* There has to be a command string */
		HASHT_GVN_DEFINITION_RETRY_OR_ERROR(trigger_index, ",\"CMD\"");
	memcpy(trig_cmds, val.str.addr, val.str.len);
	trig_cmds[val.str.len] = '\0';
	/* get(^#t(GVN,trigindx,"OPTIONS") */
	BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[OPTIONS_SUB],
		STRLEN(trigger_subs[OPTIONS_SUB]));
	if (gvcst_get(&val))
		memcpy(trig_options, val.str.addr, val.str.len);
	else
		val.str.len = 0;
	trig_options[val.str.len] = '\0';
	/* get(^#t(GVN,trigindx,"TRIGNAME") */
	BUILD_HASHT_SUB_MSUB_SUB_CURRKEY(trigvn, trigvn_len, trigindx, trigger_subs[TRIGNAME_SUB],
		STRLEN(trigger_subs[TRIGNAME_SUB]));
	if (gvcst_get(&val))
		memcpy(trig_name, val.str.addr, val.str.len);
	else
		val.str.len = 0;
	trig_name[val.str.len] = '\0';
	if ('+' == add_delete)
	{
		result = add_trigger_cmd_attributes(trigvn, trigvn_len, trigger_index, trig_cmds, values, value_len,
						    set_compare, kill_compare, kill_hash, set_hash);
		switch (result)
		{
			case K_ZTK_CONFLICT:
			case INVALID_LABEL:
			case ADD_NEW_TRIGGER:
			case VAL_TOO_LONG:
			case KEY_TOO_LONG:
				return result;
			default:
				retval = result;
		}
		result = update_trigger_name(trigvn, trigvn_len, trigger_index, trig_name, values[TRIGNAME_SUB],
					     value_len[TRIGNAME_SUB]);
		if ((INVALID_LABEL == result) || (VAL_TOO_LONG == result) || (KEY_TOO_LONG == result))
			return result;
		retval |= result;
		result = add_trigger_options_attributes(trigvn, trigvn_len, trigger_index, trig_options, values, value_len);
		if ((INVALID_LABEL == result) || (VAL_TOO_LONG == result) || (KEY_TOO_LONG == result))
			return result;
		retval |= result;
	} else
	{
		name_matches = (0 == value_len[TRIGNAME_SUB])
			|| ((value_len[TRIGNAME_SUB] == (STRLEN(trig_name) - 1))
				&& (0 == memcmp(values[TRIGNAME_SUB], trig_name, value_len[TRIGNAME_SUB])));
		if (name_matches)
		{
			retval = SUB_UPDATE_NAME;
			memcpy(db_cmds, trig_cmds, SIZEOF(trig_cmds));
			retval |= subtract_trigger_cmd_attributes(trigvn, trigvn_len, trig_cmds, values, value_len, set_compare,
					kill_hash, set_hash);
			retval |= subtract_trigger_options_attributes(trigvn, trigvn_len, trig_options, values[OPTIONS_SUB]);
		}
		if ((0 != (retval & SUB_UPDATE_NAME)) && (0 != (retval & SUB_UPDATE_OPTIONS)) && (0 != (retval & SUB_UPDATE_CMDS)))
		{
			if ((0 == trig_cmds[0]) && (0 == trig_options[0]))
			{
				result = trigger_delete(trigvn, trigvn_len, trigger_count, trigger_index);
				if ((VAL_TOO_LONG == result) || (KEY_TOO_LONG == result))
					return result;
				retval = DELETE_REC;
			} else
			{
				retval = 0;
				if (0 != trig_cmds[0])
				{
					result = update_commands(trigvn, trigvn_len, trigger_index, trig_cmds, db_cmds);
					if (SUB_UPDATE_CMDS != result)
						return result;
					retval |= result;
				}
				if (0 != trig_options[0])
				{
					result = update_options(trigvn, trigvn_len, trigger_index, trig_options,
								values[OPTIONS_SUB]);
					if (SUB_UPDATE_OPTIONS != result)
						return result;
					if (0 != value_len[OPTIONS_SUB])
						retval |= result;
				}
			}
		}
		else
			retval = SUB_UPDATE_NOCHANGE;
	}
	return retval;
}

STATICFNDEF int4 gen_trigname_sequence(char *trigvn, int trigvn_len, mval *trigger_count, char *user_trigname_str,
				       uint4 user_trigname_len)
{
	sgmnt_addrs		*csa;
	char			name_and_index[MAX_MIDENT_LEN + 1 + MAX_DIGITS_IN_INT];
	char			*ptr1;
	int			rndm_int;
	int			seq_num;
	char			save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key			*save_gv_currkey;
	gd_region		*save_gv_cur_region;
	gv_namehead		*save_gv_target;
	sgm_info		*save_sgm_info_ptr;
	int4			result;
	char			*seq_ptr, *uniq_ptr;
	char			trig_name[MAX_USER_TRIGNAME_LEN + 1];
	uint4			trigname_len;
	char			unique_seq_str[NUM_TRIGNAME_SEQ_CHARS + 1];
	mval			val, *val_ptr;
	char			val_str[MAX_DIGITS_IN_INT + 1];
	int			var_count;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(MAX_USER_TRIGNAME_LEN >= user_trigname_len);
	uniq_ptr = unique_seq_str;
	seq_num = 1;
	if (0 == user_trigname_len)
	{	/* autogenerated name  -- might be long */
		trigname_len = MIN(trigvn_len, MAX_AUTO_TRIGNAME_LEN);
		strncpy(trig_name, trigvn, trigname_len);
	} else
	{	/* user supplied name */
		trigname_len = user_trigname_len;
		strncpy(trig_name, user_trigname_str, user_trigname_len);
	}
	SAVE_TRIGGER_REGION_INFO;
	SWITCH_TO_DEFAULT_REGION;
	if (gv_cur_region->read_only)
		rts_error(VARLSTCNT(4) ERR_TRIGMODREGNOTRW, 2, REG_LEN_STR(gv_cur_region));
	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	if (0 == user_trigname_len)
	{	/* autogenerated name */
		val_ptr = &val;
		if (0 != gv_target->root)
		{
			/* $get(^#t("#TNAME",GVN,"#SEQCOUNT")) */
			BUILD_HASHT_SUB_SUB_SUB_CURRKEY(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), trig_name, trigname_len,
				LITERAL_HASHSEQNUM, STRLEN(LITERAL_HASHSEQNUM));
			seq_num = gvcst_get(val_ptr) ? mval2i(val_ptr) + 1 : 1;
			if (MAX_TRIGNAME_SEQ_NUM < seq_num)
			{
				RESTORE_TRIGGER_REGION_INFO;
				return TOO_MANY_TRIGGERS;
			}
		}
		INT2STR(seq_num, uniq_ptr);
		/* set ^#t("#TNAME",GVN,"#SEQCOUNT")++ via unique_seq_str which came from seq_num*/
		SET_TRIGGER_GLOBAL_SUB_SUB_SUB_STR(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), trig_name, trigname_len,
			LITERAL_HASHSEQNUM, STRLEN(LITERAL_HASHSEQNUM), unique_seq_str, STRLEN(unique_seq_str), result);
		if (PUT_SUCCESS != result)
		{
			RESTORE_TRIGGER_REGION_INFO;
			return result;
		}
		/* set ^#t("#TNAME",GVN,"#TNCOUNT")++ */
		BUILD_HASHT_SUB_SUB_SUB_CURRKEY(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), trig_name, trigname_len,
			LITERAL_HASHTNCOUNT, STRLEN(LITERAL_HASHTNCOUNT));
		var_count = gvcst_get(val_ptr) ? mval2i(val_ptr) + 1 : 1;
		i2mval(&val, var_count);
		SET_TRIGGER_GLOBAL_SUB_SUB_SUB_MVAL(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), trig_name, trigname_len,
			LITERAL_HASHTNCOUNT, STRLEN(LITERAL_HASHTNCOUNT), val, result);
		if (PUT_SUCCESS != result)
		{
			RESTORE_TRIGGER_REGION_INFO;
			return result;
		}
	} else
		*uniq_ptr = '\0';	/* user supplied name */
	seq_ptr = user_trigname_str;
	memcpy(seq_ptr, trig_name, trigname_len);
	seq_ptr += trigname_len;
	if (0 == user_trigname_len)
	{	/* Autogenerated */
		*seq_ptr++ = TRIGNAME_SEQ_DELIM;
		memcpy(seq_ptr, unique_seq_str, STRLEN(unique_seq_str));
		seq_ptr += STRLEN(unique_seq_str);
	}
	*seq_ptr = '\0';
	ptr1 = name_and_index;
	memcpy(ptr1, trigvn, trigvn_len);
	ptr1 += trigvn_len;
	*ptr1++ = '\0';
	MV_FORCE_STR(trigger_count);
	memcpy(ptr1, trigger_count->str.addr, trigger_count->str.len);
	SET_TRIGGER_GLOBAL_SUB_SUB_STR(LITERAL_HASHTNAME, STRLEN(LITERAL_HASHTNAME), user_trigname_str, STRLEN(user_trigname_str),
		name_and_index, trigvn_len + 1 + trigger_count->str.len, result);
	if (PUT_SUCCESS != result)
	{
		RESTORE_TRIGGER_REGION_INFO;
		return result;
	}
	*seq_ptr++ = TRIGNAME_SEQ_DELIM;
	*seq_ptr = '\0';
	RESTORE_TRIGGER_REGION_INFO;
	return SEQ_SUCCESS;
}

boolean_t trigger_update_rec(char *trigger_rec, uint4 len, boolean_t noprompt, uint4 *trig_stats, io_pair *trigfile_device,
		int4 *record_num)
{
	char			add_delete;
	char			ans[2];
	sgmnt_addrs		*csa;
	boolean_t		cmd_modified;
	char			db_trig_name[MAX_USER_TRIGNAME_LEN + 1];
	boolean_t		full_match;
	mstr			gbl_name, var_name;
	mname_entry		gvent;
	gv_namehead		*hasht_tree;
	boolean_t		kill_cmp;
	int4			max_len;
	boolean_t 		multi_line, multi_line_xecute;
	mval			mv_hash;
	boolean_t		new_name;
	int			num;
	int4			offset;
	char			*ptr1;
	int4			rec_len;
	int4			rec_num;
	boolean_t		result;
	char			save_currkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	char			save_altkey[SIZEOF(gv_key) + DBKEYSIZE(MAX_KEY_SZ)];
	gv_key			*save_gv_currkey;
	gv_key			*save_gv_altkey;
	gv_namehead		*save_gvtarget;
	boolean_t		set_cmp;
	boolean_t		skip_set_trigger;
	boolean_t		status;
	int			sub_indx;
	char			tcount[MAX_DIGITS_IN_INT];
	char			tfile_rec_val[MAX_BUFF_SIZE];
	char			trig_cmds[MAX_COMMANDS_LEN + 1];
	mval			*trig_cnt_ptr;
	char			trig_name[MAX_USER_TRIGNAME_LEN + 2];	/* One spot for '#' delimiter and one for trailing '\0' */
	char			trig_options[MAX_OPTIONS_LEN + 1];
	char			trigvn[MAX_MIDENT_LEN + 1];
	mval			trigger_count;
	int			trigvn_len;
	int			trigindx, kill_index = -1;
	int4			updates = 0;
	char			*values[NUM_SUBS];
	uint4			value_len[NUM_SUBS];
	stringkey		kill_trigger_hash, set_trigger_hash;
	char			tmp_str[MAX_HASH_INDEX_LEN + 1 + MAX_DIGITS_IN_INT];
	char			xecute_buffer[MAX_XECUTE_LEN];
	mval			xecute_index, xecute_size;
	mval			reportname, reportnamealt;
	io_pair			io_save_device;
	int4			max_xecute_size;
	boolean_t		no_error;
	boolean_t		newtrigger;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert(0 > memcmp(LITERAL_HASHLABEL, LITERAL_MAXHASHVAL, MIN(STRLEN(LITERAL_HASHLABEL), STRLEN(LITERAL_MAXHASHVAL))));
	assert(0 > memcmp(LITERAL_HASHCOUNT, LITERAL_MAXHASHVAL, MIN(STRLEN(LITERAL_HASHCOUNT), STRLEN(LITERAL_MAXHASHVAL))));
	assert(0 > memcmp(LITERAL_HASHCYCLE, LITERAL_MAXHASHVAL, MIN(STRLEN(LITERAL_HASHCYCLE), STRLEN(LITERAL_MAXHASHVAL))));
	assert(0 > memcmp(LITERAL_HASHTNAME, LITERAL_MAXHASHVAL, MIN(STRLEN(LITERAL_HASHTNAME), STRLEN(LITERAL_MAXHASHVAL))));
	assert(0 > memcmp(LITERAL_HASHTNCOUNT, LITERAL_MAXHASHVAL, MIN(STRLEN(LITERAL_HASHTNCOUNT), STRLEN(LITERAL_MAXHASHVAL))));
	rec_num = (NULL == record_num) ? 0 : *record_num;
	gvinit();
	if ((0 == len) || (COMMENT_LITERAL == *trigger_rec))
		return TRIG_SUCCESS;
	if (('-' != *trigger_rec) && ('+' != *trigger_rec))
	{
		trig_stats[STATS_ERROR]++;
		util_out_print_gtmio("missing +/- at start of line: !AD", FLUSH, len, trigger_rec);
		return TRIG_FAILURE;
	}
	add_delete = *trigger_rec++;
	len--;
	if ('-' == add_delete)
	{
		if ((1 == len) && ('*' == *trigger_rec))
		{
			if (!noprompt)
			{
				util_out_print("This operation will delete all triggers.", FLUSH);
				util_out_print("Proceed? [y/n]: ", FLUSH);
				SCANF("%1s", ans);	/* We only need one char, any more would overflow our buffer */
				if ('y' != ans[0] && 'Y' != ans[0])
				{
					util_out_print_gtmio("Triggers NOT deleted", FLUSH);
					return TRIG_SUCCESS;
				}
			}
			trigger_delete_all();
			return TRIG_SUCCESS;
		} else if ((0 == len) || ('^' != *trigger_rec))
		{	/* if the length < 0 let trigger_delete_name respond with the error message */
			if (TRIG_FAILURE == (status = trigger_delete_name(trigger_rec, len, trig_stats)))
				trig_stats[STATS_ERROR]++;
			return status;
		}
	}
	values[GVSUBS_SUB] = tfile_rec_val;	/* GVSUBS will be the first entry set so initialize it */
	max_len = (int4)SIZEOF(tfile_rec_val);
	multi_line_xecute = FALSE;
	no_error = TRUE;
	if (!trigger_parse(trigger_rec, len, trigvn, values, value_len, &max_len, &multi_line_xecute))
	{
		if (multi_line_xecute)
			no_error = FALSE;
		else
		{
			trig_stats[STATS_ERROR]++;
			return TRIG_FAILURE;
		}
	}
	trigvn_len = STRLEN(trigvn);
	set_trigger_hash.str.addr = tmp_str;
	set_trigger_hash.str.len = SIZEOF(tmp_str);
	build_set_cmp_str(trigvn, trigvn_len, values, value_len, &set_trigger_hash.str, multi_line_xecute);
	COMPUTE_HASH_STR(&set_trigger_hash);
	kill_trigger_hash.str.addr = tmp_str;
	kill_trigger_hash.str.len = SIZEOF(tmp_str);
	build_kill_cmp_str(trigvn, trigvn_len, values, value_len, &kill_trigger_hash.str, multi_line_xecute);
	COMPUTE_HASH_STR(&kill_trigger_hash);
	if (multi_line_xecute)
	{
		if (NULL == trigfile_device)
		{
			util_out_print_gtmio("Cannot use multi-line xecute in $ztrigger ITEM", FLUSH);
			return TRIG_FAILURE;
		}
		io_save_device = io_curr_device;
		io_curr_device = *trigfile_device;
		values[XECUTE_SUB] = xecute_buffer;
		value_len[XECUTE_SUB] = 0;
		max_xecute_size = SIZEOF(xecute_buffer);
		multi_line = multi_line_xecute;
		while (multi_line && (0 <= (rec_len = file_input_get(&trigger_rec))))
		{
			rec_num++;
			io_curr_device = io_save_device;	/* In case we have to write an error message */
			no_error &= trigger_parse(trigger_rec, (uint4)rec_len, trigvn, values, value_len, &max_xecute_size,
				&multi_line);
			io_curr_device = *trigfile_device;
		}
		if (NULL != record_num)
			*record_num = rec_num;
		if (!no_error)
		{
			io_curr_device = io_save_device;
			trig_stats[STATS_ERROR]++;
			return TRIG_FAILURE;
		}
		if (0 > rec_len)
		{
			io_curr_device = io_save_device;
			util_out_print_gtmio("Multi-line trigger -XECUTE is not properly terminated", FLUSH);
			trig_stats[STATS_ERROR]++;
			return TRIG_FAILURE;
		}
		STR_HASH(values[XECUTE_SUB], value_len[XECUTE_SUB], set_trigger_hash.hash_code, set_trigger_hash.hash_code);
		STR_HASH(values[XECUTE_SUB], value_len[XECUTE_SUB], kill_trigger_hash.hash_code, kill_trigger_hash.hash_code);
		io_curr_device = io_save_device;
	}
	gbl_name.addr = trigvn;
	gbl_name.len = trigvn_len;
	GV_BIND_NAME_ONLY(gd_header, &gbl_name);
	if (gv_cur_region->read_only)
		rts_error(VARLSTCNT(4) ERR_TRIGMODREGNOTRW, 2, REG_LEN_STR(gv_cur_region));
	csa = gv_target->gd_csa;
	/* Now that the gv_target of the global the trigger refers to is setup, check if we are attempting to modify/delete a
	 * trigger for a global that has already had a trigger fire in this transaction. For these single-region (at a time)
	 * checks, we can do them all the time as they are cheap.
	 */
	if (gv_target->trig_local_tn == local_tn)
		rts_error(VARLSTCNT(1) ERR_TRIGMODINTP);
	csa->incr_db_trigger_cycle = TRUE; /* so that we increment csd->db_trigger_cycle at commit time */
	if (dollar_ztrigger_invoked)
	{	/* increment db_dztrigger_cycle so that next gvcst_put/gvcst_kill in this transaction, on this region, will re-read
		 * triggers. Note that the below increment happens for every record added. So, even if a single trigger file loaded
		 * multiple triggers on the same region, db_dztrigger_cycle will be incremented more than one for same transaction.
		 * This is considered okay since we only need db_dztrigger_cycle to be equal to a different value than
		 * gvt->db_dztrigger_cycle
		 */
		csa->db_dztrigger_cycle++;
	}
	SETUP_TRIGGER_GLOBAL;
	INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;
	new_name = check_unique_trigger_name(trigvn, trigvn_len, values[TRIGNAME_SUB], value_len[TRIGNAME_SUB]);
	cmd_modified = skip_set_trigger = newtrigger = FALSE;
	trigindx = 1;
	assert(('+' == add_delete) || ('-' == add_delete));	/* Has to be + or - */
	if (0 != gv_target->root)
	{
		BUILD_HASHT_SUB_SUB_CURRKEY(trigvn, trigvn_len, LITERAL_HASHCOUNT, STRLEN(LITERAL_HASHCOUNT));
		if (gvcst_get(&trigger_count))
		{
			if (trigger_already_exists(trigvn, trigvn_len, values, value_len, &trigindx, &kill_index, &set_cmp,
						   &kill_cmp, &full_match, &set_trigger_hash, &kill_trigger_hash,
						   &reportname, &reportnamealt))
			{
				if (!new_name && ('+' == add_delete) && (!full_match))
				{
					TRIGGER_SAME_NAME_EXISTS_ERROR;
				}
				if (-1 != kill_index)
				{
					if (0 != value_len[TRIGNAME_SUB])
					{ /* can't match two different trigger with a user defined name */
						trig_stats[STATS_ERROR]++;
						util_out_print_gtmio("Conflicting trigger definition for global ^!AD. Definition " \
						       "matches trigger named !AD and attempts to create a new trigger named !AD",
						       FLUSH, trigvn_len, trigvn, reportnamealt.str.len, reportnamealt.str.addr,
						       value_len[TRIGNAME_SUB], values[TRIGNAME_SUB]);
						return TRIG_FAILURE;
					}
					updates = modify_record(trigvn, trigvn_len, add_delete, kill_index, values, value_len,
						&trigger_count, FALSE, TRUE, &kill_trigger_hash, &set_trigger_hash);
					switch (updates)
					{
						case INVALID_LABEL:
							trig_stats[STATS_ERROR]++;
							util_out_print_gtmio("Current trigger format not compatible to update " \
							       "the trigger on ^!AD named !AD", FLUSH, trigvn_len, trigvn,
							       reportnamealt.str.len, reportnamealt.str.addr);
							return TRIG_FAILURE;
						case VAL_TOO_LONG:
							trig_stats[STATS_ERROR]++;
							util_out_print_gtmio("^!AD trigger - value larger than record size", FLUSH,
								trigvn_len, trigvn);
							return TRIG_FAILURE;
						case K_ZTK_CONFLICT:
							trig_stats[STATS_ERROR]++;
							util_out_print_gtmio("Command options !AD incompatible with trigger on " \
								"^!AD named !AD", FLUSH, value_len[CMD_SUB], values[CMD_SUB],
								trigvn_len, trigvn, reportnamealt.str.len, reportnamealt.str.addr);
							return TRIG_FAILURE;
						default:
							if ((0 != (updates & ADD_UPDATE_CMDS)) ||
							    (0 != (updates & SUB_UPDATE_CMDS)))
							{
								if (0 == trig_stats[STATS_ERROR])
									util_out_print_gtmio("Updated trigger on ^!AD named " \
											     "!AD and ", NOFLUSH, trigvn_len,
											     trigvn, reportnamealt.str.len,
											     reportnamealt.str.addr);
								trig_stats[STATS_MODIFIED]++;
							} else if ((0 != (updates & ADD_UPDATE_NAME)) ||
								   (0 != (updates & SUB_UPDATE_NAME)) ||
								   (0 != (updates & SUB_UPDATE_OPTIONS)) ||
								   (0 != (updates & ADD_UPDATE_OPTIONS)))
							{ /* NAME and OPTIONS cannot change on K-type match */
								assertpro(FALSE);
							} else if (0 != (updates & DELETE_REC))
							{
								if (0 == trig_stats[STATS_ERROR])
									util_out_print_gtmio("Deleted trigger on ^!AD named "\
											     "!AD and ", NOFLUSH, trigvn_len,
											     trigvn, reportnamealt.str.len,
											     reportnamealt.str.addr);
								trig_stats[STATS_DELETED]++;
								/* if trigger deleted, search for possible new SET trigger index */
								if(kill_index < trigindx &&
								   !(trigger_already_exists(trigvn, trigvn_len, values, value_len,
											    &trigindx, &kill_index,
											    &set_cmp, &kill_cmp, &full_match,
											    &set_trigger_hash, &kill_trigger_hash,
											    &reportname, &reportnamealt)))
								{ /* SET trigger found previously is not found again */
									if (CDB_STAGNATE > t_tries)
										t_retry(cdb_sc_triggermod);
									else
									{
										assert(WBTEST_HELPOUT_TRIGDEFBAD == \
												gtm_white_box_test_case_number);
										trig_stats[STATS_ERROR]++;
										util_out_print_gtmio("Previously found trigger on" \
												     "^!AD ,named !AD but cannot " \
												     "find it again",
												     FLUSH, trigvn_len, trigvn,
												     reportnamealt.str.len,
												     reportnamealt.str.addr);
									}
									return TRIG_FAILURE;
								}
							} else
							{
								util_out_print_gtmio("Trigger on ^!AD already present " \
										     "-- same as trigger named !AD and ",
										     NOFLUSH, trigvn_len, trigvn,
										     reportname.str.len, reportname.str.addr);
								trig_stats[STATS_UNCHANGED]++;
							}
					}
				}
				updates = modify_record(trigvn, trigvn_len, add_delete, trigindx, values, value_len,
						&trigger_count, set_cmp, kill_cmp, &kill_trigger_hash, &set_trigger_hash);
				switch (updates)
				{
					case ADD_NEW_TRIGGER:
						if (0 != value_len[TRIGNAME_SUB])
						{ /* can't add a new trigger when you already matched on a name */
							trig_stats[STATS_ERROR]++;
							util_out_print_gtmio("Conflicting trigger definition for global ^!AD." \
									     " Definition matches trigger named !AD and attempts" \
									     " to create a new trigger named !AD", FLUSH,
									     trigvn_len, trigvn,
									     reportname.str.len, reportname.str.addr,
									     value_len[TRIGNAME_SUB], values[TRIGNAME_SUB]);
							return TRIG_FAILURE;
						}
						cmd_modified = TRUE;
						trig_cnt_ptr = &trigger_count;
						num = mval2i(trig_cnt_ptr);
						trigindx = ++num;
						i2mval(&trigger_count, num);
						break;
					case INVALID_LABEL:
						trig_stats[STATS_ERROR]++;
						util_out_print_gtmio("Current trigger format not compatible to update " \
						       "the trigger on ^!AD named !AD", FLUSH, trigvn_len, trigvn,
						       reportname.str.len, reportname.str.addr);
						return TRIG_FAILURE;
					case VAL_TOO_LONG:
						trig_stats[STATS_ERROR]++;
						util_out_print_gtmio("^!AD trigger - value larger than record size", FLUSH,
							trigvn_len, trigvn);
						return TRIG_FAILURE;
					case K_ZTK_CONFLICT:
						trig_stats[STATS_ERROR]++;
						util_out_print_gtmio("Command options !AD incompatible with trigger on " \
							"^!AD named !AD", FLUSH, value_len[CMD_SUB], values[CMD_SUB],
							trigvn_len, trigvn, reportname.str.len, reportname.str.addr);
						return TRIG_FAILURE;
					default:
						skip_set_trigger = TRUE;
						if ((0 != (updates & ADD_UPDATE_NAME)) || (0 != (updates & ADD_UPDATE_CMDS))
							|| (0 != (updates & ADD_UPDATE_OPTIONS)))
						{
							i2mval(&trigger_count, trigindx);
							trig_stats[STATS_MODIFIED]++;
							if (0 == trig_stats[STATS_ERROR])
								util_out_print_gtmio("Updated trigger on ^!AD named !AD", FLUSH,
										     trigvn_len, trigvn, reportname.str.len,
										     reportname.str.addr);
						} else if (0 != (updates & DELETE_REC))
						{
							trig_stats[STATS_DELETED]++;
							if (0 == trig_stats[STATS_ERROR])
								util_out_print_gtmio("Deleted trigger on ^!AD named !AD",
										     FLUSH, trigvn_len, trigvn,
										     reportname.str.len, reportname.str.addr);
						} else if ((0 != (updates & SUB_UPDATE_NAME)) || (0 != (updates & SUB_UPDATE_CMDS))
							   || (0 != (updates & SUB_UPDATE_OPTIONS)))
						{
							trig_stats[STATS_MODIFIED]++;
							if (0 == trig_stats[STATS_ERROR])
								util_out_print_gtmio("Updated trigger on ^!AD named !AD", FLUSH,
										     trigvn_len, trigvn, reportname.str.len,
										     reportname.str.addr);
						} else if ('+' == add_delete)
						{
							if (0 == trig_stats[STATS_ERROR])
							{
								util_out_print_gtmio("Trigger on ^!AD already present -- same" \
									" as trigger named !AD", FLUSH, trigvn_len, trigvn,
									reportname.str.len, reportname.str.addr);
								trig_stats[STATS_UNCHANGED]++;
							}
						} else
						{
							if (0 == trig_stats[STATS_ERROR])
							{
								util_out_print_gtmio("Trigger on ^!AD does not exist - " \
									"no action taken", FLUSH, trigvn_len, trigvn);
								trig_stats[STATS_UNCHANGED]++;
							}
						}
				}
			} else if ('+' == add_delete)
			{
				assert(0 == trigindx);
				if (!new_name)
				{
					TRIGGER_SAME_NAME_EXISTS_ERROR;
				}
				trig_cnt_ptr = &trigger_count;
				num = mval2i(trig_cnt_ptr);
				trigindx = ++num;
				i2mval(&trigger_count, num);
			} else
			{ /* '-' == add_delete */
				if (0 == trig_stats[STATS_ERROR])
				{
					trig_stats[STATS_UNCHANGED]++;
					util_out_print_gtmio("Trigger on ^!AD does not exist - no action taken", FLUSH,
							     trigvn_len, trigvn);
				}
				skip_set_trigger = TRUE;
			}
		} else
			newtrigger = TRUE;
	} else
		newtrigger = TRUE;
	if (newtrigger)
	{
		if ('-' == add_delete)
		{
			if (0 == trig_stats[STATS_ERROR])
				util_out_print_gtmio("Trigger on ^!AD does not exist - no action taken",
						     FLUSH, trigvn_len, trigvn);
			else
				trig_stats[STATS_DELETED]++;
			skip_set_trigger = TRUE;
		} else
		{
			if (!new_name)
			{
				TRIGGER_SAME_NAME_EXISTS_ERROR;
			}
			trigger_count = literal_one;
		}
	}
	/* Since a specified trigger name will grow by 1, copy it to a long enough array */
	if (((0 != (updates & ADD_UPDATE_NAME)) && ('-' != add_delete)) || !skip_set_trigger)
	{
		memcpy(trig_name, values[TRIGNAME_SUB], value_len[TRIGNAME_SUB] + 1);
		values[TRIGNAME_SUB] = trig_name;
		result = gen_trigname_sequence(trigvn, trigvn_len, &trigger_count, values[TRIGNAME_SUB],
					       value_len[TRIGNAME_SUB]);
		if (SEQ_SUCCESS != result)
		{
			if (TOO_MANY_TRIGGERS == result)
				util_out_print_gtmio("^!AD trigger - Too many triggers", FLUSH, trigvn_len, trigvn);
			else
			{
				TOO_LONG_REC_KEY_ERROR_MSG;
			}
			trig_stats[STATS_ERROR]++;
			return TRIG_FAILURE;
		}
	}
	if (!skip_set_trigger && (0 == trig_stats[STATS_ERROR]))
	{
		value_len[TRIGNAME_SUB] = STRLEN(values[TRIGNAME_SUB]);
		values[CHSET_SUB] = (gtm_utf8_mode) ? UTF8_NAME : LITERAL_M;
		value_len[CHSET_SUB] = STRLEN(values[CHSET_SUB]);
		/* set ^#t(GVN,"#LABEL") = "2" */
		SET_TRIGGER_GLOBAL_SUB_SUB_STR(trigvn, trigvn_len, LITERAL_HASHLABEL, STRLEN(LITERAL_HASHLABEL),
					       HASHT_GBL_CURLABEL, STRLEN(HASHT_GBL_CURLABEL), result);
		IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
		trigger_incr_cycle(trigvn, trigvn_len);
		/* set ^#t(GVN,"#COUNT") = trigger_count */
		SET_TRIGGER_GLOBAL_SUB_SUB_MVAL(trigvn, trigvn_len, LITERAL_HASHCOUNT, STRLEN(LITERAL_HASHCOUNT),
						trigger_count, result);
		IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
		for (sub_indx = 0; sub_indx < NUM_SUBS; sub_indx++)
		{
			if (0 >= value_len[sub_indx])	/* subscript index length is zero (no longer used), skip it */
				continue;
			/* set ^#t(GVN,trigger_count,values[sub_indx]) = xecute string */
			SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_STR(trigvn, trigvn_len, trigger_count,
				trigger_subs[sub_indx], STRLEN(trigger_subs[sub_indx]), values[sub_indx],
				value_len[sub_indx], result);
			if (XECUTE_SUB != sub_indx)
			{
				IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
			} else
			{	/* XECUTE_SUB == sub_indx */
				max_len = value_len[XECUTE_SUB];
				assert(0 < max_len);
				if (PUT_SUCCESS != result)
				{	/* xecute string does not fit in one record, break it up */
					i2mval(&xecute_size, max_len);
					num = 0;
					ptr1 = values[XECUTE_SUB];
					i2mval(&xecute_index, num);
					/* set ^#t(GVN,trigger_count,"XECUTE",0) = xecute string length */
					BUILD_HASHT_SUB_MSUB_SUB_MSUB_CURRKEY(trigvn, trigvn_len, trigger_count,
						trigger_subs[sub_indx], STRLEN(trigger_subs[sub_indx]), xecute_index);
					SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_MSUB_MVAL(trigvn, trigvn_len, trigger_count,
						trigger_subs[sub_indx], STRLEN(trigger_subs[sub_indx]), xecute_index,
						xecute_size, result);
					IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
					while (0 < max_len)
					{
						i2mval(&xecute_index, ++num);
						BUILD_HASHT_SUB_MSUB_SUB_MSUB_CURRKEY(trigvn, trigvn_len, trigger_count,
							trigger_subs[sub_indx], STRLEN(trigger_subs[sub_indx]), xecute_index);
						offset = MIN(gv_cur_region->max_rec_size - (gv_currkey->end + 1 + SIZEOF(rec_hdr)),
							     max_len);
						/* set ^#t(GVN,trigger_count,"XECUTE",num) = xecute string[offset] */
						SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_MSUB_STR(trigvn, trigvn_len, trigger_count,
							trigger_subs[sub_indx], STRLEN(trigger_subs[sub_indx]), xecute_index,
							ptr1, offset, result);
						IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
						ptr1 += offset;
						max_len -= offset;
					}
				}
			}
		}
		result = add_trigger_hash_entry(trigvn, trigvn_len, values[CMD_SUB], trigindx, TRUE, &kill_trigger_hash,
				&set_trigger_hash);
		IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
		MV_FORCE_UMVAL(&mv_hash, kill_trigger_hash.hash_code);
		SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_MVAL(trigvn, trigvn_len, trigger_count,
			trigger_subs[LHASH_SUB], STRLEN(trigger_subs[LHASH_SUB]), mv_hash, result);
		IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
		MV_FORCE_UMVAL(&mv_hash, set_trigger_hash.hash_code);
		SET_TRIGGER_GLOBAL_SUB_MSUB_SUB_MVAL(trigvn, trigvn_len, trigger_count, trigger_subs[BHASH_SUB],
			STRLEN(trigger_subs[BHASH_SUB]), mv_hash, result);
		IF_ERROR_THEN_TOO_LONG_ERROR_MSG_AND_RETURN_FAILURE(result);
		trig_stats[STATS_ADDED]++;
		if (cmd_modified)
			util_out_print_gtmio("Modified commands of the trigger on ^!AD named !AD", FLUSH, trigvn_len, trigvn,
					     value_len[TRIGNAME_SUB], values[TRIGNAME_SUB]);
		else
			util_out_print_gtmio("Added trigger on ^!AD named !AD", FLUSH, trigvn_len, trigvn,
					     value_len[TRIGNAME_SUB], values[TRIGNAME_SUB]);
	} else if (0 != trig_stats[STATS_ERROR])
	{
		if ('+' == add_delete)
			trig_stats[STATS_ADDED]++;
		util_out_print_gtmio("No errors", FLUSH);
	}
	return TRIG_SUCCESS;
}

STATICFNDEF boolean_t trigger_update_rec_helper(char *trigger_rec, uint4 len, boolean_t noprompt, uint4 *trig_stats)
{
	enum cdb_sc		cdb_status;
	boolean_t		trigger_status;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	ESTABLISH_RET(trigger_tpwrap_ch, TRIG_FAILURE);
	trigger_status = trigger_update_rec(trigger_rec, len, TRUE, trig_stats, NULL, NULL);
	if (TRIG_SUCCESS == trigger_status)
	{
		GVTR_OP_TCOMMIT(cdb_status);
		if (cdb_sc_normal != cdb_status)
			t_retry(cdb_status);	/* won't return */
	} else
	{	/* Record cannot be committed - undo everything */
		assert(donot_INVOKE_MUMTSTART);
		DEBUG_ONLY(donot_INVOKE_MUMTSTART = FALSE);
		OP_TROLLBACK(-1);		/* returns but kills implicit transaction */
	}
	REVERT;
	return trigger_status;
}

boolean_t trigger_update(char *trigger_rec, uint4 len)
{
	uint4			i;
	uint4			trig_stats[NUM_STATS];
	boolean_t		trigger_status = TRIG_FAILURE;
	mval			ts_mv;
	int			loopcnt;
	DEBUG_ONLY(unsigned int	lcl_t_tries;)
	enum cdb_sc		failure;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;

	for (i = 0; NUM_STATS > i; i++)
		trig_stats[i] = 0;
	ts_mv.mvtype = MV_STR;
	ts_mv.str.len = 0;
	ts_mv.str.addr = NULL;
	if (0 == dollar_tlevel)
	{
		assert(!donot_INVOKE_MUMTSTART);
		DEBUG_ONLY(donot_INVOKE_MUMTSTART = TRUE);
		/* Note down dollar_tlevel before op_tstart. This is needed to determine if we need to break from the for-loop
		 * below after a successful op_tcommit of the $ZTRIGGER operation. We cannot check that dollar_tlevel is zero
		 * since the op_tstart done below can be a nested sub-transaction
		 */
		op_tstart((IMPLICIT_TSTART + IMPLICIT_TRIGGER_TSTART), TRUE, &ts_mv, 0); /* 0 ==> save no locals but RESTART OK */
		/* The following for loop structure is similar to that in module trigger_trgfile.c (function
		 * "trigger_trgfile_tpwrap") and module gv_trigger.c (function gvtr_db_tpwrap) so any changes here
		 * might need to be reflected there as well.
		 */
		for (loopcnt = 0; ; loopcnt++)
		{
			assert(donot_INVOKE_MUMTSTART);	/* Make sure still set */
			DEBUG_ONLY(lcl_t_tries = t_tries);
			trigger_status = trigger_update_rec_helper(trigger_rec, len, TRUE, trig_stats);
			if (0 == dollar_tlevel)
				break;
			assert(0 < t_tries);
			assert((CDB_STAGNATE == t_tries) || (lcl_t_tries == t_tries - 1));
			failure = LAST_RESTART_CODE;
			assert(((cdb_sc_onln_rlbk1 != failure) && (cdb_sc_onln_rlbk2 != failure))
				|| !gv_target || !gv_target->root);
			assert((cdb_sc_onln_rlbk2 != failure) || !IS_GTM_IMAGE || TREF(dollar_zonlnrlbk));
			if (cdb_sc_onln_rlbk2 == failure)
				rts_error(VARLSTCNT(1) ERR_DBROLLEDBACK);
			/* else if (cdb_sc_onln_rlbk1 == status) we don't need to do anything other than trying again. Since this
			 * is ^#t global, we don't need to GVCST_ROOT_SEARCH before continuing with the next restart because the
			 * trigger load logic already takes care of doing INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED before doing the
			 * actual trigger load
			 */
			util_out_print_gtmio("RESTART has invalidated this transaction's previous output.  New output follows.",
					     FLUSH);
			/* 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 GTMASSERT if that is reached as it suggests an out-of-design situation.
			 */
			if (TPWRAP_HELPER_MAX_ATTEMPTS < loopcnt)
				GTMASSERT;
		}
	} else
	{
		trigger_status = trigger_update_rec(trigger_rec, len, TRUE, trig_stats, NULL, NULL);
		assert(0 < dollar_tlevel);
	}
	return (TRIG_FAILURE == trigger_status);
}
#endif /* GTM_TRIGGER */
