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

it.unibz.inf.ontop.model.term.functionsymbol.impl.MinOrMaxOrSampleSPARQLFunctionSymbolImpl Maven / Gradle / Ivy

package it.unibz.inf.ontop.model.term.functionsymbol.impl;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import it.unibz.inf.ontop.exception.MinorOntopInternalBugException;
import it.unibz.inf.ontop.iq.node.VariableNullability;
import it.unibz.inf.ontop.iq.request.DefinitionPushDownRequest;
import it.unibz.inf.ontop.model.term.*;
import it.unibz.inf.ontop.model.term.functionsymbol.InequalityLabel;
import it.unibz.inf.ontop.model.term.functionsymbol.SPARQLAggregationFunctionSymbol;
import it.unibz.inf.ontop.model.type.*;
import it.unibz.inf.ontop.model.vocabulary.SPARQL;
import it.unibz.inf.ontop.substitution.Substitution;
import it.unibz.inf.ontop.utils.ImmutableCollectors;
import it.unibz.inf.ontop.utils.VariableGenerator;

import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class MinOrMaxOrSampleSPARQLFunctionSymbolImpl extends SPARQLFunctionSymbolImpl
        implements SPARQLAggregationFunctionSymbol {

    public enum MinMaxSampleType {
        MIN,
        MAX,
        SAMPLE
    }

    private final TypeFactory typeFactory;
    private final MinMaxSampleType type;
    private final RDFDatatype abstractNumericType;
    private final RDFDatatype dateTimeType;
    private final ObjectRDFType bnodeType;
    private final ObjectRDFType iriType;
    private final String defaultAggVariableName;
    private final InequalityLabel inequalityLabel;

    protected MinOrMaxOrSampleSPARQLFunctionSymbolImpl(TypeFactory typeFactory, MinMaxSampleType type) {
        this(
                type == MinMaxSampleType.MAX ? "SP_MAX" : (type == MinMaxSampleType.MIN ? "SP_MIN" : "SP_SAMPLE"),
                type == MinMaxSampleType.MAX ? SPARQL.MAX : (type == MinMaxSampleType.MIN ? SPARQL.MIN : SPARQL.SAMPLE),
                typeFactory,
                type
        );
    }
    protected MinOrMaxOrSampleSPARQLFunctionSymbolImpl(String name, String officialName, TypeFactory typeFactory, MinMaxSampleType type) {
        super(name, officialName, ImmutableList.of(typeFactory.getAbstractRDFTermType()));
        this.typeFactory = typeFactory;
        this.type = type;
        this.abstractNumericType = typeFactory.getAbstractOntopNumericDatatype();
        this.dateTimeType = typeFactory.getXsdDatetimeDatatype();
        this.bnodeType = typeFactory.getBlankNodeType();
        this.iriType = typeFactory.getIRITermType();
        this.defaultAggVariableName = type == MinMaxSampleType.MAX ? "max1" : (type == MinMaxSampleType.MIN ? "min1" : "sample1");
        this.inequalityLabel = type == MinMaxSampleType.MAX ? InequalityLabel.GT : InequalityLabel.LT;
    }

    @Override
    protected boolean tolerateNulls() {
        return false;
    }

    @Override
    public boolean isAlwaysInjectiveInTheAbsenceOfNonInjectiveFunctionalTerms() {
        return false;
    }

    @Override
    public boolean isAggregation() {
        return true;
    }

    /**
     * Too complex to be implemented for the moment
     */
    @Override
    public Optional inferType(ImmutableList terms) {
        return Optional.empty();
    }

    @Override
    public boolean canBePostProcessed(ImmutableList arguments) {
        return false;
    }

    @Override
    public boolean isNullable(ImmutableSet nullableIndexes) {
        return true;
    }

    /**
     * TODO: put it into common in an abstract class
     */
    @Override
    public Optional decomposeIntoDBAggregation(ImmutableList subTerms,
                                                                          ImmutableList> possibleRDFTypes,
                                                                          boolean hasGroupBy, VariableNullability variableNullability,
                                                                          VariableGenerator variableGenerator, TermFactory termFactory) {
        if (possibleRDFTypes.size() != getArity()) {
            throw new IllegalArgumentException("The size of possibleRDFTypes is expected to match the arity of " +
                    "the function symbol");
        }
        ImmutableTerm subTerm = subTerms.get(0);
        ImmutableSet subTermPossibleTypes = possibleRDFTypes.get(0);

        switch (subTermPossibleTypes.size()) {
            case 0:
                throw new MinorOntopInternalBugException("At least one RDF type was expected to be inferred for the first sub-term");
            case 1:
                return Optional.of(decomposeUnityped(subTerm, subTermPossibleTypes.iterator().next(), hasGroupBy, variableNullability,
                        variableGenerator, termFactory));
            default:
                return Optional.of(decomposeMultityped(subTerm, subTermPossibleTypes, hasGroupBy, variableNullability,
                        variableGenerator, termFactory));
        }
    }

    private AggregationSimplification decomposeUnityped(ImmutableTerm subTerm, RDFTermType subTermType, boolean hasGroupBy,
                                                        VariableNullability variableNullability, VariableGenerator variableGenerator,
                                                        TermFactory termFactory) {

        ImmutableTerm subTermLexicalTerm = extractLexicalTerm(subTerm, termFactory);

        ImmutableFunctionalTerm dbAggTerm = createAggregate(
                subTermType,
                termFactory.getConversionFromRDFLexical2DB(subTermLexicalTerm, subTermType),
                termFactory);

        RDFTermTypeConstant subTermTypeConstant = termFactory.getRDFTermTypeConstant(subTermType);

        Variable dbAggregationVariable = variableGenerator.generateNewVariable(defaultAggVariableName);

        boolean isSubTermNullable = subTermLexicalTerm.isNullable(variableNullability.getNullableVariables());
        boolean dbAggMayReturnNull = !(hasGroupBy && (!isSubTermNullable));

        ImmutableTerm typeTerm = dbAggMayReturnNull
                ? termFactory.getIfElseNull(
                        termFactory.getDBIsNotNull(dbAggregationVariable), subTermTypeConstant)
                : subTermTypeConstant;

        ImmutableFunctionalTerm liftedTerm = termFactory.getRDFFunctionalTerm(
                termFactory.getConversion2RDFLexical(dbAggregationVariable, subTermType),
                typeTerm);

        ImmutableFunctionalTerm.FunctionalTermDecomposition decomposition = termFactory.getFunctionalTermDecomposition(
                liftedTerm,
                termFactory.getSubstitution(ImmutableMap.of(dbAggregationVariable, dbAggTerm)));

        return AggregationSimplification.create(decomposition);
    }

    private ImmutableFunctionalTerm createAggregate(RDFTermType rdfType, ImmutableTerm dbSubTerm, TermFactory termFactory) {
        DBTermType dbType = rdfType.getClosestDBType(typeFactory.getDBTypeFactory());
        return type == MinMaxSampleType.MAX
                ? termFactory.getDBMax(dbSubTerm, dbType)
                : (type == MinMaxSampleType.MIN
                    ? termFactory.getDBMin(dbSubTerm, dbType)
                    : termFactory.getDBSample(dbSubTerm, dbType)
                );
    }

    private AggregationSimplification decomposeMultityped(ImmutableTerm subTerm, ImmutableSet subTermPossibleTypes,
                                                          boolean hasGroupBy, VariableNullability variableNullability,
                                                          VariableGenerator variableGenerator, TermFactory termFactory) {

        ImmutableTerm subTermLexicalTerm = extractLexicalTerm(subTerm, termFactory);
        ImmutableTerm subTermTypeTerm = extractRDFTermTypeTerm(subTerm, termFactory);

        // One fresh variable per type
        ImmutableMap subVariableMap = subTermPossibleTypes.stream()
                .collect(ImmutableCollectors.toMap(
                        t -> t,
                        t -> variableGenerator.generateNewVariable()));

        // Each variable of the subVariableMap will have the closest DB type to its associated RDF type
        ImmutableSet pushDownRequests = computeRequests(subTermLexicalTerm, subTermTypeTerm,
                subVariableMap, termFactory, variableNullability);

        // One fresh variable per type
        ImmutableMap aggregateMap = subTermPossibleTypes.stream()
                .collect(ImmutableCollectors.toMap(
                        t -> t,
                        t -> variableGenerator.generateNewVariable()));

        Substitution substitution = computeSubstitution(subVariableMap, aggregateMap,
                termFactory);

        ImmutableFunctionalTerm liftableTerm = computeLiftableTerm(aggregateMap, termFactory);

        return AggregationSimplification.create(
                termFactory.getFunctionalTermDecomposition(liftableTerm, substitution),
                pushDownRequests);
    }

    private ImmutableSet computeRequests(ImmutableTerm subTermLexicalTerm, ImmutableTerm subTermTypeTerm,
                                                                    ImmutableMap subVariableMap,
                                                                    TermFactory termFactory, VariableNullability variableNullability) {
        return subVariableMap.entrySet().stream()
                .map(e -> computeRequest(subTermLexicalTerm, subTermTypeTerm, e.getKey(), e.getValue(), termFactory,
                        variableNullability))
                .collect(ImmutableCollectors.toSet());
    }

    private DefinitionPushDownRequest computeRequest(ImmutableTerm subTermLexicalTerm, ImmutableTerm subTermTypeTerm,
                                                     RDFTermType rdfType, Variable newVariable, TermFactory termFactory,
                                                     VariableNullability variableNullability) {
        ImmutableTerm definition = termFactory.getConversionFromRDFLexical2DB(subTermLexicalTerm, rdfType)
                .simplify(variableNullability);

        ImmutableExpression condition = termFactory.getStrictEquality(subTermTypeTerm, termFactory.getRDFTermTypeConstant(rdfType));

        return DefinitionPushDownRequest.create(newVariable, definition, condition);
    }

    private Substitution computeSubstitution(ImmutableMap subVariableMap,
                                                                      ImmutableMap aggregateMap,
                                                                      TermFactory termFactory) {
        return termFactory.getSubstitution(aggregateMap.entrySet().stream()
                .collect(ImmutableCollectors.toMap(
                        Map.Entry::getValue,
                        e -> createAggregate(e.getKey(), subVariableMap.get(e.getKey()), termFactory))));
    }

    private ImmutableFunctionalTerm computeLiftableTerm(ImmutableMap aggregateMap,
                                                        TermFactory termFactory) {

        ImmutableList> typeTermWhenPairs = computeTypeTermWhenPairs(
                aggregateMap, termFactory);

        ImmutableFunctionalTerm typeTerm = termFactory.getDBCase(typeTermWhenPairs.stream(), termFactory.getNullConstant(), true);
        ImmutableFunctionalTerm lexicalTerm = computeLexicalTerm(typeTermWhenPairs, aggregateMap, termFactory);

        return termFactory.getRDFFunctionalTerm(lexicalTerm, typeTerm);
    }

    private ImmutableList> computeTypeTermWhenPairs(
            ImmutableMap aggregateMap,
            TermFactory termFactory) {

        Optional> bnodeEntry =
                Optional.ofNullable(aggregateMap.get(bnodeType))
                        .map(v -> createRegularTypeWhenPair(v, bnodeType, termFactory));

        Optional> iriEntry =
                Optional.ofNullable(aggregateMap.get(iriType))
                        .map(v -> createRegularTypeWhenPair(v, iriType, termFactory));

        Stream> objectEntries = Stream.of(bnodeEntry, iriEntry)
                .filter(Optional::isPresent)
                .map(Optional::get);

        Stream> numericEntries = computeNumericOrDatetimeTypePairs(
                abstractNumericType, aggregateMap,
                (agg1, agg2) -> termFactory.getDBNumericInequality(inequalityLabel, agg1, agg2),
                termFactory);

        Stream> datetimeEntries = computeNumericOrDatetimeTypePairs(
                dateTimeType, aggregateMap,
                (agg1, agg2) -> termFactory.getDBDatetimeInequality(inequalityLabel, agg1, agg2),
                termFactory);

        Stream> otherEntries = aggregateMap.entrySet().stream()
                .filter(e -> e.getKey() instanceof RDFDatatype)
                .filter(e -> (!e.getKey().isA(abstractNumericType))
                        && (!e.getKey().isA(dateTimeType)))
                .map(e -> createRegularTypeWhenPair(e.getValue(), e.getKey(), termFactory));

        return Stream.concat(
                    Stream.concat(objectEntries, numericEntries),
                    Stream.concat(datetimeEntries, otherEntries))
                .collect(ImmutableCollectors.toList());
    }

    private BiFunction orOnlyFirstArgumentIsNotNull(
            BiFunction fct, TermFactory termFactory) {
        return (agg1, agg2) -> termFactory.getDisjunction(
                fct.apply(agg1, agg2),
                termFactory.getConjunction(
                        termFactory.getDBIsNotNull(agg1),
                        termFactory.getDBIsNull(agg2)));
    }

    private Map.Entry createRegularTypeWhenPair(Variable aggregateVariable,
                                                                                              RDFTermType rdfType,
                                                                                              TermFactory termFactory) {
        return Maps.immutableEntry(
                termFactory.getDBIsNotNull(aggregateVariable),
                termFactory.getRDFTermTypeConstant(rdfType));
    }

    private Stream> computeNumericOrDatetimeTypePairs(
            RDFDatatype baseDatatype, ImmutableMap aggregateMap,
            BiFunction partialComparisonFct,
            TermFactory termFactory) {

        BiFunction comparisonFct = orOnlyFirstArgumentIsNotNull(
                partialComparisonFct, termFactory);

        ImmutableList matchingTypes = aggregateMap.keySet().stream()
                .filter(t -> t.isA(baseDatatype))
                .collect(ImmutableCollectors.toList());

        if (matchingTypes.isEmpty())
            return Stream.empty();

        Stream> firstPairStream = IntStream.range(0, matchingTypes.size() - 1)
                .mapToObj(i -> createNumericOrDatetimeTypePair(matchingTypes.get(i),
                        matchingTypes.subList(i + 1, matchingTypes.size()), aggregateMap,
                        comparisonFct, termFactory));

        RDFTermType lastType = matchingTypes.get(matchingTypes.size() - 1);
        Map.Entry lastPair = createRegularTypeWhenPair(
                aggregateMap.get(lastType), lastType, termFactory);

        return Stream.concat(firstPairStream, Stream.of(lastPair));
    }

    private Map.Entry createNumericOrDatetimeTypePair(
            RDFTermType rdfType, ImmutableList typesToCompareWith,
            ImmutableMap aggregateMap, BiFunction comparisonFct,
            TermFactory termFactory) {
        Variable aggregateVariable = aggregateMap.get(rdfType);

        ImmutableExpression expression = termFactory.getConjunction(
                typesToCompareWith.stream()
                        .map(aggregateMap::get)
                        .map(agg2 -> comparisonFct.apply(aggregateVariable, agg2)))
                .orElseThrow(() -> new MinorOntopInternalBugException("At least one type to compare with was expected"));

        return Maps.immutableEntry(expression, termFactory.getRDFTermTypeConstant(rdfType));
    }

    /**
     * Replaces the "then values" of typeTermWhenPairs by the lexical values of the corresponding
     * aggregation variables.
     */
    private ImmutableFunctionalTerm computeLexicalTerm(
            ImmutableList> typeTermWhenPairs,
            ImmutableMap aggregateMap, TermFactory termFactory) {
        Stream> lexicalWhenPairs = typeTermWhenPairs.stream()
                .map(e -> {
                            RDFTermType rdfType = e.getValue().getRDFTermType();
                            ImmutableFunctionalTerm thenLexicalTerm = termFactory.getConversion2RDFLexical(
                                    aggregateMap.get(rdfType),
                                    rdfType);
                            return Maps.immutableEntry(e.getKey(), thenLexicalTerm);
                        });

        return termFactory.getDBCase(lexicalWhenPairs, termFactory.getNullConstant(),
                // TODO: double-check
                true);
    }


    @Override
    public Constant evaluateEmptyBag(TermFactory termFactory) {
        return termFactory.getNullConstant();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy