All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.search.facet.FacetFieldProcessorByArray Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.solr.search.facet;

import java.io.IOException;
import java.util.Date;
import java.util.function.IntFunction;

import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.search.Query;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.facet.SlotAcc.SlotContext;

import static org.apache.solr.search.facet.FacetContext.SKIP_FACET;

/**
 * Base class for DV/UIF accumulating counts into an array by ordinal.  It's
 * for {@link org.apache.lucene.index.SortedDocValues} and {@link org.apache.lucene.index.SortedSetDocValues} only.
 * It can handle terms (strings), not numbers directly but those encoded as terms, and is multi-valued capable.
 */
abstract class FacetFieldProcessorByArray extends FacetFieldProcessor {
  BytesRefBuilder prefixRef;
  int startTermIndex;
  int endTermIndex;
  int nTerms;
  int nDocs;
  int maxSlots;

  int allBucketsSlot = -1;  // slot for the primary Accs (countAcc, collectAcc)

  FacetFieldProcessorByArray(FacetContext fcontext, FacetField freq, SchemaField sf) {
    super(fcontext, freq, sf);
  }

  abstract protected void findStartAndEndOrds() throws IOException;

  abstract protected void collectDocs() throws IOException;

  /** this BytesRef may be shared across calls and should be deep-cloned if necessary */
  abstract protected BytesRef lookupOrd(int ord) throws IOException;

  @Override
  public void process() throws IOException {
    super.process();
    response = calcFacets();
  }

  private SimpleOrderedMap calcFacets() throws IOException {
    SimpleOrderedMap refineResult = null;
    boolean skipThisFacet = (fcontext.flags & SKIP_FACET) != 0;

    if (fcontext.facetInfo != null) {
      refineResult = refineFacets();
      // if we've seen this facet bucket, then refining can be done.  If we haven't, we still
      // only need to continue if we need allBuckets or numBuckets info.
      if (skipThisFacet || !freq.allBuckets) return refineResult;
    }

    String prefix = freq.prefix;
    if (prefix == null || prefix.length() == 0) {
      prefixRef = null;
    } else {
      prefixRef = new BytesRefBuilder();
      prefixRef.copyChars(prefix);
    }

    findStartAndEndOrds();

    if (refineResult != null) {
      if (freq.allBuckets) {
        // count is irrelevant, but hardcoded in collect(...), so intercept/mask normal counts.
        // Set here to prevent createAccs(...) from creating a 1-slot countAcc that will fail with AIOOBE
        countAcc = SlotAcc.DEV_NULL_SLOT_ACC;
        createAccs(nDocs, 1);
        otherAccs = accs; // accs is created above and set on allBucketsAcc; but during collection, setNextReader is called on otherAccs.
        allBucketsAcc = new SpecialSlotAcc(fcontext, null, -1, accs, 0);
        collectDocs();

        SimpleOrderedMap allBuckets = new SimpleOrderedMap<>();
        allBuckets.add("count", allBucketsAcc.getSpecialCount());
        allBucketsAcc.setValues(allBuckets, -1); // -1 slotNum is unused for SpecialSlotAcc
        refineResult.add("allBuckets", allBuckets);
        return refineResult;
      }
    }

    maxSlots = nTerms;

    if (freq.allBuckets) {
      allBucketsSlot = maxSlots++;
    }

    createCollectAcc(nDocs, maxSlots);

    if (freq.allBuckets) {
      allBucketsAcc = new SpecialSlotAcc(fcontext, collectAcc, allBucketsSlot, otherAccs, 0);
    }

    collectDocs();

    return super.findTopSlots(nTerms, nTerms,
        slotNum -> { // getBucketValFromSlotNum
          try {
            return (Comparable) sf.getType().toObject(sf, lookupOrd(slotNum + startTermIndex));
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
        },
        obj -> valueObjToString(obj)
    );
  }

  private static String valueObjToString(Object obj) {
    return (obj instanceof Date) ? ((Date)obj).toInstant().toString() : obj.toString();
  }

                                                           
  /**
   * SlotContext to use during all {@link SlotAcc} collection.
   *
   * @see #lookupOrd
   */
  public IntFunction slotContext = (slotNum) -> {
    try {
      Object value = sf.getType().toObject(sf, lookupOrd(slotNum + startTermIndex));
      Query q = makeBucketQuery(valueObjToString(value));
      assert null != q : "null query for: '" + value + "'";
      return new SlotContext(q);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  };
}