void MVCandidate::logMVWasDisqualified(const char* reason)
{
  // Avoid duplicate messages.
  if (wasDisqualified_)
    return;
  wasDisqualified_ = TRUE;

  char buffer[1024];
  const MVDetailsPtr mvDetails = getMvDetails();
  sprintf(buffer, "MV %s was disqualified because %s",
	  mvDetails->getMVName().data(), reason);
  QRLogger::log(CAT_MVCAND, LL_INFO, buffer);

  disqualifiedReason_ = new (heap_) NAString(buffer, heap_);
}
MVCandidatesForJBBSubsetPtr QRGroupLattice::search(QRJBBPtr	           queryJbb, 
						   MVCandidatesForJBBPtr   mvCandidates, 
						   QRJoinSubGraphMapPtr    map,
                                                   ElementPtrList*         minimizedGroupingList)
{
  QRTRACER("QRGroupLattice::search()");
  DescriptorDetailsPtr queryDetails = mvCandidates->getAllCandidates()->getQueryDetails();
  LatticeKeyList keyList(heap_);

  if (minimizedGroupingList)
  {
    if (!elementListToKeyList(*minimizedGroupingList, keyList, map, queryDetails, FALSE))
      return NULL;
  }
  else
  {
    // Get the grouping columns of the passed query JBB, and create a key list
    // from them. Return now if there are no grouping items -- a query without a
    // Group By can't match an MV that has one.
    QRGroupByPtr groupBy = queryJbb->getGroupBy();
    if (!groupBy)
      return NULL;

    // An aggregate query with no GroupBy has an empty key list.
    if (!getGroupingLatticeKeys(keyList, map, queryDetails, queryJbb, TRUE, FALSE))
      return NULL;
  }

  // Create the search node, disallowing the addition of any new keys to the hash
  // table used by the lattice; if the key isn't already used, the node can have
  // no supersets.
  QRLatticeIndexSearchNode searchNode(keyList, lattice_, TRUE, heap_);
  if (!searchNode.isValid())   // because a key wasn't added
    return NULL;

  NAString keysText;
  searchNode.dumpNode(keysText, *lattice_->getKeyArr());
  QRLogger::log(CAT_GRP_LATTCE_INDX, LL_DEBUG, 
    "Searching LatticeIndex for: %s.", keysText.data());

  // Find the supersets of the query's group-by list in the MV group lattice
  // index. For each lattice index node returned, create an MVCandidate
  // corresponding to each MV represented by that node, and add it to the
  // candidate list passed by the caller.
  NAPtrList<QRLatticeIndexNodePtr> nodes;
  lattice_->findSupersets(searchNode, nodes);

  if (nodes.entries() == 0)
  {
    QRLogger::log(CAT_GRP_LATTCE_INDX, LL_DEBUG, "No match found.");
    return NULL;
  }

  MVCandidatesForJBBSubsetPtr jbbSubset = new(heap_) 
    MVCandidatesForJBBSubset(mvCandidates, ADD_MEMCHECK_ARGS(heap_));
  jbbSubset->setSubGraphMap(map);
  jbbSubset->setGroupBy(TRUE);

  if (minimizedGroupingList != NULL)
    jbbSubset->setMinimizedGroupingList(minimizedGroupingList);

  for (CollIndex i=0; i<nodes.entries(); i++)
    {
      QRLatticeIndexNodePtr thisNode = nodes[i];
      const SubGraphMapList& matchingMVs = thisNode->getMVs();
      NABoolean isPreferred = (*thisNode == searchNode); // preferred if exact match

      GroupingList* extraGroupingColumns = new(heap_) GroupingList(heap_);
      if (!isPreferred)
      {
	// For preferred match modes, there are no extraGroupingColumns.
	LatticeKeySubArray* diff = thisNode->computeDiff(searchNode, heap_);
	for(CollIndex i = 0; diff->nextUsed(i); i++)
	{
          LatticeIndexablePtr key = diff->element(i);
          QRElementPtr elem = keyToElement(key, map);
	  extraGroupingColumns->insert(elem);
	}
	delete diff;
      }

      for (CollIndex j=0; j<matchingMVs.entries(); j++)
        {
	  MVDetailsPtr mv = matchingMVs[j]->getMVDetails();
          QRLogger::log(CAT_GRP_LATTCE_INDX, LL_DEBUG, 
            "Found MV: %s!", mv->getMVName().data() );
	  jbbSubset->insert(mv, queryJbb, isPreferred, extraGroupingColumns, NULL, heap_);
        }
    }

  return jbbSubset;

}  // QRGroupLattice::search()
void MVCandidatesForJBBSubset::insert(MVDetailsPtr	    mv, 
				      QRJBBPtr		    queryJbb,
				      NABoolean		    isPreferredMatch, 
				      GroupingList*	    extraGroupingColumns,
				      QRJoinSubGraphMapPtr  mvMap,
				      CollHeap*		    heap)
{
  DescriptorDetailsPtr queryDetails =  jbbCandidates_->getAllCandidates()->getQueryDetails();
  NAString excuse(heap);
  if (mv->isInitialized() == FALSE)
  {
    // Disqualify uninitialized MVs
    excuse = " was skipped because it is not initialized";
  }
  else
  {
    QRDescriptorPtr desc = queryDetails->getDescriptor();
    QRQueryDescriptorPtr queryDesc = static_cast<QRQueryDescriptorPtr>(desc);
    QRQueryMiscPtr misc = queryDesc->getMisc();
    const NAString& mvAge = misc->getMVAge();
    
    switch (misc->getRewriteLevel())
    {
      case MRL_FRESH:
	// Allow only ON STATEMENT MVs.
	if (mv->isImmediate() == FALSE)
	{
	  // Disqualify stale MVs.
	  excuse = " was skipped because it is not completey fresh";
	}
	break;
	      
      case MRL_STALE:
	if (mv->isImmediate())
	{
	  // Allow immediate MVs. No need to calculate MV_AGE.
	  break;
	}

	if ( mv->isUMV() )
	{
	  // Disqualify UMVs.
	  excuse = " was skipped because it is user maintained";
	  break;
	}

	// Allow all MVs that were refreshed at least MV_AGE  ago
	if ( mvAge != "")
	{
	  const Int32 mvAgeSeconds = parseMVAge(mvAge);
          const Int64 mvRefreshTS = mv->getRefreshTimestamp()/1000000;
	  const Int64 nowTS = NA_JulianTimestamp()/1000000;
	  Int32 refreshAge = (Int32)(nowTS - mvRefreshTS);
          QRLogger::log(CAT_MVCAND, LL_DEBUG,
            "MV_AGE is: %d seconds, RefreshAge is: %d seconds.", mvAgeSeconds, refreshAge);
	  if (mvAgeSeconds < refreshAge)
	  {
	    // Disqualify stale MVs.
	    excuse = " was skipped because it is stale.";
	  }
	}
	break;
	      
      case MRL_OLD:
	if ( mv->isUMV() )
	{
	  // Disqualify UMVs.
	  excuse = " was skipped because it is user maintained";
	}
	break;

      case MRL_UMVS:
        // All MVs are allowed here.
        break;
	      
      case MRL_OFF:
        // MVQR is OFF. Not supposed to get a descriptor in this case.
      default:
        assertLogAndThrow1(CAT_MVCAND, LL_ERROR, 
  	                   FALSE, QRLogicException, 
	                   "Invalid value for MVQR_REWRITE_LEVEL: %d", (Int32)misc->getRewriteLevel());
    }
  }
  
  // Create a new MVCandidate object for it.
  MVCandidatePtr newCandidate = new(heap) 
    MVCandidate(mv, queryDetails, queryJbb, this, ADD_MEMCHECK_ARGS(heap));

  if (mvMap != NULL)
    newCandidate->setMvSubGraphMap(mvMap);

  newCandidate->init(isPreferredMatch, extraGroupingColumns);

  insert(newCandidate);
  
  if (excuse != "")
  {
    // The reason we insert the MVCandidate and then disqualify it is that
    // we want the disqualification message to be added to the result descriptor.
    NAString msg(heap);
    msg = "MV ";
    msg.append(mv->getMVName());
    msg.append(excuse);
    newCandidate->logMVWasDisqualified(msg);
    newCandidate->disqualify();
  }
}  // MVCandidatesForJBBSubset::insert()