Пример #1
0
  @Override
  protected RelationPlan visitUnnest(Unnest node, Void context) {
    TupleDescriptor descriptor = analysis.getOutputDescriptor(node);
    ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
    for (Field field : descriptor.getVisibleFields()) {
      Symbol symbol = symbolAllocator.newSymbol(field);
      outputSymbolsBuilder.add(symbol);
    }
    List<Symbol> unnestedSymbols = outputSymbolsBuilder.build();

    // If we got here, then we must be unnesting a constant, and not be in a join (where there could
    // be column references)
    ImmutableList.Builder<Symbol> argumentSymbols = ImmutableList.builder();
    ImmutableList.Builder<Expression> values = ImmutableList.builder();
    ImmutableMap.Builder<Symbol, List<Symbol>> unnestSymbols = ImmutableMap.builder();
    Iterator<Symbol> unnestedSymbolsIterator = unnestedSymbols.iterator();
    for (Expression expression : node.getExpressions()) {
      Object constantValue =
          evaluateConstantExpression(expression, analysis.getCoercions(), metadata, session);
      Type type = analysis.getType(expression);
      values.add(LiteralInterpreter.toExpression(constantValue, type));
      Symbol inputSymbol = symbolAllocator.newSymbol(expression, type);
      argumentSymbols.add(inputSymbol);
      if (type instanceof ArrayType) {
        unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next()));
      } else if (type instanceof MapType) {
        unnestSymbols.put(
            inputSymbol,
            ImmutableList.of(unnestedSymbolsIterator.next(), unnestedSymbolsIterator.next()));
      } else {
        throw new IllegalArgumentException("Unsupported type for UNNEST: " + type);
      }
    }
    Optional<Symbol> ordinalitySymbol =
        node.isWithOrdinality() ? Optional.of(unnestedSymbolsIterator.next()) : Optional.empty();
    checkState(
        !unnestedSymbolsIterator.hasNext(),
        "Not all output symbols were matched with input symbols");
    ValuesNode valuesNode =
        new ValuesNode(
            idAllocator.getNextId(),
            argumentSymbols.build(),
            ImmutableList.<List<Expression>>of(values.build()));

    UnnestNode unnestNode =
        new UnnestNode(
            idAllocator.getNextId(),
            valuesNode,
            ImmutableList.<Symbol>of(),
            unnestSymbols.build(),
            ordinalitySymbol);
    return new RelationPlan(unnestNode, descriptor, unnestedSymbols, Optional.empty());
  }
Пример #2
0
  public PlanBuilder appendProjections(
      Iterable<Expression> expressions,
      SymbolAllocator symbolAllocator,
      PlanNodeIdAllocator idAllocator) {
    TranslationMap translations = copyTranslations();

    ImmutableMap.Builder<Symbol, Expression> projections = ImmutableMap.builder();

    // add an identity projection for underlying plan
    for (Symbol symbol : getRoot().getOutputSymbols()) {
      projections.put(symbol, symbol.toSymbolReference());
    }

    ImmutableMap.Builder<Symbol, Expression> newTranslations = ImmutableMap.builder();
    ParameterRewriter parameterRewriter = new ParameterRewriter(parameters, getAnalysis());
    for (Expression expression : expressions) {
      Expression rewritten = ExpressionTreeRewriter.rewriteWith(parameterRewriter, expression);
      translations.addIntermediateMapping(expression, rewritten);
      Symbol symbol =
          symbolAllocator.newSymbol(rewritten, getAnalysis().getTypeWithCoercions(expression));
      projections.put(symbol, translations.rewrite(rewritten));
      newTranslations.put(symbol, rewritten);
    }
    // Now append the new translations into the TranslationMap
    for (Map.Entry<Symbol, Expression> entry : newTranslations.build().entrySet()) {
      translations.put(entry.getValue(), entry.getKey());
    }

    return new PlanBuilder(
        translations,
        new ProjectNode(idAllocator.getNextId(), getRoot(), projections.build()),
        parameters);
  }
Пример #3
0
  private PlanBuilder appendSemiJoin(PlanBuilder subPlan, InPredicate inPredicate) {
    TranslationMap translations = new TranslationMap(subPlan.getRelationPlan(), analysis);
    translations.copyMappingsFrom(subPlan.getTranslations());

    subPlan = appendProjections(subPlan, ImmutableList.of(inPredicate.getValue()));
    Symbol sourceJoinSymbol = subPlan.translate(inPredicate.getValue());

    checkState(inPredicate.getValueList() instanceof SubqueryExpression);
    SubqueryExpression subqueryExpression = (SubqueryExpression) inPredicate.getValueList();
    RelationPlanner relationPlanner =
        new RelationPlanner(analysis, symbolAllocator, idAllocator, metadata, session);
    RelationPlan valueListRelation = relationPlanner.process(subqueryExpression.getQuery(), null);
    Symbol filteringSourceJoinSymbol =
        Iterables.getOnlyElement(valueListRelation.getRoot().getOutputSymbols());

    Symbol semiJoinOutputSymbol = symbolAllocator.newSymbol("semijoinresult", BOOLEAN);

    translations.put(inPredicate, semiJoinOutputSymbol);

    return new PlanBuilder(
        translations,
        new SemiJoinNode(
            idAllocator.getNextId(),
            subPlan.getRoot(),
            valueListRelation.getRoot(),
            sourceJoinSymbol,
            filteringSourceJoinSymbol,
            semiJoinOutputSymbol,
            Optional.empty(),
            Optional.empty()),
        subPlan.getSampleWeight());
  }
Пример #4
0
  private PlanBuilder appendProjections(PlanBuilder subPlan, Iterable<Expression> expressions) {
    TranslationMap translations = new TranslationMap(subPlan.getRelationPlan(), analysis);

    // Carry over the translations from the source because we are appending projections
    translations.copyMappingsFrom(subPlan.getTranslations());

    ImmutableMap.Builder<Symbol, Expression> projections = ImmutableMap.builder();

    // add an identity projection for underlying plan
    for (Symbol symbol : subPlan.getRoot().getOutputSymbols()) {
      Expression expression = new QualifiedNameReference(symbol.toQualifiedName());
      projections.put(symbol, expression);
    }

    ImmutableMap.Builder<Symbol, Expression> newTranslations = ImmutableMap.builder();
    for (Expression expression : expressions) {
      Symbol symbol = symbolAllocator.newSymbol(expression, analysis.getType(expression));

      // TODO: CHECK IF THE REWRITE OF A SEMI JOINED EXPRESSION WILL WORK!!!!!!!

      projections.put(symbol, translations.rewrite(expression));
      newTranslations.put(symbol, expression);
    }
    // Now append the new translations into the TranslationMap
    for (Map.Entry<Symbol, Expression> entry : newTranslations.build().entrySet()) {
      translations.put(entry.getValue(), entry.getKey());
    }

    return new PlanBuilder(
        translations,
        new ProjectNode(idAllocator.getNextId(), subPlan.getRoot(), projections.build()),
        subPlan.getSampleWeight());
  }
Пример #5
0
  @Override
  protected RelationPlan visitSampledRelation(SampledRelation node, Void context) {
    if (node.getColumnsToStratifyOn().isPresent()) {
      throw new UnsupportedOperationException("STRATIFY ON is not yet implemented");
    }

    RelationPlan subPlan = process(node.getRelation(), context);

    TupleDescriptor outputDescriptor = analysis.getOutputDescriptor(node);
    double ratio = analysis.getSampleRatio(node);
    Symbol sampleWeightSymbol = null;
    if (node.getType() == SampledRelation.Type.POISSONIZED) {
      sampleWeightSymbol = symbolAllocator.newSymbol("$sampleWeight", BIGINT);
    }
    PlanNode planNode =
        new SampleNode(
            idAllocator.getNextId(),
            subPlan.getRoot(),
            ratio,
            SampleNode.Type.fromType(node.getType()),
            node.isRescaled(),
            Optional.ofNullable(sampleWeightSymbol));
    return new RelationPlan(
        planNode,
        outputDescriptor,
        subPlan.getOutputSymbols(),
        Optional.ofNullable(sampleWeightSymbol));
  }
Пример #6
0
  @Override
  protected RelationPlan visitTable(Table node, Void context) {
    Query namedQuery = analysis.getNamedQuery(node);
    if (namedQuery != null) {
      RelationPlan subPlan = process(namedQuery, null);
      return new RelationPlan(
          subPlan.getRoot(),
          analysis.getOutputDescriptor(node),
          subPlan.getOutputSymbols(),
          subPlan.getSampleWeight());
    }

    TupleDescriptor descriptor = analysis.getOutputDescriptor(node);
    TableHandle handle = analysis.getTableHandle(node);

    ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
    ImmutableMap.Builder<Symbol, ColumnHandle> columns = ImmutableMap.builder();
    for (Field field : descriptor.getAllFields()) {
      Symbol symbol = symbolAllocator.newSymbol(field.getName().get(), field.getType());

      outputSymbolsBuilder.add(symbol);
      columns.put(symbol, analysis.getColumn(field));
    }

    List<Symbol> planOutputSymbols = outputSymbolsBuilder.build();
    Optional<ColumnHandle> sampleWeightColumn =
        metadata.getSampleWeightColumnHandle(session, handle);
    Symbol sampleWeightSymbol = null;
    if (sampleWeightColumn.isPresent()) {
      sampleWeightSymbol = symbolAllocator.newSymbol("$sampleWeight", BIGINT);
      outputSymbolsBuilder.add(sampleWeightSymbol);
      columns.put(sampleWeightSymbol, sampleWeightColumn.get());
    }

    List<Symbol> nodeOutputSymbols = outputSymbolsBuilder.build();
    PlanNode root =
        new TableScanNode(
            idAllocator.getNextId(),
            handle,
            nodeOutputSymbols,
            columns.build(),
            Optional.empty(),
            TupleDomain.all(),
            null);
    return new RelationPlan(
        root, descriptor, planOutputSymbols, Optional.ofNullable(sampleWeightSymbol));
  }
Пример #7
0
  private RelationPlan processAndCoerceIfNecessary(Relation node, Void context) {
    Type[] coerceToTypes = analysis.getRelationCoercion(node);

    RelationPlan plan = node.accept(this, context);

    if (coerceToTypes == null) {
      return plan;
    }

    List<Symbol> oldSymbols = plan.getOutputSymbols();
    TupleDescriptor oldDescriptor = plan.getDescriptor().withOnlyVisibleFields();
    verify(coerceToTypes.length == oldSymbols.size());
    ImmutableList.Builder<Symbol> newSymbols = new ImmutableList.Builder<>();
    Field[] newFields = new Field[coerceToTypes.length];
    ImmutableMap.Builder<Symbol, Expression> assignments = new ImmutableMap.Builder<>();
    for (int i = 0; i < coerceToTypes.length; i++) {
      Symbol inputSymbol = oldSymbols.get(i);
      Type inputType = symbolAllocator.getTypes().get(inputSymbol);
      Type outputType = coerceToTypes[i];
      if (outputType != inputType) {
        Cast cast =
            new Cast(
                new QualifiedNameReference(inputSymbol.toQualifiedName()),
                outputType.getTypeSignature().toString());
        Symbol outputSymbol = symbolAllocator.newSymbol(cast, outputType);
        assignments.put(outputSymbol, cast);
        newSymbols.add(outputSymbol);
      } else {
        assignments.put(inputSymbol, new QualifiedNameReference(inputSymbol.toQualifiedName()));
        newSymbols.add(inputSymbol);
      }
      Field oldField = oldDescriptor.getFieldByIndex(i);
      newFields[i] =
          new Field(
              oldField.getRelationAlias(),
              oldField.getName(),
              coerceToTypes[i],
              oldField.isHidden());
    }
    ProjectNode projectNode =
        new ProjectNode(idAllocator.getNextId(), plan.getRoot(), assignments.build());
    return new RelationPlan(
        projectNode, new TupleDescriptor(newFields), newSymbols.build(), plan.getSampleWeight());
  }
Пример #8
0
  private RelationPlan planCrossJoinUnnest(RelationPlan leftPlan, Join joinNode, Unnest node) {
    TupleDescriptor outputDescriptor = analysis.getOutputDescriptor(joinNode);
    TupleDescriptor unnestOutputDescriptor = analysis.getOutputDescriptor(node);
    // Create symbols for the result of unnesting
    ImmutableList.Builder<Symbol> unnestedSymbolsBuilder = ImmutableList.builder();
    for (Field field : unnestOutputDescriptor.getVisibleFields()) {
      Symbol symbol = symbolAllocator.newSymbol(field);
      unnestedSymbolsBuilder.add(symbol);
    }
    ImmutableList<Symbol> unnestedSymbols = unnestedSymbolsBuilder.build();

    // Add a projection for all the unnest arguments
    PlanBuilder planBuilder = initializePlanBuilder(leftPlan);
    planBuilder = appendProjections(planBuilder, node.getExpressions());
    TranslationMap translations = planBuilder.getTranslations();
    ProjectNode projectNode =
        checkType(planBuilder.getRoot(), ProjectNode.class, "planBuilder.getRoot()");

    ImmutableMap.Builder<Symbol, List<Symbol>> unnestSymbols = ImmutableMap.builder();
    UnmodifiableIterator<Symbol> unnestedSymbolsIterator = unnestedSymbols.iterator();
    for (Expression expression : node.getExpressions()) {
      Type type = analysis.getType(expression);
      Symbol inputSymbol = translations.get(expression);
      if (type instanceof ArrayType) {
        unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next()));
      } else if (type instanceof MapType) {
        unnestSymbols.put(
            inputSymbol,
            ImmutableList.of(unnestedSymbolsIterator.next(), unnestedSymbolsIterator.next()));
      } else {
        throw new IllegalArgumentException("Unsupported type for UNNEST: " + type);
      }
    }
    Optional<Symbol> ordinalitySymbol =
        node.isWithOrdinality() ? Optional.of(unnestedSymbolsIterator.next()) : Optional.empty();
    checkState(
        !unnestedSymbolsIterator.hasNext(),
        "Not all output symbols were matched with input symbols");

    UnnestNode unnestNode =
        new UnnestNode(
            idAllocator.getNextId(),
            projectNode,
            leftPlan.getOutputSymbols(),
            unnestSymbols.build(),
            ordinalitySymbol);
    return new RelationPlan(
        unnestNode, outputDescriptor, unnestNode.getOutputSymbols(), Optional.empty());
  }
Пример #9
0
  private RelationPlan addConstantSampleWeight(RelationPlan subPlan) {
    ImmutableMap.Builder<Symbol, Expression> projections = ImmutableMap.builder();
    for (Symbol symbol : subPlan.getOutputSymbols()) {
      Expression expression = new QualifiedNameReference(symbol.toQualifiedName());
      projections.put(symbol, expression);
    }
    Expression one = new LongLiteral("1");

    Symbol sampleWeightSymbol = symbolAllocator.newSymbol("$sampleWeight", BIGINT);
    projections.put(sampleWeightSymbol, one);
    ProjectNode projectNode =
        new ProjectNode(idAllocator.getNextId(), subPlan.getRoot(), projections.build());
    return new RelationPlan(
        projectNode,
        subPlan.getDescriptor(),
        projectNode.getOutputSymbols(),
        Optional.of(sampleWeightSymbol));
  }
Пример #10
0
  @Override
  protected RelationPlan visitValues(Values node, Void context) {
    TupleDescriptor descriptor = analysis.getOutputDescriptor(node);
    ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
    for (Field field : descriptor.getVisibleFields()) {
      Symbol symbol = symbolAllocator.newSymbol(field);
      outputSymbolsBuilder.add(symbol);
    }

    ImmutableList.Builder<List<Expression>> rows = ImmutableList.builder();
    for (Expression row : node.getRows()) {
      ImmutableList.Builder<Expression> values = ImmutableList.builder();
      if (row instanceof Row) {
        List<Expression> items = ((Row) row).getItems();
        for (int i = 0; i < items.size(); i++) {
          Expression expression = items.get(i);
          Object constantValue =
              evaluateConstantExpression(expression, analysis.getCoercions(), metadata, session);
          values.add(
              LiteralInterpreter.toExpression(
                  constantValue, descriptor.getFieldByIndex(i).getType()));
        }
      } else {
        Object constantValue =
            evaluateConstantExpression(row, analysis.getCoercions(), metadata, session);
        values.add(
            LiteralInterpreter.toExpression(
                constantValue, descriptor.getFieldByIndex(0).getType()));
      }

      rows.add(values.build());
    }

    ValuesNode valuesNode =
        new ValuesNode(idAllocator.getNextId(), outputSymbolsBuilder.build(), rows.build());
    return new RelationPlan(valuesNode, descriptor, outputSymbolsBuilder.build(), Optional.empty());
  }
Пример #11
0
    private SubPlanBuilder addDistributedAggregation(
        SubPlanBuilder plan,
        Map<Symbol, FunctionCall> aggregations,
        Map<Symbol, Signature> functions,
        Map<Symbol, Symbol> masks,
        List<Symbol> groupBy,
        Optional<Symbol> sampleWeight,
        double confidence) {
      Map<Symbol, FunctionCall> finalCalls = new HashMap<>();
      Map<Symbol, FunctionCall> intermediateCalls = new HashMap<>();
      Map<Symbol, Signature> intermediateFunctions = new HashMap<>();
      Map<Symbol, Symbol> intermediateMask = new HashMap<>();
      for (Map.Entry<Symbol, FunctionCall> entry : aggregations.entrySet()) {
        Signature signature = functions.get(entry.getKey());
        FunctionInfo function = metadata.getFunction(signature);

        Symbol intermediateSymbol =
            allocator.newSymbol(function.getName().getSuffix(), function.getIntermediateType());
        intermediateCalls.put(intermediateSymbol, entry.getValue());
        intermediateFunctions.put(intermediateSymbol, signature);
        if (masks.containsKey(entry.getKey())) {
          intermediateMask.put(intermediateSymbol, masks.get(entry.getKey()));
        }

        // rewrite final aggregation in terms of intermediate function
        finalCalls.put(
            entry.getKey(),
            new FunctionCall(
                function.getName(),
                ImmutableList.<Expression>of(
                    new QualifiedNameReference(intermediateSymbol.toQualifiedName()))));
      }

      // create partial aggregation plan
      AggregationNode partialAggregation =
          new AggregationNode(
              idAllocator.getNextId(),
              plan.getRoot(),
              groupBy,
              intermediateCalls,
              intermediateFunctions,
              intermediateMask,
              PARTIAL,
              sampleWeight,
              confidence);
      plan.setRoot(
          new SinkNode(
              idAllocator.getNextId(), partialAggregation, partialAggregation.getOutputSymbols()));

      // create final aggregation plan
      ExchangeNode source =
          new ExchangeNode(
              idAllocator.getNextId(), plan.getId(), plan.getRoot().getOutputSymbols());
      AggregationNode finalAggregation =
          new AggregationNode(
              idAllocator.getNextId(),
              source,
              groupBy,
              finalCalls,
              functions,
              ImmutableMap.<Symbol, Symbol>of(),
              FINAL,
              Optional.<Symbol>absent(),
              confidence);

      if (groupBy.isEmpty()) {
        plan = createSingleNodePlan(finalAggregation).addChild(plan.build());
      } else {
        plan.setHashOutputPartitioning(groupBy);
        plan = createFixedDistributionPlan(finalAggregation).addChild(plan.build());
      }
      return plan;
    }
Пример #12
0
  @Override
  protected RelationPlan visitUnion(Union node, Void context) {
    checkArgument(!node.getRelations().isEmpty(), "No relations specified for UNION");

    List<Symbol> unionOutputSymbols = null;
    ImmutableList.Builder<PlanNode> sources = ImmutableList.builder();
    ImmutableListMultimap.Builder<Symbol, Symbol> symbolMapping = ImmutableListMultimap.builder();
    List<RelationPlan> subPlans =
        node.getRelations()
            .stream()
            .map(relation -> processAndCoerceIfNecessary(relation, context))
            .collect(toImmutableList());

    boolean hasSampleWeight = false;
    for (RelationPlan subPlan : subPlans) {
      if (subPlan.getSampleWeight().isPresent()) {
        hasSampleWeight = true;
        break;
      }
    }

    Optional<Symbol> outputSampleWeight = Optional.empty();
    for (RelationPlan relationPlan : subPlans) {
      if (hasSampleWeight && !relationPlan.getSampleWeight().isPresent()) {
        relationPlan = addConstantSampleWeight(relationPlan);
      }

      List<Symbol> childOutputSymbols = relationPlan.getOutputSymbols();
      if (unionOutputSymbols == null) {
        // Use the first Relation to derive output symbol names
        TupleDescriptor descriptor = relationPlan.getDescriptor();
        ImmutableList.Builder<Symbol> outputSymbolBuilder = ImmutableList.builder();
        for (Field field : descriptor.getVisibleFields()) {
          int fieldIndex = descriptor.indexOf(field);
          Symbol symbol = childOutputSymbols.get(fieldIndex);
          outputSymbolBuilder.add(
              symbolAllocator.newSymbol(symbol.getName(), symbolAllocator.getTypes().get(symbol)));
        }
        unionOutputSymbols = outputSymbolBuilder.build();
        outputSampleWeight = relationPlan.getSampleWeight();
      }

      TupleDescriptor descriptor = relationPlan.getDescriptor();
      checkArgument(
          descriptor.getVisibleFieldCount() == unionOutputSymbols.size(),
          "Expected relation to have %s symbols but has %s symbols",
          descriptor.getVisibleFieldCount(),
          unionOutputSymbols.size());

      int unionFieldId = 0;
      for (Field field : descriptor.getVisibleFields()) {
        int fieldIndex = descriptor.indexOf(field);
        symbolMapping.put(unionOutputSymbols.get(unionFieldId), childOutputSymbols.get(fieldIndex));
        unionFieldId++;
      }

      sources.add(relationPlan.getRoot());
    }

    PlanNode planNode =
        new UnionNode(idAllocator.getNextId(), sources.build(), symbolMapping.build());
    if (node.isDistinct()) {
      planNode = distinct(planNode);
    }
    return new RelationPlan(
        planNode,
        analysis.getOutputDescriptor(node),
        planNode.getOutputSymbols(),
        outputSampleWeight);
  }
Пример #13
0
  @Override
  protected RelationPlan visitJoin(Join node, Void context) {
    // TODO: translate the RIGHT join into a mirrored LEFT join when we refactor (@martint)
    RelationPlan leftPlan = process(node.getLeft(), context);

    // Convert CROSS JOIN UNNEST to an UnnestNode
    if (node.getRight() instanceof Unnest
        || (node.getRight() instanceof AliasedRelation
            && ((AliasedRelation) node.getRight()).getRelation() instanceof Unnest)) {
      Unnest unnest;
      if (node.getRight() instanceof AliasedRelation) {
        unnest = (Unnest) ((AliasedRelation) node.getRight()).getRelation();
      } else {
        unnest = (Unnest) node.getRight();
      }
      if (node.getType() != Join.Type.CROSS && node.getType() != Join.Type.IMPLICIT) {
        throw new SemanticException(
            NOT_SUPPORTED, unnest, "UNNEST only supported on the right side of CROSS JOIN");
      }
      return planCrossJoinUnnest(leftPlan, node, unnest);
    }

    RelationPlan rightPlan = process(node.getRight(), context);

    PlanBuilder leftPlanBuilder = initializePlanBuilder(leftPlan);
    PlanBuilder rightPlanBuilder = initializePlanBuilder(rightPlan);

    TupleDescriptor outputDescriptor = analysis.getOutputDescriptor(node);

    // NOTE: symbols must be in the same order as the outputDescriptor
    List<Symbol> outputSymbols =
        ImmutableList.<Symbol>builder()
            .addAll(leftPlan.getOutputSymbols())
            .addAll(rightPlan.getOutputSymbols())
            .build();

    ImmutableList.Builder<JoinNode.EquiJoinClause> equiClauses = ImmutableList.builder();
    Expression postInnerJoinCriteria = new BooleanLiteral("TRUE");
    if (node.getType() != Join.Type.CROSS && node.getType() != Join.Type.IMPLICIT) {
      Expression criteria = analysis.getJoinCriteria(node);

      TupleDescriptor left = analysis.getOutputDescriptor(node.getLeft());
      TupleDescriptor right = analysis.getOutputDescriptor(node.getRight());
      List<Expression> leftExpressions = new ArrayList<>();
      List<Expression> rightExpressions = new ArrayList<>();
      List<ComparisonExpression.Type> comparisonTypes = new ArrayList<>();
      for (Expression conjunct : ExpressionUtils.extractConjuncts(criteria)) {
        if (!(conjunct instanceof ComparisonExpression)) {
          throw new SemanticException(
              NOT_SUPPORTED, node, "Unsupported non-equi join form: %s", conjunct);
        }

        ComparisonExpression comparison = (ComparisonExpression) conjunct;
        ComparisonExpression.Type comparisonType = comparison.getType();
        if (comparison.getType() != EQUAL && node.getType() != INNER) {
          throw new SemanticException(
              NOT_SUPPORTED, node, "Non-equi joins only supported for inner join: %s", conjunct);
        }
        Set<QualifiedName> firstDependencies =
            DependencyExtractor.extractNames(comparison.getLeft());
        Set<QualifiedName> secondDependencies =
            DependencyExtractor.extractNames(comparison.getRight());

        Expression leftExpression;
        Expression rightExpression;
        if (Iterables.all(firstDependencies, left.canResolvePredicate())
            && Iterables.all(secondDependencies, right.canResolvePredicate())) {
          leftExpression = comparison.getLeft();
          rightExpression = comparison.getRight();
        } else if (Iterables.all(firstDependencies, right.canResolvePredicate())
            && Iterables.all(secondDependencies, left.canResolvePredicate())) {
          leftExpression = comparison.getRight();
          rightExpression = comparison.getLeft();
          comparisonType = flipComparison(comparisonType);
        } else {
          // must have a complex expression that involves both tuples on one side of the comparison
          // expression (e.g., coalesce(left.x, right.x) = 1)
          throw new SemanticException(
              NOT_SUPPORTED, node, "Unsupported non-equi join form: %s", conjunct);
        }
        leftExpressions.add(leftExpression);
        rightExpressions.add(rightExpression);
        comparisonTypes.add(comparisonType);
      }

      Analysis.JoinInPredicates joinInPredicates = analysis.getJoinInPredicates(node);

      // Add semi joins if necessary
      if (joinInPredicates != null) {
        leftPlanBuilder = appendSemiJoins(leftPlanBuilder, joinInPredicates.getLeftInPredicates());
        rightPlanBuilder =
            appendSemiJoins(rightPlanBuilder, joinInPredicates.getRightInPredicates());
      }

      // Add projections for join criteria
      leftPlanBuilder = appendProjections(leftPlanBuilder, leftExpressions);
      rightPlanBuilder = appendProjections(rightPlanBuilder, rightExpressions);

      List<Expression> postInnerJoinComparisons = new ArrayList<>();
      for (int i = 0; i < comparisonTypes.size(); i++) {
        Symbol leftSymbol = leftPlanBuilder.translate(leftExpressions.get(i));
        Symbol rightSymbol = rightPlanBuilder.translate(rightExpressions.get(i));

        equiClauses.add(new JoinNode.EquiJoinClause(leftSymbol, rightSymbol));

        Expression leftExpression = leftPlanBuilder.rewrite(leftExpressions.get(i));
        Expression rightExpression = rightPlanBuilder.rewrite(rightExpressions.get(i));
        postInnerJoinComparisons.add(
            new ComparisonExpression(comparisonTypes.get(i), leftExpression, rightExpression));
      }
      postInnerJoinCriteria = ExpressionUtils.and(postInnerJoinComparisons);
    }

    PlanNode root;
    if (node.getType() == INNER) {
      root =
          new JoinNode(
              idAllocator.getNextId(),
              JoinNode.Type.CROSS,
              leftPlanBuilder.getRoot(),
              rightPlanBuilder.getRoot(),
              ImmutableList.<JoinNode.EquiJoinClause>of(),
              Optional.empty(),
              Optional.empty());
      root = new FilterNode(idAllocator.getNextId(), root, postInnerJoinCriteria);
    } else {
      root =
          new JoinNode(
              idAllocator.getNextId(),
              JoinNode.Type.typeConvert(node.getType()),
              leftPlanBuilder.getRoot(),
              rightPlanBuilder.getRoot(),
              equiClauses.build(),
              Optional.empty(),
              Optional.empty());
    }
    Optional<Symbol> sampleWeight = Optional.empty();
    if (leftPlanBuilder.getSampleWeight().isPresent()
        || rightPlanBuilder.getSampleWeight().isPresent()) {
      Expression expression =
          new ArithmeticBinaryExpression(
              ArithmeticBinaryExpression.Type.MULTIPLY,
              oneIfNull(leftPlanBuilder.getSampleWeight()),
              oneIfNull(rightPlanBuilder.getSampleWeight()));
      sampleWeight = Optional.of(symbolAllocator.newSymbol(expression, BIGINT));
      ImmutableMap.Builder<Symbol, Expression> projections = ImmutableMap.builder();
      projections.put(sampleWeight.get(), expression);
      for (Symbol symbol : root.getOutputSymbols()) {
        projections.put(symbol, new QualifiedNameReference(symbol.toQualifiedName()));
      }
      root = new ProjectNode(idAllocator.getNextId(), root, projections.build());
    }

    return new RelationPlan(root, outputDescriptor, outputSymbols, sampleWeight);
  }