
io.mindmaps.graql.internal.query.VarImpl 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.Maps;
import io.mindmaps.concept.ResourceType;
import io.mindmaps.graql.ValuePredicate;
import io.mindmaps.graql.Var;
import io.mindmaps.graql.admin.*;
import io.mindmaps.graql.internal.util.StringConverter;
import io.mindmaps.graql.internal.gremlin.MultiTraversal;
import io.mindmaps.graql.internal.gremlin.VarTraversals;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;
import static io.mindmaps.graql.Graql.eq;
import static io.mindmaps.graql.Graql.var;
import static io.mindmaps.util.ErrorMessage.MULTIPLE_IDS;
import static io.mindmaps.util.ErrorMessage.MULTIPLE_TYPES;
import static io.mindmaps.util.ErrorMessage.SET_GENERATED_VARIABLE_NAME;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
/**
* Implementation of Var interface
*/
class VarImpl implements VarInternal {
private String name;
private final boolean userDefinedName;
private boolean abstractFlag = false;
private Optional> datatype = Optional.empty();
private Optional id = Optional.empty();
private boolean valueFlag = false;
private final Set values = new HashSet<>();
private Optional lhs = Optional.empty();
private Optional rhs = Optional.empty();
private Optional isa = Optional.empty();
private Optional ako = Optional.empty();
private final Set hasRole = new HashSet<>();
private final Set playsRole = new HashSet<>();
private final Set hasScope = new HashSet<>();
private final Set hasResourceTypes = new HashSet<>();
private final Map> resources = new HashMap<>();
private final Set castings = new HashSet<>();
private Optional varPattern = Optional.empty();
/**
* Create a variable with a random variable name
*/
VarImpl() {
this.name = UUID.randomUUID().toString();
this.userDefinedName = false;
}
/**
* @param name the variable name of the variable
*/
VarImpl(String name) {
this.name = name;
this.userDefinedName = true;
}
/**
* Create a variable by combining a collection of other variables
* @param vars a collection of variables to combine
*/
VarImpl(Collection vars) {
VarAdmin first = vars.iterator().next();
this.name = first.getName();
this.userDefinedName = first.isUserDefinedName();
valueFlag = false;
for (VarAdmin var : vars) {
if (var.isUserDefinedName()) {
this.name = var.getName();
}
valueFlag |= var.hasValue();
abstractFlag |= var.getAbstract();
var.getDatatype().ifPresent(this::datatype);
var.getType().ifPresent(this::isa);
var.getAko().ifPresent(this::ako);
var.getId().ifPresent(this::id);
var.getLhs().ifPresent(this::lhs);
var.getRhs().ifPresent(this::rhs);
values.addAll(var.getValuePredicates());
hasRole.addAll(var.getHasRoles());
playsRole.addAll(var.getPlaysRoles());
hasScope.addAll(var.getScopes());
// Currently it is guaranteed that resource types are specified with an ID
//noinspection OptionalGetWithoutIsPresent
var.getResourcePredicates().forEach(
(type, values) -> values.forEach(value -> has(type.getId().get(), value))
);
castings.addAll(var.getCastings());
}
}
@Override
public Var id(String id) {
this.id.ifPresent(
prevId -> {
if (!prevId.equals(id)) throw new IllegalStateException(MULTIPLE_IDS.getMessage(id, prevId));
}
);
this.id = Optional.of(id);
return this;
}
@Override
public Var value() {
valueFlag = true;
return this;
}
@Override
public Var value(Object value) {
return value(eq(value));
}
@Override
public Var value(ValuePredicate predicate) {
values.add(predicate.admin());
return this;
}
@Override
public Var has(String type) {
VarAdmin resourceVar = var().id(Objects.requireNonNull(type)).admin();
resources.putIfAbsent(resourceVar, new HashSet<>());
return this;
}
@Override
public Var has(String type, Object value) {
return has(type, eq(value));
}
@Override
public Var has(String type, ValuePredicate predicate) {
VarAdmin resourceVar = var().id(Objects.requireNonNull(type)).admin();
resources.computeIfAbsent(resourceVar, k -> new HashSet<>()).add(predicate.admin());
return this;
}
@Override
public Var isa(String type) {
return isa(var().id(type));
}
@Override
public Var isa(Var type) {
VarAdmin var = type.admin();
isa.ifPresent(
other -> {
if (!var.getName().equals(other.getName()) && !var.getIdOnly().equals(other.getIdOnly())) {
throw new IllegalStateException(
MULTIPLE_TYPES.getMessage(
getPrintableName(), var.getPrintableName(), other.getPrintableName()
)
);
}
}
);
isa = Optional.of(var);
return this;
}
@Override
public Var ako(String type) {
return ako(var().id(type));
}
@Override
public Var ako(Var type) {
ako = Optional.of(type.admin());
return this;
}
@Override
public Var hasRole(String type) {
return hasRole(var().id(type));
}
@Override
public Var hasRole(Var type) {
hasRole.add(type.admin());
return this;
}
@Override
public Var playsRole(String type) {
return playsRole(var().id(type));
}
@Override
public Var playsRole(Var type) {
playsRole.add(type.admin());
return this;
}
@Override
public Var hasScope(Var type) {
hasScope.add(type.admin());
return this;
}
@Override
public Var hasResource(String type) {
return hasResource(var().id(type));
}
@Override
public Var hasResource(Var type) {
hasResourceTypes.add(type.admin());
return this;
}
@Override
public Var rel(String roleplayer) {
return rel(var(roleplayer));
}
@Override
public Var rel(Var roleplayer) {
castings.add(new Casting(roleplayer.admin()));
return this;
}
@Override
public Var rel(String roletype, String roleplayer) {
return rel(var().id(roletype), var(roleplayer));
}
@Override
public Var rel(Var roletype, String roleplayer) {
return rel(roletype, var(roleplayer));
}
@Override
public Var rel(String roletype, Var roleplayer) {
return rel(var().id(roletype), roleplayer);
}
@Override
public Var rel(Var roletype, Var roleplayer) {
castings.add(new Casting(roletype.admin(), roleplayer.admin()));
return this;
}
@Override
public Var isAbstract() {
abstractFlag = true;
return this;
}
@Override
public Var datatype(ResourceType.DataType> datatype) {
this.datatype = Optional.of(datatype);
return this;
}
@Override
public Var lhs(String lhs) {
this.lhs = Optional.of(lhs);
return this;
}
@Override
public Var rhs(String rhs) {
this.rhs = Optional.of(rhs);
return this;
}
@Override
public VarInternal admin() {
return this;
}
@Override
public Optional getType() {
return isa;
}
@Override
public boolean isRelation() {
return !castings.isEmpty();
}
@Override
public boolean isUserDefinedName() {
return userDefinedName;
}
@Override
public boolean usesNonEqualPredicate() {
Stream predicates = Stream.of(
values.stream(),
resources.values().stream().flatMap(Collection::stream)
).flatMap(Function.identity());
return predicates.anyMatch(id -> !id.equalsValue().isPresent());
}
@Override
public boolean getAbstract() {
return abstractFlag;
}
@Override
public Optional> getDatatype() {
return datatype;
}
@Override
public boolean hasValue() {
return valueFlag;
}
@Override
public Optional getAko() {
return ako;
}
@Override
public Set getHasRoles() {
return hasRole;
}
@Override
public Set getPlaysRoles() {
return playsRole;
}
@Override
public Set getScopes() {
return hasScope;
}
@Override
public Set getHasResourceTypes() {
return hasResourceTypes;
}
@Override
public Set getRoleTypes() {
return getIdNames(castings.stream().map(VarAdmin.Casting::getRoleType).flatMap(this::optionalToStream));
}
@Override
public Optional getId() {
return id;
}
@Override
public Set getResourceTypes() {
// Currently it is guaranteed that resource types are specified with an ID
//noinspection OptionalGetWithoutIsPresent
return resources.keySet().stream().map(var -> var.getId().get()).collect(toSet());
}
@Override
public boolean hasNoProperties() {
// return true if this variable has any properties set
return !id.isPresent() && !valueFlag && values.isEmpty() && !isa.isPresent() && !ako.isPresent() &&
hasRole.isEmpty() && playsRole.isEmpty() && hasScope.isEmpty() && resources.isEmpty() &&
castings.isEmpty();
}
@Override
public Optional getIdOnly() {
if (id.isPresent() && !valueFlag && values.isEmpty() && !isa.isPresent() && !ako.isPresent() &&
hasRole.isEmpty() && playsRole.isEmpty() && hasScope.isEmpty() && resources.isEmpty() &&
castings.isEmpty() && !userDefinedName) {
return getId();
} else {
return Optional.empty();
}
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
if (!userDefinedName) throw new RuntimeException(SET_GENERATED_VARIABLE_NAME.getMessage(name));
this.name = name;
}
@Override
public String getPrintableName() {
if (userDefinedName) {
return "$" + name;
} else {
return getId().map(StringConverter::idToString).orElse("$" + name);
}
}
@Override
public Set> getValueEqualsPredicates() {
return getEqualsPredicatesUnknownType(values);
}
@Override
public Set getValuePredicates() {
return values;
}
@Override
public Optional getLhs() {
return lhs;
}
@Override
public Optional getRhs() {
return rhs;
}
@Override
public Map> getResourceEqualsPredicates() {
return Maps.transformValues(resources, this::getEqualsPredicatesUnknownType);
}
@Override
public Map> getResourcePredicates() {
return resources;
}
public Set getCastings() {
return castings;
}
@Override
public Set getMultiTraversals() {
return getVarTraversals().getTraversals().collect(toSet());
}
@Override
public Set getInnerVars() {
Stack newVars = new Stack<>();
Set vars = new HashSet<>();
newVars.add(this);
while (!newVars.isEmpty()) {
VarAdmin var = newVars.pop();
vars.add(var);
var.getType().ifPresent(newVars::add);
var.getAko().ifPresent(newVars::add);
var.getHasRoles().forEach(newVars::add);
var.getPlaysRoles().forEach(newVars::add);
var.getScopes().forEach(newVars::add);
var.getHasResourceTypes().forEach(newVars::add);
var.getResourcePredicates().keySet().forEach(newVars::add);
var.getCastings().forEach(casting -> {
casting.getRoleType().ifPresent(newVars::add);
newVars.add(casting.getRolePlayer());
});
}
return vars;
}
@Override
public Set getTypeIds() {
Set results = new HashSet<>();
getId().ifPresent(results::add);
getResourceTypes().forEach(results::add);
id.ifPresent(results::add);
return results;
}
@Override
public String toString() {
Set properties = new HashSet<>();
Set innerVars = getInnerVars();
innerVars.remove(this);
if (!innerVars.stream().allMatch(v -> v.getIdOnly().isPresent() || v.hasNoProperties())) {
throw new UnsupportedOperationException("Graql strings cannot represent a query with inner variables");
}
id.ifPresent(i -> properties.add("id " + StringConverter.valueToString(i)));
if (isRelation()) {
properties.add("(" + castings.stream().map(Object::toString).collect(joining(", ")) + ")");
}
isa.ifPresent(v -> properties.add("isa " + v.getPrintableName()));
ako.ifPresent(v -> properties.add("ako " + v.getPrintableName()));
playsRole.forEach(v -> properties.add("plays-role " + v.getPrintableName()));
hasRole.forEach(v -> properties.add("has-role " + v.getPrintableName()));
hasScope.forEach(v -> properties.add("has-scope " + v.getPrintableName()));
hasResourceTypes.forEach(v -> properties.add("has-resource " + v.getPrintableName()));
getDatatypeName().ifPresent(d -> properties.add("datatype " + d));
if (getAbstract()) properties.add("is-abstract");
values.forEach(v -> properties.add("value " + v));
// Currently it is guaranteed that resource types are specified with an ID
//noinspection OptionalGetWithoutIsPresent
resources.forEach(
(type, predicates) -> predicates.forEach(p -> properties.add("has " + type.getId().get() + " " + p))
);
lhs.ifPresent(s -> properties.add("lhs {" + s + "}"));
rhs.ifPresent(s -> properties.add("rhs {" + s + "}"));
String name = isUserDefinedName() ? getPrintableName() + " " : "";
return name + properties.stream().collect(joining(", "));
}
/**
* @return the datatype's name (as referred to in native Graql), if one is specified
*/
private Optional getDatatypeName() {
return datatype.map(
d -> {
if (d == ResourceType.DataType.BOOLEAN) {
return "boolean";
} else if (d == ResourceType.DataType.DOUBLE) {
return "double";
} else if (d == ResourceType.DataType.LONG) {
return "long";
} else if (d == ResourceType.DataType.STRING) {
return "string";
} else {
throw new RuntimeException("Unknown data type: " + d.getName());
}
}
);
}
/**
* @param vars a stream of variables
* @return the IDs of all variables that refer to things by id in the graph
*/
private Set getIdNames(Stream vars) {
return vars.map(VarAdmin::getId).flatMap(this::optionalToStream).collect(toSet());
}
/**
* @param optional the optional to change into a stream
* @param the type in the optional
* @return a stream of one item if the optional has an element, else an empty stream
*/
private Stream optionalToStream(Optional optional) {
return optional.map(Stream::of).orElseGet(Stream::empty);
}
/**
* @param predicates a collection of predicates of an unknown type
* @return all values of predicates in the collection which are simple 'equals' predicates
*/
private Set> getEqualsPredicatesUnknownType(Collection predicates) {
return predicates.stream()
.map(ValuePredicateAdmin::equalsValue)
.flatMap(this::optionalToStream)
.collect(toSet());
}
/**
* @return the VarTraversals object representing this Var as gremlin traversals
*/
private VarTraversals getVarTraversals() {
VarTraversals varTraversals = this.varPattern.orElseGet(() -> new VarTraversals(this));
this.varPattern = Optional.of(varTraversals);
return varTraversals;
}
@Override
public Disjunction> getDisjunctiveNormalForm() {
// a disjunction containing only one option
Conjunction conjunction = Patterns.conjunction(Collections.singleton(this));
return Patterns.disjunction(Collections.singleton(conjunction));
}
/**
* A casting is the pairing of roletype and roleplayer in a relation, where the roletype may be unknown
*/
public class Casting implements VarAdmin.Casting {
private final Optional roleType;
private final VarAdmin rolePlayer;
/**
* A casting without a role type specified
* @param rolePlayer the role player of the casting
*/
Casting(VarAdmin rolePlayer) {
this.roleType = Optional.empty();
this.rolePlayer = rolePlayer;
}
/**
* @param roletype the role type of the casting
* @param rolePlayer the role player of the casting
*/
Casting(VarAdmin roletype, VarAdmin rolePlayer) {
this.roleType = Optional.of(roletype);
this.rolePlayer = rolePlayer;
}
@Override
public Optional getRoleType() {
return roleType;
}
@Override
public VarAdmin getRolePlayer() {
return rolePlayer;
}
@Override
public String toString() {
return getRoleType().map(r -> r.getPrintableName() + " ").orElse("") + getRolePlayer().getPrintableName();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy