{
if (EnumSet.of(Join.Type.FULL).contains(node.getType())) {
throw new SemanticException(NOT_SUPPORTED, node, "Full outer joins are not supported");
}
JoinCriteria criteria = node.getCriteria().orNull();
if (criteria instanceof NaturalJoin) {
throw new SemanticException(NOT_SUPPORTED, node, "Natural join not supported");
}
TupleDescriptor left = process(node.getLeft(), context);
TupleDescriptor right = process(node.getRight(), context);
Sets.SetView<QualifiedName> duplicateAliases = Sets.intersection(left.getRelationAliases(), right.getRelationAliases());
if (!duplicateAliases.isEmpty()) {
throw new SemanticException(DUPLICATE_RELATION, node, "Relations appear more than once: %s", duplicateAliases);
}
// compute output descriptor (all fields from left followed by all fields from right)
List<Field> outputFields = ImmutableList.<Field>builder()
.addAll(left.getFields())
.addAll(right.getFields())
.build();
TupleDescriptor output = new TupleDescriptor(outputFields);
if (node.getType() == Join.Type.CROSS) {
analysis.setOutputDescriptor(node, output);
return output;
}
if (criteria instanceof JoinUsing) {
// TODO: implement proper "using" semantics with respect to output columns
List<String> columns = ((JoinUsing) criteria).getColumns();
ImmutableList.Builder<EquiJoinClause> builder = ImmutableList.builder();
for (String column : columns) {
Expression leftExpression = new QualifiedNameReference(QualifiedName.of(column));
Expression rightExpression = new QualifiedNameReference(QualifiedName.of(column));
ExpressionAnalysis leftExpressionAnalysis = Analyzer.analyzeExpression(session, metadata, left, analysis, approximateQueriesEnabled, context, leftExpression);
ExpressionAnalysis rightExpressionAnalysis = Analyzer.analyzeExpression(session, metadata, right, analysis, approximateQueriesEnabled, context, rightExpression);
checkState(leftExpressionAnalysis.getSubqueryInPredicates().isEmpty(), "INVARIANT");
checkState(rightExpressionAnalysis.getSubqueryInPredicates().isEmpty(), "INVARIANT");
builder.add(new EquiJoinClause(leftExpression, rightExpression));
}
analysis.setEquijoinCriteria(node, builder.build());
}
else if (criteria instanceof JoinOn) {
Expression expression = ((JoinOn) criteria).getExpression();
// ensure all names can be resolved, types match, etc (we don't need to record resolved names, subexpression types, etc. because
// we do it further down when after we determine which subexpressions apply to left vs right tuple)
ExpressionAnalyzer analyzer = new ExpressionAnalyzer(analysis, session, metadata, approximateQueriesEnabled);
analyzer.analyze(expression, output, context);
Analyzer.verifyNoAggregatesOrWindowFunctions(metadata, expression, "JOIN");
Object optimizedExpression = ExpressionInterpreter.expressionOptimizer(expression, metadata, session).optimize(NoOpSymbolResolver.INSTANCE);
if (!(optimizedExpression instanceof Expression) && optimizedExpression instanceof Boolean) {
// If the JoinOn clause evaluates to a boolean expression, simulate a cross join by adding the relevant redundant expression
if (optimizedExpression.equals(Boolean.TRUE)) {
optimizedExpression = new ComparisonExpression(ComparisonExpression.Type.EQUAL, new LongLiteral("0"), new LongLiteral("0"));
}
else {
optimizedExpression = new ComparisonExpression(ComparisonExpression.Type.EQUAL, new LongLiteral("0"), new LongLiteral("1"));
}
}
if (!(optimizedExpression instanceof Expression)) {
throw new SemanticException(TYPE_MISMATCH, node, "Join clause must be a boolean expression");
}
ImmutableList.Builder<EquiJoinClause> clauses = ImmutableList.builder();
for (Expression conjunct : ExpressionUtils.extractConjuncts((Expression) optimizedExpression)) {
if (!(conjunct instanceof ComparisonExpression)) {
throw new SemanticException(NOT_SUPPORTED, node, "Non-equi joins not supported: %s", conjunct);
}
ComparisonExpression comparison = (ComparisonExpression) conjunct;
if (comparison.getType() != ComparisonExpression.Type.EQUAL) {
throw new SemanticException(NOT_SUPPORTED, node, "Non-equi joins not supported: %s", conjunct);
}
Set<QualifiedName> firstDependencies = DependencyExtractor.extract(comparison.getLeft());
Set<QualifiedName> secondDependencies = DependencyExtractor.extract(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();
}
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, "Non-equi joins not supported: %s", conjunct);
}
// analyze the clauses to record the types of all subexpressions and resolve names against the left/right underlying tuples
ExpressionAnalysis leftExpressionAnalysis = Analyzer.analyzeExpression(session, metadata, left, analysis, approximateQueriesEnabled, context, leftExpression);
ExpressionAnalysis rightExpressionAnalysis = Analyzer.analyzeExpression(session, metadata, right, analysis, approximateQueriesEnabled, context, rightExpression);
analysis.addJoinInPredicates(node, new Analysis.JoinInPredicates(leftExpressionAnalysis.getSubqueryInPredicates(), rightExpressionAnalysis.getSubqueryInPredicates()));
clauses.add(new EquiJoinClause(leftExpression, rightExpression));
}
analysis.setEquijoinCriteria(node, clauses.build());
}
else {
throw new UnsupportedOperationException("unsupported join criteria: " + criteria.getClass().getName());
}
analysis.setOutputDescriptor(node, output);
return output;
}