1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-04-18 21:44:02 +03:00
mariadb-columnstore-engine/dbcon/joblist/tupleconstantstep.cpp
Sergey Zefirov e99db9c212
fix(plugin): MCOL-4942 No-table-SELECT now can return empty set (#3415)
The query like "SELECT 1 WHERE 1=0" was returning a row despite
unsatisfiable condition in WHERE. Now it returns an empty set.
2025-03-05 07:36:05 +00:00

852 lines
21 KiB
C++

/* Copyright (C) 2014 InfiniDB, Inc.
Copyright (C) 2019 MariaDB Corporation
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 of
the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA. */
// $Id: tupleconstantstep.cpp 9649 2013-06-25 16:08:05Z xlou $
//#define NDEBUG
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <boost/uuid/uuid_io.hpp>
using namespace boost;
#include "messagequeue.h"
using namespace messageqcpp;
#include "loggingid.h"
#include "errorcodes.h"
#include "idberrorinfo.h"
#include "errorids.h"
using namespace logging;
#include "calpontsystemcatalog.h"
#include "constantcolumn.h"
using namespace execplan;
#include "jobstep.h"
#include "rowgroup.h"
using namespace rowgroup;
#include "querytele.h"
using namespace querytele;
#include "jlf_common.h"
#include "tupleconstantstep.h"
namespace joblist
{
// static utility method
SJSTEP TupleConstantStep::addConstantStep(const JobInfo& jobInfo, const rowgroup::RowGroup* rg)
{
TupleConstantStep* tcs = NULL;
if (jobInfo.constantCol != CONST_COL_ONLY)
{
tcs = new TupleConstantStep(jobInfo);
}
else
{
tcs = new TupleConstantOnlyStep(jobInfo);
}
tcs->initialize(jobInfo, rg);
SJSTEP spcs(tcs);
return spcs;
}
TupleConstantStep::TupleConstantStep(const JobInfo& jobInfo)
: JobStep(jobInfo)
, fRowsReturned(0)
, fInputDL(NULL)
, fOutputDL(NULL)
, fInputIterator(0)
, fRunner(0)
, fEndOfResult(false)
{
fExtendedInfo = "TCS: ";
fQtc.stepParms().stepType = StepTeleStats::T_TCS;
}
TupleConstantStep::~TupleConstantStep()
{
}
void TupleConstantStep::setOutputRowGroup(const rowgroup::RowGroup& rg)
{
throw runtime_error("Disabled, use initialize() to set output RowGroup.");
}
void TupleConstantStep::initialize(const JobInfo& jobInfo, const RowGroup* rgIn)
{
vector<uint32_t> oids, oidsIn = fRowGroupIn.getOIDs();
vector<uint32_t> keys, keysIn = fRowGroupIn.getKeys();
vector<uint32_t> scale, scaleIn = fRowGroupIn.getScale();
vector<uint32_t> precision, precisionIn = fRowGroupIn.getPrecision();
vector<CalpontSystemCatalog::ColDataType> types, typesIn = fRowGroupIn.getColTypes();
vector<uint32_t> csNums, csNumsIn = fRowGroupIn.getCharsetNumbers();
vector<uint32_t> pos;
pos.push_back(2);
if (rgIn)
{
fRowGroupIn = *rgIn;
fRowGroupIn.initRow(&fRowIn);
oidsIn = fRowGroupIn.getOIDs();
keysIn = fRowGroupIn.getKeys();
scaleIn = fRowGroupIn.getScale();
precisionIn = fRowGroupIn.getPrecision();
typesIn = fRowGroupIn.getColTypes();
csNumsIn = fRowGroupIn.getCharsetNumbers();
}
for (uint64_t i = 0, j = 0; i < jobInfo.deliveredCols.size(); i++)
{
const ConstantColumn* cc = dynamic_cast<const ConstantColumn*>(jobInfo.deliveredCols[i].get());
if (cc != NULL)
{
CalpontSystemCatalog::ColType ct = cc->resultType();
if (ct.colDataType == CalpontSystemCatalog::VARCHAR)
ct.colWidth++;
// Round colWidth up
if (ct.colWidth == 3)
ct.colWidth = 4;
else if (ct.colWidth == 5 || ct.colWidth == 6 || ct.colWidth == 7)
ct.colWidth = 8;
oids.push_back(-1);
keys.push_back(-1);
scale.push_back(ct.scale);
precision.push_back(ct.precision);
types.push_back(ct.colDataType);
csNums.push_back(ct.charsetNumber);
pos.push_back(pos.back() + ct.colWidth);
fIndexConst.push_back(i);
}
else
{
// select (select a) from region;
if (j >= oidsIn.size() && jobInfo.tableList.empty())
{
throw IDBExcept(ERR_NO_FROM);
}
idbassert(j < oidsIn.size());
oids.push_back(oidsIn[j]);
keys.push_back(keysIn[j]);
scale.push_back(scaleIn[j]);
precision.push_back(precisionIn[j]);
types.push_back(typesIn[j]);
csNums.push_back(csNumsIn[j]);
pos.push_back(pos.back() + fRowGroupIn.getColumnWidth(j));
j++;
fIndexMapping.push_back(i);
}
}
fRowGroupOut =
RowGroup(oids.size(), pos, oids, keys, types, csNums, scale, precision, jobInfo.stringTableThreshold);
fRowGroupOut.initRow(&fRowOut);
fRowGroupOut.initRow(&fRowConst, true);
constructContanstRow(jobInfo);
}
void TupleConstantStep::constructContanstRow(const JobInfo& jobInfo)
{
// construct a row with only the constant values
fConstRowData.reset(new uint8_t[fRowConst.getSize()]);
fRowConst.setData(rowgroup::Row::Pointer(fConstRowData.get()));
fRowConst.initToNull(); // make sure every col is init'd to something, because later we copy the whole row
const vector<CalpontSystemCatalog::ColDataType>& types = fRowGroupOut.getColTypes();
for (vector<uint64_t>::iterator i = fIndexConst.begin(); i != fIndexConst.end(); i++)
{
const ConstantColumn* cc = dynamic_cast<const ConstantColumn*>(jobInfo.deliveredCols[*i].get());
const execplan::Result c = cc->result();
if (cc->isNull())
{
if (types[*i] == CalpontSystemCatalog::CHAR || types[*i] == CalpontSystemCatalog::VARCHAR ||
types[*i] == CalpontSystemCatalog::TEXT)
{
fRowConst.setStringField(nullptr, 0, *i);
}
else if (isUnsigned(types[*i]))
{
fRowConst.setUintField(fRowConst.getNullValue(*i), *i);
}
else
{
fRowConst.setIntField(fRowConst.getSignedNullValue(*i), *i);
}
continue;
}
switch (types[*i])
{
case CalpontSystemCatalog::TINYINT:
case CalpontSystemCatalog::SMALLINT:
case CalpontSystemCatalog::MEDINT:
case CalpontSystemCatalog::INT:
case CalpontSystemCatalog::BIGINT:
case CalpontSystemCatalog::DATE:
case CalpontSystemCatalog::DATETIME:
case CalpontSystemCatalog::TIME:
case CalpontSystemCatalog::TIMESTAMP:
{
fRowConst.setIntField(c.intVal, *i);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
if (fRowGroupOut.getColWidths()[*i] > datatypes::MAXLEGACYWIDTH)
fRowConst.setInt128Field(c.decimalVal.TSInt128::getValue(), *i);
else
fRowConst.setIntField(c.decimalVal.value, *i);
break;
}
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
{
fRowConst.setFloatField(c.floatVal, *i);
break;
}
case CalpontSystemCatalog::DOUBLE:
case CalpontSystemCatalog::UDOUBLE:
{
fRowConst.setDoubleField(c.doubleVal, *i);
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
fRowConst.setLongDoubleField(c.longDoubleVal, *i);
break;
}
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::VARCHAR:
case CalpontSystemCatalog::TEXT:
{
fRowConst.setStringField(c.strVal, *i);
break;
}
case CalpontSystemCatalog::UTINYINT:
case CalpontSystemCatalog::USMALLINT:
case CalpontSystemCatalog::UMEDINT:
case CalpontSystemCatalog::UINT:
case CalpontSystemCatalog::UBIGINT:
{
fRowConst.setUintField(c.uintVal, *i);
break;
}
default:
{
throw runtime_error("un-supported constant column type.");
break;
}
} // switch
} // for constant columns
}
void TupleConstantStep::run()
{
if (fInputJobStepAssociation.outSize() == 0)
{
throw logic_error("No input data list for constant step.");
}
fInputDL = fInputJobStepAssociation.outAt(0)->rowGroupDL();
if (fInputDL == NULL)
throw logic_error("Input is not a RowGroup data list.");
fInputIterator = fInputDL->getIterator();
if (fDelivery == false)
{
if (fOutputJobStepAssociation.outSize() == 0)
throw logic_error("No output data list for non-delivery constant step.");
fOutputDL = fOutputJobStepAssociation.outAt(0)->rowGroupDL();
if (fOutputDL == NULL)
throw logic_error("Output is not a RowGroup data list.");
fRunner = jobstepThreadPool.invoke(Runner(this));
}
}
void TupleConstantStep::join()
{
if (fRunner)
jobstepThreadPool.join(fRunner);
}
uint32_t TupleConstantStep::nextBand(messageqcpp::ByteStream& bs)
{
RGData rgDataIn;
RGData rgDataOut;
bool more = false;
uint32_t rowCount = 0;
try
{
bs.restart();
more = fInputDL->next(fInputIterator, &rgDataIn);
if (traceOn() && dlTimes.FirstReadTime().tv_sec == 0)
dlTimes.setFirstReadTime();
if (!more && cancelled())
{
fEndOfResult = true;
}
if (more && !fEndOfResult)
{
fRowGroupIn.setData(&rgDataIn);
rgDataOut.reinit(fRowGroupOut, fRowGroupIn.getRowCount());
fRowGroupOut.setData(&rgDataOut);
fillInConstants();
fRowGroupOut.serializeRGData(bs);
rowCount = fRowGroupOut.getRowCount();
}
else
{
fEndOfResult = true;
}
}
catch (...)
{
handleException(std::current_exception(), logging::tupleConstantStepErr, logging::ERR_ALWAYS_CRITICAL,
"TupleConstantStep::nextBand()");
while (more)
more = fInputDL->next(fInputIterator, &rgDataIn);
fEndOfResult = true;
}
if (fEndOfResult)
{
// send an empty / error band
RGData rgData(fRowGroupOut, 0);
fRowGroupOut.setData(&rgData);
fRowGroupOut.resetRowGroup(0);
fRowGroupOut.setStatus(status());
fRowGroupOut.serializeRGData(bs);
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
}
return rowCount;
}
void TupleConstantStep::execute()
{
RGData rgDataIn;
RGData rgDataOut;
bool more = false;
StepTeleStats sts;
sts.query_uuid = fQueryUuid;
sts.step_uuid = fStepUuid;
try
{
more = fInputDL->next(fInputIterator, &rgDataIn);
if (traceOn())
dlTimes.setFirstReadTime();
sts.msg_type = StepTeleStats::ST_START;
sts.total_units_of_work = 1;
postStepStartTele(sts);
if (!more && cancelled())
{
fEndOfResult = true;
}
while (more && !fEndOfResult)
{
fRowGroupIn.setData(&rgDataIn);
rgDataOut.reinit(fRowGroupOut, fRowGroupIn.getRowCount());
fRowGroupOut.setData(&rgDataOut);
fillInConstants();
more = fInputDL->next(fInputIterator, &rgDataIn);
if (cancelled())
{
fEndOfResult = true;
}
else
{
fOutputDL->insert(rgDataOut);
}
}
}
catch (...)
{
handleException(std::current_exception(), logging::tupleConstantStepErr, logging::ERR_ALWAYS_CRITICAL,
"TupleConstantStep::execute()");
}
while (more)
more = fInputDL->next(fInputIterator, &rgDataIn);
sts.msg_type = StepTeleStats::ST_SUMMARY;
sts.total_units_of_work = sts.units_of_work_completed = 1;
sts.rows = fRowsReturned;
postStepSummaryTele(sts);
// Bug 3136, let mini stats to be formatted if traceOn.
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
fEndOfResult = true;
fOutputDL->endOfInput();
}
// *DRRTUY Copy row at once not one field at a time
void TupleConstantStep::fillInConstants()
{
fRowGroupIn.getRow(0, &fRowIn);
fRowGroupOut.getRow(0, &fRowOut);
if (fIndexConst.size() > 1 || fIndexConst[0] != 0)
{
for (uint64_t i = 0; i < fRowGroupIn.getRowCount(); ++i)
{
copyRow(fRowConst, &fRowOut);
fRowOut.setRid(fRowIn.getRelRid());
for (uint64_t j = 0; j < fIndexMapping.size(); ++j)
fRowIn.copyField(fRowOut, fIndexMapping[j], j);
fRowIn.nextRow();
fRowOut.nextRow();
}
}
else // only first column is constant
{
for (uint64_t i = 0; i < fRowGroupIn.getRowCount(); ++i)
{
fRowOut.setRid(fRowIn.getRelRid());
fRowConst.copyField(fRowOut, 0, 0);
for (uint32_t i = 1; i < fRowOut.getColumnCount(); i++)
fRowIn.copyField(fRowOut, i, i - 1);
fRowIn.nextRow();
fRowOut.nextRow();
}
}
fRowGroupOut.resetRowGroup(fRowGroupIn.getBaseRid());
fRowGroupOut.setRowCount(fRowGroupIn.getRowCount());
fRowsReturned += fRowGroupOut.getRowCount();
}
void TupleConstantStep::fillInConstants(const rowgroup::Row& rowIn, rowgroup::Row& rowOut)
{
if (fIndexConst.size() > 1 || fIndexConst[0] != 0)
{
copyRow(fRowConst, &rowOut);
rowOut.setRid(rowIn.getRelRid());
for (uint64_t j = 0; j < fIndexMapping.size(); ++j)
rowIn.copyField(rowOut, fIndexMapping[j], j);
}
else // only first column is constant
{
rowOut.setRid(rowIn.getRelRid());
fRowConst.copyField(rowOut, 0, 0);
for (uint32_t i = 1; i < rowOut.getColumnCount(); i++)
rowIn.copyField(rowOut, i, i - 1);
}
}
const RowGroup& TupleConstantStep::getOutputRowGroup() const
{
return fRowGroupOut;
}
const RowGroup& TupleConstantStep::getDeliveredRowGroup() const
{
return fRowGroupOut;
}
void TupleConstantStep::deliverStringTableRowGroup(bool b)
{
fRowGroupOut.setUseStringTable(b);
}
bool TupleConstantStep::deliverStringTableRowGroup() const
{
return fRowGroupOut.usesStringTable();
}
const string TupleConstantStep::toString() const
{
ostringstream oss;
oss << "ConstantStep ses:" << fSessionId << " txn:" << fTxnId << " st:" << fStepId;
oss << " in:";
for (unsigned i = 0; i < fInputJobStepAssociation.outSize(); i++)
oss << fInputJobStepAssociation.outAt(i);
oss << " out:";
for (unsigned i = 0; i < fOutputJobStepAssociation.outSize(); i++)
oss << fOutputJobStepAssociation.outAt(i);
oss << endl;
return oss.str();
}
void TupleConstantStep::printCalTrace()
{
time_t t = time(0);
char timeString[50];
ctime_r(&t, timeString);
timeString[strlen(timeString) - 1] = '\0';
ostringstream logStr;
logStr << "ses:" << fSessionId << " st: " << fStepId << " finished at " << timeString
<< "; total rows returned-" << fRowsReturned << endl
<< "\t1st read " << dlTimes.FirstReadTimeString() << "; EOI " << dlTimes.EndOfInputTimeString()
<< "; runtime-" << JSTimeStamp::tsdiffstr(dlTimes.EndOfInputTime(), dlTimes.FirstReadTime())
<< "s;\n\tUUID " << uuids::to_string(fStepUuid) << endl
<< "\tJob completion status " << status() << endl;
logEnd(logStr.str().c_str());
fExtendedInfo += logStr.str();
formatMiniStats();
}
void TupleConstantStep::formatMiniStats()
{
ostringstream oss;
oss << "TCS "
<< "UM "
<< "- "
<< "- "
<< "- "
<< "- "
<< "- "
<< "- " << JSTimeStamp::tsdiffstr(dlTimes.EndOfInputTime(), dlTimes.FirstReadTime()) << " "
<< fRowsReturned << " ";
fMiniInfo += oss.str();
}
// class TupleConstantOnlyStep
TupleConstantOnlyStep::TupleConstantOnlyStep(const JobInfo& jobInfo)
: TupleConstantStep(jobInfo)
, fEmptySet(jobInfo.constantFalse)
{
// fExtendedInfo = "TCOS: ";
}
TupleConstantOnlyStep::~TupleConstantOnlyStep()
{
}
// void TupleConstantOnlyStep::initialize(const RowGroup& rgIn, const JobInfo& jobInfo)
void TupleConstantOnlyStep::initialize(const JobInfo& jobInfo, const rowgroup::RowGroup* rgIn)
{
vector<uint32_t> oids;
vector<uint32_t> keys;
vector<uint32_t> scale;
vector<uint32_t> precision;
vector<CalpontSystemCatalog::ColDataType> types;
vector<uint32_t> csNums;
vector<uint32_t> pos;
pos.push_back(2);
deliverStringTableRowGroup(false);
for (uint64_t i = 0; i < jobInfo.deliveredCols.size(); i++)
{
const ConstantColumn* cc = dynamic_cast<const ConstantColumn*>(jobInfo.deliveredCols[i].get());
if (cc == NULL)
throw runtime_error("none constant column found.");
CalpontSystemCatalog::ColType ct = cc->resultType();
if (ct.colDataType == CalpontSystemCatalog::VARCHAR)
ct.colWidth++;
// Round colWidth up
if (ct.colWidth == 3)
ct.colWidth = 4;
else if (ct.colWidth == 5 || ct.colWidth == 6 || ct.colWidth == 7)
ct.colWidth = 8;
oids.push_back(-1);
keys.push_back(-1);
scale.push_back(ct.scale);
precision.push_back(ct.precision);
types.push_back(ct.colDataType);
csNums.push_back(ct.charsetNumber);
pos.push_back(pos.back() + ct.colWidth);
fIndexConst.push_back(i);
}
fRowGroupOut = RowGroup(oids.size(), pos, oids, keys, types, csNums, scale, precision,
jobInfo.stringTableThreshold, false);
fRowGroupOut.initRow(&fRowOut);
fRowGroupOut.initRow(&fRowConst, true);
constructContanstRow(jobInfo);
}
void TupleConstantOnlyStep::run()
{
if (fDelivery == false)
{
if (fOutputJobStepAssociation.outSize() == 0)
throw logic_error("No output data list for non-delivery constant step.");
fOutputDL = fOutputJobStepAssociation.outAt(0)->rowGroupDL();
if (fOutputDL == NULL)
throw logic_error("Output is not a RowGroup data list.");
try
{
RGData rgDataOut(fRowGroupOut, 1);
fRowGroupOut.setData(&rgDataOut);
if (traceOn())
dlTimes.setFirstReadTime();
fillInConstants();
if (!fEmptySet)
{
fOutputDL->insert(rgDataOut);
}
}
catch (...)
{
handleException(std::current_exception(), logging::tupleConstantStepErr, logging::ERR_ALWAYS_CRITICAL,
"TupleConstantOnlyStep::run()");
}
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
// Bug 3136, let mini stats to be formatted if traceOn.
fEndOfResult = true;
fOutputDL->endOfInput();
}
}
uint32_t TupleConstantOnlyStep::nextBand(messageqcpp::ByteStream& bs)
{
RGData rgDataOut;
uint32_t rowCount = 0;
if (!fEndOfResult)
{
try
{
bs.restart();
if (traceOn() && dlTimes.FirstReadTime().tv_sec == 0)
dlTimes.setFirstReadTime();
rgDataOut.reinit(fRowGroupOut, 1);
fRowGroupOut.setData(&rgDataOut);
fillInConstants();
fRowGroupOut.serializeRGData(bs);
rowCount = fRowGroupOut.getRowCount();
}
catch (...)
{
handleException(std::current_exception(), logging::tupleConstantStepErr, logging::ERR_ALWAYS_CRITICAL,
"TupleConstantOnlyStep::nextBand()");
}
fEndOfResult = true;
}
else
{
// send an empty / error band
RGData rgData(fRowGroupOut, 0);
fRowGroupOut.setData(&rgData);
fRowGroupOut.resetRowGroup(0);
fRowGroupOut.setStatus(status());
fRowGroupOut.serializeRGData(bs);
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
}
return rowCount;
}
void TupleConstantOnlyStep::fillInConstants()
{
fRowGroupOut.getRow(0, &fRowOut);
idbassert(fRowConst.getColumnCount() == fRowOut.getColumnCount());
fRowOut.usesStringTable(fRowConst.usesStringTable());
copyRow(fRowConst, &fRowOut);
fRowGroupOut.resetRowGroup(0);
fRowGroupOut.setRowCount(1);
fRowsReturned = 1;
}
const string TupleConstantOnlyStep::toString() const
{
ostringstream oss;
oss << "ConstantOnlyStep ses:" << fSessionId << " txn:" << fTxnId << " st:" << fStepId;
oss << " out:";
for (unsigned i = 0; i < fOutputJobStepAssociation.outSize(); i++)
oss << fOutputJobStepAssociation.outAt(i);
oss << endl;
return oss.str();
}
// class TupleConstantBooleanStep
TupleConstantBooleanStep::TupleConstantBooleanStep(const JobInfo& jobInfo, bool value)
: TupleConstantStep(jobInfo), fValue(value)
{
// fExtendedInfo = "TCBS: ";
}
TupleConstantBooleanStep::~TupleConstantBooleanStep()
{
}
void TupleConstantBooleanStep::initialize(const RowGroup& rgIn, const JobInfo&)
{
fRowGroupOut = rgIn;
fRowGroupOut.initRow(&fRowOut);
fRowGroupOut.initRow(&fRowConst, true);
}
void TupleConstantBooleanStep::run()
{
if (fDelivery == false)
{
if (fOutputJobStepAssociation.outSize() == 0)
throw logic_error("No output data list for non-delivery constant step.");
fOutputDL = fOutputJobStepAssociation.outAt(0)->rowGroupDL();
if (fOutputDL == NULL)
throw logic_error("Output is not a RowGroup data list.");
if (traceOn())
{
dlTimes.setFirstReadTime();
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
// Bug 3136, let mini stats to be formatted if traceOn.
fOutputDL->endOfInput();
}
}
uint32_t TupleConstantBooleanStep::nextBand(messageqcpp::ByteStream& bs)
{
// send an empty band
RGData rgData(fRowGroupOut, 0);
fRowGroupOut.setData(&rgData);
fRowGroupOut.resetRowGroup(0);
fRowGroupOut.setStatus(status());
fRowGroupOut.serializeRGData(bs);
if (traceOn())
{
dlTimes.setFirstReadTime();
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
return 0;
}
const string TupleConstantBooleanStep::toString() const
{
ostringstream oss;
oss << "ConstantBooleanStep ses:" << fSessionId << " txn:" << fTxnId << " st:" << fStepId;
oss << " out:";
for (unsigned i = 0; i < fOutputJobStepAssociation.outSize(); i++)
oss << fOutputJobStepAssociation.outAt(i);
oss << endl;
return oss.str();
}
} // namespace joblist