Skip to content

Commit 9c11778

Browse files
authored
fix(interactive): Limit Maximum Hops in Path Expand (#4451)
<!-- Thanks for your contribution! please review https://github.com/alibaba/GraphScope/blob/main/CONTRIBUTING.md before opening an issue. --> ## What do these changes do? 1. Limit the maximum hops in path expansion to prevent compilation errors and excessive consumption of computational resources during execution. 2. Handle invalid hops, i.e. `Match (a)-[e:4..3]-(b)`, which will throw exceptions containing `exceeds the maximum allowed iterations` at compiling phase. <!-- Please give a short brief about these changes. --> ## Related issue number <!-- Are there any issues opened that will be resolved by merging this change? --> Fixes
1 parent e54c98d commit 9c11778

File tree

8 files changed

+179
-25
lines changed

8 files changed

+179
-25
lines changed

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/config/YamlConfigs.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ public class YamlConfigs extends Configs {
207207
.put(
208208
"query.execution.timeout.ms",
209209
(Configs configs) -> configs.get("compiler.query_timeout"))
210+
.put(
211+
"query.execution.max.iterations",
212+
(Configs configs) -> configs.get("compiler.query_max_iterations"))
210213
.put("engine.type", (Configs configs) -> configs.get("compute_engine.type"))
211214
.put(
212215
"calcite.default.charset",

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/meta/glogue/PrimitiveCountEstimator.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.alibaba.graphscope.common.ir.rel.metadata.glogue.GlogueQuery;
2020
import com.alibaba.graphscope.common.ir.rel.metadata.glogue.pattern.*;
2121
import com.alibaba.graphscope.common.ir.rel.metadata.schema.EdgeTypeId;
22+
import com.alibaba.graphscope.common.ir.tools.QueryExecutionValidator;
2223

2324
import org.checkerframework.checker.nullness.qual.Nullable;
2425

@@ -91,27 +92,26 @@ public double estimate(PatternEdge edge, EdgeTypeId typeId) {
9192
minHop = range.getOffset();
9293
maxHop = range.getOffset() + range.getFetch() - 1;
9394
}
94-
double sum = 0.0d;
95-
for (int hop = minHop; hop <= maxHop; ++hop) {
96-
sum += estimate(edge, typeId, hop);
97-
}
98-
return sum;
99-
}
100-
101-
public double estimate(PatternEdge edge, EdgeTypeId typeId, int hops) {
10295
PatternVertex srcVertex = new SinglePatternVertex(typeId.getSrcLabelId(), 0);
10396
PatternVertex dstVertex = new SinglePatternVertex(typeId.getDstLabelId(), 1);
104-
if (hops == 0) {
105-
return estimate(srcVertex);
106-
}
10797
Pattern edgePattern = new Pattern();
10898
edgePattern.addVertex(srcVertex);
10999
edgePattern.addVertex(dstVertex);
110100
edgePattern.addEdge(
111101
srcVertex, dstVertex, new SinglePatternEdge(srcVertex, dstVertex, typeId, 0));
112102
edgePattern.reordering();
113-
return Math.pow(gq.getRowCount(edgePattern, false), hops)
114-
/ Math.pow(estimate(dstVertex), hops - 1);
103+
double edgeCount = gq.getRowCount(edgePattern, false);
104+
double dstVertexCount = estimate(dstVertex);
105+
double sum = 0.0d;
106+
int baseHop = Math.max(minHop, 1);
107+
double baseHopCount = Math.pow(edgeCount, baseHop) / Math.pow(dstVertexCount, baseHop - 1);
108+
for (int hop = baseHop, iters = 0;
109+
hop <= maxHop && iters < QueryExecutionValidator.SYSTEM_MAX_ITERATIONS;
110+
++hop, ++iters) {
111+
sum += baseHopCount;
112+
baseHopCount *= (edgeCount / dstVertexCount);
113+
}
114+
return sum;
115115
}
116116

117117
private @Nullable PatternVertex getIntersectVertex(Pattern pattern) {

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphBuilder.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.apache.calcite.rel.RelFieldCollation;
5454
import org.apache.calcite.rel.RelNode;
5555
import org.apache.calcite.rel.core.*;
56+
import org.apache.calcite.rel.logical.LogicalValues;
5657
import org.apache.calcite.rel.type.*;
5758
import org.apache.calcite.rex.*;
5859
import org.apache.calcite.sql.SqlAggFunction;
@@ -181,11 +182,13 @@ public GraphBuilder getV(GetVConfig config) {
181182
config.getAlias(),
182183
getAliasNameWithId(
183184
config.getStartAlias(),
184-
(RelDataType type) ->
185-
(type instanceof GraphSchemaType)
186-
&& ((GraphSchemaType) type).getScanOpt()
187-
== GraphOpt.Source.EDGE
188-
|| type instanceof GraphPathType));
185+
(RelDataType type) -> {
186+
if (input instanceof LogicalValues) return true;
187+
return (type instanceof GraphSchemaType)
188+
&& ((GraphSchemaType) type).getScanOpt()
189+
== GraphOpt.Source.EDGE
190+
|| type instanceof GraphPathType;
191+
}));
189192
replaceTop(getV);
190193
return this;
191194
}
@@ -225,10 +228,12 @@ public GraphBuilder pathExpand(PathExpandConfig pxdConfig) {
225228
pxdConfig.getAlias(),
226229
getAliasNameWithId(
227230
pxdConfig.getStartAlias(),
228-
(RelDataType type) ->
229-
(type instanceof GraphSchemaType)
230-
&& ((GraphSchemaType) type).getScanOpt()
231-
== GraphOpt.Source.VERTEX));
231+
(RelDataType type) -> {
232+
if (input instanceof LogicalValues) return true;
233+
return (type instanceof GraphSchemaType)
234+
&& ((GraphSchemaType) type).getScanOpt()
235+
== GraphOpt.Source.VERTEX;
236+
}));
232237
replaceTop(pathExpand);
233238
return this;
234239
}

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphPlanner.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
import org.slf4j.LoggerFactory;
5454
import org.yaml.snakeyaml.Yaml;
5555

56-
import java.io.*;
56+
import java.io.File;
57+
import java.io.FileOutputStream;
58+
import java.io.IOException;
5759
import java.net.URI;
5860
import java.nio.charset.StandardCharsets;
5961
import java.util.Map;
@@ -69,6 +71,7 @@ public class GraphPlanner {
6971
private final GraphRelOptimizer optimizer;
7072
private final RexBuilder rexBuilder;
7173
private final LogicalPlanFactory logicalPlanFactory;
74+
private final QueryExecutionValidator validator;
7275

7376
public static final Function<Configs, RexBuilder> rexBuilderFactory =
7477
(Configs configs) -> new GraphRexBuilder(new GraphTypeFactoryImpl(configs));
@@ -81,6 +84,7 @@ public GraphPlanner(
8184
this.optimizer = optimizer;
8285
this.logicalPlanFactory = logicalPlanFactory;
8386
this.rexBuilder = rexBuilderFactory.apply(graphConfig);
87+
this.validator = new QueryExecutionValidator(graphConfig);
8488
}
8589

8690
public PlannerInstance instance(String query, IrMeta irMeta) {
@@ -103,8 +107,8 @@ public PlannerInstance instance(
103107
GraphBuilder graphBuilder =
104108
GraphBuilder.create(
105109
graphConfig, optCluster, new GraphOptSchema(optCluster, schema));
106-
107110
LogicalPlan logicalPlan = logicalPlanFactory.create(graphBuilder, irMeta, query);
111+
this.validator.validate(logicalPlan, true);
108112
return new PlannerInstance(query, logicalPlan, graphBuilder, irMeta, queryLogger);
109113
}
110114

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2020 Alibaba Group Holding Limited.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.graphscope.common.ir.tools;
18+
19+
import com.alibaba.graphscope.common.config.Config;
20+
import com.alibaba.graphscope.common.config.Configs;
21+
import com.alibaba.graphscope.common.exception.FrontendException;
22+
import com.alibaba.graphscope.common.ir.meta.schema.CommonOptTable;
23+
import com.alibaba.graphscope.common.ir.rel.CommonTableScan;
24+
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalExpand;
25+
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalPathExpand;
26+
import com.alibaba.graphscope.common.ir.rel.graph.GraphPhysicalExpand;
27+
import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalMultiMatch;
28+
import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalSingleMatch;
29+
import com.alibaba.graphscope.proto.frontend.Code;
30+
import com.google.common.collect.Lists;
31+
32+
import org.apache.calcite.rel.RelNode;
33+
import org.apache.calcite.rex.RexLiteral;
34+
35+
import java.util.List;
36+
37+
public class QueryExecutionValidator {
38+
public static final int SYSTEM_MAX_ITERATIONS = 15;
39+
40+
private static final Config<Integer> CONFIG_MAX_ITERATIONS =
41+
Config.intConfig("query.execution.max.iterations", 15);
42+
43+
private final int maxIterations;
44+
45+
public QueryExecutionValidator(Configs configs) {
46+
int maxIterations = CONFIG_MAX_ITERATIONS.get(configs);
47+
if (maxIterations > SYSTEM_MAX_ITERATIONS) {
48+
throw new FrontendException(
49+
Code.LOGICAL_PLAN_BUILD_FAILED,
50+
"max iterations "
51+
+ maxIterations
52+
+ " exceeds the system limit "
53+
+ SYSTEM_MAX_ITERATIONS);
54+
}
55+
this.maxIterations = maxIterations;
56+
}
57+
58+
public boolean validate(LogicalPlan plan, boolean throwsOnFail) {
59+
if (plan.getRegularQuery() == null || plan.isReturnEmpty()) return true;
60+
int hops = 0;
61+
List<RelNode> queue = Lists.newArrayList(plan.getRegularQuery());
62+
while (!queue.isEmpty()) {
63+
RelNode top = queue.remove(0);
64+
if (top instanceof GraphLogicalExpand) {
65+
++hops;
66+
} else if (top instanceof GraphPhysicalExpand) {
67+
++hops;
68+
} else if (top instanceof GraphLogicalPathExpand) {
69+
GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) top;
70+
// null means no limit
71+
if (pxd.getFetch() == null) {
72+
if (throwsOnFail) {
73+
throw new FrontendException(
74+
Code.LOGICAL_PLAN_BUILD_FAILED,
75+
"path expand with no upper bound exceeds the maximum allowed"
76+
+ " iterations "
77+
+ maxIterations);
78+
}
79+
return false;
80+
}
81+
int lower =
82+
(pxd.getOffset() == null)
83+
? 0
84+
: ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
85+
hops +=
86+
(lower
87+
+ ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
88+
- 1);
89+
} else if (top instanceof GraphLogicalSingleMatch) {
90+
validate(
91+
new LogicalPlan(((GraphLogicalSingleMatch) top).getSentence()),
92+
throwsOnFail);
93+
} else if (top instanceof GraphLogicalMultiMatch) {
94+
for (RelNode sentence : ((GraphLogicalMultiMatch) top).getSentences()) {
95+
validate(new LogicalPlan(sentence), throwsOnFail);
96+
}
97+
} else if (top instanceof CommonTableScan) {
98+
CommonOptTable optTable = (CommonOptTable) top.getTable();
99+
validate(new LogicalPlan(optTable.getCommon()), throwsOnFail);
100+
}
101+
queue.addAll(top.getInputs());
102+
}
103+
if (hops <= maxIterations) return true;
104+
if (throwsOnFail) {
105+
throw new FrontendException(
106+
Code.LOGICAL_PLAN_BUILD_FAILED,
107+
"query hops "
108+
+ hops
109+
+ " exceeds the maximum allowed iterations "
110+
+ maxIterations);
111+
}
112+
return false;
113+
}
114+
}

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeInference.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.alibaba.graphscope.common.ir.rel.type.TableConfig;
2222
import com.alibaba.graphscope.common.ir.tools.AliasInference;
2323
import com.alibaba.graphscope.common.ir.tools.GraphBuilder;
24+
import com.alibaba.graphscope.common.ir.tools.LogicalPlan;
25+
import com.alibaba.graphscope.common.ir.tools.QueryExecutionValidator;
2426
import com.alibaba.graphscope.common.ir.tools.config.GraphOpt;
2527
import com.google.common.base.Preconditions;
2628
import com.google.common.collect.ImmutableList;
@@ -58,6 +60,7 @@ public GraphTypeInference(GraphBuilder builder) {
5860
* @return
5961
*/
6062
public RelNode inferTypes(RelNode top) {
63+
if (new LogicalPlan(top).isReturnEmpty()) return top;
6164
return visitRels(ImmutableList.of(top)).get(0);
6265
}
6366

@@ -847,6 +850,9 @@ public GraphPathTypeInference(
847850
}
848851

849852
public GraphPathType inferPathType() {
853+
if (this.maxHop > QueryExecutionValidator.SYSTEM_MAX_ITERATIONS) {
854+
return this.pxdType;
855+
}
850856
recursive(startVType, new CompositePathType(Lists.newArrayList()), 0);
851857
List<GraphLabelType.Entry> expandTypes = Lists.newArrayList();
852858
List<GraphLabelType.Entry> getVTypes = Lists.newArrayList();

interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/integration/suite/standard/IrGremlinQueryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,7 @@ public Traversal<Vertex, Long> get_g_VX2X_both_with_trail_allve_count() {
16331633
@Override
16341634
public Traversal<Vertex, Object> get_g_V_path_expand_until_age_gt_30_values_age() {
16351635
return ((IrCustomizedTraversal)
1636-
g.V().out("1..100", "knows")
1636+
g.V().out("1..15", "knows") // system maximum hops is 15
16371637
.with(
16381638
"UNTIL",
16391639
com.alibaba.graphscope.gremlin.integration.suite.utils

interactive_engine/compiler/src/test/java/com/alibaba/graphscope/sdk/JNICompilePlanTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.alibaba.graphscope.sdk;
2020

2121
import org.apache.commons.io.FileUtils;
22+
import org.junit.Assert;
2223
import org.junit.BeforeClass;
2324
import org.junit.Test;
2425

@@ -50,4 +51,25 @@ public void path_expand_test() throws Exception {
5051
+ " src.__entity_id__ AS sId, dest.__entity_id__ AS dId;";
5152
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
5253
}
54+
55+
@Test
56+
public void path_expand_max_hop_test() throws Exception {
57+
try {
58+
String query = "MATCH (src)-[e:test6*1..1000000]->(dest) Return src, dest";
59+
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
60+
} catch (Exception e) {
61+
Assert.assertTrue(e.getMessage().contains("exceeds the maximum allowed iterations"));
62+
}
63+
}
64+
65+
@Test
66+
public void path_expand_invalid_hop_test() throws Exception {
67+
try {
68+
// the max hop will be set as unlimited if it is less than min hop
69+
String query = "MATCH (src)-[e:test6*5..4]->(dest) Return src, dest";
70+
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
71+
} catch (Exception e) {
72+
Assert.assertTrue(e.getMessage().contains("exceeds the maximum allowed iterations"));
73+
}
74+
}
5375
}

0 commit comments

Comments
 (0)