
it.unibz.inf.ontop.iq.node.impl.InnerJoinNodeImpl Maven / Gradle / Ivy
package it.unibz.inf.ontop.iq.node.impl;
import com.google.common.collect.*;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import it.unibz.inf.ontop.evaluator.TermNullabilityEvaluator;
import it.unibz.inf.ontop.exception.MinorOntopInternalBugException;
import it.unibz.inf.ontop.injection.IntermediateQueryFactory;
import it.unibz.inf.ontop.iq.exception.InvalidIntermediateQueryException;
import it.unibz.inf.ontop.iq.exception.QueryNodeTransformationException;
import it.unibz.inf.ontop.iq.impl.IQTreeTools;
import it.unibz.inf.ontop.iq.node.*;
import it.unibz.inf.ontop.iq.node.normalization.ConditionSimplifier.ExpressionAndSubstitution;
import it.unibz.inf.ontop.iq.node.normalization.ConditionSimplifier;
import it.unibz.inf.ontop.iq.node.normalization.InnerJoinNormalizer;
import it.unibz.inf.ontop.iq.request.FunctionalDependencies;
import it.unibz.inf.ontop.iq.request.VariableNonRequirement;
import it.unibz.inf.ontop.iq.transform.IQTreeExtendedTransformer;
import it.unibz.inf.ontop.iq.transform.IQTreeVisitingTransformer;
import it.unibz.inf.ontop.iq.visit.IQVisitor;
import it.unibz.inf.ontop.model.term.*;
import it.unibz.inf.ontop.model.type.TypeFactory;
import it.unibz.inf.ontop.substitution.Substitution;
import it.unibz.inf.ontop.iq.*;
import it.unibz.inf.ontop.iq.transform.node.HomogeneousQueryNodeTransformer;
import it.unibz.inf.ontop.substitution.InjectiveSubstitution;
import it.unibz.inf.ontop.substitution.SubstitutionFactory;
import it.unibz.inf.ontop.utils.ImmutableCollectors;
import it.unibz.inf.ontop.utils.VariableGenerator;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class InnerJoinNodeImpl extends JoinLikeNodeImpl implements InnerJoinNode {
private static final String JOIN_NODE_STR = "JOIN";
private final InnerJoinNormalizer normalizer;
@AssistedInject
protected InnerJoinNodeImpl(@Assisted Optional optionalFilterCondition,
TermNullabilityEvaluator nullabilityEvaluator,
TermFactory termFactory, TypeFactory typeFactory,
IntermediateQueryFactory iqFactory, SubstitutionFactory substitutionFactory,
IQTreeTools iqTreeTools,
JoinOrFilterVariableNullabilityTools variableNullabilityTools, ConditionSimplifier conditionSimplifier,
InnerJoinNormalizer normalizer) {
super(optionalFilterCondition, nullabilityEvaluator, termFactory, iqFactory, typeFactory,
substitutionFactory, variableNullabilityTools, conditionSimplifier, iqTreeTools);
this.normalizer = normalizer;
}
@AssistedInject
private InnerJoinNodeImpl(@Assisted ImmutableExpression joiningCondition,
TermNullabilityEvaluator nullabilityEvaluator,
TermFactory termFactory, TypeFactory typeFactory,
IntermediateQueryFactory iqFactory, SubstitutionFactory substitutionFactory,
IQTreeTools iqTreeTools,
JoinOrFilterVariableNullabilityTools variableNullabilityTools, ConditionSimplifier conditionSimplifier, InnerJoinNormalizer normalizer) {
this(Optional.of(joiningCondition), nullabilityEvaluator, termFactory, typeFactory, iqFactory,
substitutionFactory, iqTreeTools, variableNullabilityTools, conditionSimplifier, normalizer);
}
@AssistedInject
private InnerJoinNodeImpl(TermNullabilityEvaluator nullabilityEvaluator, TermFactory termFactory,
TypeFactory typeFactory, IntermediateQueryFactory iqFactory,
SubstitutionFactory substitutionFactory, IQTreeTools iqTreeTools,
JoinOrFilterVariableNullabilityTools variableNullabilityTools, ConditionSimplifier conditionSimplifier, InnerJoinNormalizer normalizer) {
this(Optional.empty(), nullabilityEvaluator, termFactory, typeFactory, iqFactory,
substitutionFactory, iqTreeTools, variableNullabilityTools, conditionSimplifier, normalizer);
}
@Override
public void acceptVisitor(QueryNodeVisitor visitor) {
visitor.visit(this);
}
@Override
public InnerJoinNode acceptNodeTransformer(HomogeneousQueryNodeTransformer transformer)
throws QueryNodeTransformationException {
return transformer.transform(this);
}
@Override
public ImmutableSet> getPossibleVariableDefinitions(ImmutableList children) {
return children.stream()
.map(IQTree::getPossibleVariableDefinitions)
.filter(s -> !s.isEmpty())
.reduce(ImmutableSet.of(), this::combineVarDefs);
}
private ImmutableSet> combineVarDefs(
ImmutableSet> s1,
ImmutableSet> s2) {
// substitutionFactory.compose takes the first definition of a common variable.
// It behaves like a union except that is robust to "non-identical" definitions.
// If normalized, two definitions for the same variables are expected to be compatible.
//
// If not normalized, the definitions may be incompatible, but that's fine
// since they will not produce any result.
return s1.isEmpty()
? s2
: s1.stream()
.flatMap(d1 -> s2.stream()
.map(d2 -> substitutionFactory.onNonVariableTerms().compose(d2, d1)))
.collect(ImmutableCollectors.toSet());
}
@Override
public InnerJoinNode changeOptionalFilterCondition(Optional newOptionalFilterCondition) {
return iqFactory.createInnerJoinNode(newOptionalFilterCondition);
}
@Override
public int hashCode() {
return getOptionalFilterCondition().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof InnerJoinNodeImpl) {
InnerJoinNodeImpl that = (InnerJoinNodeImpl) o;
return getOptionalFilterCondition().equals(that.getOptionalFilterCondition());
}
return false;
}
@Override
public String toString() {
return JOIN_NODE_STR + getOptionalFilterString();
}
/**
* TODO: refactor
*/
@Override
public IQTree normalizeForOptimization(ImmutableList children, VariableGenerator variableGenerator, IQTreeCache treeCache) {
return normalizer.normalizeForOptimization(this, children, variableGenerator, treeCache);
}
@Override
public IQTree applyDescendingSubstitution(Substitution extends VariableOrGroundTerm> descendingSubstitution,
Optional constraint, ImmutableList children,
VariableGenerator variableGenerator) {
Optional unoptimizedExpression = getOptionalFilterCondition()
.map(descendingSubstitution::apply);
VariableNullability simplifiedChildFutureVariableNullability = variableNullabilityTools.getSimplifiedVariableNullability(
iqTreeTools.computeNewProjectedVariables(descendingSubstitution, iqTreeTools.getChildrenVariables(children)));
VariableNullability extendedVariableNullability = constraint
.map(c -> simplifiedChildFutureVariableNullability.extendToExternalVariables(c.getVariableStream()))
.orElse(simplifiedChildFutureVariableNullability);
try {
ExpressionAndSubstitution expressionAndSubstitution = conditionSimplifier.simplifyCondition(
unoptimizedExpression, ImmutableSet.of(), children, simplifiedChildFutureVariableNullability);
Optional downConstraint = conditionSimplifier.computeDownConstraint(constraint,
expressionAndSubstitution, extendedVariableNullability);
Substitution extends VariableOrGroundTerm> downSubstitution =
substitutionFactory.onVariableOrGroundTerms().compose(descendingSubstitution, expressionAndSubstitution.getSubstitution());
ImmutableList newChildren = children.stream()
.map(c -> c.applyDescendingSubstitution(downSubstitution, downConstraint, variableGenerator))
.collect(ImmutableCollectors.toList());
IQTree joinTree = iqFactory.createNaryIQTree(
iqFactory.createInnerJoinNode(expressionAndSubstitution.getOptionalExpression()),
newChildren);
return iqTreeTools.createConstructionNodeTreeIfNontrivial(joinTree, expressionAndSubstitution.getSubstitution(),
() -> iqTreeTools.computeNewProjectedVariables(descendingSubstitution, iqTreeTools.getChildrenVariables(children)));
}
catch (UnsatisfiableConditionException e) {
return iqFactory.createEmptyNode(
iqTreeTools.computeNewProjectedVariables(descendingSubstitution, iqTreeTools.getChildrenVariables(children)));
}
}
@Override
public IQTree applyDescendingSubstitutionWithoutOptimizing(
Substitution extends VariableOrGroundTerm> descendingSubstitution, ImmutableList children,
VariableGenerator variableGenerator) {
InnerJoinNode newJoinNode = iqTreeTools.createInnerJoinNode(
getOptionalFilterCondition().map(descendingSubstitution::apply));
ImmutableList newChildren = children.stream()
.map(c -> c.applyDescendingSubstitutionWithoutOptimizing(descendingSubstitution, variableGenerator))
.collect(ImmutableCollectors.toList());
return iqFactory.createNaryIQTree(newJoinNode, newChildren);
}
@Override
public IQTree applyFreshRenaming(InjectiveSubstitution renamingSubstitution, ImmutableList children,
IQTreeCache treeCache) {
ImmutableList newChildren = children.stream()
.map(c -> c.applyFreshRenaming(renamingSubstitution))
.collect(ImmutableCollectors.toList());
Optional newCondition = getOptionalFilterCondition()
.map(renamingSubstitution::apply);
InnerJoinNode newJoinNode = newCondition.equals(getOptionalFilterCondition())
? this
: iqFactory.createInnerJoinNode(newCondition);
IQTreeCache newTreeCache = treeCache.applyFreshRenaming(renamingSubstitution);
return iqFactory.createNaryIQTree(newJoinNode, newChildren, newTreeCache);
}
@Override
public VariableNullability getVariableNullability(ImmutableList children) {
return variableNullabilityTools.getVariableNullability(children, getOptionalFilterCondition());
}
@Override
public boolean isConstructed(Variable variable, ImmutableList children) {
return children.stream()
.anyMatch(c -> c.isConstructed(variable));
}
@Override
public boolean isDistinct(IQTree tree, ImmutableList children) {
return super.isDistinct(tree, children);
}
@Override
public IQTree liftIncompatibleDefinitions(Variable variable, ImmutableList children, VariableGenerator variableGenerator) {
return IntStream.range(0, children.size())
.mapToObj(i -> Maps.immutableEntry(i, children.get(i)))
.filter(e -> e.getValue().isConstructed(variable))
// index -> new child
.map(e -> Maps.immutableEntry(e.getKey(), e.getValue().liftIncompatibleDefinitions(variable, variableGenerator)))
.filter(e -> {
QueryNode newRootNode = e.getValue().getRootNode();
return (newRootNode instanceof UnionNode)
&& ((UnionNode) newRootNode).hasAChildWithLiftableDefinition(variable,
e.getValue().getChildren());
})
.findFirst()
.map(e -> liftUnionChild(e.getKey(), (NaryIQTree) e.getValue(), children, variableGenerator))
.orElseGet(() -> iqFactory.createNaryIQTree(this, children));
}
@Override
public IQTree acceptTransformer(IQTree tree, IQTreeVisitingTransformer transformer, ImmutableList children) {
return transformer.transformInnerJoin(tree,this, children);
}
@Override
public IQTree acceptTransformer(IQTree tree, IQTreeExtendedTransformer transformer, ImmutableList children,
T context) {
return transformer.transformInnerJoin(tree,this, children, context);
}
@Override
public T acceptVisitor(IQVisitor visitor, ImmutableList children) {
return visitor.visitInnerJoin(this, children);
}
@Override
public void validateNode(ImmutableList children) throws InvalidIntermediateQueryException {
if (children.size() < 2) {
throw new InvalidIntermediateQueryException("JOIN node " + this
+" does not have at least 2 children.\n" + children);
}
getOptionalFilterCondition()
.ifPresent(e -> checkExpression(e, children));
checkNonProjectedVariables(children);
}
@Override
public IQTree removeDistincts(ImmutableList children, IQTreeCache treeCache) {
ImmutableList newChildren = children.stream()
.map(IQTree::removeDistincts)
.collect(ImmutableCollectors.toList());
IQTreeCache newTreeCache = treeCache.declareDistinctRemoval(newChildren.equals(children));
return iqFactory.createNaryIQTree(this, newChildren, newTreeCache);
}
/**
* For unique constraints to emerge from an inner join, children must provide unique constraints.
*/
@Override
public ImmutableSet> inferUniqueConstraints(ImmutableList children) {
ImmutableSet childrenSet = ImmutableSet.copyOf(children);
ImmutableMap>> constraintMap = childrenSet.stream()
.collect(ImmutableCollectors.toMap(
c -> c,
IQTree::inferUniqueConstraints));
/*
* Pre-condition: all the children must have at least one unique constraint
*/
if (constraintMap.values().stream().anyMatch(AbstractCollection::isEmpty))
return ImmutableSet.of();
ImmutableSet> naturalJoinConstraints = extractConstraintsOverNaturalJoins(children,
childrenSet, constraintMap);
ImmutableSet> combinedConstraints = extractCombinedConstraints(constraintMap.values(),
getVariableNullability(children));
return removeRedundantConstraints(Sets.union(naturalJoinConstraints, combinedConstraints));
}
/**
* Naturally joined over some of children constraints.
* TODO: see if still needed
*/
private ImmutableSet> extractConstraintsOverNaturalJoins(ImmutableList children,
ImmutableSet childrenSet,
ImmutableMap>> childConstraintMap) {
// Non-saturated
ImmutableMultimap directDependencyMap = IntStream.range(0, children.size() - 1)
.boxed()
.flatMap(i -> IntStream.range(i +1, children.size())
.boxed()
.flatMap(j -> extractFunctionalDependencies(children.get(i), children.get(j), childConstraintMap)))
.collect(ImmutableCollectors.toMultimap());
Multimap saturatedDependencyMap = saturateDependencies(directDependencyMap);
return saturatedDependencyMap.asMap().entrySet().stream()
.filter(e -> e.getValue().containsAll(Sets.difference(childrenSet, ImmutableSet.of(e.getKey()))))
.map(Map.Entry::getKey)
.flatMap(child -> childConstraintMap.get(child).stream())
.collect(ImmutableCollectors.toSet());
}
private ImmutableSet> extractCombinedConstraints(
ImmutableCollection>> childConstraints,
VariableNullability variableNullability) {
ImmutableList>> nonNullableConstraints = childConstraints.stream()
.map(cs -> cs.stream()
.filter(c -> c.stream().noneMatch(variableNullability::isPossiblyNullable))
.collect(ImmutableCollectors.toSet()))
.collect(ImmutableCollectors.toList());
if (nonNullableConstraints.isEmpty() || nonNullableConstraints.stream().anyMatch(AbstractCollection::isEmpty))
return ImmutableSet.of();
return computeCartesianProduct(nonNullableConstraints, 0);
}
private ImmutableSet> computeCartesianProduct(ImmutableList>> nonNullableConstraints,
int index) {
int arity = nonNullableConstraints.size();
if (index == (arity -1))
return nonNullableConstraints.get(index);
ImmutableSet> followingCartesianProduct = computeCartesianProduct(nonNullableConstraints, index + 1);
return nonNullableConstraints.get(index).stream()
.flatMap(c -> followingCartesianProduct.stream()
.map(c1 -> Sets.union(c, c1).immutableCopy()))
.collect(ImmutableCollectors.toSet());
}
private ImmutableSet> removeRedundantConstraints(Set> allConstraints) {
Set> mergedConstraints = allConstraints.stream()
.sorted(Comparator.comparingInt(AbstractCollection::size))
.reduce(Sets.newHashSet(),
(cs, c1) -> {
if (cs.stream().noneMatch(c1::containsAll))
cs.add(c1);
return cs;
}
,
(c1, c2) -> {
throw new MinorOntopInternalBugException("No merging");
});
return ImmutableSet.copyOf(mergedConstraints);
}
/*
We can simply collect all FDs from the children.
*/
@Override
public FunctionalDependencies inferFunctionalDependencies(ImmutableList children, ImmutableSet> uniqueConstraints, ImmutableSet variables) {
return children.stream()
.flatMap(child -> child.inferFunctionalDependencies().stream())
.collect(FunctionalDependencies.toFunctionalDependencies());
}
@Override
public VariableNonRequirement computeVariableNonRequirement(ImmutableList children) {
return super.computeVariableNonRequirement(children);
}
private Stream> extractFunctionalDependencies(
IQTree t1, IQTree t2, ImmutableMap>> constraintMap) {
Set commonVariables = Sets.intersection(t1.getVariables(), t2.getVariables());
if (commonVariables.isEmpty())
return Stream.empty();
return Stream.of(
Optional.of(Maps.immutableEntry(t1, t2))
.filter(e -> constraintMap.get(e.getValue()).stream()
.anyMatch(commonVariables::containsAll)),
Optional.of(Maps.immutableEntry(t2, t1))
.filter(e -> constraintMap.get(e.getValue()).stream()
.anyMatch(commonVariables::containsAll)))
.flatMap(Optional::stream);
}
private Multimap saturateDependencies(ImmutableMultimap directDependencyMap) {
Multimap mutableMultimap = HashMultimap.create(directDependencyMap);
boolean hasConverged;
do {
hasConverged = true;
for (IQTree determinant : directDependencyMap.keys()) {
ImmutableSet dependents = ImmutableSet.copyOf(mutableMultimap.get(determinant));
for (IQTree dependent : dependents) {
if (mutableMultimap.putAll(determinant, mutableMultimap.get(dependent)))
hasConverged = false;
}
}
} while (!hasConverged);
return mutableMultimap;
}
@Override
public IQTree propagateDownConstraint(ImmutableExpression constraint, ImmutableList children,
VariableGenerator variableGenerator) {
VariableNullability extendedChildrenVariableNullability = variableNullabilityTools.getChildrenVariableNullability(children)
.extendToExternalVariables(constraint.getVariableStream());
try {
ExpressionAndSubstitution conditionSimplificationResults = conditionSimplifier.simplifyCondition(
getOptionalFilterCondition(), ImmutableSet.of(), children, extendedChildrenVariableNullability);
Optional downConstraint = conditionSimplifier.computeDownConstraint(Optional.of(constraint),
conditionSimplificationResults, extendedChildrenVariableNullability);
//TODO: propagate different constraints to different children
ImmutableList newChildren = Optional.of(conditionSimplificationResults.getSubstitution())
.filter(s -> !s.isEmpty())
.map(s -> children.stream()
.map(child -> child.applyDescendingSubstitution(s, downConstraint, variableGenerator))
.collect(ImmutableCollectors.toList()))
.orElseGet(() -> downConstraint
.map(s -> children.stream()
.map(child -> child.propagateDownConstraint(s, variableGenerator))
.collect(ImmutableCollectors.toList()))
.orElse(children));
InnerJoinNode newJoin = conditionSimplificationResults.getOptionalExpression().equals(getOptionalFilterCondition())
? this
: iqTreeTools.createInnerJoinNode(conditionSimplificationResults.getOptionalExpression());
NaryIQTree joinTree = iqFactory.createNaryIQTree(newJoin, newChildren);
return iqTreeTools.createConstructionNodeTreeIfNontrivial(joinTree, conditionSimplificationResults.getSubstitution(),
() -> iqTreeTools.getChildrenVariables(children));
}
catch (UnsatisfiableConditionException e) {
return iqFactory.createEmptyNode(iqTreeTools.getChildrenVariables(children));
}
}
private IQTree liftUnionChild(int childIndex, NaryIQTree newUnionChild, ImmutableList initialChildren,
VariableGenerator variableGenerator) {
UnionNode newUnionNode = iqFactory.createUnionNode(iqTreeTools.getChildrenVariables(initialChildren));
return iqFactory.createNaryIQTree(newUnionNode,
newUnionChild.getChildren().stream()
.map(unionGrandChild -> createJoinSubtree(childIndex, unionGrandChild, initialChildren))
.collect(ImmutableCollectors.toList()))
.normalizeForOptimization(variableGenerator);
}
private IQTree createJoinSubtree(int childIndex, IQTree unionGrandChild, ImmutableList initialChildren) {
return iqFactory.createNaryIQTree(this,
IntStream.range(0, initialChildren.size())
.mapToObj(i -> i == childIndex
? unionGrandChild
: initialChildren.get(i))
.collect(ImmutableCollectors.toList()));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy