示例#1
0
DB_STATUS
qen_tsort(
QEN_NODE           *node,
QEF_RCB            *qef_rcb,
QEE_DSH		   *dsh,
i4		    function )
{
    QEF_CB	*qef_cb = dsh->dsh_qefcb;
    PTR		*cbs = dsh->dsh_cbs;
    ADE_EXCB	*ade_excb;
    DMR_CB	*dmr_load = (DMR_CB*)cbs[node->node_qen.qen_tsort.tsort_load];
    DMR_CB	*dmr_get = (DMR_CB*) cbs[node->node_qen.qen_tsort.tsort_get];
    QEN_NODE	*out_node = node->node_qen.qen_tsort.tsort_out;
    QEE_XADDRS	*node_xaddrs = dsh->dsh_xaddrs[node->qen_num];
    QEN_STATUS	*qen_status = node_xaddrs->qex_status;
    QEN_SHD	*shd = dsh->dsh_shd[node->node_qen.qen_tsort.tsort_shd];
    DB_STATUS	status;
    bool	reset = FALSE;
    bool	heap_sort = TRUE, no_qef = FALSE;
    i4		rowno;
    i4		out_func = NO_FUNC;
    DM_MDATA	dm_mdata;
    i4	val1;
    i4	val2;
    TIMERSTAT	timerstat;
    i4          loop_cntr = 0;

    if (function != 0)
    {
	if (function & FUNC_RESET)
	{
	    reset = TRUE;
	    out_func = FUNC_RESET;
	}

	/* Do open processing, if required. Only if this is the root node
	** of the query tree do we continue executing the function. */
	if ((function & TOP_OPEN || function & MID_OPEN) && 
	    !(qen_status->node_status_flags & QEN1_NODE_OPEN))
	{
	    status = (*out_node->qen_func)(out_node, qef_rcb, dsh, MID_OPEN);
	    qen_status->node_status_flags = QEN1_NODE_OPEN;
	    if (function & MID_OPEN)
		return(E_DB_OK);
	    function &= ~TOP_OPEN;
	}
	/* Do close processing, if required. */
	if (function & FUNC_CLOSE)
	{
	    if (!(qen_status->node_status_flags & QEN8_NODE_CLOSED))
	    {
		status = (*out_node->qen_func)(out_node, qef_rcb, dsh, FUNC_CLOSE);
		qen_status->node_status_flags = 
		    (qen_status->node_status_flags & ~QEN1_NODE_OPEN) | QEN8_NODE_CLOSED;
	    }
	    return(E_DB_OK);
	}
	if (function & FUNC_EOGROUP)
	{
	    /* End of partition group request ends our sort if we're returning
	    ** rows.  If we aren't in the middle of returning rows, pass the
	    ** EOG request on down so that the child skips the upcoming group
	    ** and moves on to the next one.
	    */
	    if (qen_status->node_status == QEN3_GET_NEXT_INNER)
	    {
		status = qen_ts_reset(dsh, node, qen_status);
		/* FIXME we should do better at remembering whether this
		** sort load got EOF or EOG, and pass it on up now.  At
		** present (Apr '07), tsorts aren't very common except under
		** merge join plans, where early eof detection doesn't
		** matter much.  (the tsort is on the outer side.)
		** For now, pass EOG back up, if we're really at EOF caller
		** will find out soon enough.
		*/
		if (status == E_DB_OK)
		{
		    status = E_DB_WARN;
		    dsh->dsh_error.err_code = E_QE00A5_END_OF_PARTITION;
		}
	    }
	    else
	    {
		status = (*out_node->qen_func)(out_node, qef_rcb, dsh, FUNC_EOGROUP);
		/* Pass resulting EOF or EOG indication in dsh up to caller */
	    }
	    return(status);
	}
    } /* if function != 0 */


    /* Check for cancel, context switch if not MT */
    CScancelCheck(dsh->dsh_sid);
    if (QEF_CHECK_FOR_INTERRUPT(qef_cb, dsh) == E_DB_ERROR)
	return (E_DB_ERROR);

    /* If the trace point qe90 is turned on then gather cpu and dio stats */
    if (dsh->dsh_qp_stats)
    {
	qen_bcost_begin(dsh, &timerstat, qen_status);
    }

    if (node->node_qen.qen_tsort.tsort_dups == DMR_NODUPLICATES &&
        ult_check_macro(&qef_cb->qef_trace, 92, &val1, &val2))
            heap_sort = FALSE;

    if (ult_check_macro(&qef_cb->qef_trace, 94, &val1, &val2))
    {
	no_qef = TRUE;
	qen_status->node_u.node_sort.node_sort_status = QEN9_DMF_SORT;
    }

#ifdef xDEBUG
    (VOID) qe2_chk_qp(dsh);
#endif

    for (;;)	    /* to break off in case of error */
    {
	if (reset && qen_status->node_status != QEN0_INITIAL)
	{
	    status = qen_ts_reset(dsh, node, qen_status);
	    if (status != E_DB_OK)
		break;
	}

	/* If NO MORE ROWS from this node, just return */
	if (qen_status->node_status == QEN4_NO_MORE_ROWS)
	{
	    dsh->dsh_error.err_code = E_QE0015_NO_MORE_ROWS;
	    status = E_DB_WARN;
	    break;
	}

	rowno = node->node_qen.qen_tsort.tsort_mat->qen_output;
	ade_excb = node_xaddrs->qex_otmat;

	/* If this is the first time execution, or if the node is reset, 
	** initialize the sorter. If this is not, just go get a tuple.
	*/
	if (qen_status->node_status == QEN0_INITIAL || 
	    qen_status->node_status == QEN1_EXECUTED)
	{
	    if (qen_status->node_status == QEN0_INITIAL)
	    {
		if (qen_status->node_u.node_sort.node_sort_status == QEN0_QEF_SORT)
		{
		    status = qes_init(dsh, shd, node, rowno,
				node->node_qen.qen_tsort.tsort_dups);
		    if (status != E_DB_OK)
		    {
			if (dsh->dsh_error.err_code == E_QE000D_NO_MEMORY_LEFT)
			{
			    /* out of space, convert it to DMF sort */
			    qen_status->node_u.node_sort.node_sort_status = QEN9_DMF_SORT;
			    status = E_DB_OK;
			}
			else
			{
			    break;
			}
		    }
		}

		if (qen_status->node_u.node_sort.node_sort_status == QEN9_DMF_SORT)
		{
		    status = qen_ts_dump(shd, dsh, node, 
					rowno, heap_sort, no_qef);
		    if (status != E_DB_OK)
			break;
		}

		if (node->node_qen.qen_tsort.tsort_pqual != NULL)
		    qeq_join_pqreset(dsh, node->node_qen.qen_tsort.tsort_pqual);

		qen_status->node_status = QEN1_EXECUTED;
	    }

	    /* Common code for QEN0_INITIAL and QEN1_EXECUTED */

	    /* Get dm_mdata ready for DMF loading  */
	    dm_mdata.data_address = dsh->dsh_row[rowno];
	    dm_mdata.data_size = dsh->dsh_qp_ptr->qp_row_len[rowno];
	    dmr_load->dmr_mdata = &dm_mdata;

            dsh->dsh_qp_status |= DSH_SORT;

	    /* Get rows from the underneath node and append them to the 
	    ** sorter */
	    for (;;)
	    {
		/* fetch rows */
		status = (*out_node->qen_func)(out_node, qef_rcb, dsh, out_func);
		out_func = NO_FUNC;
		if (status != E_DB_OK)
		{
		    /* the error.err_code field in qef_rcb should already be
		    ** filled in.
		    */
		    if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS)
		    {
			status = E_DB_OK;
		    }
		    else if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION &&
			(node->qen_flags & QEN_PART_SEPARATE))
		    {
			/* End of rows from partitioning group. Flag the
			** fact and make it look like regular "eof". */
			qen_status->node_status_flags |= QEN2_OPART_END;
			dsh->dsh_error.err_code = E_QE0015_NO_MORE_ROWS;
			status = E_DB_OK;
		    }

		    break;
		}
		
		/* project the attributes into sort tuple row */
		status = qen_execute_cx(dsh, ade_excb);
		if (status != E_DB_OK)
		    break;

		/* If we're generating partition qualifications on behalf
		** of a parent FSM join, eval against this row.
		*/
		if (node->node_qen.qen_tsort.tsort_pqual != NULL)
		{
		    status = qeq_join_pquals(dsh, node->node_qen.qen_tsort.tsort_pqual);
		    if (status != E_DB_OK)
			break;
		}

                /* append the sort tuple into the sorter - note periodic
                ** differences between heap sort and trace point mandated
                ** insert sort */
		if (qen_status->node_u.node_sort.node_sort_status == QEN0_QEF_SORT)
		{
		    if (heap_sort) status = qes_putheap(dsh, shd, 
                         node->node_qen.qen_tsort.tsort_cmplist,
                         node->node_qen.qen_tsort.tsort_scount); 
                     else status = qes_insert(dsh, shd,
                         node->node_qen.qen_tsort.tsort_cmplist,
                         node->node_qen.qen_tsort.tsort_scount);

		    if (status != E_DB_OK && 
			dsh->dsh_error.err_code == E_QE000D_NO_MEMORY_LEFT)
		    {
			/* out of space, convert it to DMF sort */
			qen_status->node_u.node_sort.node_sort_status = QEN9_DMF_SORT;
			status = qen_ts_dump(shd, dsh, 
					node, rowno, heap_sort, no_qef);
		    }
		    if (status != E_DB_OK)
			break;
		}

		if(qen_status->node_u.node_sort.node_sort_status == QEN9_DMF_SORT)  
		{
		    status = dmf_call(DMR_LOAD, dmr_load);
		    if (status != E_DB_OK)
		    {
			dsh->dsh_error.err_code = dmr_load->error.err_code; 
			break;
		    }
		}

                if (!(Qef_s_cb->qef_state & QEF_S_IS_MT) && (loop_cntr++ >1000))
                {
                   /* On an Internal threaded system, after processing 1000 rows
                   ** give another thread a chance.
                   */

                   loop_cntr = 0;

                   CSswitch();
                }
	    }
	    if (status != E_DB_OK)
		break;

	    /* End of loading loop */
	    /* Finish up join-time part qual if we're doing it */
	    if (node->node_qen.qen_tsort.tsort_pqual != NULL)
	    {
		qeq_join_pqeof(dsh, node->node_qen.qen_tsort.tsort_pqual);
	    }
	    if(qen_status->node_u.node_sort.node_sort_status == QEN0_QEF_SORT)
	    {
		if (!heap_sort) qes_endsort(dsh, shd);
						/* prep return of tups */

	    }
	    else   /* DMF */
	    {
		/* Tell DMF that there are no more rows */
		dmr_load->dmr_flags_mask = (DMR_ENDLOAD | DMR_SORT_NOCOPY);
		status = dmf_call(DMR_LOAD, dmr_load);
		if (status != E_DB_OK)
		{
		    dsh->dsh_error.err_code = dmr_load->error.err_code;
		    break;
		}

		/* position the table for reading sorted tuples */
		dmr_get->dmr_flags_mask = DMR_SORTGET;
		dmr_get->dmr_position_type = DMR_ALL;
		status = dmf_call(DMR_POSITION, dmr_get);
		if (status != E_DB_OK)
		{
		    dsh->dsh_error.err_code = dmr_get->error.err_code;
		    break;
		}
	    }

	    /* Mark the node is ready to return tuples */
	    qen_status->node_status = QEN3_GET_NEXT_INNER;
	}


	/* 
	** Return a tuple from the sorter to the caller.
	*/
	if (qen_status->node_u.node_sort.node_sort_status == QEN0_QEF_SORT)
	{
	    if (heap_sort) 
	    {
		status = qes_getheap(dsh, shd, 
                         node->node_qen.qen_tsort.tsort_cmplist,
                         node->node_qen.qen_tsort.tsort_scount); 
		if (status != E_DB_OK) 
		{
		    if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS)
		    {
			qen_status->node_status = QEN4_NO_MORE_ROWS;
			status = E_DB_WARN;
		    }
		    break;
		}
	    }
	    else
	    {		/* rows come straight from pointer array */
	 	/* Check for the end of buffer */
		if (shd->shd_next_tup == shd->shd_tup_cnt)
		{
		    qen_status->node_status = QEN4_NO_MORE_ROWS;
		    dsh->dsh_error.err_code = E_QE0015_NO_MORE_ROWS;
		    status = E_DB_WARN;
		    break;
		}

		/* Copy tuple to the row buffer */
		MEcopy((PTR)(shd->shd_vector[shd->shd_next_tup]), 
		    shd->shd_width, (PTR)shd->shd_row);
		++shd->shd_next_tup;
		status = E_DB_OK;
	    }
	}
	else
	{
	    dmr_get->dmr_flags_mask = (DMR_NEXT | DMR_SORTGET);
	    status = dmf_call(DMR_GET, dmr_get);
	    if (status != E_DB_OK)
	    {
		if (dmr_get->error.err_code == E_DM0055_NONEXT)
		{
		    qen_status->node_status = QEN4_NO_MORE_ROWS;
		    dsh->dsh_error.err_code = E_QE0015_NO_MORE_ROWS;
		    status = E_DB_WARN;
		}
		else
		{
		    dsh->dsh_error.err_code = dmr_get->error.err_code;
		}
		break;
	    }
	}
	/* status has to be OK here */

	/* Increment the count of rows that this node has returned */
	qen_status->node_rcount++;
	dsh->dsh_error.err_code = 0;

	/* print tracing information DO NOT xDEBUG THIS */
	if (node->qen_prow &&
		(ult_check_macro(&qef_cb->qef_trace, 100+node->qen_num,
				    &val1, &val2) ||
		    ult_check_macro(&qef_cb->qef_trace, 99,
				    &val1, &val2)
		)
	    )
	{
	    (void) qen_print_row(node, qef_rcb, dsh);
	}

	break;
    }	    /* end of error-break loop */

#ifdef xDEBUG
    (VOID) qe2_chk_qp(dsh);
#endif

    if (dsh->dsh_qp_stats)
    {
	qen_ecost_end(dsh, &timerstat, qen_status);
    }

    dsh->dsh_qp_status &= ~DSH_SORT;

    if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS &&
	(qen_status->node_status_flags & QEN2_OPART_END))
    {
	/* If this was just the end of a partition, reset error code
	** to notify the caller. */
	qen_status->node_status_flags &= ~QEN2_OPART_END;
	dsh->dsh_error.err_code = E_QE00A5_END_OF_PARTITION;
	status = E_DB_WARN;
    }
    return (status);
}
示例#2
0
DB_STATUS
qen_fsmjoin(
QEN_NODE	*node,
QEF_RCB		*qef_rcb,
QEE_DSH		*dsh,
i4		function )
{
    QEF_CB	    *qef_cb = dsh->dsh_qefcb;
    DMR_CB	    *dmrcb;
    QEN_NODE	    *out_node = node->node_qen.qen_sjoin.sjn_out;
    QEN_NODE	    *in_node = node->node_qen.qen_sjoin.sjn_inner;
    QEE_XADDRS	    *node_xaddrs = dsh->dsh_xaddrs[node->qen_num];
    QEN_STATUS	    *qen_status = node_xaddrs->qex_status;
    ADE_EXCB	    *ade_excb;
    ADE_EXCB	    *jqual_excb = node_xaddrs->qex_jqual;
    QEN_HOLD	    *qen_hold;
    QEN_HOLD	    *ijFlagsHold = (QEN_HOLD *)NULL;
    QEN_SHD         *qen_shd;
    QEN_SHD         *ijFlagsShd;
    DB_STATUS	    status = E_DB_OK;
    bool	    reset = FALSE;
    bool	    out_reset = FALSE;
    bool	    in_reset = FALSE;
    bool            ojoin = (node->node_qen.qen_sjoin.sjn_oj != NULL); 
    bool            ljoin = FALSE;
    bool            rjoin = FALSE;
    bool	    innerTupleJoined;
    bool	    rematerializeInnerTuple = TRUE;
			/* During full joins, the last driving tuple may left
			** join.  This 0s all special eqcs from the
			** re-scannable stream.  The current re-scannable
			** tuple will right join.  To recover the state of
			** its special eqcs, simply re-materialize the inner
			** tuple.  That's what this variable is for.
			*/
    i4		    new_to_old;
    i4		    join_result;
    i4	    val1;
    i4	    val2;
    TIMERSTAT	    timerstat;
    bool potential_card_violation = FALSE;

#ifdef xDEBUG
    (VOID) qe2_chk_qp(dsh);
#endif

    if (function != 0)
    {
	if (function & FUNC_RESET)
	{
	    reset = in_reset = out_reset = TRUE;
	}

	/* Do open processing, if required. Only if this is the root node
	** of the query tree do we continue executing the function. */
	if ((function & TOP_OPEN || function & MID_OPEN) 
	    && !(qen_status->node_status_flags & QEN1_NODE_OPEN))
	{
	    status = (*out_node->qen_func)(out_node, qef_rcb, dsh, MID_OPEN);
	    status = (*in_node->qen_func)(in_node, qef_rcb, dsh, MID_OPEN);
	    qen_status->node_status_flags |= QEN1_NODE_OPEN;
	    if (function & MID_OPEN)
		return(E_DB_OK);
	    function &= ~TOP_OPEN;
	}
	/* Do close processing, if required. */
	if (function & FUNC_CLOSE)
	{
	    if (!(qen_status->node_status_flags & QEN8_NODE_CLOSED))
	    {
		/* Ideally we would clean up all of our own shd crap here
		** instead of making qee do it...
		*/
		status = (*out_node->qen_func)(out_node, qef_rcb, dsh, FUNC_CLOSE);
		status = (*in_node->qen_func)(in_node, qef_rcb, dsh, FUNC_CLOSE);
		qen_status->node_status_flags = 
		    (qen_status->node_status_flags & ~QEN1_NODE_OPEN) | QEN8_NODE_CLOSED;
	    }
	    return(E_DB_OK);
	}

	/* End of partition group call just gets passed down. */
	if (function & FUNC_EOGROUP)
	{
	    status = (*out_node->qen_func)(out_node, qef_rcb, dsh, FUNC_EOGROUP);
	    status = (*in_node->qen_func)(in_node, qef_rcb, dsh, FUNC_EOGROUP);
	    return(E_DB_OK);
	}
    } /* if function */

    /* If the trace point qe90 is turned on then gather cpu and dio stats */
    if (dsh->dsh_qp_stats)
    {
	qen_bcost_begin(dsh, &timerstat, qen_status);
    }
     

    /* Check for cancel, context switch if not MT */
    CScancelCheck(dsh->dsh_sid);
    if (QEF_CHECK_FOR_INTERRUPT(qef_cb, dsh) == E_DB_ERROR)
	return (E_DB_ERROR);

    dsh->dsh_error.err_code = E_QE0000_OK;

    qen_hold = dsh->dsh_hold[node->node_qen.qen_sjoin.sjn_hfile];
    qen_shd = dsh->dsh_shd[dsh->dsh_qp_ptr->qp_sort_cnt +
                            node->node_qen.qen_sjoin.sjn_hfile];

    if( ojoin && node->node_qen.qen_sjoin.sjn_oj->oj_ijFlagsFile >= 0 )
    {
        ijFlagsHold =
	  dsh->dsh_hold[node->node_qen.qen_sjoin.sjn_oj->oj_ijFlagsFile];
        ijFlagsShd = dsh->dsh_shd[dsh->dsh_qp_ptr->qp_sort_cnt +
                            node->node_qen.qen_sjoin.sjn_oj->oj_ijFlagsFile];
    }

    if ( ojoin ) switch(node->node_qen.qen_sjoin.sjn_oj->oj_jntype)
    {
       case DB_LEFT_JOIN:
         ljoin = TRUE;
         break;
       case DB_RIGHT_JOIN:
         rjoin = TRUE;
         break;
       case DB_FULL_JOIN:
         ljoin = TRUE;
         rjoin = TRUE;
         break;
       default: break;
    }

    /* If the node is to be reset, dump the hold file and reset the
    ** inner/outer nodes */

loop_reset:
    if (reset)
    {
	if (qen_status->node_status != QEN0_INITIAL && 
	    in_node->qen_type != QE_SORT)
	{
	    /* reset in memory or dump dmf hold if it has been created */
	    status = qen_u9_dump_hold(qen_hold, dsh, qen_shd);
	    if(status) goto errexit;
	    qen_hold->hold_medium = HMED_IN_MEMORY;  /* set back to mem */
	}
	qen_hold->hold_buffer_status = HFILE6_BUF_EMPTY;

	if ( qen_status->node_status != QEN0_INITIAL && ijFlagsHold )
	{
	    /* dump tid hold file if it has been created */

	    status = qen_u9_dump_hold( ijFlagsHold, dsh, ijFlagsShd );
	    if(status) goto errexit;  
	    ijFlagsHold->hold_medium = HMED_IN_MEMORY;  /* set back to mem */
	}

	qen_status->node_status = QEN0_INITIAL;  /* reset = reintialize */
	qen_status->node_u.node_join.node_inner_status = QEN0_INITIAL;
	qen_status->node_u.node_join.node_outer_status = QEN0_INITIAL;
	qen_status->node_u.node_join.node_outer_count = 0;

	qen_status->node_access = (	QEN_READ_NEXT_OUTER |
					QEN_READ_NEXT_INNER |
					QEN_OUTER_HAS_JOINED	);  
    }

    if (qen_status->node_status == QEN0_INITIAL)  
    {
	qen_status->node_u.node_join.node_outer_status = QEN0_INITIAL;

	/* set num entries in mem_hold in case we build one */
	/* this may not be a hard number in future */
	/* qen_shd->shd_tup_cnt = 20; */

	/* by setting it to -1, the required memory will be configured to */
	/* suit the condition. if it is < 20, it will use the dmf hold mem*/
	/* ramra01 19-oct-94 */
	qen_shd->shd_tup_cnt = -1;

	if( ijFlagsHold )
	{
	    ijFlagsHold->hold_status = HFILE0_NOFILE;	/* default */
	    ijFlagsHold->hold_status2 = 0;		/* default */
	    ijFlagsHold->hold_medium = HMED_IN_MEMORY;  /* default */
	    /* in case we build a hold file
	    ** tell qen_u1_append to calculate its size in memory
	    ** or go to DMF hold */
	    ijFlagsShd->shd_tup_cnt = -1;
	}

	if(rjoin)
	{
	    /* consistency check */
	    if( !ijFlagsHold )
	    {
		/* rjoin and no hold file for inner join flags */
		dsh->dsh_error.err_code = E_QE0002_INTERNAL_ERROR;
		status = E_DB_ERROR;
		goto errexit;           
	    }
	}

	qen_status->node_access = (	QEN_READ_NEXT_OUTER |
					QEN_READ_NEXT_INNER |
					QEN_OUTER_HAS_JOINED	);  
    }

    for (;;)   /* The loop */
    {
	status = E_DB_OK;

	/*********************************************************
	**	
	**	LOGIC TO READ FROM THE OUTER TUPLE STREAM
	**
	**
	**
	*********************************************************/

	if( qen_status->node_access & QEN_READ_NEXT_OUTER )
	{

	    /*
	    ** If the previous outer tuple did not inner join with
	    ** any inner tuples, then it's an outer join.  Return
	    ** it along with nulls for the right side if it passes
	    ** the WHERE clause.
	    */

	    if ( ljoin && !( qen_status->node_access & QEN_OUTER_HAS_JOINED ) )
	    {
		/*
		** Set the "outer has joined" flag so that if we emit
		** a left join, we won't come back into this conditional
		** the next time through this fmsjoin node.
		*/
		qen_status->node_access |= QEN_OUTER_HAS_JOINED;

		/* now execute oj_lnull */
		status = qen_execute_cx(dsh, node_xaddrs->qex_lnull);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */

		/* Execute jqual restriction, if any */
		if ( jqual_excb == NULL)
		    break;	/* emit a left join */
		else
		{
		    status = qen_execute_cx(dsh, jqual_excb);
		    if (status != E_DB_OK)
			goto errexit;   /* if ade error, return error */
		    if (jqual_excb->excb_value == ADE_TRUE)
			break;	/* emit a left join */
		}
	    }	/* endif previous outer did not join */

	    qen_status->node_access &= ~(	QEN_READ_NEXT_OUTER |
						QEN_OUTER_HAS_JOINED	);
	    /* get a new outer */

	newouter:
	    if ( qen_status->node_u.node_join.node_outer_status == QEN8_OUTER_EOF )
	    {
	        status = E_DB_WARN;
		dsh->dsh_error.err_code = E_QE0015_NO_MORE_ROWS;
	    }
	    else	/* this is where we actually read the outer stream! */
	    {
	        status = (*out_node->qen_func)(out_node, qef_rcb, dsh,
				(out_reset) ? FUNC_RESET : NO_FUNC);
		if (status == E_DB_OK)
		    qen_status->node_u.node_join.node_outer_count++;
	    }
	    out_reset = FALSE;

	    /* a little error handling.  check for end of outer stream */
	    if (status != E_DB_OK)
	    {
		if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS ||
		    dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION &&
		    node->qen_flags & QEN_PART_SEPARATE)
		{
		    /* If no outer rows were read and we're doing partition
		    ** grouping, skip the next inner partition to re-sync. */
		    if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION &&
			qen_status->node_u.node_join.node_outer_count <= 0)
		     if (!rjoin)
		     {
			if (node->qen_flags & QEN_PART_SEPARATE)
			{
			    status = (*in_node->qen_func)(in_node, qef_rcb, 
						dsh, FUNC_EOGROUP);
			    if (dsh->dsh_error.err_code == 
						E_QE00A5_END_OF_PARTITION)
				qen_status->node_status_flags |=
						QEN4_IPART_END;
					/* if just EOPartition, flag it */
			}
		     goto errexit;
		     }

		    qen_status->node_access |= QEN_OUTER_HAS_JOINED;
                    qen_status->node_u.node_join.node_outer_status = QEN8_OUTER_EOF;

		    if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION)
			qen_status->node_status_flags |= QEN2_OPART_END;
					/* if just EOPartition, flag it */

		    /*
		    ** If ( the inner stream is exhausted and there's nothing
		    ** to rescan ) or we're not right joining,
		    ** then there are no more tuples to return.  This should
		    ** be the only way to end this fsmjoin node.
		    */
		    if ( INNER_STREAM_EXHAUSTED || rjoin == FALSE  )
                    {
		       qen_status->node_status = QEN4_NO_MORE_ROWS;
                       break;    /* done */
                    }    
                    
                    else	/* we must check some more inner tuples */
		    {
                       dsh->dsh_error.err_code = 0;   /*reset*/

		       if(qen_status->node_status == QEN0_INITIAL)  /* empty */
		       {
			    qen_status->node_status = QEN1_EXECUTED;
			    qen_hold->hold_status = HFILE0_NOFILE;
			    qen_hold->hold_status2 = 0;
			    qen_hold->hold_medium = HMED_IN_MEMORY;
			    if(in_node->qen_type == QE_SORT)
			    {
				status = qen_u32_dosort(in_node, 
							qef_rcb, 
							dsh, 
							qen_status, 
							qen_hold,
							qen_shd, 
					(in_reset) ? FUNC_RESET : NO_FUNC);
				if(status) goto errexit;
			    }
			    in_reset = FALSE;
		       }	/* endif first time through */
		    }	/* endif no more inner tuples to check */
		}
		else if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION)
		{
		    /* No more rows in partitioning group - read from 
		    ** next group. */
		    out_reset = TRUE;
		    goto newouter;
		}
		else  /* return error from reading outer stream */
		{
		    break;
		}
	    }	/* end of error handling for outer stream EOF */


            if(qen_status->node_status == QEN0_INITIAL)
            {
		qen_status->node_status = QEN1_EXECUTED;  /* init done */
		qen_hold->hold_status = HFILE0_NOFILE;  /* default */
		qen_hold->hold_status2 = 0;		/* default */
		qen_hold->hold_medium = HMED_IN_MEMORY;  /* default */
		if(in_node->qen_type  == QE_SORT)
		{

		    status = qen_u32_dosort(in_node, 
					    qef_rcb, 
					    dsh, 
					    qen_status, 
					    qen_hold,
					    qen_shd, 
					(in_reset) ? FUNC_RESET : NO_FUNC);
		    if(status) goto errexit;
		    in_reset = FALSE;
		}

		/* now materialize the first join key */
		status = qen_execute_cx(dsh, node_xaddrs->qex_okmat);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */
	    }

	    /* If not the first time */
	    else
	    {
		if ( qen_status->node_u.node_join.node_outer_status == QEN8_OUTER_EOF )
		{
		    new_to_old = NEW_GT_OLD;
		}
		else	/* outer not at EOF */
		{
		    /* compare the old outer key to the new one. */
		    new_to_old = ADE_1EQ2;
		    if ((ade_excb = node_xaddrs->qex_kcompare) != NULL)
		    {
			status = qen_execute_cx(dsh, ade_excb);
			if (status != E_DB_OK)
			    goto errexit;   /* if ade error, return error */
			new_to_old = ade_excb->excb_cmp;
		    }

		    /* Materialize the new outer key if the old and the 
		    ** new outer keys are not equal */
		    if (new_to_old != ADE_1EQ2)
		    {
			status = qen_execute_cx(dsh, node_xaddrs->qex_okmat);
			if (status != E_DB_OK)
			    goto errexit;   /* if ade error, return error */
		    }
		    else if ((node->qen_flags & QEN_CARD_MASK) == QEN_CARD_01L)
		    {
			/* Right outer - note cardinality */
			potential_card_violation = (new_to_old == ADE_1EQ2);
		    }
		}	/* endif outer not at EOF */

		/*
		** If there are inner tuples to rescan, decide whether
		** to thumb through them again or dump them.
		*/


		if ( qen_status->node_access & QEN_RESCAN_MARKED )
		{
		    if ( new_to_old == ADE_1EQ2 )
		    {
		        status = repositionInnerStream( node, dsh );
		        if(status != E_DB_OK) break;   /* to error */
		        continue;
		    }

		    else	/* key has changed */
		    {
		        if ( rjoin )
		        {
		            status = repositionInnerStream( node, dsh );
		            if(status != E_DB_OK) break;   /* to error */
		            qen_status->node_access |= QEN_LOOKING_FOR_RIGHT_JOINS;
		            continue; /* to get a new inner */
		        }
		        else	/* don't have to return right joins */
		        {
		            status = clearHoldFiles( node, dsh );
		            if(status != E_DB_OK) break;   /* to error */
		        }
		    }	/* endif comparison of new and old keys */
		}	/* endif there are inner tuples to rescan */
	    }	/* end first or subsequent times */
	}  /* end if read_outer  */

	/*********************************************************
	**	
	**	LOGIC TO READ FROM THE INNER TUPLE STREAM
	**
	**
	*********************************************************/

	if( qen_status->node_access & QEN_READ_NEXT_INNER )
	{
            qen_status->node_access &= ~QEN_READ_NEXT_INNER;

	    if ( !INNER_STREAM_EXHAUSTED )
	    {
		/*
		** If we're rescanning the hold files and will eventually
		** have to look for right joins, read from the hold file
		** of inner join flags.
		*/
		if ( rjoin &&
		     ( ijFlagsHold->hold_status2 & HFILE_REPOSITIONED )  )
		{
		    if (qen_u40_readInnerJoinFlag( ijFlagsHold, dsh,
	    				ijFlagsShd,
					&innerTupleJoined ) != E_DB_OK)
		    {
			/*
			** If innerJoinFlags is exhausted and we were
			** looking for right joins, then we've found
			** all the right joins for this key.  Dump the
			** hold files.
			*/
		        if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS)
		        {
			    /* Hold file ends, mark this. Continue reading. */
			    ijFlagsHold->hold_buffer_status = HFILE6_BUF_EMPTY;
			    ijFlagsHold->hold_status = HFILE2_AT_EOF;
			    if ( qen_status->node_access &
				 QEN_LOOKING_FOR_RIGHT_JOINS )
			    {
				qen_status->node_access &= 
				    ~( QEN_LOOKING_FOR_RIGHT_JOINS |
				       QEN_READ_NEXT_OUTER );
				qen_status->node_access |= 
				    QEN_READ_NEXT_INNER;
				status = clearHoldFiles( node, dsh );
				if(status != E_DB_OK) break;   /* to error */
				continue;	/* get next inner */
			    }
			}
			else	/* other errors are fatal */
			{
			    break;
			}
		    }	/* endif innerJoinFlags read wasn't OK */
		}	/* endif rjoin and rescanning hold files */

	        /* Read from hold file if it is positioned */
	        if (qen_hold->hold_status == HFILE3_POSITIONED)
	        {
		    if (qen_u4_read_positioned(qen_hold, dsh,
	    			       qen_shd) != E_DB_OK)
		    {
		        if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS)
		        {
			    /* Hold file ends, must read from inner node */
			    qen_hold->hold_buffer_status = HFILE6_BUF_EMPTY;
			    qen_hold->hold_status = HFILE2_AT_EOF;
			    dsh->dsh_error.err_code = 0;
			    qen_status->node_access |= QEN_READ_NEXT_INNER;
			    if (node->qen_flags & QEN_PART_SEPARATE &&
				!(qen_hold->hold_status2 &
						HFILE_LAST_PARTITION))
			    {
				qen_status->node_status_flags |=
							QEN4_IPART_END;
				dsh->dsh_error.err_code = 
						E_QE00A5_END_OF_PARTITION;
			    }
			    continue;	/* to read a new inner */
		        }
			else	/* other, presumably fatal error */
			{
			    break;
			}
		    }   /* end if hold end */

		    if(in_node->qen_type == QE_SORT)  /* if hold from sort */
		    {
		        /* Materialize the inner tuple from sort's row buffer
		           into my row buffer. */
			status = qen_execute_cx(dsh, node_xaddrs->qex_itmat);
			if (status != E_DB_OK)
			    goto errexit;   /* if ade error, return error */
			rematerializeInnerTuple = FALSE;
		    } /* end if hold from sort */

		    qen_hold->hold_buffer_status = HFILE7_FROM_HOLD;
	        }	/* end if positioned */
	        /* if not EOF on stream */
	        else if (qen_status->node_u.node_join.node_inner_status != QEN11_INNER_ENDS)
	        {
		    if(qen_hold->unget_status)   /* if occupied */
                    {
                        /* put unget in row buffer */
                        MEcopy((PTR)qen_hold->unget_buffer, 
			   qen_shd->shd_width,
                           (PTR)qen_shd->shd_row);
                        qen_hold->unget_status = 0;   /* set no unget */
		        qen_hold->hold_buffer_status = HFILE8_FROM_INNER;
                    }
                    else   /* get new from stream */
                    {
		newinner:
		        status = (*in_node->qen_func)(in_node, qef_rcb, dsh,
					(in_reset) ? FUNC_RESET : NO_FUNC);
		        in_reset = FALSE;
		        if (status != E_DB_OK)
		        {
			    if (dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS ||
				dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION
				&& (node->qen_flags & QEN_PART_SEPARATE))
			    {
			        qen_hold->hold_buffer_status = HFILE6_BUF_EMPTY;

				if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION)
				    qen_status->node_status_flags |= QEN4_IPART_END;
					/* if just EOPartition, flag it */

			        /* mark EOF on stream */
			        qen_status->node_u.node_join.node_inner_status = QEN11_INNER_ENDS;
			        if(qen_hold->hold_status == HFILE2_AT_EOF ||
			           qen_hold->hold_status == HFILE0_NOFILE ||
			           qen_hold->hold_status == HFILE1_EMPTY )
			        {
			           qen_status->node_u.node_join.node_hold_stream = 
			        	    QEN5_HOLD_STREAM_EOF;
			        }
			    }
			    else if (dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION)
			    {
				/* No more rows in partitioning group - read 
				** from next group. */
				in_reset = TRUE;
				goto newinner;
			    }
			    else
			    {
			        break;	/* other, fatal error */
			    }
		        }
			else	/* inner tuple successfully read */
			{
		            /* Materialize the inner tuple into row buffer. */
			    status = qen_execute_cx(dsh, node_xaddrs->qex_itmat);
			    if (status != E_DB_OK)
				goto errexit;
		            qen_hold->hold_buffer_status = HFILE8_FROM_INNER;
			    rematerializeInnerTuple = FALSE;
			}
                    }  /* end if unget occupied */
	        }   /* end of read from hold/inner */
	    }	/* endif inner stream not exhausted */
	}    /* end if read_inner */

	/***************************************************************
	**
	**	LOOK FOR RIGHT JOINS
	**
	**
	***************************************************************/

	if ( qen_status->node_access & QEN_LOOKING_FOR_RIGHT_JOINS )
	{
	    qen_status->node_access &= ~QEN_READ_NEXT_OUTER;
	    qen_status->node_access |= QEN_READ_NEXT_INNER;

	    if ( innerTupleJoined == FALSE )
	    {
		status = qen_execute_cx(dsh, node_xaddrs->qex_rnull);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */
		if (jqual_excb == NULL)
		    break;	/* return right join */
		else
		{
		    status = qen_execute_cx(dsh, jqual_excb);
		    if (status != E_DB_OK)
			goto errexit;   /* if ade error, return error */
		    if (jqual_excb->excb_value == ADE_TRUE)
			break;  /* to return right join */
		}
	    }	/* endif inner tuple joined with some outer */

	    continue;	/* evaluate next inner tuple for right joins */
	}	/* endif looking for right joins */

	/***************************************************************
	**
	**	COMPARE THE INNER AND OUTER JOIN KEYS
	**
	**
	***************************************************************/


	if ( INNER_STREAM_EXHAUSTED ||
	     qen_hold->hold_buffer_status == HFILE6_BUF_EMPTY )
	{
	    join_result = OUTER_LT_INNER;
	}

	else if(qen_status->node_u.node_join.node_outer_status == QEN8_OUTER_EOF)
	{
	    join_result = OUTER_GT_INNER;
	}

	else	/* we have an inner and outer.  join them on the join key. */
	{
	    join_result = ADE_1EQ2;
	    if ((ade_excb = node_xaddrs->qex_joinkey) != NULL)
	    {
		status = qen_execute_cx(dsh, ade_excb);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */
		join_result = ade_excb->excb_cmp;
	    }

	    if (join_result == ADE_BOTHNULL)
	    {  
	        join_result = OUTER_GT_INNER;
	    }

	    else if (join_result == ADE_1ISNULL)
	    {
	        join_result = OUTER_GT_INNER;
	    }

	    else if (join_result == ADE_2ISNULL)
	    {
	        join_result = OUTER_LT_INNER;
	    }
	}	/* endif we have inner and outer */

	/***************************************************************
	**
	**	OUTER AND INNER KEYS NOW JOINED.  PERFORM OTHER
	**	QUALIFICATIONS NOW.  EMIT JOINS WHERE APPROPRIATE.
	**
	***************************************************************/


	if (join_result == OUTER_LT_INNER)
	{                           
	    qen_status->node_access |= QEN_READ_NEXT_OUTER;
	    qen_status->node_access &= ~QEN_READ_NEXT_INNER;
	    continue;	/* get next outer */
	}

	if ( join_result == OUTER_GT_INNER )
	{
	    qen_status->node_access &= ~QEN_READ_NEXT_OUTER;
	    qen_status->node_access |= QEN_READ_NEXT_INNER;

	    if ( rjoin )
	    {
		/* rematerialize inner tuple if the ultimate outer tuple
		** just left joined.  rematerialization will reset the
		** special equivalence classes from the inner stream.
		*/

		if ( rematerializeInnerTuple == TRUE )
		{
		    status = qen_execute_cx(dsh, node_xaddrs->qex_itmat);
		    if (status != E_DB_OK)
			goto errexit;   /* if ade error, return error */
		    rematerializeInnerTuple = FALSE;
		}

		/* execute oj_rnull */
		status = qen_execute_cx(dsh, node_xaddrs->qex_rnull);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */

		if (jqual_excb == NULL)
		    break;	/* return right join */
		else
		{
		    status = qen_execute_cx(dsh, jqual_excb);
		    if (status != E_DB_OK)
			goto errexit;   /* if ade error, return error */
		    if (jqual_excb->excb_value == ADE_TRUE)
			break;  /* to return right join */
		}
	    }
	    continue;	/* get next inner */
	}    /* endif outer greater than inner */
		
	/* We come to this point when joinkey returns OUTER_EQ_INNER */ 

	if ( join_result != OUTER_EQ_INNER )
	{
	    /* consistency check */
	    dsh->dsh_error.err_code = E_QE0002_INTERNAL_ERROR;
	    status = E_DB_ERROR;
	    goto errexit;           
	}	/* end consistency check */

	if (qen_hold->hold_buffer_status == HFILE8_FROM_INNER)
	{
	    /* append to hold */
            status = qen_u1_append(qen_hold, qen_shd, dsh); 
	    if(status) break;  /* to return error */
	}	

	/* If this is the first inner that joins with the current
	** outer, save the hold file TID so we can reposition it later.
	*/
	if ( !( qen_status->node_access & QEN_RESCAN_MARKED ) )
	{
	    if ( qen_u5_save_position(qen_hold, qen_shd) )
		goto errexit;
	    qen_status->node_access |= QEN_RESCAN_MARKED;
	}
	else if ((node->qen_flags & QEN_CARD_MASK) == QEN_CARD_01R &&
	    (qen_status->node_access & QEN_OUTER_HAS_JOINED) != 0)
	{
	    /* Left outer - note cardinality */
	    potential_card_violation = TRUE;
	}

	qen_status->node_access &= ~QEN_READ_NEXT_OUTER;
	qen_status->node_access |= QEN_READ_NEXT_INNER;

	/* execute OQUAL */
	ade_excb = node_xaddrs->qex_onqual;
	status = qen_execute_cx(dsh, ade_excb);
	if (status != E_DB_OK)
	    goto errexit;   /* if ade error, return error */
	if (ade_excb == NULL || ade_excb->excb_value == ADE_TRUE)
	{
	    /* not OJ, or OQUAL succeeds.  Remember that a join occurred. */
	    qen_status->node_access |= QEN_OUTER_HAS_JOINED;  

	    if ( rjoin )
	    {
		if ( status = qen_u41_storeInnerJoinFlag( ijFlagsHold,
		ijFlagsShd, dsh, innerTupleJoined,
		     ( i4 ) TRUE ) ) goto errexit;   /* error */
	    }

	    /* set the special eqcs to "inner join" state */
	    status = qen_execute_cx(dsh, node_xaddrs->qex_eqmat);
	    if (status != E_DB_OK)
		goto errexit;   /* if ade error, return error */

	    if (jqual_excb != NULL)
	    {
		status = qen_execute_cx(dsh, jqual_excb);
		if (status != E_DB_OK)
		    goto errexit;   /* if ade error, return error */
	    }
	    if( jqual_excb == NULL || jqual_excb->excb_value == ADE_TRUE)
	    {
		/* JQUAL succeeds */
		if(node->node_qen.qen_sjoin.sjn_kuniq)  /* if kuniq */
		{
		    /* make next entry read new outer bit not new inner */
		    qen_status->node_access |= QEN_READ_NEXT_OUTER;
		    qen_status->node_access &= ~QEN_READ_NEXT_INNER;
		}	/* endif key unique */
		if (potential_card_violation)
		{
		    /* We only want to act on seltype violation after
		    ** qualification and that is now. */
		    qen_status->node_status = QEN7_FAILED;
		    dsh->dsh_error.err_code = E_QE004C_NOT_ZEROONE_ROWS;
		    status = E_DB_ERROR;
		    goto errexit;
		}

		break;  /* emit inner join */
	    }
	}
	else	/* OQUAL failed */
	{
	    if ( rjoin )
	    {
	        if ( status = qen_u41_storeInnerJoinFlag( ijFlagsHold,
	            ijFlagsShd, dsh, innerTupleJoined,
		    ( i4 ) FALSE ) )
	        goto errexit;   /* error */
	    }

	}	/* end check of OQUAL status */

	/* OQUAL or JQUAL failed.  Get next inner. */

	continue;

    }	/* end of get loop */

    /********************************************************************
    **
    **	CLEANUP.  MATERIALIZE FUNCTION ATTRIBUTES.  ERROR EXIT WHEN
    **	APPROPRIATE.
    **
    ********************************************************************/

    if (status == E_DB_OK)
    {
	status = qen_execute_cx(dsh, node_xaddrs->qex_fatts);
	if (status != E_DB_OK)
	    goto errexit;   

	/* Increment the count of rows that this node has returned */
	qen_status->node_rcount++;

	/* print tracing information DO NOT xDEBUG THIS */
	if (node->qen_prow != NULL &&
		(ult_check_macro(&qef_cb->qef_trace, 100+node->qen_num,
				    &val1, &val2) ||
		    ult_check_macro(&qef_cb->qef_trace, 99,
				    &val1, &val2)
		)
	    )
	{
	    if (status == E_DB_OK)
	    {
		status = qen_print_row(node, qef_rcb, dsh);
		if (status != E_DB_OK)
		{
		    goto errexit;
		}
	    }
	}

#ifdef xDEBUG
	(VOID) qe2_chk_qp(dsh);
#endif 
    }
    else
    {
	if(in_node->qen_type == QE_SORT)  /* if sort child */
	/* release the memory now if in memory */
	{
	    qen_u31_release_mem(qen_hold, dsh,
	    	  dsh->dsh_shd[in_node->node_qen.qen_sort.sort_shd] );
	}
	else
	if(qen_hold)  /* if hold file */
	{
	    /* release our hold file, if in memory */
	    qen_u31_release_mem(qen_hold, dsh, qen_shd );
	}
    }

errexit:

    if ((dsh->dsh_error.err_code == E_QE0015_NO_MORE_ROWS ||
	dsh->dsh_error.err_code == E_QE00A5_END_OF_PARTITION) &&
	(qen_status->node_status_flags & 
			(QEN2_OPART_END | QEN4_IPART_END)))
    {
	/* Restart using next partitioning group. */
	out_reset = in_reset = reset = TRUE;
	qen_status->node_status_flags &=
			~(QEN2_OPART_END | QEN4_IPART_END);
	goto loop_reset;
    }

    if (dsh->dsh_qp_stats)
    {
	qen_ecost_end(dsh, &timerstat, qen_status);
    }

    return (status);	    

}