/*
* Licensed to 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.relations;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import io.crate.analyze.symbol.*;
import io.crate.metadata.ReplaceMode;
import io.crate.metadata.ReplacingSymbolVisitor;
import io.crate.operation.operator.AndOperator;
import io.crate.sql.tree.QualifiedName;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
class QuerySplittingVisitor extends ReplacingSymbolVisitor<QuerySplittingVisitor.Context> {
public static final QuerySplittingVisitor INSTANCE = new QuerySplittingVisitor();
private QuerySplittingVisitor() {
super(ReplaceMode.MUTATE);
}
public static class Context {
private AnalyzedRelation seenRelation;
private final Set<AnalyzedRelation> seenRelations = Collections.newSetFromMap(new IdentityHashMap<AnalyzedRelation, Boolean>());
private final Multimap<QualifiedName, Symbol> queries = HashMultimap.create();
private final List<JoinPair> joinPairs;
private boolean multiRelation = false;
private Symbol query;
public Context(List<JoinPair> joinPairs) {
this.joinPairs = joinPairs;
}
public Symbol query() {
return query;
}
public Multimap<QualifiedName, Symbol> queries() {
return queries;
}
}
public Context process(Symbol query, List<JoinPair> joinPairs) {
Context context = new Context(joinPairs);
context.query = process(query, context);
if (!context.multiRelation) {
assert context.seenRelation != null : "context.seenRelation must not be null";
context.queries.put(context.seenRelation.getQualifiedName(), context.query);
context.query = null;
}
return context;
}
@Override
public Symbol process(Symbol symbol, @Nullable Context context) {
assert context != null : "context must not be null";
context.seenRelation = null;
return super.process(symbol, context);
}
@Override
public Symbol visitField(Field field, Context context) {
context.seenRelation = field.relation();
return super.visitField(field, context);
}
@Override
public Symbol visitFunction(Function function, Context context) {
// ensure we come from bottom up with the last relation
// context.seenRelations.clear();
if (AndOperator.NAME.equals(function.info().ident().name())) {
context.multiRelation = false;
context.seenRelation = null;
Symbol left = process(function.arguments().get(0), context);
function.arguments().set(0, left);
boolean leftMultiRel = context.multiRelation;
AnalyzedRelation leftRelation = context.seenRelation;
context.multiRelation = false;
context.seenRelation = null;
Symbol right = process(function.arguments().get(1), context);
function.arguments().set(1, right);
boolean rightMultiRel = context.multiRelation;
AnalyzedRelation rightRelation = context.seenRelation;
if (leftMultiRel && rightMultiRel) {
context.multiRelation = true;
return function;
} else if (leftMultiRel) {
context.multiRelation = true;
if (rightRelation != null) {
context.queries.put(rightRelation.getQualifiedName(), right);
function.arguments().set(1, Literal.BOOLEAN_TRUE);
context.seenRelation = null;
return function;
}
// pass up, since we are multirel anyways
return function;
} else if (rightMultiRel) {
context.multiRelation = true;
if (leftRelation != null) {
context.queries.put(leftRelation.getQualifiedName(), left);
function.arguments().set(0, Literal.BOOLEAN_TRUE);
context.seenRelation = null;
return function;
}
// pass up, since we are multirel anyways
return function;
}
if (leftRelation != rightRelation) {
if (leftRelation == null) {
context.seenRelation = rightRelation;
context.multiRelation = false;
} else if (rightRelation == null) {
context.seenRelation = leftRelation;
context.multiRelation = false;
} else {
context.queries.put(leftRelation.getQualifiedName(), left);
function.arguments().set(0, Literal.BOOLEAN_TRUE);
context.seenRelation = rightRelation;
}
}
} else {
context.seenRelations.clear();
FieldsVisitor.visitFields(function, f -> context.seenRelations.add(f.relation()));
context.multiRelation = context.seenRelations.size() > 1;
if (context.seenRelations.size() == 1) {
context.seenRelation = Iterables.getOnlyElement(context.seenRelations);
if (JoinPairs.isOuterRelation(context.seenRelation.getQualifiedName(), context.joinPairs)) {
// don't split by marking as multi relation
context.multiRelation = true;
}
} else if (context.seenRelations.isEmpty()) {
context.seenRelation = null;
} else {
context.multiRelation = true;
context.seenRelation = null;
}
}
return function;
}
@Override
public Symbol visitMatchPredicate(MatchPredicate matchPredicate, Context context) {
AnalyzedRelation relation = null;
for (Field field : matchPredicate.identBoostMap().keySet()) {
if (relation == null) {
relation = field.relation();
} else if (!relation.equals(field.relation())) {
throw new IllegalArgumentException("Must not use columns from more than 1 relation inside the MATCH predicate");
}
}
context.seenRelation = relation;
return matchPredicate;
}
}