public String toSanitizedtring() throws ServiceException {
   StringBuilder out = new StringBuilder();
   for (Query clause : clauses) {
     clause.toSanitizedString(out);
   }
   return out.toString();
 }
 @Override
 public String toString() {
   StringBuilder out = new StringBuilder("ZQ: ");
   for (Query clause : clauses) {
     clause.toString(out);
   }
   return out.toString();
 }
 /** Returns true if this query has at least one text query, false if it's entirely DB query. */
 public boolean hasTextOperation() {
   for (Query query : clauses) {
     if (query.hasTextOperation()) {
       return true;
     }
   }
   return false;
 }
  private void compile() throws ServiceException {
    assert clauses != null;
    if (clauses.isEmpty()) {
      return;
    }

    // Convert list of Queries into list of QueryOperations, then optimize them.
    // this generates all of the query operations
    operation = parseTree.compile(mailbox);
    ZimbraLog.search.debug("OP=%s", operation);

    // expand the is:local and is:remote parts into in:(LIST)'s
    operation = operation.expandLocalRemotePart(mailbox);
    ZimbraLog.search.debug("AFTEREXP=%s", operation);

    // optimize the query down
    operation = operation.optimize(mailbox);
    ZimbraLog.search.debug("OPTIMIZED=%s", operation);
    if (operation == null || operation instanceof NoTermQueryOperation) {
      operation = new NoResultsQueryOperation();
      return;
    }

    // Use the OperationContext to update the set of visible referenced folders, local AND remote.
    Set<QueryTarget> targets = operation.getQueryTargets();
    assert (operation instanceof UnionQueryOperation
        || QueryTarget.getExplicitTargetCount(targets) <= 1);

    // easiest to treat the query two unions: one the LOCAL and one REMOTE parts
    UnionQueryOperation remoteOps = new UnionQueryOperation();
    UnionQueryOperation localOps = new UnionQueryOperation();

    if (operation instanceof UnionQueryOperation) {
      UnionQueryOperation union = (UnionQueryOperation) operation;
      // separate out the LOCAL vs REMOTE parts...
      for (QueryOperation op : union.operations) {
        Set<QueryTarget> set = op.getQueryTargets();

        // this assertion OK because we have already distributed multi-target query ops
        // during the optimize() step
        assert (QueryTarget.getExplicitTargetCount(set) <= 1);

        // the assertion above is critical: the code below all assumes
        // that we only have ONE target (ie we've already distributed if necessary)

        if (QueryTarget.hasExternalTarget(set)) {
          remoteOps.add(op);
        } else {
          localOps.add(op);
        }
      }
    } else {
      // single target: might be local, might be remote
      Set<QueryTarget> set = operation.getQueryTargets();
      // this assertion OK because we have already distributed multi-target query ops
      // during the optimize() step
      assert (QueryTarget.getExplicitTargetCount(set) <= 1);

      if (QueryTarget.hasExternalTarget(set)) {
        remoteOps.add(operation);
      } else {
        localOps.add(operation);
      }
    }

    // Handle the REMOTE side:
    if (!remoteOps.operations.isEmpty()) {
      // Since optimize() has already been run, we know that each of our ops
      // only has one target (or none).  Find those operations which have
      // an external target and wrap them in RemoteQueryOperations

      // iterate backwards so we can remove/add w/o screwing iteration
      for (int i = remoteOps.operations.size() - 1; i >= 0; i--) {
        QueryOperation op = remoteOps.operations.get(i);
        Set<QueryTarget> set = op.getQueryTargets();

        // this assertion OK because we have already distributed multi-target query ops
        // during the optimize() step
        assert (QueryTarget.getExplicitTargetCount(set) <= 1);

        // the assertion above is critical: the code below all assumes
        // that we only have ONE target (ie we've already distributed if necessary)

        if (QueryTarget.hasExternalTarget(set)) {
          remoteOps.operations.remove(i);
          boolean foundOne = false;
          // find a remoteOp to add this one to
          for (QueryOperation remoteOp : remoteOps.operations) {
            if (remoteOp instanceof RemoteQueryOperation) {
              if (((RemoteQueryOperation) remoteOp).tryAddOredOperation(op)) {
                foundOne = true;
                break;
              }
            }
          }
          if (!foundOne) {
            RemoteQueryOperation remoteOp = new RemoteQueryOperation();
            remoteOp.tryAddOredOperation(op);
            remoteOps.operations.add(i, remoteOp);
          }
        }
      }

      // ...we need to call setup on every RemoteQueryOperation we end up with...
      for (QueryOperation remoteOp : remoteOps.operations) {
        assert (remoteOp instanceof RemoteQueryOperation);
        try {
          ((RemoteQueryOperation) remoteOp).setup(protocol, octxt.getAuthToken(), params);
        } catch (Exception e) {
          ZimbraLog.search.info("Ignoring %s during RemoteQuery generation for %s", e, remoteOps);
        }
      }
    }

    // For the LOCAL parts of the query, do permission checks, do trash/spam exclusion
    if (!localOps.operations.isEmpty()) {
      ZimbraLog.search.debug("LOCAL_IN=%s", localOps);

      Account authAcct = octxt != null ? octxt.getAuthenticatedUser() : mailbox.getAccount();

      // Now, for all the LOCAL PARTS of the query, add the trash/spam exclusion part
      boolean includeTrash = false;
      boolean includeSpam = false;
      if (params.inDumpster()) {
        // Always include trash and spam for dumpster searches.  Excluding spam is a client side
        // choice.
        includeTrash = true;
        includeSpam = true;
        if (!mailbox.hasFullAdminAccess(
            octxt)) { // If the requester is not an admin, limit to recent date range.
          long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
          long mdate = now - authAcct.getDumpsterUserVisibleAge();
          IntersectionQueryOperation and = new IntersectionQueryOperation();
          DBQueryOperation db = new DBQueryOperation();
          db.addMDateRange(mdate, false, -1L, false, true);
          and.addQueryOp((QueryOperation) localOps.clone());
          and.addQueryOp(db);
          localOps.operations.clear();
          localOps.operations.add(and);
        }
      } else {
        includeTrash = authAcct.isPrefIncludeTrashInSearch();
        ;
        includeSpam = authAcct.isPrefIncludeSpamInSearch();
      }
      if (!includeTrash || !includeSpam) {
        // First check that we aren't specifically looking for items in one of these.
        // For instance, in ZWC, if "Include Shared Items" is selected, the Trash list may currently
        // use a
        // search similar to : 'in:"trash" (inid:565 OR is:local)'
        // where 565 is the folder ID for a shared folder.  We don't want to end up doing a search
        // for items
        // that are both in "trash" and NOT in "trash"...
        for (Query q : clauses) {
          if (q.toString().equalsIgnoreCase("Q(IN:Trash)")) {
            includeTrash = true;
          }
          if (q.toString().equalsIgnoreCase("Q(IN:Junk)")) {
            includeSpam = true;
          }
        }
      }
      if (!includeTrash || !includeSpam) {
        List<QueryOperation> toAdd = new ArrayList<QueryOperation>();
        for (Iterator<QueryOperation> iter = localOps.operations.iterator(); iter.hasNext(); ) {
          QueryOperation cur = iter.next();
          if (!cur.hasSpamTrashSetting()) {
            QueryOperation newOp = cur.ensureSpamTrashSetting(mailbox, includeTrash, includeSpam);
            if (newOp != cur) {
              iter.remove();
              toAdd.add(newOp);
            }
          }
        }
        localOps.operations.addAll(toAdd);
        ZimbraLog.search.debug("LOCAL_AFTER_TRASH/SPAM/DUMPSTER=%s", localOps);
      }

      // Check to see if we need to filter out private appointment data
      boolean allowPrivateAccess = true;
      if (octxt != null) {
        allowPrivateAccess =
            AccessManager.getInstance()
                .allowPrivateAccess(
                    octxt.getAuthenticatedUser(),
                    mailbox.getAccount(),
                    octxt.isUsingAdminPrivileges());
      }

      //
      // bug 28892 - ACL.RIGHT_PRIVATE support:
      //
      // Basically, if ACL.RIGHT_PRIVATE is set somewhere, and if we're excluding private items from
      // search, then we need to run the query twice -- once over the whole mailbox with
      // private items excluded and then UNION it with a second run, this time only in the
      // RIGHT_PRIVATE enabled folders, with private items enabled.
      //
      UnionQueryOperation clonedLocal = null;
      Set<Folder> hasFolderRightPrivateSet = new HashSet<Folder>();

      // ...don't do any of this if they aren't asking for a calendar type...
      Set<MailItem.Type> types = params.getTypes();
      boolean hasCalendarType =
          types.contains(MailItem.Type.APPOINTMENT) || types.contains(MailItem.Type.TASK);
      if (hasCalendarType && !allowPrivateAccess && getTextOperationCount(localOps) > 0) {
        // the searcher is NOT allowed to see private items globally....lets check
        // to see if there are any individual folders that they DO have rights to...
        // if there are any, then we'll need to run special searches in those
        // folders
        Set<Folder> allVisibleFolders = mailbox.getVisibleFolders(octxt);
        if (allVisibleFolders == null) {
          allVisibleFolders = new HashSet<Folder>();
          allVisibleFolders.addAll(mailbox.getFolderList(octxt, SortBy.NONE));
        }
        for (Folder f : allVisibleFolders) {
          if (f.getType() == MailItem.Type.FOLDER
              && CalendarItem.allowPrivateAccess(f, authAcct, false)) {
            hasFolderRightPrivateSet.add(f);
          }
        }
        if (!hasFolderRightPrivateSet.isEmpty()) {
          clonedLocal = (UnionQueryOperation) localOps.clone();
        }
      }

      Set<Folder> visibleFolders = mailbox.getVisibleFolders(octxt);

      localOps = handleLocalPermissionChecks(localOps, visibleFolders, allowPrivateAccess);

      ZimbraLog.search.debug("LOCAL_AFTER_PERM_CHECKS=%s", localOps);

      if (!hasFolderRightPrivateSet.isEmpty()) {
        ZimbraLog.search.debug("CLONED_LOCAL_BEFORE_PERM=%s", clonedLocal);

        // now we're going to setup the clonedLocal tree
        // to run with private access ALLOWED, over the set of folders
        // that have RIGHT_PRIVATE (note that we build this list from the visible
        // folder list, so we are
        clonedLocal = handleLocalPermissionChecks(clonedLocal, hasFolderRightPrivateSet, true);

        ZimbraLog.search.debug("CLONED_LOCAL_AFTER_PERM=%s", clonedLocal);

        // clonedLocal should only have the single INTERSECT in it
        assert (clonedLocal.operations.size() == 1);

        QueryOperation optimizedClonedLocal = clonedLocal.optimize(mailbox);
        ZimbraLog.search.debug("CLONED_LOCAL_AFTER_OPTIMIZE=%s", optimizedClonedLocal);

        UnionQueryOperation withPrivateExcluded = localOps;
        localOps = new UnionQueryOperation();
        localOps.add(withPrivateExcluded);
        localOps.add(optimizedClonedLocal);

        ZimbraLog.search.debug("LOCAL_WITH_CLONED=%s", localOps);

        //
        // we should end up with:
        //
        // localOps =
        //    UNION(withPrivateExcluded,
        //          UNION(INTERSECT(clonedLocal,
        //                          UNION(hasFolderRightPrivateList)
        //                         )
        //                )
        //          )
        //
      }
    }

    UnionQueryOperation union = new UnionQueryOperation();
    union.add(localOps);
    union.add(remoteOps);
    ZimbraLog.search.debug("BEFORE_FINAL_OPT=%s", union);
    operation = union.optimize(mailbox);

    ZimbraLog.search.debug("COMPILED=%s", operation);
  }
 @Override
 QueryOperation compile(Mailbox mbox) throws ServiceException {
   return query.compile(mbox, bool);
 }
 ThingNode(Query query) {
   this.query = query;
   this.bool = query.getBool();
 }