390 lines
15 KiB
C
390 lines
15 KiB
C
/****************************************************************
|
|
* *
|
|
* Copyright 2001, 2011 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. *
|
|
* *
|
|
****************************************************************/
|
|
|
|
/*
|
|
* ------------------------------------------------------------------------------------------------------------
|
|
* op_merge.c
|
|
* ==============
|
|
* Description:
|
|
* Main routine of MERGE command.
|
|
*
|
|
* Arguments:
|
|
* Already op_merge_arg.c have saved all the inputs in merge_args and mglvnp.
|
|
*
|
|
* Return:
|
|
* none
|
|
*
|
|
* Side Effects:
|
|
* merge_args will be reset to 0 after successful operation.
|
|
*
|
|
* Notes:
|
|
* ------------------------------------------------------------------------------------------------------------
|
|
*/
|
|
#include "mdef.h"
|
|
|
|
#include "min_max.h"
|
|
#include "lv_val.h"
|
|
#include "rtnhdr.h"
|
|
#include "mv_stent.h"
|
|
#include "gdsroot.h"
|
|
#include "gdskill.h"
|
|
#include "gtm_facility.h"
|
|
#include "fileinfo.h"
|
|
#include "gdsbt.h"
|
|
#include "gdsfhead.h"
|
|
#include "zshow.h"
|
|
#include "zwrite.h"
|
|
#include "filestruct.h"
|
|
#include "gdscc.h"
|
|
#include "copy.h"
|
|
#include "jnl.h"
|
|
#include "hashtab_int4.h" /* needed for tp.h */
|
|
#include "buddy_list.h"
|
|
#include "tp.h"
|
|
#include "gtm_string.h"
|
|
#include "merge_def.h"
|
|
#include "gvname_info.h"
|
|
#include "op_merge.h"
|
|
#include "gvsub2str.h"
|
|
#include "op.h"
|
|
#include "mvalconv.h"
|
|
#include "stringpool.h"
|
|
#include "outofband.h"
|
|
#include "gtmmsg.h"
|
|
#include "format_targ_key.h"
|
|
#include "sgnl.h"
|
|
#include "util.h"
|
|
#include "collseq.h"
|
|
#include "alias.h"
|
|
|
|
#define UNDO_ACTIVE_LV \
|
|
{ \
|
|
if (NULL != active_lv) \
|
|
{ \
|
|
if (!LV_IS_VAL_DEFINED(active_lv) && !LV_HAS_CHILD(active_lv)) \
|
|
op_kill(active_lv); \
|
|
active_lv = (lv_val *)NULL; \
|
|
} \
|
|
}
|
|
|
|
GBLREF mv_stent *mv_chain;
|
|
GBLREF unsigned char *msp, *stackwarn, *stacktop;
|
|
GBLREF int4 outofband;
|
|
GBLREF gv_key *gv_currkey;
|
|
GBLREF gd_region *gv_cur_region;
|
|
GBLREF zshow_out *zwr_output;
|
|
GBLREF int merge_args;
|
|
GBLREF merge_glvn_ptr mglvnp;
|
|
GBLREF gv_namehead *gv_target;
|
|
GBLREF lvzwrite_datablk *lvzwrite_block;
|
|
GBLREF lv_val *active_lv;
|
|
|
|
error_def(ERR_MAXNRSUBSCRIPTS);
|
|
error_def(ERR_MERGEINCOMPL);
|
|
error_def(ERR_NCTCOLLDIFF);
|
|
error_def(ERR_STACKCRIT);
|
|
error_def(ERR_STACKOFLOW);
|
|
|
|
void op_merge(void)
|
|
{
|
|
boolean_t found, check_for_null_subs, is_base_var;
|
|
lv_val *dst_lv;
|
|
mval *mkey, *value, *subsc;
|
|
int org_glvn1_keysz, org_glvn2_keysz, delta2, dollardata_src, dollardata_dst, sbs_depth;
|
|
unsigned char *ptr, *ptr2;
|
|
unsigned char buff[MAX_ZWR_KEY_SZ];
|
|
unsigned char nullcoll_src, nullcoll_dst;
|
|
zshow_out output;
|
|
DCL_THREADGBL_ACCESS;
|
|
|
|
SETUP_THREADGBL_ACCESS;
|
|
assert(MAX_STRLEN >= MAX_ZWR_KEY_SZ);
|
|
assert ((merge_args == (MARG1_LCL | MARG2_LCL)) ||
|
|
(merge_args == (MARG1_LCL | MARG2_GBL)) ||
|
|
(merge_args == (MARG1_GBL | MARG2_LCL)) ||
|
|
(merge_args == (MARG1_GBL | MARG2_GBL)));
|
|
assert(!lvzwrite_block || 0 == lvzwrite_block->curr_subsc);
|
|
/* Need to protect value from stpgcol */
|
|
PUSH_MV_STENT(MVST_MVAL);
|
|
value = &mv_chain->mv_st_cont.mvs_mval;
|
|
value->mvtype = 0; /* initialize mval in the M-stack in case stp_gcol gets called before value gets initialized below */
|
|
if (MARG2_IS_GBL(merge_args))
|
|
{ /* Need to protect mkey returned from gvcst_queryget from stpgcol */
|
|
PUSH_MV_STENT(MVST_MVAL);
|
|
mkey = &mv_chain->mv_st_cont.mvs_mval;
|
|
mkey->mvtype = 0; /* initialize mval in M-stack in case stp_gcol gets called before mkey gets initialized below */
|
|
gvname_env_restore(mglvnp->gblp[IND2]);
|
|
/* now $DATA will be done for gvn2. op_gvdata input parameters are set in the form of some GBLREF */
|
|
op_gvdata(value);
|
|
dollardata_src = MV_FORCE_INT(value);
|
|
if (0 == dollardata_src)
|
|
{ /* nothing in source global */
|
|
UNDO_ACTIVE_LV;
|
|
POP_MV_STENT(); /* value */
|
|
POP_MV_STENT(); /* mkey */
|
|
if (MARG1_IS_GBL(merge_args))
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* store destination as naked indicator in gv_currkey */
|
|
merge_args = 0; /* Must reset to zero to reuse the Global */
|
|
return;
|
|
}
|
|
if (NULL == TREF(gv_mergekey2))
|
|
{ /* We need to initialize gvn2 (right hand side). */
|
|
GVKEY_INIT(TREF(gv_mergekey2), DBKEYSIZE(MAX_KEY_SZ));
|
|
}
|
|
org_glvn1_keysz = mglvnp->gblp[IND1]->s_gv_currkey->end + 1;
|
|
org_glvn2_keysz = gv_currkey->end + 1;
|
|
(TREF(gv_mergekey2))->end = gv_currkey->end;
|
|
(TREF(gv_mergekey2))->prev = gv_currkey->prev;
|
|
memcpy((TREF(gv_mergekey2))->base, gv_currkey->base, gv_currkey->end + 1);
|
|
if (MARG1_IS_GBL(merge_args))
|
|
{ /*==================== MERGE ^gvn1=^gvn2 =====================*/
|
|
if (mglvnp->gblp[IND2]->s_gv_target->nct != mglvnp->gblp[IND1]->s_gv_target->nct)
|
|
rts_error(VARLSTCNT(1) ERR_NCTCOLLDIFF);
|
|
/* if self merge then NOOP*/
|
|
if (!merge_desc_check()) /* will not proceed if one is descendant of another */
|
|
{
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* store destination as naked indicator in gv_currkey */
|
|
POP_MV_STENT(); /* value */
|
|
merge_args = 0; /* Must reset to zero to reuse the Global */
|
|
return;
|
|
}
|
|
nullcoll_src = mglvnp->gblp[IND2]->s_gv_cur_region->std_null_coll;
|
|
nullcoll_dst = mglvnp->gblp[IND1]->s_gv_cur_region->std_null_coll;
|
|
if (1 == dollardata_src || 11 == dollardata_src)
|
|
{
|
|
found = op_gvget(value); /* value of ^glvn2 */
|
|
if (found)
|
|
{ /* SET ^gvn1=^gvn2 */
|
|
gvname_env_restore(mglvnp->gblp[IND1]);
|
|
op_gvput(value);
|
|
/* Note: If ^gvn1's null_sub=ALLOWEXISTING and say ^gvn1("")=^gvn,
|
|
* this will give NULL_SUBC error
|
|
*/
|
|
}
|
|
}
|
|
check_for_null_subs = (NEVER != mglvnp->gblp[IND2]->s_gv_cur_region->null_subs) &&
|
|
(ALWAYS != mglvnp->gblp[IND1]->s_gv_cur_region->null_subs);
|
|
/* Traverse descendant of ^gvn2 and copy into ^gvn1 */
|
|
for (; ;)
|
|
{
|
|
if (outofband)
|
|
{
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* naked indicator is restored into gv_currkey */
|
|
outofband_action(FALSE);
|
|
}
|
|
/* Restore last key under ^gvn2 we worked */
|
|
gvname_env_restore(mglvnp->gblp[IND2]);
|
|
assert(0 == gv_currkey->base[gv_currkey->end - 1] && 0 == gv_currkey->base[gv_currkey->end]);
|
|
/* following is an attempt to find immidiate right sibling */
|
|
gv_currkey->base[gv_currkey->end] = 1;
|
|
gv_currkey->base[gv_currkey->end + 1] = 0;
|
|
gv_currkey->base[gv_currkey->end + 2] = 0;
|
|
gv_currkey->end += 2;
|
|
/* Do atomic $QUERY and $GET of current glvn2:
|
|
* mkey is a mstr which contains $QUERY result in database format (So no conversion necessary)
|
|
* value is a mstr which contains $GET result
|
|
*/
|
|
if (!op_gvqueryget(mkey, value))
|
|
break;
|
|
assert(MV_IS_STRING(mkey));
|
|
if (mkey->str.len < org_glvn2_keysz)
|
|
break;
|
|
if (0 != *((unsigned char *)mkey->str.addr + (TREF(gv_mergekey2))->end - 1) ||
|
|
memcmp(mkey->str.addr, (TREF(gv_mergekey2))->base, (TREF(gv_mergekey2))->end - 1))
|
|
break; /* mkey is not under the sub-tree */
|
|
delta2 = mkey->str.len - org_glvn2_keysz; /* length increase of source key */
|
|
assert (0 < delta2);
|
|
/* Save the new source key for next iteration */
|
|
memcpy(mglvnp->gblp[IND2]->s_gv_currkey->base + org_glvn2_keysz - 2,
|
|
mkey->str.addr + org_glvn2_keysz - 2, delta2 + 2);
|
|
mglvnp->gblp[IND2]->s_gv_currkey->end = mkey->str.len - 1;
|
|
/* Create the destination key for this iteration (under ^glvn1) */
|
|
gvname_env_restore(mglvnp->gblp[IND1]);
|
|
if (gv_cur_region->max_key_size < org_glvn1_keysz + delta2)
|
|
ISSUE_GVSUBOFLOW_ERROR(gv_currkey);
|
|
assert(gv_currkey->end == org_glvn1_keysz - 1);
|
|
memcpy(gv_currkey->base + org_glvn1_keysz - 2,
|
|
mkey->str.addr + org_glvn2_keysz - 2, delta2 + 2);
|
|
gv_currkey->end = org_glvn1_keysz + delta2 - 1;
|
|
if (nullcoll_src != nullcoll_dst)
|
|
{
|
|
if (0 == nullcoll_dst)
|
|
{ /* Standard to GTM null subscript conversion*/
|
|
STD2GTMNULLCOLL((unsigned char *)gv_currkey->base + org_glvn1_keysz - 1,
|
|
delta2 - 1);
|
|
} else
|
|
{ /* GTM to standard null subscript conversion */
|
|
GTM2STDNULLCOLL((unsigned char *)gv_currkey->base + org_glvn1_keysz - 1,
|
|
delta2 - 1);
|
|
}
|
|
}
|
|
/* check null subscripts in destination key, note that we have already restored, destination global
|
|
* and curresponding region, key information
|
|
*/
|
|
if (check_for_null_subs)
|
|
{
|
|
ptr2 = gv_currkey->base + gv_currkey->end - 1;
|
|
for (ptr = gv_currkey->base + org_glvn1_keysz - 2; ptr < ptr2; )
|
|
{
|
|
if (KEY_DELIMITER == *ptr++ && KEY_DELIMITER == *(ptr + 1) &&
|
|
(0 == gv_cur_region->std_null_coll ? (STR_SUB_PREFIX == *ptr) :
|
|
(SUBSCRIPT_STDCOL_NULL == *ptr)))
|
|
/* Note: For sgnl_gvnulsubsc/rts_error
|
|
* we do not restore proper naked indicator.
|
|
* The standard states that the effect of a MERGE command
|
|
* on the naked indicator is that the naked indicator will be changed
|
|
* as if a specific SET command would have been executed.
|
|
* The standard also states that the effect on the naked indicator
|
|
* will only take be visible after the MERGE command has completed.
|
|
* So, if there is an error during the execution of a MERGE command,
|
|
* the standard allows the naked indicator to reflect any intermediate
|
|
* state. This provision was made intentionally, otherwise it would
|
|
* have become nearly impossible to create a fully standard
|
|
* implementation. : From Ed de Moel : 2/1/2
|
|
*/
|
|
sgnl_gvnulsubsc();
|
|
}
|
|
}
|
|
/* Now put value of ^glvn2 descendant into corresponding descendant under ^glvn1 */
|
|
op_gvput(value);
|
|
}
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* store destination as naked indicator in gv_currkey */
|
|
} else
|
|
{ /*==================== MERGE lvn1=^gvn2 =====================*/
|
|
assert(MARG1_IS_LCL(merge_args));
|
|
assert(mglvnp->lclp[IND1]);
|
|
/* Need to protect subsc created from global variable subscripts from stpgcol */
|
|
PUSH_MV_STENT(MVST_MVAL);
|
|
subsc = &mv_chain->mv_st_cont.mvs_mval;
|
|
/* Restore ^gvn2 we will work */
|
|
gvname_env_save(mglvnp->gblp[IND2]);
|
|
if (1 == dollardata_src || 11 == dollardata_src)
|
|
{ /* SET lvn1=^gvn2 */
|
|
found = op_gvget(value);
|
|
if (found)
|
|
mglvnp->lclp[IND1]->v = *value;
|
|
}
|
|
for (; ;)
|
|
{
|
|
if (outofband)
|
|
{
|
|
gvname_env_restore(mglvnp->gblp[IND2]); /* naked indicator is restored into gv_currkey */
|
|
outofband_action(FALSE);
|
|
}
|
|
assert(0 == gv_currkey->base[gv_currkey->end - 1] && 0 == gv_currkey->base[gv_currkey->end]);
|
|
/* following is an attempt to find immidiate right sibling */
|
|
gv_currkey->base[gv_currkey->end] = 1;
|
|
gv_currkey->base[gv_currkey->end + 1] = 0;
|
|
gv_currkey->base[gv_currkey->end + 2] = 0;
|
|
gv_currkey->end += 2;
|
|
/* Do $QUERY and $GET of current glvn2. Result will be in mkey and value respectively.
|
|
* mkey->str contains data as database format. So no conversion necessary
|
|
*/
|
|
if (!op_gvqueryget(mkey, value))
|
|
break;
|
|
if (mkey->str.len < (TREF(gv_mergekey2))->end + 1)
|
|
break;
|
|
ptr = (unsigned char *)mkey->str.addr + (TREF(gv_mergekey2))->end - 1;
|
|
if (0 != *ptr || memcmp(mkey->str.addr, (TREF(gv_mergekey2))->base, (TREF(gv_mergekey2))->end - 1))
|
|
break;
|
|
assert(MV_IS_STRING(mkey));
|
|
delta2 = mkey->str.len - org_glvn2_keysz; /* length increase of key */
|
|
assert (0 < delta2);
|
|
/* Create next key for ^glvn2 */
|
|
memcpy(gv_currkey->base + org_glvn2_keysz - 2, mkey->str.addr + org_glvn2_keysz - 2, delta2 + 2);
|
|
gv_currkey->end = mkey->str.len - 1;
|
|
/* Now add subscripts to create the entire key */
|
|
dst_lv = mglvnp->lclp[IND1];
|
|
is_base_var = LV_IS_BASE_VAR(dst_lv);
|
|
ptr = (unsigned char *)gv_currkey->base + org_glvn2_keysz - 1;
|
|
assert(*ptr);
|
|
do
|
|
{
|
|
LV_SBS_DEPTH(dst_lv, is_base_var, sbs_depth);
|
|
if (MAX_LVSUBSCRIPTS <= sbs_depth)
|
|
rts_error(VARLSTCNT(3) ERR_MERGEINCOMPL, 0, ERR_MAXNRSUBSCRIPTS);
|
|
ptr2 = gvsub2str(ptr, buff, FALSE);
|
|
subsc->mvtype = MV_STR;
|
|
subsc->str.addr = (char *)buff;
|
|
subsc->str.len = INTCAST(ptr2 - buff);
|
|
s2pool(&subsc->str);
|
|
dst_lv = op_putindx(VARLSTCNT(2) dst_lv, subsc);
|
|
while (*ptr++); /* skip to start of next subscript */
|
|
is_base_var = FALSE;
|
|
} while (*ptr);
|
|
/* We created the key. Pre-process the node in case a container is being replaced,
|
|
* then assign the value directly. Note there is no need to worry about MV_ALIASCONT
|
|
* propagation since the source in this case is a global var.
|
|
*/
|
|
DECR_AC_REF(dst_lv, TRUE);
|
|
dst_lv->v = *value;
|
|
}
|
|
gvname_env_restore(mglvnp->gblp[IND2]); /* naked indicator is restored into gv_currkey */
|
|
POP_MV_STENT(); /* subsc */
|
|
}
|
|
POP_MV_STENT(); /* mkey */
|
|
} else
|
|
{ /* source is local */
|
|
op_fndata(mglvnp->lclp[IND2], value);
|
|
dollardata_src = MV_FORCE_INT(value);
|
|
if (0 == dollardata_src)
|
|
{
|
|
UNDO_ACTIVE_LV;
|
|
POP_MV_STENT(); /* value */
|
|
if (MARG1_IS_GBL(merge_args))
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* store destination as naked indicator in gv_currkey */
|
|
merge_args = 0; /* Must reset to zero to reuse the Global */
|
|
return;
|
|
}
|
|
/* not memsetting output to 0 here can cause garbage value of output.out_var.lv.child which in turn can
|
|
* cause a premature return from lvzwr_var resulting in op_merge() returning without having done the merge.
|
|
*/
|
|
memset(&output, 0, SIZEOF(output));
|
|
if (MARG1_IS_LCL(merge_args))
|
|
{ /*==================== MERGE lvn1=lvn2 =====================*/
|
|
assert(mglvnp->lclp[IND1]);
|
|
/* if self merge then NOOP */
|
|
if (!merge_desc_check()) /* will not proceed if one is descendant of another */
|
|
{
|
|
POP_MV_STENT(); /* value */
|
|
merge_args = 0; /* Must reset to zero to reuse the Global */
|
|
return;
|
|
}
|
|
output.buff = (char *)buff;
|
|
output.ptr = output.buff;
|
|
output.out_var.lv.lvar = mglvnp->lclp[IND1];
|
|
zwr_output = &output;
|
|
lvzwr_init(zwr_patrn_mident, &mglvnp->lclp[IND2]->v);
|
|
lvzwr_arg(ZWRITE_ASTERISK, 0, 0);
|
|
lvzwr_var(mglvnp->lclp[IND2], 0);
|
|
/* assert that destination got all data of the source and its descendants */
|
|
DEBUG_ONLY(op_fndata(mglvnp->lclp[IND1], value));
|
|
DEBUG_ONLY(dollardata_dst = MV_FORCE_INT(value));
|
|
assert((dollardata_src & dollardata_dst) == dollardata_src);
|
|
} else
|
|
{ /*==================== MERGE ^gvn1=lvn2 =====================*/
|
|
assert(MARG1_IS_GBL(merge_args) && MARG2_IS_LCL(merge_args));
|
|
gvname_env_save(mglvnp->gblp[IND1]);
|
|
output.buff = (char *)buff;
|
|
output.ptr = output.buff;
|
|
output.out_var.gv.end = gv_currkey->end;
|
|
output.out_var.gv.prev = gv_currkey->prev;
|
|
zwr_output = &output;
|
|
lvzwr_init(zwr_patrn_mident, &mglvnp->lclp[IND2]->v);
|
|
lvzwr_arg(ZWRITE_ASTERISK, 0, 0);
|
|
lvzwr_var(mglvnp->lclp[IND2], 0);
|
|
gvname_env_restore(mglvnp->gblp[IND1]); /* store destination as naked indicator in gv_currkey */
|
|
}
|
|
}
|
|
POP_MV_STENT(); /* value */
|
|
merge_args = 0; /* Must reset to zero to reuse the Global */
|
|
}
|