/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/
package io.crate.analyze;
import com.google.common.primitives.Booleans;
import io.crate.analyze.symbol.Symbol;
import io.crate.analyze.symbol.Symbols;
import io.crate.collections.Lists2;
import io.crate.exceptions.AmbiguousOrderByException;
import io.crate.metadata.TransactionContext;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
public class OrderBy implements Streamable {
private List<Symbol> orderBySymbols;
private boolean[] reverseFlags;
private Boolean[] nullsFirst;
public OrderBy(List<Symbol> orderBySymbols, boolean[] reverseFlags, Boolean[] nullsFirst) {
assert !orderBySymbols.isEmpty() : "orderBySymbols must not be empty";
assert orderBySymbols.size() == reverseFlags.length && reverseFlags.length == nullsFirst.length :
"size of symbols / reverseFlags / nullsFirst must match";
this.orderBySymbols = orderBySymbols;
this.reverseFlags = reverseFlags;
this.nullsFirst = nullsFirst;
}
private OrderBy() {
}
public List<Symbol> orderBySymbols() {
return orderBySymbols;
}
public boolean[] reverseFlags() {
return reverseFlags;
}
public Boolean[] nullsFirst() {
return nullsFirst;
}
public void normalize(EvaluatingNormalizer normalizer, TransactionContext transactionContext) {
normalizer.normalizeInplace(orderBySymbols, transactionContext);
}
public OrderBy subset(Collection<Integer> positions) {
List<Symbol> orderBySymbols = new ArrayList<>(positions.size());
Boolean[] nullsFirst = new Boolean[positions.size()];
boolean[] reverseFlags = new boolean[positions.size()];
int pos = 0;
for (Integer i : positions) {
orderBySymbols.add(Symbols.DEEP_COPY.apply(this.orderBySymbols.get(i)));
nullsFirst[pos] = this.nullsFirst[i];
reverseFlags[pos] = this.reverseFlags[i];
pos++;
}
return new OrderBy(orderBySymbols, reverseFlags, nullsFirst);
}
/**
* Create a new OrderBy with symbols that match the predicate
*/
@Nullable
public OrderBy subset(Predicate<? super Symbol> predicate) {
List<Integer> subSet = new ArrayList<>();
Integer i = 0;
for (Symbol orderBySymbol : orderBySymbols) {
if (predicate.test(orderBySymbol)) {
subSet.add(i);
}
i++;
}
if (subSet.isEmpty()) {
return null;
}
return subset(subSet);
}
public static void toStream(OrderBy orderBy, StreamOutput out) throws IOException {
orderBy.writeTo(out);
}
public static OrderBy fromStream(StreamInput in) throws IOException {
OrderBy orderBy = new OrderBy();
orderBy.readFrom(in);
return orderBy;
}
@Override
public void readFrom(StreamInput in) throws IOException {
int numOrderBy = in.readVInt();
reverseFlags = new boolean[numOrderBy];
for (int i = 0; i < numOrderBy; i++) {
reverseFlags[i] = in.readBoolean();
}
orderBySymbols = new ArrayList<>(numOrderBy);
for (int i = 0; i < numOrderBy; i++) {
orderBySymbols.add(Symbols.fromStream(in));
}
nullsFirst = new Boolean[numOrderBy];
for (int i = 0; i < numOrderBy; i++) {
nullsFirst[i] = in.readOptionalBoolean();
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(reverseFlags.length);
for (boolean reverseFlag : reverseFlags) {
out.writeBoolean(reverseFlag);
}
for (Symbol symbol : orderBySymbols) {
Symbols.toStream(symbol, out);
}
for (Boolean nullFirst : nullsFirst) {
out.writeOptionalBoolean(nullFirst);
}
}
public OrderBy copyAndReplace(Function<? super Symbol, ? extends Symbol> replaceFunction) {
return new OrderBy(Lists2.copyAndReplace(orderBySymbols, replaceFunction), reverseFlags, nullsFirst);
}
public void replace(Function<? super Symbol, ? extends Symbol> replaceFunction) {
ListIterator<Symbol> listIt = orderBySymbols.listIterator();
while (listIt.hasNext()) {
listIt.set(replaceFunction.apply(listIt.next()));
}
}
public OrderBy merge(@Nullable OrderBy otherOrderBy) {
if (otherOrderBy != null) {
List<Symbol> newOrderBySymbols = otherOrderBy.orderBySymbols();
List<Boolean> newReverseFlags = new ArrayList<>(Booleans.asList(otherOrderBy.reverseFlags()));
List<Boolean> newNullsFirst = new ArrayList<>(Arrays.asList(otherOrderBy.nullsFirst()));
for (int i = 0; i < orderBySymbols.size(); i++) {
Symbol orderBySymbol = orderBySymbols.get(i);
int idx = newOrderBySymbols.indexOf(orderBySymbol);
if (idx == -1) {
newOrderBySymbols.add(orderBySymbol);
newReverseFlags.add(reverseFlags[i]);
newNullsFirst.add(nullsFirst[i]);
} else {
if (newReverseFlags.get(idx) != reverseFlags[i]) {
throw new AmbiguousOrderByException(orderBySymbol);
}
if (newNullsFirst.get(idx) != nullsFirst[i]) {
throw new AmbiguousOrderByException(orderBySymbol);
}
}
}
this.orderBySymbols = newOrderBySymbols;
this.reverseFlags = Booleans.toArray(newReverseFlags);
this.nullsFirst = newNullsFirst.toArray(new Boolean[0]);
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderBy orderBy = (OrderBy) o;
return orderBySymbols.equals(orderBy.orderBySymbols) &&
Arrays.equals(reverseFlags, orderBy.reverseFlags) &&
Arrays.equals(nullsFirst, orderBy.nullsFirst);
}
@Override
public int hashCode() {
return Objects.hash(orderBySymbols, reverseFlags, nullsFirst);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("OrderBy{");
for (int i = 0; i < orderBySymbols.size(); i++) {
Symbol symbol = orderBySymbols.get(i);
sb.append(symbol);
sb.append(" ");
if (reverseFlags[i]) {
sb.append("ASC");
} else {
sb.append("DESC");
}
Boolean nullFirst = nullsFirst[i];
if (nullFirst != null) {
sb.append(" ");
sb.append(nullFirst ? "NULLS FIRST" : "NULLS LAST");
}
if (i + 1 < orderBySymbols.size()) {
sb.append(" ");
}
}
return sb.toString();
}
}