/* * 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.cassandra.index.sasi.utils; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import org.apache.cassandra.io.util.FileUtils; /** * Range Union Iterator is used to return sorted stream of elements from multiple RangeIterator instances. * * PriorityQueue is used as a sorting mechanism for the ranges, where each computeNext() operation would poll * from the queue (and push when done), which returns range that contains the smallest element, because * sorting is done on the moving window of range iteration {@link RangeIterator#getCurrent()}. Once retrieved * the smallest element (return candidate) is attempted to be merged with other ranges, because there could * be equal elements in adjacent ranges, such ranges are poll'ed only if their {@link RangeIterator#getCurrent()} * equals to the return candidate. * * @param <K> The type used to sort ranges. * @param <D> The container type which is going to be returned by {@link Iterator#next()}. */ @SuppressWarnings("resource") public class RangeUnionIterator<K extends Comparable<K>, D extends CombinedValue<K>> extends RangeIterator<K, D> { private final PriorityQueue<RangeIterator<K, D>> ranges; private RangeUnionIterator(Builder.Statistics<K, D> statistics, PriorityQueue<RangeIterator<K, D>> ranges) { super(statistics); this.ranges = ranges; } public D computeNext() { RangeIterator<K, D> head = null; while (!ranges.isEmpty()) { head = ranges.poll(); if (head.hasNext()) break; FileUtils.closeQuietly(head); } if (head == null || !head.hasNext()) return endOfData(); D candidate = head.next(); List<RangeIterator<K, D>> processedRanges = new ArrayList<>(); if (head.hasNext()) processedRanges.add(head); else FileUtils.closeQuietly(head); while (!ranges.isEmpty()) { // peek here instead of poll is an optimization // so we can re-insert less ranges back if candidate // is less than head of the current range. RangeIterator<K, D> range = ranges.peek(); int cmp = candidate.get().compareTo(range.getCurrent()); assert cmp <= 0; if (cmp < 0) { break; // candidate is smaller than next token, return immediately } else if (cmp == 0) { candidate.merge(range.next()); // consume and merge range = ranges.poll(); // re-prioritize changed range if (range.hasNext()) processedRanges.add(range); else FileUtils.closeQuietly(range); } } ranges.addAll(processedRanges); return candidate; } protected void performSkipTo(K nextToken) { List<RangeIterator<K, D>> changedRanges = new ArrayList<>(); while (!ranges.isEmpty()) { if (ranges.peek().getCurrent().compareTo(nextToken) >= 0) break; RangeIterator<K, D> head = ranges.poll(); if (head.getMaximum().compareTo(nextToken) >= 0) { head.skipTo(nextToken); changedRanges.add(head); continue; } FileUtils.closeQuietly(head); } ranges.addAll(changedRanges.stream().collect(Collectors.toList())); } public void close() throws IOException { ranges.forEach(FileUtils::closeQuietly); } public static <K extends Comparable<K>, D extends CombinedValue<K>> Builder<K, D> builder() { return new Builder<>(); } public static <K extends Comparable<K>, D extends CombinedValue<K>> RangeIterator<K, D> build(List<RangeIterator<K, D>> tokens) { return new Builder<K, D>().add(tokens).build(); } public static class Builder<K extends Comparable<K>, D extends CombinedValue<K>> extends RangeIterator.Builder<K, D> { public Builder() { super(IteratorType.UNION); } protected RangeIterator<K, D> buildIterator() { switch (rangeCount()) { case 1: return ranges.poll(); default: return new RangeUnionIterator<>(statistics, ranges); } } } }