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

io.trino.sql.planner.iterative.rule.ExtractSpatialJoins Maven / Gradle / Ivy

There is a newer version: 465
Show newest version
/*
 * 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 io.trino.sql.planner.iterative.rule;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.errorprone.annotations.FormatMethod;
import io.airlift.slice.Slices;
import io.trino.Session;
import io.trino.geospatial.KdbTree;
import io.trino.geospatial.KdbTreeUtils;
import io.trino.matching.Capture;
import io.trino.matching.Captures;
import io.trino.matching.Pattern;
import io.trino.metadata.Metadata;
import io.trino.metadata.QualifiedObjectName;
import io.trino.metadata.ResolvedFunction;
import io.trino.metadata.Split;
import io.trino.metadata.TableHandle;
import io.trino.spi.Page;
import io.trino.spi.TrinoException;
import io.trino.spi.connector.ColumnHandle;
import io.trino.spi.connector.ConnectorPageSource;
import io.trino.spi.connector.DynamicFilter;
import io.trino.spi.type.Type;
import io.trino.spi.type.TypeSignature;
import io.trino.split.PageSourceManager;
import io.trino.split.PageSourceProvider;
import io.trino.split.SplitManager;
import io.trino.split.SplitSource;
import io.trino.split.SplitSource.SplitBatch;
import io.trino.sql.PlannerContext;
import io.trino.sql.ir.Call;
import io.trino.sql.ir.Cast;
import io.trino.sql.ir.Comparison;
import io.trino.sql.ir.Constant;
import io.trino.sql.ir.Expression;
import io.trino.sql.ir.Reference;
import io.trino.sql.planner.BuiltinFunctionCallBuilder;
import io.trino.sql.planner.ResolvedFunctionCallBuilder;
import io.trino.sql.planner.Symbol;
import io.trino.sql.planner.iterative.Rule;
import io.trino.sql.planner.iterative.Rule.Context;
import io.trino.sql.planner.iterative.Rule.Result;
import io.trino.sql.planner.plan.Assignments;
import io.trino.sql.planner.plan.FilterNode;
import io.trino.sql.planner.plan.JoinNode;
import io.trino.sql.planner.plan.PlanNode;
import io.trino.sql.planner.plan.PlanNodeId;
import io.trino.sql.planner.plan.ProjectNode;
import io.trino.sql.planner.plan.SpatialJoinNode;
import io.trino.sql.planner.plan.UnnestNode;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static io.airlift.concurrent.MoreFutures.getFutureValue;
import static io.trino.SystemSessionProperties.getSpatialPartitioningTableName;
import static io.trino.SystemSessionProperties.isSpatialJoinEnabled;
import static io.trino.matching.Capture.newCapture;
import static io.trino.spi.StandardErrorCode.INVALID_SPATIAL_PARTITIONING;
import static io.trino.spi.connector.Constraint.alwaysTrue;
import static io.trino.spi.type.DoubleType.DOUBLE;
import static io.trino.spi.type.IntegerType.INTEGER;
import static io.trino.spi.type.VarcharType.VARCHAR;
import static io.trino.sql.ir.Comparison.Operator.LESS_THAN;
import static io.trino.sql.ir.Comparison.Operator.LESS_THAN_OR_EQUAL;
import static io.trino.sql.planner.ExpressionNodeInliner.replaceExpression;
import static io.trino.sql.planner.SymbolsExtractor.extractUnique;
import static io.trino.sql.planner.plan.JoinType.INNER;
import static io.trino.sql.planner.plan.JoinType.LEFT;
import static io.trino.sql.planner.plan.Patterns.filter;
import static io.trino.sql.planner.plan.Patterns.join;
import static io.trino.sql.planner.plan.Patterns.source;
import static io.trino.util.SpatialJoinUtils.extractSupportedSpatialComparisons;
import static io.trino.util.SpatialJoinUtils.extractSupportedSpatialFunctions;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

/**
 * Applies to broadcast spatial joins, inner and left, expressed via ST_Contains,
 * ST_Intersects and ST_Distance functions.
 * 

* For example: *

    *
  • {@code SELECT ... FROM a, b WHERE ST_Contains(b.geometry, a.geometry)}
  • *
  • {@code SELECT ... FROM a, b WHERE ST_Intersects(b.geometry, a.geometry)}
  • *
  • {@code SELECT ... FROM a, b WHERE ST_Distance(b.geometry, a.geometry) <= 300}
  • *
  • {@code SELECT ... FROM a, b WHERE 15.5 > ST_Distance(b.geometry, a.geometry)}
  • *
*

* Joins expressed via ST_Contains and ST_Intersects functions must match all of * the following criteria: *

* - arguments of the spatial function are non-scalar expressions; * - one of the arguments uses symbols from left side of the join, the other from right. *

* Joins expressed via ST_Distance function must use less than or less than or equals operator * to compare ST_Distance value with a radius and must match all of the following criteria: *

* - arguments of the spatial function are non-scalar expressions; * - one of the arguments uses symbols from left side of the join, the other from right; * - radius is either scalar expression or uses symbols only from the right (build) side of the join. *

* For inner join, replaces cross join node and a qualifying filter on top with a single * spatial join node. *

* For both inner and left joins, pushes non-trivial expressions of the spatial function * arguments and radius into projections on top of join child nodes. *

* Examples: *

 * Point-in-polygon inner join
 *      {@code ST_Contains(ST_GeometryFromText(a.wkt), ST_Point(b.longitude, b.latitude))}
 * becomes a spatial join
 *      {@code ST_Contains(st_geometryfromtext, st_point)}
 * with {@code st_geometryfromtext -> 'ST_GeometryFromText(a.wkt)'} and
 * {@code st_point -> 'ST_Point(b.longitude, b.latitude)'} projections on top of child nodes.
 *
 * Distance query
 *      {@code ST_Distance(ST_Point(a.lon, a.lat), ST_Point(b.lon, b.lat)) <= 10 / (111.321 * cos(radians(b.lat)))}
 * becomes a spatial join
 *      {@code ST_Distance(st_point_a, st_point_b) <= radius}
 * with {@code st_point_a -> 'ST_Point(a.lon, a.lat)', st_point_b -> 'ST_Point(b.lon, b.lat)'}
 * and {@code radius -> '10 / (111.321 * cos(radians(b.lat)))'} projections on top of child nodes.
 * 
*/ public class ExtractSpatialJoins { private static final TypeSignature GEOMETRY_TYPE_SIGNATURE = new TypeSignature("Geometry"); private static final TypeSignature SPHERICAL_GEOGRAPHY_TYPE_SIGNATURE = new TypeSignature("SphericalGeography"); private static final String KDB_TREE_TYPENAME = "KdbTree"; private final PlannerContext plannerContext; private final SplitManager splitManager; private final PageSourceManager pageSourceManager; public ExtractSpatialJoins(PlannerContext plannerContext, SplitManager splitManager, PageSourceManager pageSourceManager) { this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.splitManager = requireNonNull(splitManager, "splitManager is null"); this.pageSourceManager = requireNonNull(pageSourceManager, "pageSourceManager is null"); } public Set> rules() { return ImmutableSet.of( new ExtractSpatialInnerJoin(plannerContext, splitManager, pageSourceManager), new ExtractSpatialLeftJoin(plannerContext, splitManager, pageSourceManager)); } @VisibleForTesting public static final class ExtractSpatialInnerJoin implements Rule { private static final Capture JOIN = newCapture(); private static final Pattern PATTERN = filter() .with(source().matching(join().capturedAs(JOIN).matching(JoinNode::isCrossJoin))); private final PlannerContext plannerContext; private final SplitManager splitManager; private final PageSourceManager pageSourceManager; public ExtractSpatialInnerJoin(PlannerContext plannerContext, SplitManager splitManager, PageSourceManager pageSourceManager) { this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.splitManager = requireNonNull(splitManager, "splitManager is null"); this.pageSourceManager = requireNonNull(pageSourceManager, "pageSourceManager is null"); } @Override public boolean isEnabled(Session session) { return isSpatialJoinEnabled(session); } @Override public Pattern getPattern() { return PATTERN; } @Override public Result apply(FilterNode node, Captures captures, Context context) { JoinNode joinNode = captures.get(JOIN); Expression filter = node.getPredicate(); List spatialFunctions = extractSupportedSpatialFunctions(filter); for (Call spatialFunction : spatialFunctions) { Result result = tryCreateSpatialJoin(context, joinNode, filter, node.getId(), node.getOutputSymbols(), spatialFunction, Optional.empty(), plannerContext, splitManager, pageSourceManager); if (!result.isEmpty()) { return result; } } List spatialComparisons = extractSupportedSpatialComparisons(filter); for (Comparison spatialComparison : spatialComparisons) { Result result = tryCreateSpatialJoin(context, joinNode, filter, node.getId(), node.getOutputSymbols(), spatialComparison, plannerContext, splitManager, pageSourceManager); if (!result.isEmpty()) { return result; } } return Result.empty(); } } @VisibleForTesting public static final class ExtractSpatialLeftJoin implements Rule { private static final Pattern PATTERN = join().matching(node -> node.getCriteria().isEmpty() && node.getFilter().isPresent() && node.getType() == LEFT); private final PlannerContext plannerContext; private final SplitManager splitManager; private final PageSourceManager pageSourceManager; public ExtractSpatialLeftJoin(PlannerContext plannerContext, SplitManager splitManager, PageSourceManager pageSourceManager) { this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.splitManager = requireNonNull(splitManager, "splitManager is null"); this.pageSourceManager = requireNonNull(pageSourceManager, "pageSourceManager is null"); } @Override public boolean isEnabled(Session session) { return isSpatialJoinEnabled(session); } @Override public Pattern getPattern() { return PATTERN; } @Override public Result apply(JoinNode joinNode, Captures captures, Context context) { Expression filter = joinNode.getFilter().get(); List spatialFunctions = extractSupportedSpatialFunctions(filter); for (Call spatialFunction : spatialFunctions) { Result result = tryCreateSpatialJoin(context, joinNode, filter, joinNode.getId(), joinNode.getOutputSymbols(), spatialFunction, Optional.empty(), plannerContext, splitManager, pageSourceManager); if (!result.isEmpty()) { return result; } } List spatialComparisons = extractSupportedSpatialComparisons(filter); for (Comparison spatialComparison : spatialComparisons) { Result result = tryCreateSpatialJoin(context, joinNode, filter, joinNode.getId(), joinNode.getOutputSymbols(), spatialComparison, plannerContext, splitManager, pageSourceManager); if (!result.isEmpty()) { return result; } } return Result.empty(); } } private static Result tryCreateSpatialJoin( Context context, JoinNode joinNode, Expression filter, PlanNodeId nodeId, List outputSymbols, Comparison spatialComparison, PlannerContext plannerContext, SplitManager splitManager, PageSourceManager pageSourceManager) { PlanNode leftNode = joinNode.getLeft(); PlanNode rightNode = joinNode.getRight(); List leftSymbols = leftNode.getOutputSymbols(); List rightSymbols = rightNode.getOutputSymbols(); Expression radius; Optional newRadiusSymbol; Comparison newComparison; if (spatialComparison.operator() == LESS_THAN || spatialComparison.operator() == LESS_THAN_OR_EQUAL) { // ST_Distance(a, b) <= r radius = spatialComparison.right(); Set radiusSymbols = extractUnique(radius); if (radiusSymbols.isEmpty() || (rightSymbols.containsAll(radiusSymbols) && containsNone(leftSymbols, radiusSymbols))) { newRadiusSymbol = newRadiusSymbol(context, radius); newComparison = new Comparison(spatialComparison.operator(), spatialComparison.left(), toExpression(newRadiusSymbol, radius)); } else { return Result.empty(); } } else { // r >= ST_Distance(a, b) radius = spatialComparison.left(); Set radiusSymbols = extractUnique(radius); if (radiusSymbols.isEmpty() || (rightSymbols.containsAll(radiusSymbols) && containsNone(leftSymbols, radiusSymbols))) { newRadiusSymbol = newRadiusSymbol(context, radius); newComparison = new Comparison(spatialComparison.operator().flip(), spatialComparison.right(), toExpression(newRadiusSymbol, radius)); } else { return Result.empty(); } } Expression newFilter = replaceExpression(filter, ImmutableMap.of(spatialComparison, newComparison)); PlanNode newRightNode = newRadiusSymbol.map(symbol -> addProjection(context, rightNode, symbol, radius)).orElse(rightNode); JoinNode newJoinNode = new JoinNode( joinNode.getId(), joinNode.getType(), leftNode, newRightNode, joinNode.getCriteria(), joinNode.getLeftOutputSymbols(), joinNode.getRightOutputSymbols(), joinNode.isMaySkipOutputDuplicates(), Optional.of(newFilter), joinNode.getLeftHashSymbol(), joinNode.getRightHashSymbol(), joinNode.getDistributionType(), joinNode.isSpillable(), joinNode.getDynamicFilters(), joinNode.getReorderJoinStatsAndCost()); return tryCreateSpatialJoin(context, newJoinNode, newFilter, nodeId, outputSymbols, (Call) newComparison.left(), Optional.of(newComparison.right()), plannerContext, splitManager, pageSourceManager); } private static Result tryCreateSpatialJoin( Context context, JoinNode joinNode, Expression filter, PlanNodeId nodeId, List outputSymbols, Call spatialFunction, Optional radius, PlannerContext plannerContext, SplitManager splitManager, PageSourceManager pageSourceManager) { // TODO Add support for distributed left spatial joins Optional spatialPartitioningTableName = joinNode.getType() == INNER ? getSpatialPartitioningTableName(context.getSession()) : Optional.empty(); Optional kdbTree = spatialPartitioningTableName.map(tableName -> loadKdbTree(tableName, context.getSession(), plannerContext.getMetadata(), splitManager, pageSourceManager)); List arguments = spatialFunction.arguments(); verify(arguments.size() == 2); Expression firstArgument = arguments.get(0); Expression secondArgument = arguments.get(1); Type sphericalGeographyType = plannerContext.getTypeManager().getType(SPHERICAL_GEOGRAPHY_TYPE_SIGNATURE); if (firstArgument.type().equals(sphericalGeographyType) || secondArgument.type().equals(sphericalGeographyType)) { return Result.empty(); } Set firstSymbols = extractUnique(firstArgument); Set secondSymbols = extractUnique(secondArgument); if (firstSymbols.isEmpty() || secondSymbols.isEmpty()) { return Result.empty(); } Optional newFirstSymbol = newGeometrySymbol(context, firstArgument); Optional newSecondSymbol = newGeometrySymbol(context, secondArgument); PlanNode leftNode = joinNode.getLeft(); PlanNode rightNode = joinNode.getRight(); PlanNode newLeftNode; PlanNode newRightNode; // Check if the order of arguments of the spatial function matches the order of join sides int alignment = checkAlignment(joinNode, firstSymbols, secondSymbols); if (alignment > 0) { newLeftNode = newFirstSymbol.map(symbol -> addProjection(context, leftNode, symbol, firstArgument)).orElse(leftNode); newRightNode = newSecondSymbol.map(symbol -> addProjection(context, rightNode, symbol, secondArgument)).orElse(rightNode); } else if (alignment < 0) { newLeftNode = newSecondSymbol.map(symbol -> addProjection(context, leftNode, symbol, secondArgument)).orElse(leftNode); newRightNode = newFirstSymbol.map(symbol -> addProjection(context, rightNode, symbol, firstArgument)).orElse(rightNode); } else { return Result.empty(); } Expression newFirstArgument = toExpression(newFirstSymbol, firstArgument); Expression newSecondArgument = toExpression(newSecondSymbol, secondArgument); Optional leftPartitionSymbol = Optional.empty(); Optional rightPartitionSymbol = Optional.empty(); if (kdbTree.isPresent()) { leftPartitionSymbol = Optional.of(context.getSymbolAllocator().newSymbol("pid", INTEGER)); rightPartitionSymbol = Optional.of(context.getSymbolAllocator().newSymbol("pid", INTEGER)); if (alignment > 0) { newLeftNode = addPartitioningNodes(plannerContext, context, newLeftNode, leftPartitionSymbol.get(), kdbTree.get(), newFirstArgument, Optional.empty()); newRightNode = addPartitioningNodes(plannerContext, context, newRightNode, rightPartitionSymbol.get(), kdbTree.get(), newSecondArgument, radius); } else { newLeftNode = addPartitioningNodes(plannerContext, context, newLeftNode, leftPartitionSymbol.get(), kdbTree.get(), newSecondArgument, Optional.empty()); newRightNode = addPartitioningNodes(plannerContext, context, newRightNode, rightPartitionSymbol.get(), kdbTree.get(), newFirstArgument, radius); } } ResolvedFunction resolvedFunction = spatialFunction.function(); Expression newSpatialFunction = ResolvedFunctionCallBuilder.builder(resolvedFunction) .addArgument(newFirstArgument) .addArgument(newSecondArgument) .build(); Expression newFilter = replaceExpression(filter, ImmutableMap.of(spatialFunction, newSpatialFunction)); return Result.ofPlanNode(new SpatialJoinNode( nodeId, SpatialJoinNode.Type.fromJoinNodeType(joinNode.getType()), newLeftNode, newRightNode, outputSymbols, newFilter, leftPartitionSymbol, rightPartitionSymbol, kdbTree.map(KdbTreeUtils::toJson))); } private static KdbTree loadKdbTree(String tableName, Session session, Metadata metadata, SplitManager splitManager, PageSourceManager pageSourceManager) { QualifiedObjectName name = toQualifiedObjectName(tableName, session.getCatalog().get(), session.getSchema().get()); TableHandle tableHandle = metadata.getTableHandle(session, name) .orElseThrow(() -> new TrinoException(INVALID_SPATIAL_PARTITIONING, format("Table not found: %s", name))); Map columnHandles = metadata.getColumnHandles(session, tableHandle); List visibleColumnHandles = columnHandles.values().stream() .filter(handle -> !metadata.getColumnMetadata(session, tableHandle, handle).isHidden()) .collect(toImmutableList()); checkSpatialPartitioningTable(visibleColumnHandles.size() == 1, "Expected single column for table %s, but found %s columns", name, columnHandles.size()); ColumnHandle kdbTreeColumn = Iterables.getOnlyElement(visibleColumnHandles); Optional kdbTree = Optional.empty(); try (SplitSource splitSource = splitManager.getSplits(session, session.getQuerySpan(), tableHandle, DynamicFilter.EMPTY, alwaysTrue())) { PageSourceProvider statefulPageSourceProvider = pageSourceManager.createPageSourceProvider(tableHandle.catalogHandle()); while (!Thread.currentThread().isInterrupted()) { SplitBatch splitBatch = getFutureValue(splitSource.getNextBatch(1000)); List splits = splitBatch.getSplits(); for (Split split : splits) { try (ConnectorPageSource pageSource = statefulPageSourceProvider.createPageSource(session, split, tableHandle, ImmutableList.of(kdbTreeColumn), DynamicFilter.EMPTY)) { do { getFutureValue(pageSource.isBlocked()); Page page = pageSource.getNextPage(); if (page != null && page.getPositionCount() > 0) { checkSpatialPartitioningTable(kdbTree.isEmpty(), "Expected exactly one row for table %s, but found more", name); checkSpatialPartitioningTable(page.getPositionCount() == 1, "Expected exactly one row for table %s, but found %s rows", name, page.getPositionCount()); String kdbTreeJson = VARCHAR.getSlice(page.getBlock(0), 0).toStringUtf8(); try { kdbTree = Optional.of(KdbTreeUtils.fromJson(kdbTreeJson)); } catch (IllegalArgumentException e) { checkSpatialPartitioningTable(false, "Invalid JSON string for KDB tree: %s", e.getMessage()); } } } while (!pageSource.isFinished()); } catch (IOException e) { throw new UncheckedIOException(e); } } if (splitBatch.isLastBatch()) { break; } } } checkSpatialPartitioningTable(kdbTree.isPresent(), "Expected exactly one row for table %s, but got none", name); return kdbTree.get(); } @FormatMethod private static void checkSpatialPartitioningTable(boolean condition, String message, Object... arguments) { if (!condition) { throw new TrinoException(INVALID_SPATIAL_PARTITIONING, format(message, arguments)); } } private static QualifiedObjectName toQualifiedObjectName(String name, String catalog, String schema) { ImmutableList ids = ImmutableList.copyOf(Splitter.on('.').split(name)); if (ids.size() == 3) { return new QualifiedObjectName(ids.get(0), ids.get(1), ids.get(2)); } if (ids.size() == 2) { return new QualifiedObjectName(catalog, ids.get(0), ids.get(1)); } if (ids.size() == 1) { return new QualifiedObjectName(catalog, schema, ids.get(0)); } throw new TrinoException(INVALID_SPATIAL_PARTITIONING, format("Invalid name: %s", name)); } private static int checkAlignment(JoinNode joinNode, Set maybeLeftSymbols, Set maybeRightSymbols) { List leftSymbols = joinNode.getLeft().getOutputSymbols(); List rightSymbols = joinNode.getRight().getOutputSymbols(); if (leftSymbols.containsAll(maybeLeftSymbols) && containsNone(leftSymbols, maybeRightSymbols) && rightSymbols.containsAll(maybeRightSymbols) && containsNone(rightSymbols, maybeLeftSymbols)) { return 1; } if (leftSymbols.containsAll(maybeRightSymbols) && containsNone(leftSymbols, maybeLeftSymbols) && rightSymbols.containsAll(maybeLeftSymbols) && containsNone(rightSymbols, maybeRightSymbols)) { return -1; } return 0; } private static Expression toExpression(Optional optionalSymbol, Expression defaultExpression) { return optionalSymbol.map(symbol -> (Expression) symbol.toSymbolReference()).orElse(defaultExpression); } private static Optional newGeometrySymbol(Context context, Expression expression) { if (expression instanceof Reference) { return Optional.empty(); } return Optional.of(context.getSymbolAllocator().newSymbol(expression)); } private static Optional newRadiusSymbol(Context context, Expression expression) { if (expression instanceof Reference) { return Optional.empty(); } return Optional.of(context.getSymbolAllocator().newSymbol(expression)); } private static PlanNode addProjection(Context context, PlanNode node, Symbol symbol, Expression expression) { Assignments.Builder projections = Assignments.builder(); for (Symbol outputSymbol : node.getOutputSymbols()) { projections.putIdentity(outputSymbol); } projections.put(symbol, expression); return new ProjectNode(context.getIdAllocator().getNextId(), node, projections.build()); } private static PlanNode addPartitioningNodes(PlannerContext plannerContext, Context context, PlanNode node, Symbol partitionSymbol, KdbTree kdbTree, Expression geometry, Optional radius) { Assignments.Builder projections = Assignments.builder(); for (Symbol outputSymbol : node.getOutputSymbols()) { projections.putIdentity(outputSymbol); } TypeSignature typeSignature = new TypeSignature(KDB_TREE_TYPENAME); BuiltinFunctionCallBuilder spatialPartitionsCall = BuiltinFunctionCallBuilder.resolve(plannerContext.getMetadata()) .setName("spatial_partitions") .addArgument(typeSignature, new Cast(new Constant(VARCHAR, Slices.utf8Slice(KdbTreeUtils.toJson(kdbTree))), plannerContext.getTypeManager().getType(typeSignature))) .addArgument(GEOMETRY_TYPE_SIGNATURE, geometry); radius.ifPresent(value -> spatialPartitionsCall.addArgument(DOUBLE, value)); Call partitioningFunction = spatialPartitionsCall.build(); Symbol partitionsSymbol = context.getSymbolAllocator().newSymbol(partitioningFunction); projections.put(partitionsSymbol, partitioningFunction); return new UnnestNode( context.getIdAllocator().getNextId(), new ProjectNode(context.getIdAllocator().getNextId(), node, projections.build()), node.getOutputSymbols(), ImmutableList.of(new UnnestNode.Mapping(partitionsSymbol, ImmutableList.of(partitionSymbol))), Optional.empty(), INNER); } private static boolean containsNone(Collection values, Collection testValues) { return values.stream().noneMatch(ImmutableSet.copyOf(testValues)::contains); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy