/* * 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.base.MoreObjects; import com.google.common.collect.Iterators; import io.crate.analyze.symbol.Literal; import io.crate.analyze.symbol.Symbol; import io.crate.analyze.symbol.Symbols; import io.crate.analyze.symbol.ValueSymbolVisitor; import io.crate.analyze.where.DocKeys; import io.crate.metadata.TransactionContext; import io.crate.operation.operator.AndOperator; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; import java.io.IOException; import java.util.*; public class WhereClause extends QueryClause implements Streamable { public static final WhereClause MATCH_ALL = new WhereClause(Literal.BOOLEAN_TRUE); public static final WhereClause NO_MATCH = new WhereClause(Literal.BOOLEAN_FALSE); private Optional<Set<Symbol>> clusteredBy = Optional.empty(); private Optional<DocKeys> docKeys = Optional.empty(); private List<String> partitions = new ArrayList<>(); public WhereClause(StreamInput in) throws IOException { readFrom(in); } public WhereClause(@Nullable Symbol normalizedQuery, @Nullable DocKeys docKeys, @Nullable List<String> partitions) { super(normalizedQuery); docKeys(docKeys); if (partitions != null) { this.partitions = partitions; } } public WhereClause(@Nullable Symbol query) { super(query); } public WhereClause normalize(EvaluatingNormalizer normalizer, TransactionContext transactionContext) { if (noMatch || query == null) { return this; } Symbol normalizedQuery = normalizer.normalize(query, transactionContext); if (normalizedQuery == query) { return this; } WhereClause normalizedWhereClause = new WhereClause(normalizedQuery, docKeys.orElse(null), partitions); normalizedWhereClause.clusteredBy = clusteredBy; return normalizedWhereClause; } public Optional<Set<Symbol>> clusteredBy() { return clusteredBy; } @Nullable public Set<String> routingValues() { if (clusteredBy.isPresent()) { HashSet<String> result = new HashSet<>(clusteredBy.get().size()); Iterators.addAll(result, Iterators.transform( clusteredBy.get().iterator(), ValueSymbolVisitor.STRING.function)); return result; } else { return null; } } public void clusteredBy(@Nullable Set<Symbol> clusteredBy) { assert this != NO_MATCH && this != MATCH_ALL : "may not set clusteredByLiteral on MATCH_ALL/NO_MATCH singleton"; if (clusteredBy != null && !clusteredBy.isEmpty()) { this.clusteredBy = Optional.of(clusteredBy); } } public void partitions(List<Literal> partitions) { assert this != NO_MATCH && this != MATCH_ALL : "may not set partitions on MATCH_ALL/NO_MATCH singleton"; for (Literal partition : partitions) { this.partitions.add(ValueSymbolVisitor.STRING.process(partition)); } } public void docKeys(@Nullable DocKeys docKeys) { assert this != NO_MATCH && this != MATCH_ALL : "may not set docKeys on MATCH_ALL/NO_MATCH singleton"; if (docKeys == null) { this.docKeys = Optional.empty(); } else { this.docKeys = Optional.of(docKeys); } } public Optional<DocKeys> docKeys() { return docKeys; } /** * Returns a predefined list of partitions this query can be executed on * instead of the entire partition set. * <p> * If the list is empty no prefiltering can be done. * <p> * Note that the NO_MATCH case has to be tested separately. */ public List<String> partitions() { return partitions; } @Override public void readFrom(StreamInput in) throws IOException { if (in.readBoolean()) { query = Symbols.fromStream(in); } else { noMatch = in.readBoolean(); } } @Override public void writeTo(StreamOutput out) throws IOException { if (query != null) { out.writeBoolean(true); Symbols.toStream(query, out); } else { out.writeBoolean(false); out.writeBoolean(noMatch); } } @Override public String toString() { MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this); if (noMatch()) { helper.add("NO_MATCH", true); } else if (!hasQuery()) { helper.add("MATCH_ALL", true); } else { helper.add("query", query); } return helper.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof WhereClause)) return false; WhereClause that = (WhereClause) o; if (query != null ? !query.equals(that.query) : that.query != null) return false; if (noMatch != that.noMatch) return false; if (!docKeys.equals(that.docKeys)) return false; if (!clusteredBy.equals(that.clusteredBy)) return false; if (partitions != null ? !partitions.equals(that.partitions) : that.partitions != null) return false; return true; } @Override public int hashCode() { int result = query != null ? query.hashCode() : 0; result = 31 * result + (noMatch ? 1 : 0); result = 31 * result + (clusteredBy != null ? clusteredBy.hashCode() : 0); result = 31 * result + (partitions != null ? partitions.hashCode() : 0); return result; } public boolean hasVersions() { return docKeys.isPresent() && docKeys.get().withVersions(); } /** * Adds another query to this WhereClause. * <p> * The result is either a new WhereClause or the same (but modified) instance. */ public WhereClause add(Symbol otherQuery) { assert !docKeys.isPresent() : "Cannot add otherQuery if there are docKeys in the WhereClause"; if (this == MATCH_ALL) { return new WhereClause(otherQuery); } if (this == NO_MATCH) { // NO_MATCH & anything is still NO_MATCH return NO_MATCH; } this.query = AndOperator.of(this.query, otherQuery); return this; } }