bitflicker64 commented on code in PR #2994:
URL: https://github.com/apache/hugegraph/pull/2994#discussion_r4022110046


##########
hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphIndexTransaction.java:
##########
@@ -415,8 +417,11 @@ private IdHolderList queryByLabel(ConditionQuery query) {
         HugeType queryType = query.resultType();
         IndexLabel il = IndexLabel.label(queryType);
         validateIndexLabel(il);
-        Id label = query.condition(HugeKeys.LABEL);
-        assert label != null;
+        // Query-by-label builds a label index entry and requires one
+        // deterministically resolved label instead of best-effort fallback.
+        Id label = query.conditionValue(HugeKeys.LABEL);
+        E.checkState(label != null, "Expect one label value for query: %s",

Review Comment:
   ๐Ÿงน This defensive check is unreachable and re-resolves a label the caller 
already resolved.
   
   Evidence: `queryByLabel()` has a single call site (`git grep -n 
"queryByLabel(" 459a2b2 -- '*/main/java/*'` โ†’ only line 408), and that site is 
guarded by `Id label = query.singleConditionValueOrNull(HugeKeys.LABEL); if 
(query.allSysprop() && conds.size() == 1 && label != null)`. Because 
`singleConditionValueOrNull()` already returned exactly one value, 
`conditionValue()` on line 422 repeats the whole `collectConditionValues` + 
`resolveConditionValues` pass and can never return `null` nor hit its 
multi-value `E.checkState`, so the `E.checkState(label != null, ...)` on this 
line is dead.
   
   Requested change: pass the already-resolved id in (`queryByLabel(query, 
label)`) and drop the duplicate resolution, or keep the resolution here and 
drop it from `queryIndex()` โ€” but not both.



##########
hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java:
##########
@@ -645,14 +674,533 @@ private static boolean 
extractHasContainers(HugeVertexStep<?> newStep,
 
     private static boolean canExtractHasContainers(HugeGraph graph,
                                                    HasContainerHolder holder) {
-        for (HasContainer has : holder.getHasContainers()) {
+        // Keep unsafe labels and their sibling properties for local filtering.
+        if (hasUnsafeLabelPredicate(holder)) {
+            return false;
+        }
+        List<HasContainer> hasContainers = holder.getHasContainers();
+        for (HasContainer has : hasContainers) {
             if (!canExtractHasContainer(graph, has)) {
                 return false;
             }
         }
         return true;
     }
 
+    private static void prepareLocalHasContainers(
+            Step<?, ?> source, Traversal.Admin<?, ?> traversal) {
+        QueryHolder query = (QueryHolder) source;
+        Step<?, ?> step = source.getNextStep();
+        while (step instanceof HasStep || step instanceof NoOpBarrierStep) {
+            Step<?, ?> next = step.getNextStep();
+            if (step instanceof HasStep) {
+                HasContainerHolder holder = (HasContainerHolder) step;
+                for (HasContainer has : new 
ArrayList<>(holder.getHasContainers())) {
+                    // Paging is query metadata, never an element property 
filter.
+                    if (QueryHolder.SYSPROP_PAGE.equals(has.getKey())) {
+                        query.addHasContainer(has);
+                        holder.removeHasContainer(has);
+                        continue;
+                    }
+                    if (T.id.getAccessor().equals(has.getKey())) {
+                        // ID lookup is complete across labels. Existing source
+                        // IDs and unsupported predicates still filter locally.
+                        if (source instanceof HugeGraphStep &&
+                            GraphStep.processHasContainerIds((HugeGraphStep<?, 
?>) source, has)) {
+                            holder.removeHasContainer(has);
+                            continue;
+                        }
+                        holder.removeHasContainer(has);
+                        holder.addHasContainer(new 
LocalIdHasContainer(has.getPredicate()));
+                        continue;
+                    }
+                    if (T.label.getAccessor().equals(has.getKey())) {
+                        holder.removeHasContainer(has);
+                        HasContainer label = new 
LocalLabelHasContainer(has.getPredicate());
+                        if (source instanceof HugeGraphStep &&
+                            canPushPositiveLabel((HugeGraphStep<?, ?>) source, 
has)) {
+                            // A positive label conjunct uses the label index,
+                            // independent of per-label property index 
coverage.
+                            // Keep the runtime label matcher for source-ID 
queries.
+                            query.addHasContainer(label);
+                        } else {
+                            // Keep unsupported candidates and adjacent-vertex
+                            // labels local, including their paging boundary.
+                            holder.addHasContainer(label);
+                        }
+                        continue;
+                    }
+                    if (keyForContainsKey(has.getKey()) || 
keyForContainsValue(has.getKey())) {
+                        // Backend CONTAINS support varies and source IDs use
+                        // local filtering too. Preserve HugeGraph's map query
+                        // semantics without treating "key"/"value" as names.
+                        holder.removeHasContainer(has);
+                        traversal.addStep(traversal.getSteps().indexOf(step),
+                                          new LocalContainsStep<>(traversal, 
has));
+                        continue;
+                    }
+                    List<P<Object>> predicates = new ArrayList<>();
+                    collectPredicates(predicates, 
ImmutableList.of(has.getPredicate()));
+                    if (predicates.stream().anyMatch(p ->
+                            p.getBiPredicate() == 
Condition.RelationType.TEXT_CONTAINS)) {
+                        // Child traversals can be unbound during optimization;
+                        // the matcher resolves the graph from each runtime 
element.
+                        holder.removeHasContainer(has);
+                        holder.addHasContainer(new LocalSearchHasContainer(
+                                has.getKey(), has.getPredicate()));
+                    }
+                }
+                if (holder.getHasContainers().isEmpty()) {
+                    TraversalHelper.copyLabels(step, step.getPreviousStep(), 
false);
+                    traversal.removeStep(step);
+                }
+            }
+            step = next;
+        }
+        if (query.queryInfo().paging() && step instanceof RangeGlobalStep) {
+            // Bound the raw backend page even when local filters prevent 
normal
+            // range extraction. Keep the range step to apply offset/limit 
after
+            // those filters; a filtered page may contain fewer results.
+            query.setRange(0, ((RangeGlobalStep<?>) step).getHighRange());
+        }
+    }
+
+    private static boolean canPushPositiveLabel(HugeGraphStep<?, ?> source,
+                                                HasContainer has) {
+        if (!isEqInLabelPredicate(has)) {
+            return false;
+        }
+        HugeGraph graph = tryGetGraph(source);
+        if (graph == null) {
+            return false;
+        }
+        List<P<Object>> predicates = new ArrayList<>();
+        collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
+        for (P<Object> predicate : predicates) {
+            Object value = predicate.getValue();
+            BiPredicate<?, ?> bp = predicate.getBiPredicate();
+            if (bp == Contains.within && !(value instanceof Collection)) {
+                return false;
+            }
+            Collection<?> values = bp == Contains.within ?
+                                   (Collection<?>) value : 
Collections.singletonList(value);
+            for (Object candidate : values) {
+                if (!hasLabelIndex(graph, source.returnsVertex(), candidate)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static boolean hasLabelIndex(HugeGraph graph, boolean vertex, 
Object value) {
+        if (value instanceof Number) {
+            value = IdGenerator.of(((Number) value).longValue());
+        }
+        try {
+            SchemaLabel label;
+            if (value instanceof Id) {
+                Id id = (Id) value;
+                // Nonpositive IDs include internal schema objects that aren't
+                // user labels. Preserve their local matching behavior.
+                if (!id.number() || id.asLong() <= 0L) {
+                    return false;
+                }
+                label = vertex ? graph.vertexLabel(id) : graph.edgeLabel(id);
+            } else if (value instanceof String) {
+                label = vertex ? graph.vertexLabel((String) value) : 
graph.edgeLabel((String) value);
+            } else {
+                return false;
+            }
+            // Do not turn a working local filter into a missing-label/index 
error.
+            return label != null && label.enableLabelIndex();
+        } catch (IllegalArgumentException | NotFoundException e) {
+            return false;
+        }
+    }
+
+    private static P<?> localIdPredicate(P<?> predicate) {
+        // Keep IDs local to preserve source-ID intersections and step 
ordering.
+        // UUID/Element values need the same representation as 
HugeElement.id(). Leave
+        // strings unchanged so HasContainer retains its string-ID comparison.
+        P<?> copy = predicate.clone();
+        List<P<Object>> leaves = new ArrayList<>();
+        collectPredicates(leaves, ImmutableList.of(copy));
+        for (P<Object> leaf : leaves) {
+            Object value = leaf.getValue();
+            if (value instanceof Collection) {
+                List<Object> values = new ArrayList<>();
+                for (Object item : (Collection<?>) value) {
+                    values.add(localIdValue(item));
+                }
+                leaf.setValue(values);
+            } else {
+                leaf.setValue(localIdValue(value));
+            }
+        }
+        return copy;
+    }
+
+    private static Object localIdValue(Object value) {
+        if (value instanceof UUID || value instanceof Element) {
+            return HugeElement.getIdValue(HugeType.VERTEX, value);
+        }
+        return value;
+    }
+
+    private static final class LocalIdHasContainer extends HasContainer {
+
+        private static final long serialVersionUID = 1L;
+
+        private LocalIdHasContainer(P<?> predicate) {
+            super(T.id.getAccessor(), localIdPredicate(predicate));
+        }
+
+        @Override
+        protected boolean testId(Element element) {
+            return testLocalIdPredicate(this.getPredicate(), element.id());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean testLocalIdPredicate(P<?> predicate, Object id) {
+        if (predicate instanceof ConnectiveP) {
+            boolean and = predicate instanceof AndP;
+            for (P<?> child : ((ConnectiveP<?>) predicate).getPredicates()) {
+                if (testLocalIdPredicate(child, id) != and) {
+                    return !and;
+                }
+            }
+            return and;
+        }
+        Object value = predicate.getValue();
+        Object first = value;
+        if (value instanceof Collection) {
+            Collection<?> values = (Collection<?>) value;
+            first = values.isEmpty() ? null : values.iterator().next();
+        }
+        // HasContainer decides string-ID comparison from only the top-level
+        // P value. ConnectiveP has no such value; decide separately per leaf.
+        Object actual = first instanceof String ? id.toString() : id;
+        return ((BiPredicate<Object, Object>) 
predicate.getBiPredicate()).test(actual, value);
+    }
+
+    private static final class LocalLabelHasContainer extends HasContainer {
+
+        private static final long serialVersionUID = 1L;
+
+        private LocalLabelHasContainer(P<?> predicate) {
+            super(T.label.getAccessor(), predicate.clone());
+        }
+
+        @Override
+        protected boolean testLabel(Element element) {
+            // Resolve against the actual element, not a graph captured while
+            // strategies run. Child traversals may be unbound or later cloned.
+            return testLabelPredicate(this.getPredicate(), ((HugeElement) 
element).schemaLabel());
+        }
+    }
+
+    private static final class LocalContainsStep<S extends Element> extends 
HasStep<S> {
+
+        private static final long serialVersionUID = 1L;
+
+        private LocalContainsStep(Traversal.Admin<?, ?> traversal, 
HasContainer has) {
+            super(traversal, has.clone());
+            E.checkArgument(has.getPredicate().getBiPredicate() == Compare.eq,
+                            "CONTAINS query with relation '%s' is not 
supported",
+                            has.getPredicate().getBiPredicate());
+        }
+
+        @Override
+        protected boolean filter(Traverser.Admin<S> traverser) {
+            HugeElement element = (HugeElement) traverser.get();
+            // Adjacent vertices can be ID/label-only shells. Like 
properties(),
+            // load their properties before evaluating the system property map.
+            element.getFilledProperties();
+            // Keep a HasStep boundary so count/range cannot bypass this 
filter.
+            // Resolve schema from the runtime element; clone/reset must not
+            // retain a graph or transaction captured during optimization.
+            for (HasContainer has : this.getHasContainers()) {
+                boolean matches = keyForContainsKey(has.getKey()) || 
keyForContainsValue(has.getKey()) ?
+                                  convContains2Relation(element.graph(), 
has).test(element) :
+                                  has.test(element);
+                if (!matches) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean testLabelPredicate(P<?> predicate, SchemaLabel 
label) {
+        if (predicate instanceof ConnectiveP) {
+            boolean and = predicate instanceof AndP;
+            for (P<?> child : ((ConnectiveP<?>) predicate).getPredicates()) {
+                if (testLabelPredicate(child, label) != and) {
+                    return !and;
+                }
+            }
+            return and;
+        }
+        BiPredicate<?, ?> bp = predicate.getBiPredicate();
+        Object value = predicate.getValue();
+        if (bp == Contains.within || bp == Contains.without) {
+            for (Object item : (Collection<?>) value) {
+                if (testLabelValue(Compare.eq, label, item)) {
+                    return bp == Contains.within;
+                }
+            }
+            return bp == Contains.without;
+        }
+        return testLabelValue((BiPredicate<Object, Object>) bp, label, value);
+    }
+
+    private static boolean testLabelValue(BiPredicate<Object, Object> 
predicate,
+                                          SchemaLabel label, Object value) {
+        if (value instanceof Number) {
+            value = IdGenerator.of(((Number) value).longValue());
+        }
+        return predicate.test(value instanceof Id ? label.id() : label.name(), 
value);
+    }
+
+    private static final class LocalSearchHasContainer extends HasContainer {
+
+        private static final long serialVersionUID = 1L;
+
+        private transient HugeGraph matcherGraph;
+        private transient P<?> matcherPredicate;
+        private transient Predicate<Object> matcher;
+
+        private LocalSearchHasContainer(String key, P<?> predicate) {
+            super(key, predicate.clone());
+        }
+
+        @Override
+        protected boolean testValue(Property property) {
+            // Keep the public P tree intact for strategies, hashing and Java
+            // serialization. Resolve the analyzer from the element's graph,
+            // including after cloning, deserialization or graph rebinding.
+            HugeGraph graph = (HugeGraph) property.element().graph();
+            if (this.matcherGraph != graph ||
+                !samePredicateValues(this.getPredicate(), 
this.matcherPredicate)) {
+                P<?> predicate = this.getPredicate().clone();
+                this.matcher = localSearchMatcher(predicate, graph);
+                this.matcherPredicate = predicate;
+                this.matcherGraph = graph;
+            }
+            return this.matcher.test(property.value());
+        }
+
+        @Override
+        public LocalSearchHasContainer clone() {
+            LocalSearchHasContainer clone = (LocalSearchHasContainer) 
super.clone();
+            clone.matcherGraph = null;
+            clone.matcherPredicate = null;
+            clone.matcher = null;
+            return clone;
+        }
+    }
+
+    private static boolean samePredicateValues(P<?> current, P<?> cached) {
+        // P.equals() compares originalValue, not the value changed by 
setValue().
+        if (cached == null || current.getClass() != cached.getClass()) {
+            return false;
+        }
+        if (current instanceof ConnectiveP) {
+            List<? extends P<?>> children = ((ConnectiveP<?>) 
current).getPredicates();
+            List<? extends P<?>> oldChildren = ((ConnectiveP<?>) 
cached).getPredicates();
+            if (children.size() != oldChildren.size()) {
+                return false;
+            }
+            for (int i = 0; i < children.size(); i++) {
+                if (!samePredicateValues(children.get(i), oldChildren.get(i))) 
{
+                    return false;
+                }
+            }
+            return true;
+        }
+        return current.getBiPredicate().equals(cached.getBiPredicate()) &&
+               Objects.equals(current.getValue(), cached.getValue());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Predicate<Object> localSearchMatcher(P<?> predicate, 
HugeGraph graph) {
+        if (predicate instanceof ConnectiveP) {
+            List<Predicate<Object>> children = new ArrayList<>();
+            for (P<?> child : ((ConnectiveP<?>) predicate).getPredicates()) {
+                children.add(localSearchMatcher(child, graph));
+            }
+            boolean and = predicate instanceof AndP;
+            return value -> {
+                for (Predicate<Object> child : children) {
+                    if (child.test(value) != and) {
+                        return !and;
+                    }
+                }
+                return and;
+            };
+        }
+        if (predicate.getBiPredicate() != 
Condition.RelationType.TEXT_CONTAINS) {
+            return (P<Object>) predicate;
+        }
+        // Match SEARCH terms in place, preserving range and side-effect 
ordering.
+        return graph.searchPredicate((String) predicate.getValue());
+    }
+
+    private static boolean hasUnsafeLabelInTraversal(
+            Traversal.Admin<?, ?> traversal, Step<?, ?> sourceStep) {
+        // Partial pushdown can lose candidates before local label filtering.
+        // Scan the remaining traversal, its children and each ancestor's
+        // remaining steps for filters on child output. Arbitrary extension
+        // steps don't reliably expose element identity, so stay conservative.
+        // FIXME(#3201): Restore selective pushdown when every candidate 
schema label
+        // has compatible index coverage for extracted property predicates.
+        // Outside the proven root suffix below, negative labels can disable
+        // property pushdown even across element changes (including ancestors
+        // and unknown extension steps), potentially requiring a full scan.
+        List<Step> steps = traversal.getSteps();
+        int start = 0;
+        while (start < steps.size() && steps.get(start) != sourceStep) {
+            start++;
+        }
+        start++;
+        for (int i = start; i < steps.size(); i++) {
+            Step<?, ?> step = steps.get(i);
+            if (changesCurrentElement(step) &&
+                traversal.getParent() instanceof EmptyStep &&
+                onlyCurrentElementSuffix(steps.subList(i, steps.size()))) {
+                return false;
+            }
+            if (step instanceof HasStep) {
+                HasContainerHolder holder = (HasContainerHolder) step;
+                if (hasUnsafeLabelPredicate(holder)) {
+                    return true;
+                }
+            }
+            if (hasUnsafeLabelInChildren(step)) {
+                return true;
+            }
+        }
+        TraversalParent parent = traversal.getParent();
+        if (parent instanceof Step && !(parent instanceof EmptyStep)) {
+            Step<?, ?> parentStep = (Step<?, ?>) parent;
+            // RepeatStep's until/emit siblings can filter this child's output.
+            // This helper only descends, so revisiting the owning step's 
children
+            // cannot recurse back into this ancestor walk.
+            if (hasUnsafeLabelInChildren(parentStep)) {
+                return true;
+            }
+            return hasUnsafeLabelInTraversal(parentStep.getTraversal(), 
parentStep);
+        }
+        return false;
+    }
+
+    private static boolean changesCurrentElement(Step<?, ?> step) {
+        return step instanceof VertexStep || step instanceof EdgeVertexStep ||
+               step instanceof PropertiesStep;
+    }
+
+    private static boolean onlyCurrentElementSuffix(List<Step> steps) {
+        for (Step<?, ?> step : steps) {
+            // An allowlist is deliberate: select/path, lambdas, repeat and
+            // extension steps may recover earlier elements. Never infer their
+            // provenance from the output type alone.
+            if (!(changesCurrentElement(step) || step instanceof HasStep ||
+                  step instanceof NoOpBarrierStep || step instanceof 
RangeGlobalStep ||
+                  step instanceof IdentityStep || step instanceof NotStep ||
+                  step instanceof AndStep || step instanceof OrStep ||
+                  step instanceof TraversalFilterStep ||
+                  step instanceof IdStep || step instanceof LabelStep ||
+                  step instanceof PropertyKeyStep || step instanceof 
PropertyValueStep ||
+                  step instanceof CountGlobalStep || step instanceof 
SumGlobalStep ||
+                  step instanceof MinGlobalStep || step instanceof 
MaxGlobalStep ||
+                  step instanceof MeanGlobalStep)) {
+                return false;
+            }
+            if (step instanceof TraversalParent) {
+                TraversalParent parent = (TraversalParent) step;
+                for (Traversal.Admin<?, ?> child : parent.getLocalChildren()) {
+                    if (!onlyCurrentElementSuffix(child.getSteps())) {
+                        return false;
+                    }
+                }
+                for (Traversal.Admin<?, ?> child : parent.getGlobalChildren()) 
{
+                    if (!onlyCurrentElementSuffix(child.getSteps())) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private static boolean hasUnsafeLabelInChildren(Step<?, ?> step) {
+        return hasUnsafeLabelInChildren(step, false);
+    }
+
+    private static boolean hasUnsafeLabelInChildren(Step<?, ?> step, boolean 
negated) {
+        if (!(step instanceof TraversalParent)) {
+            return false;
+        }
+        TraversalParent parent = (TraversalParent) step;
+        // Even an EQ label under not() describes a complement, whose property
+        // index coverage is unknown. Stay conservative for nested negations 
too.
+        negated |= step instanceof NotStep;
+        for (Traversal.Admin<?, ?> child : parent.getLocalChildren()) {
+            if (hasUnsafeLabelInChildTraversal(child, negated)) {
+                return true;
+            }
+        }
+        for (Traversal.Admin<?, ?> child : parent.getGlobalChildren()) {
+            if (hasUnsafeLabelInChildTraversal(child, negated)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasUnsafeLabelInChildTraversal(
+            Traversal.Admin<?, ?> traversal, boolean negated) {
+        for (Step<?, ?> childStep : traversal.getSteps()) {
+            if (childStep instanceof HasStep &&
+                hasUnsafeLabelPredicate((HasContainerHolder) childStep, 
negated)) {

Review Comment:
   โš ๏ธ A negative label inside a child traversal that filters a *different* 
element still disables all property pushdown at the source.
   
   Evidence: `hasUnsafeLabelInTraversal()` deliberately relaxes the check for 
the top-level suffix โ€” when it reaches a `changesCurrentElement()` step whose 
suffix is `onlyCurrentElementSuffix(...)`, it returns `false` so 
`g.V().has("city","X").out().hasLabel(P.neq("author"))` keeps the `city` index. 
`hasUnsafeLabelInChildTraversal()` (this method) applies no such relaxation: it 
walks every step of every local/global child and returns `true` on the first 
unsafe label container, regardless of whether that child has already moved to 
an adjacent element.
   
   So `g.V().has("city","Beijing").where(__.out().hasLabel(P.neq("author")))` 
is classified unsafe: the child is a `TraversalFilterStep` whose steps are 
`[VertexStep(OUT), HasStep(~label.neq(author))]`, `hasUnsafeLabelInChildren()` 
descends into it and `hasUnsafeLabelPredicate()` fires. `extractHasContainer()` 
then routes to `prepareLocalHasContainers()`, which pushes nothing but `~page`, 
resolvable `T.id` and index-backed positive labels, so 
`HugeGraphStep.makeQuery()` falls to `new Query(HugeType.VERTEX)` 
(`hasContainers.isEmpty()`) โ€” a full vertex scan โ€” even though the negative 
label never applies to the source vertices and `city` is fully indexed. The 
same shape via `and(...)`/`or(...)`/`not(...)` children behaves identically.
   
   Requested change: apply the same `changesCurrentElement()` / 
`onlyCurrentElementSuffix()` reasoning inside 
`hasUnsafeLabelInChildTraversal()` (stop treating a child as unsafe once it has 
moved off the source element), or document and test that any label predicate 
anywhere in a `where()`/`and()`/`or()` child intentionally forces a full scan 
of the source. Please add a regression asserting the chosen behaviour for 
`g.V().has(<indexed prop>, v).where(__.out().hasLabel(P.neq(...)))`.



##########
hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/ram/RamTable.java:
##########
@@ -316,7 +328,7 @@ private Iterator<HugeEdge> query(ConditionQuery query) {
         if (dir == null) {
             dir = Directions.BOTH;
         }
-        Id label = query.condition(HugeKeys.LABEL);
+        Id label = query.singleConditionValueOrNull(HugeKeys.LABEL);

Review Comment:
   ๐Ÿงน The wildcard fallback here can no longer distinguish "no label condition" 
from "label condition that did not resolve".
   
   Evidence: `singleConditionValueOrNull()` returns `null` for three different 
states โ€” no top-level EQ/IN LABEL relation, an EQ/IN set that intersects to 
empty, and a set with more than one value โ€” and all three fall through to 
`label = IdGenerator.ZERO`, which `EdgeRangeIterator` treats as "match every 
label". Today that is unreachable only because `matched()` (line 275) rejects 
empty/unresolvable label sets and `ConditionQueryFlatten.flatten()` always 
expands a LABEL `IN` into EQ branches (`RelationType.UNFLATTEN_TYPES` contains 
`IN`, so `isFlattened()` is false for such a query). The guard in 
`query(Query)` is `assert this.matched(query)`, which is disabled in 
production, so if that invariant ever changes the RamTable silently returns 
edges of every label instead of failing.
   
   Requested change: this PR adds exactly the API needed to close the gap โ€” 
reserve `IdGenerator.ZERO` for `!query.containsConditionValues(HugeKeys.LABEL)` 
and throw (or return an empty iterator) when a LABEL EQ/IN condition exists but 
does not resolve to a single value.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to