1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-04-18 21:44:02 +03:00
2023-03-02 15:59:42 +00:00

818 lines
22 KiB
C++

/* Copyright (C) 2014 InfiniDB, Inc.
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: crossenginestep.cpp 9709 2013-07-20 06:08:46Z xlou $
#define PREFER_MY_CONFIG_H
#include "crossenginestep.h"
#include <unistd.h>
//#define NDEBUG
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.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 "rowgroup.h"
using namespace rowgroup;
#include "querytele.h"
using namespace querytele;
#include "funcexp.h"
#include "jobstep.h"
#include "jlf_common.h"
#include "libmysql_client.h"
namespace joblist
{
CrossEngineStep::CrossEngineStep(const std::string& schema, const std::string& table,
const std::string& alias, const JobInfo& jobInfo)
: BatchPrimitive(jobInfo)
, fRowsRetrieved(0)
, fRowsReturned(0)
, fRowsPerGroup(256)
, fOutputDL(NULL)
, fOutputIterator(0)
, fRunner(0)
, fEndOfResult(false)
, fSchema(schema)
, fTable(table)
, fAlias(alias)
, fColumnCount(0)
, fFeInstance(funcexp::FuncExp::instance())
{
fExtendedInfo = "CES: ";
getMysqldInfo(jobInfo);
fQtc.stepParms().stepType = StepTeleStats::T_CES;
mysql = new utils::LibMySQL();
}
CrossEngineStep::~CrossEngineStep()
{
delete mysql;
}
void CrossEngineStep::setOutputRowGroup(const rowgroup::RowGroup& rg)
{
fRowGroupOut = fRowGroupDelivered = fRowGroupAdded = rg;
}
void CrossEngineStep::deliverStringTableRowGroup(bool b)
{
// results are either using setField, or mapping to delivered row group
fRowGroupDelivered.setUseStringTable(b);
}
bool CrossEngineStep::deliverStringTableRowGroup() const
{
return fRowGroupDelivered.usesStringTable();
}
void CrossEngineStep::addFcnJoinExp(const std::vector<execplan::SRCP>& fe)
{
fFeFcnJoin = fe;
}
void CrossEngineStep::addFcnExpGroup1(const boost::shared_ptr<ParseTree>& fe)
{
fFeFilters.push_back(fe);
}
void CrossEngineStep::setFE1Input(const rowgroup::RowGroup& rg)
{
fRowGroupFe1 = rg;
}
void CrossEngineStep::setFcnExpGroup3(const std::vector<execplan::SRCP>& fe)
{
fFeSelects = fe;
}
void CrossEngineStep::setFE23Output(const rowgroup::RowGroup& rg)
{
fRowGroupFe3 = fRowGroupDelivered = fRowGroupAdded = rg;
}
void CrossEngineStep::makeMappings()
{
fFe1Column.reset(new int[fColumnCount]);
for (uint64_t i = 0; i < fColumnCount; ++i)
fFe1Column[i] = -1;
if (fFeFilters.size() > 0 || fFeFcnJoin.size() > 0)
{
const std::vector<uint32_t>& colInFe1 = fRowGroupFe1.getKeys();
for (uint64_t i = 0; i < colInFe1.size(); i++)
{
map<uint32_t, uint32_t>::iterator it = fColumnMap.find(colInFe1[i]);
if (it != fColumnMap.end())
fFe1Column[it->second] = i;
}
fFeMapping1 = makeMapping(fRowGroupFe1, fRowGroupOut);
}
if (!fFeSelects.empty())
fFeMapping3 = makeMapping(fRowGroupOut, fRowGroupFe3);
}
void CrossEngineStep::setField(int i, const char* value, unsigned long length, MYSQL_FIELD* field, Row& row)
{
CalpontSystemCatalog::ColDataType colType = row.getColType(i);
if ((colType == CalpontSystemCatalog::CHAR || colType == CalpontSystemCatalog::VARCHAR) &&
row.getColumnWidth(i) > 8)
{
if (value != NULL)
row.setStringField(value, i);
else
row.setStringField("", i);
}
else if ((colType == CalpontSystemCatalog::BLOB) || (colType == CalpontSystemCatalog::TEXT) ||
(colType == CalpontSystemCatalog::VARBINARY))
{
if (value != NULL)
row.setVarBinaryField((const uint8_t*)value, length, i);
else
row.setVarBinaryField(NULL, 0, i);
}
else
{
CalpontSystemCatalog::ColType ct;
ct.colDataType = colType;
ct.colWidth = row.getColumnWidth(i);
if (colType == CalpontSystemCatalog::DECIMAL || colType == CalpontSystemCatalog::UDECIMAL)
{
ct.scale = field->decimals;
ct.precision = field->length;
if (ct.colWidth == datatypes::MAXDECIMALWIDTH)
{
row.setInt128Field(convertValueNum<int128_t>(value, ct), i);
return;
}
}
else
{
ct.scale = row.getScale(i);
ct.precision = row.getPrecision(i);
}
row.setIntField(convertValueNum<int64_t>(value, ct), i);
}
}
inline void CrossEngineStep::addRow(RGData& data)
{
fRowDelivered.setRid(fRowsReturned % fRowsPerGroup);
fRowDelivered.nextRow();
fRowGroupAdded.incRowCount();
if (++fRowsReturned % fRowsPerGroup == 0)
{
fOutputDL->insert(data);
data.reinit(fRowGroupAdded, fRowsPerGroup);
fRowGroupAdded.setData(&data);
fRowGroupAdded.resetRowGroup(fRowsReturned);
fRowGroupAdded.getRow(0, &fRowDelivered);
}
}
// simplified version of convertValueNum() in jlf_execplantojoblist.cpp.
template <typename T>
T CrossEngineStep::convertValueNum(const char* str, const CalpontSystemCatalog::ColType& ct)
{
T rv = 0;
bool pushWarning = false;
bool nullFlag = (str == NULL);
boost::any anyVal =
ct.convertColumnData((nullFlag ? "" : str), pushWarning, fTimeZone, nullFlag, true, false);
// Out of range values are treated as NULL as discussed during design review.
if (pushWarning)
{
anyVal = ct.getNullValueForType();
}
switch (ct.colDataType)
{
case CalpontSystemCatalog::BIT: rv = boost::any_cast<bool>(anyVal); break;
case CalpontSystemCatalog::TINYINT:
#if BOOST_VERSION >= 105200
rv = boost::any_cast<char>(anyVal);
#else
rv = boost::any_cast<int8_t>(anyVal);
#endif
break;
case CalpontSystemCatalog::UTINYINT: rv = boost::any_cast<uint8_t>(anyVal); break;
case CalpontSystemCatalog::SMALLINT: rv = boost::any_cast<int16_t>(anyVal); break;
case CalpontSystemCatalog::USMALLINT: rv = boost::any_cast<uint16_t>(anyVal); break;
case CalpontSystemCatalog::MEDINT:
case CalpontSystemCatalog::INT:
rv = boost::any_cast<int32_t>(anyVal);
break;
case CalpontSystemCatalog::UMEDINT:
case CalpontSystemCatalog::UINT:
rv = boost::any_cast<uint32_t>(anyVal);
break;
case CalpontSystemCatalog::BIGINT: rv = boost::any_cast<long long>(anyVal); break;
case CalpontSystemCatalog::UBIGINT: rv = boost::any_cast<uint64_t>(anyVal); break;
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
{
float f = boost::any_cast<float>(anyVal);
// N.B. There is a bug in boost::any or in gcc where, if you store a nan,
// you will get back a nan, but not necessarily the same bits that you put in.
// This only seems to be for float (double seems to work).
if (isnan(f))
{
uint32_t ti = joblist::FLOATNULL;
float* tfp = (float*)&ti;
f = *tfp;
}
float* fp = &f;
int32_t* ip = reinterpret_cast<int32_t*>(fp);
rv = *ip;
}
break;
case CalpontSystemCatalog::DOUBLE:
case CalpontSystemCatalog::UDOUBLE:
{
double d = boost::any_cast<double>(anyVal);
double* dp = &d;
int64_t* ip = reinterpret_cast<int64_t*>(dp);
rv = *ip;
}
break;
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::VARCHAR:
case CalpontSystemCatalog::VARBINARY:
case CalpontSystemCatalog::BLOB:
case CalpontSystemCatalog::TEXT:
case CalpontSystemCatalog::CLOB:
{
std::string i = boost::any_cast<std::string>(anyVal);
// bug 1932, pad nulls up to the size of v
i.resize(sizeof(rv), 0);
rv = *((uint64_t*)i.data());
}
break;
case CalpontSystemCatalog::DATE: rv = boost::any_cast<uint32_t>(anyVal); break;
case CalpontSystemCatalog::DATETIME: rv = boost::any_cast<uint64_t>(anyVal); break;
case CalpontSystemCatalog::TIMESTAMP: rv = boost::any_cast<uint64_t>(anyVal); break;
case CalpontSystemCatalog::TIME: rv = boost::any_cast<int64_t>(anyVal); break;
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
if (LIKELY(ct.colWidth == datatypes::MAXDECIMALWIDTH))
rv = boost::any_cast<int128_t>(anyVal);
else if (ct.colWidth == execplan::CalpontSystemCatalog::EIGHT_BYTE)
rv = boost::any_cast<long long>(anyVal);
else if (ct.colWidth == execplan::CalpontSystemCatalog::FOUR_BYTE)
rv = boost::any_cast<int32_t>(anyVal);
else if (ct.colWidth == execplan::CalpontSystemCatalog::TWO_BYTE)
rv = boost::any_cast<int16_t>(anyVal);
else if (ct.colWidth == execplan::CalpontSystemCatalog::ONE_BYTE)
rv = boost::any_cast<char>(anyVal);
break;
default: break;
}
return rv;
}
void CrossEngineStep::getMysqldInfo(const JobInfo& jobInfo)
{
if (jobInfo.rm->getMysqldInfo(fHost, fUser, fPasswd, fPort) == false)
throw IDBExcept(IDBErrorInfo::instance()->errorMsg(ERR_CROSS_ENGINE_CONFIG), ERR_CROSS_ENGINE_CONFIG);
}
void CrossEngineStep::run()
{
// idbassert(!fDelivery);
if (fOutputJobStepAssociation.outSize() == 0)
throw logic_error("No output data list for cross engine step.");
fOutputDL = fOutputJobStepAssociation.outAt(0)->rowGroupDL();
if (fOutputDL == NULL)
throw logic_error("Output is not a RowGroup data list.");
if (fDelivery == true)
{
fOutputIterator = fOutputDL->getIterator();
}
fRunner = jobstepThreadPool.invoke(Runner(this));
}
void CrossEngineStep::join()
{
if (fRunner)
jobstepThreadPool.join(fRunner);
}
void CrossEngineStep::execute()
{
int ret = 0;
StepTeleStats sts;
sts.query_uuid = fQueryUuid;
sts.step_uuid = fStepUuid;
try
{
sts.msg_type = StepTeleStats::ST_START;
sts.total_units_of_work = 1;
postStepStartTele(sts);
ret = mysql->init(fHost.c_str(), fPort, fUser.c_str(), fPasswd.c_str(), fSchema.c_str());
if (ret != 0)
mysql->handleMySqlError(mysql->getError().c_str(), ret);
std::string query(makeQuery());
fLogger->logMessage(logging::LOG_TYPE_INFO, "QUERY to foreign engine: " + query);
if (traceOn())
cout << "QUERY: " << query << endl;
ret = mysql->run(query.c_str());
if (ret != 0)
mysql->handleMySqlError(mysql->getError().c_str(), ret);
int num_fields = mysql->getFieldCount();
char** rowIn; // input
// shared_array<uint8_t> rgDataDelivered; // output
RGData rgDataDelivered;
fRowGroupAdded.initRow(&fRowDelivered);
// use getDataSize() i/o getMaxDataSize() to make sure there are 8192 rows.
rgDataDelivered.reinit(fRowGroupAdded, fRowsPerGroup);
fRowGroupAdded.setData(&rgDataDelivered);
fRowGroupAdded.resetRowGroup(0);
fRowGroupAdded.getRow(0, &fRowDelivered);
if (traceOn())
dlTimes.setFirstReadTime();
// Any functions to evaluate
makeMappings();
bool doFE1 = ((fFeFcnJoin.size() > 0) || (fFeFilters.size() > 0));
bool doFE3 = (fFeSelects.size() > 0);
if (!doFE1 && !doFE3)
{
while ((rowIn = mysql->nextRow()) && !cancelled())
{
for (int i = 0; i < num_fields; i++)
setField(i, rowIn[i], mysql->getFieldLength(i), mysql->getField(i), fRowDelivered);
addRow(rgDataDelivered);
}
}
else if (doFE1 && !doFE3) // FE in WHERE clause only
{
shared_array<uint8_t> rgDataFe1; // functions in where clause
Row rowFe1; // row for fe evaluation
fRowGroupFe1.initRow(&rowFe1, true);
rgDataFe1.reset(new uint8_t[rowFe1.getSize()]);
rowFe1.setData(rgDataFe1.get());
while ((rowIn = mysql->nextRow()) && !cancelled())
{
// Parse the columns used in FE1 first, the other column may not need be parsed.
for (int i = 0; i < num_fields; i++)
{
if (fFe1Column[i] != -1)
setField(fFe1Column[i], rowIn[i], mysql->getFieldLength(i), mysql->getField(i), rowFe1);
}
if (fFeFilters.size() > 0)
{
bool feBreak = false;
for (std::vector<boost::shared_ptr<execplan::ParseTree> >::iterator it = fFeFilters.begin();
it != fFeFilters.end(); it++)
{
if (fFeInstance->evaluate(rowFe1, (*it).get()) == false)
{
feBreak = true;
break;
}
}
if (feBreak)
continue;
}
// evaluate the FE join column
fFeInstance->evaluate(rowFe1, fFeFcnJoin);
// Pass throug the parsed columns, and parse the remaining columns.
applyMapping(fFeMapping1, rowFe1, &fRowDelivered);
for (int i = 0; i < num_fields; i++)
{
if (fFe1Column[i] == -1)
setField(i, rowIn[i], mysql->getFieldLength(i), mysql->getField(i), fRowDelivered);
}
addRow(rgDataDelivered);
}
}
else if (!doFE1 && doFE3) // FE in SELECT clause only
{
shared_array<uint8_t> rgDataFe3; // functions in select clause
Row rowFe3; // row for fe evaluation
fRowGroupOut.initRow(&rowFe3, true);
rgDataFe3.reset(new uint8_t[rowFe3.getSize()]);
rowFe3.setData(rgDataFe3.get());
while ((rowIn = mysql->nextRow()) && !cancelled())
{
for (int i = 0; i < num_fields; i++)
setField(i, rowIn[i], mysql->getFieldLength(i), mysql->getField(i), rowFe3);
fFeInstance->evaluate(rowFe3, fFeSelects);
applyMapping(fFeMapping3, rowFe3, &fRowDelivered);
addRow(rgDataDelivered);
}
}
else // FE in SELECT clause, FE join and WHERE clause
{
shared_array<uint8_t> rgDataFe1; // functions in where clause
Row rowFe1; // row for fe1 evaluation
fRowGroupFe1.initRow(&rowFe1, true);
rgDataFe1.reset(new uint8_t[rowFe1.getSize()]);
rowFe1.setData(rgDataFe1.get());
shared_array<uint8_t> rgDataFe3; // functions in select clause
Row rowFe3; // row for fe3 evaluation
fRowGroupOut.initRow(&rowFe3, true);
rgDataFe3.reset(new uint8_t[rowFe3.getSize()]);
rowFe3.setData(rgDataFe3.get());
while ((rowIn = mysql->nextRow()) && !cancelled())
{
// Parse the columns used in FE1 first, the other column may not need be parsed.
for (int i = 0; i < num_fields; i++)
{
if (fFe1Column[i] != -1)
setField(fFe1Column[i], rowIn[i], mysql->getFieldLength(i), mysql->getField(i), rowFe1);
}
if (fFeFilters.size() > 0)
{
bool feBreak = false;
for (std::vector<boost::shared_ptr<execplan::ParseTree> >::iterator it = fFeFilters.begin();
it != fFeFilters.end(); it++)
{
if (fFeInstance->evaluate(rowFe1, (*it).get()) == false)
{
feBreak = true;
break;
}
}
if (feBreak)
continue;
}
// evaluate the FE join column
fFeInstance->evaluate(rowFe1, fFeFcnJoin);
// Pass throug the parsed columns, and parse the remaining columns.
applyMapping(fFeMapping1, rowFe1, &rowFe3);
for (int i = 0; i < num_fields; i++)
{
if (fFe1Column[i] == -1)
setField(i, rowIn[i], mysql->getFieldLength(i), mysql->getField(i), rowFe3);
}
fFeInstance->evaluate(rowFe3, fFeSelects);
applyMapping(fFeMapping3, rowFe3, &fRowDelivered);
addRow(rgDataDelivered);
}
}
// INSERT_ADAPTER(fOutputDL, rgDataDelivered);
fOutputDL->insert(rgDataDelivered);
fRowsRetrieved = mysql->getRowCount();
}
catch (...)
{
handleException(std::current_exception(), logging::ERR_CROSS_ENGINE_CONNECT, logging::ERR_ALWAYS_CRITICAL,
"CrossEngineStep::execute()");
}
sts.msg_type = StepTeleStats::ST_SUMMARY;
sts.total_units_of_work = sts.units_of_work_completed = 1;
sts.rows = fRowsReturned;
postStepSummaryTele(sts);
fEndOfResult = true;
fOutputDL->endOfInput();
// Bug 3136, let mini stats to be formatted if traceOn.
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
printCalTrace();
}
}
void CrossEngineStep::setBPP(JobStep* jobStep)
{
pColStep* pcs = dynamic_cast<pColStep*>(jobStep);
pColScanStep* pcss = NULL;
pDictionaryStep* pds = NULL;
pDictionaryScan* pdss = NULL;
FilterStep* fs = NULL;
std::string bop = " AND ";
if (pcs != 0)
{
if (dynamic_cast<PseudoColStep*>(pcs) != NULL)
throw logic_error("No Psedo Column for foreign engine.");
if (pcs->BOP() == BOP_OR)
bop = " OR ";
addFilterStr(pcs->getFilters(), bop);
}
else if ((pcss = dynamic_cast<pColScanStep*>(jobStep)) != NULL)
{
if (pcss->BOP() == BOP_OR)
bop = " OR ";
addFilterStr(pcss->getFilters(), bop);
}
else if ((pds = dynamic_cast<pDictionaryStep*>(jobStep)) != NULL)
{
if (pds->BOP() == BOP_OR)
bop = " OR ";
addFilterStr(pds->getFilters(), bop);
}
else if ((pdss = dynamic_cast<pDictionaryScan*>(jobStep)) != NULL)
{
if (pds->BOP() == BOP_OR)
bop = " OR ";
addFilterStr(pdss->getFilters(), bop);
}
else if ((fs = dynamic_cast<FilterStep*>(jobStep)) != NULL)
{
addFilterStr(fs->getFilters(), bop);
}
}
void CrossEngineStep::addFilterStr(const std::vector<const Filter*>& f, const std::string& bop)
{
if (f.size() == 0)
return;
std::string filterStr;
for (uint64_t i = 0; i < f.size(); i++)
{
if (f[i]->data().empty())
continue;
if (!filterStr.empty())
filterStr += bop;
filterStr += f[i]->data();
}
if (!filterStr.empty())
{
if (!fWhereClause.empty())
fWhereClause += " AND (" + filterStr + ")";
else
fWhereClause += " WHERE (" + filterStr + ")";
}
}
void CrossEngineStep::setProjectBPP(JobStep* jobStep1, JobStep*)
{
fColumnMap[jobStep1->tupleId()] = fColumnCount++;
if (!fSelectClause.empty())
fSelectClause += ", ";
else
fSelectClause += "SELECT ";
fSelectClause += "`" + jobStep1->name() + "`";
}
std::string CrossEngineStep::makeQuery()
{
ostringstream oss;
oss << fSelectClause << " FROM `" << fTable << "`";
if (fTable.compare(fAlias) != 0)
oss << " `" << fAlias << "`";
if (!fWhereClause.empty())
oss << fWhereClause;
// the std::string must consist of a single SQL statement without a terminating semicolon ; or \g.
// oss << ";";
return oss.str();
}
const RowGroup& CrossEngineStep::getOutputRowGroup() const
{
return fRowGroupOut;
}
const RowGroup& CrossEngineStep::getDeliveredRowGroup() const
{
return fRowGroupDelivered;
}
uint32_t CrossEngineStep::nextBand(messageqcpp::ByteStream& bs)
{
// shared_array<uint8_t> rgDataOut;
RGData rgDataOut;
bool more = false;
uint32_t rowCount = 0;
try
{
bs.restart();
more = fOutputDL->next(fOutputIterator, &rgDataOut);
if (traceOn() && dlTimes.FirstReadTime().tv_sec == 0)
dlTimes.setFirstReadTime();
if (more && !cancelled())
{
fRowGroupDelivered.setData(&rgDataOut);
fRowGroupDelivered.serializeRGData(bs);
rowCount = fRowGroupDelivered.getRowCount();
}
else
{
while (more)
more = fOutputDL->next(fOutputIterator, &rgDataOut);
fEndOfResult = true;
}
}
catch (...)
{
handleException(std::current_exception(), logging::ERR_IN_DELIVERY, logging::ERR_ALWAYS_CRITICAL,
"CrossEngineStep::nextBand()");
while (more)
more = fOutputDL->next(fOutputIterator, &rgDataOut);
fEndOfResult = true;
}
if (fEndOfResult)
{
// send an empty / error band
rgDataOut.reinit(fRowGroupDelivered, 0);
fRowGroupDelivered.setData(&rgDataOut);
fRowGroupDelivered.resetRowGroup(0);
fRowGroupDelivered.setStatus(status());
fRowGroupDelivered.serializeRGData(bs);
if (traceOn())
{
dlTimes.setLastReadTime();
dlTimes.setEndOfInputTime();
}
if (traceOn())
printCalTrace();
}
return rowCount;
}
const std::string CrossEngineStep::toString() const
{
ostringstream oss;
oss << "CrossEngineStep 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 CrossEngineStep::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 << "; rows retrieved-"
<< fRowsRetrieved << "; 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 CrossEngineStep::formatMiniStats()
{
ostringstream oss;
oss << "CES "
<< "UM "
<< "- "
<< "- "
<< "- "
<< "- "
<< "- "
<< "- " << JSTimeStamp::tsdiffstr(dlTimes.EndOfInputTime(), dlTimes.FirstReadTime()) << " "
<< fRowsReturned << " ";
fMiniInfo += oss.str();
}
} // namespace joblist