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

#ifndef GDSCC_H_INCLUDED
#define GDSCC_H_INCLUDED

/* this requires gdsroot.h gtm_facilit.h fileinfo.h gdsbt.h gdsfhead.h gdir.h gdskey.h  */

#include <sys/types.h>

/* BIG_UA is the maximum size of a single update array specified as an unsigned quantity (usages rely on this). It is 16MB. */
#define BIG_UA  (uint4)16777216

#define CDB_R_SET_SIZE		32
#define CDB_CW_SET_SIZE 	(MAX_BT_DEPTH * 3 + 1 + 2)
#define CDB_W_SET_SIZE		16

/* CDB_CW_SET_SIZE = 24
 *	3 for all the levels (including updated block, newly created sibling and possible bitmap update)
 *	1 extra for the root level (to take care of gds_t_write_root case)
 *	2 in the case of creation of a new global variable (1 index block with a * key and 1 data block
 *	  containing the key)
 */

#define CDB_T_CREATE 0
#define CDB_T_WRITE 1
#define CDB_T_WRITE_ROOT 2

/* The following defines the write_type for a block that is going to be updated.
 * GDS_WRITE_PLAIN is the default type for most updates.
 * GDS_WRITE_BLOCK_SPLIT is set in case of a block update due to a block split.
 * GDS_WRITE_KILLTN requires a little more explanation.
 *
 * The TP commit logic ("tp_tend") makes use of an optimization referred to as the "indexmod" optimization.
 * This optimization tries to avoid a restart in the case where a TP transaction does a SET to a data block and later finds
 * at TCOMMIT time that the index block which was part of the SET had been updated by a concurrent SET (or a REORG split
 * operation) to a different data block (that also had the same index block as an ancestor) which resulted in a block split
 * causing the index block to be updated. In this case there is no reason to restart. The index block could have been
 * modified by other operations as well (e.g. M-kill, REORG coalesce or swap operations or any DSE command or a block split
 * operation that caused the height of the global variable tree to increase [C9B11-001813]). In these cases, we don't want
 * this optimization to take effect as we can't be sure everything that was relied upon for the TP transaction was still valid.
 * These disallowed operations are generically referred to as "kill" type of operations. This optimization is implemented by
 * having a field "killtn" (name derived from "kill" type of operations) in the bt (block-table) structure for each block.
 * This field is assigned the same value as the "tn" whenever an index block gets updated due to one of the disallowed operations.
 * Otherwise it stays untouched (i.e. killtn <= tn at all times). It is the "killtn" (and not "tn") that is used in the
 * cdb_sc_blkmod validation check in tp_tend if we are validating a index block. Since KILLs, MUPIP REORG and/or DSE operations
 * are usually rare compared to SET activity, most of the cases we expect the indexmod optimization to be in effect and
 * therefore help reduce the # of TP restarts due to index block changes. Note that each SET/GET/KILL operation in TP goes
 * through an intermediate validation routine tp_hist which does the cdb_sc_blkmod validation using "tn" (not "killtn").
 * Only if that passed, do we relax the commit time validation for index blocks.
 *
 * The operations that are not allowed to use this optimization (M-kill, REORG or DSE) are supposed to make sure they
 * set the write_type of the cw-set-element to GDS_WRITE_KILLTN. Failing to do so cause "killtn" in the bt to NOT be uptodate
 * which in turn can cause false validation passes (in the cdb_sc_blkmod check) causing GT.M processes to incorrectly commit
 * when they should not. This can lead to GT.M/application level data integrity errors.
 */
#define	GDS_WRITE_PLAIN		0
#define	GDS_WRITE_KILLTN	1
#define	GDS_WRITE_BLOCK_SPLIT	2
/* blk_prior_state's last bit indicates whether the block was free before update
 * BLOCK FREE:          0b*******1, BLOCK NOT FREE:     0b*******0
 */
#define BIT_SET_FREE(X)		((X) |= 0x00000001)
#define BIT_CLEAR_FREE(X)		((X) &= 0xfffffffe)
#define WAS_FREE(X)		((X) & 0x00000001)
/* blk_prior_state's last but one bit indicates whether the block was recycled before update
 * BLOCK RECYCLED:  	0b******1*, BLOCK NOT RECYCLED: 0b******0*
 */
#define BIT_SET_RECYCLED_AND_CLEAR_FREE(X)      ((X) = ((X) & 0xfffffffc) + 0x00000002)
#define BIT_CLEAR_RECYCLED_AND_SET_FREE(X)	((X) = ((X) & 0xfffffffc) + 0x00000001)
#define BIT_CLEAR_RECYCLED(X)	((X) &= 0xfffffffd)
#define BIT_SET_RECYCLED(X)	((X) |= 0x00000002)
#define WAS_RECYCLED(X)		(((X) & 0x00000002))
/* blk_prior_state's last but two bit indicates whether the block was in directory tree or global variable tree
 * IN_GV_TREE:         0b*****1**,  IN_DIR_TREE:        0b*****0**
 */
#define IN_GV_TREE 4
#define IN_DIR_TREE 0
#define BIT_SET_DIR_TREE(X)	((X) &= 0xfffffffb)
#define BIT_SET_GV_TREE(X)	((X) |= 0x00000004)
#define KEEP_TREE_STATUS 0x00000004


/* macro to traverse to the end of an horizontal cw_set_element list */

#define TRAVERSE_TO_LATEST_CSE(x)					\
{									\
	GBLREF	uint4		dollar_tlevel;				\
									\
	assert(dollar_tlevel);						\
	if (x)								\
                for ( ; (x)->high_tlevel; x = (x)->high_tlevel)		\
                        ;						\
}

typedef uint4	block_offset;
typedef int4   	block_index;

/* If a new mode is added to the table below, make sure pre-existing mode usages in the current codebase are examined to see
 * if the new mode needs to be added there as well. For example, there is code in tp_incr_commit.c and tp_incr_clean_up.c
 * where gds_t_create and kill_t_create are used explicitly. If the new mode is yet another *create* type, then it might need
 * to be added in those places as well.
 */
enum gds_t_mode
{
	gds_t_noop = 0,		/* there is code that initializes stuff to 0 relying on it being equal to gds_t_noop */
	gds_t_create,
	gds_t_write,
	gds_t_write_recycled,	/* modify a recycled block (currently only done by MUPIP REORG UPGRADE/DOWNGRADE) */
	gds_t_acquired,
	gds_t_writemap,
	gds_t_committed,	/* t_end   relies on this particular placement */
	gds_t_write_root,	/* t_end   relies on this being AFTER gds_t_committed */
	gds_t_busy2free,	/* t_end   relies on this being AFTER gds_t_committed */
	gds_t_recycled2free,	/* t_end   relies on this being AFTER gds_t_committed */
	n_gds_t_op,		/* tp_tend and other routines rely on this being BEFORE kill_t* modes and AFTER all gds_t_* modes */
	kill_t_create,		/* tp_tend relies on this being AFTER n_gds_t_op */
	kill_t_write,		/* tp_tend relies on this being AFTER n_gds_t_op */
};

typedef struct key_value_struct
{
	gv_key_buf		keybuf;
	mstr			value;
	struct key_value_struct	*next;
} key_cum_value;

/* Create/write set element. This is used to describe modification of a database block */

typedef struct cw_set_element_struct
{
	trans_num	tn;				/* transaction number for bit maps */
	sm_uc_ptr_t	old_block;			/* Address of 'before-image' of block to be over-written */
	cache_rec_ptr_t	cr;
        struct cw_set_element_struct  *next_cw_set;
        struct cw_set_element_struct  *prev_cw_set;	/* linked list (vertical) of cw_set_elements with one link per block */

        struct cw_set_element_struct  *high_tlevel;
        struct cw_set_element_struct  *low_tlevel;	/* linked list (horizontal) of cw_set elements for a given block with
							 * different transaction levels. Latest cw_set_elements (for a given block)
							 * are inserted at the beginning of the horizontal list	*/
	off_jnl_t	jnl_freeaddr;			/* journal update address */
	uint4		write_type;			/* can be GDS_WRITE_PLAIN or GDS_WRITE_KILLTN or GDS_WRITE_BLOCK_SPLIT
							 * or bit-wise-or of both */
	key_cum_value	*recompute_list_head;		/* pointer to a list of keys (with values) that need to be recomputed */
	key_cum_value	*recompute_list_tail;		/* pointer to a list of keys (with values) that need to be recomputed */
	enum gds_t_mode	mode;				/* Create, write, or write root	*/
	block_id	blk;				/* Block number or a hint block number for creates */
	unsigned char	*upd_addr;			/* Address of the block segment array containing update info
							 * for this block */
        unsigned char   *new_buff;     			/* Address of a buffer created for each global mentioned inside of a
                                       			 * transaction more then once (for tp) */
	gv_namehead	*blk_target;			/* address of the "gv_target" associated with a new_buff
							 * used to invalidate clues that point to malloc'ed copies */
	int4		cycle;

 	/* When a block splits a new block must be created and the parent must be updated to
	 * to have a record pointing to the new block.  The created block number will not be
	 * known until the last possible moment.  Thus it is not possible to completely modify
	 * the parent.  The following 2 fields are used in such a case. "ins_off" tells where
	 * the created block's number should be put in the parent block. "index" tells which
	 * element of the create/write set is being created.
	 */

        block_offset    first_off;
	block_offset	ins_off;			/* Insert block number offset */
        block_offset    next_off;
	block_index	index;				/* Insert block number index */
	int4		reference_cnt;			/* Relevant only for a bitmap block.
							 *    > 0 => # of non-bitmap blocks to be allocated in this bitmap;
							 *    < 0 => # of non-bitmap blocks to be freed up in this bitmap;
							 *   == 0 => change to bitmap block without any non-bitmap block change
							 * Used to update csd->free_blocks when the bitmap block is built
							 */
	int4		level;				/* Block level for newly created blocks	*/
        boolean_t	done;           		/* Has this update been done already? */
	boolean_t	first_copy;			/* If overlaying same buffer, set if first copy needed */
							/* just an optimisation - avoids copying first few bytes, if anyway
							 * we are just overlaying the new_buff in the same transaction */
	boolean_t	forward_process;		/* Need to process update array from front when doing kills */
	uint4		t_level;			/* transaction level associated with cw element, for incremental rollback */
	enum db_ver	ondsk_blkver;			/* Actual block version from block header as it exists on disk.
							 * If "cse->mode" is gds_t_write_root, this is uninitialized.
							 * If "cse->mode" is gds_t_create/gds_t_acquired, this is GDSVCURR.
							 * Otherwise, this is set to cr->ondsk_blkver (cr is got from the history).
							 * Whenever "cse->old_block" is reset, this needs to be reset too (except
							 *	in the case of gds_t_create/gds_t_acquired).
							 */
	int4		old_mode;			/* Saved copy of "cse->mode" before being reset to gds_t_committed.
							 * Is negated at end of bg_update_phase1 to indicate (to secshr_db_clnup)
							 * that phase1 is complete. Is negated back to the postive value at end
							 * of bg_update_phase2. Since this can take on negative values, its type
							 * is int4 (signed) and not enum gds_t_mode (which is unsigned).
							 */
	/* The following two fields aid in rolling back the transactions. 'undo_next_off' holds the
	 * original next_off in the blk buffer that would be if another nested transaction was not
	 * started. 'undo_offset' holds the offset at which 'undo_next_off' should be applied in case
	 * of an undo due to trollback.
	 * A 'kill' might change the next_off field at most in two places in the blk buffer. So, is
	 * an array of size two.
	 */

	block_offset	undo_next_off[2];
	block_offset	undo_offset[2];
	uint4		blk_checksum;
	/*blk_prior_state:the block was in global variable tree/directory tree and was free/busy before update*/
	uint4		blk_prior_state;
} cw_set_element;

#ifdef DEBUG
GBLREF	uint4		dollar_tlevel;
#endif

/* See comment in tp_tend where this macro is used for details */
#define	IS_BG_PHASE2_COMMIT_IN_CRIT(CSE, MODE)							\
	((gds_t_writemap == MODE) || (CSE->recompute_list_head && (gds_t_write == MODE)))

#endif
