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

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 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 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 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