Merge "Enable aliases for change query operators"

This commit is contained in:
Edwin Kempin
2019-10-29 12:28:40 +00:00
committed by Gerrit Code Review
6 changed files with 135 additions and 6 deletions

View File

@@ -3594,6 +3594,39 @@ information.
+
Default is false.
[[operator-alias]]
=== Section operator alias
Operator aliasing allows global aliases to be defined for query operators.
Currently only change queries are supported. The alias name is the git
config key name, and the operator being aliased is the git config value.
For example:
----
[operator-alias "change"]
oldage = age
number = change
----
This section is particularly useful to alias operator names which may be
long and clunky because they include a plugin name in them to a shorter
name without the plugin name.
Aliases are resolved dynamically at invocation time to any currently
loaded versions of plugins. If the alias points to an operator provided
by a plugin which is not currently loaded, or the plugin does not define
the operator, then "unsupported operator" is returned to the user.
Aliases will override existing operators. In the case of multiple aliases
with the same name, the last one defined will be used.
When the target of an alias doesn't exist, the operator with the name
of the alias will be used (if present). This enables an admin to config
the system to override a core operator with an operator provided by a
plugin when present and otherwise fall back to the operator provided by
core.
[[pack]]
=== Section pack

View File

@@ -29,6 +29,7 @@ import static com.google.gerrit.index.query.QueryParser.SINGLE_WORD;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.Nullable;
@@ -42,7 +43,9 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.antlr.runtime.tree.Tree;
/**
@@ -184,6 +187,7 @@ public abstract class QueryBuilder<T, Q extends QueryBuilder<T, Q>> {
protected final Definition<T, Q> builderDef;
private final ImmutableMap<String, OperatorFactory<T, Q>> opFactories;
protected Map<String, String> opAliases = Collections.emptyMap();
protected QueryBuilder(
Definition<T, Q> def,
@@ -220,6 +224,10 @@ public abstract class QueryBuilder<T, Q extends QueryBuilder<T, Q>> {
return toPredicate(QueryParser.parse(query));
}
public void setOperatorAliases(Map<String, String> opAliases) {
this.opAliases = opAliases;
}
/**
* Parse multiple user-supplied query strings into a list of predicates.
*
@@ -290,8 +298,12 @@ public abstract class QueryBuilder<T, Q extends QueryBuilder<T, Q>> {
@SuppressWarnings("unchecked")
private Predicate<T> operator(String name, String value) throws QueryParseException {
String opName = MoreObjects.firstNonNull(opAliases.get(name), name);
@SuppressWarnings("rawtypes")
OperatorFactory f = opFactories.get(name);
OperatorFactory f = opFactories.get(opName);
if (f == null && !opName.equals(name)) {
f = opFactories.get(name);
}
if (f == null) {
throw error("Unsupported operator " + name + ":" + value);
}

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2019 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.config;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.lib.Config;
@Singleton
public class OperatorAliasConfig {
private static final String SECTION = "operator-alias";
private static final String SUBSECTION_CHANGE = "change";
private final Config cfg;
private final Map<String, String> changeQueryOperatorAliases;
@Inject
OperatorAliasConfig(@GerritServerConfig Config cfg) {
this.cfg = cfg;
changeQueryOperatorAliases = new HashMap<>();
loadChangeOperatorAliases();
}
public Map<String, String> getChangeQueryOperatorAliases() {
return changeQueryOperatorAliases;
}
private void loadChangeOperatorAliases() {
for (String name : cfg.getNames(SECTION, SUBSECTION_CHANGE)) {
changeQueryOperatorAliases.put(name, cfg.getString(SECTION, SUBSECTION_CHANGE, name));
}
}
}

View File

@@ -63,6 +63,7 @@ import com.google.gerrit.server.account.VersionedAccountQueries;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.OperatorAliasConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
@@ -219,6 +220,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
final SubmitDryRun submitDryRun;
final GroupMembers groupMembers;
final Provider<AnonymousUser> anonymousUserProvider;
final OperatorAliasConfig operatorAliasConfig;
private final Provider<CurrentUser> self;
@@ -250,7 +252,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
GroupMembers groupMembers,
Provider<AnonymousUser> anonymousUserProvider) {
Provider<AnonymousUser> anonymousUserProvider,
OperatorAliasConfig operatorAliasConfig) {
this(
queryProvider,
rewriter,
@@ -277,7 +280,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
starredChangesUtil,
accountCache,
groupMembers,
anonymousUserProvider);
anonymousUserProvider,
operatorAliasConfig);
}
private Arguments(
@@ -306,7 +310,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
GroupMembers groupMembers,
Provider<AnonymousUser> anonymousUserProvider) {
Provider<AnonymousUser> anonymousUserProvider,
OperatorAliasConfig operatorAliasConfig) {
this.queryProvider = queryProvider;
this.rewriter = rewriter;
this.opFactories = opFactories;
@@ -333,6 +338,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
this.hasOperands = hasOperands;
this.groupMembers = groupMembers;
this.anonymousUserProvider = anonymousUserProvider;
this.operatorAliasConfig = operatorAliasConfig;
}
Arguments asUser(CurrentUser otherUser) {
@@ -362,7 +368,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
starredChangesUtil,
accountCache,
groupMembers,
anonymousUserProvider);
anonymousUserProvider,
operatorAliasConfig);
}
Arguments asUser(Account.Id otherId) {
@@ -407,6 +414,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
@Inject
ChangeQueryBuilder(Arguments args) {
this(mydef, args);
setupAliases();
}
@VisibleForTesting
@@ -415,6 +423,10 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
this.args = args;
}
private void setupAliases() {
setOperatorAliases(args.operatorAliasConfig.getChangeQueryOperatorAliases());
}
public Arguments getArgs() {
return args;
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -97,6 +98,31 @@ public class QueryChangeIT extends AbstractDaemonTest {
assertThat(result2.get(1).get(0)._moreChanges).isTrue();
}
@Test
@SuppressWarnings("unchecked")
@GerritConfig(name = "operator-alias.change.numberaliastest", value = "change")
public void aliasQuery() throws Exception {
String cId1 = createChange().getChangeId();
String cId2 = createChange().getChangeId();
int numericId1 = gApi.changes().id(cId1).get()._number;
int numericId2 = gApi.changes().id(cId2).get()._number;
QueryChanges queryChanges = queryChangesProvider.get();
queryChanges.addQuery("numberaliastest:12345");
queryChanges.addQuery("numberaliastest:" + numericId1);
queryChanges.addQuery("numberaliastest:" + numericId2);
List<List<ChangeInfo>> result =
(List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
assertThat(result).hasSize(3);
assertThat(result.get(0)).hasSize(0);
assertThat(result.get(1)).hasSize(1);
assertThat(result.get(2)).hasSize(1);
assertThat(result.get(1).get(0)._number).isEqualTo(numericId1);
assertThat(result.get(2).get(0)._number).isEqualTo(numericId2);
}
private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
for (ChangeInfo info : results) {
assertThat(info._moreChanges).isNull();

View File

@@ -27,7 +27,7 @@ public class FakeQueryBuilder extends ChangeQueryBuilder {
new ChangeQueryBuilder.Definition<>(FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, indexes, null, null, null, null, null, null, null));
null, null, null, null, indexes, null, null, null, null, null, null, null, null));
}
@Operator