imbajin commented on code in PR #833:
URL: 
https://github.com/apache/hugegraph-toolchain/pull/833#discussion_r4350132075


##########
hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/util/ReadOnlyGremlin.java:
##########
@@ -65,13 +143,183 @@ public static void validate(String query) {
         if (!"V".equals(start) && !"E".equals(start)) {
             throw invalid();
         }
-        parser.arguments(0);
-        parser.chain(STEPS, 0);
+        List<Step> steps = new ArrayList<>();
+        steps.add(new Step(start, parser.arguments(0), 0));
+        parser.chain(STEPS.keySet(), 0, steps);
         parser.consume(';');
         parser.whitespace();
         if (parser.position != query.length()) {
             throw invalid();
         }
+        return new Traversal(steps, query.length());
+    }
+
+    private enum Output {
+        PRESERVE, EXPAND, REDUCE, COLLECT, LIMIT, CONSUME
+    }
+
+    static final class Traversal {
+
+        private final List<Step> steps;
+        private final int endPosition;
+
+        private Traversal(List<Step> steps, int endPosition) {
+            this.steps = List.copyOf(steps);
+            this.endPosition = endPosition;
+        }
+
+        int limitPosition(boolean preview, int limit) {
+            // Preview limits include the sentinel row used to report 
truncation.
+            BigInteger previewSize = BigInteger.valueOf((long) limit - 1);
+            Analysis result = this.analyze(previewSize);
+            if (preview && result.largeCollection) {
+                throw new 
ExternalException("gremlin.preview.collection_requires_async", previewSize);
+            }
+            return !result.singleResult && (preview || result.bound == null) ? 
result.position : -1;
+        }
+
+        private Analysis analyze(BigInteger previewSize) {
+            BigInteger bound = null;
+            boolean singleResult = false;
+            boolean largeCollection = false;
+            boolean nestedCollection = false;
+            int position = this.endPosition;
+            for (int i = 0; i < this.steps.size(); i++) {
+                Step step = this.steps.get(i);
+                switch (STEPS.get(step.name)) {
+                    case CONSUME:
+                        // Cap the traversal before its final consumer, 
preserving
+                        // the List/Set/value/boolean result type. Further 
methods
+                        // would operate on materialized results, outside this 
grammar.
+                        if (i != this.steps.size() - 1) {
+                            return new Analysis(-1, bound, true, false);
+                        }
+                        position = step.dotPosition;
+                        if ("hasNext".equals(step.name)) {
+                            largeCollection = false;
+                            nestedCollection = false;
+                        }
+                        break;
+                    case EXPAND:
+                        bound = null;
+                        singleResult = false;
+                        largeCollection = false;
+                        break;
+                    case REDUCE:
+                        largeCollection = false;
+                        nestedCollection = false;
+                        if (!step.localScope()) {
+                            bound = BigInteger.ONE;
+                            singleResult = true;
+                        }
+                        break;
+                    case COLLECT:
+                        // The keyed group overloads accumulate a side effect
+                        // while passing each input traverser through.
+                        if (("group".equals(step.name) || 
"groupCount".equals(step.name)) &&
+                            !step.arguments.isEmpty()) {
+                            break;
+                        }
+                        if (!step.localScope()) {
+                            // A single List/Map can still download an entire
+                            // graph. A later outer limit cannot shrink its 
input.
+                            largeCollection |= bound == null || 
bound.compareTo(previewSize) > 0;
+                            // group() values are collections; unfolding its 
Map
+                            // returns entries that retain those complete 
values.
+                            nestedCollection |= "group".equals(step.name) && 
largeCollection;

Review Comment:
   P2: nested fold still bypasses the preview collection bound. At 
99ba42d216f38141b61c928625f0db7ed87ac11e, g.V().fold().fold().unfold().limit(1) 
is accepted: only group marks nestedCollection, and EXPAND/unfold clears 
largeCollection even though one nested collection remains. Two independent 
bounded probes compiled the exact parser and executed TinkerGraph3.8.1 fixtures 
of1001 and1500 vertices. Each returned one List containing all fixture 
vertices, even with the outer preview row limit; an input limit(10) control 
returned10 values. This is a supported-grammar result-size bypass, not proof of 
a production outage or a full Hubble deployment test. Track collection 
nesting/provenance across collection and expansion steps; add paired 
regressions for double-fold followed by one unfold, safe single-fold/unfold, 
and bounded double-unfold. The separate WITH LIMIT frontend repair is verified 
and should retain fix credit.



-- 
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