fis-gtm/sr_port/op_fnquery.c

384 lines
13 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. *
* *
****************************************************************/
#include "mdef.h"
#include <stdarg.h>
#include "gtm_string.h"
#include "lv_val.h"
#include "subscript.h"
#include "stringpool.h"
#include <rtnhdr.h>
#include "mv_stent.h"
#include "collseq.h"
#include "compiler.h"
#include "op.h"
#include "do_xform.h"
#include "mvalconv.h"
#include "numcmp.h"
#include "promodemo.h" /* for "demote" prototype used in LV_NODE_GET_KEY */
GBLREF spdesc stringpool;
GBLREF mv_stent *mv_chain;
GBLREF unsigned char *msp, *stackwarn, *stacktop;
LITREF mval literal_null;
error_def(ERR_STACKOFLOW);
error_def(ERR_STACKCRIT);
error_def(ERR_LVNULLSUBS);
void op_fnquery(UNIX_ONLY_COMMA(int sbscnt) mval *dst, ...)
{
int length;
mval tmp_sbs;
va_list var;
mval *varname, *v1, *v2, *mv, tmpmv;
mval *arg1, **argpp, *args[MAX_LVSUBSCRIPTS], **argpp2, *lfrsbs, *argp2;
mval xform_args[MAX_LVSUBSCRIPTS]; /* for lclcol */
mstr format_out;
lv_val *v;
lvTreeNode **h1, **h2, *history[MAX_LVSUBSCRIPTS], *parent, *node, *nullsubsnode, *nullsubsparent;
lvTree *lvt;
int i, j;
VMS_ONLY(int sbscnt;)
boolean_t found, is_num, last_sub_null, nullify_term, is_str;
DCL_THREADGBL_ACCESS;
SETUP_THREADGBL_ACCESS;
VAR_START(var, dst);
VMS_ONLY(va_count(sbscnt));
assert(3 <= sbscnt);
sbscnt -= 3;
varname = va_arg(var, mval *);
v = va_arg(var, lv_val *);
assert(v);
assert(LV_IS_BASE_VAR(v));
lvt = LV_GET_CHILD(v);
if (NULL == lvt)
{
dst->mvtype = MV_STR;
dst->str.len = 0;
return;
}
h1 = history;
*h1++ = (lvTreeNode *)v;
found = FALSE;
DEBUG_ONLY(node = NULL;)
for (i = 0, argpp = &args[0]; i < sbscnt; i++, argpp++, h1++)
{
if (NULL == lvt)
{ /* Subtree does not exist at this level. To get $query of input node,
* need to start from right sibling of parent level. */
h1--;
assert(h1 >= &history[1]);
assert(NULL != node);
assert(node == *h1);
node = *(h1 - 1);
lvt = LV_GET_CHILD(node);
assert(NULL != lvt);
break;
}
arg1 = *argpp = va_arg(var, mval *);
MV_FORCE_DEFINED(arg1);
is_str = MV_IS_STRING(arg1);
if (is_str)
{
if ((0 == arg1->str.len) && (i + 1 != sbscnt) && (LVNULLSUBS_NEVER == TREF(lv_null_subs)))
{ /* This is not the last subscript, we don't allow nulls subs and it was null */
va_end(var);
rts_error(VARLSTCNT(1) ERR_LVNULLSUBS);
}
if (is_num = MV_IS_CANONICAL(arg1))
MV_FORCE_NUM(arg1);
else if ((i + 1 == sbscnt) && (0 == arg1->str.len))
{ /* The last search argument is a null string. For this situation, there is the possibility
* of a syntax collision if (1) the user had (for example) specified $Q(a(1,3,"") to get
* the first a(1,3,x) node or (2) this is the "next" element that was legitimately returned
* by $Query on the last call. If the element a(1,3,"") actually exists, the code whereby
* we simply strip off the last null search argument and continue will just find this
* element again. Because of this we save the mvals of the subscrips of the last $QUERY
* result if the result had a null last subscript. In that case, we will compare the input
* variable and subscripts with the last returned value. If they are identical, we will
* bypass the elimination of the trailing null subscript. This will allow the successor
* element to be found. Note that this is a single value that is kept for the entire
* process. Any intervening $QUery calls for other local variables will reset the check.
* SE 2/2004 (D9D08-002352).
*/
nullify_term = TRUE;
if ((TREF(last_fnquery_return_varname)).str.len
&& (TREF(last_fnquery_return_varname)).str.len == varname->str.len
&& 0 == memcmp((TREF(last_fnquery_return_varname)).str.addr,
varname->str.addr, varname->str.len)
&& sbscnt == TREF(last_fnquery_return_subcnt))
{ /* We have an equivalent varname and same number subscripts */
for (j = 0, argpp2 = &args[0], lfrsbs = TADR(last_fnquery_return_sub);
j < i; j++, argpp2++, lfrsbs++)
{ /* For each subscript prior to the trailing null subscript */
argp2 = *argpp2;
if (MV_IS_NUMERIC(argp2) && MV_IS_NUMERIC(lfrsbs))
{ /* Have numeric subscripts */
if (0 != numcmp(argp2, lfrsbs))
break; /* This subscript isn't the same */
} else if (MV_IS_STRING(argp2) && MV_IS_STRING(lfrsbs))
{ /* Should be string only in order to compare */
if ((argp2)->str.len == lfrsbs->str.len
&& 0 != memcmp((argp2)->str.addr, lfrsbs->str.addr,
lfrsbs->str.len))
break; /* This subscript isn't the same */
} else
break; /* This subscript isn't even close.. */
}
if (j == i)
nullify_term = FALSE;/* We made it through the loop unscathed !! */
}
if (nullify_term)
{
i++;
assert(i == sbscnt);
break;
}
}
} else /* not string */
{
assert(MV_IS_NUMERIC(arg1));
assert(MV_IS_CANONICAL(arg1));
is_num = TRUE;
}
if (!is_num)
{
assert(!TREE_KEY_SUBSCR_IS_CANONICAL(arg1->mvtype));
if (TREF(local_collseq))
{
ALLOC_XFORM_BUFF(arg1->str.len);
/* D9607-258 changed xback to xform and added xform_args[] to hold mval pointing to
* xform'd subscript which is in pool space. tmp_sbs (which is really a mval) was being
* overwritten if >1 alpha subscripts */
assert(NULL != TREF(lcl_coll_xform_buff));
tmp_sbs.mvtype = MV_STR;
tmp_sbs.str.addr = TREF(lcl_coll_xform_buff);
tmp_sbs.str.len = TREF(max_lcl_coll_xform_bufsiz);
/* KMK subscript index is i+1 */
do_xform(TREF(local_collseq), XFORM, &arg1->str, &tmp_sbs.str, &length);
tmp_sbs.str.len = length;
s2pool(&(tmp_sbs.str));
xform_args[i] = tmp_sbs;
arg1 = &xform_args[i];
}
node = lvAvlTreeLookupStr(lvt, arg1, &parent);
} else
{
tmp_sbs = *arg1;
arg1 = &tmp_sbs;
MV_FORCE_NUM(arg1);
TREE_KEY_SUBSCR_SET_MV_CANONICAL_BIT(arg1); /* used by the lvAvlTreeLookup* functions below */
if (MVTYPE_IS_INT(arg1->mvtype))
node = lvAvlTreeLookupInt(lvt, arg1, &parent);
else
node = lvAvlTreeLookupNum(lvt, arg1, &parent);
}
if (NULL == node)
break; /* Key not found in tree at this level. find right sibling in "lvt" tree with key="arg1" */
lvt = LV_GET_CHILD(node);
*h1 = node;
}
va_end(var);
assert((i != sbscnt) || !found);
if (!found)
{
if (i == sbscnt)
{
assert(!found);
parent = *(h1 - 1);
assert(NULL != parent);
lvt = LV_GET_CHILD(parent);
if (lvt)
{
found = TRUE;
nullsubsnode = TREF(local_collseq_stdnull)
? lvAvlTreeLookupStr(lvt, (treeKeySubscr *)&literal_null, &nullsubsparent)
: NULL;
node = (NULL == nullsubsnode) ? lvAvlTreeFirst(lvt) : nullsubsnode;
assert(NULL != node);
*h1 = node;
} else
--h1;
} else
{ /* Need to find right sibling. "lvt" is tree to search in and "arg1" is key to search for. */
node = lvAvlTreeKeyCollatedNext(lvt, arg1);
if (NULL != node)
{
*h1 = node;
found = TRUE;
} else /* if "node" is still NULL, need to start searching for right siblings at the parent level. */
h1--;
}
}
if (!found) /* separate if needed because "found" could change after the previous "if" but before this "if" */
{
for ( ; ; --h1)
{
assert(h1 >= &history[0]);
if (h1 == &history[0])
{
dst->mvtype = MV_STR;
dst->str.len = 0;
return;
}
node = *h1;
assert(NULL != node);
/* Find the right sibling (next) subscript */
node = lvAvlTreeNodeCollatedNext(node);
if (NULL != node)
{
*h1 = node;
break;
}
}
}
/* Saved last query result is irrelevant now */
TREF(last_fnquery_return_subcnt) = 0;
(TREF(last_fnquery_return_varname)).mvtype = MV_STR;
(TREF(last_fnquery_return_varname)).str.len = 0;
/* Go down leftmost subtree path (potentially > 1 avl trees) starting from "node" until you find the first DEFINED mval */
while (!LV_IS_VAL_DEFINED(node))
{
lvt = LV_GET_CHILD(node);
assert(NULL != lvt); /* there cannot be an undefined lv node with no children dangling around */
nullsubsnode = TREF(local_collseq_stdnull)
? lvAvlTreeLookupStr(lvt, (treeKeySubscr *)&literal_null, &nullsubsparent)
: NULL;
node = (NULL == nullsubsnode) ? lvAvlTreeFirst(lvt) : nullsubsnode;
assert(NULL != node);
*++h1 = node;
}
/* Before we start formatting for output, decide whether we will be saving mvals of our subscripts
* as we format. We only do this if the last subscript is a null. Bypassing it otherwise is a time saver.
*/
last_sub_null = LV_NODE_KEY_IS_NULL_SUBS(node);
/* format the output string */
ENSURE_STP_FREE_SPACE(varname->str.len + 1);
PUSH_MV_STENT(MVST_MVAL);
v1 = &mv_chain->mv_st_cont.mvs_mval;
v1->mvtype = MV_STR;
v1->str.len = 0;
v1->str.addr = (char *)stringpool.free;
PUSH_MV_STENT(MVST_MVAL);
v2 = &mv_chain->mv_st_cont.mvs_mval;
v2->mvtype = 0; /* initialize it to 0 to avoid "stp_gcol" from getting confused if it gets invoked before v2 has been
* completely setup. */
memcpy(stringpool.free, varname->str.addr, varname->str.len);
if (last_sub_null)
{
(TREF(last_fnquery_return_varname)).str.addr = (char *)stringpool.free;
(TREF(last_fnquery_return_varname)).str.len += varname->str.len;
}
stringpool.free += varname->str.len;
*stringpool.free++ = '(';
for (h2 = &history[1]; h2 <= h1; h2++)
{
node = *h2;
assert(!LV_IS_BASE_VAR(node)); /* guarantees to us that "node" is a "lvTreeNode *" and not "lv_val *" */
mv = &tmpmv;
LV_NODE_GET_KEY(node, mv); /* Get node key into "mv" depending on the structure type of "node" */
if (MV_IS_NUMERIC(mv))
{ /* number */
if (!IS_STP_SPACE_AVAILABLE(MAX_NUM_SIZE))
{
v1->str.len = INTCAST((char *)stringpool.free - v1->str.addr);
INVOKE_STP_GCOL(MAX_NUM_SIZE);
assert((char *)stringpool.free - v1->str.addr == v1->str.len);
}
*v2 = *mv;
/* Now that we have ensured enough space in the stringpool, we dont expect any more
* garbage collections or expansions until we are done with the n2s.
*/
DBG_MARK_STRINGPOOL_UNEXPANDABLE;
n2s(v2);
/* Now that we are done with any stringpool.free usages, mark as free for expansion */
DBG_MARK_STRINGPOOL_EXPANDABLE;
if (last_sub_null)
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))++) = *v2;
} else
{ /* string */
assert(MV_IS_STRING(mv));
v1->str.len = INTCAST((char *)stringpool.free - v1->str.addr);
v2->mvtype = 0; /* initialize it to 0 to avoid "stp_gcol" from getting confused
* if it gets invoked before v2 has been completely setup. */
if (TREF(local_collseq))
{
ALLOC_XFORM_BUFF(mv->str.len);
assert(NULL != TREF(lcl_coll_xform_buff));
tmp_sbs.str.addr = TREF(lcl_coll_xform_buff);
tmp_sbs.str.len = TREF(max_lcl_coll_xform_bufsiz);
do_xform(TREF(local_collseq), XBACK, &mv->str, &tmp_sbs.str, &length);
tmp_sbs.str.len = length;
v2->str = tmp_sbs.str;
} else
v2->str = mv->str;
/* Now that v2->str has been initialized, initialize mvtype as well (doing this in the other
* order could cause "stp_gcol" (if invoked in between) to get confused since v2->str is
* not yet initialized with current subscript (in the M-stack).
*/
v2->mvtype = MV_STR;
mval_lex(v2, &format_out);
if (format_out.addr != (char *)stringpool.free) /* BYPASSOK */
{ /* We must put the string on the string pool ourself - mval_lex didn't do it
* because v2 is a canonical numeric string. It is canonical but has too many
* digits to be treated as a number. It must be output as a quoted string. */
if (!IS_STP_SPACE_AVAILABLE(v2->str.len + 2))
{
v1->str.len = INTCAST((char *)stringpool.free - v1->str.addr);
INVOKE_STP_GCOL(v2->str.len + 2);
assert((char *)stringpool.free - v1->str.addr == v1->str.len);
}
*stringpool.free++ = '\"';
memcpy(stringpool.free, v2->str.addr, v2->str.len);
if (last_sub_null)
{
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))).mvtype = MV_STR;
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))).str.addr =
(char *)stringpool.free;
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))++).str.len =
v2->str.len;
}
stringpool.free += v2->str.len;
*stringpool.free++ = '\"';
} else
{
if (last_sub_null)
{
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))).mvtype = MV_STR;
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))).str.addr =
(char *)stringpool.free;
TAREF1(last_fnquery_return_sub,(TREF(last_fnquery_return_subcnt))++).str.len =
format_out.len;
}
stringpool.free += format_out.len;
}
}
if (!IS_STP_SPACE_AVAILABLE(1))
{
v1->str.len = INTCAST((char *)stringpool.free - v1->str.addr);
INVOKE_STP_GCOL(1);
assert((char *)stringpool.free - v1->str.addr == v1->str.len);
}
*stringpool.free++ = (h2 < h1 ? ',' : ')');
}
dst->mvtype = MV_STR;
dst->str.len = INTCAST((char *)stringpool.free - v1->str.addr);
dst->str.addr = v1->str.addr;
POP_MV_STENT(); /* v2 */
POP_MV_STENT(); /* v1 */
}