
io.mindmaps.graql.internal.query.InsertQueryExecutor Maven / Gradle / Ivy
/*
* MindmapsDB - A Distributed Semantic Database
* Copyright (C) 2016 Mindmaps Research Ltd
*
* MindmapsDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MindmapsDB 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MindmapsDB. If not, see .
*/
package io.mindmaps.graql.internal.query;
import com.google.common.collect.ImmutableMap;
import io.mindmaps.MindmapsGraph;
import io.mindmaps.concept.Concept;
import io.mindmaps.concept.Instance;
import io.mindmaps.concept.Relation;
import io.mindmaps.concept.RelationType;
import io.mindmaps.concept.Resource;
import io.mindmaps.concept.ResourceType;
import io.mindmaps.concept.RoleType;
import io.mindmaps.concept.Type;
import io.mindmaps.graql.admin.VarAdmin;
import io.mindmaps.graql.internal.util.GraqlType;
import io.mindmaps.graql.internal.pattern.Patterns;
import io.mindmaps.util.ErrorMessage;
import io.mindmaps.util.Schema;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.mindmaps.util.ErrorMessage.INSERT_NON_RESOURCE_WITH_VALUE;
/**
* A class for executing insert queries.
*
* This behaviour is moved to its own class to allow InsertQueryImpl to have fewer mutable fields.
*/
class InsertQueryExecutor {
private final MindmapsGraph graph;
private final Collection vars;
private final Map concepts = new HashMap<>();
private final Stack visitedVars = new Stack<>();
private final ImmutableMap> varsByName;
private final ImmutableMap> varsById;
InsertQueryExecutor(Collection vars, MindmapsGraph graph) {
this.vars = vars;
this.graph = graph;
// Group variables by name
varsByName = ImmutableMap.copyOf(
vars.stream().collect(Collectors.groupingBy(VarAdmin::getName))
);
// Group variables by id (if they have one defined)
// the 'filter' step guarantees the remaining have an ID
//noinspection OptionalGetWithoutIsPresent
varsById = ImmutableMap.copyOf(
vars.stream()
.filter(var -> var.getId().isPresent())
.collect(Collectors.groupingBy(var -> var.getId().get()))
);
}
/**
* Insert all the Vars
*/
Stream insertAll() {
return insertAll(new HashMap<>());
}
/**
* Insert all the Vars
* @param results the result of a match query
*/
Stream insertAll(Map results) {
concepts.clear();
concepts.putAll(new HashMap<>(results));
// First insert each var
vars.forEach(this::insertVar);
// Then add resources to each var, streaming out the results.
// It is necessary to add resources last to be sure that any 'has-resource' information in the query has
// already been added.
// TODO: Fix this so this step is no longer necessary (can be done by correctly expanding 'has-resource')
return vars.stream().map(this::insertVarResources);
}
/**
* @param var the Var to insert into the graph
*/
private Concept insertVar(VarAdmin var) {
Concept concept = getConcept(var);
if (var.getAbstract()) concept.asType().setAbstract(true);
var.getHasRoles().forEach(role -> concept.asRelationType().hasRole(getConcept(role).asRoleType()));
var.getPlaysRoles().forEach(role -> concept.asType().playsRole(getConcept(role).asRoleType()));
var.getScopes().forEach(scope -> concept.asRelation().scope(getConcept(scope).asInstance()));
var.getHasResourceTypes().forEach(resourceType -> addResourceType(var, resourceType));
var.getCastings().forEach(casting -> addCasting(var, casting));
var.getRegex().ifPresent(regex -> concept.asResourceType().setRegex(regex));
return concept;
}
/**
* Add all the resources of the given var
* @param var the var to add resources to
*/
private Concept insertVarResources(VarAdmin var) {
Concept concept = getConcept(var);
if (!var.getResources().isEmpty()) {
Instance instance = concept.asInstance();
var.getResources().forEach(resource -> attachResource(instance, getConcept(resource).asResource()));
}
return concept;
}
/**
* @param var the Var that is represented by a concept in the graph
* @return the same as addConcept, but using an internal map to remember previous calls
*/
private Concept getConcept(VarAdmin var) {
String name = var.getName();
if (visitedVars.contains(name)) {
throw new IllegalStateException(ErrorMessage.INSERT_RECURSIVE.getMessage(var.getPrintableName()));
}
visitedVars.push(name);
Concept concept = concepts.computeIfAbsent(name, n -> addConcept(var));
visitedVars.pop();
return concept;
}
/**
* @param var the Var that is to be added into the graph
* @return the concept representing the given Var, creating it if it doesn't exist
*/
private Concept addConcept(VarAdmin var) {
var = mergeVar(var);
Optional type = var.getType();
Optional ako = var.getAko();
if (type.isPresent() && ako.isPresent()) {
String printableName = var.getPrintableName();
throw new IllegalStateException(ErrorMessage.INSERT_ISA_AND_AKO.getMessage(printableName));
}
Concept concept;
// If 'ako' provided, use that, else use 'isa', else get existing concept by id
if (ako.isPresent()) {
String id = getTypeIdOrThrow(var.getId());
concept = putConceptBySuperType(id, getConcept(ako.get()).asType());
} else if (type.isPresent()) {
concept = putConceptByType(var.getId(), var, getConcept(type.get()).asType());
} else {
concept = var.getId().map(graph::getConcept).orElse(null);
}
if (concept == null) {
System.out.println(varsById);
throw new IllegalStateException(
var.getId().map(ErrorMessage.INSERT_GET_NON_EXISTENT_ID::getMessage)
.orElse(ErrorMessage.INSERT_UNDEFINED_VARIABLE.getMessage(var.getName()))
);
}
return concept;
}
/**
* Merge a variable with any other variables referred to with the same variable name or id
* @param var the variable to merge
* @return the merged variable
*/
private VarAdmin mergeVar(VarAdmin var) {
boolean changed = true;
Set varsToMerge = new HashSet<>();
// Keep merging until the set of merged variables stops changing
// This handles cases when variables are referred to with multiple degrees of separation
// e.g.
// "123" isa movie; $x id "123"; $y id "123"; ($y, $z)
while (changed) {
// Merge variable referred to by name...
boolean byNameChange = varsToMerge.addAll(varsByName.get(var.getName()));
var = Patterns.mergeVars(varsToMerge);
// Then merge variables referred to by id...
boolean byIdChange = var.getId().map(id -> varsToMerge.addAll(varsById.get(id))).orElse(false);
var = Patterns.mergeVars(varsToMerge);
changed = byNameChange | byIdChange;
}
return var;
}
/**
* @param id the ID of the concept
* @param var the Var representing the concept in the insert query
* @param type the type of the concept
* @return a concept with the given ID and the specified type
*/
private Concept putConceptByType(Optional id, VarAdmin var, Type type) {
String typeId = type.getId();
if (!type.isResourceType() && !var.getValuePredicates().isEmpty()) {
throw new IllegalStateException(INSERT_NON_RESOURCE_WITH_VALUE.getMessage(type.getId()));
}
if (typeId.equals(Schema.MetaType.ENTITY_TYPE.getId())) {
return graph.putEntityType(getTypeIdOrThrow(id));
} else if (typeId.equals(Schema.MetaType.RELATION_TYPE.getId())) {
return graph.putRelationType(getTypeIdOrThrow(id));
} else if (typeId.equals(Schema.MetaType.ROLE_TYPE.getId())) {
return graph.putRoleType(getTypeIdOrThrow(id));
} else if (typeId.equals(Schema.MetaType.RESOURCE_TYPE.getId())) {
return graph.putResourceType(getTypeIdOrThrow(id), getDataType(var));
} else if (typeId.equals(Schema.MetaType.RULE_TYPE.getId())) {
return graph.putRuleType(getTypeIdOrThrow(id));
} else if (type.isEntityType()) {
return putInstance(id, type.asEntityType(), graph::putEntity, graph::addEntity);
} else if (type.isRelationType()) {
return putInstance(id, type.asRelationType(), graph::putRelation, graph::addRelation);
} else if (type.isResourceType()) {
return graph.putResource(getValue(var), type.asResourceType());
} else if (type.isRuleType()) {
String lhs = var.getLhs().get();
String rhs = var.getRhs().get();
return putInstance(
id, type.asRuleType(),
(ruleId, ruleType) -> graph.putRule(ruleId, lhs, rhs, ruleType),
ruleType -> graph.addRule(lhs, rhs, ruleType)
);
} else {
throw new RuntimeException("Unrecognized type " + type.getId());
}
}
/**
* @param id the ID of the concept
* @param superType the supertype of the concept
* @return a concept with the given ID and the specified supertype
*/
private Concept putConceptBySuperType(String id, Type superType) {
if (superType.isEntityType()) {
return graph.putEntityType(id).superType(superType.asEntityType());
} else if (superType.isRelationType()) {
return graph.putRelationType(id).superType(superType.asRelationType());
} else if (superType.isRoleType()) {
return graph.putRoleType(id).superType(superType.asRoleType());
} else if (superType.isResourceType()) {
ResourceType superResource = superType.asResourceType();
return graph.putResourceType(id, superResource.getDataType()).superType(superResource);
} else if (superType.isRuleType()) {
return graph.putRuleType(id).superType(superType.asRuleType());
} else {
throw new IllegalStateException(ErrorMessage.INSERT_METATYPE.getMessage(id, superType.getId()));
}
}
/**
* Put an instance of a type which may or may not have an ID specified
* @param id the ID of the instance to create, or empty to not specify an ID
* @param type the type of the instance
* @param putInstance a 'put' method on a MindmapsGraph, such as graph::putEntity
* @param addInstance an 'add' method on a MindmapsGraph such a graph::addEntity
* @param the class of the type of the instance, e.g. EntityType
* @param the class of the instance, e.g. Entity
* @return an instance of the specified type, with the given ID if one was specified
*/
private S putInstance(
Optional id, T type, BiFunction putInstance, Function addInstance
) {
return id.map(i -> putInstance.apply(i, type)).orElseGet(() -> addInstance.apply(type));
}
/**
* Get an ID from an optional for a type, throwing an exception if it is not present.
* This is because types must have specified IDs.
* @param id an optional ID to get
* @return the ID, if present
* @throws IllegalStateException if the ID was not present
*/
private String getTypeIdOrThrow(Optional id) throws IllegalStateException {
return id.orElseThrow(() -> new IllegalStateException(ErrorMessage.INSERT_TYPE_WITHOUT_ID.getMessage()));
}
/**
* Add a roleplayer to the given relation
* @param var the variable representing the relation
* @param casting a casting between a role type and role player
*/
private void addCasting(VarAdmin var, VarAdmin.Casting casting) {
Relation relation = getConcept(var).asRelation();
VarAdmin roleVar = casting.getRoleType().orElseThrow(
() -> new IllegalStateException(ErrorMessage.INSERT_RELATION_WITHOUT_ROLE_TYPE.getMessage())
);
RoleType roleType = getConcept(roleVar).asRoleType();
Instance roleplayer = getConcept(casting.getRolePlayer()).asInstance();
relation.putRolePlayer(roleType, roleplayer);
}
private Object getValue(VarAdmin var) {
Iterator> values = var.getValueEqualsPredicates().iterator();
if (values.hasNext()) {
Object value = values.next();
if (values.hasNext()) {
throw new IllegalStateException(ErrorMessage.INSERT_MULTIPLE_VALUES.getMessage(value, values.next()));
}
return value;
} else {
throw new IllegalStateException(ErrorMessage.INSERT_RESOURCE_WITHOUT_VALUE.getMessage());
}
}
/**
* Get the datatype of a Var if specified, else throws an IllegalStateException
* @return the datatype of the given var
*/
private ResourceType.DataType> getDataType(VarAdmin var) {
return var.getDatatype().orElseThrow(
() -> new IllegalStateException(ErrorMessage.INSERT_NO_DATATYPE.getMessage(var.getPrintableName()))
);
}
/**
* @param var the var representing a type that can own the given resource type
* @param resourceVar the var representing the resource type
*/
private void addResourceType(VarAdmin var, VarAdmin resourceVar) {
Type type = getConcept(var).asType();
ResourceType resourceType = getConcept(resourceVar).asResourceType();
RoleType owner = graph.putRoleType(GraqlType.HAS_RESOURCE_OWNER.getId(resourceType.getId()));
RoleType value = graph.putRoleType(GraqlType.HAS_RESOURCE_VALUE.getId(resourceType.getId()));
graph.putRelationType(GraqlType.HAS_RESOURCE.getId(resourceType.getId()))
.hasRole(owner).hasRole(value);
type.playsRole(owner);
resourceType.playsRole(value);
}
/**
* Add a resource to the given instance, using the has-resource relation
* @param instance the instance to add a resource to
* @param resource the resource instance
*/
private void attachResource(Instance instance, Resource resource) {
ResourceType type = resource.type();
RelationType hasResource = graph.getRelationType(GraqlType.HAS_RESOURCE.getId(type.getId()));
RoleType hasResourceTarget = graph.getRoleType(GraqlType.HAS_RESOURCE_OWNER.getId(type.getId()));
RoleType hasResourceValue = graph.getRoleType(GraqlType.HAS_RESOURCE_VALUE.getId(type.getId()));
if (hasResource == null || hasResourceTarget == null || hasResourceValue == null) {
throw new IllegalStateException(
ErrorMessage.INSERT_NO_RESOURCE_RELATION.getMessage(instance.type().getId(), type.getId())
);
}
Relation relation = graph.addRelation(hasResource);
relation.putRolePlayer(hasResourceTarget, instance);
relation.putRolePlayer(hasResourceValue, resource);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy