LCOV - code coverage report
Current view: top level - index - Schema.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 93 113 82.3 %
Date: 2022-11-19 15:00:39 Functions: 30 32 93.8 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.index;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.base.Preconditions.checkState;
      19             : import static com.google.common.collect.ImmutableList.toImmutableList;
      20             : import static com.google.common.collect.ImmutableMap.toImmutableMap;
      21             : import static com.google.common.collect.ImmutableSet.toImmutableSet;
      22             : 
      23             : import com.google.common.base.MoreObjects;
      24             : import com.google.common.collect.ImmutableList;
      25             : import com.google.common.collect.ImmutableMap;
      26             : import com.google.common.collect.ImmutableSet;
      27             : import com.google.common.collect.Sets;
      28             : import com.google.common.flogger.FluentLogger;
      29             : import com.google.gerrit.common.Nullable;
      30             : import com.google.gerrit.exceptions.StorageException;
      31             : import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
      32             : import java.util.ArrayList;
      33             : import java.util.Arrays;
      34             : import java.util.Collections;
      35             : import java.util.List;
      36             : import java.util.Objects;
      37             : import java.util.Optional;
      38             : import java.util.Set;
      39             : import java.util.function.Function;
      40             : 
      41             : /** Specific version of a secondary index schema. */
      42             : public class Schema<T> {
      43         154 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      44             : 
      45         154 :   public static class Builder<T> {
      46         154 :     private final List<SchemaField<T, ?>> searchFields = new ArrayList<>();
      47         154 :     private final List<IndexedField<T, ?>> indexedFields = new ArrayList<>();
      48             : 
      49         154 :     private Optional<Integer> version = Optional.empty();
      50             : 
      51             :     public Builder<T> version(int version) {
      52         154 :       this.version = Optional.of(version);
      53         154 :       return this;
      54             :     }
      55             : 
      56             :     public Builder<T> add(Schema<T> schema) {
      57         154 :       this.indexedFields.addAll(schema.getIndexFields().values());
      58         154 :       this.searchFields.addAll(schema.getSchemaFields().values());
      59         154 :       if (!version.isPresent()) {
      60         154 :         version(schema.getVersion() + 1);
      61             :       }
      62         154 :       return this;
      63             :     }
      64             : 
      65             :     @SafeVarargs
      66             :     public final Builder<T> add(FieldDef<T, ?>... fields) {
      67         154 :       return add(ImmutableList.copyOf(fields));
      68             :     }
      69             : 
      70             :     public final Builder<T> add(ImmutableList<FieldDef<T, ?>> fields) {
      71         154 :       this.searchFields.addAll(fields);
      72         154 :       return this;
      73             :     }
      74             : 
      75             :     @SafeVarargs
      76             :     public final Builder<T> remove(FieldDef<T, ?>... fields) {
      77         154 :       this.searchFields.removeAll(Arrays.asList(fields));
      78         154 :       return this;
      79             :     }
      80             : 
      81             :     @SafeVarargs
      82             :     public final Builder<T> addSearchSpecs(IndexedField<T, ?>.SearchSpec... searchSpecs) {
      83         154 :       return addSearchSpecs(ImmutableList.copyOf(searchSpecs));
      84             :     }
      85             : 
      86             :     public Builder<T> addSearchSpecs(ImmutableList<IndexedField<T, ?>.SearchSpec> searchSpecs) {
      87         154 :       for (IndexedField<T, ?>.SearchSpec searchSpec : searchSpecs) {
      88         154 :         checkArgument(
      89         154 :             this.indexedFields.contains(searchSpec.getField()),
      90             :             "%s spec can only be added to the schema that contains %s field",
      91         154 :             searchSpec.getName(),
      92         154 :             searchSpec.getField().name());
      93         154 :       }
      94         154 :       this.searchFields.addAll(searchSpecs);
      95         154 :       return this;
      96             :     }
      97             : 
      98             :     @SafeVarargs
      99             :     public final Builder<T> addIndexedFields(IndexedField<T, ?>... fields) {
     100         154 :       return addIndexedFields(ImmutableList.copyOf(fields));
     101             :     }
     102             : 
     103             :     public Builder<T> addIndexedFields(ImmutableList<IndexedField<T, ?>> indexedFields) {
     104         154 :       this.indexedFields.addAll(indexedFields);
     105         154 :       return this;
     106             :     }
     107             : 
     108             :     @SafeVarargs
     109             :     public final Builder<T> remove(IndexedField<T, ?>.SearchSpec... searchSpecs) {
     110         153 :       this.searchFields.removeAll(Arrays.asList(searchSpecs));
     111         153 :       return this;
     112             :     }
     113             : 
     114             :     @SafeVarargs
     115             :     public final Builder<T> remove(IndexedField<T, ?>... indexedFields) {
     116         153 :       for (IndexedField<T, ?> field : indexedFields) {
     117         153 :         ImmutableMap<String, ? extends IndexedField<T, ?>.SearchSpec> searchSpecs =
     118         153 :             field.getSearchSpecs();
     119         153 :         checkArgument(
     120         153 :             !searchSpecs.values().stream().anyMatch(this.searchFields::contains),
     121             :             "Field %s can be only removed from schema after all of its searches are removed.",
     122         153 :             field.name());
     123             :       }
     124         153 :       this.indexedFields.removeAll(Arrays.asList(indexedFields));
     125         153 :       return this;
     126             :     }
     127             : 
     128             :     public Schema<T> build() {
     129         154 :       checkState(version.isPresent());
     130         154 :       return new Schema<>(
     131         154 :           version.get(), ImmutableList.copyOf(indexedFields), ImmutableList.copyOf(searchFields));
     132             :     }
     133             :   }
     134             : 
     135             :   public static class Values<T> {
     136             :     private final SchemaField<T, ?> field;
     137             :     private final Iterable<?> values;
     138             : 
     139           7 :     private Values(SchemaField<T, ?> field, Iterable<?> values) {
     140           7 :       this.field = field;
     141           7 :       this.values = values;
     142           7 :     }
     143             : 
     144             :     public SchemaField<T, ?> getField() {
     145           7 :       return field;
     146             :     }
     147             : 
     148             :     public Iterable<?> getValues() {
     149           7 :       return values;
     150             :     }
     151             :   }
     152             : 
     153             :   private static <T> SchemaField<T, ?> checkSame(SchemaField<T, ?> f1, SchemaField<T, ?> f2) {
     154         116 :     checkState(f1 == f2, "Mismatched %s fields: %s != %s", f1.getName(), f1, f2);
     155         116 :     return f1;
     156             :   }
     157             : 
     158             :   private final ImmutableSet<String> storedFields;
     159             : 
     160             :   private final ImmutableMap<String, SchemaField<T, ?>> schemaFields;
     161             :   private final ImmutableMap<String, IndexedField<T, ?>> indexedFields;
     162             : 
     163             :   private int version;
     164             : 
     165             :   private Schema(
     166             :       int version,
     167             :       ImmutableList<IndexedField<T, ?>> indexedFields,
     168         154 :       ImmutableList<SchemaField<T, ?>> schemaFields) {
     169         154 :     this.version = version;
     170             : 
     171         154 :     this.indexedFields =
     172         154 :         indexedFields.stream().collect(toImmutableMap(IndexedField::name, Function.identity()));
     173         154 :     this.schemaFields =
     174         154 :         schemaFields.stream().collect(toImmutableMap(SchemaField::getName, Function.identity()));
     175             : 
     176         154 :     Set<String> duplicateKeys =
     177         154 :         Sets.intersection(this.schemaFields.keySet(), this.indexedFields.keySet());
     178         154 :     checkArgument(
     179         154 :         duplicateKeys.isEmpty(),
     180             :         "DuplicateKeys found %s, indexFields:%s, schemaFields: %s",
     181             :         duplicateKeys,
     182         154 :         this.indexedFields.keySet(),
     183         154 :         this.schemaFields.keySet());
     184         154 :     this.storedFields =
     185         154 :         schemaFields.stream()
     186         154 :             .filter(SchemaField::isStored)
     187         154 :             .map(SchemaField::getName)
     188         154 :             .collect(toImmutableSet());
     189         154 :   }
     190             : 
     191             :   public final int getVersion() {
     192         154 :     return version;
     193             :   }
     194             : 
     195             :   /**
     196             :    * Get all fields in this schema.
     197             :    *
     198             :    * <p>This is primarily useful for iteration. Most callers should prefer one of the helper methods
     199             :    * {@link #getField(SchemaField, SchemaField...)} or {@link #hasField(SchemaField)} to looking up
     200             :    * fields by name
     201             :    *
     202             :    * @return all fields in this schema indexed by name.
     203             :    */
     204             :   public final ImmutableMap<String, SchemaField<T, ?>> getSchemaFields() {
     205         154 :     return schemaFields;
     206             :   }
     207             : 
     208             :   public final ImmutableMap<String, IndexedField<T, ?>> getIndexFields() {
     209         154 :     return indexedFields;
     210             :   }
     211             : 
     212             :   /** Returns all fields in this schema where {@link FieldDef#isStored()} is true. */
     213             :   public final ImmutableSet<String> getStoredFields() {
     214         151 :     return storedFields;
     215             :   }
     216             : 
     217             :   /**
     218             :    * Look up fields in this schema.
     219             :    *
     220             :    * @param first the preferred field to look up.
     221             :    * @param rest additional fields to look up.
     222             :    * @return the first field in the schema matching {@code first} or {@code rest}, in order, or
     223             :    *     absent if no field matches.
     224             :    */
     225             :   @SafeVarargs
     226             :   public final Optional<SchemaField<T, ?>> getField(
     227             :       SchemaField<T, ?> first, SchemaField<T, ?>... rest) {
     228           0 :     SchemaField<T, ?> field = getSchemaField(first);
     229           0 :     if (field != null) {
     230           0 :       return Optional.of(checkSame(field, first));
     231             :     }
     232           0 :     for (SchemaField<T, ?> f : rest) {
     233           0 :       field = getSchemaField(first);
     234           0 :       if (field != null) {
     235           0 :         return Optional.of(checkSame(field, f));
     236             :       }
     237             :     }
     238           0 :     return Optional.empty();
     239             :   }
     240             : 
     241             :   /**
     242             :    * Check whether a field is present in this schema.
     243             :    *
     244             :    * @param field field to look up.
     245             :    * @return whether the field is present.
     246             :    */
     247             :   public final boolean hasField(SchemaField<T, ?> field) {
     248         116 :     SchemaField<T, ?> f = getSchemaField(field);
     249         116 :     if (f == null) {
     250          12 :       return false;
     251             :     }
     252         116 :     checkSame(f, field);
     253         116 :     return true;
     254             :   }
     255             : 
     256             :   public final boolean hasField(String fieldName) {
     257           2 :     return this.getSchemaField(fieldName) != null;
     258             :   }
     259             : 
     260             :   private SchemaField<T, ?> getSchemaField(SchemaField<T, ?> field) {
     261         116 :     return getSchemaField(field.getName());
     262             :   }
     263             : 
     264             :   public SchemaField<T, ?> getSchemaField(String fieldName) {
     265         116 :     return schemaFields.get(fieldName);
     266             :   }
     267             : 
     268             :   private @Nullable Values<T> fieldValues(
     269             :       T obj, SchemaField<T, ?> f, ImmutableSet<String> skipFields) {
     270           7 :     if (skipFields.contains(f.getName())) {
     271           4 :       return null;
     272             :     }
     273             : 
     274             :     Object v;
     275             :     try {
     276           7 :       v = f.get(obj);
     277           0 :     } catch (StorageException e) {
     278             :       // StorageException is thrown when the object is not found. On this case,
     279             :       // it is pointless to make further attempts for each field, so propagate
     280             :       // the exception to return an empty list.
     281           0 :       logger.atSevere().withCause(e).log("error getting field %s of %s", f.getName(), obj);
     282           0 :       throw e;
     283           0 :     } catch (RuntimeException e) {
     284           0 :       logger.atSevere().withCause(e).log("error getting field %s of %s", f.getName(), obj);
     285           0 :       return null;
     286           7 :     }
     287           7 :     if (v == null) {
     288           7 :       return null;
     289           7 :     } else if (f.isRepeatable()) {
     290           7 :       return new Values<>(f, (Iterable<?>) v);
     291             :     } else {
     292           7 :       return new Values<>(f, Collections.singleton(v));
     293             :     }
     294             :   }
     295             : 
     296             :   /**
     297             :    * Build all fields in the schema from an input object.
     298             :    *
     299             :    * <p>Null values are omitted, as are fields which cause errors, which are logged.
     300             :    *
     301             :    * @param obj input object.
     302             :    * @param skipFields set of field names to skip when indexing the document
     303             :    * @return all non-null field values from the object.
     304             :    */
     305             :   public final Iterable<Values<T>> buildFields(T obj, ImmutableSet<String> skipFields) {
     306             :     try {
     307           7 :       return schemaFields.values().stream()
     308           7 :           .map(f -> fieldValues(obj, f, skipFields))
     309           7 :           .filter(Objects::nonNull)
     310           7 :           .collect(toImmutableList());
     311             : 
     312           0 :     } catch (StorageException e) {
     313           0 :       return ImmutableList.of();
     314             :     }
     315             :   }
     316             : 
     317             :   @Override
     318             :   public String toString() {
     319           0 :     return MoreObjects.toStringHelper(this)
     320           0 :         .addValue(indexedFields.keySet())
     321           0 :         .addValue(schemaFields.keySet())
     322           0 :         .toString();
     323             :   }
     324             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750