Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ private QueryResults<R> each(IdHolder holder) {
return null;
}

return this.queryByIndexIds(ids);
return this.queryByIndexIds(ids, holder.keepOrder());
});
}

Expand All @@ -275,7 +275,8 @@ public PageResults<R> iterator(int index, String page, long pageSize) {
return PageResults.emptyIterator();
}

QueryResults<R> results = this.queryByIndexIds(pageIds.ids());
QueryResults<R> results = this.queryByIndexIds(pageIds.ids(),
holder.keepOrder());

return new PageResults<>(results, pageIds.pageState());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,24 +256,27 @@ public boolean containsLabelOrUserpropRelation() {
return false;
}

/**
* Returns the legacy condition value of the specified key.
*
* This method keeps the historical behavior for existing callers:
* <ul>
* <li>returns {@code null} if no top-level EQ/IN relation exists</li>
* <li>returns {@code null} if top-level EQ/IN relations resolve to empty</li>
* <li>returns the single value if only one value is resolved</li>
* <li>returns the raw IN list if there is exactly one top-level IN relation</li>
* <li>throws if multiple values remain after resolving several relations</li>
* </ul>
*
* Prefer {@link #conditionValues(Object)}, {@link #uniqueConditionValue(Object)}
* or {@link #conditionValue(Object)} for new code that needs explicit
* semantics.
*/
@Watched
public <T> T condition(Object key) {
List<Object> valuesEQ = InsertionOrderUtil.newList();
List<Object> valuesIN = InsertionOrderUtil.newList();
for (Condition c : this.conditions) {
if (c.isRelation()) {
Condition.Relation r = (Condition.Relation) c;
if (r.key().equals(key)) {
if (r.relation() == RelationType.EQ) {
valuesEQ.add(r.value());
} else if (r.relation() == RelationType.IN) {
Object value = r.value();
assert value instanceof List;
valuesIN.add(value);
}
}
}
}
this.collectConditionValues(key, valuesEQ, valuesIN);
if (valuesEQ.isEmpty() && valuesIN.isEmpty()) {
return null;
}
Expand All @@ -288,29 +291,8 @@ public <T> T condition(Object key) {
return value;
}

boolean initialized = false;
Set<Object> intersectValues = InsertionOrderUtil.newSet();
for (Object value : valuesEQ) {
List<Object> valueAsList = ImmutableList.of(value);
if (!initialized) {
intersectValues.addAll(valueAsList);
initialized = true;
} else {
CollectionUtil.intersectWithModify(intersectValues,
valueAsList);
}
}
for (Object value : valuesIN) {
@SuppressWarnings("unchecked")
List<Object> valueAsList = (List<Object>) value;
if (!initialized) {
intersectValues.addAll(valueAsList);
initialized = true;
} else {
CollectionUtil.intersectWithModify(intersectValues,
valueAsList);
}
}
Set<Object> intersectValues = this.resolveConditionValues(valuesEQ,
valuesIN);

if (intersectValues.isEmpty()) {
return null;
Expand All @@ -323,20 +305,151 @@ public <T> T condition(Object key) {
return value;
}

/**
* Returns whether there is any top-level relation for the specified key.
*/
public boolean containsCondition(Object key) {
for (Condition c : this.conditions) {
if (c.isRelation()) {
Condition.Relation r = (Condition.Relation) c;
if (r.key().equals(key)) {
return true;
}
}
}
return false;
}

/**
* Returns the resolved candidate values of the specified key from
* top-level EQ/IN relations.
*
* Use {@link #containsConditionValues(Object)} to distinguish "no EQ/IN
* condition" from "EQ/IN conditions exist but resolve to an empty
* intersection".
*/
public Set<Object> conditionValues(Object key) {
List<Object> valuesEQ = InsertionOrderUtil.newList();
List<Object> valuesIN = InsertionOrderUtil.newList();
this.collectConditionValues(key, valuesEQ, valuesIN);
if (valuesEQ.isEmpty() && valuesIN.isEmpty()) {
return InsertionOrderUtil.newSet();
}
return this.resolveConditionValues(valuesEQ, valuesIN);
}

/**
* Returns whether there is any top-level EQ/IN relation for the specified
* key.
*/
public boolean containsConditionValues(Object key) {
for (Condition c : this.conditions) {
if (c.isRelation()) {
Condition.Relation r = (Condition.Relation) c;
if (r.key().equals(key) &&
(r.relation() == RelationType.EQ ||
r.relation() == RelationType.IN)) {
return true;
}
}
}
return false;
}

/**
* Returns the unique resolved value of the specified key from top-level
* EQ/IN relations.
*
* Returns {@code null} when the resolved candidate set is empty. Throws
* if multiple values remain after resolution.
*/
public <T> T conditionValue(Object key) {
Set<Object> values = this.conditionValues(key);
if (values.isEmpty()) {
return null;
}
E.checkState(values.size() == 1,
"Illegal key '%s' with more than one value: %s",
key, values);
@SuppressWarnings("unchecked")
T value = (T) values.iterator().next();
return value;
}

/**
* Returns the unique resolved value of the specified key from top-level
* EQ/IN relations, or {@code null} if the resolved candidate set doesn't
* contain exactly one value.
*
* Use this method when callers want "single-or-null" semantics instead of
* treating multiple remaining values as an error.
*/
public <T> T uniqueConditionValue(Object key) {
Set<Object> values = this.conditionValues(key);
if (values.size() != 1) {
return null;
}
@SuppressWarnings("unchecked")
T value = (T) values.iterator().next();
return value;
}

public void unsetCondition(Object key) {
this.conditions.removeIf(c -> c.isRelation() && ((Relation) c).key().equals(key));
}

public boolean containsCondition(HugeKeys key) {
return this.containsCondition((Object) key);
}

public boolean containsConditionValues(HugeKeys key) {
return this.containsConditionValues((Object) key);
}

private void collectConditionValues(Object key, List<Object> valuesEQ,
List<Object> valuesIN) {
for (Condition c : this.conditions) {
if (c.isRelation()) {
Condition.Relation r = (Condition.Relation) c;
if (r.key().equals(key)) {
return true;
if (r.relation() == RelationType.EQ) {
valuesEQ.add(r.value());
} else if (r.relation() == RelationType.IN) {
Object value = r.value();
assert value instanceof List;
valuesIN.add(value);
}
}
}
}
return false;
}

private Set<Object> resolveConditionValues(List<Object> valuesEQ,
List<Object> valuesIN) {
boolean initialized = false;
Comment thread
contrueCT marked this conversation as resolved.
Set<Object> intersectValues = InsertionOrderUtil.newSet();
for (Object value : valuesEQ) {
List<Object> valueAsList = ImmutableList.of(value);
if (!initialized) {
intersectValues.addAll(valueAsList);
initialized = true;
} else {
CollectionUtil.intersectWithModify(intersectValues,
valueAsList);
}
}
for (Object value : valuesIN) {
@SuppressWarnings("unchecked")
List<Object> valueAsList = (List<Object>) value;
if (!initialized) {
intersectValues.addAll(valueAsList);
initialized = true;
} else {
CollectionUtil.intersectWithModify(intersectValues,
valueAsList);
}
}
return intersectValues;
}

public boolean containsCondition(Condition.RelationType type) {
Expand Down Expand Up @@ -566,6 +679,15 @@ public boolean hasNeqCondition() {
return false;
}

public boolean hasUserpropNeqCondition() {
for (Condition.Relation r : this.userpropRelations()) {
if (r.relation() == RelationType.NEQ) {
return true;
}
}
return false;
}

public boolean matchUserpropKeys(List<Id> keys) {
Set<Id> conditionKeys = this.userpropKeys();
return !keys.isEmpty() && conditionKeys.containsAll(keys);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ public <T extends Idfiable> Iterator<T> keepInputOrderIfNeeded(
return origin;
}
Collection<Id> ids;
if (!this.mustSortByInputIds() || this.paging() ||
if (!this.mustSortByInputIds() ||
(ids = this.queryIds()).size() <= 1) {
/*
* Return the original iterator if it's paging query or if the
* query input is less than one id, or don't have to do sort.
* NOTE: queryIds() only return the first batch of index query
* Return the original iterator if the query input is less than one
* id, or don't have to do sort.
* NOTE: queryIds() only return the first batch of index query.
*/
return origin;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ private Query writeQueryEdgeRangeCondition(ConditionQuery cq) {
if (direction == null) {
direction = Directions.OUT;
}
Id label = cq.condition(HugeKeys.LABEL);
Id label = (Id) this.edgeIdConditionValue(cq, HugeKeys.LABEL);
Comment thread
contrueCT marked this conversation as resolved.

BytesBuffer start = BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID);
writePartitionedId(HugeType.EDGE, vertex, start);
Expand Down Expand Up @@ -722,7 +722,7 @@ private Query writeQueryEdgePrefixCondition(ConditionQuery cq) {
int count = 0;
BytesBuffer buffer = BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID);
for (HugeKeys key : EdgeId.KEYS) {
Object value = cq.condition(key);
Object value = this.edgeIdConditionValue(cq, key);

if (value != null) {
count++;
Expand Down Expand Up @@ -763,6 +763,17 @@ private Query writeQueryEdgePrefixCondition(ConditionQuery cq) {
return null;
}

private Object edgeIdConditionValue(ConditionQuery cq, HugeKeys key) {
if (key == HugeKeys.LABEL) {
/*
* LABEL may still be represented by multiple top-level EQ/IN
* relations before strict edge-id serialization.
*/
return cq.conditionValue(key);
}
return cq.condition(key);
}

@Override
protected Query writeQueryCondition(Query query) {
HugeType type = query.resultType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ private Query writeQueryEdgeRangeCondition(ConditionQuery cq) {
if (direction == null) {
direction = Directions.OUT;
}
Object label = cq.condition(HugeKeys.LABEL);
Object label = this.edgeIdConditionValue(cq, HugeKeys.LABEL);

List<String> start = new ArrayList<>(cq.conditionsSize());
start.add(writeEntryId((Id) vertex));
Expand Down Expand Up @@ -491,7 +491,7 @@ private Query writeQueryEdgePrefixCondition(ConditionQuery cq) {
List<String> condParts = new ArrayList<>(cq.conditionsSize());

for (HugeKeys key : EdgeId.KEYS) {
Object value = cq.condition(key);
Object value = this.edgeIdConditionValue(cq, key);
if (value == null) {
break;
}
Expand All @@ -516,6 +516,17 @@ private Query writeQueryEdgePrefixCondition(ConditionQuery cq) {
return null;
}

private Object edgeIdConditionValue(ConditionQuery cq, HugeKeys key) {
if (key == HugeKeys.LABEL) {
/*
* LABEL may still be represented by multiple top-level EQ/IN
* relations before strict edge-id serialization.
*/
return cq.conditionValue(key);
}
return cq.condition(key);
}

@Override
protected Query writeQueryCondition(Query query) {
ConditionQuery result = (ConditionQuery) query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public boolean matched(Query query) {
int conditionsSize = cq.conditionsSize();
Object owner = cq.condition(HugeKeys.OWNER_VERTEX);
Directions direction = cq.condition(HugeKeys.DIRECTION);
Id label = cq.condition(HugeKeys.LABEL);
Id label = cq.uniqueConditionValue(HugeKeys.LABEL);

if (direction == null && conditionsSize > 1) {
for (Condition cond : cq.conditions()) {
Expand Down Expand Up @@ -316,7 +316,7 @@ private Iterator<HugeEdge> query(ConditionQuery query) {
if (dir == null) {
dir = Directions.BOTH;
}
Id label = query.condition(HugeKeys.LABEL);
Id label = query.uniqueConditionValue(HugeKeys.LABEL);
if (label == null) {
label = IdGenerator.ZERO;
}
Expand Down
Loading
Loading