/* 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: jlf_common.cpp 9655 2013-06-25 23:08:13Z xlou $

#include "calpontsystemcatalog.h"
#include "aggregatecolumn.h"
#include "pseudocolumn.h"
#include "simplecolumn.h"
using namespace std;
using namespace execplan;

#include "messagelog.h"
using namespace logging;

#include <boost/algorithm/string/case_conv.hpp>
namespace ba = boost::algorithm;

#include "dbrm.h"
#include "extentmap.h"
using namespace BRM;

#include "jlf_common.h"
using namespace joblist;
#include "mcs_decimal.h"

namespace
{
// @brief Returns unique key for a column, table, or expresssion.
uint32_t uniqTupleKey(JobInfo& jobInfo, CalpontSystemCatalog::OID& o, CalpontSystemCatalog::OID& t,
                      const string& cn, const string& ca, const string& tn, const string& ta,
                      const string& sn, const string& vw, uint32_t pi, uint64_t en, bool correlated = false)
{
  uint64_t subId = jobInfo.subId;

  if (correlated && jobInfo.pJobInfo)
    subId = jobInfo.pJobInfo->subId;

  string alias(ta);
  //	if (!ca.empty())
  //		alias += "." + ca;
  string nm(ta);

  if (!cn.empty())
    nm += "." + cn;

  UniqId id(o, ta, sn, vw, pi, subId);
  TupleKeyMap::iterator iter = jobInfo.keyInfo->tupleKeyMap.find(id);

  if (iter != jobInfo.keyInfo->tupleKeyMap.end())
    return iter->second;

  uint32_t newId = jobInfo.keyInfo->nextKey++;
  // cout << "new id: " << newId << " -- " << o << ", " << pi << ", " << nm << ", " << vw << ", " << sn << ",
  // " << subId << endl;
  jobInfo.keyInfo->tupleKeyMap[id] = newId;
  jobInfo.keyInfo->tupleKeyVec.push_back(id);
  jobInfo.keyInfo->tupleKeyToTableOid.insert(make_pair(newId, t));
  jobInfo.keyInfo->crossEngine.push_back((en != 0));

  string ss = vw;

  if (ss.length() > 0)
    ss += ".";

  if (sn.length() > 0)
    ss += sn + ".";

  if (o != t)
  {
    string name = cn;

    if (!ca.empty())  // has an alias
      name = ca;
    else if (ta.compare(0, 5, "$sub_") && ta.compare(0, 4, "$exp"))  // compare != 0
      name = ss + ta + "." + name;

    jobInfo.keyInfo->tupleKeyToName.push_back(name);
    jobInfo.keyInfo->keyName.insert(make_pair(newId, cn));
  }
  else
  {
    string name = ta;
    bool useAlias = true;

    if (name.empty())
    {
      name = tn;
      useAlias = false;
    }

    if (tn.compare(0, 4, "$sub") && tn.compare(0, 4, "$exp"))
    {
      if (!useAlias)
        name = ss + name;
    }
    else if (tn.compare(0, 4, "$sub") == 0)
    {
      name = "sub-query";
    }
    else
    {
      name = "expression";
    }

    jobInfo.keyInfo->tupleKeyToName.push_back(name);
    jobInfo.keyInfo->keyName.insert(make_pair(newId, tn));
  }

  return newId;
}

// @brief Returns a suitably fudged column width
uint32_t fudgeWidth(const CalpontSystemCatalog::ColType& ict, CalpontSystemCatalog::OID oid)
{
  CalpontSystemCatalog::OID dictOid = isDictCol(ict);
  CalpontSystemCatalog::ColType ct = ict;

  if (ct.colDataType != CalpontSystemCatalog::VARBINARY && ct.colDataType != CalpontSystemCatalog::BLOB)
  {
    if (ct.colDataType == CalpontSystemCatalog::VARCHAR || ct.colDataType == CalpontSystemCatalog::TEXT)
      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;
    else if (dictOid > 0 && oid != dictOid)  // token column
      ct.colWidth = 8;
  }
  else
  {
    ct.colWidth += 2;  // insert the length bytes
  }

  return ct.colWidth;
}

// @brief Set some tuple info
TupleInfo setTupleInfo_(const CalpontSystemCatalog::ColType& ct, CalpontSystemCatalog::OID col_oid,
                        JobInfo& jobInfo, CalpontSystemCatalog::OID tbl_oid, const string& col_name,
                        const string& col_alias, const string& sch_name, const string& tbl_name,
                        const string& tbl_alias, const string& vw_name, bool correlated = false,
                        uint32_t pc_id = 0, uint64_t engine = 0)
{
  // get the unique tupleOids for this column
  uint32_t tbl_key = uniqTupleKey(jobInfo, tbl_oid, tbl_oid, "", "", tbl_name, tbl_alias, sch_name, vw_name,
                                  0, engine, correlated);
  uint32_t col_key = uniqTupleKey(jobInfo, col_oid, tbl_oid, col_name, col_alias, tbl_name, tbl_alias,
                                  sch_name, vw_name, pc_id, engine, correlated);
  // If this is the first time we've seen this col, add it to the tim
  TupleInfoMap::iterator it = jobInfo.keyInfo->tupleInfoMap.find(col_key);
  TupleInfo ti;

  if (it != jobInfo.keyInfo->tupleInfoMap.end())
  {
    // We've seen the key
    ti = it->second;
  }
  else
  {
    // Haven't even seen the table yet, much less this col
    ti = TupleInfo(fudgeWidth(ct, col_oid), col_oid, col_key, tbl_key, ct.scale, ct.precision, ct.colDataType,
                   ct.charsetNumber);
    jobInfo.keyInfo->tupleInfoMap[col_key] = ti;
    jobInfo.keyInfo->colKeyToTblKey[col_key] = tbl_key;
    jobInfo.keyInfo->colKeyToTblKey[tbl_key] = tbl_key;
    jobInfo.keyInfo->colType[col_key] = ct;
    jobInfo.keyInfo->pseudoType[col_key] = pc_id;
  }

  if (pc_id > 0 && jobInfo.pseudoColTable.find(tbl_key) == jobInfo.pseudoColTable.end())
    jobInfo.pseudoColTable.insert(tbl_key);

  return ti;
}

uint32_t getTupleKey_(const JobInfo& jobInfo, CalpontSystemCatalog::OID oid, const string& colName,
                      const string& tblAlias, const string& schema, const string& view,
                      bool correlated = false, uint32_t pseudo = 0, uint64_t engine = 0)
{
  uint64_t subId = jobInfo.subId;

  if (correlated && jobInfo.pJobInfo)
    subId = jobInfo.pJobInfo->subId;

  string alias(tblAlias);
  string name(tblAlias);

  if (!colName.empty())
    name += "." + colName;

  //	if (!colAlias.empty())
  //		alias += "." + colAlias;
  UniqId id(oid, tblAlias, schema, view, pseudo, subId);
  TupleKeyMap::const_iterator iter = jobInfo.keyInfo->tupleKeyMap.find(id);

  if (iter != jobInfo.keyInfo->tupleKeyMap.end())
    return iter->second;

  // dictionaryscan tableOid is 0 in the tuplehashjoin.
  if (oid != 0)
  {
    ostringstream strstm;
    strstm << "(" << oid << ", ";

    if (!alias.empty())
      strstm << alias;

    if (!view.empty())
      strstm << ", " << view;

    strstm << ") not found in tuple info map.";

    Message::Args args;
    args.add(strstm.str());
    jobInfo.logger->logMessage(LOG_TYPE_DEBUG, LogMakeJobList, args,
                               LoggingID(5, jobInfo.sessionId, jobInfo.txnId, 0));
    cerr << strstm.str() << endl;

    // cout << "not found: " << oid << ", " << name << ", " << view << ", " << schema << ", " << subId <<
    // endl;
    throw logic_error("column is not found in info map.");
  }

  return static_cast<uint32_t>(-1);
}
}  // namespace

namespace joblist
{
UniqId::UniqId(const execplan::SimpleColumn* sc)
 : fId(sc->oid())
 ,
 // fName(extractTableAlias(sc)+"."+sc->columnName()),
 fTable(extractTableAlias(sc))
 , fSchema(sc->schemaName())
 , fView(sc->viewName())
 , fPseudo(0)
 , fSubId(-1)
{
  const PseudoColumn* pc = dynamic_cast<const execplan::PseudoColumn*>(sc);
  uint32_t pseudoType = (pc) ? pc->pseudoType() : execplan::PSEUDO_UNKNOWN;
  fPseudo = pseudoType;
}

UniqId::UniqId(int o, const execplan::SimpleColumn* sc)
 : fId(o)
 ,
 // fName(extractTableAlias(sc)+"."+sc->columnName()),
 fTable(extractTableAlias(sc))
 , fSchema(sc->schemaName())
 , fView(sc->viewName())
 , fPseudo(0)
 , fSubId(-1)
{
}

string UniqId::toString() const
{
  ostringstream strstm;
  strstm << fId << ":" << fTable << ":" << fSchema << ":" << fView << ":" << fPseudo << ":"
         << (int64_t)fSubId;
  return strstm.str();
}

//------------------------------------------------------------------------------
// Returns the table alias for the specified column
//------------------------------------------------------------------------------
string extractTableAlias(const SimpleColumn* sc)
{
  return sc->tableAlias();
}

//------------------------------------------------------------------------------
// Returns the table alias for the specified column
//------------------------------------------------------------------------------
string extractTableAlias(const SSC& sc)
{
  return sc->tableAlias();
}

//------------------------------------------------------------------------------
// Returns OID associated with colType if it is a dictionary column, else
// the value returned for the OID is 0.
//------------------------------------------------------------------------------
CalpontSystemCatalog::OID isDictCol(const CalpontSystemCatalog::ColType& colType)
{
  if (colType.isWideDecimalType())
    return 0;

  if (colType.colWidth > 8)
    return colType.ddn.dictOID;

  if (colType.colDataType == CalpontSystemCatalog::VARCHAR && colType.colWidth > 7)
    return colType.ddn.dictOID;

  if (colType.colDataType == CalpontSystemCatalog::VARBINARY ||
      colType.colDataType == CalpontSystemCatalog::BLOB || colType.colDataType == CalpontSystemCatalog::TEXT)
    return colType.ddn.dictOID;

  return 0;
}

//------------------------------------------------------------------------------
// Determines if colType is a character column
//------------------------------------------------------------------------------
bool isCharCol(const CalpontSystemCatalog::ColType& colType)
{
  switch (colType.colDataType)
  {
    case CalpontSystemCatalog::VARCHAR:
    case CalpontSystemCatalog::CHAR:
    case CalpontSystemCatalog::BLOB:
    case CalpontSystemCatalog::TEXT:
    case CalpontSystemCatalog::CLOB: return true; break;

    default: return false; break;
  }

  return false;
}

//------------------------------------------------------------------------------
// Returns OID associated with a table
//------------------------------------------------------------------------------
CalpontSystemCatalog::OID tableOid(const SimpleColumn* sc, boost::shared_ptr<CalpontSystemCatalog> cat)
{
  if (sc->schemaName().empty())
    return execplan::CNX_VTABLE_ID;

  if (sc->isColumnStore() == false)
    return 0;

  CalpontSystemCatalog::ROPair p = cat->tableRID(make_table(sc->schemaName(), sc->tableName()));
  return p.objnum;
}

uint32_t getTupleKey(JobInfo& jobInfo, const execplan::SimpleColumn* sc, bool add)
{
  int key = -1;
  const PseudoColumn* pc = dynamic_cast<const execplan::PseudoColumn*>(sc);
  uint32_t pseudoType = (pc) ? pc->pseudoType() : execplan::PSEUDO_UNKNOWN;

  if (sc == NULL)
  {
    return -1;
  }

  if (add)
  {
    // setTupleInfo first if add is true, ok if already set.
    if (sc->schemaName().empty())
    {
      SimpleColumn tmp(*sc, jobInfo.sessionId);
      tmp.oid(tableOid(sc, jobInfo.csc) + 1 + sc->colPosition());
      key = getTupleKey(jobInfo, &tmp);  // sub-query should be there
    }
    else
    {
      CalpontSystemCatalog::ColType ct = sc->colType();
      string alias(extractTableAlias(sc));
      CalpontSystemCatalog::OID tblOid = tableOid(sc, jobInfo.csc);
      TupleInfo ti(setTupleInfo(ct, sc->oid(), jobInfo, tblOid, sc, alias));
      key = ti.key;

      CalpontSystemCatalog::OID dictOid = isDictCol(ct);

      if (dictOid > 0)
      {
        ti = setTupleInfo(ct, dictOid, jobInfo, tblOid, sc, alias);
        jobInfo.keyInfo->dictKeyMap[key] = ti.key;
        key = ti.key;
      }
    }
  }
  else
  {
    // TupleInfo is expected to be set already
    return getTupleKey_(jobInfo, sc->oid(), sc->columnName(), extractTableAlias(sc), sc->schemaName(),
                        sc->viewName(), ((sc->joinInfo() & execplan::JOIN_CORRELATED) != 0), pseudoType,
                        (sc->isColumnStore() ? 0 : 1));
  }

  return key;
}

uint32_t getTupleKey(JobInfo& jobInfo, const SRCP& srcp, bool add)
{
  int key = -1;

  if (add)
  {
    // setTupleInfo first if add is true, ok if already set.
    const SimpleColumn* sc = dynamic_cast<const SimpleColumn*>(srcp.get());

    if (sc != NULL)
    {
      if (sc->schemaName().empty())
      {
        SimpleColumn tmp(*sc, jobInfo.sessionId);
        tmp.oid(tableOid(sc, jobInfo.csc) + 1 + sc->colPosition());
        key = getTupleKey(jobInfo, &tmp);  // sub-query should be there
      }
      else
      {
        CalpontSystemCatalog::ColType ct = sc->colType();
        string alias(extractTableAlias(sc));
        CalpontSystemCatalog::OID tblOid = tableOid(sc, jobInfo.csc);
        TupleInfo ti(setTupleInfo(ct, sc->oid(), jobInfo, tblOid, sc, alias));
        key = ti.key;

        CalpontSystemCatalog::OID dictOid = isDictCol(ct);

        if (dictOid > 0)
        {
          ti = setTupleInfo(ct, dictOid, jobInfo, tblOid, sc, alias);
          jobInfo.keyInfo->dictKeyMap[key] = ti.key;
          key = ti.key;
        }
      }
    }
    else
    {
      CalpontSystemCatalog::ColType ct = srcp->resultType();
      TupleInfo ti(setExpTupleInfo(ct, srcp->expressionId(), srcp->alias(), jobInfo));
      key = ti.key;
    }
  }
  else
  {
    // TupleInfo is expected to be set already
    const SimpleColumn* sc = dynamic_cast<const SimpleColumn*>(srcp.get());

    if (sc != NULL)
    {
      if (sc->schemaName().empty())
      {
        SimpleColumn tmp(*sc, jobInfo.sessionId);
        tmp.oid(tableOid(sc, jobInfo.csc) + 1 + sc->colPosition());
        key = getTupleKey(jobInfo, &tmp);
      }
      else
      {
        key = getTupleKey(jobInfo, sc);
      }

      // check if this is a dictionary column
      if (jobInfo.keyInfo->dictKeyMap.find(key) != jobInfo.keyInfo->dictKeyMap.end())
        key = jobInfo.keyInfo->dictKeyMap[key];
    }
    else
    {
      key = getExpTupleKey(jobInfo, srcp->expressionId());
    }
  }

  return key;
}

uint32_t getTableKey(const JobInfo& jobInfo, execplan::CalpontSystemCatalog::OID tableOid,
                     const string& alias, const string& schema, const string& view)
{
  return getTupleKey_(jobInfo, tableOid, "", alias, schema, view);
}

uint32_t getTableKey(const JobInfo& jobInfo, uint32_t cid)
{
  return jobInfo.keyInfo->colKeyToTblKey[cid];
}

void updateTableKey(uint32_t cid, uint32_t tid, JobInfo& jobInfo)
{
  jobInfo.keyInfo->colKeyToTblKey[cid] = tid;
}

uint32_t getTableKey(JobInfo& jobInfo, JobStep* js)
{
  CalpontSystemCatalog::OID tableOid = js->tableOid();
  return getTupleKey_(jobInfo, tableOid, "", js->alias(), js->schema(), js->view());
}

uint32_t makeTableKey(JobInfo& jobInfo, const execplan::SimpleColumn* sc)
{
  CalpontSystemCatalog::OID o = tableOid(sc, jobInfo.csc);
  return uniqTupleKey(jobInfo, o, o, "", "", sc->tableName(), extractTableAlias(sc), sc->schemaName(),
                      sc->viewName(), 0, (sc->isColumnStore() ? 0 : 1),
                      ((sc->joinInfo() & execplan::JOIN_CORRELATED) != 0));
}

uint32_t makeTableKey(JobInfo& jobInfo, CalpontSystemCatalog::OID o, const string& tn, const string& ta,
                      const string& sn, const string& vn, uint64_t en)
{
  return uniqTupleKey(jobInfo, o, o, "", "", tn, ta, sn, vn, 0, en);
}

TupleInfo getTupleInfo(uint32_t columnKey, const JobInfo& jobInfo)
{
  TupleInfoMap::const_iterator cit = jobInfo.keyInfo->tupleInfoMap.find(columnKey);

  if ((cit == jobInfo.keyInfo->tupleInfoMap.end()) || (cit->second.dtype == CalpontSystemCatalog::BIT))
  {
    ostringstream strstm;
    strstm << "TupleInfo for (" << jobInfo.keyInfo->tupleKeyVec[columnKey].fId << ","
           << jobInfo.keyInfo->tupleKeyVec[columnKey].fTable;

    if (jobInfo.keyInfo->tupleKeyVec[columnKey].fView.length() > 0)
      strstm << "," << jobInfo.keyInfo->tupleKeyVec[columnKey].fView;

    strstm << ") could not be found." << endl;
    cerr << strstm.str();

    Message::Args args;
    args.add(strstm.str());
    jobInfo.logger->logMessage(LOG_TYPE_DEBUG, LogMakeJobList, args,
                               LoggingID(5, jobInfo.sessionId, jobInfo.txnId, 0));

    throw runtime_error("column's tuple info could not be found");
  }

  return cit->second;
}

TupleInfo setTupleInfo(const execplan::CalpontSystemCatalog::ColType& ct,
                       execplan::CalpontSystemCatalog::OID col_oid, JobInfo& jobInfo,
                       execplan::CalpontSystemCatalog::OID tbl_oid, const execplan::SimpleColumn* sc,
                       const string& alias)
{
  const PseudoColumn* pc = dynamic_cast<const execplan::PseudoColumn*>(sc);
  uint32_t pseudoType = (pc) ? pc->pseudoType() : execplan::PSEUDO_UNKNOWN;
  return setTupleInfo_(ct, col_oid, jobInfo, tbl_oid, sc->columnName(), sc->alias(), sc->schemaName(),
                       sc->tableName(), alias, sc->viewName(),
                       ((sc->joinInfo() & execplan::JOIN_CORRELATED) != 0), pseudoType,
                       (sc->isColumnStore() ? 0 : 1));
}

TupleInfo setExpTupleInfo(const execplan::CalpontSystemCatalog::ColType& ct, uint64_t expressionId,
                          const string& alias, JobInfo& jobInfo, bool cr)
{
  // pretend all expressions belong to "virtual" table EXPRESSION, (CNX_EXP_TABLE_ID, expression)
  // CNX_EXP_TABLE_ID(999) is not for user table or column, there will be no confilict in queries.
  JobInfo* ji = &jobInfo;

  if (cr && jobInfo.pJobInfo)
    ji = jobInfo.pJobInfo;

  string expAlias("$exp");

  if (!(ji->subAlias.empty()))
    expAlias += ji->subAlias;

  return setTupleInfo_(ct, expressionId, jobInfo, CNX_EXP_TABLE_ID, "", alias, "", "$exp", expAlias, "", cr);
}

TupleInfo setExpTupleInfo(const execplan::ReturnedColumn* rc, JobInfo& jobInfo)
{
  return setExpTupleInfo(rc->resultType(), rc->expressionId(), rc->alias(), jobInfo,
                         ((rc->joinInfo() & execplan::JOIN_CORRELATED) != 0));
}

uint32_t getExpTupleKey(const JobInfo& jobInfo, uint64_t eid, bool cr)
{
  const JobInfo* ji = &jobInfo;

  if (cr && jobInfo.pJobInfo)
    ji = jobInfo.pJobInfo;

  string expAlias("$exp");

  if (!(ji->subAlias.empty()))
    expAlias += ji->subAlias;

  return getTupleKey_(jobInfo, eid, "", expAlias, "", "", cr);
}

void addAggregateColumn(ReturnedColumn* agc, int idx, RetColsVector& vec, JobInfo& jobInfo)
{
  uint32_t eid = agc->expressionId();
  setExpTupleInfo(agc->resultType(), eid, agc->alias(), jobInfo);

  vector<pair<int, int> >::iterator i;

  for (i = jobInfo.aggEidIndexList.begin(); i != jobInfo.aggEidIndexList.end(); ++i)
  {
    if (i->first == (int)eid)
      break;
  }

  if (idx < 0 && i != jobInfo.aggEidIndexList.end())
  {
    agc->inputIndex(i->second);
    jobInfo.cloneAggregateColMap.insert(make_pair(vec[i->second].get(), agc));
  }
  else
  {
    SRCP srcp;

    if (idx < 0)
    {
      srcp.reset(agc->clone());
      idx = vec.size();
      vec.push_back(srcp);
    }
    else
    {
      srcp = vec[idx];
    }

    jobInfo.aggEidIndexList.push_back(make_pair(eid, idx));
    agc->inputIndex(idx);
    jobInfo.cloneAggregateColMap.insert(make_pair(srcp.get(), agc));
  }
}

bool operator<(const struct UniqId& x, const struct UniqId& y)
{
  return ((x.fId < y.fId) || (x.fId == y.fId && x.fTable < y.fTable) ||
          (x.fId == y.fId && x.fTable == y.fTable && x.fSchema < y.fSchema) ||
          (x.fId == y.fId && x.fTable == y.fTable && x.fSchema == y.fSchema && x.fView < y.fView) ||
          (x.fId == y.fId && x.fTable == y.fTable && x.fSchema == y.fSchema && x.fView == y.fView &&
           x.fPseudo < y.fPseudo) ||
          (x.fId == y.fId && x.fTable == y.fTable && x.fSchema == y.fSchema && x.fView == y.fView &&
           x.fPseudo == y.fPseudo && x.fSubId < y.fSubId));
}

bool operator==(const struct UniqId& x, const struct UniqId& y)
{
  return (x.fId == y.fId && x.fTable == y.fTable && x.fSchema == y.fSchema && x.fView == y.fView &&
          x.fPseudo == y.fPseudo && x.fSubId == y.fSubId);
}

void updateDerivedColumn(JobInfo& jobInfo, SimpleColumn* sc, CalpontSystemCatalog::ColType& ct)
{
  sc->oid(tableOid(sc, jobInfo.csc) + 1 + sc->colPosition());

  map<UniqId, execplan::CalpontSystemCatalog::ColType>::iterator i = jobInfo.vtableColTypes.find(UniqId(sc));

  if (i != jobInfo.vtableColTypes.end())
    ct = i->second;
}

bool filterWithDictionary(execplan::CalpontSystemCatalog::OID dictOid, uint64_t n)
{
  // if n == 0, no dictionary scan, alway filter with dictionary.
  if (n == 0)
    return true;

  // if n == ulong_max, always use dictionary scan
  if (n == ULONG_MAX)
    return false;

  vector<struct EMEntry> entries;
  DBRM dbrm;

  if (dbrm.getExtents(dictOid, entries) != 0)
    return false;  // Just do pdictionaryscan and let job step handle this.

  vector<struct EMEntry>::iterator it = entries.begin();
  bool ret = false;
  n--;  // HWM starts at 0

  while (it != entries.end())
  {
    if (it->HWM > n)
    {
      ret = true;
      break;
    }

    it++;
  }

  return ret;
}

// @Bug 1230 & 1955
// Don't allow join/compare on "incompatible" cols
// Compatible columns:
// any 1,2,4,8-byte int to any 1,2,4,8-byte int
// decimal w/scale x to decimal w/scale x
// date to date
// datetime to datetime
// string to string
bool compatibleColumnTypes(const CalpontSystemCatalog::ColType& ct1, const CalpontSystemCatalog::ColType& ct2,
                           bool forJoin)
{
  return compatibleColumnTypes(ct1.colDataType, ct1.scale, ct2.colDataType, ct2.scale, forJoin);
}

bool compatibleColumnTypes(const CalpontSystemCatalog::ColDataType& dt1, uint32_t scale1,
                           const CalpontSystemCatalog::ColDataType& dt2, uint32_t scale2, bool forJoin)
{
  // disable VARBINARY used in join
  if (dt1 == CalpontSystemCatalog::VARBINARY || dt2 == CalpontSystemCatalog::VARBINARY)
    throw runtime_error("Comparsion between VARBINARY columns is not supported.");

  switch (dt1)
  {
    case CalpontSystemCatalog::BIT:
      if (dt2 != CalpontSystemCatalog::BIT)
        return false;

      break;

    case CalpontSystemCatalog::TINYINT:
    case CalpontSystemCatalog::SMALLINT:
    case CalpontSystemCatalog::MEDINT:
    case CalpontSystemCatalog::INT:
    case CalpontSystemCatalog::BIGINT:
    case CalpontSystemCatalog::DECIMAL:
    case CalpontSystemCatalog::UTINYINT:
    case CalpontSystemCatalog::USMALLINT:
    case CalpontSystemCatalog::UMEDINT:
    case CalpontSystemCatalog::UINT:
    case CalpontSystemCatalog::UBIGINT:
    case CalpontSystemCatalog::UDECIMAL:
      if (dt2 != CalpontSystemCatalog::TINYINT && dt2 != CalpontSystemCatalog::SMALLINT &&
          dt2 != CalpontSystemCatalog::MEDINT && dt2 != CalpontSystemCatalog::INT &&
          dt2 != CalpontSystemCatalog::BIGINT && dt2 != CalpontSystemCatalog::DECIMAL &&
          dt2 != CalpontSystemCatalog::UTINYINT && dt2 != CalpontSystemCatalog::USMALLINT &&
          dt2 != CalpontSystemCatalog::UMEDINT && dt2 != CalpontSystemCatalog::UINT &&
          dt2 != CalpontSystemCatalog::UBIGINT && dt2 != CalpontSystemCatalog::UDECIMAL)
        return false;

      if (scale2 != scale1)
        return false;

      break;

    case CalpontSystemCatalog::DATE:
      if (dt2 != CalpontSystemCatalog::DATE)
        return false;

      break;

    case CalpontSystemCatalog::DATETIME:
      if (dt2 != CalpontSystemCatalog::DATETIME)
        return false;

      break;

    case CalpontSystemCatalog::TIMESTAMP:
      if (dt2 != CalpontSystemCatalog::TIMESTAMP)
        return false;

      break;

    case CalpontSystemCatalog::TIME:
      if (dt2 != CalpontSystemCatalog::TIME)
        return false;

      break;

    case CalpontSystemCatalog::CHAR:
    case CalpontSystemCatalog::VARCHAR:
    case CalpontSystemCatalog::TEXT:

      // @bug 1495 compound/string join
      if (dt2 != CalpontSystemCatalog::VARCHAR && dt2 != CalpontSystemCatalog::CHAR &&
          dt2 != CalpontSystemCatalog::TEXT)
        return false;

      break;

    case CalpontSystemCatalog::VARBINARY:
    case CalpontSystemCatalog::BLOB:
      if (dt2 != CalpontSystemCatalog::VARBINARY && dt2 != CalpontSystemCatalog::BLOB)
        return false;

      break;

    case CalpontSystemCatalog::FLOAT:
    case CalpontSystemCatalog::UFLOAT:
      if (forJoin && (dt2 != CalpontSystemCatalog::FLOAT && dt2 != CalpontSystemCatalog::FLOAT))
        return false;
      else if (dt2 != CalpontSystemCatalog::FLOAT && dt2 != CalpontSystemCatalog::DOUBLE &&
               dt2 != CalpontSystemCatalog::UFLOAT && dt2 != CalpontSystemCatalog::UDOUBLE)
        return false;

      break;

    case CalpontSystemCatalog::DOUBLE:
    case CalpontSystemCatalog::UDOUBLE:
      if (forJoin && (dt2 != CalpontSystemCatalog::DOUBLE && dt2 != CalpontSystemCatalog::UDOUBLE))
        return false;
      else if (dt2 != CalpontSystemCatalog::FLOAT && dt2 != CalpontSystemCatalog::DOUBLE &&
               dt2 != CalpontSystemCatalog::UFLOAT && dt2 != CalpontSystemCatalog::UDOUBLE)
        return false;

      break;

    case CalpontSystemCatalog::LONGDOUBLE:
      if (forJoin && (dt2 != CalpontSystemCatalog::LONGDOUBLE))
        return false;
      else if (dt2 != CalpontSystemCatalog::FLOAT && dt2 != CalpontSystemCatalog::DOUBLE &&
               dt2 != CalpontSystemCatalog::UFLOAT && dt2 != CalpontSystemCatalog::UDOUBLE &&
               dt2 != CalpontSystemCatalog::LONGDOUBLE)
        return false;

      break;

    default: return false; break;
  }

  return true;
}

}  // namespace joblist
// vim:ts=4 sw=4:
