/* * 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.symbol.DefaultTraversalSymbolVisitor; import io.crate.analyze.symbol.Symbol; import io.crate.collections.Lists2; import io.crate.metadata.TransactionContext; import io.crate.operation.scalar.cast.CastFunctionResolver; import io.crate.types.DataType; import io.crate.types.DataTypes; import javax.annotation.Nullable; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class QuerySpec { private Optional<List<Symbol>> groupBy = Optional.empty(); private Optional<OrderBy> orderBy = Optional.empty(); private Optional<HavingClause> having = Optional.empty(); private List<Symbol> outputs; private WhereClause where = WhereClause.MATCH_ALL; private Optional<Symbol> limit = Optional.empty(); private Optional<Symbol> offset = Optional.empty(); private boolean hasAggregates = false; public Optional<List<Symbol>> groupBy() { return groupBy; } public QuerySpec groupBy(@Nullable List<Symbol> groupBy) { assert groupBy == null || groupBy.size() > 0 : "groupBy must not be empty"; this.groupBy = Optional.ofNullable(groupBy); return this; } public WhereClause where() { return where; } public QuerySpec where(@Nullable WhereClause where) { if (where == null) { this.where = WhereClause.MATCH_ALL; } else { this.where = where; } return this; } public Optional<Symbol> limit() { return limit; } public QuerySpec limit(Optional<Symbol> limit) { assert limit != null : "argument limit must not be null but absent"; this.limit = limit; return this; } public Optional<Symbol> offset() { return offset; } public QuerySpec offset(Optional<Symbol> offset) { this.offset = offset; return this; } public Optional<HavingClause> having() { return having; } public QuerySpec having(@Nullable HavingClause having) { if (having == null || !having.hasQuery() && !having.noMatch()) { this.having = Optional.empty(); } else { this.having = Optional.of(having); } return this; } public Optional<OrderBy> orderBy() { return orderBy; } public QuerySpec orderBy(@Nullable OrderBy orderBy) { this.orderBy = Optional.ofNullable(orderBy); return this; } public List<Symbol> outputs() { return outputs; } public QuerySpec outputs(List<Symbol> outputs) { this.outputs = outputs; return this; } public boolean hasAggregates() { return hasAggregates; } public QuerySpec hasAggregates(boolean hasAggregates) { this.hasAggregates = hasAggregates; return this; } public void normalize(EvaluatingNormalizer normalizer, TransactionContext context) { if (groupBy.isPresent()) { normalizer.normalizeInplace(groupBy.get(), context); } if (orderBy.isPresent()) { orderBy.get().normalize(normalizer, context); } if (outputs != null) { normalizer.normalizeInplace(outputs, context); } if (where != null && where != WhereClause.MATCH_ALL) { this.where(where.normalize(normalizer, context)); } if (having.isPresent()) { having = Optional.of(having.get().normalize(normalizer, context)); } } /** * Tries to cast the outputs to the given types. Types might contain Null values, which indicates to skip that * position. The iterator needs to return exactly the same number of types as there are outputs. * * @param types an iterable providing the types * @return -1 if all casts where successfully applied or the position of the failed cast */ public int castOutputs(Iterator<DataType> types) { int i = 0; ListIterator<Symbol> outputsIt = outputs.listIterator(); while (types.hasNext() && outputsIt.hasNext()) { DataType targetType = types.next(); assert targetType != null : "targetType must not be null"; Symbol output = outputsIt.next(); DataType sourceType = output.valueType(); if (!sourceType.equals(targetType)) { if (sourceType.isConvertableTo(targetType)) { Symbol castFunction = CastFunctionResolver.generateCastFunction(output, targetType, false); if (groupBy.isPresent()) { Collections.replaceAll(groupBy.get(), output, castFunction); } if (orderBy.isPresent()) { Collections.replaceAll(orderBy.get().orderBySymbols(), output, castFunction); } outputsIt.set(castFunction); } else if (!targetType.equals(DataTypes.UNDEFINED)) { return i; } } i++; } assert i == outputs.size() : "i must be equal to outputs.size()"; return -1; } /** * create a new QuerySpec which is a subset of this which contains only symbols which match the predicate */ public QuerySpec subset(Predicate<? super Symbol> predicate, boolean traverseFunctions) { if (hasAggregates) { throw new UnsupportedOperationException("Cannot create a subset of a querySpec if it has aggregations"); } QuerySpec newSpec = new QuerySpec() .limit(limit) .offset(offset); if (traverseFunctions) { newSpec.outputs(SubsetVisitor.filter(outputs, predicate)); } else { newSpec.outputs(outputs.stream().filter(predicate).collect(Collectors.toList())); } if (!where.hasQuery()) { newSpec.where(where); } else if (predicate.test(where.query())) { newSpec.where(where); } if (orderBy.isPresent()) { newSpec.orderBy(orderBy.get().subset(predicate)); } return newSpec; } private static class SubsetVisitor extends DefaultTraversalSymbolVisitor<SubsetVisitor.SubsetContext, Void> { static class SubsetContext { Predicate<? super Symbol> predicate; List<Symbol> outputs = new ArrayList<>(); } private static List<Symbol> filter(List<Symbol> outputs, Predicate<? super Symbol> predicate) { SubsetVisitor.SubsetContext ctx = new SubsetVisitor.SubsetContext(); ctx.predicate = predicate; SubsetVisitor visitor = new SubsetVisitor(); for (Symbol output : outputs) { visitor.process(output, ctx); } return ctx.outputs; } @Override protected Void visitSymbol(Symbol symbol, SubsetContext context) { if (context.predicate.test(symbol)) { context.outputs.add(symbol); } return null; } } public QuerySpec copyAndReplace(Function<? super Symbol, ? extends Symbol> replaceFunction) { QuerySpec newSpec = new QuerySpec() .limit(limit) .offset(offset) .hasAggregates(hasAggregates) .outputs(Lists2.copyAndReplace(outputs, replaceFunction)); if (!where.hasQuery()) { newSpec.where(where); } else { newSpec.where(new WhereClause(replaceFunction.apply(where.query()), where.docKeys().orElse(null), where.partitions())); } if (orderBy.isPresent()) { newSpec.orderBy(orderBy.get().copyAndReplace(replaceFunction)); } if (having.isPresent()) { HavingClause havingClause = having.get(); if (havingClause.hasQuery()) { newSpec.having(new HavingClause(replaceFunction.apply(havingClause.query))); } } if (groupBy.isPresent()) { newSpec.groupBy(Lists2.copyAndReplace(groupBy.get(), replaceFunction)); } return newSpec; } public void replace(Function<? super Symbol, ? extends Symbol> replaceFunction) { Lists2.replaceItems(outputs, replaceFunction); if (where.hasQuery()) { where = new WhereClause(replaceFunction.apply(where.query()), where.docKeys().orElse(null), where.partitions()); } if (orderBy.isPresent()) { orderBy.get().replace(replaceFunction); } if (groupBy.isPresent()) { Lists2.replaceItems(groupBy.get(), replaceFunction); } } /** * Visit all symbols present in this query spec. * <p> * (non-recursive, so function symbols won't be walked into) * </p> */ public void visitSymbols(Consumer<? super Symbol> consumer) { for (Symbol output : outputs) { consumer.accept(output); } if (where.hasQuery()) { consumer.accept(where.query()); } if (groupBy.isPresent()) { List<Symbol> groupBySymbols = groupBy.get(); for (Symbol groupBySymbol : groupBySymbols) { consumer.accept(groupBySymbol); } } if (having.isPresent()) { HavingClause havingClause = having.get(); if (havingClause.hasQuery()) { consumer.accept(havingClause.query()); } } if (orderBy.isPresent()) { OrderBy orderBy = this.orderBy.get(); for (Symbol orderBySymbol : orderBy.orderBySymbols()) { consumer.accept(orderBySymbol); } } limit.ifPresent(consumer); offset.ifPresent(consumer); } @Override public String toString() { return String.format(Locale.ENGLISH, "QS{ SELECT %s WHERE %s GROUP BY %s HAVING %s ORDER BY %s LIMIT %s OFFSET %s}", outputs, where, groupBy, having, orderBy, limit, offset); } }