Java tutorial
/* * GRAKN.AI - THE KNOWLEDGE GRAPH * Copyright (C) 2018 Grakn Labs Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package grakn.core.graql.reasoner.query; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import grakn.core.common.exception.ErrorMessage; import grakn.core.concept.Label; import grakn.core.concept.answer.ConceptMap; import grakn.core.concept.type.Rule; import grakn.core.concept.type.Type; import grakn.core.graql.exception.GraqlSemanticException; import grakn.core.graql.reasoner.ResolutionIterator; import grakn.core.graql.reasoner.atom.Atom; import grakn.core.graql.reasoner.atom.Atomic; import grakn.core.graql.reasoner.state.CompositeState; import grakn.core.graql.reasoner.state.QueryStateBase; import grakn.core.graql.reasoner.state.ResolutionState; import grakn.core.graql.reasoner.unifier.MultiUnifier; import grakn.core.graql.reasoner.unifier.Unifier; import grakn.core.server.kb.concept.ConceptUtils; import grakn.core.server.session.TransactionOLTP; import graql.lang.Graql; import graql.lang.pattern.Conjunction; import graql.lang.pattern.Negation; import graql.lang.pattern.Pattern; import graql.lang.statement.Statement; import graql.lang.statement.Variable; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; /** * * A class representing a composite query: a conjunctive query containing a positive and negative part: * * For a conjunctive query Q := P, R1, R2, ... Ri * * the corresponding composite query is: * * CQ : [ P, {R1, R2, ... Ri} ] * * The positive part P is defined by a conjunctive query. * The negative {R1, R2, ... Ri} part is a set of composite queries (we allow nesting). * * The negative part is stored in terms of the negation complement - hence all stored queries are positive. * */ public class CompositeQuery implements ResolvableQuery { final private ReasonerQueryImpl conjunctiveQuery; final private Set<ResolvableQuery> complementQueries; final private TransactionOLTP tx; CompositeQuery(Conjunction<Pattern> pattern, TransactionOLTP tx) throws GraqlSemanticException { Conjunction<Statement> positiveConj = Graql.and(pattern.getPatterns().stream().filter(p -> !p.isNegation()) .flatMap(p -> p.statements().stream()).collect(Collectors.toSet())); this.tx = tx; //conjunction of negation patterns Set<Conjunction<Pattern>> complementPattern = complementPattern(pattern); this.conjunctiveQuery = ReasonerQueries.create(positiveConj, tx); this.complementQueries = complementPattern.stream().map(comp -> ReasonerQueries.resolvable(comp, tx)) .collect(Collectors.toSet()); if (!isNegationSafe()) { throw GraqlSemanticException.unsafeNegationBlock(this); } } CompositeQuery(ReasonerQueryImpl conj, Set<ResolvableQuery> comp, TransactionOLTP tx) { this.conjunctiveQuery = conj; this.complementQueries = comp; this.tx = tx; } @Override public CompositeQuery asComposite() { return this; } /** * We interpret negation blocks as equivalent to defining a rule with the content of the block being the rule body. * Writing the query in terms of variables it depends on we have: * * Q(x1, ..., xn) :- P1(xi, ...), ..., Pn(..., xj), NOT { R1(xk, ...), ..., Rn(..., xm) } * * We can then rewrite the negative part in terms of some unknown relation: * * ?(xk', ..., xm') :- R1(xk, ...), ..., Rn(..., xm) * * Where the sets of variables: * V = {x1, ..., xn} * Vp = {xi, ..., xj} * Vn = {xk, ..., xm} * Vr = {xk', ..., xm'} * * satisfy: * * Vp e V * Vn e V * Vr e Vn * * This procedure can follow recursively for multiple nested negation blocks. * Then, for the negation to be safe, we require: * * - the set of variables Vr to be non-empty * * NB: We do not require the negation blocks to be ground with respect to the positive part as we can rewrite the * negation blocks in terms of a ground relation defined via rule. i.e.: * * Q(x) :- T(x), (x, y), NOT{ (y, z) } * * can be rewritten as: * * Q(x) :- T(x), (x, y), NOT{ ?(y) } * ?(y) :- (y, z) * * @return true if this composite query is safe to resolve */ private boolean isNegationSafe() { if (this.isPositive()) return true; if (bindingVariables().isEmpty()) return false; //check nested blocks return getComplementQueries().stream().map(ResolvableQuery::asComposite) .allMatch(CompositeQuery::isNegationSafe); } private Set<Variable> bindingVariables() { return Sets.intersection(getConjunctiveQuery().getVarNames(), getComplementQueries().stream().flatMap(q -> q.getVarNames().stream()).collect(Collectors.toSet())); } private Set<Conjunction<Pattern>> complementPattern(Conjunction<Pattern> pattern) { return pattern.getPatterns().stream().filter(Pattern::isNegation).map(Pattern::asNegation) .map(Negation::getPattern).map(p -> { Set<Conjunction<Pattern>> patterns = p.getNegationDNF().getPatterns(); if (p.getNegationDNF().getPatterns().size() != 1) { throw GraqlSemanticException.disjunctiveNegationBlock(); } return Iterables.getOnlyElement(patterns); }).collect(Collectors.toSet()); } /** * Validate as rule body, Ensure: * - no negation nesting * - no disjunctions * - at most single negation block * @param graph transaction to be validated against * @param pattern pattern to be validated * @return set of error messages applicable */ public static Set<String> validateAsRuleBody(Conjunction<Pattern> pattern, Rule rule, TransactionOLTP graph) { Set<String> errors = new HashSet<>(); try { CompositeQuery body = ReasonerQueries.composite(pattern, graph); Set<ResolvableQuery> complementQueries = body.getComplementQueries(); if (complementQueries.size() > 1) { errors.add(ErrorMessage.VALIDATION_RULE_MULTIPLE_NEGATION_BLOCKS.getMessage(rule.label())); } if (!body.isPositive() && complementQueries.stream().noneMatch(ReasonerQuery::isPositive)) { errors.add(ErrorMessage.VALIDATION_RULE_NESTED_NEGATION.getMessage(rule.label())); } } catch (GraqlSemanticException e) { errors.add(ErrorMessage.VALIDATION_RULE_INVALID.getMessage(rule.label(), e.getMessage())); } return errors; } @Override public Set<String> validateOntologically(Label ruleLabel) { Set<String> validation = getConjunctiveQuery().validateOntologically(ruleLabel); getComplementQueries().stream().map(q -> q.validateOntologically(ruleLabel)).forEach(validation::addAll); return validation; } @Override public CompositeQuery withSubstitution(ConceptMap sub) { return new CompositeQuery(getConjunctiveQuery().withSubstitution(sub), getComplementQueries().stream().map(q -> q.withSubstitution(sub)).collect(Collectors.toSet()), tx()); } @Override public CompositeQuery inferTypes() { return new CompositeQuery(getConjunctiveQuery().inferTypes(), getComplementQueries(), this.tx()); } @Override public ResolvableQuery neqPositive() { return new CompositeQuery(getConjunctiveQuery().neqPositive(), getComplementQueries(), tx()); } public ReasonerQueryImpl getConjunctiveQuery() { return conjunctiveQuery; } public Set<ResolvableQuery> getComplementQueries() { return complementQueries; } @Override public ResolvableQuery copy() { return new CompositeQuery(getConjunctiveQuery().copy(), getComplementQueries().stream().map(ResolvableQuery::copy).collect(Collectors.toSet()), this.tx()); } @Override public boolean isAtomic() { return getComplementQueries().isEmpty() && getConjunctiveQuery().isAtomic(); } @Override public boolean isComposite() { return true; } @Override public boolean isPositive() { return complementQueries.isEmpty(); } @Override public boolean isEquivalent(ResolvableQuery q) { CompositeQuery that = q.asComposite(); return getConjunctiveQuery().isEquivalent(that.getConjunctiveQuery()) && getComplementQueries().size() == that.getComplementQueries().size() && getComplementQueries() .stream().allMatch(c -> that.getComplementQueries().stream().anyMatch(c::isEquivalent)); } @Override public String toString() { String complementString = getComplementQueries().stream().map(q -> "\nNOT {" + q.toString() + "\n}") .collect(Collectors.joining()); return getConjunctiveQuery().toString() + (!getComplementQueries().isEmpty() ? complementString : ""); } @Override public ReasonerQuery conjunction(ReasonerQuery q) { return new CompositeQuery( Graql.and(Sets.union(this.getPattern().getPatterns(), q.getPattern().getPatterns())), this.tx()); } @Override public TransactionOLTP tx() { return tx; } @Override public void checkValid() { getConjunctiveQuery().checkValid(); getComplementQueries().forEach(ResolvableQuery::checkValid); } @Override public Set<Variable> getVarNames() { Set<Variable> varNames = getConjunctiveQuery().getVarNames(); getComplementQueries().stream().flatMap(q -> q.getVarNames().stream()).forEach(varNames::add); return varNames; } @Override public Set<Atomic> getAtoms() { Set<Atomic> atoms = new HashSet<>(getConjunctiveQuery().getAtoms()); getComplementQueries().stream().flatMap(q -> q.getAtoms().stream()).forEach(atoms::add); return atoms; } @Override public Conjunction<Pattern> getPattern() { Set<Pattern> pattern = Sets.newHashSet(getConjunctiveQuery().getPattern()); getComplementQueries().stream().map(ResolvableQuery::getPattern).forEach(pattern::add); return Graql.and(pattern); } @Override public ConceptMap getSubstitution() { ConceptMap sub = getConjunctiveQuery().getSubstitution(); for (ResolvableQuery comp : getComplementQueries()) { sub = ConceptUtils.mergeAnswers(sub, comp.getSubstitution()); } return sub; } @Override public boolean isRuleResolvable() { return getConjunctiveQuery().isRuleResolvable() || getComplementQueries().stream().anyMatch(ResolvableQuery::isRuleResolvable); } @Override public boolean isTypeRoleCompatible(Variable typedVar, Type parentType) { throw new UnsupportedOperationException(); } @Override public MultiUnifier getMultiUnifier(ReasonerQuery parent) { throw new UnsupportedOperationException(); } @Override public Type getUnambiguousType(Variable var, boolean inferTypes) { throw new UnsupportedOperationException(); } @Override public ImmutableSetMultimap<Variable, Type> getVarTypeMap() { throw new UnsupportedOperationException(); } @Override public ImmutableSetMultimap<Variable, Type> getVarTypeMap(boolean inferTypes) { return getVarTypeMap(); } @Override public ImmutableSetMultimap<Variable, Type> getVarTypeMap(ConceptMap sub) { return getVarTypeMap(); } @Override public Stream<ConceptMap> resolve() { return resolve(new HashSet<>(), this.requiresReiteration()); } @Override public Stream<ConceptMap> resolve(Set<ReasonerAtomicQuery> subGoals, boolean reiterate) { return new ResolutionIterator(this, subGoals, reiterate).hasStream(); } @Override public boolean requiresReiteration() { return getConjunctiveQuery().requiresReiteration() || getComplementQueries().stream().anyMatch(ResolvableQuery::requiresReiteration); } @Override public Stream<Atom> selectAtoms() { return getAtoms(Atom.class).filter(Atomic::isSelectable); } @Override public boolean requiresDecomposition() { return getConjunctiveQuery().requiresDecomposition() || (!getComplementQueries().isEmpty() && getComplementQueries().stream().anyMatch(ResolvableQuery::requiresDecomposition)); } @Override public CompositeQuery rewrite() { return new CompositeQuery(getConjunctiveQuery().rewrite(), getComplementQueries().isEmpty() ? getComplementQueries() : getComplementQueries().stream().map(ResolvableQuery::rewrite).collect(Collectors.toSet()), tx()); } @Override public ResolutionState subGoal(ConceptMap sub, Unifier u, QueryStateBase parent, Set<ReasonerAtomicQuery> subGoals) { return isPositive() ? getConjunctiveQuery().subGoal(sub, u, parent, subGoals) : new CompositeState(this, sub, u, parent, subGoals); } }