Merge "Add filter for mentioned threads in Comments Tab"
diff --git a/java/com/google/gerrit/index/IndexedField.java b/java/com/google/gerrit/index/IndexedField.java
index dc47a1b..e44f562 100644
--- a/java/com/google/gerrit/index/IndexedField.java
+++ b/java/com/google/gerrit/index/IndexedField.java
@@ -258,7 +258,7 @@
   }
 
   public SearchSpec longSearch(String name) {
-    checkState(fieldType().equals(LONG_TYPE));
+    checkState(fieldType().equals(LONG_TYPE) || fieldType().equals(ITERABLE_LONG_TYPE));
     return addSearchSpec(name, SearchOption.EXACT);
   }
 
diff --git a/java/com/google/gerrit/index/testing/BUILD b/java/com/google/gerrit/index/testing/BUILD
index a30eaca..9af2598 100644
--- a/java/com/google/gerrit/index/testing/BUILD
+++ b/java/com/google/gerrit/index/testing/BUILD
@@ -23,5 +23,6 @@
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
+        "//proto:entities_java_proto",
     ],
 )
diff --git a/java/com/google/gerrit/index/testing/TestIndexedFields.java b/java/com/google/gerrit/index/testing/TestIndexedFields.java
new file mode 100644
index 0000000..f80b8a1
--- /dev/null
+++ b/java/com/google/gerrit/index/testing/TestIndexedFields.java
@@ -0,0 +1,209 @@
+// Copyright (C) 2022 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.index.testing;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gerrit.entities.converter.ChangeProtoConverter;
+import com.google.gerrit.index.IndexedField;
+import com.google.gerrit.index.SchemaFieldDefs.Getter;
+import com.google.gerrit.index.SchemaFieldDefs.Setter;
+import com.google.gerrit.proto.Entities;
+import com.google.gerrit.proto.Entities.Change;
+import com.google.gerrit.proto.Entities.Change_Id;
+import java.io.IOException;
+import java.sql.Timestamp;
+
+/**
+ * Collection of {@link IndexedField}, used in unit tests.
+ *
+ * <p>The list of {@link IndexedField} below are field types, that are currently supported and used
+ * in different index implementations
+ *
+ * <p>They are used in unit tests to make sure these types can be extracted to index and assigned
+ * back to object.
+ */
+public final class TestIndexedFields {
+
+  /** Test input object for {@link IndexedField} */
+  public static class TestIndexedData {
+
+    /** Key that is used to index to identify indexed object */
+    private Object key;
+
+    /** Field value that is extracted from this indexed object to the index document. */
+    private Object testFieldValue;
+
+    public Object getTestField() {
+      return testFieldValue;
+    }
+
+    public void setTestFieldValue(Object testFieldValue) {
+      this.testFieldValue = testFieldValue;
+    }
+
+    public Object getKey() {
+      return key;
+    }
+
+    public void setKey(Object key) {
+      this.key = key;
+    }
+  }
+
+  /** Setter for {@link TestIndexedData} */
+  private static class TestIndexedDataSetter<T> implements Setter<TestIndexedData, T> {
+    @Override
+    public void set(TestIndexedData testIndexedData, T value) {
+      testIndexedData.setTestFieldValue(value);
+    }
+  }
+
+  /** Getter for {@link TestIndexedData} */
+  @SuppressWarnings("unchecked")
+  private static class TestIndexedDataGetter<T> implements Getter<TestIndexedData, T> {
+    @Override
+    public T get(TestIndexedData input) throws IOException {
+      return (T) input.getTestField();
+    }
+  }
+
+  public static <T> TestIndexedDataSetter<T> setter() {
+    return new TestIndexedDataSetter<>();
+  }
+
+  public static <T> TestIndexedDataGetter<T> getter() {
+    return new TestIndexedDataGetter<>();
+  }
+
+  public static final IndexedField<TestIndexedData, Integer> INTEGER_FIELD =
+      IndexedField.<TestIndexedData>integerBuilder("IntegerTestField").build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Integer>.SearchSpec INTEGER_FIELD_SPEC =
+      INTEGER_FIELD.integer("integer_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<Integer>> ITERABLE_INTEGER_FIELD =
+      IndexedField.<TestIndexedData>iterableIntegerBuilder("IterableIntegerTestField")
+          .build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<Integer>>.SearchSpec
+      ITERABLE_INTEGER_FIELD_SPEC = ITERABLE_INTEGER_FIELD.integer("iterable_integer_test");
+
+  public static final IndexedField<TestIndexedData, Integer> INTEGER_RANGE_FIELD =
+      IndexedField.<TestIndexedData>integerBuilder("IntegerRangeTestField")
+          .build(TestIndexedFields.getter(), TestIndexedFields.setter());
+  public static final IndexedField<TestIndexedData, Integer>.SearchSpec INTEGER_RANGE_FIELD_SPEC =
+      INTEGER_RANGE_FIELD.range("integer_range_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<Integer>>
+      ITERABLE_INTEGER_RANGE_FIELD =
+          IndexedField.<TestIndexedData>iterableIntegerBuilder("IterableIntegerRangeTestField")
+              .build(TestIndexedFields.getter(), TestIndexedFields.setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<Integer>>.SearchSpec
+      ITERABLE_INTEGER_RANGE_FIELD_SPEC =
+          ITERABLE_INTEGER_RANGE_FIELD.range("iterable_integer_range_test");
+
+  public static final IndexedField<TestIndexedData, Long> LONG_FIELD =
+      IndexedField.<TestIndexedData>longBuilder("LongTestField").build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Long>.SearchSpec LONG_FIELD_SPEC =
+      LONG_FIELD.longSearch("long_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<Long>> ITERABLE_LONG_FIELD =
+      IndexedField.<TestIndexedData, Iterable<Long>>builder(
+              "IterableLongTestField", IndexedField.ITERABLE_LONG_TYPE)
+          .build(TestIndexedFields.getter(), TestIndexedFields.setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<Long>>.SearchSpec
+      ITERABLE_LONG_FIELD_SPEC = ITERABLE_LONG_FIELD.longSearch("iterable_long_test");
+
+  public static final IndexedField<TestIndexedData, Long> LONG_RANGE_FIELD =
+      IndexedField.<TestIndexedData>longBuilder("LongRangeTestField")
+          .build(TestIndexedFields.getter(), TestIndexedFields.setter());
+
+  public static final IndexedField<TestIndexedData, Long>.SearchSpec LONG_RANGE_FIELD_SPEC =
+      LONG_RANGE_FIELD.range("long_range_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<Long>> ITERABLE_LONG_RANGE_FIELD =
+      IndexedField.<TestIndexedData, Iterable<Long>>builder(
+              "IterableLongRangeTestField", IndexedField.ITERABLE_LONG_TYPE)
+          .build(TestIndexedFields.getter(), TestIndexedFields.setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<Long>>.SearchSpec
+      ITERABLE_LONG_RANGE_FIELD_SPEC = ITERABLE_LONG_RANGE_FIELD.range("iterable_long_range_test");
+
+  public static final IndexedField<TestIndexedData, Timestamp> TIMESTAMP_FIELD =
+      IndexedField.<TestIndexedData>timestampBuilder("TimestampTestField")
+          .build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Timestamp>.SearchSpec TIMESTAMP_FIELD_SPEC =
+      TIMESTAMP_FIELD.timestamp("timestamp_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<String>> ITERABLE_STRING_FIELD =
+      IndexedField.<TestIndexedData>iterableStringBuilder("IterableStringTestField")
+          .build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<String>>.SearchSpec
+      ITERABLE_STRING_FIELD_SPEC = ITERABLE_STRING_FIELD.fullText("iterable_test_string");
+
+  public static final IndexedField<TestIndexedData, String> STRING_FIELD =
+      IndexedField.<TestIndexedData>stringBuilder("StringTestField").build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, String>.SearchSpec STRING_FIELD_SPEC =
+      STRING_FIELD.fullText("string_test");
+
+  public static final IndexedField<TestIndexedData, Iterable<byte[]>> ITERABLE_STORED_BYTE_FIELD =
+      IndexedField.<TestIndexedData>iterableByteArrayBuilder("IterableByteTestField")
+          .stored()
+          .build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, Iterable<byte[]>>.SearchSpec
+      ITERABLE_STORED_BYTE_SPEC = ITERABLE_STORED_BYTE_FIELD.storedOnly("iterable_byte_test");
+
+  public static final IndexedField<TestIndexedData, byte[]> STORED_BYTE_FIELD =
+      IndexedField.<TestIndexedData>byteArrayBuilder("ByteTestField")
+          .stored()
+          .build(getter(), setter());
+
+  public static final IndexedField<TestIndexedData, byte[]>.SearchSpec STORED_BYTE_SPEC =
+      STORED_BYTE_FIELD.storedOnly("byte_test");
+
+  public static final IndexedField<TestIndexedData, Entities.Change> STORED_PROTO_FIELD =
+      IndexedField.<TestIndexedData, Entities.Change>builder(
+              "TestChange", new TypeToken<Entities.Change>() {})
+          .stored()
+          .build(getter(), setter(), ChangeProtoConverter.INSTANCE);
+
+  public static final IndexedField<TestIndexedData, Entities.Change>.SearchSpec
+      STORED_PROTO_FIELD_SPEC = STORED_PROTO_FIELD.storedOnly("test_change");
+
+  public static final IndexedField<TestIndexedData, Iterable<Entities.Change>>
+      ITERABLE_STORED_PROTO_FIELD =
+          IndexedField.<TestIndexedData, Iterable<Entities.Change>>builder(
+                  "IterableTestChange", new TypeToken<Iterable<Entities.Change>>() {})
+              .stored()
+              .build(getter(), setter(), ChangeProtoConverter.INSTANCE);
+
+  public static final IndexedField<TestIndexedData, Iterable<Entities.Change>>.SearchSpec
+      ITERABLE_PROTO_FIELD_SPEC = ITERABLE_STORED_PROTO_FIELD.storedOnly("iterable_test_change");
+
+  public static Change createChangeProto(int id) {
+    return Entities.Change.newBuilder()
+        .setChangeId(Change_Id.newBuilder().setId(id).build())
+        .build();
+  }
+
+  private TestIndexedFields() {}
+}
diff --git a/javatests/com/google/gerrit/server/index/IndexedFieldTest.java b/javatests/com/google/gerrit/server/index/IndexedFieldTest.java
index e0ab019..6a62ed1 100644
--- a/javatests/com/google/gerrit/server/index/IndexedFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/IndexedFieldTest.java
@@ -16,21 +16,37 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.index.testing.TestIndexedFields.INTEGER_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.INTEGER_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.INTEGER_RANGE_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_INTEGER_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_INTEGER_RANGE_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_LONG_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_LONG_RANGE_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_PROTO_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_STORED_BYTE_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_STORED_BYTE_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_STORED_PROTO_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_STRING_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.ITERABLE_STRING_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.LONG_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.LONG_RANGE_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.STORED_BYTE_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.STORED_BYTE_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.STORED_PROTO_FIELD;
+import static com.google.gerrit.index.testing.TestIndexedFields.STORED_PROTO_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.STRING_FIELD_SPEC;
+import static com.google.gerrit.index.testing.TestIndexedFields.TIMESTAMP_FIELD_SPEC;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.reflect.TypeToken;
-import com.google.gerrit.entities.converter.ChangeProtoConverter;
 import com.google.gerrit.index.IndexedField;
-import com.google.gerrit.index.SchemaFieldDefs.Getter;
-import com.google.gerrit.index.SchemaFieldDefs.Setter;
 import com.google.gerrit.index.StoredValue;
 import com.google.gerrit.index.testing.FakeStoredValue;
+import com.google.gerrit.index.testing.TestIndexedFields;
+import com.google.gerrit.index.testing.TestIndexedFields.TestIndexedData;
 import com.google.gerrit.proto.Entities;
-import com.google.gerrit.proto.Entities.Change;
-import com.google.gerrit.proto.Entities.Change_Id;
 import com.google.gerrit.proto.Protos;
-import java.io.IOException;
 import java.io.Serializable;
 import java.nio.charset.StandardCharsets;
 import java.sql.Timestamp;
@@ -47,122 +63,19 @@
 @RunWith(Theories.class)
 public class IndexedFieldTest {
 
-  /** Test input object for {@link IndexedField} */
-  static class TestIndexedData {
-
-    private Object testField;
-
-    public Object getTestField() {
-      return testField;
-    }
-
-    public void setTestField(Object testField) {
-      this.testField = testField;
-    }
-  }
-
-  private static class TestIndexedDataSetter<T> implements Setter<TestIndexedData, T> {
-    @Override
-    public void set(TestIndexedData testIndexedData, T value) {
-      testIndexedData.setTestField(value);
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  private static class TestIndexedDataGetter<T> implements Getter<TestIndexedData, T> {
-    @Override
-    public T get(TestIndexedData input) throws IOException {
-      return (T) input.getTestField();
-    }
-  }
-
-  public static <T> TestIndexedDataSetter<T> setter() {
-    return new TestIndexedDataSetter<>();
-  }
-
-  public static <T> TestIndexedDataGetter<T> getter() {
-    return new TestIndexedDataGetter<>();
-  }
-
-  static final IndexedField<TestIndexedData, Integer> INTEGER_FIELD =
-      IndexedField.<TestIndexedData>integerBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Integer>.SearchSpec INTEGER_FIELD_SPEC =
-      INTEGER_FIELD.integer("test");
-
-  static final IndexedField<TestIndexedData, Iterable<Integer>> ITERABLE_INTEGER_FIELD =
-      IndexedField.<TestIndexedData>iterableIntegerBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Iterable<Integer>>.SearchSpec
-      ITERABLE_INTEGER_FIELD_SPEC = ITERABLE_INTEGER_FIELD.integer("test");
-
-  static final IndexedField<TestIndexedData, Iterable<String>> ITERABLE_STRING_FIELD =
-      IndexedField.<TestIndexedData>iterableStringBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Long> LONG_FIELD =
-      IndexedField.<TestIndexedData>longBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Long>.SearchSpec LONG_FIELD_SPEC =
-      LONG_FIELD.longSearch("test");
-
-  static final IndexedField<TestIndexedData, Timestamp> TIMESTAMP_FIELD =
-      IndexedField.<TestIndexedData>timestampBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Timestamp>.SearchSpec TIMESTAMP_FIELD_SPEC =
-      TIMESTAMP_FIELD.timestamp("test");
-
-  static final IndexedField<TestIndexedData, Iterable<String>>.SearchSpec
-      ITERABLE_STRING_FIELD_SPEC = ITERABLE_STRING_FIELD.fullText("test");
-
-  static final IndexedField<TestIndexedData, String> STRING_FIELD =
-      IndexedField.<TestIndexedData>stringBuilder("TestField").build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, String>.SearchSpec STRING_FIELD_SPEC =
-      STRING_FIELD.fullText("test");
-
-  static final IndexedField<TestIndexedData, Iterable<byte[]>> ITERABLE_STORED_BYTE_FIELD =
-      IndexedField.<TestIndexedData>iterableByteArrayBuilder("TestField")
-          .stored()
-          .build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, Iterable<byte[]>>.SearchSpec
-      ITERABLE_STORED_BYTE_SPEC = ITERABLE_STORED_BYTE_FIELD.storedOnly("test");
-
-  static final IndexedField<TestIndexedData, byte[]> STORED_BYTE_FIELD =
-      IndexedField.<TestIndexedData>byteArrayBuilder("TestField")
-          .stored()
-          .build(getter(), setter());
-
-  static final IndexedField<TestIndexedData, byte[]>.SearchSpec STORED_BYTE_SPEC =
-      STORED_BYTE_FIELD.storedOnly("test");
-
-  static final IndexedField<TestIndexedData, Entities.Change> STORED_PROTO_FIELD =
-      IndexedField.<TestIndexedData, Entities.Change>builder(
-              "TestChange", new TypeToken<Entities.Change>() {})
-          .stored()
-          .build(getter(), setter(), ChangeProtoConverter.INSTANCE);
-
-  static final IndexedField<TestIndexedData, Entities.Change>.SearchSpec STORED_PROTO_FIELD_SPEC =
-      STORED_PROTO_FIELD.storedOnly("test_change");
-
-  static final IndexedField<TestIndexedData, Iterable<Entities.Change>>
-      ITERABLE_STORED_PROTO_FIELD =
-          IndexedField.<TestIndexedData, Iterable<Entities.Change>>builder(
-                  "TestChange", new TypeToken<Iterable<Entities.Change>>() {})
-              .stored()
-              .build(getter(), setter(), ChangeProtoConverter.INSTANCE);
-
-  static final IndexedField<TestIndexedData, Iterable<Entities.Change>>.SearchSpec
-      ITERABLE_PROTO_FIELD_SPEC = ITERABLE_STORED_PROTO_FIELD.storedOnly("test_change");
-
   @DataPoints("nonProtoTypes")
   public static final ImmutableList<
           Entry<IndexedField<TestIndexedData, ?>.SearchSpec, Serializable>>
       fieldToStoredValue =
           new ImmutableMap.Builder<IndexedField<TestIndexedData, ?>.SearchSpec, Serializable>()
               .put(INTEGER_FIELD_SPEC, 123456)
+              .put(INTEGER_RANGE_FIELD_SPEC, 123456)
               .put(ITERABLE_INTEGER_FIELD_SPEC, ImmutableList.of(123456, 654321))
+              .put(ITERABLE_INTEGER_RANGE_FIELD_SPEC, ImmutableList.of(123456, 654321))
               .put(LONG_FIELD_SPEC, 123456L)
+              .put(LONG_RANGE_FIELD_SPEC, 123456L)
+              .put(ITERABLE_LONG_FIELD_SPEC, ImmutableList.of(123456L, 654321L))
+              .put(ITERABLE_LONG_RANGE_FIELD_SPEC, ImmutableList.of(123456L, 654321L))
               .put(TIMESTAMP_FIELD_SPEC, new Timestamp(1234567L))
               .put(STRING_FIELD_SPEC, "123456")
               .put(ITERABLE_STRING_FIELD_SPEC, ImmutableList.of("123456"))
@@ -180,9 +93,11 @@
       protoFieldToStoredValue =
           ImmutableMap.<IndexedField<TestIndexedData, ?>.SearchSpec, Serializable>of(
                   STORED_PROTO_FIELD_SPEC,
-                  createChangeProto(12345),
+                  TestIndexedFields.createChangeProto(12345),
                   ITERABLE_PROTO_FIELD_SPEC,
-                  ImmutableList.of(createChangeProto(12345), createChangeProto(54321)))
+                  ImmutableList.of(
+                      TestIndexedFields.createChangeProto(12345),
+                      TestIndexedFields.createChangeProto(54321)))
               .entrySet()
               .asList();
 
@@ -201,7 +116,7 @@
 
   @Test
   public void testSetIfPossible_protoFromBytes() {
-    Entities.Change changeProto = createChangeProto(12345);
+    Entities.Change changeProto = TestIndexedFields.createChangeProto(12345);
     StoredValue storedValue = new FakeStoredValue(Protos.toByteArray(changeProto));
     TestIndexedData testIndexedData = new TestIndexedData();
     STORED_PROTO_FIELD_SPEC.setIfPossible(testIndexedData, storedValue);
@@ -211,7 +126,8 @@
   @Test
   public void testSetIfPossible_iterableProtoFromIterableBytes() {
     ImmutableList<Entities.Change> changeProtos =
-        ImmutableList.of(createChangeProto(12345), createChangeProto(54321));
+        ImmutableList.of(
+            TestIndexedFields.createChangeProto(12345), TestIndexedFields.createChangeProto(54321));
     StoredValue storedValue =
         new FakeStoredValue(
             changeProtos.stream()
@@ -257,10 +173,4 @@
     assertThat(STORED_BYTE_FIELD.isProtoIterableType()).isFalse();
     assertThat(ITERABLE_STORED_BYTE_FIELD.isProtoType()).isFalse();
   }
-
-  private static Change createChangeProto(int id) {
-    return Entities.Change.newBuilder()
-        .setChangeId(Change_Id.newBuilder().setId(id).build())
-        .build();
-  }
 }