Add ElasticQuerySource to ElasticLuceneIndex

This commit is a no-op change that reduces duplicate code in the ES
indices by creating a shared implementation of QuerySource.

Change-Id: Ibbb0058c3a0d9b99516ed86a342bf809148becbb
This commit is contained in:
Patrick Hiesel
2017-11-16 11:06:06 +01:00
parent 077c55edf9
commit 7e7103d9b8
5 changed files with 352 additions and 634 deletions

View File

@@ -23,15 +23,21 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
import com.google.gerrit.index.FieldDef; import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.FieldType; import com.google.gerrit.index.FieldType;
import com.google.gerrit.index.Index; import com.google.gerrit.index.Index;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema; import com.google.gerrit.index.Schema;
import com.google.gerrit.index.Schema.Values; import com.google.gerrit.index.Schema.Values;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.FieldBundle; import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils; import com.google.gerrit.server.index.IndexUtils;
@@ -41,24 +47,38 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gwtorm.protobuf.ProtobufCodec; import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import io.searchbox.client.JestResult; import io.searchbox.client.JestResult;
import io.searchbox.client.http.JestHttpClient; import io.searchbox.client.http.JestHttpClient;
import io.searchbox.core.Bulk; import io.searchbox.core.Bulk;
import io.searchbox.core.Delete; import io.searchbox.core.Delete;
import io.searchbox.core.Search;
import io.searchbox.core.search.sort.Sort;
import io.searchbox.indices.CreateIndex; import io.searchbox.indices.CreateIndex;
import io.searchbox.indices.DeleteIndex; import io.searchbox.indices.DeleteIndex;
import io.searchbox.indices.IndicesExists; import io.searchbox.indices.IndicesExists;
import java.io.IOException; import java.io.IOException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
abstract class AbstractElasticIndex<K, V> implements Index<K, V> { abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
private static final Logger log = LoggerFactory.getLogger(AbstractElasticIndex.class);
protected static <T> List<T> decodeProtos( protected static <T> List<T> decodeProtos(
JsonObject doc, String fieldName, ProtobufCodec<T> codec) { JsonObject doc, String fieldName, ProtobufCodec<T> codec) {
JsonArray field = doc.getAsJsonArray(fieldName); JsonArray field = doc.getAsJsonArray(fieldName);
@@ -158,7 +178,7 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
protected io.searchbox.core.Index insert(String type, V v) throws IOException { protected io.searchbox.core.Index insert(String type, V v) throws IOException {
String id = getId(v); String id = getId(v);
String doc = toDoc(v); String doc = toDocument(v);
return new io.searchbox.core.Index.Builder(doc).index(indexName).type(type).id(id).build(); return new io.searchbox.core.Index.Builder(doc).index(indexName).type(type).id(id).build();
} }
@@ -166,7 +186,7 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
return !(element instanceof String) || !((String) element).isEmpty(); return !(element instanceof String) || !((String) element).isEmpty();
} }
private String toDoc(V v) throws IOException { private String toDocument(V v) throws IOException {
XContentBuilder builder = jsonBuilder().startObject(); XContentBuilder builder = jsonBuilder().startObject();
for (Values<V> values : schema.buildFields(v)) { for (Values<V> values : schema.buildFields(v)) {
String name = values.getField().getName(); String name = values.getField().getName();
@@ -184,6 +204,8 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
return builder.endObject().string(); return builder.endObject().string();
} }
protected abstract V fromDocument(JsonObject doc, Set<String> fields);
protected FieldBundle toFieldBundle(JsonObject doc) { protected FieldBundle toFieldBundle(JsonObject doc) {
Map<String, FieldDef<V, ?>> allFields = getSchema().getFields(); Map<String, FieldDef<V, ?>> allFields = getSchema().getFields();
ListMultimap<String, Object> rawFields = ArrayListMultimap.create(); ListMultimap<String, Object> rawFields = ArrayListMultimap.create();
@@ -215,4 +237,95 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
} }
return new FieldBundle(rawFields); return new FieldBundle(rawFields);
} }
protected class ElasticQuerySource implements DataSource<V> {
private final QueryOptions opts;
private final Search search;
ElasticQuerySource(Predicate<V> p, QueryOptions opts, String type, Sort sort)
throws QueryParseException {
this(p, opts, ImmutableList.of(type), ImmutableList.of(sort));
}
ElasticQuerySource(
Predicate<V> p, QueryOptions opts, Collection<String> types, Collection<Sort> sorts)
throws QueryParseException {
this.opts = opts;
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
SearchSourceBuilder searchSource =
new SearchSourceBuilder()
.query(qb)
.from(opts.start())
.size(opts.limit())
.fields(Lists.newArrayList(opts.fields()));
search =
new Search.Builder(searchSource.toString())
.addType(types)
.addSort(sorts)
.addIndex(indexName)
.build();
}
@Override
public int getCardinality() {
return 10;
}
@Override
public ResultSet<V> read() throws OrmException {
return readImpl((doc) -> AbstractElasticIndex.this.fromDocument(doc, opts.fields()));
}
@Override
public ResultSet<FieldBundle> readRaw() throws OrmException {
return readImpl(AbstractElasticIndex.this::toFieldBundle);
}
@Override
public String toString() {
return search.toString();
}
private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
try {
List<T> results = Collections.emptyList();
JestResult result = client.execute(search);
if (result.isSucceeded()) {
JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
if (obj.get("hits") != null) {
JsonArray json = obj.getAsJsonArray("hits");
results = Lists.newArrayListWithCapacity(json.size());
for (int i = 0; i < json.size(); i++) {
T mapperResult = mapper.apply(json.get(i).getAsJsonObject());
if (mapperResult != null) {
results.add(mapperResult);
}
}
}
} else {
log.error(result.getErrorMessage());
}
final List<T> r = Collections.unmodifiableList(results);
return new ResultSet<T>() {
@Override
public Iterator<T> iterator() {
return r.iterator();
}
@Override
public List<T> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
}
}
}
} }

View File

@@ -16,14 +16,11 @@ package com.google.gerrit.elasticsearch;
import static com.google.gerrit.server.index.account.AccountField.ID; import static com.google.gerrit.server.index.account.AccountField.ID;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties; import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.index.QueryOptions; import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema; import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource; import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
@@ -34,31 +31,19 @@ import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils; import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.account.AccountField; import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndex; import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import io.searchbox.client.JestResult; import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk; import io.searchbox.core.Bulk;
import io.searchbox.core.Bulk.Builder; import io.searchbox.core.Bulk.Builder;
import io.searchbox.core.Search;
import io.searchbox.core.search.sort.Sort; import io.searchbox.core.search.sort.Sort;
import io.searchbox.core.search.sort.Sort.Sorting; import io.searchbox.core.search.sort.Sort.Sorting;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, AccountState> public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, AccountState>
implements AccountIndex { implements AccountIndex {
@@ -73,8 +58,6 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
static final String ACCOUNTS = "accounts"; static final String ACCOUNTS = "accounts";
static final String ACCOUNTS_PREFIX = ACCOUNTS + "_"; static final String ACCOUNTS_PREFIX = ACCOUNTS + "_";
private static final Logger log = LoggerFactory.getLogger(ElasticAccountIndex.class);
private final AccountMapping mapping; private final AccountMapping mapping;
private final Provider<AccountCache> accountCache; private final Provider<AccountCache> accountCache;
@@ -111,7 +94,9 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
@Override @Override
public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts) public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts)
throws QueryParseException { throws QueryParseException {
return new QuerySource(p, opts); Sort sort = new Sort(AccountField.ID.getName(), Sorting.ASC);
sort.setIgnoreUnmapped();
return new ElasticQuerySource(p, opts.filterFields(IndexUtils::accountFields), ACCOUNTS, sort);
} }
@Override @Override
@@ -130,104 +115,18 @@ public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, Accoun
return as.getAccount().getId().toString(); return as.getAccount().getId().toString();
} }
private class QuerySource implements DataSource<AccountState> { @Override
private final Search search; protected AccountState fromDocument(JsonObject json, Set<String> fields) {
private final Set<String> fields; JsonElement source = json.get("_source");
if (source == null) {
QuerySource(Predicate<AccountState> p, QueryOptions opts) throws QueryParseException { source = json.getAsJsonObject().get("fields");
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
fields = IndexUtils.accountFields(opts);
SearchSourceBuilder searchSource =
new SearchSourceBuilder()
.query(qb)
.from(opts.start())
.size(opts.limit())
.fields(Lists.newArrayList(fields));
Sort sort = new Sort(AccountField.ID.getName(), Sorting.ASC);
sort.setIgnoreUnmapped();
search =
new Search.Builder(searchSource.toString())
.addType(ACCOUNTS)
.addIndex(indexName)
.addSort(ImmutableList.of(sort))
.build();
} }
@Override Account.Id id = new Account.Id(source.getAsJsonObject().get(ID.getName()).getAsInt());
public int getCardinality() { // Use the AccountCache rather than depending on any stored fields in the
return 10; // document (of which there shouldn't be any). The most expensive part to
} // compute anyway is the effective group IDs, and we don't have a good way
// to reindex when those change.
@Override return accountCache.get().get(id);
public ResultSet<AccountState> read() throws OrmException {
return readImpl(this::toAccountState);
}
@Override
public ResultSet<FieldBundle> readRaw() throws OrmException {
return readImpl(ElasticAccountIndex.this::toFieldBundle);
}
private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
try {
List<T> results = Collections.emptyList();
JestResult result = client.execute(search);
if (result.isSucceeded()) {
JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
if (obj.get("hits") != null) {
JsonArray json = obj.getAsJsonArray("hits");
results = Lists.newArrayListWithCapacity(json.size());
for (int i = 0; i < json.size(); i++) {
T mapperResult = mapper.apply(json.get(i).getAsJsonObject());
if (mapperResult != null) {
results.add(mapperResult);
}
}
}
} else {
log.error(result.getErrorMessage());
}
final List<T> r = Collections.unmodifiableList(results);
return new ResultSet<T>() {
@Override
public Iterator<T> iterator() {
return r.iterator();
}
@Override
public List<T> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
}
}
@Override
public String toString() {
return search.toString();
}
private AccountState toAccountState(JsonElement json) {
JsonElement source = json.getAsJsonObject().get("_source");
if (source == null) {
source = json.getAsJsonObject().get("fields");
}
Account.Id id = new Account.Id(source.getAsJsonObject().get(ID.getName()).getAsInt());
// Use the AccountCache rather than depending on any stored fields in the
// document (of which there shouldn't be any). The most expensive part to
// compute anyway is the effective group IDs, and we don't have a good way
// to reindex when those change.
return accountCache.get().get(id);
}
} }
} }

View File

@@ -35,7 +35,7 @@ import com.google.common.collect.Sets;
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties; import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.index.QueryOptions; import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema; import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle; import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
@@ -54,39 +54,28 @@ import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexRewriter; import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.project.SubmitRuleOptions; import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import io.searchbox.client.JestResult; import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk; import io.searchbox.core.Bulk;
import io.searchbox.core.Bulk.Builder; import io.searchbox.core.Bulk.Builder;
import io.searchbox.core.Search;
import io.searchbox.core.search.sort.Sort; import io.searchbox.core.search.sort.Sort;
import io.searchbox.core.search.sort.Sort.Sorting; import io.searchbox.core.search.sort.Sort.Sorting;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Secondary index implementation using Elasticsearch. */ /** Secondary index implementation using Elasticsearch. */
class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData> class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
implements ChangeIndex { implements ChangeIndex {
private static final Logger log = LoggerFactory.getLogger(ElasticChangeIndex.class);
static class ChangeMapping { static class ChangeMapping {
MappingProperties openChanges; MappingProperties openChanges;
MappingProperties closedChanges; MappingProperties closedChanges;
@@ -155,7 +144,7 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
} }
@Override @Override
public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts) public DataSource getSource(Predicate<ChangeData> p, QueryOptions opts)
throws QueryParseException { throws QueryParseException {
Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p); Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
List<String> indexes = Lists.newArrayListWithCapacity(2); List<String> indexes = Lists.newArrayListWithCapacity(2);
@@ -165,7 +154,16 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) { if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
indexes.add(CLOSED_CHANGES); indexes.add(CLOSED_CHANGES);
} }
return new QuerySource(indexes, p, opts);
List<Sort> sorts =
ImmutableList.of(
new Sort(ChangeField.UPDATED.getName(), Sorting.DESC),
new Sort(ChangeField.LEGACY_ID.getName(), Sorting.DESC));
for (Sort sort : sorts) {
sort.setIgnoreUnmapped();
}
QueryOptions filteredOpts = opts.filterFields(IndexUtils::changeFields);
return new ElasticQuerySource(p, filteredOpts, indexes, sorts);
} }
@Override @Override
@@ -183,306 +181,210 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
return cd.getId().toString(); return cd.getId().toString();
} }
private class QuerySource implements ChangeDataSource { @Override
private final Search search; protected ChangeData fromDocument(JsonObject json, Set<String> fields) {
private final Set<String> fields; JsonElement sourceElement = json.get("_source");
if (sourceElement == null) {
sourceElement = json.getAsJsonObject().get("fields");
}
JsonObject source = sourceElement.getAsJsonObject();
JsonElement c = source.get(ChangeField.CHANGE.getName());
QuerySource(List<String> types, Predicate<ChangeData> p, QueryOptions opts) if (c == null) {
throws QueryParseException { int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
List<Sort> sorts = // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
ImmutableList.of( String projectName = checkNotNull(source.get(ChangeField.PROJECT.getName()).getAsString());
new Sort(ChangeField.UPDATED.getName(), Sorting.DESC), return changeDataFactory.create(
new Sort(ChangeField.LEGACY_ID.getName(), Sorting.DESC)); db.get(), new Project.NameKey(projectName), new Change.Id(id));
for (Sort sort : sorts) { }
sort.setIgnoreUnmapped();
ChangeData cd =
changeDataFactory.create(
db.get(), CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
// Any decoding that is done here must also be done in {@link LuceneChangeIndex}.
// Patch sets.
cd.setPatchSets(decodeProtos(source, ChangeField.PATCH_SET.getName(), PATCH_SET_CODEC));
// Approvals.
if (source.get(ChangeField.APPROVAL.getName()) != null) {
cd.setCurrentApprovals(decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
} else if (fields.contains(ChangeField.APPROVAL.getName())) {
cd.setCurrentApprovals(Collections.emptyList());
}
// Added & Deleted.
JsonElement addedElement = source.get(ChangeField.ADDED.getName());
JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
if (addedElement != null && deletedElement != null) {
// Changed lines.
int added = addedElement.getAsInt();
int deleted = deletedElement.getAsInt();
if (added != 0 && deleted != 0) {
cd.setChangedLines(added, deleted);
} }
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
fields = IndexUtils.changeFields(opts);
SearchSourceBuilder searchSource =
new SearchSourceBuilder()
.query(qb)
.from(opts.start())
.size(opts.limit())
.fields(Lists.newArrayList(fields));
search =
new Search.Builder(searchSource.toString())
.addType(types)
.addSort(sorts)
.addIndex(indexName)
.build();
} }
@Override // Mergeable.
public int getCardinality() { JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
return 10; if (mergeableElement != null) {
String mergeable = mergeableElement.getAsString();
if ("1".equals(mergeable)) {
cd.setMergeable(true);
} else if ("0".equals(mergeable)) {
cd.setMergeable(false);
}
} }
@Override // Reviewed-by.
public ResultSet<ChangeData> read() throws OrmException { if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
return readImpl(this::toChangeData); JsonArray reviewedBy = source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
} if (reviewedBy.size() > 0) {
Set<Account.Id> accounts = Sets.newHashSetWithExpectedSize(reviewedBy.size());
@Override for (int i = 0; i < reviewedBy.size(); i++) {
public ResultSet<FieldBundle> readRaw() throws OrmException { int aId = reviewedBy.get(i).getAsInt();
return readImpl(ElasticChangeIndex.this::toFieldBundle); if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
} break;
@Override
public boolean hasChange() {
return false;
}
@Override
public String toString() {
return search.toString();
}
private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
try {
List<T> results = Collections.emptyList();
JestResult result = client.execute(search);
if (result.isSucceeded()) {
JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
if (obj.get("hits") != null) {
JsonArray json = obj.getAsJsonArray("hits");
results = Lists.newArrayListWithCapacity(json.size());
for (int i = 0; i < json.size(); i++) {
results.add(mapper.apply(json.get(i).getAsJsonObject()));
}
} }
} else { accounts.add(new Account.Id(aId));
log.error(result.getErrorMessage());
} }
final List<T> r = Collections.unmodifiableList(results); cd.setReviewedBy(accounts);
return new ResultSet<T>() {
@Override
public Iterator<T> iterator() {
return r.iterator();
}
@Override
public List<T> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
} }
} else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
cd.setReviewedBy(Collections.emptySet());
} }
private ChangeData toChangeData(JsonElement json) { // Hashtag.
JsonElement sourceElement = json.getAsJsonObject().get("_source"); if (source.get(ChangeField.HASHTAG.getName()) != null) {
if (sourceElement == null) { JsonArray hashtagArray = source.get(ChangeField.HASHTAG.getName()).getAsJsonArray();
sourceElement = json.getAsJsonObject().get("fields"); if (hashtagArray.size() > 0) {
} Set<String> hashtags = Sets.newHashSetWithExpectedSize(hashtagArray.size());
JsonObject source = sourceElement.getAsJsonObject(); for (int i = 0; i < hashtagArray.size(); i++) {
JsonElement c = source.get(ChangeField.CHANGE.getName()); hashtags.add(hashtagArray.get(i).getAsString());
if (c == null) {
int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
// IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
String projectName = checkNotNull(source.get(ChangeField.PROJECT.getName()).getAsString());
return changeDataFactory.create(
db.get(), new Project.NameKey(projectName), new Change.Id(id));
}
ChangeData cd =
changeDataFactory.create(
db.get(), CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
// Any decoding that is done here must also be done in {@link LuceneChangeIndex}.
// Patch sets.
cd.setPatchSets(decodeProtos(source, ChangeField.PATCH_SET.getName(), PATCH_SET_CODEC));
// Approvals.
if (source.get(ChangeField.APPROVAL.getName()) != null) {
cd.setCurrentApprovals(
decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
} else if (fields.contains(ChangeField.APPROVAL.getName())) {
cd.setCurrentApprovals(Collections.emptyList());
}
// Added & Deleted.
JsonElement addedElement = source.get(ChangeField.ADDED.getName());
JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
if (addedElement != null && deletedElement != null) {
// Changed lines.
int added = addedElement.getAsInt();
int deleted = deletedElement.getAsInt();
if (added != 0 && deleted != 0) {
cd.setChangedLines(added, deleted);
} }
cd.setHashtags(hashtags);
} }
} else if (fields.contains(ChangeField.HASHTAG.getName())) {
// Mergeable. cd.setHashtags(Collections.emptySet());
JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
if (mergeableElement != null) {
String mergeable = mergeableElement.getAsString();
if ("1".equals(mergeable)) {
cd.setMergeable(true);
} else if ("0".equals(mergeable)) {
cd.setMergeable(false);
}
}
// Reviewed-by.
if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
JsonArray reviewedBy = source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
if (reviewedBy.size() > 0) {
Set<Account.Id> accounts = Sets.newHashSetWithExpectedSize(reviewedBy.size());
for (int i = 0; i < reviewedBy.size(); i++) {
int aId = reviewedBy.get(i).getAsInt();
if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
break;
}
accounts.add(new Account.Id(aId));
}
cd.setReviewedBy(accounts);
}
} else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
cd.setReviewedBy(Collections.emptySet());
}
// Hashtag.
if (source.get(ChangeField.HASHTAG.getName()) != null) {
JsonArray hashtagArray = source.get(ChangeField.HASHTAG.getName()).getAsJsonArray();
if (hashtagArray.size() > 0) {
Set<String> hashtags = Sets.newHashSetWithExpectedSize(hashtagArray.size());
for (int i = 0; i < hashtagArray.size(); i++) {
hashtags.add(hashtagArray.get(i).getAsString());
}
cd.setHashtags(hashtags);
}
} else if (fields.contains(ChangeField.HASHTAG.getName())) {
cd.setHashtags(Collections.emptySet());
}
// Star.
if (source.get(ChangeField.STAR.getName()) != null) {
JsonArray starArray = source.get(ChangeField.STAR.getName()).getAsJsonArray();
if (starArray.size() > 0) {
ListMultimap<Account.Id, String> stars =
MultimapBuilder.hashKeys().arrayListValues().build();
for (int i = 0; i < starArray.size(); i++) {
StarredChangesUtil.StarField starField =
StarredChangesUtil.StarField.parse(starArray.get(i).getAsString());
stars.put(starField.accountId(), starField.label());
}
cd.setStars(stars);
}
} else if (fields.contains(ChangeField.STAR.getName())) {
cd.setStars(ImmutableListMultimap.of());
}
// Reviewer.
if (source.get(ChangeField.REVIEWER.getName()) != null) {
cd.setReviewers(
ChangeField.parseReviewerFieldValues(
FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.REVIEWER.getName())) {
cd.setReviewers(ReviewerSet.empty());
}
// Reviewer-by-email.
if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
cd.setReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
FluentIterable.from(
source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.REVIEWER_BY_EMAIL.getName())) {
cd.setReviewersByEmail(ReviewerByEmailSet.empty());
}
// Pending-reviewer.
if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
cd.setPendingReviewers(
ChangeField.parseReviewerFieldValues(
FluentIterable.from(
source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.PENDING_REVIEWER.getName())) {
cd.setPendingReviewers(ReviewerSet.empty());
}
// Pending-reviewer-by-email.
if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
cd.setPendingReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
FluentIterable.from(
source
.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())
.getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())) {
cd.setPendingReviewersByEmail(ReviewerByEmailSet.empty());
}
// Stored-submit-record-strict.
decodeSubmitRecords(
source,
ChangeField.STORED_SUBMIT_RECORD_STRICT.getName(),
ChangeField.SUBMIT_RULE_OPTIONS_STRICT,
cd);
// Stored-submit-record-leniant.
decodeSubmitRecords(
source,
ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
cd);
// Ref-state.
if (fields.contains(ChangeField.REF_STATE.getName())) {
cd.setRefStates(getByteArray(source, ChangeField.REF_STATE.getName()));
}
// Ref-state-pattern.
if (fields.contains(ChangeField.REF_STATE_PATTERN.getName())) {
cd.setRefStatePatterns(getByteArray(source, ChangeField.REF_STATE_PATTERN.getName()));
}
// Unresolved-comment-count.
decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
return cd;
} }
private Iterable<byte[]> getByteArray(JsonObject source, String name) { // Star.
JsonElement element = source.get(name); if (source.get(ChangeField.STAR.getName()) != null) {
return element != null JsonArray starArray = source.get(ChangeField.STAR.getName()).getAsJsonArray();
? Iterables.transform(element.getAsJsonArray(), e -> Base64.decodeBase64(e.getAsString())) if (starArray.size() > 0) {
: Collections.emptyList(); ListMultimap<Account.Id, String> stars =
MultimapBuilder.hashKeys().arrayListValues().build();
for (int i = 0; i < starArray.size(); i++) {
StarredChangesUtil.StarField starField =
StarredChangesUtil.StarField.parse(starArray.get(i).getAsString());
stars.put(starField.accountId(), starField.label());
}
cd.setStars(stars);
}
} else if (fields.contains(ChangeField.STAR.getName())) {
cd.setStars(ImmutableListMultimap.of());
} }
private void decodeSubmitRecords( // Reviewer.
JsonObject doc, String fieldName, SubmitRuleOptions opts, ChangeData out) { if (source.get(ChangeField.REVIEWER.getName()) != null) {
JsonArray records = doc.getAsJsonArray(fieldName); cd.setReviewers(
if (records == null) { ChangeField.parseReviewerFieldValues(
return; FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
} .transform(JsonElement::getAsString)));
ChangeField.parseSubmitRecords( } else if (fields.contains(ChangeField.REVIEWER.getName())) {
FluentIterable.from(records) cd.setReviewers(ReviewerSet.empty());
.transform(i -> new String(decodeBase64(i.toString()), UTF_8))
.toList(),
opts,
out);
} }
private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) { // Reviewer-by-email.
JsonElement count = doc.get(fieldName); if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
if (count == null) { cd.setReviewersByEmail(
return; ChangeField.parseReviewerByEmailFieldValues(
} FluentIterable.from(
out.setUnresolvedCommentCount(count.getAsInt()); source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.REVIEWER_BY_EMAIL.getName())) {
cd.setReviewersByEmail(ReviewerByEmailSet.empty());
} }
// Pending-reviewer.
if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
cd.setPendingReviewers(
ChangeField.parseReviewerFieldValues(
FluentIterable.from(
source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.PENDING_REVIEWER.getName())) {
cd.setPendingReviewers(ReviewerSet.empty());
}
// Pending-reviewer-by-email.
if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
cd.setPendingReviewersByEmail(
ChangeField.parseReviewerByEmailFieldValues(
FluentIterable.from(
source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
.transform(JsonElement::getAsString)));
} else if (fields.contains(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())) {
cd.setPendingReviewersByEmail(ReviewerByEmailSet.empty());
}
// Stored-submit-record-strict.
decodeSubmitRecords(
source,
ChangeField.STORED_SUBMIT_RECORD_STRICT.getName(),
ChangeField.SUBMIT_RULE_OPTIONS_STRICT,
cd);
// Stored-submit-record-leniant.
decodeSubmitRecords(
source,
ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
cd);
// Ref-state.
if (fields.contains(ChangeField.REF_STATE.getName())) {
cd.setRefStates(getByteArray(source, ChangeField.REF_STATE.getName()));
}
// Ref-state-pattern.
if (fields.contains(ChangeField.REF_STATE_PATTERN.getName())) {
cd.setRefStatePatterns(getByteArray(source, ChangeField.REF_STATE_PATTERN.getName()));
}
// Unresolved-comment-count.
decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
return cd;
}
private Iterable<byte[]> getByteArray(JsonObject source, String name) {
JsonElement element = source.get(name);
return element != null
? Iterables.transform(element.getAsJsonArray(), e -> Base64.decodeBase64(e.getAsString()))
: Collections.emptyList();
}
private void decodeSubmitRecords(
JsonObject doc, String fieldName, SubmitRuleOptions opts, ChangeData out) {
JsonArray records = doc.getAsJsonArray(fieldName);
if (records == null) {
return;
}
ChangeField.parseSubmitRecords(
FluentIterable.from(records)
.transform(i -> new String(decodeBase64(i.toString()), UTF_8))
.toList(),
opts,
out);
}
private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
JsonElement count = doc.get(fieldName);
if (count == null) {
return;
}
out.setUnresolvedCommentCount(count.getAsInt());
} }
} }

View File

@@ -14,14 +14,11 @@
package com.google.gerrit.elasticsearch; package com.google.gerrit.elasticsearch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties; import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.index.QueryOptions; import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema; import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource; import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -32,31 +29,18 @@ import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.IndexUtils; import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.group.GroupField; import com.google.gerrit.server.index.group.GroupField;
import com.google.gerrit.server.index.group.GroupIndex; import com.google.gerrit.server.index.group.GroupIndex;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import io.searchbox.client.JestResult; import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk; import io.searchbox.core.Bulk;
import io.searchbox.core.Bulk.Builder; import io.searchbox.core.Bulk.Builder;
import io.searchbox.core.Search;
import io.searchbox.core.search.sort.Sort; import io.searchbox.core.search.sort.Sort;
import io.searchbox.core.search.sort.Sort.Sorting;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, InternalGroup> public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, InternalGroup>
implements GroupIndex { implements GroupIndex {
@@ -71,8 +55,6 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
static final String GROUPS = "groups"; static final String GROUPS = "groups";
static final String GROUPS_PREFIX = GROUPS + "_"; static final String GROUPS_PREFIX = GROUPS + "_";
private static final Logger log = LoggerFactory.getLogger(ElasticGroupIndex.class);
private final GroupMapping mapping; private final GroupMapping mapping;
private final Provider<GroupCache> groupCache; private final Provider<GroupCache> groupCache;
@@ -109,7 +91,9 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
@Override @Override
public DataSource<InternalGroup> getSource(Predicate<InternalGroup> p, QueryOptions opts) public DataSource<InternalGroup> getSource(Predicate<InternalGroup> p, QueryOptions opts)
throws QueryParseException { throws QueryParseException {
return new QuerySource(p, opts); Sort sort = new Sort(GroupField.UUID.getName(), Sort.Sorting.ASC);
sort.setIgnoreUnmapped();
return new ElasticQuerySource(p, opts.filterFields(IndexUtils::groupFields), GROUPS, sort);
} }
@Override @Override
@@ -128,104 +112,18 @@ public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, I
return group.getGroupUUID().get(); return group.getGroupUUID().get();
} }
private class QuerySource implements DataSource<InternalGroup> { @Override
private final Search search; protected InternalGroup fromDocument(JsonObject json, Set<String> fields) {
private final Set<String> fields; JsonElement source = json.get("_source");
if (source == null) {
QuerySource(Predicate<InternalGroup> p, QueryOptions opts) throws QueryParseException { source = json.getAsJsonObject().get("fields");
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
fields = IndexUtils.groupFields(opts);
SearchSourceBuilder searchSource =
new SearchSourceBuilder()
.query(qb)
.from(opts.start())
.size(opts.limit())
.fields(Lists.newArrayList(fields));
Sort sort = new Sort(GroupField.UUID.getName(), Sorting.ASC);
sort.setIgnoreUnmapped();
search =
new Search.Builder(searchSource.toString())
.addType(GROUPS)
.addIndex(indexName)
.addSort(ImmutableList.of(sort))
.build();
} }
@Override AccountGroup.UUID uuid =
public int getCardinality() { new AccountGroup.UUID(
return 10; source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
} // Use the GroupCache rather than depending on any stored fields in the
// document (of which there shouldn't be any).
@Override return groupCache.get().get(uuid).orElse(null);
public ResultSet<InternalGroup> read() throws OrmException {
return readImpl(this::toInternalGroup);
}
@Override
public ResultSet<FieldBundle> readRaw() throws OrmException {
return readImpl(ElasticGroupIndex.this::toFieldBundle);
}
@Override
public String toString() {
return search.toString();
}
private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
try {
List<T> results = Collections.emptyList();
JestResult result = client.execute(search);
if (result.isSucceeded()) {
JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
if (obj.get("hits") != null) {
JsonArray json = obj.getAsJsonArray("hits");
results = Lists.newArrayListWithCapacity(json.size());
for (int i = 0; i < json.size(); i++) {
T mapperResult = mapper.apply(json.get(i).getAsJsonObject());
if (mapperResult != null) {
results.add(mapperResult);
}
}
}
} else {
log.error(result.getErrorMessage());
}
final List<T> r = Collections.unmodifiableList(results);
return new ResultSet<T>() {
@Override
public Iterator<T> iterator() {
return r.iterator();
}
@Override
public List<T> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
}
}
private InternalGroup toInternalGroup(JsonObject json) {
JsonElement source = json.get("_source");
if (source == null) {
source = json.getAsJsonObject().get("fields");
}
AccountGroup.UUID uuid =
new AccountGroup.UUID(
source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
// Use the GroupCache rather than depending on any stored fields in the
// document (of which there shouldn't be any).
return groupCache.get().get(uuid).orElse(null);
}
} }
} }

View File

@@ -14,14 +14,11 @@
package com.google.gerrit.elasticsearch; package com.google.gerrit.elasticsearch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties; import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
import com.google.gerrit.index.QueryOptions; import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.Schema; import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.DataSource; import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
@@ -32,30 +29,19 @@ import com.google.gerrit.server.index.project.ProjectField;
import com.google.gerrit.server.index.project.ProjectIndex; import com.google.gerrit.server.index.project.ProjectIndex;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectData; import com.google.gerrit.server.project.ProjectData;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import io.searchbox.client.JestResult; import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk; import io.searchbox.core.Bulk;
import io.searchbox.core.Bulk.Builder; import io.searchbox.core.Bulk.Builder;
import io.searchbox.core.Search;
import io.searchbox.core.search.sort.Sort; import io.searchbox.core.search.sort.Sort;
import io.searchbox.core.search.sort.Sort.Sorting; import io.searchbox.core.search.sort.Sort.Sorting;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData> public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData>
implements ProjectIndex { implements ProjectIndex {
@@ -70,8 +56,6 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
static final String PROJECTS = "projects"; static final String PROJECTS = "projects";
static final String PROJECTS_PREFIX = PROJECTS + "_"; static final String PROJECTS_PREFIX = PROJECTS + "_";
private static final Logger log = LoggerFactory.getLogger(ElasticProjectIndex.class);
private final ProjectMapping mapping; private final ProjectMapping mapping;
private final Provider<ProjectCache> projectCache; private final Provider<ProjectCache> projectCache;
@@ -108,7 +92,9 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
@Override @Override
public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts) public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
throws QueryParseException { throws QueryParseException {
return new QuerySource(p, opts); Sort sort = new Sort(ProjectField.NAME.getName(), Sorting.ASC);
sort.setIgnoreUnmapped();
return new ElasticQuerySource(p, opts.filterFields(IndexUtils::projectFields), PROJECTS, sort);
} }
@Override @Override
@@ -127,96 +113,16 @@ public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, P
return projectState.getProject().getName(); return projectState.getProject().getName();
} }
private class QuerySource implements DataSource<ProjectData> { @Override
private final Search search; protected ProjectData fromDocument(JsonObject json, Set<String> fields) {
private final Set<String> fields; JsonElement source = json.get("_source");
if (source == null) {
QuerySource(Predicate<ProjectData> p, QueryOptions opts) throws QueryParseException { source = json.getAsJsonObject().get("fields");
QueryBuilder qb = queryBuilder.toQueryBuilder(p);
fields = IndexUtils.projectFields(opts);
SearchSourceBuilder searchSource =
new SearchSourceBuilder()
.query(qb)
.from(opts.start())
.size(opts.limit())
.fields(Lists.newArrayList(fields));
Sort sort = new Sort(ProjectField.NAME.getName(), Sorting.ASC);
sort.setIgnoreUnmapped();
search =
new Search.Builder(searchSource.toString())
.addType(PROJECTS)
.addIndex(indexName)
.addSort(ImmutableList.of(sort))
.build();
} }
@Override Project.NameKey nameKey =
public int getCardinality() { new Project.NameKey(
return 10; source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
} return projectCache.get().get(nameKey).toProjectData();
@Override
public ResultSet<ProjectData> read() throws OrmException {
try {
List<ProjectData> results = Collections.emptyList();
JestResult result = client.execute(search);
if (result.isSucceeded()) {
JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
if (obj.get("hits") != null) {
JsonArray json = obj.getAsJsonArray("hits");
results = Lists.newArrayListWithCapacity(json.size());
for (int i = 0; i < json.size(); i++) {
results.add(toProjectData(json.get(i)));
}
}
} else {
log.error(result.getErrorMessage());
}
final List<ProjectData> r = Collections.unmodifiableList(results);
return new ResultSet<ProjectData>() {
@Override
public Iterator<ProjectData> iterator() {
return r.iterator();
}
@Override
public List<ProjectData> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
}
}
@Override
public ResultSet<FieldBundle> readRaw() throws OrmException {
// TOOD(hiesel): Make a generic implementation for Lucene/ES
throw new UnsupportedOperationException("not implemented");
}
@Override
public String toString() {
return search.toString();
}
private ProjectData toProjectData(JsonElement json) {
JsonElement source = json.getAsJsonObject().get("_source");
if (source == null) {
source = json.getAsJsonObject().get("fields");
}
Project.NameKey nameKey =
new Project.NameKey(
source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
return projectCache.get().get(nameKey).toProjectData();
}
} }
} }