All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.index.IndexRewriteImpl Maven / Gradle / Ivy

// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.index;

import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.server.query.AndPredicate;
import com.google.gerrit.server.query.NotPredicate;
import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.AndSource;
import com.google.gerrit.server.query.change.BasicChangeRewrites;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
import com.google.gerrit.server.query.change.OrSource;
import com.google.inject.Inject;

import java.util.BitSet;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/** Rewriter that pushes boolean logic into the secondary index. */
public class IndexRewriteImpl implements ChangeQueryRewriter {
  /** Set of all open change statuses. */
  public static final Set OPEN_STATUSES;

  /** Set of all closed change statuses. */
  public static final Set CLOSED_STATUSES;

  static {
    EnumSet open = EnumSet.noneOf(Change.Status.class);
    EnumSet closed = EnumSet.noneOf(Change.Status.class);
    for (Change.Status s : Change.Status.values()) {
      if (s.isOpen()) {
        open.add(s);
      } else {
        closed.add(s);
      }
    }
    OPEN_STATUSES = Sets.immutableEnumSet(open);
    CLOSED_STATUSES = Sets.immutableEnumSet(closed);
  }

  /**
   * Get the set of statuses that changes matching the given predicate may have.
   *
   * @param in predicate
   * @return the maximal set of statuses that any changes matching the input
   *     predicates may have, based on examining boolean and
   *     {@link ChangeStatusPredicate}s.
   */
  public static EnumSet getPossibleStatus(Predicate in) {
    EnumSet s = extractStatus(in);
    return s != null ? s : EnumSet.allOf(Change.Status.class);
  }

  private static EnumSet extractStatus(Predicate in) {
    if (in instanceof ChangeStatusPredicate) {
      return EnumSet.of(((ChangeStatusPredicate) in).getStatus());
    } else if (in instanceof NotPredicate) {
      EnumSet s = extractStatus(in.getChild(0));
      return s != null ? EnumSet.complementOf(s) : null;
    } else if (in instanceof OrPredicate) {
      EnumSet r = null;
      int childrenWithStatus = 0;
      for (int i = 0; i < in.getChildCount(); i++) {
        EnumSet c = extractStatus(in.getChild(i));
        if (c != null) {
          if (r == null) {
            r = EnumSet.noneOf(Change.Status.class);
          }
          r.addAll(c);
          childrenWithStatus++;
        }
      }
      if (r != null && childrenWithStatus < in.getChildCount()) {
        // At least one child supplied a status but another did not.
        // Assume all statuses for the children that did not feed a
        // status at this part of the tree. This matches behavior if
        // the child was used at the root of a query.
        return EnumSet.allOf(Change.Status.class);
      }
      return r;
    } else if (in instanceof AndPredicate) {
      EnumSet r = null;
      for (int i = 0; i < in.getChildCount(); i++) {
        EnumSet c = extractStatus(in.getChild(i));
        if (c != null) {
          if (r == null) {
            r = EnumSet.allOf(Change.Status.class);
          }
          r.retainAll(c);
        }
      }
      return r;
    }
    return null;
  }

  private final IndexCollection indexes;
  private final BasicChangeRewrites basicRewrites;

  @Inject
  IndexRewriteImpl(IndexCollection indexes,
      BasicChangeRewrites basicRewrites) {
    this.indexes = indexes;
    this.basicRewrites = basicRewrites;
  }

  @Override
  public Predicate rewrite(Predicate in, int start)
      throws QueryParseException {
    ChangeIndex index = indexes.getSearchIndex();
    in = basicRewrites.rewrite(in);
    int limit = Objects.firstNonNull(
        ChangeQueryBuilder.getLimit(in), DEFAULT_MAX_QUERY_LIMIT);
    // Increase the limit rather than skipping, since we don't know how many
    // skipped results would have been filtered out by the enclosing AndSource.
    limit += start;
    limit = Math.max(limit, 1);

    Predicate out = rewriteImpl(in, index, limit);
    if (in == out || out instanceof IndexPredicate) {
      return new IndexedChangeQuery(index, out, limit);
    } else if (out == null /* cannot rewrite */) {
      return in;
    } else {
      return out;
    }
  }

  /**
   * Rewrite a single predicate subtree.
   *
   * @param in predicate to rewrite.
   * @param index index whose schema determines which fields are indexed.
   * @param limit maximum number of results to return.
   * @return {@code null} if no part of this subtree can be queried in the
   *     index directly. {@code in} if this subtree and all its children can be
   *     queried directly in the index. Otherwise, a predicate that is
   *     semantically equivalent, with some of its subtrees wrapped to query the
   *     index directly.
   * @throws QueryParseException if the underlying index implementation does not
   *     support this predicate.
   */
  private Predicate rewriteImpl(Predicate in,
      ChangeIndex index, int limit) throws QueryParseException {
    if (isIndexPredicate(in, index)) {
      return in;
    } else if (!isRewritePossible(in)) {
      return null; // magic to indicate "in" cannot be rewritten
    }

    int n = in.getChildCount();
    BitSet isIndexed = new BitSet(n);
    BitSet notIndexed = new BitSet(n);
    BitSet rewritten = new BitSet(n);
    List> newChildren = Lists.newArrayListWithCapacity(n);
    for (int i = 0; i < n; i++) {
      Predicate c = in.getChild(i);
      Predicate nc = rewriteImpl(c, index, limit);
      if (nc == c) {
        isIndexed.set(i);
        newChildren.add(c);
      } else if (nc == null /* cannot rewrite c */) {
        notIndexed.set(i);
        newChildren.add(c);
      } else {
        rewritten.set(i);
        newChildren.add(nc);
      }
    }

    if (isIndexed.cardinality() == n) {
      return in; // All children are indexed, leave as-is for parent.
    } else if (notIndexed.cardinality() == n) {
      return null; // Can't rewrite any children, so cannot rewrite in.
    } else if (rewritten.cardinality() == n) {
      return in.copy(newChildren); // All children were rewritten.
    }
    return partitionChildren(in, newChildren, isIndexed, index, limit);
  }

  private boolean isIndexPredicate(Predicate in, ChangeIndex index) {
    if (!(in instanceof IndexPredicate)) {
      return false;
    }
    IndexPredicate p = (IndexPredicate) in;
    return index.getSchema().getFields().containsKey(p.getField().getName());
  }

  private Predicate partitionChildren(
      Predicate in,
      List> newChildren,
      BitSet isIndexed,
      ChangeIndex index,
      int limit) throws QueryParseException {
    if (isIndexed.cardinality() == 1) {
      int i = isIndexed.nextSetBit(0);
      newChildren.add(
          0, new IndexedChangeQuery(index, newChildren.remove(i), limit));
      return copy(in, newChildren);
    }

    // Group all indexed predicates into a wrapped subtree.
    List> indexed =
        Lists.newArrayListWithCapacity(isIndexed.cardinality());

    List> all =
        Lists.newArrayListWithCapacity(
            newChildren.size() - isIndexed.cardinality() + 1);

    for (int i = 0; i < newChildren.size(); i++) {
      Predicate c = newChildren.get(i);
      if (isIndexed.get(i)) {
        indexed.add(c);
      } else {
        all.add(c);
      }
    }
    all.add(0, new IndexedChangeQuery(index, in.copy(indexed), limit));
    return copy(in, all);
  }

  private Predicate copy(
      Predicate in,
      List> all) {
    if (in instanceof AndPredicate) {
      return new AndSource(all);
    } else if (in instanceof OrPredicate) {
      return new OrSource(all);
    }
    return in.copy(all);
  }

  private static boolean isRewritePossible(Predicate p) {
    return p.getChildCount() > 0 && (
           p instanceof AndPredicate
        || p instanceof OrPredicate
        || p instanceof NotPredicate);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy