LCOV - code coverage report
Current view: top level - index - FieldDef.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 47 56 83.9 %
Date: 2022-11-19 15:00:39 Functions: 23 23 100.0 %

          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 java.util.Objects.requireNonNull;
      20             : 
      21             : import com.google.common.base.CharMatcher;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.exceptions.StorageException;
      24             : import com.google.gerrit.index.SchemaFieldDefs.Getter;
      25             : import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
      26             : import com.google.gerrit.index.SchemaFieldDefs.Setter;
      27             : import java.io.IOException;
      28             : import java.sql.Timestamp;
      29             : import java.util.Optional;
      30             : 
      31             : /**
      32             :  * Definition of a field stored in the secondary index.
      33             :  *
      34             :  * <p>{@link FieldDef}-s must not be changed once introduced to the codebase. Instead, a new
      35             :  * FieldDef must be added and the old one removed from the schema (in two upgrade steps, see {@code
      36             :  * com.google.gerrit.index.IndexUpgradeValidator}).
      37             :  *
      38             :  * <p>Note that {@link FieldDef} does not override {@link Object#equals(Object)}. It relies on
      39             :  * instances being singletons so that the default (i.e. reference) comparison works.
      40             :  *
      41             :  * @param <I> input type from which documents are created and search results are returned.
      42             :  * @param <T> type that should be extracted from the input object when converting to an index
      43             :  *     document.
      44             :  */
      45             : public final class FieldDef<I, T> implements SchemaField<I, T> {
      46             :   public static FieldDef.Builder<String> exact(String name) {
      47         155 :     return new FieldDef.Builder<>(FieldType.EXACT, name);
      48             :   }
      49             : 
      50             :   public static FieldDef.Builder<String> fullText(String name) {
      51         155 :     return new FieldDef.Builder<>(FieldType.FULL_TEXT, name);
      52             :   }
      53             : 
      54             :   public static FieldDef.Builder<Integer> intRange(String name) {
      55         155 :     return new FieldDef.Builder<>(FieldType.INTEGER_RANGE, name).stored();
      56             :   }
      57             : 
      58             :   public static FieldDef.Builder<Integer> integer(String name) {
      59         155 :     return new FieldDef.Builder<>(FieldType.INTEGER, name);
      60             :   }
      61             : 
      62             :   public static FieldDef.Builder<String> prefix(String name) {
      63         155 :     return new FieldDef.Builder<>(FieldType.PREFIX, name);
      64             :   }
      65             : 
      66             :   public static FieldDef.Builder<byte[]> storedOnly(String name) {
      67         155 :     return new FieldDef.Builder<>(FieldType.STORED_ONLY, name).stored();
      68             :   }
      69             : 
      70             :   public static FieldDef.Builder<Timestamp> timestamp(String name) {
      71         155 :     return new FieldDef.Builder<>(FieldType.TIMESTAMP, name);
      72             :   }
      73             : 
      74             :   public static class Builder<T> {
      75             :     private final FieldType<T> type;
      76             :     private final String name;
      77             :     private boolean stored;
      78             : 
      79         155 :     public Builder(FieldType<T> type, String name) {
      80         155 :       this.type = requireNonNull(type);
      81         155 :       this.name = requireNonNull(name);
      82         155 :     }
      83             : 
      84             :     public Builder<T> stored() {
      85         155 :       this.stored = true;
      86         155 :       return this;
      87             :     }
      88             : 
      89             :     public <I> FieldDef<I, T> build(Getter<I, T> getter) {
      90         155 :       return new FieldDef<>(name, type, stored, false, getter, null);
      91             :     }
      92             : 
      93             :     public <I> FieldDef<I, T> build(Getter<I, T> getter, Setter<I, T> setter) {
      94         155 :       return new FieldDef<>(name, type, stored, false, getter, setter);
      95             :     }
      96             : 
      97             :     public <I> FieldDef<I, Iterable<T>> buildRepeatable(Getter<I, Iterable<T>> getter) {
      98         155 :       return new FieldDef<>(name, type, stored, true, getter, null);
      99             :     }
     100             : 
     101             :     public <I> FieldDef<I, Iterable<T>> buildRepeatable(
     102             :         Getter<I, Iterable<T>> getter, Setter<I, Iterable<T>> setter) {
     103         155 :       return new FieldDef<>(name, type, stored, true, getter, setter);
     104             :     }
     105             :   }
     106             : 
     107             :   private final String name;
     108             :   private final FieldType<?> type;
     109             :   /** Allow reading the actual data from the index. */
     110             :   private final boolean stored;
     111             : 
     112             :   private final boolean repeatable;
     113             :   private final Getter<I, T> getter;
     114             :   private final Optional<Setter<I, T>> setter;
     115             : 
     116             :   private FieldDef(
     117             :       String name,
     118             :       FieldType<?> type,
     119             :       boolean stored,
     120             :       boolean repeatable,
     121             :       Getter<I, T> getter,
     122         155 :       @Nullable Setter<I, T> setter) {
     123         155 :     checkArgument(
     124             :         !(repeatable && type == FieldType.INTEGER_RANGE),
     125             :         "Range queries against repeated fields are unsupported");
     126         155 :     this.name = checkName(name);
     127         155 :     this.type = requireNonNull(type);
     128         155 :     this.stored = stored;
     129         155 :     this.repeatable = repeatable;
     130         155 :     this.getter = requireNonNull(getter);
     131         155 :     this.setter = Optional.ofNullable(setter);
     132         155 :   }
     133             : 
     134             :   private static String checkName(String name) {
     135         155 :     CharMatcher m = CharMatcher.anyOf("abcdefghijklmnopqrstuvwxyz0123456789_");
     136         155 :     checkArgument(name != null && m.matchesAllOf(name), "illegal field name: %s", name);
     137         155 :     return name;
     138             :   }
     139             : 
     140             :   /** Returns name of the field. */
     141             :   @Override
     142             :   public String getName() {
     143         154 :     return name;
     144             :   }
     145             : 
     146             :   /** Returns type of the field; for repeatable fields, the inner type, not the iterable type. */
     147             :   @Override
     148             :   public FieldType<?> getType() {
     149         105 :     return type;
     150             :   }
     151             : 
     152             :   /** Returns whether the field should be stored in the index. */
     153             :   @Override
     154             :   public boolean isStored() {
     155         154 :     return stored;
     156             :   }
     157             : 
     158             :   /**
     159             :    * Get the field contents from the input object.
     160             :    *
     161             :    * @param input input object.
     162             :    * @return the field value(s) to index.
     163             :    */
     164             :   @Override
     165             :   @Nullable
     166             :   public T get(I input) {
     167             :     try {
     168         107 :       return getter.get(input);
     169           0 :     } catch (IOException e) {
     170           0 :       throw new StorageException(e);
     171             :     }
     172             :   }
     173             : 
     174             :   /**
     175             :    * Set the field contents back to an object. Used to reconstruct fields from indexed values. No-op
     176             :    * if the field can't be reconstructed.
     177             :    *
     178             :    * @param object input object.
     179             :    * @param doc indexed document
     180             :    * @return {@code true} if the field was set, {@code false} otherwise
     181             :    */
     182             :   @SuppressWarnings("unchecked")
     183             :   @Override
     184             :   public boolean setIfPossible(I object, StoredValue doc) {
     185         101 :     if (!setter.isPresent()) {
     186         101 :       return false;
     187             :     }
     188             : 
     189         100 :     if (FieldType.STRING_TYPES.stream().anyMatch(t -> t.getName().equals(getType().getName()))) {
     190         100 :       setter.get().set(object, (T) (isRepeatable() ? doc.asStrings() : doc.asString()));
     191         100 :       return true;
     192         100 :     } else if (FieldType.INTEGER_TYPES.stream()
     193         100 :         .anyMatch(t -> t.getName().equals(getType().getName()))) {
     194         100 :       setter.get().set(object, (T) (isRepeatable() ? doc.asIntegers() : doc.asInteger()));
     195         100 :       return true;
     196         100 :     } else if (FieldType.LONG.getName().equals(getType().getName())) {
     197           0 :       setter.get().set(object, (T) (isRepeatable() ? doc.asLongs() : doc.asLong()));
     198           0 :       return true;
     199         100 :     } else if (FieldType.STORED_ONLY.getName().equals(getType().getName())) {
     200         100 :       setter.get().set(object, (T) (isRepeatable() ? doc.asByteArrays() : doc.asByteArray()));
     201         100 :       return true;
     202           0 :     } else if (FieldType.TIMESTAMP.getName().equals(getType().getName())) {
     203           0 :       checkState(!isRepeatable(), "can't repeat timestamp values");
     204           0 :       setter.get().set(object, (T) doc.asTimestamp());
     205           0 :       return true;
     206             :     }
     207           0 :     return false;
     208             :   }
     209             : 
     210             :   /** Returns whether the field is repeatable. */
     211             :   @Override
     212             :   public boolean isRepeatable() {
     213         105 :     return repeatable;
     214             :   }
     215             : }

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