/* * 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 io.crate.analyze.relations.*; import io.crate.analyze.symbol.Field; import io.crate.analyze.symbol.FieldReplacer; import io.crate.analyze.symbol.Symbol; import io.crate.collections.Sets2; import io.crate.metadata.Functions; import io.crate.metadata.Path; import io.crate.metadata.TransactionContext; import io.crate.metadata.table.Operation; import io.crate.sql.tree.QualifiedName; import javax.annotation.Nonnull; import java.util.*; import java.util.function.Function; public class MultiSourceSelect implements QueriedRelation { private final Map<QualifiedName, AnalyzedRelation> sources; private final Fields fields; private final List<JoinPair> joinPairs; private final Set<Symbol> requiredForQuery; private final Set<Field> canBeFetched; private final Optional<RemainingOrderBy> remainingOrderBy; private QualifiedName qualifiedName; private QuerySpec querySpec; /** * Create a new MultiSourceSelect which has it's sources transformed into subQueries which contain * any expressions that only affect a single source. * Example: * <pre> * select t1.x, t2.y from t1, t2 where t1.x = 10 * * becomes * * select t1.x, t2.y from * (select x from t1 where x = 10) t1, * (select y from t2) * </pre> */ public static MultiSourceSelect createWithPushDown(Functions functions, TransactionContext transactionContext, MultiSourceSelect mss, QuerySpec querySpec) { RelationSplitter splitter = new RelationSplitter( querySpec, mss.sources.values(), mss.joinPairs ); splitter.process(); Function<Field, Field> convertFieldToPointToNewRelations = Function.identity(); for (Map.Entry<QualifiedName, AnalyzedRelation> entry : mss.sources.entrySet()) { AnalyzedRelation relation = entry.getValue(); QuerySpec spec = splitter.getSpec(relation); QueriedRelation queriedRelation = Relations.applyQSToRelation(functions, transactionContext, relation, spec); Function<Field, Field> convertField = f -> mapFieldToNewRelation(f, relation, queriedRelation); querySpec = querySpec.copyAndReplace(FieldReplacer.bind(convertField)); entry.setValue(queriedRelation); convertFieldToPointToNewRelations = convertFieldToPointToNewRelations.andThen(convertField); } Function<? super Symbol, ? extends Symbol> convertFieldInSymbolsToNewRelations = FieldReplacer.bind(convertFieldToPointToNewRelations); if (splitter.remainingOrderBy().isPresent()) { splitter.remainingOrderBy().get().orderBy().replace(convertFieldInSymbolsToNewRelations); } for (JoinPair joinPair : mss.joinPairs) { joinPair.replaceCondition(convertFieldInSymbolsToNewRelations); } Set<Symbol> requiredForQuery = Sets2.transformedCopy(splitter.requiredForQuery(), convertFieldInSymbolsToNewRelations); Set<Field> canBeFetched = Sets2.transformedCopy(splitter.canBeFetched(), convertFieldToPointToNewRelations); return new MultiSourceSelect( mss.sources(), mss.fields(), querySpec, mss.joinPairs, requiredForQuery, canBeFetched, splitter.remainingOrderBy() ); } private static Field mapFieldToNewRelation(Field f, AnalyzedRelation oldRelation, QueriedRelation newRelation) { if (f.relation().equals(oldRelation)) { Field field = newRelation.getField(f.path(), Operation.READ); assert field != null : "Must be able to resolve field from injected relation: " + f; return field; } return f; } public MultiSourceSelect(Map<QualifiedName, AnalyzedRelation> sources, Collection<? extends Path> outputNames, QuerySpec querySpec, List<JoinPair> joinPairs) { assert sources.size() > 1 : "MultiSourceSelect requires at least 2 relations"; this.sources = sources; for (Map.Entry<QualifiedName, AnalyzedRelation> entry : sources.entrySet()) { entry.getValue().setQualifiedName(entry.getKey()); } this.querySpec = querySpec; this.joinPairs = joinPairs; assert outputNames.size() == querySpec.outputs().size() : "size of outputNames and outputSymbols must match"; fields = new Fields(outputNames.size()); Iterator<Symbol> outputsIterator = querySpec.outputs().iterator(); for (Path path : outputNames) { fields.add(path, new Field(this, path, outputsIterator.next().valueType())); } this.requiredForQuery = Collections.emptySet(); this.canBeFetched = Collections.emptySet(); this.remainingOrderBy = Optional.empty(); } private MultiSourceSelect(Map<QualifiedName, AnalyzedRelation> sources, Collection<Field> fields, QuerySpec querySpec, List<JoinPair> joinPairs, Set<Symbol> requiredForQuery, Set<Field> canBeFetched, Optional<RemainingOrderBy> remainingOrderBy) { this.sources = sources; this.joinPairs = joinPairs; this.querySpec = querySpec; this.fields = new Fields(fields.size()); for (Field field : fields) { this.fields.add(field.path(), new Field(this, field.path(), field.valueType())); } this.requiredForQuery = requiredForQuery; this.canBeFetched = canBeFetched; this.remainingOrderBy = remainingOrderBy; } public Set<Symbol> requiredForQuery() { return requiredForQuery; } public Set<Field> canBeFetched() { return canBeFetched; } public Map<QualifiedName, AnalyzedRelation> sources() { return sources; } public List<JoinPair> joinPairs() { return joinPairs; } @Override public <C, R> R accept(AnalyzedRelationVisitor<C, R> visitor, C context) { return visitor.visitMultiSourceSelect(this, context); } @Override public Field getField(Path path, Operation operation) throws UnsupportedOperationException { if (operation != Operation.READ) { throw new UnsupportedOperationException("getField on MultiSourceSelect is only supported for READ operations"); } return fields.get(path); } @Override public List<Field> fields() { return fields.asList(); } @Override public QualifiedName getQualifiedName() { return qualifiedName; } @Override public void setQualifiedName(@Nonnull QualifiedName qualifiedName) { this.qualifiedName = qualifiedName; } @Override public QuerySpec querySpec() { return querySpec; } public Optional<RemainingOrderBy> remainingOrderBy() { return remainingOrderBy; } @Override public String toString() { return "MSS{" + sources.keySet() + '}'; } }