All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.speedment.runtime.core.internal.component.sql.optimizer.FilterSortedSkipOptimizer Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
 *
 * Licensed 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.
 */
package com.speedment.runtime.core.internal.component.sql.optimizer;

import com.speedment.runtime.config.identifier.ColumnIdentifier;
import com.speedment.runtime.core.component.sql.Metrics;
import com.speedment.runtime.core.component.sql.SqlStreamOptimizer;
import com.speedment.runtime.core.component.sql.SqlStreamOptimizerInfo;
import com.speedment.runtime.core.db.AsynchronousQueryResult;
import com.speedment.runtime.core.db.DbmsType;
import com.speedment.runtime.core.internal.stream.builder.action.reference.FilterAction;
import com.speedment.runtime.core.internal.stream.builder.action.reference.LimitAction;
import com.speedment.runtime.core.internal.stream.builder.action.reference.SkipAction;
import com.speedment.runtime.core.internal.stream.builder.action.reference.SortedComparatorAction;
import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil;
import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.RenderResult;
import com.speedment.runtime.core.stream.Pipeline;
import com.speedment.runtime.core.stream.action.Action;
import com.speedment.runtime.field.EnumField;
import com.speedment.runtime.field.comparator.CombinedComparator;
import com.speedment.runtime.field.comparator.FieldComparator;
import com.speedment.runtime.field.comparator.NullOrder;
import com.speedment.runtime.field.comparator.ReferenceFieldComparator;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;

import static com.speedment.runtime.core.db.DbmsType.SkipLimitSupport.NONE;
import static com.speedment.runtime.core.db.DbmsType.SkipLimitSupport.ONLY_AFTER_SORTED;
import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isContainingOnlyFieldPredicate;
import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isSortedActionWithFieldPredicate;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

/**
 * This Optimizer takes care of the following case:
 * 
    *
  • a) Zero or more filter() operations *
  • b) Zero or more sorted() operations *
  • c) Zero or more skip() operations *
  • d) Zero or more limit() operations *
* * No other operations must be in the sequence a-d or within the * individual items a-d. All parameters in a and b must be obtained via * fields. Failure to any of these rules will make the Optimizer reject * optimization. Steps a) and b) may swap places. * * Thus, this optimizer can handle a (FILTER*, SORTED*, SKIP*, LIMIT*) or * (SORTED*, LIMIT*, SKIP*, LIMIT*) pattern where all non-primitive parameters * are all Field derived * * @author Per Minborg * @param entity type */ public final class FilterSortedSkipOptimizer implements SqlStreamOptimizer { private final FilterOperation FILTER_OPERATION = new FilterOperation(); private final SortedOperation SORTED_OPERATION = new SortedOperation(); private final SkipOperation SKIP_OPERATION = new SkipOperation(); private final LimitOperation LIMIT_OPERATION = new LimitOperation(); private final List> FILTER_SORTED_SKIP_LIMIT_PATH = Arrays.asList( FILTER_OPERATION, SORTED_OPERATION, SKIP_OPERATION, LIMIT_OPERATION ); private final List> SORTED_FILTER_SKIP_LIMIT_PATH = Arrays.asList( SORTED_OPERATION, FILTER_OPERATION, SKIP_OPERATION, LIMIT_OPERATION ); // FILTER <-> SORTED // This optimizer can handle a (FILTER*,SORTED*,SKIP*, LIMIT*) pattern where filter and sorted parameters are all Field derived @Override public Metrics metrics(Pipeline initialPipeline, DbmsType dbmsType) { requireNonNull(initialPipeline); requireNonNull(dbmsType); final DbmsType.SkipLimitSupport skipLimitSupport = dbmsType.getSkipLimitSupport(); final AtomicInteger filterCounter = new AtomicInteger(); final AtomicInteger orderCounter = new AtomicInteger(); final AtomicInteger skipCounter = new AtomicInteger(); final AtomicInteger limitCounter = new AtomicInteger(); traverse(initialPipeline, $ -> filterCounter.incrementAndGet(), $ -> orderCounter.incrementAndGet(), $ -> skipCounter.incrementAndGet(), $ -> limitCounter.incrementAndGet() ); if (skipLimitSupport == ONLY_AFTER_SORTED && orderCounter.get() == 0) { // Just decline. There are other optimizer that handles just filtering better return Metrics.empty(); } if (skipLimitSupport == NONE) { return Metrics.of(filterCounter.get() + orderCounter.get(), filterCounter.get(), orderCounter.get(), 0, 0); } return Metrics.of( filterCounter.get() + orderCounter.get() + skipCounter.get() + limitCounter.get(), filterCounter.get(), orderCounter.get(), skipCounter.get() > 0 ? 1 : 0, limitCounter.get() > 0 ? 1 : 0 ); } @Override @SuppressWarnings("unchecked") public

P optimize( final P initialPipeline, final SqlStreamOptimizerInfo info, final AsynchronousQueryResult query ) { requireNonNull(initialPipeline); requireNonNull(info); requireNonNull(query); final DbmsType dbmsType = info.getDbmsType(); final DbmsType.SkipLimitSupport skipLimitSupport = dbmsType.getSkipLimitSupport(); final List> filters = new ArrayList<>(); final List> sorteds = new ArrayList<>(); final List> skips = new ArrayList<>(); final List> limits = new ArrayList<>(); traverse(initialPipeline, filters::add, sorteds::add, skips::add, limits::add); final List values = new ArrayList<>(); final StringBuilder sql = new StringBuilder(); sql.append(info.getSqlSelect()); if (!filters.isEmpty()) { @SuppressWarnings("unchecked") List> predicates = filters.stream() .map(FilterAction::getPredicate) .map(p -> (Predicate) p) .collect(toList()); final RenderResult rr = StreamTerminatorUtil.renderSqlWhere( dbmsType, info.getSqlColumnNamer(), info.getSqlDatabaseTypeFunction(), predicates ); final String whereFragmentSql = rr.getSql(); if (!whereFragmentSql.isEmpty()) { sql.append(" WHERE ").append(whereFragmentSql); values.addAll(rr.getValues()); } } if (!sorteds.isEmpty()) { final List> fieldComparators = new ArrayList<>(); for (int i = sorteds.size() - 1; i >= 0; i--) { final SortedComparatorAction sortedAction = sorteds.get(i); @SuppressWarnings("unchecked") final Comparator comparator = sortedAction.getComparator(); if (comparator instanceof FieldComparator) { @SuppressWarnings("unchecked") final FieldComparator fieldComparator = (FieldComparator) sortedAction.getComparator(); fieldComparators.add(fieldComparator); } else if (comparator instanceof CombinedComparator) { @SuppressWarnings("unchecked") final CombinedComparator combinedComparator = (CombinedComparator) sortedAction.getComparator(); combinedComparator.stream() .map(c -> (FieldComparator) c) .forEachOrdered(fieldComparators::add); } else { // We sort on a field that we do not know how to handle. Fallback to no optimization. return initialPipeline; } } if (!fieldComparators.isEmpty()) { sql.append(" ORDER BY "); // Iterate backwards final Set> columns = new HashSet<>(); int cnt = 0; for (FieldComparator fieldComparator : fieldComparators) { final ColumnIdentifier columnIdentifier = fieldComparator.getField().identifier(); // Some databases (e.g. SQL Server) only allows distinct columns in ORDER BY if (columns.add(columnIdentifier)) { if (cnt++ != 0) { sql.append(", "); } boolean isReversed = fieldComparator.isReversed(); String fieldName = info.getSqlColumnNamer().apply(fieldComparator.getField()); final NullOrder effectiveNullOrder = isReversed ? fieldComparator.getNullOrder().reversed() : fieldComparator.getNullOrder(); // Specify NullOrder pre column if nulls are first if (effectiveNullOrder == NullOrder.FIRST) { if (dbmsType.getSortByNullOrderInsertion() == DbmsType.SortByNullOrderInsertion.PRE) { sql.append(fieldName).append("IS NOT NULL, "); } if (dbmsType.getSortByNullOrderInsertion() == DbmsType.SortByNullOrderInsertion.PRE_WITH_CASE) { sql.append("CASE WHEN ").append(fieldName).append(" IS NULL THEN 0 ELSE 1 END, "); } } sql.append(fieldName); if (String.class.equals(info.getSqlDatabaseTypeFunction().apply(fieldComparator.getField()))) { sql.append(dbmsType.getCollateFragment().getSql()); } if (isReversed) { sql.append(" DESC"); } else { sql.append(" ASC"); } // Specify NullOrder post column if (effectiveNullOrder == NullOrder.FIRST && dbmsType.getSortByNullOrderInsertion() == DbmsType.SortByNullOrderInsertion.POST) { sql.append(" NULLS FIRST"); } } } } } final String finalSql; if (skipLimitSupport == NONE) { finalSql = sql.toString(); initialPipeline.removeIf(a -> filters.contains(a) || sorteds.contains(a)); } else { final long sumSkip = skips.stream().mapToLong(SkipAction::getSkip).sum(); final long minLimit = limits.stream().mapToLong(LimitAction::getLimit).min().orElse(Long.MAX_VALUE); finalSql = dbmsType .applySkipLimit(sql.toString(), values, sumSkip, minLimit); initialPipeline.removeIf(a -> filters.contains(a) || sorteds.contains(a) || skips.contains(a) || limits.contains(a)); } query.setSql(finalSql); query.setValues(values); return initialPipeline; } private void traverse(Pipeline pipeline, final Consumer> filterConsumer, final Consumer> sortedConsumer, final Consumer> skipConsumer, final Consumer> limitConsumer ) { if (pipeline.isEmpty()) { return; } final Consumers consumers = new Consumers<>(filterConsumer, sortedConsumer, skipConsumer, limitConsumer); final Action firstAction = pipeline.getFirst(); // The path is the way we can walk the stream pipeline // and still satisfy the requirement on this optimizer // There are two paths: // Sorted*,Filter*,Skip*,Limit* // Filter*,Sorted*,Skip*,Limit* // If there are other operations types in between, the optimizer will not kick in final List> path; if (firstAction instanceof SortedComparatorAction) { path = SORTED_FILTER_SKIP_LIMIT_PATH; } else { path = FILTER_SORTED_SKIP_LIMIT_PATH; } // Keeps track on where we are in the path // Start with the first operation type (i.e. either SORTED or FILTER) int pathStart = 0; for (Action action : pipeline) { for(int pos = pathStart; true; pos++) { if (pos >= path.size()) { return; // Reached the end of the path without finding the action } Operation operation = path.get(pos); if (operation.is(action)) { operation.consume(action, consumers); pathStart = pos; // Never look back at parts of the path that are now to be considered passed break; } } } } private boolean isFilterActionAndContainingOnlyFieldPredicate(Action action) { if (action instanceof FilterAction) { @SuppressWarnings("unchecked") final FilterAction filterAction = (FilterAction) action; return isContainingOnlyFieldPredicate(filterAction.getPredicate()); } return false; } private static class Consumers { private final Consumer> filterConsumer; private final Consumer> sortedConsumer; private final Consumer> skipConsumer; private final Consumer> limitConsumer; public Consumers( final Consumer> filterConsumer, final Consumer> sortedConsumer, final Consumer> skipConsumer, final Consumer> limitConsumer ) { this.filterConsumer = requireNonNull(filterConsumer); this.sortedConsumer = requireNonNull(sortedConsumer);; this.skipConsumer = requireNonNull(skipConsumer); this.limitConsumer = requireNonNull(limitConsumer); } public Consumer> getFilterConsumer() { return filterConsumer; } public Consumer> getSortedConsumer() { return sortedConsumer; } public Consumer> getSkipConsumer() { return skipConsumer; } public Consumer> getLimitConsumer() { return limitConsumer; } } private interface Operation { boolean is(Action action); void consume(Action action, Consumers consumers); } private class FilterOperation implements Operation { @Override public boolean is(Action action) { return isFilterActionAndContainingOnlyFieldPredicate(action); } @Override public void consume(Action action, Consumers consumers) { @SuppressWarnings("unchecked") final FilterAction filterAction = (FilterAction) action; consumers.getFilterConsumer().accept(filterAction); } } private class SortedOperation implements Operation { @Override public boolean is(Action action) { return isSortedActionWithFieldPredicate(action); } @Override public void consume(Action action, Consumers consumers) { @SuppressWarnings("unchecked") final SortedComparatorAction sortedAction = (SortedComparatorAction) action; consumers.getSortedConsumer().accept(sortedAction); } } private class SkipOperation implements Operation { @Override public boolean is(Action action) { return action instanceof SkipAction; } @Override public void consume(Action action, Consumers consumers) { @SuppressWarnings("unchecked") final SkipAction skipAction = (SkipAction) action; consumers.getSkipConsumer().accept(skipAction); } } private class LimitOperation implements Operation { @Override public boolean is(Action action) { return action instanceof LimitAction; } @Override public void consume(Action action, Consumers consumers) { @SuppressWarnings("unchecked") final LimitAction limitAction = (LimitAction) action; consumers.getLimitConsumer().accept(limitAction); } } }