File: gv_trigger.h

package info (click to toggle)
fis-gtm 7.1-006-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 32,908 kB
  • sloc: ansic: 344,906; asm: 5,184; csh: 4,859; sh: 2,000; awk: 294; makefile: 73; sed: 13
file content (693 lines) | stat: -rw-r--r-- 35,290 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
/****************************************************************
 *								*
 * Copyright (c) 2010-2021 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.	*
 *								*
 ****************************************************************/

#ifndef GV_TRIGGER_H_INCLUDED
#define GV_TRIGGER_H_INCLUDED

#include "gv_trigger_common.h"	/* ^#t related macros (common to both Unix and VMS) */
#include "hashtab_mname.h"	/* for COMPUTE_HASH_MNAME macro */

error_def(ERR_GVIS);
error_def(ERR_GVZTRIGFAIL);
error_def(ERR_TRIGREPLSTATE);

#define	HASHT_OPT_ISOLATION	"I"
#define	HASHT_OPT_NOISOLATION	"NOI"
#define	HASHT_OPT_CONSISTENCY	"C"
#define	HASHT_OPT_NOCONSISTENCY	"NOC"

typedef enum
{
#define	GV_TRIG_CMD_ENTRY(cmdtype, cmdlit, cmdmaskval)	cmdtype,
#include "gv_trig_cmd_table.h"
#undef GV_TRIG_CMD_ENTRY
	GVTR_CMDTYPES		/* Total number of command types related to triggers */
} gvtr_cmd_type_t;

#define GVTR_SUBS_STAR		1	/* Allow ANY value for this subscript */
#define GVTR_SUBS_POINT		2	/* Allow a FIXED value for this subscript */
#define GVTR_SUBS_RANGE		3	/* Allow a RANGE of values for this subscript */
#define GVTR_SUBS_PATTERN	4	/* Allow all values for subscript that match a specified pattern */

#define	GVTR_RANGE_OPEN_LEN	(uint4)-1	/* length assigned to open side of a range (e.g. "a": has an open right side);
					   	 * is considered an impossible value for the length of any valid subscript */

#define	GVTR_LIST_ELE_SIZE	8	/* size of each element in gv_trig_list buddy list (see comment in gv_trigger.c) */
#define	GV_TRIG_LIST_INIT_ALLOC	256	/* we anticipate 256 bytes to be used by each trigger so start the buddy list there */

#define MAX_TRIG_UTIL_LEN 	40	/* needed for storing the trigger index and the property (CMD, TRIGNAME.. etc.)
					 * in util_buff to be passed as the last parameter for TRIGDEFBAD error message.
					 * Both the index and the property name are guaranteed to be less than 20
					 * and hence MAX_TRIG_UTIL_LEN set to 40 should be enough
					 */
/* Miscellaneous structures needed to build the global variable trigger superstructures : gv_trigger_t and gvt_trigger_t */
typedef struct gvtr_subs_star_struct
{
        uint4   		gvtr_subs_type;
        uint4			filler_8byte_align;
        union gvtr_subs_struct	*next_range;
} gvtr_subs_star_t;

typedef struct gvtr_subs_point_struct
{
        uint4			gvtr_subs_type;
        uint4			len;
        union gvtr_subs_struct	*next_range;
        char			*subs_key;
} gvtr_subs_point_t;

typedef struct gvtr_subs_range_struct
{
        uint4			gvtr_subs_type;
        uint4			len1;
        union gvtr_subs_struct	*next_range;
        char			*subs_key1;
        uint4			len2;
        char			*subs_key2;
} gvtr_subs_range_t;

typedef struct gvtr_subs_pattern_struct
{
        uint4			gvtr_subs_type;
        uint4			filler_8byte_align;
        union gvtr_subs_struct	*next_range;
        mval    		pat_mval;
} gvtr_subs_pattern_t;

typedef union gvtr_subs_struct
{
        uint4                   gvtr_subs_type;
        gvtr_subs_star_t        gvtr_subs_star;
        gvtr_subs_point_t       gvtr_subs_point;
        gvtr_subs_range_t       gvtr_subs_range;
        gvtr_subs_pattern_t     gvtr_subs_pattern;
} gvtr_subs_t;

typedef gtm_num_range_t	gvtr_piece_t;

/* gv_trigger_t is the structure describing the static components of ONE trigger for a given global variable name. At the time of
 * trigger invocation, there is some process-context-specific information that needs to be passed in which is done in a separate
 * gtm_trigger_parms structure (defined in gtm_trigger.h). The below fields are arranged in order to ensure minimal structure
 * padding added by the compiler. This might not seem logical at first (for e.g. the related fields is_zdelim & delimiter are in
 * different sections).
 */
typedef struct gv_trigger_struct
{
	struct gv_trigger_struct	/* 3 chains - one for each command type since a given trigger could be on all 3. */
			*next_set, 	/* Next SET type trigger for this global */
			*next_kill, 	/* Next KILL type trigger for this global */
			*next_ztrig;	/* Next ZTRIGGER type trigger for this global */
	uint4		cmdmask;	/* bitwise OR of all commands defining this trigger in ^#t(<GBL>,<index>,"CMD").
					 * e.g. if ^#t(..,"CMD") is "S,K", cmdmask will be "GVTR_OPER_SET | GVTR_OPER_KILL" */
	uint4		numsubs;	/* # of comma-separated subscripts specified for this particular trigger */
	uint4		numlvsubs;	/* # of subscripts for which the trigger requested a local variable name to be bound.
					 * i.e. numlvsubs <= numsubs is always true. */
	uint4		numpieces;	/* # of contiguous piece ranges specified in the trigger */
	gvtr_subs_t	*subsarray;	/* pointer to an array of "numsubs" number of gvtr_subs_t structures.
					 * NULL if no subscript specified in the trigger (i.e. numsubs = 0) and is very unusual. */
	uint4		*lvindexarray;	/* pointer to an array of "numlvsubs" number of uint4 type fields which contain the index
					 * in "subsarray" of the subscript whose local variable name binding this corresponds to. */
	mname_entry	*lvnamearray;	/* pointer to an array of "numlvsubs" number of mname_entry structures that contain the
					 * local variable names which need to be bound to each subscript at trigger invocation.
					 * If no subscripts have lvns specified, this is NULL.
					 * e.g. if the trigger specified "^GBL(lvn1=:,1,lvn3=:) ..." in this case,
					 * 	numsubs = 3, numlvsubs = 2, lvindexarray[0] = 0, lvindexarray[1] = 2,
					 * 	lvnamearray[0].var_name = "lvn1", lvnamearray[1].var_name = "lvn3", */
	gtm_num_range_t	*piecearray;	/* pointer to an array of "numpieces" ranges (range is the closed interval [min,max]).
					 * e.g, if the piece string specified in ^#t(<GBL>,<index>,"PIECES") is "4:6;8" then
					 *	numpieces = 2
					 *	piecearray[0].min=4, piecearray[0].max=6
					 *	piecearray[1].min=8, piecearray[1].max=8 */
	rtn_tabent	rtn_desc;	/* Compiled routine name/objcode; Inited by gvtr_db_read_hasht() */
	boolean_t	is_zdelim;	/* TRUE if "ZDELIM" was specified; FALSE if "DELIM" was specified */
	mval		delimiter;	/* is a copy of ^#t(<GBL>,<index>,"DELIM") or ^#t(..."ZDELIM") whichever is defined */
	mstr		options;	/* is a copy of ^#t(<GBL>,<index>,"OPTIONS") */
	mval		xecute_str;	/* Trigger code to execute */
	struct gvt_trigger_struct	/* top of gvt_trigger block this trigger belongs to. Used in src lookup when we know */
			*gvt_trigger;	/* .. gv_trigger_t but not owning region or trigger#. Allows us to get both with no
					 * additional lookup */
} gv_trigger_t;

/* Structure describing ALL triggers for a given global variable name */
typedef struct gvt_trigger_struct
{
	uint4				gv_trigger_cycle;	/* copy of ^#t(<gbl>,"#CYCLE") */
	uint4				num_gv_triggers;	/* copy of ^#t(<gbl>,"#COUNT") */
	struct gv_trigger_struct	*set_triglist;		/* -> circular list of SET triggers (using next_set link) */
	struct gv_trigger_struct	*kill_triglist;		/* -> circular list of KILL type triggers (using next_kill link) */
	struct gv_trigger_struct	*ztrig_triglist;	/* -> circular list of ZTRIG triggers (using next_ztrig link) */
	gv_namehead			*gv_target;		/* gv_target that owns these triggers - used in trigr src lkup */
	gv_trigger_t			*gv_trig_top;		/* top of the array of triggers */
	struct buddy_list_struct	*gv_trig_list;		/* buddy list that maintains mallocs done inside gv_trig_array */
	gv_trigger_t			*gv_trig_array;		/* array of triggers read in from ^#t(<gbl>,...) */
} gvt_trigger_t;

/* Structure describing parameters passed (from gvcst_put/gvcst_kill) to trigger invocation routine */
typedef struct gvtr_invoke_parms_struct
{
	gvt_trigger_t	*gvt_trigger;		/* Input parameter */
	gvtr_cmd_type_t	gvtr_cmd;		/* Input parameter */
	int		num_triggers_invoked;	/* Output parameter : # of triggers invoked by an update */
} gvtr_invoke_parms_t;

/* Requires #include of op.h, tp_set_sgm.h, t_begin.h in the caller of this macro */
#define	GVTR_INIT_AND_TPWRAP_IF_NEEDED(CSA, CSD, GVT, GVT_TRGR, LCL_TSTART, IS_TPWRAP, T_ERR)					\
{																\
	GBLREF	boolean_t		skip_dbtriggers;	/* see gbldefs.c for description of this global */		\
	GBLREF	sgmnt_data_ptr_t	cs_data;										\
	GBLREF	uint4			dollar_tlevel;										\
	GBLREF	jnl_gbls_t		jgbl;											\
	GBLREF	uint4			t_err;											\
	GBLREF	trans_num		local_tn;										\
	GBLREF	uint4			update_trans;										\
																\
	DEBUG_ONLY(GBLREF gv_namehead	*reset_gv_target;)									\
	DEBUG_ONLY(GBLREF boolean_t	donot_INVOKE_MUMTSTART;)								\
	DEBUG_ONLY(GBLREF tp_frame	*tp_pointer;)										\
	DEBUG_ONLY(GBLREF stack_frame	*frame_pointer;)									\
	DEBUG_ONLY(GBLREF mv_stent	*mv_chain;)										\
	DEBUG_ONLY(GBLREF unsigned char	*msp;)											\
	DEBUG_ONLY(GBLREF int		tprestart_state;)									\
	DEBUG_ONLY(GBLREF sgm_info	*sgm_info_ptr;)										\
	DEBUG_ONLY(gv_namehead		*save_reset_gv_target;)									\
	DEBUG_ONLY(boolean_t		was_nontp;)										\
																\
	LITREF	mval			literal_batch;										\
	uint4				cycle;											\
	boolean_t			set_upd_trans_t_err, cycle_mismatch, db_trigger_cycle_mismatch, ztrig_cycle_mismatch;	\
	DBGTRIGR_ONLY(char 		gvnamestr[MAX_MIDENT_LEN + 1];)								\
																\
	assert(TPRESTART_STATE_NORMAL == tprestart_state);									\
	assert(!skip_dbtriggers);												\
	/* If start of transaction, read in GVT's triggers from ^#t global if not already done.	If restart and if it was due to	\
	 * GVT's triggers being out-of-date re-read them. Note theoretically either CSD or CSA can be used to get the cycle for	\
	 * comparison with GVT but while using CSD can relatively reduce the # of times "gvtr_init" is invoked, it can also	\
	 * cause an issue where a nested trigger for the same global can cause a restart which unloads a running trigger	\
	 * causing problems (see trigthrash subtest in triggers test suite specifically trigthrash3.m). For that reason, we	\
	 * stick wish CSA.													\
	 */															\
	cycle = CSA->db_trigger_cycle;												\
	assert(CSD == cs_data);													\
	/* triggers can be invoked only by updates currently */									\
	assert(!dollar_tlevel || sgm_info_ptr);											\
	assert((sgm_info_ptr && (dollar_tlevel && sgm_info_ptr->update_trans)) || (!dollar_tlevel && update_trans)); 		\
	set_upd_trans_t_err = FALSE;												\
	ztrig_cycle_mismatch = (CSA->db_dztrigger_cycle && (GVT->db_dztrigger_cycle != CSA->db_dztrigger_cycle));		\
	db_trigger_cycle_mismatch = (GVT->db_trigger_cycle != cycle);								\
	cycle_mismatch = (db_trigger_cycle_mismatch || ztrig_cycle_mismatch);							\
	DBGTRIGR_ONLY(memcpy(gvnamestr, GVT->gvname.var_name.addr, GVT->gvname.var_name.len);)					\
	DBGTRIGR_ONLY(gvnamestr[GVT->gvname.var_name.len]='\0';)								\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: CSA=%s GVT=%s\n", CSA->region->rname, gvnamestr));			\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: CSA->db_trigger_cycle=%d\n", cycle));				\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: GVT->db_trigger_cycle=%d\n", GVT->db_trigger_cycle));		\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: CSA->db_dztrigger_cycle=%d\n", CSA->db_dztrigger_cycle));		\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: GVT->db_dztrigger_cycle=%d\n", GVT->db_dztrigger_cycle));		\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: local_tn=%d\n", local_tn));						\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: GVT->trig_local_tn=%d\n", GVT->trig_local_tn));			\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: ztrig_cycle_mismatch=%d\n", ztrig_cycle_mismatch));			\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: db_trigger_cycle_mismatch=%d\n", db_trigger_cycle_mismatch));	\
	DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: cycle_mismatch=%d t_tries=%d\n", cycle_mismatch, t_tries));		\
	/* Set up wrapper even if no triggers if this is for ZTRIGGER command */						\
	if (cycle_mismatch || (NULL != GVT->gvt_trigger) || (ERR_GVZTRIGFAIL == T_ERR))						\
	{	/* Create TP wrap if needed */											\
		if (!dollar_tlevel)												\
		{	/* need to create implicit TP wrap */									\
			DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: creating TP wrapper\n"));				\
			DEBUG_ONLY(was_nontp = TRUE;)										\
			assert(!LCL_TSTART);											\
			/* Set a debug-only global variable to indicate that from now onwards, until the completion of this	\
			 * tp-wrapped non-tp update, we dont expect "t_retry" to be called while this gvcst_put is in the	\
			 * C-call-stack. This is because we have not set up the TP transaction using opp_tstart (like what M	\
			 * code does) and so there is no point to go back to generated code (mdb_condition_handler invoked from	\
			 * t_retry does this transfer of control using the MUM_TSTART macro). We instead expect to handle	\
			 * retries internally in gvcst_put. We also expect any restarts occurring in nested trigger code to	\
			 * eventually end up as a RESTART return code from "gtm_trigger" so we get to choose how to handle the	\
			 * restart for this implicit TSTART.									\
			 */													\
			assert(!donot_INVOKE_MUMTSTART);									\
			DEBUG_ONLY(donot_INVOKE_MUMTSTART = TRUE;)								\
			/* With journal recovery, we expect it to play non-TP journal records as non-TP transactions and ZTP	\
			 * journal records as ZTP transactions so we dont expect an implicit TP wrap to be done inside recovery	\
			 * due to a trigger (as this means GT.M and recovery have different values for GVT->gvt_trigger which	\
			 * is not possible). Assert that.									\
			 */													\
			assert(!jgbl.forw_phase_recovery);									\
			LCL_TSTART = TRUE;											\
			 /* 0 ==> save no locals but RESTART OK */ 								\
			op_tstart((IMPLICIT_TSTART + IMPLICIT_TRIGGER_TSTART), TRUE, &literal_batch, 0);			\
			/* Ensure that the op_tstart done above has set up the TP frame and that the first entry is		\
			 * of MVST_TPHOLD type.											\
			 */													\
			assert((tp_pointer->fp == frame_pointer) && (MVST_TPHOLD == mv_chain->mv_st_type)			\
				&& (msp == (unsigned char *)mv_chain));								\
			IS_TPWRAP = TRUE;											\
			assert(!sgm_info_ptr || (!CSA->sgm_info_ptr->tp_set_sgm_done && !CSA->sgm_info_ptr->update_trans));	\
			tp_set_sgm();												\
			/* tp_set_sgm above could modify CSA->db_trigger_cycle (from CSD->db_trigger_cycle). Set local variable	\
			 * cycle to match CSA->db_trigger_cycle so as to pass the updated value to gvtr_init. Also in that	\
			 * case recompute cycle_mismatch (and related variables) now that CSA->db_trigger_cycle changed.	\
			 */													\
			if (cycle != CSA->db_trigger_cycle)									\
			{													\
				cycle = CSA->db_trigger_cycle;									\
				/* Assert that if db_trigger_cycle mismatch was TRUE above,					\
				 * it better be TRUE after 'cycle' update as well.						\
				 */												\
				assert(!db_trigger_cycle_mismatch || (GVT->db_trigger_cycle != cycle));				\
				db_trigger_cycle_mismatch = (GVT->db_trigger_cycle != cycle);					\
				cycle_mismatch = (db_trigger_cycle_mismatch || ztrig_cycle_mismatch);				\
			}													\
			/* An implicit TP wrap is created for an explicit TP update. tp_set_sgm call done above will initalize	\
			 * sgm_info_ptr for this TP transaction and will have sgm_info_ptr->update_trans set to zero. If the 	\
			 * op_tstart done above is for ^#t read, then set_upd_trans_t_err set to TRUE just after gvtr_init will \
			 * take care of resetting si->update_trans and t_err at macro exit. However, if this op_tstart is for	\
			 * the actual transaction (for eg., triggers for this global is already read for this process and an	\
			 * explicit non-tp update is in progress) then we need to set si->update_trans and t_err to correct 	\
			 * values before the macro exit. 									\
			 */													\
			set_upd_trans_t_err = TRUE;										\
			assert(((0 < dollar_tlevel) || (ERR_GVZTRIGFAIL != T_ERR)) && (1)) ;	/* &&(1) idents assert */	\
		} else														\
		{														\
			/* Already in TP */											\
			DEBUG_ONLY(was_nontp = FALSE;)										\
			assert(sgm_info_ptr == CSA->sgm_info_ptr);								\
		}														\
		if (cycle_mismatch)												\
		{	/* Process' trigger view changed. Re-read triggers */							\
			assert(GVT->gd_csa == CSA);										\
			if ((local_tn == GVT->trig_local_tn) && db_trigger_cycle_mismatch)					\
			{	/* Already dispatched trigger for this gvn in this transaction - must restart. But do so ONLY	\
				 * if the process' trigger view changed because of a concurrent trigger load/unload and NOT	\
				 * because of $ZTRIGGER as part of this transaction as that could cause unintended restarts.	\
				 */												\
				assert(!LCL_TSTART);										\
				assert(UPDATE_CAN_RETRY(t_tries, t_fail_hist[t_tries]));					\
				DBGTRIGR((stderr, "GVTR_INIT_AND_TPWRAP_IF_NEEDED: throwing TP restart\n"));			\
				t_retry(cdb_sc_triggermod);									\
			}													\
			DEBUG_ONLY(save_reset_gv_target = reset_gv_target); 							\
			gvtr_init(GVT, cycle, LCL_TSTART, T_ERR);								\
			/* ^#t reads done via gvtr_init will cause t_err to be set to GVGETFAIL which needs to be reset to 	\
			 * T_ERR (incoming parameter to this macro) before leaving the macro. Also, in case of an implicit	\
			 * tstart (LCL_TSTART = TRUE), if a restart happens while reading ^#t global, gvtr_tpwrap_ch will be	\
			 * invoked which inturn will invoke tp_restart that would reset sgm_info_ptr->update_trans to zero. The	\
			 * caller of the macro (gvcst_put and gvcst_kill) relies on sgm_info_ptr->update_trans being non-zero. 	\
			 */													\
			assert(((0 < dollar_tlevel) || (ERR_GVZTRIGFAIL != T_ERR)) && (2)) ;	/* &&(2) idents assert */	\
			set_upd_trans_t_err = TRUE;										\
			/* Check that gvtr_init does not play with "reset_gv_target" a global variable that callers of this	\
			 * function (e.g. gvcst_put) might have set to a non-default value.					\
			 */													\
			assert(reset_gv_target == save_reset_gv_target);							\
			CSD = cs_data; /* if MM and db extension occurred, reset CSD to cs_data to avoid stale value */		\
		}														\
	} 															\
	GVT_TRGR = GVT->gvt_trigger;												\
	assert((NULL == GVT_TRGR) || dollar_tlevel);										\
	if ((NULL == GVT_TRGR) && IS_TPWRAP && !dollar_tlevel)									\
	{															\
		assert(set_upd_trans_t_err);											\
		assert(was_nontp);												\
		assert(0 == t_tries);												\
		/* We came in as a non-tp update and initiated op_tstart to do the ^#t reads. However, no triggers were defined	\
		 * because of which we did the corresponding op_tcommit in gvtr_db_tpwrap_helper. Now the TP wrap is complete	\
		 * restore any global variables the wrap messed with. 								\
		 */														\
		IS_TPWRAP = LCL_TSTART = FALSE;											\
		DEBUG_ONLY(donot_INVOKE_MUMTSTART = FALSE;)									\
	} 															\
	if (set_upd_trans_t_err) /* Reset update_trans/si->update_trans and t_err */						\
	{															\
		if (!dollar_tlevel)												\
		{	/* In t_begin we expect update_trans to always be 0. */							\
			update_trans = 0;											\
		}														\
		/* If non-tp, the below macro will invoke t_begin which will set up the necessary structures for the Non-TP 	\
		 * update													\
		 */														\
		T_BEGIN_SETORKILL_NONTP_OR_TP(T_ERR);										\
	}															\
}

#define	GVTR_OP_TCOMMIT(STATUS)										\
{													\
	GBLREF	boolean_t		skip_INVOKE_RESTART;						\
													\
	DEBUG_ONLY(GBLREF boolean_t	donot_INVOKE_MUMTSTART;)					\
	DEBUG_ONLY(GBLREF tp_frame	*tp_pointer;)							\
	DEBUG_ONLY(GBLREF stack_frame	*frame_pointer;)						\
	DEBUG_ONLY(GBLREF mv_stent	*mv_chain;)							\
	DEBUG_ONLY(GBLREF unsigned char	*msp;)								\
	DEBUG_ONLY(GBLREF uint4		dollar_tlevel;)							\
													\
	assert(dollar_tlevel);										\
	assert((tp_pointer->fp == frame_pointer) && (MVST_TPHOLD == mv_chain->mv_st_type)		\
		&& (msp == (unsigned char *)mv_chain));							\
	assert(!skip_INVOKE_RESTART);									\
	skip_INVOKE_RESTART = TRUE;	/* causes op_tcommit to return code if restart situation */	\
	STATUS = op_tcommit();										\
	assert(!skip_INVOKE_RESTART);	/* should have been reset by op_tcommit at very beginning */	\
	DEBUG_ONLY(if (cdb_sc_normal == STATUS) donot_INVOKE_MUMTSTART = FALSE;)			\
}

/* Protect MVALs from garabage collection */
#define INCR_AND_PUSH_MV_STENT(VAR)				\
{								\
	PUSH_MV_STENT(MVST_MVAL);				\
	VAR = &mv_chain->mv_st_cont.mvs_mval;			\
	VAR->mvtype = 0;					\
	trig_protected_mval_push_count++;			\
}

#define DECR_AND_POP_MV_STENT()					\
{								\
	while (0 < trig_protected_mval_push_count--)		\
		POP_MV_STENT();					\
}

#define RETURN_AND_POP_MVALS(RET)				\
{ /* Convenience helper macro to avoid repeated retun & pop */	\
	DECR_AND_POP_MV_STENT();				\
	return RET;						\
}

#define	PUSH_ZTOLDMVAL_ON_M_STACK(ZTOLD_MVAL, SAVE_MSP, SAVE_MV_CHAIN)						\
{														\
	GBLREF	mv_stent		*mv_chain;								\
	GBLREF	unsigned char		*msp;									\
														\
	/* Create mval on the M-stack (thereby it is known to stp_gcol (BYPASSOK)) to save 			\
	 * pre-gvcst_put/gvcst_kill value. The memory needed to save the actual value will be obtained from the \
	 * stringpool later.											\
	 */													\
	assert(NULL == ZTOLD_MVAL);										\
	SAVE_MSP = msp;		/* Save current msp & mv_chain to restore finally */				\
	SAVE_MV_CHAIN = mv_chain;										\
	PUSH_MV_STENT(MVST_MVAL);	/* protect $ztoldval from stp_gcol (BYPASSOK) */			\
	ZTOLD_MVAL = &mv_chain->mv_st_cont.mvs_mval;								\
	ZTOLD_MVAL->mvtype = 0;	/* make sure mval is setup enough to protect stp_gcol (BYPASSOK)(if invoked 	\
				 * below) from	incorrectly reading its contents until it is fully initialized 	\
				 * later. */									\
}

/* The POP_MVALS_FROM_M_STACK_IF_NEEDED macro below pops some mvals pushed on the stack but pops them in an unusual way for
 * performance reasons (not really popping them but just restoring previous pointers). The debug version of that macro will use the
 * slightly longer but verifying form of unwind defined below to make sure we aren't popping something we need in an invisible and
 * tough to track fashion.
 */
#ifdef DEBUG
#define UNW_MV_STENT_TO(prev_msp, prev_mv_chain)				\
{										\
	mv_stent *mvc;								\
	mvc = mv_chain;								\
	while (mvc < prev_mv_chain)						\
	{									\
		assert(MVST_MVAL == mvc->mv_st_type);				\
		mvc = (mv_stent *)(mvc->mv_st_next + (char *)mvc);		\
	}									\
	assert(prev_mv_chain == mvc);						\
	assert(prev_msp <= (unsigned char *)mvc);				\
	msp = prev_msp;								\
	mv_chain = mvc;								\
}
#else
#define UNW_MV_STENT_TO(prev_msp, prev_mv_chain)			\
{									\
	msp = prev_msp;							\
	mv_chain = prev_mv_chain;					\
}
#endif

#define	POP_MVALS_FROM_M_STACK_IF_NEEDED(ZTOLD_MVAL, SAVE_MSP, SAVE_MV_CHAIN)		\
{											\
	GBLREF	boolean_t		skip_dbtriggers;				\
	GBLREF	mv_stent		*mv_chain;					\
	GBLREF	unsigned char		*msp;						\
											\
	if (NULL != ZTOLD_MVAL)								\
	{	/* ZTOLD_MVAL & potentially a few other mvals have been pushed onto the	\
		 * M-stack. Pop them all in one restore of the M-stack.			\
		 */									\
		assert(!skip_dbtriggers);						\
		assert(SAVE_MSP > msp);							\
		if (SAVE_MSP > msp)							\
			UNW_MV_STENT_TO(SAVE_MSP, SAVE_MV_CHAIN);			\
		ZTOLD_MVAL = NULL;							\
	}										\
}

#define SET_GVTARGET_TO_HASHT_GBL(CSA)								\
{												\
	mname_entry	gvname;									\
	gv_namehead	*hasht_tree;								\
												\
	GBLREF	gv_namehead	*gv_target;							\
												\
	hasht_tree = CSA->hasht_tree;								\
	if (NULL == hasht_tree)									\
	{	/* Allocate gv_target like structure for "^#t" global in this database file */	\
		gvname.var_name.addr = HASHT_GBLNAME;						\
		gvname.var_name.len = HASHT_GBLNAME_LEN;					\
		gvname.marked = FALSE;								\
		COMPUTE_HASH_MNAME(&gvname);							\
		hasht_tree = targ_alloc(CSA->hdr->max_key_size, &gvname, NULL);			\
		hasht_tree->gd_csa = CSA;							\
		CSA->hasht_tree = hasht_tree;							\
	}											\
	gv_target = hasht_tree;									\
}

#define INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED										\
{															\
	unsigned char		*key;											\
															\
	GBLREF	gv_key			*gv_currkey;									\
	GBLREF	gd_region		*gv_cur_region;									\
	GBLREF	sgmnt_data_ptr_t	cs_data;									\
															\
	key = &gv_currkey->base[0];											\
	assert(gv_currkey->base + gv_currkey->end + HASHT_GBLNAME_FULL_LEN <= ((gv_currkey->base) + gv_currkey->top));	\
	memcpy(key, HASHT_GBLNAME, HASHT_GBLNAME_FULL_LEN);	/* including terminating '\0' subscript */		\
	key += HASHT_GBLNAME_FULL_LEN;											\
	*key++ = '\0';		/* double '\0' for terminating key */							\
	gv_currkey->end = HASHT_GBLNAME_FULL_LEN;									\
	/* Determine root block of ^#t global in this database file. Need to use gvcst_root_search for this. It expects	\
	 * gv_currkey, gv_target, gv_cur_region, cs_addrs & cs_data to be set up appropriately. gv_currkey & gv_target	\
	 * are already set up. The remaining should be set up which is asserted below.					\
	 */														\
	assert(&FILE_INFO(gv_cur_region)->s_addrs == cs_addrs);								\
	assert(cs_data == cs_addrs->hdr);										\
	/* Do the actual search for ^#t global in the directory tree */							\
	GVCST_ROOT_SEARCH;												\
}

/* Caller has to check for "NULL" value of "cs_addrs" and act accordingly */
#define	GVTR_SWITCH_REG_AND_HASHT_BIND_NAME(reg)							\
{													\
	GBLREF	gv_namehead	*gv_target;								\
	GBLREF	gd_region	*gv_cur_region;								\
	GBLREF	gv_key		*gv_currkey;								\
													\
	assert(!IS_STATSDB_REGNAME(reg));	/* caller should have ensured this */			\
	if (!reg->open)											\
		gv_init_reg(reg, NULL);									\
	gv_cur_region = reg;										\
	change_reg(); /* TP_CHANGE_REG wont work as we need to set sgm_info_ptr */			\
	if (NULL != cs_addrs)										\
	{												\
		SET_GVTARGET_TO_HASHT_GBL(cs_addrs);	/* sets up gv_target */				\
		assert(NULL != gv_target);								\
		INITIAL_HASHT_ROOT_SEARCH_IF_NEEDED;	/* sets up gv_currkey */			\
	} else												\
	{												\
		DEBUG_ONLY(gv_target = NULL;)	/* to keep cs_addrs/gv_target in sync */		\
		DEBUG_ONLY(gv_currkey->base[0] = '\0';)	/* to keep gv_target/gv_currkey in sync */	\
	}												\
}

GBLREF	uint4		dollar_tlevel;
GBLREF	int4		gtm_trigger_depth;
GBLREF	int4		tstart_trigger_depth;

/* This macro returns if the current update is an EXPLICIT update or not. Any update done as part of a trigger invocation is not
 * considered an explicit update. Note that it is possible to do a TROLLBACK while inside trigger code. In this case, any updates
 * done after the trollback while still inside the trigger code are considered explicit updates. Hence the seemingly complicated
 * check below. There is a version without the asserts for use ONLY WITH the IS_OK_TO_INVOKE_GVCST_KILL macro where nested asserts
 * dont work well with the C preprocessor.
 */
#define	IS_EXPLICIT_UPDATE	(DBG_ASSERT(!dollar_tlevel || (tstart_trigger_depth <= gtm_trigger_depth))	\
					IS_EXPLICIT_UPDATE_NOASSERT)

#define	IS_EXPLICIT_UPDATE_NOASSERT	(!dollar_tlevel || (tstart_trigger_depth == gtm_trigger_depth))

/* Check if update is inside trigger (implicit update) and to a replicated database. If so check that corresponding triggering
 * update (explicit update) also occurred in a replicated database. If not this is an out-of-design situation as the replicating
 * secondary will see no journal records for this TP transaction (since the triggering update did not get replicated) and so cannot
 * keep the secondary in sync with the primary. In this case, issue an error.
 */
#define	TRIG_CHECK_REPLSTATE_MATCHES_EXPLICIT_UPDATE(REG, CSA)							\
{														\
	GBLREF	boolean_t	explicit_update_repl_state;							\
	GBLREF	gv_key		*gv_currkey;									\
														\
	if (!IS_EXPLICIT_UPDATE && !explicit_update_repl_state && REPL_ALLOWED(CSA))				\
	{													\
		unsigned char	buff[MAX_ZWR_KEY_SZ], *end;							\
														\
		if (0 == (end = format_targ_key(buff, MAX_ZWR_KEY_SZ, gv_currkey, TRUE)))			\
			end = &buff[MAX_ZWR_KEY_SZ - 1];							\
		rts_error_csa(CSA_ARG(CSA) VARLSTCNT(8) ERR_TRIGREPLSTATE, 2, DB_LEN_STR(REG), ERR_GVIS, 2,	\
				end - buff, buff);								\
	}													\
}

#define	TRIG_PROCESS_JNL_STR_NODEFLAGS(NODEFLAGS)			\
{									\
	GBLREF	boolean_t	skip_dbtriggers;			\
	GBLREF	mval		dollar_ztwormhole;			\
									\
	assert(!(JS_NOT_REPLICATED_MASK & NODEFLAGS));			\
	skip_dbtriggers = (NODEFLAGS & JS_SKIP_TRIGGERS_MASK);		\
	if (NODEFLAGS & JS_NULL_ZTWORM_MASK)				\
		dollar_ztwormhole.str.len = 0;				\
}

#define	JNL_FORMAT_ZTWORM_IF_NEEDED(CSA, WRITE_LOGICAL_JNLRECS, JNL_OP, KEY, VAL, ZTWORM_JFB, JFB, JNL_FORMAT_DONE)	\
{															\
	GBLREF	mval		dollar_ztwormhole;									\
	GBLREF	int4		gtm_trigger_depth;									\
	GBLREF	int4		tstart_trigger_depth;									\
	GBLREF	boolean_t	skip_dbtriggers;									\
	GBLREF	boolean_t	explicit_update_repl_state;								\
	GBLREF	uint4		dollar_tlevel;										\
	GBLREF	jnl_gbls_t	jgbl;											\
															\
	uint4			nodeflags;										\
															\
	/* No need to write ZTWORMHOLE journal records for updates inside trigger since those records are not		\
	 * replicated anyway.												\
	 */														\
	assert(dollar_tlevel);	/* tstart_trigger_depth is not usable otherwise */					\
	assert(tstart_trigger_depth <= gtm_trigger_depth);								\
	assert(!skip_dbtriggers); /* we ignore the JS_SKIP_TRIGGERS_MASK bit in nodeflags below because of this */	\
	ZTWORM_JFB = NULL;												\
	if (tstart_trigger_depth == gtm_trigger_depth)									\
	{	/* explicit update so need to write ztwormhole records */						\
		assert(WRITE_LOGICAL_JNLRECS == JNL_WRITE_LOGICAL_RECS(CSA));						\
		nodeflags = 0;												\
		explicit_update_repl_state = REPL_ALLOWED(CSA);								\
		/* Write ZTWORMHOLE records only if replicating since secondary is the only one that cares about it. */	\
		if (explicit_update_repl_state && dollar_ztwormhole.str.len)						\
		{	/* $ZTWORMHOLE is non-NULL. Journal that BEFORE the corresponding SET record. If it is found	\
			 * that the trigger invocation did not REFERENCE it, we will remove this from the list of	\
			 * formatted journal records.									\
			 */												\
			ZTWORM_JFB = jnl_format(JNL_ZTWORM, NULL, &dollar_ztwormhole, 0);				\
			/* Note : ztworm_jfb could be NULL if it was determined that this ZTWORMHOLE record is not	\
			 * needed i.e. if the exact same value of ZTWORMHOLE was already written as part of the		\
			 * previous update in this TP transaction.							\
			 */												\
		}													\
	} else														\
		nodeflags = JS_NOT_REPLICATED_MASK;									\
	assert(!JNL_FORMAT_DONE);											\
	/* Need to write logical SET or KILL journal records irrespective of trigger depth */				\
	if (WRITE_LOGICAL_JNLRECS)											\
	{														\
		nodeflags |= JS_HAS_TRIGGER_MASK;	/* gvt_trigger is non-NULL */					\
		if (!dollar_ztwormhole.str.len)										\
		{													\
			/* Set jgbl.prev_ztworm_ptr to NULL. This is needed so that any subsequent non-null ztwormhole	\
			 * assignment which matches with the ztwormhole value before the NULL ztwormhole SHOULD write	\
			 * ZTWORM records in the journal file. 								\
			 */												\
			jgbl.save_ztworm_ptr = jgbl.prev_ztworm_ptr;							\
			jgbl.prev_ztworm_ptr = NULL;									\
			nodeflags |= JS_NULL_ZTWORM_MASK;								\
		}													\
		/* Insert SET journal record now that ZTWORMHOLE (if any) has been inserted */				\
		JFB = jnl_format(JNL_OP, KEY, VAL, nodeflags);								\
		assert(NULL != JFB);											\
		JNL_FORMAT_DONE = TRUE;											\
	}														\
}

#define	REMOVE_ZTWORM_JFB_IF_NEEDED(ZTWORM_JFB, JFB, SI)								\
{															\
	GBLREF	boolean_t	ztwormhole_used;	/* TRUE if $ztwormhole was used by trigger code */		\
	GBLREF	jnl_gbls_t	jgbl;											\
															\
	jnl_format_buffer	*tmpjfb;										\
															\
	if ((NULL != ZTWORM_JFB) && !ztwormhole_used)									\
	{ 	/* $ZTWORMHOLE was non-zero before the trigger invocation and was never used inside the	trigger. We	\
		 * need to remove the corresponding formatted journal record. We dont free up the memory occupied by	\
		 * ZTWORM_JFB as it is not easy to free up memory in the middle of a buddy list. This memory will	\
		 * anyway be freed up eventually at tp_clean_up time.							\
		 * NOTE: Trigger code that does NOT use the $ZTWORMHOLE is equivalent to a trigger code that has	\
		 * $ZTWORMHOLE set to NULL and hence should have the JS_NULL_ZTWORM_MASK set in the nodeflags. But for	\
		 * that to happen, checksum of the SET/KILL/ZTRIG records need to be recomputed. Since this is a costly	\
		 * operation, we don't touch the nodeflags as there is no known correctness issue.		\
		 */													\
		assert(NULL != JFB);											\
		tmpjfb = ZTWORM_JFB->prev;										\
		if (NULL != tmpjfb)											\
		{													\
			tmpjfb->next = JFB;										\
			JFB->prev = tmpjfb;										\
		} else													\
		{													\
			assert(SI->jnl_head == ZTWORM_JFB);								\
			SI->jnl_head = JFB;										\
			assert(IS_UUPD(JFB->rectype));									\
			assert(((jnl_record *)JFB->buff)->prefix.jrec_type == JFB->rectype);				\
			JFB->rectype--;											\
			assert(IS_TUPD(JFB->rectype));									\
			((jnl_record *)JFB->buff)->prefix.jrec_type = JFB->rectype;					\
		}													\
		jgbl.prev_ztworm_ptr = jgbl.save_ztworm_ptr;								\
		assert(0 < jgbl.cumul_index);										\
		DEBUG_ONLY(jgbl.cumul_index--;)										\
		jgbl.cumul_jnl_rec_len -= ZTWORM_JFB->record_size;							\
	}														\
	jgbl.save_ztworm_ptr = NULL;											\
}

#define SET_PARAM_STRING(UTIL_BUFF, UTIL_LEN, TRIGIDX, PARAM)				\
{											\
	uchar_ptr_t		util_ptr;						\
											\
	util_ptr = i2asc(&UTIL_BUFF[0], TRIGIDX);	/* UTIL_BUFF = 1 */		\
	assert(MAX_TRIG_UTIL_LEN >= STR_LIT_LEN(PARAM));				\
	MEMCPY_LIT(util_ptr, PARAM);			/* UTIL_BUFF = 1,"CMD" */	\
	util_ptr += STR_LIT_LEN(PARAM);							\
	UTIL_LEN = UINTCAST(util_ptr - &UTIL_BUFF[0]);					\
	assert(MAX_TRIG_UTIL_LEN >= UTIL_LEN);						\
}

/* 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,CSA)				\
{												\
	if (UPDATE_CAN_RETRY(t_tries, t_fail_hist[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_csa(CSA_ARG(CSA) VARLSTCNT(8) ERR_TRIGDEFBAD, 6, trigvn_len, trigvn,	\
			trigvn_len, trigvn, util_len, util_buff);				\
	}											\
}

#endif