blob: 99f45fc1cd749b197fd90d1b05fa11553d6d599c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.traversal.optimize;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.BackendException;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.page.PageInfo;
import org.apache.hugegraph.backend.page.PageState;
import org.apache.hugegraph.backend.query.Aggregate;
import org.apache.hugegraph.backend.query.Condition;
import org.apache.hugegraph.backend.query.ConditionQuery;
import org.apache.hugegraph.backend.query.Query;
import org.apache.hugegraph.exception.NotSupportException;
import org.apache.hugegraph.iterator.FilterIterator;
import org.apache.hugegraph.schema.PropertyKey;
import org.apache.hugegraph.schema.SchemaLabel;
import org.apache.hugegraph.structure.HugeElement;
import org.apache.hugegraph.structure.HugeProperty;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.Directions;
import org.apache.hugegraph.type.define.HugeKeys;
import org.apache.hugegraph.util.CollectionUtil;
import org.apache.hugegraph.util.DateUtil;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.JsonUtil;
import org.apache.tinkerpop.gremlin.process.traversal.Compare;
import org.apache.tinkerpop.gremlin.process.traversal.Contains;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.NoOpBarrierStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.ElementValueComparator;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.ReducingBarrierStep;
import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.PropertyType;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
import com.google.common.collect.ImmutableList;
public final class TraversalUtil {
public static final String P_CALL = "P.";
public static HugeGraph getGraph(Step<?, ?> step) {
HugeGraph graph = tryGetGraph(step);
if (graph != null) {
return graph;
}
throw new IllegalArgumentException("There is no graph in step: " + step);
}
public static HugeGraph tryGetGraph(Step<?, ?> step) {
// TODO: remove these EmptyGraph judgments when upgrade tinkerpop (refer-tinkerpop#1699)
Optional<Graph> graph = step.getTraversal()
.getGraph()
.filter(g -> !(g instanceof EmptyGraph));
if (!graph.isPresent()) {
TraversalParent parent = step.getTraversal().getParent();
if (parent instanceof Traversal) {
Optional<Graph> parentGraph;
parentGraph = ((Traversal<?, ?>) parent).asAdmin()
.getGraph()
.filter(g -> !(g instanceof EmptyGraph));
if (parentGraph.isPresent()) {
step.getTraversal().setGraph(parentGraph.get());
return (HugeGraph) parentGraph.get();
}
}
return null;
}
assert graph.get() instanceof HugeGraph;
return (HugeGraph) graph.get();
}
public static void trySetGraph(Step<?, ?> step, HugeGraph graph) {
if (graph == null || step == null || step.getTraversal() == null) {
return;
}
// TODO: remove these EmptyGraph judgments when upgrade tinkerpop (refer-tinkerpop#1699)
Optional<Graph> stepGraph = step.getTraversal()
.getGraph()
.filter(g -> !(g instanceof EmptyGraph));
if (step instanceof TraversalParent) {
for (final Traversal.Admin<?, ?> local : ((TraversalParent) step).getLocalChildren()) {
if (local.getGraph().filter(g -> !(g instanceof EmptyGraph)).isPresent()) {
continue;
}
local.setGraph(graph);
}
for (final Traversal.Admin<?, ?> global :
((TraversalParent) step).getGlobalChildren()) {
if (global.getGraph().filter(g -> !(g instanceof EmptyGraph)).isPresent()) {
continue;
}
global.setGraph(graph);
}
}
if (stepGraph.isPresent()) {
assert stepGraph.get() instanceof HugeGraph;
return;
}
step.getTraversal().setGraph(graph);
}
public static void extractHasContainer(HugeGraphStep<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof HasStep) {
HasContainerHolder holder = (HasContainerHolder) step;
for (HasContainer has : holder.getHasContainers()) {
if (!GraphStep.processHasContainerIds(newStep, has)) {
newStep.addHasContainer(has);
}
}
TraversalHelper.copyLabels(step, step.getPreviousStep(), false);
traversal.removeStep(step);
}
} while (step instanceof HasStep || step instanceof NoOpBarrierStep);
}
public static void extractHasContainer(HugeVertexStep<?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
if (step instanceof HasStep) {
HasContainerHolder holder = (HasContainerHolder) step;
for (HasContainer has : holder.getHasContainers()) {
newStep.addHasContainer(has);
}
TraversalHelper.copyLabels(step, step.getPreviousStep(), false);
traversal.removeStep(step);
}
step = step.getNextStep();
} while (step instanceof HasStep || step instanceof NoOpBarrierStep);
}
public static void extractOrder(Step<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof OrderGlobalStep) {
QueryHolder holder = (QueryHolder) newStep;
OrderGlobalStep<?, ?> orderStep = (OrderGlobalStep<?, ?>) step;
orderStep.getComparators().forEach(comp -> {
ElementValueComparator<?> comparator =
(ElementValueComparator<?>) comp.getValue1();
holder.orderBy(comparator.getPropertyKey(),
(Order) comparator.getValueComparator());
});
TraversalHelper.copyLabels(step, newStep, false);
traversal.removeStep(step);
}
step = step.getNextStep();
} while (step instanceof OrderGlobalStep ||
step instanceof IdentityStep);
}
public static void extractRange(Step<?, ?> newStep,
Traversal.Admin<?, ?> traversal,
boolean extractOnlyLimit) {
QueryHolder holder = (QueryHolder) newStep;
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof RangeGlobalStep) {
@SuppressWarnings("unchecked")
RangeGlobalStep<Object> range = (RangeGlobalStep<Object>) step;
/*
* NOTE: keep the step to limit results after query from DB
* due to `limit`(in DB) may not be implemented accurately.
* but the backend driver should ensure `offset` accurately.
*/
// TraversalHelper.copyLabels(step, newStep, false);
// traversal.removeStep(step);
if (extractOnlyLimit) {
// May need to retain offset for multiple sub-queries
holder.setRange(0, range.getHighRange());
} else {
long limit = holder.setRange(range.getLowRange(),
range.getHighRange());
RangeGlobalStep<Object> newRange = new RangeGlobalStep<>(
traversal, 0, limit);
TraversalHelper.replaceStep(range, newRange, traversal);
}
}
} while (step instanceof RangeGlobalStep ||
step instanceof IdentityStep ||
step instanceof NoOpBarrierStep);
}
public static void extractCount(Step<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof CountGlobalStep) {
QueryHolder holder = (QueryHolder) newStep;
holder.setCount();
}
} while (step instanceof CountGlobalStep ||
step instanceof FilterStep ||
step instanceof IdentityStep ||
step instanceof NoOpBarrierStep);
}
public static void extractAggregateFunc(Step<?, ?> newStep,
Traversal.Admin<?, ?> traversal) {
PropertiesStep<?> propertiesStep = null;
Step<?, ?> step = newStep;
do {
step = step.getNextStep();
if (step instanceof PropertiesStep) {
PropertiesStep<?> propStep = (PropertiesStep<?>) step;
if (propStep.getReturnType() == PropertyType.VALUE &&
propStep.getPropertyKeys().length == 1) {
propertiesStep = propStep;
}
} else if (propertiesStep != null &&
step instanceof ReducingBarrierStep) {
Aggregate.AggregateFunc aggregateFunc;
if (step instanceof CountGlobalStep) {
aggregateFunc = Aggregate.AggregateFunc.COUNT;
} else if (step instanceof MaxGlobalStep) {
aggregateFunc = Aggregate.AggregateFunc.MAX;
} else if (step instanceof MinGlobalStep) {
aggregateFunc = Aggregate.AggregateFunc.MIN;
} else if (step instanceof MeanGlobalStep) {
aggregateFunc = Aggregate.AggregateFunc.AVG;
} else if (step instanceof SumGlobalStep) {
aggregateFunc = Aggregate.AggregateFunc.SUM;
} else {
aggregateFunc = null;
}
if (aggregateFunc != null) {
QueryHolder holder = (QueryHolder) newStep;
holder.setAggregate(aggregateFunc,
propertiesStep.getPropertyKeys()[0]);
traversal.removeStep(step);
traversal.removeStep(propertiesStep);
}
}
} while (step instanceof FilterStep ||
step instanceof PropertiesStep ||
step instanceof IdentityStep ||
step instanceof NoOpBarrierStep);
}
public static ConditionQuery fillConditionQuery(
ConditionQuery query,
List<HasContainer> hasContainers,
HugeGraph graph) {
HugeType resultType = query.resultType();
for (HasContainer has : hasContainers) {
Condition condition = convHas2Condition(has, resultType, graph);
query.query(condition);
}
return query;
}
public static void fillConditionQuery(ConditionQuery query,
Map<Id, Object> properties,
HugeGraph graph) {
for (Map.Entry<Id, Object> entry : properties.entrySet()) {
Id key = entry.getKey();
Object value = entry.getValue();
PropertyKey pk = graph.propertyKey(key);
if (value instanceof String &&
((String) value).startsWith(TraversalUtil.P_CALL)) {
String predicate = (String) value;
query.query(TraversalUtil.parsePredicate(pk, predicate));
} else if (value instanceof Collection) {
List<Object> validValues = new ArrayList<>();
for (Object v : (Collection<?>) value) {
validValues.add(TraversalUtil.validPropertyValue(v, pk));
}
query.query(Condition.in(key, validValues));
} else {
Object validValue = TraversalUtil.validPropertyValue(value, pk);
query.query(Condition.eq(key, validValue));
}
}
}
public static Condition convHas2Condition(HasContainer has, HugeType type, HugeGraph graph) {
P<?> p = has.getPredicate();
E.checkArgument(p != null, "The predicate of has(%s) is null", has);
BiPredicate<?, ?> bp = p.getBiPredicate();
Condition condition;
if (keyForContainsKeyOrValue(has.getKey())) {
condition = convContains2Relation(graph, has);
} else if (bp instanceof Compare) {
condition = convCompare2Relation(graph, type, has);
} else if (bp instanceof Condition.RelationType) {
condition = convRelationType2Relation(graph, type, has);
} else if (bp instanceof Contains) {
condition = convIn2Relation(graph, type, has);
} else if (p instanceof AndP) {
condition = convAnd(graph, type, has);
} else if (p instanceof OrP) {
condition = convOr(graph, type, has);
} else {
// TODO: deal with other Predicate
throw newUnsupportedPredicate(p);
}
return condition;
}
public static Condition convAnd(HugeGraph graph,
HugeType type,
HasContainer has) {
P<?> p = has.getPredicate();
assert p instanceof AndP;
@SuppressWarnings("unchecked")
List<P<Object>> predicates = ((AndP<Object>) p).getPredicates();
if (predicates.size() < 2) {
throw newUnsupportedPredicate(p);
}
Condition cond = null;
for (P<Object> predicate : predicates) {
HasContainer newHas = new HasContainer(has.getKey(), predicate);
Condition newCond = convHas2Condition(newHas, type, graph);
if (cond == null) {
cond = newCond;
} else {
cond = Condition.and(newCond, cond);
}
}
return cond;
}
public static Condition convOr(HugeGraph graph,
HugeType type,
HasContainer has) {
P<?> p = has.getPredicate();
assert p instanceof OrP;
@SuppressWarnings("unchecked")
List<P<Object>> predicates = ((OrP<Object>) p).getPredicates();
if (predicates.size() < 2) {
throw newUnsupportedPredicate(p);
}
Condition cond = null;
for (P<Object> predicate : predicates) {
HasContainer newHas = new HasContainer(has.getKey(), predicate);
Condition newCond = convHas2Condition(newHas, type, graph);
if (cond == null) {
cond = newCond;
} else {
cond = Condition.or(newCond, cond);
}
}
return cond;
}
private static Condition.Relation convCompare2Relation(HugeGraph graph,
HugeType type,
HasContainer has) {
assert type.isGraph();
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
assert bp instanceof Compare;
return isSysProp(has.getKey()) ?
convCompare2SyspropRelation(graph, type, has) :
convCompare2UserpropRelation(graph, type, has);
}
private static Condition.Relation convCompare2SyspropRelation(HugeGraph graph,
HugeType type,
HasContainer has) {
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
assert bp instanceof Compare;
HugeKeys key = token2HugeKey(has.getKey());
E.checkNotNull(key, "token key");
Object value = convSysValueIfNeeded(graph, type, key, has.getValue());
switch ((Compare) bp) {
case eq:
return Condition.eq(key, value);
case gt:
return Condition.gt(key, value);
case gte:
return Condition.gte(key, value);
case lt:
return Condition.lt(key, value);
case lte:
return Condition.lte(key, value);
case neq:
return Condition.neq(key, value);
default:
throw newUnsupportedPredicate(has.getPredicate());
}
}
private static Condition.Relation convCompare2UserpropRelation(HugeGraph graph,
HugeType type,
HasContainer has) {
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
assert bp instanceof Compare;
String key = has.getKey();
PropertyKey pkey = graph.propertyKey(key);
Id pkeyId = pkey.id();
Object value = validPropertyValue(has.getValue(), pkey);
switch ((Compare) bp) {
case eq:
return Condition.eq(pkeyId, value);
case gt:
return Condition.gt(pkeyId, value);
case gte:
return Condition.gte(pkeyId, value);
case lt:
return Condition.lt(pkeyId, value);
case lte:
return Condition.lte(pkeyId, value);
case neq:
return Condition.neq(pkeyId, value);
default:
throw newUnsupportedPredicate(has.getPredicate());
}
}
private static Condition convRelationType2Relation(HugeGraph graph,
HugeType type,
HasContainer has) {
assert type.isGraph();
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
assert bp instanceof Condition.RelationType;
String key = has.getKey();
PropertyKey pkey = graph.propertyKey(key);
Id pkeyId = pkey.id();
Object value = validPropertyValue(has.getValue(), pkey);
return new Condition.UserpropRelation(pkeyId, (Condition.RelationType) bp, value);
}
public static Condition convIn2Relation(HugeGraph graph,
HugeType type,
HasContainer has) {
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
assert bp instanceof Contains;
Collection<?> values = (Collection<?>) has.getValue();
String originKey = has.getKey();
if (values.size() > 1) {
E.checkArgument(!originKey.equals(T.key.getAccessor()) &&
!originKey.equals(T.value.getAccessor()),
"Not support hasKey() or hasValue() with " +
"multiple values");
}
HugeKeys hugeKey = token2HugeKey(originKey);
List<?> valueList;
if (hugeKey != null) {
valueList = convSysListValueIfNeeded(graph, type, hugeKey, values);
switch ((Contains) bp) {
case within:
return Condition.in(hugeKey, valueList);
case without:
return Condition.nin(hugeKey, valueList);
default:
throw newUnsupportedPredicate(has.getPredicate());
}
} else {
valueList = new ArrayList<>(values);
String key = has.getKey();
PropertyKey pkey = graph.propertyKey(key);
switch ((Contains) bp) {
case within:
return Condition.in(pkey.id(), valueList);
case without:
return Condition.nin(pkey.id(), valueList);
default:
throw newUnsupportedPredicate(has.getPredicate());
}
}
}
public static Condition convContains2Relation(HugeGraph graph,
HasContainer has) {
// Convert contains-key or contains-value
BiPredicate<?, ?> bp = has.getPredicate().getBiPredicate();
E.checkArgument(bp == Compare.eq, "CONTAINS query with relation " +
"'%s' is not supported", bp);
HugeKeys key = token2HugeKey(has.getKey());
E.checkNotNull(key, "token key");
Object value = has.getValue();
if (keyForContainsKey(has.getKey())) {
if (value instanceof String) {
value = graph.propertyKey((String) value).id();
}
return Condition.containsKey(key, value);
} else {
assert keyForContainsValue(has.getKey());
return Condition.containsValue(key, value);
}
}
public static BackendException newUnsupportedPredicate(P<?> predicate) {
return new BackendException("Unsupported predicate: '%s'", predicate);
}
public static HugeKeys string2HugeKey(String key) {
HugeKeys hugeKey = token2HugeKey(key);
return hugeKey != null ? hugeKey : HugeKeys.valueOf(key);
}
public static HugeKeys token2HugeKey(String key) {
if (key.equals(T.label.getAccessor())) {
return HugeKeys.LABEL;
} else if (key.equals(T.id.getAccessor())) {
return HugeKeys.ID;
} else if (keyForContainsKeyOrValue(key)) {
return HugeKeys.PROPERTIES;
}
return null;
}
public static boolean keyForContainsKeyOrValue(String key) {
return key.equals(T.key.getAccessor()) ||
key.equals(T.value.getAccessor());
}
public static boolean keyForContainsKey(String key) {
return key.equals(T.key.getAccessor());
}
public static boolean keyForContainsValue(String key) {
return key.equals(T.value.getAccessor());
}
@SuppressWarnings("unchecked")
public static <V> Iterator<V> filterResult(
List<HasContainer> hasContainers,
Iterator<? extends Element> iterator) {
if (hasContainers.isEmpty()) {
return (Iterator<V>) iterator;
}
Iterator<?> result = new FilterIterator<>(iterator, elem -> {
return HasContainer.testAll(elem, hasContainers);
});
return (Iterator<V>) result;
}
public static Iterator<Edge> filterResult(Vertex vertex,
Directions dir,
Iterator<Edge> edges) {
return new FilterIterator<>(edges, edge -> {
return dir == Directions.OUT && vertex.equals(edge.outVertex()) ||
dir == Directions.IN && vertex.equals(edge.inVertex());
});
}
public static void convAllHasSteps(Traversal.Admin<?, ?> traversal) {
// Extract all has steps in traversal
@SuppressWarnings("rawtypes")
List<HasStep> steps =
TraversalHelper.getStepsOfAssignableClassRecursively(
HasStep.class, traversal);
if (steps.isEmpty()) {
return;
}
/*
* The graph in traversal may be null, for example:
* `g.V().hasLabel('person').union(__.has('name', 'tom'))`
* Here `__.has()` will create a new traversal, but the graph is null
*/
if (!traversal.getGraph().filter(g -> !(g instanceof EmptyGraph)).isPresent()) {
if (traversal.getParent() == null || !(traversal.getParent() instanceof Traversal)) {
return;
}
Optional<Graph> parentGraph = ((Traversal<?, ?>) traversal.getParent())
.asAdmin()
.getGraph();
if (parentGraph.filter(g -> !(g instanceof EmptyGraph)).isPresent()) {
traversal.setGraph(parentGraph.get());
}
}
HugeGraph graph = (HugeGraph) traversal.getGraph().get();
for (HasStep<?> step : steps) {
TraversalUtil.convHasStep(graph, step);
}
}
public static void convHasStep(HugeGraph graph, HasStep<?> step) {
HasContainerHolder holder = step;
for (HasContainer has : holder.getHasContainers()) {
convPredicateValue(graph, has);
}
}
private static void convPredicateValue(HugeGraph graph,
HasContainer has) {
// No need to convert if key is sys-prop
if (isSysProp(has.getKey())) {
return;
}
PropertyKey pkey = graph.propertyKey(has.getKey());
updatePredicateValue(has.getPredicate(), pkey);
}
private static void updatePredicateValue(P<?> predicate, PropertyKey pkey) {
List<P<Object>> leafPredicates = new ArrayList<>();
collectPredicates(leafPredicates, ImmutableList.of(predicate));
for (P<Object> pred : leafPredicates) {
Object value = validPropertyValue(pred.getValue(), pkey);
pred.setValue(value);
}
}
private static boolean isSysProp(String key) {
if (QueryHolder.SYSPROP_PAGE.equals(key)) {
return true;
}
// Return true if key is ~id, ~label, ~key and ~value
return token2HugeKey(key) != null;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static void collectPredicates(List<P<Object>> results,
List<P<?>> predicates) {
for (P<?> p : predicates) {
if (p instanceof ConnectiveP) {
collectPredicates(results, ((ConnectiveP) p).getPredicates());
} else {
results.add((P<Object>) p);
}
}
}
private static Object convSysValueIfNeeded(HugeGraph graph,
HugeType type,
HugeKeys key,
Object value) {
if (key == HugeKeys.LABEL && !(value instanceof Id)) {
value = SchemaLabel.getLabelId(graph, type, value);
} else if (key == HugeKeys.ID && !(value instanceof Id)) {
value = HugeElement.getIdValue(type, value);
}
return value;
}
private static List<?> convSysListValueIfNeeded(HugeGraph graph,
HugeType type,
HugeKeys key,
Collection<?> values) {
List<Object> newValues = new ArrayList<>(values.size());
for (Object value : values) {
newValues.add(convSysValueIfNeeded(graph, type, key, value));
}
return newValues;
}
public static Query.Order convOrder(Order order) {
return order == Order.desc ? Query.Order.DESC : Query.Order.ASC;
}
private static <V> V validPropertyValue(V value, PropertyKey pkey) {
if (pkey.cardinality().single() && value instanceof Collection &&
!pkey.dataType().isBlob()) {
// Expect single but got collection, like P.within([])
Collection<?> collection = (Collection<?>) value;
Collection<Object> validValues = new ArrayList<>();
for (Object element : collection) {
Object validValue = pkey.validValue(element);
if (validValue == null) {
validValues = null;
break;
}
validValues.add(validValue);
}
if (validValues == null) {
List<Class<?>> classes = new ArrayList<>();
for (Object v : (Collection<?>) value) {
classes.add(v == null ? null : v.getClass());
}
E.checkArgument(false,
"Invalid data type of query value in %s, " +
"expect %s for '%s', actual got %s",
value, pkey.dataType(), pkey.name(), classes);
}
@SuppressWarnings("unchecked")
V validValue = (V) validValues;
return validValue;
}
V validValue;
if (pkey.cardinality().multiple() && !(value instanceof Collection)) {
// Expect non-single but got single, like P.contains(value)
List<V> values = CollectionUtil.toList(value);
values = pkey.validValue(values);
validValue = values != null ? values.get(0) : null;
} else {
validValue = pkey.validValue(value);
}
if (validValue == null) {
E.checkArgument(false,
"Invalid data type of query value '%s', " +
"expect %s for '%s', actual got %s",
value, pkey.dataType(), pkey.name(),
value == null ? null : value.getClass());
}
return validValue;
}
public static void retrieveSysprop(List<HasContainer> hasContainers,
Function<HasContainer, Boolean> func) {
for (Iterator<HasContainer> iter = hasContainers.iterator(); iter.hasNext(); ) {
HasContainer container = iter.next();
if (container.getKey().startsWith("~") && func.apply(container)) {
iter.remove();
}
}
}
public static String page(GraphTraversal<?, ?> traversal) {
QueryHolder holder = firstPageStep(traversal);
E.checkState(holder != null,
"Invalid paging traversal: %s", traversal.getClass());
Object page = holder.metadata(PageInfo.PAGE);
if (page == null) {
return null;
}
/*
* Page is instance of PageInfo if traversal with condition like:
* g.V().has("x", 1).has("~page", "").
* Page is instance of PageState if traversal without condition like:
* g.V().has("~page", "")
*/
assert page instanceof PageInfo || page instanceof PageState;
return page.toString();
}
public static QueryHolder rootStep(GraphTraversal<?, ?> traversal) {
for (final Step<?, ?> step : traversal.asAdmin().getSteps()) {
if (step instanceof QueryHolder) {
return (QueryHolder) step;
}
}
return null;
}
public static QueryHolder firstPageStep(GraphTraversal<?, ?> traversal) {
for (final Step<?, ?> step : traversal.asAdmin().getSteps()) {
if (step instanceof QueryHolder &&
((QueryHolder) step).queryInfo().paging()) {
return (QueryHolder) step;
}
}
return null;
}
public static boolean testProperty(Property<?> prop, Object expected) {
Object actual = prop.value();
P<Object> predicate;
if (expected instanceof String &&
((String) expected).startsWith(TraversalUtil.P_CALL)) {
predicate = TraversalUtil.parsePredicate(((String) expected));
} else {
predicate = ConditionP.eq(expected);
}
updatePredicateValue(predicate, ((HugeProperty<?>) prop).propertyKey());
return predicate.test(actual);
}
public static Map<Id, Object> transProperties(HugeGraph graph,
Map<String, Object> props) {
Map<Id, Object> pks = new HashMap<>(props.size());
for (Map.Entry<String, Object> e : props.entrySet()) {
PropertyKey pk = graph.propertyKey(e.getKey());
pks.put(pk.id(), e.getValue());
}
return pks;
}
public static P<Object> parsePredicate(String predicate) {
/*
* Extract P from json string like {"properties": {"age": "P.gt(18)"}}
* the `predicate` may actually be like "P.gt(18)"
*/
Pattern pattern = Pattern.compile("^P\\.([a-z]+)\\(([\\S ]*)\\)$");
Matcher matcher = pattern.matcher(predicate);
if (!matcher.find()) {
throw new HugeException("Invalid predicate: %s", predicate);
}
String method = matcher.group(1);
String value = matcher.group(2);
switch (method) {
case "eq":
return P.eq(predicateNumber(value));
case "neq":
return P.neq(predicateNumber(value));
case "lt":
return P.lt(predicateNumber(value));
case "lte":
return P.lte(predicateNumber(value));
case "gt":
return P.gt(predicateNumber(value));
case "gte":
return P.gte(predicateNumber(value));
case "between":
Number[] params = predicateNumbers(value, 2);
return P.between(params[0], params[1]);
case "inside":
params = predicateNumbers(value, 2);
return P.inside(params[0], params[1]);
case "outside":
params = predicateNumbers(value, 2);
return P.outside(params[0], params[1]);
case "within":
return P.within(predicateArgs(value));
case "textcontains":
return ConditionP.textContains(predicateArg(value));
case "contains":
// Just for inner use case like auth filter
return ConditionP.contains(predicateArg(value));
default:
throw new NotSupportException("predicate '%s'", method);
}
}
public static Condition parsePredicate(PropertyKey pk, String predicate) {
Pattern pattern = Pattern.compile("^P\\.([a-z]+)\\(([\\S ]*)\\)$");
Matcher matcher = pattern.matcher(predicate);
if (!matcher.find()) {
throw new HugeException("Invalid predicate: %s", predicate);
}
String method = matcher.group(1);
String value = matcher.group(2);
Object validValue;
switch (method) {
case "eq":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.eq(pk.id(), validValue);
case "neq":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.neq(pk.id(), validValue);
case "lt":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.lt(pk.id(), validValue);
case "lte":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.lte(pk.id(), validValue);
case "gt":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.gt(pk.id(), validValue);
case "gte":
validValue = validPropertyValue(predicateNumber(value), pk);
return Condition.gte(pk.id(), validValue);
case "between":
Number[] params = predicateNumbers(value, 2);
Object v1 = validPropertyValue(params[0], pk);
Object v2 = validPropertyValue(params[1], pk);
return Condition.and(Condition.gte(pk.id(), v1),
Condition.lt(pk.id(), v2));
case "inside":
params = predicateNumbers(value, 2);
v1 = validPropertyValue(params[0], pk);
v2 = validPropertyValue(params[1], pk);
return Condition.and(Condition.gt(pk.id(), v1),
Condition.lt(pk.id(), v2));
case "outside":
params = predicateNumbers(value, 2);
v1 = validPropertyValue(params[0], pk);
v2 = validPropertyValue(params[1], pk);
return Condition.and(Condition.lt(pk.id(), v1),
Condition.gt(pk.id(), v2));
case "within":
List<T> values = predicateArgs(value);
List<T> validValues = new ArrayList<>(values.size());
for (T v : values) {
validValues.add(validPropertyValue(v, pk));
}
return Condition.in(pk.id(), validValues);
case "textcontains":
validValue = validPropertyValue(value, pk);
return Condition.textContains(pk.id(), (String) validValue);
case "contains":
validValue = validPropertyValue(value, pk);
return Condition.contains(pk.id(), validValue);
default:
throw new NotSupportException("predicate '%s'", method);
}
}
private static Number predicateNumber(String value) {
try {
return JsonUtil.fromJson(value, Number.class);
} catch (Exception e) {
// Try to parse date
if (e.getMessage().contains("not a valid number") ||
e.getMessage().contains("Unexpected character ('-'")) {
try {
if (value.startsWith("\"")) {
value = JsonUtil.fromJson(value, String.class);
}
return DateUtil.parse(value).getTime();
} catch (Exception ignored) {
// TODO: improve to throw a exception here
}
}
throw new HugeException(
"Invalid value '%s', expect a number", e, value);
}
}
private static Number[] predicateNumbers(String value, int count) {
List<Object> values = predicateArgs(value);
if (values.size() != count) {
throw new HugeException("Invalid numbers size %s, expect %s",
values.size(), count);
}
for (int i = 0; i < count; i++) {
Object v = values.get(i);
if (v instanceof Number) {
continue;
}
try {
v = predicateNumber(v.toString());
} catch (Exception ignored) {
// pass
}
if (v instanceof Number) {
values.set(i, v);
continue;
}
throw new HugeException(
"Invalid value '%s', expect a list of number", value);
}
return values.toArray(new Number[0]);
}
@SuppressWarnings("unchecked")
private static <V> V predicateArg(String value) {
try {
return (V) JsonUtil.fromJson(value, Object.class);
} catch (Exception e) {
throw new HugeException(
"Invalid value '%s', expect a single value", e, value);
}
}
@SuppressWarnings("unchecked")
private static <V> List<V> predicateArgs(String value) {
try {
return JsonUtil.fromJson("[" + value + "]", List.class);
} catch (Exception e) {
throw new HugeException(
"Invalid value '%s', expect a list", e, value);
}
}
}