/***************** Filter C++ Class Filter Code (.CPP) *****************/
/*  Name: FILTER.CPP  Version 4.0                                      */
/*                                                                     */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2017    */
/*                                                                     */
/*  This file contains the class FILTER function code.                 */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant MariaDB header file.                              */
/***********************************************************************/
#include "my_global.h"
//#include "sql_class.h"
//#include "sql_time.h"

#if defined(__WIN__)
//#include <windows.h>
#else   // !__WIN__
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif  // !__WIN__


/***********************************************************************/
/*  Include required application header files                          */
/*  global.h    is header containing all global Plug declarations.     */
/*  plgdbsem.h  is header containing the DB applic. declarations.      */
/*  xobject.h   is header containing the XOBJECT derived classes dcls. */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "tabcol.h"
#include "xtable.h"
#include "array.h"
#include "filter.h"
#include "xindex.h"

/***********************************************************************/
/*  Utility routines.                                                  */
/***********************************************************************/
void  PlugConvertConstant(PGLOBAL, void* &, short&);
//void *PlugCopyDB(PTABS, void*, INT);
void  NewPointer(PTABS, void*, void*);
void  AddPointer(PTABS, void*);

static PPARM MakeParm(PGLOBAL g, PXOB xp)
  {
  PPARM pp = (PPARM)PlugSubAlloc(g, NULL, sizeof(PARM));
  pp->Type = TYPE_XOBJECT;
  pp->Value = xp;
  pp->Domain = 0;
  pp->Next = NULL;
  return pp;
  } // end of MakeParm

/***********************************************************************/
/*  Routines called internally/externally by FILTER functions.         */
/***********************************************************************/
bool   PlugEvalLike(PGLOBAL, LPCSTR, LPCSTR, bool);
//bool  ReadSubQuery(PGLOBAL, PSUBQ);
//PSUBQ OpenSubQuery(PGLOBAL, PSQL);
//void  PlugCloseDB(PGLOBAL, PSQL);
BYTE   OpBmp(PGLOBAL g, OPVAL opc);
PARRAY MakeValueArray(PGLOBAL g, PPARM pp);

/***********************************************************************/
/*  Returns the bitmap representing the conditions that must not be    */
/*  met when returning from TestValue for a given operator.            */
/*  Bit one is EQ, bit 2 is LT, and bit 3 is GT.                       */
/***********************************************************************/
BYTE OpBmp(PGLOBAL g, OPVAL opc)
  {
  BYTE bt;

  switch (opc) {
    case OP_IN:
    case OP_EQ: bt = 0x06; break;
    case OP_NE: bt = 0x01; break;
    case OP_GT: bt = 0x03; break;
    case OP_GE: bt = 0x02; break;
    case OP_LT: bt = 0x05; break;
    case OP_LE: bt = 0x04; break;
    case OP_EXIST: bt = 0x00; break;
    default:
      sprintf(g->Message, MSG(BAD_FILTER_OP), opc);
			throw (int)TYPE_FILTER;
	} // endswitch opc

  return bt;
  } // end of OpBmp

/***********************************************************************/
/*  Routines called externally by CondFilter.                          */
/***********************************************************************/
PFIL MakeFilter(PGLOBAL g, PFIL fp1, OPVAL vop, PFIL fp2)
  {
  PFIL filp = new(g) FILTER(g, vop);

  filp->Arg(0) = fp1;
  filp->Arg(1) = (fp2) ? fp2 : pXVOID;

  if (filp->Convert(g, false))
    return NULL;

  return filp;
  } // end of MakeFilter

PFIL MakeFilter(PGLOBAL g, PCOL *colp, POPER pop, PPARM pfirst, bool neg)
{
  PPARM parmp, pp[2];
  PFIL  fp1, fp2, filp = NULL;

  if (pop->Val == OP_IN) {
    PARRAY par = MakeValueArray(g, pfirst);

    if (par) {
      pp[0] = MakeParm(g, colp[0]);
      pp[1] = MakeParm(g, par);
      fp1 = new(g) FILTER(g, pop, pp);

      if (fp1->Convert(g, false))
        return NULL;

      filp = (neg) ? MakeFilter(g, fp1, OP_NOT, NULL) : fp1;
      } // endif par

  } else if (pop->Val == OP_XX) {    // BETWEEN
    if (pfirst && pfirst->Next) {
      pp[0] = MakeParm(g, colp[0]);
      pp[1] = pfirst;
      fp1 = new(g) FILTER(g, neg ? OP_LT : OP_GE, pp);

      if (fp1->Convert(g, false))
        return NULL;

      pp[1] = pfirst->Next;
      fp2 = new(g) FILTER(g, neg ? OP_GT : OP_LE, pp);

      if (fp2->Convert(g, false))
        return NULL;

      filp = MakeFilter(g, fp1, neg ? OP_OR : OP_AND, fp2);
      } // endif parmp

  } else {
    parmp = pfirst;

    for (int i = 0; i < 2; i++)
      if (colp[i]) {
        pp[i] = MakeParm(g, colp[i]);
      } else {
        if (!parmp || parmp->Domain != i)
          return NULL;        // Logical error, should never happen

        pp[i] = parmp;
        parmp = parmp->Next;
      } // endif colp

    filp = new(g) FILTER(g, pop, pp);

    if (filp->Convert(g, false))
      return NULL;

  } // endif's Val

  return filp;
} // end of MakeFilter

/* --------------------------- Class FILTER -------------------------- */                      

/***********************************************************************/
/*  FILTER public constructors.                                        */
/***********************************************************************/
FILTER::FILTER(PGLOBAL g, POPER pop, PPARM *tp)
  {
  Constr(g, pop->Val, pop->Mod, tp);
  } // end of FILTER constructor

FILTER::FILTER(PGLOBAL g, OPVAL opc, PPARM *tp)
  {
  Constr(g, opc, 0, tp);
  } // end of FILTER constructor

void FILTER::Constr(PGLOBAL g, OPVAL opc, int opm, PPARM *tp)
  {
  Next = NULL;
  Opc = opc;
  Opm = opm;
  Bt = 0x00;

  for (int i = 0; i < 2; i++) {
    Test[i].B_T = TYPE_VOID;

    if (tp && tp[i]) {
      PlugConvertConstant(g, tp[i]->Value, tp[i]->Type);
#if defined(_DEBUG)
      assert(tp[i]->Type == TYPE_XOBJECT);
#endif
      Arg(i) = (PXOB)tp[i]->Value;
    } else
      Arg(i) = pXVOID;

    Val(i) = NULL;
    Test[i].Conv = FALSE;
    } // endfor i

  } // end of Constr

/***********************************************************************/
/*  FILTER copy constructor.                                           */
/***********************************************************************/
FILTER::FILTER(PFIL fil1)
  {
  Next = NULL;
  Opc = fil1->Opc;
  Opm = fil1->Opm;
  Test[0] = fil1->Test[0];
  Test[1] = fil1->Test[1];
  } // end of FILTER copy constructor

#if 0
/***********************************************************************/
/*  Linearize:  Does the linearization of the filter tree:             */
/*    Independent filters (not implied in OR/NOT) will be separated    */
/*    from others and filtering operations will be automated by        */
/*    making a list of filter operations in polish operation style.    */
/*  Returned value points to the first filter of the list, which ends  */
/*  with the filter that was pointed by the first call argument,       */
/*  except for separators, in which case a loop is needed to find it.  */
/*  Note: a loop is used now in all cases (was not for OP_NOT) to be   */
/*    able to handle the case of filters whose arguments are already   */
/*    linearized, as it is done in LNA semantic routines. Indeed for   */
/*    already linearized chains, the first filter is never an OP_AND,  */
/*    OP_OR or OP_NOT filter, so this function just returns 'this'.    */
/***********************************************************************/
PFIL FILTER::Linearize(bool nosep)
  {
  int  i;
  PFIL lfp[2], ffp[2] = {NULL,NULL};

  switch (Opc) {
    case OP_NOT:
      if (GetArgType(0) == TYPE_FILTER) {
        lfp[0] = (PFIL)Arg(0);
        ffp[0] = lfp[0]->Linearize(TRUE);
        } /* endif */

      if (!ffp[0])
        return NULL;

      while (lfp[0]->Next)         // See Note above
        lfp[0] = lfp[0]->Next;

      Arg(0) = lfp[0];
      lfp[0]->Next = this;
      break;
    case OP_OR:
      nosep = TRUE;
    case OP_AND:
      for (i = 0; i < 2; i++) {
        if (GetArgType(i) == TYPE_FILTER) {
          lfp[i] = (PFIL)Arg(i);
          ffp[i] = lfp[i]->Linearize(nosep);
          } /* endif */

        if (!ffp[i])
          return NULL;

        while (lfp[i]->Next)
          lfp[i] = lfp[i]->Next;

        Arg(i) = lfp[i];
        } /* endfor i */

      if (nosep) {
        lfp[0]->Next = ffp[1];
        lfp[1]->Next = this;
      } else {
        lfp[0]->Next = this;
        Opc = OP_SEP;
        Arg(1) = pXVOID;
        Next = ffp[1];
      } /* endif */

      break;
    default:
      ffp[0] = this;
    } /* endswitch */

  return (ffp[0]);
  } // end of Linearize

/***********************************************************************/
/*  Link the fil2 filter chain to the fil1(this) filter chain.         */
/***********************************************************************/
PFIL FILTER::Link(PGLOBAL g, PFIL fil2)
  {
  PFIL fil1;

  if (trace(1))
    htrc("Linking filter %p with op=%d... to filter %p with op=%d\n",
                  this, Opc, fil2, (fil2) ? fil2->Opc : 0);

  for (fil1 = this; fil1->Next; fil1 = fil1->Next) ;

  if (fil1->Opc == OP_SEP)
    fil1->Next = fil2;              // Separator already exists
  else {
    // Create a filter separator and insert it between the chains
    PFIL filp = new(g) FILTER(g, OP_SEP);

    filp->Arg(0) = fil1;
    filp->Next = fil2;
    fil1->Next = filp;
    } // endelse

  return (this);
  } // end of Link

/***********************************************************************/
/*  Remove eventual last separator from a filter chain.                */
/***********************************************************************/
PFIL FILTER::RemoveLastSep(void)
  {
  PFIL filp, gfp = NULL;

  // Find last filter block (filp) and previous one (gfp).
  for (filp = this; filp->Next; filp = filp->Next)
    gfp = filp;

  // If last filter is a separator, remove it
  if (filp->Opc == OP_SEP)
    if (gfp)
      gfp->Next = NULL;
    else
      return NULL;       // chain is now empty

  return this;
  } // end of RemoveLastSep

/***********************************************************************/
/*  CheckColumn: Checks references to Columns in the filter and change */
/*  them into references to Col Blocks.                                */
/*  Returns the number of column references or -1 in case of column    */
/*  not found and -2 in case of unrecoverable error.                   */
/*  WHERE  filters are called with *aggreg == AGG_NO.                  */
/*  HAVING filters are called with *aggreg == AGG_ANY.                 */
/***********************************************************************/
int FILTER::CheckColumn(PGLOBAL g, PSQL sqlp, PXOB &p, int &ag)
  {
  char errmsg[MAX_STR] = "";
  int  agg, k, n = 0;

  if (trace(1))
    htrc("FILTER CheckColumn: sqlp=%p ag=%d\n", sqlp, ag);

  switch (Opc) {
    case OP_SEP:
    case OP_AND:
    case OP_OR:
    case OP_NOT:
      return 0;  // This because we are called for a linearized filter
    default:
      break;
    } // endswitch Opc

  // Check all arguments even in case of error for when we are called
  // from CheckHaving, where references to an alias raise an error but
  // we must have all other arguments to be set.
  for (int i = 0; i < 2; i++) {
    if (GetArgType(i) == TYPE_FILTER)      // Should never happen in
      return 0;                            // current implementation

    agg = ag;

    if ((k = Arg(i)->CheckColumn(g, sqlp, Arg(i), agg)) < -1) {
      return k;
    } else if (k < 0) {
      if (!*errmsg)                        // Keep first error message
        strcpy(errmsg, g->Message);

    } else
      n += k;

    } // endfor i

  if (*errmsg) {
    strcpy(g->Message, errmsg);
    return -1;
  } else
    return n;

  } // end of CheckColumn

/***********************************************************************/
/*  RefNum: Find the number of references correlated sub-queries make  */
/*  to the columns of the outer query (pointed by sqlp).               */
/***********************************************************************/
int FILTER::RefNum(PSQL sqlp)
  {
  int n = 0;

  for (int i = 0; i < 2; i++)
    n += Arg(i)->RefNum(sqlp);

  return n;
  } // end of RefNum

/***********************************************************************/
/*  CheckSubQuery: see SUBQUERY::CheckSubQuery for comment.            */
/***********************************************************************/
PXOB FILTER::CheckSubQuery(PGLOBAL g, PSQL sqlp)
  {
  switch (Opc) {
    case OP_SEP:
    case OP_AND:
    case OP_OR:
    case OP_NOT:
      break;
    default:
      for (int i = 0; i < 2; i++)
        if (!(Arg(i) = (PXOB)Arg(i)->CheckSubQuery(g, sqlp)))
          return NULL;

      break;
    } // endswitch Opc

  return this;
  } // end of CheckSubQuery

/***********************************************************************/
/*  SortJoin: function that places ahead of the list the 'good' groups */
/*  for join filtering.  These are groups with only one filter that    */
/*  specify equality between two different table columns, at least     */
/*  one is a table key column.  Doing so the join filter will be in    */
/*  general compatible with linearization of the joined table tree.    */
/*  This function has been added a further sorting on column indexing. */
/***********************************************************************/
PFIL FILTER::SortJoin(PGLOBAL g)
  {
  int     k;
  PCOL    cp1, cp2;
  PTDBASE tp1, tp2;
  PFIL    fp, filp, gfp, filstart = this, filjoin = NULL, lfp = NULL;
  bool    join = TRUE, key = TRUE;

  // This routine requires that the chain ends with a separator
  // So check for it and eventually add one if necessary
  for (filp = this; filp->Next; filp = filp->Next) ;

  if (filp->Opc != OP_SEP)
    filp->Next = new(g) FILTER(g, OP_SEP);

 again:
  for (k = (key) ? 0 : MAX_MULT_KEY; k <= MAX_MULT_KEY; k++)
    for (gfp = NULL, fp = filp = filstart; filp; filp = filp->Next)
      switch (filp->Opc) {
        case OP_SEP:
          if (join) {
            // Put this filter group into the join filter group list.
            if (!lfp)
              filjoin = fp;
            else
              lfp->Next = fp;

            if (!gfp)
              filstart = filp->Next;
            else
              gfp->Next = filp->Next;

            lfp = filp;              // last block of join filter list
          } else
            gfp = filp;              // last block of bad  filter list

          join = TRUE;
          fp = filp->Next;
          break;
        case OP_LOJ:
        case OP_ROJ:
        case OP_DTJ:
          join &= TRUE;
          break;
        case OP_EQ:
          if (join && k > 0       // So specific join operators come first
                   &&  filp->GetArgType(0) == TYPE_COLBLK
                   &&  filp->GetArgType(1) == TYPE_COLBLK) {
            cp1 = (PCOL)filp->Arg(0);
            cp2 = (PCOL)filp->Arg(1);
            tp1 = (PTDBASE)cp1->GetTo_Tdb();
            tp2 = (PTDBASE)cp2->GetTo_Tdb();

            if (tp1->GetTdb_No() != tp2->GetTdb_No()) {
              if (key)
                join &= (cp1->GetKey() == k || cp2->GetKey() == k);
              else
                join &= (tp1->GetColIndex(cp1) || tp2->GetColIndex(cp2));

            } else
              join = FALSE;

          } else
            join = FALSE;

          break;
        default:
          join = FALSE;
        } // endswitch filp->Opc

  if (key) {
    key = FALSE;
    goto again;
    } // endif key

  if (filjoin) {
    lfp->Next = filstart;
    filstart = filjoin;
    } // endif filjoin

  // Removing last separator is perhaps unuseful, but it was so
  return filstart->RemoveLastSep();
  } // end of SortJoin

/***********************************************************************/
/*  Check that this filter is a good join filter.                      */
/*  If so the opj block will be set accordingly.                       */
/*  opj points to the join block, fprec to the filter block to which   */
/*  the rest of the chain must be linked in case of success.           */
/*  teq, tek and tk2 indicates the severity of the tests:              */
/*  tk2 == TRUE means both columns must be primary keys.               */
/*  tc2 == TRUE means both args must be columns (not expression).      */
/*  tek == TRUE means at least one column must be a primary key.       */
/*  teq == TRUE means the filter operator must be OP_EQ.               */
/*  tix == TRUE means at least one column must be a simple index key.  */
/*  thx == TRUE means at least one column must be a leading index key. */
/***********************************************************************/
bool FILTER::FindJoinFilter(POPJOIN opj, PFIL fprec, bool teq, bool tek,
                            bool tk2, bool tc2, bool tix, bool thx)
  {
  if (trace(1))
    htrc("FindJoinFilter: opj=%p fprec=%p tests=(%d,%d,%d,%d)\n",
                          opj, fprec, teq, tek, tk2, tc2);

  // Firstly check that this filter is an independent filter
  // meaning that it is the only one in its own group.
  if (Next && Next->Opc != OP_SEP)
    return (Opc < 0);

  // Keep only equi-joins and specific joins (Outer and Distinct)
  // Normally specific join operators comme first because they have
  // been placed first by SortJoin.
  if (teq && Opc > OP_EQ)
    return FALSE;

  // We have a candidate for join filter, now check that it
  // fulfil the requirement about its operands, to point to
  // columns of respectively the two TDB's of that join.
  int    col1 = 0, col2 = 0;
  bool   key = tk2;
  bool   idx = FALSE, ihx = FALSE;
  PIXDEF pdx;

  for (int i = 0; i < 2; i++)
    if (GetArgType(i) == TYPE_COLBLK) {
      PCOL colp = (PCOL)Arg(i);

      if (tk2)
        key &= (colp->IsKey());
      else
        key |= (colp->IsKey());

      pdx = ((PTDBASE)colp->GetTo_Tdb())->GetColIndex(colp);
      idx |= (pdx && pdx->GetNparts() == 1);
      ihx |= (pdx != NULL);

      if      (colp->VerifyColumn(opj->GetTbx1()))
        col1 = i + 1;
      else if (colp->VerifyColumn(opj->GetTbx2()))
        col2 = i + 1;

    } else if (!tc2 && GetArgType(i) != TYPE_CONST) {
      PXOB xp = Arg(i);

      if      (xp->VerifyColumn(opj->GetTbx1()))
        col1 = i + 1;
      else if (xp->VerifyColumn(opj->GetTbx2()))
        col2 = i + 1;

    } else
      return (Opc < 0);

  if (col1 == 0 || col2 == 0)
    return (Opc < 0);

  if (((tek && !key) || (tix && !idx) || (thx && !ihx)) && Opc != OP_DTJ)
    return FALSE;

  // This is the join filter, set the join block.
  if (col1 == 1) {
    opj->SetCol1(Arg(0));
    opj->SetCol2(Arg(1));
  } else {
    opj->SetCol1(Arg(1));
    opj->SetCol2(Arg(0));

    switch (Opc) {
//    case OP_GT:  Opc = OP_LT;  break;
//    case OP_LT:  Opc = OP_GT;  break;
//    case OP_GE:  Opc = OP_LE;  break;
//    case OP_LE:  Opc = OP_GE;  break;
      case OP_LOJ:
      case OP_ROJ:
      case OP_DTJ:
        // For expended join operators, the filter must indicate
        // the way the join should be done, and not the order of
        // appearance of tables in the table list (which is kept
        // because tables are sorted in AddTdb). Therefore the
        // join is inversed, not the filter.
        opj->InverseJoin();
      default: break;
      } // endswitch Opc

  } // endif col1

  if (Opc < 0) {
    // For join operators, special processing is needed
    int  knum = 0;
    PFIL fp;

    switch (Opc) {
      case OP_LOJ:
        opj->SetJtype(JT_LEFT);
        knum = opj->GetCol2()->GetKey();
        break;
      case OP_ROJ:
        opj->SetJtype(JT_RIGHT);
        knum = opj->GetCol1()->GetKey();
        break;
      case OP_DTJ:
        for (knum = 1, fp = this->Next; fp; fp = fp->Next)
          if (fp->Opc == OP_DTJ)
            knum++;
          else if (fp->Opc != OP_SEP)
            break;

        opj->SetJtype(JT_DISTINCT);
        opj->GetCol2()->SetKey(knum);
        break;
      default:
        break;
      } // endswitch Opc

    if (knum > 1) {
      // Lets take care of a multiple key join
      // We do a minimum of checking here as it will done later
      int   k = 1;
      OPVAL op;
      BYTE  tmp[sizeof(Test[0])];

      for (fp = this->Next; k < knum && fp; fp = fp->Next) {
        switch (op = fp->Opc) {
          case OP_SEP:
            continue;
          case OP_LOJ:
            if (Opc == OP_ROJ) {
              op = Opc;
              memcpy(tmp, &fp->Test[0], sizeof(Test[0]));
              fp->Test[0] = fp->Test[1];
              memcpy(&fp->Test[1], tmp, sizeof(Test[0]));
              } // endif Opc

            k++;
            break;
          case OP_ROJ:
            if (Opc == OP_LOJ) {
              op = Opc;
              memcpy(tmp, &fp->Test[0], sizeof(Test[0]));
              fp->Test[0] = fp->Test[1];
              memcpy(&fp->Test[1], tmp, sizeof(Test[0]));
              } // endif Opc

            k++;
            break;
          case OP_DTJ:
            if (op == Opc && fp->GetArgType(1) == TYPE_COLBLK)
              ((PCOL)fp->Arg(1))->SetKey(knum);

            k++;
            break;
          default:
            break;
          } // endswitch op

        if (op != Opc)
          return TRUE;

        fp->Opc = OP_EQ;
        } // endfor fp

      } // endif k

    Opc = OP_EQ;
    } // endif Opc

  // Set the join filter operator
  opj->SetOpc(Opc);

  // Now mark the columns involved in the join filter because
  // this information will be used by the linearize program.
  // Note: this should be replaced in the future by something
  // enabling to mark tables as Parent or Child.
  opj->GetCol1()->MarkCol(U_J_EXT);
  opj->GetCol2()->MarkCol(U_J_EXT);

  // Remove the filter from the filter chain.  If the filter is
  // not last in the chain, also remove the SEP filter after it.
  if (Next)                 // Next->Opc == OP_SEP
    Next = Next->Next;

  if (!fprec)
    opj->SetFilter(Next);
  else
    fprec->Next = Next;

  return FALSE;
  } // end of FindJoinFilter

/***********************************************************************/
/*  CheckHaving: check and process a filter of an HAVING clause.       */
/*  Check references to Columns and Functions in the filter.           */
/*  All these references can correspond to items existing in the       */
/*  SELECT list, else if it is a function, allocate a SELECT block     */
/*  to be added to the To_Sel list (non projected blocks).             */
/***********************************************************************/
bool FILTER::CheckHaving(PGLOBAL g, PSQL sqlp)
  {
  int  agg = AGG_ANY;
  PXOB xp;

//sqlp->SetOk(TRUE);  // Ok to look into outer queries for filters

  switch (Opc) {
    case OP_SEP:
    case OP_AND:
    case OP_OR:
    case OP_NOT:
      return FALSE;
    default:
      if (CheckColumn(g, sqlp, xp, agg) < -1)
        return TRUE;       // Unrecovable error

      break;
    } // endswitch Opc

  sqlp->SetOk(TRUE);  // Ok to look into outer queries for filters

  for (int i = 0; i < 2; i++)
    if (!(xp = Arg(i)->SetSelect(g, sqlp, TRUE)))
      return TRUE;
    else if (xp != Arg(i)) {
      Arg(i) = xp;
      Val(i) = Arg(i)->GetValue();
      } // endif

  sqlp->SetOk(FALSE);
  return FALSE;
  } // end of CheckHaving

/***********************************************************************/
/*  Used while building a table index. This function split the filter  */
/*  attached to the tdbp table into the local and not local part.      */
/*  The local filter is used to restrict the size of the index and the */
/*  not local part remains to be executed later. This has been added   */
/*  recently and not only to improve the performance but chiefly to    */
/*  avoid loosing rows when processing distinct joins.                 */
/*  Returns:                                                           */
/*    0: the whole filter is local (both arguments are)                */
/*    1: the whole filter is not local                                 */
/*    2: the filter was split in local (attached to fp[0]) and         */
/*                           not local (attached to fp[1]).            */
/***********************************************************************/
int FILTER::SplitFilter(PFIL *fp)
  {
  int i, rc[2];

  if (Opc == OP_AND) {
    for (i = 0; i < 2; i++)
      rc[i] = ((PFIL)Arg(i))->SplitFilter(fp);

    // Filter first argument should never be split because of the
    // algorithm used to de-linearize the filter.
    assert(rc[0] != 2);

    if (rc[0] != rc[1]) {
      // Splitting to be done
      if (rc[1] == 2) {
        // 2nd argument already split, add 1st to the proper filter
        assert(fp[*rc]);
        Arg(1) = fp[*rc];
        Val(1) = fp[*rc]->GetValue();
        fp[*rc] = this;
      } else for (i = 0; i < 2; i++) {
        //  Split the filter arguments
        assert(!fp[rc[i]]);
        fp[rc[i]] = (PFIL)Arg(i);
        } // endfor i

      *rc = 2;
      } // endif rc

  } else
    *rc = (CheckLocal(NULL)) ? 0 : 1;

  return *rc;
  } // end of SplitFilter

/***********************************************************************/
/*  This function is called when making a Kindex after the filter was  */
/*  split in local and nolocal part in the case of many to many joins. */
/*  Indeed the whole filter must be reconstructed to take care of next */
/*  same values when doing the explosive join. In addition, the link   */
/*  must be done respecting the way filters are de-linearized, no AND  */
/*  filter in the first argument of an AND filter, because this is     */
/*  expected to be true if SplitFilter is used again on this filter.   */
/***********************************************************************/
PFIL FILTER::LinkFilter(PGLOBAL g, PFIL fp2)
  {
  PFIL fp1, filp, filand = NULL;

  assert(fp2);           // Test must be made by caller

  // Find where the new AND filter must be attached
  for (fp1 = this; fp1->Opc == OP_AND; fp1 = (PFIL)fp1->Arg(1))
    filand = fp1;

  filp = new(g) FILTER(g, OP_AND);
  filp->Arg(0) = fp1;
  filp->Val(0) = fp1->GetValue();
  filp->Test[0].B_T = TYPE_INT;
  filp->Test[0].Conv = FALSE;
  filp->Arg(1) = fp2;
  filp->Val(1) = fp2->GetValue();
  filp->Test[1].B_T = TYPE_INT;
  filp->Test[1].Conv = FALSE;
  filp->Value = AllocateValue(g, TYPE_INT);

  if (filand) {
    // filp must be inserted here
    filand->Arg(1) = filp;
    filand->Val(1) = filp->GetValue();
    filp = this;
    } // endif filand

  return filp;
  } // end of LinkFilter

/***********************************************************************/
/*  Checks whether filter contains reference to a previous table that  */
/*  is not logically joined to the currently openned table, or whether */
/*  it is a Sub-Select filter.  In any case, local is set to FALSE.    */
/*  Note: This function is now applied to de-linearized filters.       */
/***********************************************************************/
bool FILTER::CheckLocal(PTDB tdbp)
  {
  bool local = TRUE;

  if (trace(1)) {
    if (tdbp)
      htrc("CheckLocal: filp=%p R%d\n", this, tdbp->GetTdb_No());
    else
      htrc("CheckLocal: filp=%p\n", this);
    } // endif trace

  for (int i = 0; local && i < 2; i++)
    local = Arg(i)->CheckLocal(tdbp);

  if (trace(1))
    htrc("FCL: returning %d\n", local);

  return (local);
  } // end of CheckLocal

/***********************************************************************/
/*  This routine is used to split the filter attached to the tdbp      */
/*  table into the local and not local part where "local" means that   */
/*  it applies "locally" to the FILEID special column with crit = 2    */
/*  and to the SERVID and/or TABID special columns with crit = 3.      */
/*  Returns:                                                           */
/*    0: the whole filter is local (both arguments are)                */
/*    1: the whole filter is not local                                 */
/*    2: the filter was split in local (attached to fp[0]) and         */
/*                           not local (attached to fp[1]).            */
/*  Note: "Locally" means that the "local" filter can be evaluated     */
/*  before opening the table. This implies that the special column be  */
/*  compared only with constants and that this filter not to be or'ed  */
/*  with a non "local" filter.                                         */
/***********************************************************************/
int FILTER::SplitFilter(PFIL *fp, PTDB tp, int crit)
  {
  int i, rc[2];

  if (Opc == OP_AND) {
    for (i = 0; i < 2; i++)
      rc[i] = ((PFIL)Arg(i))->SplitFilter(fp, tp, crit);

    // Filter first argument should never be split because of the
    // algorithm used to de-linearize the filter.
    assert(rc[0] != 2);

    if (rc[0] != rc[1]) {
      // Splitting to be done
      if (rc[1] == 2) {
        // 2nd argument already split, add 1st to the proper filter
        assert(fp[*rc]);
        Arg(1) = fp[*rc];
        Val(1) = fp[*rc]->GetValue();
        fp[*rc] = this;
      } else for (i = 0; i < 2; i++) {
        //  Split the filter arguments
        assert(!fp[rc[i]]);
        fp[rc[i]] = (PFIL)Arg(i);
        } // endfor i

      *rc = 2;
      } // endif rc

  } else
    *rc = (CheckSpcCol(tp, crit) == 1) ? 0 : 1;

  return *rc;
  } // end of SplitFilter

/***********************************************************************/
/*  Checks whether filter contains only references to FILEID, SERVID,  */
/*  or TABID with constants or pseudo constants.                       */
/***********************************************************************/
int FILTER::CheckSpcCol(PTDB tdbp, int n)
  {
  int n1 = Arg(0)->CheckSpcCol(tdbp, n);
  int n2 = Arg(1)->CheckSpcCol(tdbp, n);

  return max(n1, n2);
  } // end of CheckSpcCol
#endif // 0

/***********************************************************************/
/*  Reset the filter arguments to non evaluated yet.                   */
/***********************************************************************/
void FILTER::Reset(void)
  {
  for (int i = 0; i < 2; i++)
    Arg(i)->Reset();

  } // end of Reset

/***********************************************************************/
/*  Init: called when reinitializing a query (Correlated subqueries)   */
/***********************************************************************/
bool FILTER::Init(PGLOBAL g)
  {
  for (int i = 0; i < 2; i++)
    Arg(i)->Init(g);

  return FALSE;
  } // end of Init

/***********************************************************************/
/*  Convert:  does all filter setting and conversions.                 */
/*   (having = TRUE for Having Clauses, FALSE for Where Clauses)       */
/*      Note: hierarchy of types is implied by the ConvertType         */
/*      function, currently FLOAT, int, STRING and TOKEN.              */
/*  Returns FALSE if successful or TRUE in case of error.              */
/*  Note on result type for filters:                                   */
/*    Currently the result type is of TYPE_INT (should be TYPE_BOOL).  */
/*    This avoids to introduce a new type and perhaps will permit      */
/*    conversions. However the boolean operators will result in a      */
/*    boolean int result, meaning that result shall be only 0 or 1  .  */
/***********************************************************************/
bool FILTER::Convert(PGLOBAL g, bool having)
  {
  int i, comtype = TYPE_ERROR;

  if (trace(1))
    htrc("converting(?) %s %p opc=%d\n",
          (having) ? "having" : "filter", this, Opc);

  for (i = 0; i < 2; i++) {
    switch (GetArgType(i)) {
      case TYPE_COLBLK:
        if (((PCOL)Arg(i))->InitValue(g))
          return TRUE;

        break;
      case TYPE_ARRAY:
        if ((Opc != OP_IN && !Opm) || i == 0) {
          strcpy(g->Message, MSG(BAD_ARRAY_OPER));
          return TRUE;
          } // endif

        if (((PARRAY)Arg(i))->Sort(g))  // Sort the array
          return TRUE;                  // Error

        break;
      case TYPE_VOID:
        if (i == 1) {
          Val(0) = Arg(0)->GetValue();
          goto TEST;             // Filter has only one argument
          } // endif i

        strcpy(g->Message, MSG(VOID_FIRST_ARG));
        return TRUE;
      } // endswitch

    if (trace(1))
      htrc("Filter(%d): Arg type=%d\n", i, GetArgType(i));

    // Set default values
    Test[i].B_T = Arg(i)->GetResultType();
    Test[i].Conv = FALSE;

    // Special case of the LIKE operator.
    if (Opc == OP_LIKE) {
      if (!IsTypeChar((int)Test[i].B_T)) {
        sprintf(g->Message, MSG(BAD_TYPE_LIKE), i, Test[i].B_T);
        return TRUE;
        } // endif

      comtype = TYPE_STRING;
    } else {
      // Set the common type for both (eventually converted) arguments
      int argtyp = Test[i].B_T;

      if (GetArgType(i) == TYPE_CONST && argtyp == TYPE_INT) {
        // If possible, downcast the type to smaller types to avoid
        // convertion as much as possible.
        int n = Arg(i)->GetValue()->GetIntValue();

        if (n >= INT_MIN8 && n <= INT_MAX8)
          argtyp = TYPE_TINY;
        else if (n >= INT_MIN16 && n <= INT_MAX16)
          argtyp = TYPE_SHORT;

      } else if (GetArgType(i) == TYPE_ARRAY) {
        // If possible, downcast int arrays target type to TYPE_SHORT
        // to take  care of filters written like shortcol in (34,35,36).
        if (((PARRAY)Arg(i))->CanBeShort())
          argtyp = TYPE_SHORT;

      } // endif TYPE_CONST

      comtype = ConvertType(comtype, argtyp, CNV_ANY);
    } // endif Opc

    if (comtype == TYPE_ERROR) {
      strcpy(g->Message, MSG(ILL_FILTER_CONV));
      return TRUE;
      } // endif

    if (trace(1))
      htrc(" comtype=%d, B_T(%d)=%d Val(%d)=%p\n",
             comtype, i, Test[i].B_T, i, Val(i));

    } // endfor i

  // Set or allocate the filter argument values and buffers
  for (i = 0; i < 2; i++) {
    if (trace(1))
      htrc(" conv type %d ? i=%d B_T=%d comtype=%d\n",
            GetArgType(i), i, Test[i].B_T, comtype);

    if (Test[i].B_T == comtype) {
      // No conversion, set Value to argument Value
      Val(i) = Arg(i)->GetValue();
#if defined(_DEBUG)
      assert (Val(i) && Val(i)->GetType() == Test[i].B_T);
#endif
    } else {
      //  Conversion between filter arguments to be done.
      //  Note that the argument must be converted, not only the
      //  buffer and buffer type, so GetArgType() returns the new type.
      switch (GetArgType(i)) {
        case TYPE_CONST:
          if (comtype == TYPE_DATE && Test[i].B_T == TYPE_STRING) {
            // Convert according to the format of the other argument
            Val(i) = AllocateValue(g, comtype, Arg(i)->GetLength());

            if (((DTVAL*)Val(i))->SetFormat(g, Val(1-i)))
              return TRUE;

            Val(i)->SetValue_psz(Arg(i)->GetValue()->GetCharValue());
          } else {
            ((PCONST)Arg(i))->Convert(g, comtype);
            Val(i) = Arg(i)->GetValue();
          } // endif comtype

          break;
        case TYPE_ARRAY:
          // Conversion PSZ or int array to int or double FLOAT.
          if (((PARRAY)Arg(i))->Convert(g, comtype, Val(i-1)) == TYPE_ERROR)
            return TRUE;

          break;
        case TYPE_FILTER:
          strcpy(g->Message, MSG(UNMATCH_FIL_ARG));
          return TRUE;
        default:
          // Conversion from Column, Select/Func, Expr, Scalfnc...
          // The argument requires conversion during Eval
          // A separate Value block must be allocated.
          // Note: the test on comtype is to prevent unnecessary
          // domain initialization and get the correct length in
          // case of Token -> numeric conversion.
          Val(i) = AllocateValue(g, comtype, (comtype == TYPE_STRING)
                 ? Arg(i)->GetLengthEx() : Arg(i)->GetLength());

          if (comtype == TYPE_DATE && Test[i].B_T == TYPE_STRING)
            // Convert according to the format of the other argument
            if (((DTVAL*)Val(i))->SetFormat(g, Val(1 - i)))
              return TRUE;

          Test[i].Conv = TRUE;
          break;
        } // endswitch GetType

      Test[i].B_T = comtype;
    } // endif comtype

    } // endfor i

  //  Last check to be sure all is correct.
  if (Test[0].B_T != Test[1].B_T) {
    sprintf(g->Message, MSG(BAD_FILTER_CONV), Test[0].B_T, Test[1].B_T);
    return TRUE;
//} else if (Test[0].B_T == TYPE_LIST &&
//          ((LSTVAL*)Val(0))->GetN() != ((LSTVAL*)Val(1))->GetN()) {
//  sprintf(g->Message, MSG(ROW_ARGNB_ERR),
//          ((LSTVAL*)Val(0))->GetN(), ((LSTVAL*)Val(1))->GetN());
//  return TRUE;
  } // endif's B_T


 TEST: // Test for possible Eval optimization

  if (trace(1))
    htrc("Filp %p op=%d argtypes=(%d,%d)\n",
          this, Opc, GetArgType(0), GetArgType(1));

  // Check whether we have a "simple" filter and in that case
  // change its class so an optimized Eval function will be used
  if (!Test[0].Conv && !Test[1].Conv) {
    if (Opm) switch (Opc) {
      case OP_EQ:
      case OP_NE:
      case OP_GT:
      case OP_GE:
      case OP_LT:
      case OP_LE:
        if (GetArgType(1) != TYPE_ARRAY)
          break;      // On subquery, do standard processing

        // Change the FILTER class to FILTERIN
        new(this) FILTERIN;
        break;
      default:
        break;
      } // endswitch Opc

    else switch (Opc) {
#if 0
      case OP_EQ:  new(this) FILTEREQ;  break;
      case OP_NE:  new(this) FILTERNE;  break;
      case OP_GT:  new(this) FILTERGT;  break;
      case OP_GE:  new(this) FILTERGE;  break;
      case OP_LT:  new(this) FILTERLT;  break;
      case OP_LE:  new(this) FILTERLE;  break;
#endif // 0
      case OP_EQ:
      case OP_NE:
      case OP_GT:
      case OP_GE:
      case OP_LT:
      case OP_LE:  new(this) FILTERCMP(g); break;
      case OP_AND: new(this) FILTERAND; break;
      case OP_OR:  new(this) FILTEROR;  break;
      case OP_NOT: new(this) FILTERNOT; break;
      case OP_EXIST:
        if (GetArgType(1) == TYPE_VOID) {
          // For EXISTS it is the first argument that should be null
          Arg(1) = Arg(0);
          Arg(0) = pXVOID;
          } // endif void

        // fall through
      case OP_IN:
        // For IN operator do optimize if operand is an array
        if (GetArgType(1) != TYPE_ARRAY)
          break;      // IN on subquery, do standard processing

        // Change the FILTER class to FILTERIN
        new(this) FILTERIN;
        break;
      default:
        break;
      } // endswitch Opc

    } // endif Conv

  // The result value (should be TYPE_BOOL ???)
  Value = AllocateValue(g, TYPE_INT);
  return FALSE;
  } // end of Convert

/***********************************************************************/
/*  Eval: Compute filter result value.                                 */
/*  New algorithm: evaluation is now done from the root for each group */
/*  so Eval is now a recursive process for FILTER operands.            */
/***********************************************************************/
bool FILTER::Eval(PGLOBAL g)
  {
  int     i; // n = 0;
//PSUBQ   subp = NULL;
  PARRAY  ap = NULL;
  PDBUSER dup = PlgGetUser(g);

  if (Opc <= OP_XX)
    for (i = 0; i < 2; i++)
      // Evaluate the object and eventually convert it.
      if (Arg(i)->Eval(g))
        return TRUE;
      else if (Test[i].Conv)
        Val(i)->SetValue_pval(Arg(i)->GetValue());

  if (trace(1))
    htrc(" Filter: op=%d type=%d %d B_T=%d %d val=%p %p\n",
          Opc, GetArgType(0), GetArgType(1), Test[0].B_T, Test[1].B_T,
          Val(0), Val(1));

  // Main switch on filtering according to operator type.
  switch (Opc) {
    case OP_EQ:
    case OP_NE:
    case OP_GT:
    case OP_GE:
    case OP_LT:
    case OP_LE:
      if (!Opm) {
        //  Comparison boolean operators.
#if defined(_DEBUG)
        if (Val(0)->GetType() != Val(1)->GetType())
          goto FilterError;
#endif
        // Compare the two arguments
        // New algorithm to take care of TYPE_LIST
        Bt = OpBmp(g, Opc);
        Value->SetValue_bool(!(Val(0)->TestValue(Val(1)) & Bt));
        break;
        } // endif Opm

      // For modified operators, pass thru
      /* fall through */
    case OP_IN:
    case OP_EXIST:
      // For IN operations, special processing is done here
      switch (GetArgType(1)) {
        case TYPE_ARRAY:
          ap = (PARRAY)Arg(1);
          break;
        default:
          strcpy(g->Message, MSG(IN_WITHOUT_SUB));
          goto FilterError;
        } // endswitch Type

      if (trace(1)) {
        htrc(" IN filtering: ap=%p\n", ap);

        if (ap)
          htrc(" Array: type=%d size=%d other_type=%d\n",
                ap->GetType(), ap->GetSize(), Test[0].B_T);

        } // endif trace

      /*****************************************************************/
      /*  Implementation note: The Find function is now able to do a   */
      /*  conversion but limited to SHORT, int, and FLOAT arrays.     */
      /*****************************************************************/
//    Value->SetValue_bool(ap->Find(g, Val(0)));

      if (ap)
        Value->SetValue_bool(ap->FilTest(g, Val(0), Opc, Opm));

      break;

    case OP_LIKE:
#if defined(_DEBUG)
      if (!IsTypeChar((int)Test[0].B_T) || !IsTypeChar((int)Test[1].B_T))
        goto FilterError;
#endif
      if (Arg(0)->Eval(g))
        return TRUE;

      Value->SetValue_bool(PlugEvalLike(g, Val(0)->GetCharValue(),
                                           Val(1)->GetCharValue(),
                                           Val(0)->IsCi()));
      break;

    case OP_AND:
#if defined(_DEBUG)
      if (Test[0].B_T != TYPE_INT || Test[1].B_T != TYPE_INT)
        goto FilterError;
#endif

      if (Arg(0)->Eval(g))
        return TRUE;

      Value->SetValue(Val(0)->GetIntValue());

      if (!Value->GetIntValue())
        return FALSE;   // No need to evaluate 2nd argument

      if (Arg(1)->Eval(g))
        return TRUE;

      Value->SetValue(Val(1)->GetIntValue());
      break;

    case OP_OR:
#if defined(_DEBUG)
      if (Test[0].B_T != TYPE_INT || Test[1].B_T != TYPE_INT)
        goto FilterError;
#endif

      if (Arg(0)->Eval(g))
        return TRUE;

      Value->SetValue(Val(0)->GetIntValue());

      if (Value->GetIntValue())
        return FALSE;   // No need to evaluate 2nd argument

      if (Arg(1)->Eval(g))
        return TRUE;

      Value->SetValue(Val(1)->GetIntValue());
      break;

    case OP_NOT:
#if defined(_DEBUG)
      if (Test[0].B_T != TYPE_INT)      // Should be type bool ???
        goto FilterError;
#endif

      if (Arg(0)->Eval(g))
        return TRUE;

      Value->SetValue_bool(!Val(0)->GetIntValue());
      break;

    case OP_SEP:   // No more used while evaluating
    default:
      goto FilterError;
    } // endswitch Opc

  if (trace(1))
    htrc("Eval: filter %p Opc=%d result=%d\n",
                this, Opc, Value->GetIntValue());

  return FALSE;

 FilterError:
  sprintf(g->Message, MSG(BAD_FILTER),
          Opc, Test[0].B_T, Test[1].B_T, GetArgType(0), GetArgType(1));
  return TRUE;
  } // end of Eval

#if 0
/***********************************************************************/
/*  Called by PlugCopyDB to make a copy of a (linearized) filter chain.*/
/***********************************************************************/
PFIL FILTER::Copy(PTABS t)
  {
  int  i;
  PFIL fil1, fil2, newfilchain = NULL, fprec = NULL;

  for (fil1 = this; fil1; fil1 = fil1->Next) {
    fil2 = new(t->G) FILTER(fil1);

    if (!fprec)
      newfilchain = fil2;
    else
      fprec->Next = fil2;

    NewPointer(t, fil1, fil2);

    for (i = 0; i < 2; i++)
      if (fil1->GetArgType(i) == TYPE_COLBLK ||
          fil1->GetArgType(i) == TYPE_FILTER)
        AddPointer(t, &fil2->Arg(i));

    fprec = fil2;
    } /* endfor fil1 */

  return newfilchain;
  } // end of Copy
#endif // 0

/*********************************************************************/
/*  Make file output of FILTER contents.                             */
/*********************************************************************/
void FILTER::Printf(PGLOBAL g, FILE *f, uint n)
  {
  char m[64];

  memset(m, ' ', n);                    // Make margin string
  m[n] = '\0';

  bool lin = (Next != NULL);            // lin == TRUE if linearized

  for (PFIL fp = this; fp; fp = fp->Next) {
    fprintf(f, "%sFILTER: at %p opc=%d lin=%d result=%d\n",
            m, fp, fp->Opc, lin,
            (Value) ? Value->GetIntValue() : 0);

    for (int i = 0; i < 2; i++) {
      fprintf(f, "%s Arg(%d) type=%d value=%p B_T=%d val=%p\n",
              m, i, fp->GetArgType(i), fp->Arg(i),
                    fp->Test[i].B_T, fp->Val(i));

      if (lin && fp->GetArgType(i) == TYPE_FILTER)
        fprintf(f, "%s  Filter at %p\n", m, fp->Arg(i));
      else
        fp->Arg(i)->Printf(g, f, n + 2);

      } // endfor i

    } // endfor fp

  } // end of Printf

/***********************************************************************/
/*  Make string output of TABLE contents (z should be checked).        */
/***********************************************************************/
void FILTER::Prints(PGLOBAL g, char *ps, uint z)
  {
  #define FLEN 100

  typedef struct _bc {
    struct _bc *Next;
    char   Cold[FLEN+1];
    } BC, *PBC;

  char *p;
  int   n;
  PFIL  fp;
  PBC   bxp, bcp = NULL;

  *ps = '\0';

  for (fp = this; fp && z > 0; fp = fp->Next) {
    if (fp->Opc < OP_CNC || fp->Opc == OP_IN || fp->Opc == OP_NULL
                         || fp->Opc == OP_LIKE || fp->Opc == OP_EXIST) {
      if (!(bxp = new BC)) {
        strncat(ps, "Filter(s)", z);
        return;
        } /* endif */

      bxp->Next = bcp;
      bcp = bxp;
      p = bcp->Cold;
      n = FLEN;
      fp->Arg(0)->Prints(g, p, n);
      n = FLEN - strlen(p);

      switch (fp->Opc) {
        case OP_EQ:
          strncat(bcp->Cold, "=", n);
          break;
        case OP_NE:
          strncat(bcp->Cold, "!=", n);
          break;
        case OP_GT:
          strncat(bcp->Cold, ">", n);
          break;
        case OP_GE:
          strncat(bcp->Cold, ">=", n);
          break;
        case OP_LT:
          strncat(bcp->Cold, "<", n);
          break;
        case OP_LE:
          strncat(bcp->Cold, "<=", n);
          break;
        case OP_IN:
          strncat(bcp->Cold, " in ", n);
          break;
        case OP_NULL:
          strncat(bcp->Cold, " is null", n);
          break;
        case OP_LIKE:
          strncat(bcp->Cold, " like ", n);
          break;
        case OP_EXIST:
          strncat(bcp->Cold, " exists ", n);
          break;
        case OP_AND:
          strncat(bcp->Cold, " and ", n);
          break;
        case OP_OR:
          strncat(bcp->Cold, " or ", n);
          break;
        default:
          strncat(bcp->Cold, "?", n);
        } // endswitch Opc

      n = FLEN - strlen(p);
      p += strlen(p);
      fp->Arg(1)->Prints(g, p, n);
    } else
      if (!bcp) {
        strncat(ps, "???", z);
        z -= 3;
      } else
        switch (fp->Opc) {
          case OP_SEP:                    // Filter list separator
            strncat(ps, bcp->Cold, z);
            z -= strlen(bcp->Cold);
            strncat(ps, ";", z--);
            bxp = bcp->Next;
            delete bcp;
            bcp = bxp;
            break;
          case OP_NOT:                    // Filter NOT operator
            for (n = MY_MIN((int)strlen(bcp->Cold), FLEN-3); n >= 0; n--)
              bcp->Cold[n+2] = bcp->Cold[n];
            bcp->Cold[0] = '^';
            bcp->Cold[1] = '(';
            strcat(bcp->Cold, ")");
            break;
          default:
            for (n = MY_MIN((int)strlen(bcp->Cold), FLEN-4); n >= 0; n--)
              bcp->Cold[n+3] = bcp->Cold[n];
            bcp->Cold[0] = ')';
            switch (fp->Opc) {
              case OP_AND: bcp->Cold[1] = '&'; break;
              case OP_OR:  bcp->Cold[1] = '|'; break;
              default: bcp->Cold[1] = '?';
              } // endswitch
            bcp->Cold[2] = '(';
            strcat(bcp->Cold, ")");
            bxp = bcp->Next;
            for (n = MY_MIN((int)strlen(bxp->Cold), FLEN-1); n >= 0; n--)
              bxp->Cold[n+1] = bxp->Cold[n];
            bxp->Cold[0] = '(';
            strncat(bxp->Cold, bcp->Cold, FLEN-strlen(bxp->Cold));
            delete bcp;
            bcp = bxp;
          } // endswitch

    } // endfor fp

  n = 0;

  if (!bcp)
    strncat(ps, "Null-Filter", z);
  else do {
    if (z > 0) {
      if (n++ > 0) {
        strncat(ps, "*?*", z);
        z = MY_MAX(0, (int)z-3);
        } // endif
      strncat(ps, bcp->Cold, z);
      z -= strlen(bcp->Cold);
      } // endif

    bxp = bcp->Next;
    delete bcp;
    bcp = bxp;
    } while (bcp); // enddo

  } // end of Prints


/* -------------------- Derived Classes Functions -------------------- */

/***********************************************************************/
/*  FILTERCMP constructor.                                             */
/***********************************************************************/
FILTERCMP::FILTERCMP(PGLOBAL g)
  {
  Bt = OpBmp(g, Opc);
  } // end of FILTERCMP constructor

/***********************************************************************/
/*  Eval: Compute result value for comparison operators.               */
/***********************************************************************/
bool FILTERCMP::Eval(PGLOBAL g)
  {
  if (Arg(0)->Eval(g) || Arg(1)->Eval(g))
    return TRUE;

  Value->SetValue_bool(!(Val(0)->TestValue(Val(1)) & Bt));
  return FALSE;
  } // end of Eval

/***********************************************************************/
/*  Eval: Compute result value for AND filters.                        */
/***********************************************************************/
bool FILTERAND::Eval(PGLOBAL g)
  {
  if (Arg(0)->Eval(g))
    return TRUE;

  Value->SetValue(Val(0)->GetIntValue());

  if (!Value->GetIntValue())
    return FALSE;   // No need to evaluate 2nd argument

  if (Arg(1)->Eval(g))
    return TRUE;

  Value->SetValue(Val(1)->GetIntValue());
  return FALSE;
  } // end of Eval

/***********************************************************************/
/*  Eval: Compute result value for OR filters.                         */
/***********************************************************************/
bool FILTEROR::Eval(PGLOBAL g)
  {
  if (Arg(0)->Eval(g))
    return TRUE;

  Value->SetValue(Val(0)->GetIntValue());

  if (Value->GetIntValue())
    return FALSE;   // No need to evaluate 2nd argument

  if (Arg(1)->Eval(g))
    return TRUE;

  Value->SetValue(Val(1)->GetIntValue());
  return FALSE;
  } // end of Eval

/***********************************************************************/
/*  Eval: Compute result value for NOT filters.                        */
/***********************************************************************/
bool FILTERNOT::Eval(PGLOBAL g)
  {
  if (Arg(0)->Eval(g))
    return TRUE;

  Value->SetValue_bool(!Val(0)->GetIntValue());
  return FALSE;
  } // end of Eval

/***********************************************************************/
/*  Eval: Compute result value for IN filters.                         */
/***********************************************************************/
bool FILTERIN::Eval(PGLOBAL g)
  {
  if (Arg(0)->Eval(g))
    return TRUE;

  Value->SetValue_bool(((PARRAY)Arg(1))->FilTest(g, Val(0), Opc, Opm));
  return FALSE;
  } // end of Eval

/***********************************************************************/
/*  FILTERTRUE does nothing and returns TRUE.                          */
/***********************************************************************/
void FILTERTRUE::Reset(void)
  {
  } // end of Reset

bool FILTERTRUE::Eval(PGLOBAL)
  {
  return FALSE;
  } // end of Eval

/* ------------------------- Friend Functions ------------------------ */

#if 0
/***********************************************************************/
/*  Prepare: prepare a filter for execution. This implies two things:  */
/*  1) de-linearize the filter to be able to evaluate it recursively.  */
/*     This permit to conditionally evaluate only the first argument   */
/*     of OP_OR and OP_AND filters without having to pass by an        */
/*     intermediate Apply function (as this has a performance cost).   */
/*  2) do all the necessary conversion for all filter block arguments. */
/***********************************************************************/
PFIL PrepareFilter(PGLOBAL g, PFIL fp, bool having)
  {
  PFIL filp = NULL;

  if (trace(1))
    htrc("PrepareFilter: fp=%p having=%d\n", fp, having);

  while (fp) {
    if (fp->Opc == OP_SEP)
      // If separator is not last transform it into an AND filter
      if (fp->Next) {
        filp = PrepareFilter(g, fp->Next, having);
        fp->Arg(1) = filp;
        fp->Opc = OP_AND;
        fp->Next = NULL;     // This will end the loop
      } else
        break;  // Remove eventual ending separator(s)

//  if (fp->Convert(g, having))
//			throw (int)TYPE_FILTER;

    filp = fp;
    fp = fp->Next;
    filp->Next = NULL;
    } // endwhile

  if (trace(1))
    htrc(" returning filp=%p\n", filp);

  return filp;
  } // end of PrepareFilter
#endif // 0

/***********************************************************************/
/*  ApplyFilter: Apply filtering for a table (where or having clause). */
/*  New algorithm: evaluate from the root a de-linearized filter so    */
/*  AND/OR clauses can be optimized throughout the whole tree.         */
/***********************************************************************/
DllExport bool ApplyFilter(PGLOBAL g, PFIL filp)
  {
  if (!filp)
    return TRUE;

  // Must be done for null tables
  filp->Reset();

//if (tdbp && tdbp->IsNull())
//  return TRUE;

  if (filp->Eval(g))
		throw (int)TYPE_FILTER;

  if (trace(2))
    htrc("PlugFilter filp=%p result=%d\n",
                     filp, filp->GetResult());

  return filp->GetResult();
  } // end of ApplyFilter
