use of io.confluent.ksql.planner.JoinTree.Join in project ksql by confluentinc.
the class LogicalPlanner method buildJoin.
/**
* @param root the root of the Join Tree
* @param prefix the prefix to uniquely identify the plan node
* @return the PlanNode representing this Join Tree
*/
private JoinNode buildJoin(final Join root, final String prefix, final boolean isWindowed) {
final PlanNode preRepartitionLeft;
if (root.getLeft() instanceof JoinTree.Join) {
preRepartitionLeft = buildJoin((Join) root.getLeft(), prefix + "L_", isWindowed);
} else {
final JoinTree.Leaf leaf = (Leaf) root.getLeft();
preRepartitionLeft = new DataSourceNode(new PlanNodeId("KafkaTopic_" + prefix + "Left"), leaf.getSource().getDataSource(), leaf.getSource().getAlias(), isWindowed, ksqlConfig);
}
final PlanNode preRepartitionRight;
if (root.getRight() instanceof JoinTree.Join) {
preRepartitionRight = buildJoin((Join) root.getRight(), prefix + "R_", isWindowed);
} else {
final JoinTree.Leaf leaf = (Leaf) root.getRight();
preRepartitionRight = new DataSourceNode(new PlanNodeId("KafkaTopic_" + prefix + "Right"), leaf.getSource().getDataSource(), leaf.getSource().getAlias(), isWindowed, ksqlConfig);
}
final Optional<Expression> fkExpression = verifyJoin(root.getInfo(), preRepartitionLeft, preRepartitionRight);
final JoinKey joinKey = fkExpression.map(columnReferenceExp -> buildForeignJoinKey(root, fkExpression.get())).orElseGet(() -> buildJoinKey(root));
final PlanNode left = prepareSourceForJoin(root.getLeft(), preRepartitionLeft, prefix + "Left", root.getInfo().getLeftJoinExpression(), fkExpression.isPresent());
final PlanNode right = prepareSourceForJoin(root.getRight(), preRepartitionRight, prefix + "Right", root.getInfo().getRightJoinExpression(), fkExpression.isPresent());
return new JoinNode(new PlanNodeId(prefix + "Join"), root.getInfo().getType(), joinKey.rewriteWith(refRewriter::process), prefix.isEmpty(), left, right, root.getInfo().getWithinExpression(), ksqlConfig.getString(KsqlConfig.KSQL_DEFAULT_KEY_FORMAT_CONFIG));
}
use of io.confluent.ksql.planner.JoinTree.Join in project ksql by confluentinc.
the class LogicalPlanner method buildForeignJoinKey.
private JoinKey buildForeignJoinKey(final Join join, final Expression foreignKeyExpression) {
final AliasedDataSource leftSource = join.getInfo().getLeftSource();
final SourceName alias = leftSource.getAlias();
final List<QualifiedColumnReferenceExp> leftSourceKeys = leftSource.getDataSource().getSchema().key().stream().map(c -> new QualifiedColumnReferenceExp(alias, c.name())).collect(Collectors.toList());
final VisitParentExpressionVisitor<Optional<Expression>, Context<Void>> aliasRewritter = new VisitParentExpressionVisitor<Optional<Expression>, Context<Void>>(Optional.empty()) {
@Override
public Optional<Expression> visitQualifiedColumnReference(final QualifiedColumnReferenceExp node, final Context<Void> ctx) {
return Optional.of(new UnqualifiedColumnReferenceExp(ColumnNames.generatedJoinColumnAlias(node.getQualifier(), node.getColumnName())));
}
};
final Expression aliasedForeignKeyExpression = ExpressionTreeRewriter.rewriteWith(aliasRewritter::process, foreignKeyExpression);
return JoinKey.foreignKey(aliasedForeignKeyExpression, leftSourceKeys);
}
use of io.confluent.ksql.planner.JoinTree.Join in project ksql by confluentinc.
the class JoinTreeTest method handlesRightThreeWayJoin.
@Test
public void handlesRightThreeWayJoin() {
// Given:
when(j1.getLeftSource()).thenReturn(a);
when(j1.getRightSource()).thenReturn(b);
when(j2.getLeftSource()).thenReturn(c);
when(j2.getRightSource()).thenReturn(a);
when(j2.flip()).thenReturn(j2);
final List<JoinInfo> joins = ImmutableList.of(j1, j2);
// When:
final Node root = JoinTree.build(joins);
// Then:
assertThat(root, instanceOf(Join.class));
assertThat(root, is(new Join(new Join(new Leaf(a), new Leaf(b), j1), new Leaf(c), j2)));
}
use of io.confluent.ksql.planner.JoinTree.Join in project ksql by confluentinc.
the class LogicalPlanner method joinOnNonKeyAttribute.
private static boolean joinOnNonKeyAttribute(final Expression joinExpression, final PlanNode node, final AliasedDataSource aliasedDataSource) {
if (!(joinExpression instanceof ColumnReferenceExp)) {
return true;
}
final ColumnReferenceExp simpleJoinExpression = (ColumnReferenceExp) joinExpression;
final ColumnName joinAttributeName = simpleJoinExpression.getColumnName();
final List<DataSourceNode> dataSourceNodes = node.getSourceNodes().collect(Collectors.toList());
final List<Column> keyColumns;
// n-way join sub-tree (ie, not a leaf)
if (isInnerNode(node)) {
final DataSourceNode qualifiedNode;
if (simpleJoinExpression.maybeQualifier().isPresent()) {
final SourceName qualifierOrAlias = simpleJoinExpression.maybeQualifier().get();
final SourceName qualifier;
if (aliasedDataSource.getAlias().equals(qualifierOrAlias)) {
qualifier = aliasedDataSource.getDataSource().getName();
} else {
qualifier = qualifierOrAlias;
}
final List<DataSourceNode> allNodes = dataSourceNodes.stream().filter(n -> n.getDataSource().getName().equals(qualifier)).collect(Collectors.toList());
if (allNodes.size() != 1) {
throw new KsqlException(String.format("Join qualifier '%s' could not be resolved (either not found or not unique).", qualifier));
}
qualifiedNode = Iterables.getOnlyElement(allNodes);
} else {
final List<DataSourceNode> allNodes = dataSourceNodes.stream().filter(n -> n.getSchema().findColumn(simpleJoinExpression.getColumnName()).isPresent()).collect(Collectors.toList());
if (allNodes.size() != 1) {
throw new KsqlException(String.format("Join identifier '%s' could not be resolved (either not found or not unique).", joinAttributeName));
}
qualifiedNode = Iterables.getOnlyElement(allNodes);
}
keyColumns = qualifiedNode.getSchema().key();
} else {
// leaf node: we know we have single data source
keyColumns = Iterables.getOnlyElement(dataSourceNodes).getSchema().key();
}
// - thus, if the key has more than one column, the join is not on the key
if (keyColumns.size() > 1) {
return true;
}
return !joinAttributeName.equals(Iterables.getOnlyElement(keyColumns).name());
}
use of io.confluent.ksql.planner.JoinTree.Join in project ksql by confluentinc.
the class LogicalPlanner method buildAggregateSchema.
private LogicalSchema buildAggregateSchema(final PlanNode sourcePlanNode, final GroupBy groupBy, final List<SelectExpression> projectionExpressions) {
final LogicalSchema sourceSchema = sourcePlanNode.getSchema();
final LogicalSchema projectionSchema = SelectionUtil.buildProjectionSchema(sourceSchema.withPseudoAndKeyColsInValue(analysis.getWindowExpression().isPresent(), ksqlConfig), projectionExpressions, metaStore);
final List<Expression> groupByExps = groupBy.getGroupingExpressions();
final Function<Expression, Optional<ColumnName>> selectResolver = expression -> {
final List<ColumnName> foundInProjection = projectionExpressions.stream().filter(e -> e.getExpression().equals(expression)).map(SelectExpression::getAlias).collect(Collectors.toList());
switch(foundInProjection.size()) {
case 0:
return Optional.empty();
case 1:
return Optional.of(foundInProjection.get(0));
default:
final String keys = GrammaticalJoiner.and().join(foundInProjection);
throw new KsqlException("The projection contains a key column more than once: " + keys + "." + System.lineSeparator() + "Each key column must only be in the projection once. " + "If you intended to copy the key into the value, then consider using the " + AsValue.NAME + " function to indicate which key reference should be copied.");
}
};
final List<Column> valueColumns;
if (analysis.getInto().isPresent()) {
// Persistent query:
final Set<ColumnName> keyColumnNames = groupBy.getGroupingExpressions().stream().map(selectResolver).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet());
valueColumns = projectionSchema.value().stream().filter(col -> !keyColumnNames.contains(col.name())).collect(Collectors.toList());
if (valueColumns.isEmpty()) {
throw new KsqlException("The projection contains no value columns.");
}
} else {
// Transient query:
// Transient queries only return value columns, so must have key columns in the value:
valueColumns = projectionSchema.columns();
}
final Builder builder = LogicalSchema.builder();
final ExpressionTypeManager typeManager = new ExpressionTypeManager(sourceSchema, metaStore);
for (final Expression expression : groupByExps) {
final SqlType keyType = typeManager.getExpressionSqlType(expression);
final ColumnName keyName = selectResolver.apply(expression).orElseGet(() -> expression instanceof ColumnReferenceExp ? ((ColumnReferenceExp) expression).getColumnName() : ColumnNames.uniqueAliasFor(expression, sourceSchema));
builder.keyColumn(keyName, keyType);
}
return builder.valueColumns(valueColumns).build();
}
Aggregations