Skip to content

Commit 19d728e

Browse files
zhanglei1949shirly121liulx20
authored
feat(interactive): Implement a c++ wrapper for compiler's GraphPlanner (#4310)
- Implement a JNI-based c++ Wrapper for compiler's `GraphPlanner`, which is used to generate physical plan from a cypher query string. - Some minor fix for compiler's code. Fix #4306 Usage: ```c++ gs::jni::GraphPlannerWrapper planner(java_path, java_library_path, graph_schema_path); if (!planner.is_valid()) { LOG(ERROR) << "Invalid GraphPlannerWrapper."; return 1; } auto plan = planner.CompilePlan(compiler_config_path, cypher_query) ``` The `CompilePlan` are exposed by two ways: JNI or REST API, see https://github.com/zhanglei1949/GraphScope/blob/call-compiler-via-jni/docs/interactive_engine/graph_planner.md for more usages. --------- Co-authored-by: shirly121 <[email protected]> Co-authored-by: liulx20 <[email protected]>
1 parent f577fae commit 19d728e

File tree

33 files changed

+3049
-38
lines changed

33 files changed

+3049
-38
lines changed

.github/workflows/interactive.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,28 @@ jobs:
314314
cd ${GITHUB_WORKSPACE}/interactive_engine/
315315
mvn clean install -Pexperimental -DskipTests -q
316316
317+
- name: Test physical plan generation
318+
run: |
319+
cd ${GITHUB_WORKSPACE}/interactive_engine
320+
cat > /tmp/physical_plan_gen_config.yaml <<EOF
321+
compiler:
322+
planner:
323+
is_on: true
324+
opt: CBO
325+
rules:
326+
- FilterIntoJoinRule
327+
- FilterMatchRule
328+
- NotMatchToAntiJoinRule
329+
physical.opt.config: proto
330+
EOF
331+
echo " meta.reader.schema.uri: ${GITHUB_WORKSPACE}/flex/interactive/examples/modern_graph/graph.yaml" >> /tmp/physical_plan_gen_config.yaml
332+
echo " meta.reader.statistics.uri: ${GITHUB_WORKSPACE}/interactive_engine/compiler/src/test/resources/statistics/modern_statistics.json" >> /tmp/physical_plan_gen_config.yaml
333+
mvn clean install -DskipTests -Pgraph-planner-jni
334+
INTERACTIVE_ENGINE_HOME=${GITHUB_WORKSPACE}/interactive_engine
335+
./target/native/test_graph_planner ${INTERACTIVE_ENGINE_HOME}/compiler/target/compiler-0.0.1-SNAPSHOT.jar:${INTERACTIVE_ENGINE_HOME}/compiler/target/libs/ ${INTERACTIVE_ENGINE_HOME}/executor/ir/target/release/libir_core.so \
336+
${GITHUB_WORKSPACE}/flex/interactive/examples/modern_graph/graph.yaml ${INTERACTIVE_ENGINE_HOME}/compiler/src/test/resources/statistics/modern_statistics.json \
337+
"MATCH(n) return count(n);" /tmp/physical_plan_gen_config.yaml
338+
317339
- name: Run End-to-End cypher adhoc ldbc query test
318340
env:
319341
GS_TEST_DIR: ${{ github.workspace }}/gstest

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ and the vineyard store that offers efficient in-memory data transfers.
7474
interactive_engine/tinkerpop_eco
7575
interactive_engine/neo4j_eco
7676
interactive_engine/gopt
77+
interactive_engine/graph_planner
7778
interactive_engine/benchmark_tool
7879
.. interactive_engine/guide_and_examples
7980
interactive_engine/design_of_gie
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Graph Planner Interface by JNI and RESTful API
2+
3+
`GraphPlanner` is the primary entry point for GOpt’s query optimization and physical plan generation. Originally, it was tightly integrated into the Frontend service, where it optimized Cypher queries received via the Bolt protocol and generated execution plans for various backend engines.
4+
5+
To enhance its flexibility and ease of integration, `GraphPlanner` is now available as a standalone module, free from any dependencies on other Frontend modules. It supports both JNI and RESTful API interfaces, enabling lightweight and straightforward integration into diverse systems. Whether you are working on a native application or web-based services, `GraphPlanner` can seamlessly integrate into your architecture, providing efficient query optimization and physical plan generation across a wide range of use cases.
6+
7+
8+
## JNI API
9+
10+
### Interface Overview
11+
12+
We provide a c++ wrapper implementation `GraphPlannerWrapper` for the JNI interface. Here is a brief explanation of the logical interface provided by the `c++` class.
13+
14+
Constructor:
15+
16+
```cpp
17+
/**
18+
* @brief Constructs a new GraphPlannerWrapper object
19+
* @param java_path Java class path
20+
* @param jna_path JNA library path
21+
* @param graph_schema_yaml Path to the graph schema file in YAML format (optional)
22+
* @param graph_statistic_json Path to the graph statistics file in JSON format (optional)
23+
*/
24+
GraphPlannerWrapper(const std::string &java_path,
25+
const std::string &jna_path,
26+
const std::string &graph_schema_yaml = "",
27+
const std::string &graph_statistic_json = "");
28+
```
29+
30+
Method:
31+
32+
```cpp
33+
/**
34+
* @brief Compile a cypher query to a physical plan by JNI invocation.
35+
* @param compiler_config_path The path of compiler config file.
36+
* @param cypher_query_string The cypher query string.
37+
* @param graph_schema_yaml Content of the graph schema in YAML format
38+
* @param graph_statistic_json Content of the graph statistics in JSON format
39+
* @return The physical plan in bytes and result schema in yaml.
40+
*/
41+
Plan GraphPlannerWrapper::CompilePlan(const std::string &compiler_config_path,
42+
const std::string &cypher_query_string,
43+
const std::string &graph_schema_yaml,
44+
const std::string &graph_statistic_json)
45+
```
46+
47+
### Getting Started
48+
Follow the steps below to get started with the Graph Planner interface for c++ invocation.
49+
50+
#### Step 1: Build the Project
51+
52+
Navigate to the project directory and build the package using Maven:
53+
```bash
54+
cd interactive_engine
55+
mvn clean package -DskipTests -Pgraph-planner-jni
56+
```
57+
58+
#### Step 2: Locate and Extract the Package
59+
60+
After the build completes, a tarball named `graph-planner-jni.tar.gz` will be available in the `assembly/target` directory. Extract the contents of the tarball:
61+
62+
```bash
63+
cd assembly/target
64+
tar xvzf graph-planner-jni.tar.gz
65+
cd graph-planner-jni
66+
```
67+
68+
#### Step 3: Run the Example Binary
69+
70+
To demonstrate the usage of the JNI interface, an example binary `test_graph_planner` is provided. Use the following command to execute it:
71+
72+
```bash
73+
# bin/test_graph_planner <java class path> <jna lib path> <graph schema path> <graph statistics path> <query> <config path>
74+
bin/test_graph_planner libs native ./conf/graph.yaml ./conf/modern_statistics.json "MATCH (n) RETURN n, COUNT(n);" ./conf/gs_interactive_hiactor.yaml
75+
```
76+
77+
The output consists of the physical plan (in byte format) and the result schema (in YAML format). The physical plan adheres to the specifications defined in the [protobuf]().
78+
79+
Below is an example of a result schema:
80+
81+
```yaml
82+
schema:
83+
name: default
84+
description: default desc
85+
mode: READ
86+
extension: .so
87+
library: libdefault.so
88+
params: []
89+
returns:
90+
- name: n
91+
type: {primitive_type: DT_UNKNOWN}
92+
- name: $f1
93+
type: {primitive_type: DT_SIGNED_INT64}
94+
type: UNKNOWN
95+
query: MATCH (n) RETURN n, COUNT(n);
96+
```
97+
98+
The `returns` field defines the structure of the data returned by backend engines. Each nested entry in the returns field includes three components:
99+
- the column name, which specifies the name of the result column;
100+
- the entry’s ordinal position, which determines the column ID;
101+
- the type, which enforces the data type constraint for the column.
102+
103+
## Restful API
104+
105+
We provide an alternative method to expose the interface as a RESTful API. Follow the steps below to access the interface via REST.
106+
107+
### Getting Started
108+
109+
#### Step 1: Build the Project
110+
111+
To build the project, run the following command:
112+
```bash
113+
cd interactive_engine
114+
# Use '-Dskip.native=true' to skip compiling C++ native code
115+
mvn clean package -DskipTests -Pgraph-planner-jni -Dskip.native=true
116+
```
117+
118+
#### Step 2: Locate and Extract the Package
119+
120+
Once the build completes, a tarball named graph-planner-jni.tar.gz will be available in the assembly/target directory. Extract the contents as follows:
121+
122+
```bash
123+
cd assembly/target
124+
tar xvzf graph-planner-jni.tar.gz
125+
cd graph-planner-jni
126+
```
127+
128+
#### Step 3: Start the Graph Planner RESTful Service
129+
130+
To start the service, run the following command:
131+
132+
```bash
133+
java -cp ".:./libs/*" com.alibaba.graphscope.sdk.restful.GraphPlannerService --spring.config.location=./conf/application.yaml
134+
```
135+
136+
#### Step 4: Access the RESTful API by `Curl`
137+
138+
To send a request to the RESTful API, use the following `curl` command:
139+
140+
```bash
141+
curl -X POST http://localhost:8080/api/compilePlan \
142+
-H "Content-Type: application/json" \
143+
-d "{
144+
\"configPath\": \"$configPath\",
145+
\"query\": \"$query\",
146+
\"schemaYaml\": \"$schemaYaml\",
147+
\"statsJson\": \"$statsJson\"
148+
}"
149+
```
150+
151+
Replace `$configPath`, `$query`, `$schemaYaml`, and `$statsJson` with the appropriate values.
152+
153+
The response will be in JSON format, similar to:
154+
155+
```json
156+
{
157+
"graphPlan": {
158+
"physicalBytes": "",
159+
"resultSchemaYaml": ""
160+
}
161+
}
162+
```
163+
164+
The response contains two fields:
165+
1. physicalBytes: A Base64-encoded string representing the physical plan bytes.
166+
2. resultSchemaYaml: A string representing the YAML schema.
167+
168+
You can decode these values into the required structures.
169+
170+
#### Step 4: Access the RESTful API by `Java` Sdk
171+
172+
Alternatively, if you are a java-side user, we provide a java sdk example to guide you how to access the restful API and decode the response :
173+
174+
```java
175+
public static void main(String[] args) throws Exception {
176+
if (args.length < 4) {
177+
System.out.println("Usage: <configPath> <query> <schemaPath> <statsPath>");
178+
System.exit(1);
179+
}
180+
// set request body in json format
181+
String jsonPayLoad = createParameters(args[0], args[1], args[2], args[3]).toString();
182+
HttpClient client = HttpClient.newBuilder().build();
183+
// create http request, set header and body content
184+
HttpRequest request =
185+
HttpRequest.newBuilder()
186+
.uri(URI.create("http://localhost:8080/api/compilePlan"))
187+
.setHeader("Content-Type", "application/json")
188+
.POST(
189+
HttpRequest.BodyPublishers.ofString(
190+
jsonPayLoad, StandardCharsets.UTF_8))
191+
.build();
192+
// send request and get response
193+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
194+
String body = response.body();
195+
// parse response body as json
196+
JsonNode planNode = (new ObjectMapper()).readTree(body).get("graphPlan");
197+
// print result
198+
System.out.println(getPhysicalPlan(planNode));
199+
System.out.println(getResultSchemaYaml(planNode));
200+
}
201+
202+
private static JsonNode createParameters(
203+
String configPath, String query, String schemaPath, String statsPath) throws Exception {
204+
Map<String, String> params =
205+
ImmutableMap.of(
206+
"configPath",
207+
configPath,
208+
"query",
209+
query,
210+
"schemaYaml",
211+
FileUtils.readFileToString(new File(schemaPath), StandardCharsets.UTF_8),
212+
"statsJson",
213+
FileUtils.readFileToString(new File(statsPath), StandardCharsets.UTF_8));
214+
return (new ObjectMapper()).valueToTree(params);
215+
}
216+
217+
// get base64 string from json, convert it to physical bytes , then parse it to PhysicalPlan
218+
private static GraphAlgebraPhysical.PhysicalPlan getPhysicalPlan(JsonNode planNode)
219+
throws Exception {
220+
String base64Str = planNode.get("physicalBytes").asText();
221+
byte[] bytes = java.util.Base64.getDecoder().decode(base64Str);
222+
return GraphAlgebraPhysical.PhysicalPlan.parseFrom(bytes);
223+
}
224+
225+
// get result schema yaml from json
226+
private static String getResultSchemaYaml(JsonNode planNode) {
227+
return planNode.get("resultSchemaYaml").asText();
228+
}
229+
```
230+
231+
Run the java sdk example with the following command:
232+
```bash
233+
java -cp ".:./libs/*" com.alibaba.graphscope.sdk.examples.TestGraphPlanner ./conf/gs_interactive_hiactor.yaml "Match (n) Return n;" ./conf/graph.yaml ./conf/modern_statistics.json
234+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
4+
<id>graph-planner-jni</id>
5+
<formats>
6+
<format>tar.gz</format>
7+
</formats>
8+
9+
<fileSets>
10+
<fileSet>
11+
<directory>${project.parent.basedir}/executor/ir/target/release</directory>
12+
<includes>
13+
<include>libir_core.*</include>
14+
</includes>
15+
<outputDirectory>native</outputDirectory>
16+
</fileSet>
17+
<fileSet>
18+
<directory>${project.parent.basedir}/target/native/</directory>
19+
<includes>
20+
<include>libgraph_planner.*</include>
21+
</includes>
22+
<outputDirectory>native</outputDirectory>
23+
</fileSet>
24+
<fileSet>
25+
<directory>${project.parent.basedir}/target/native/</directory>
26+
<includes>
27+
<include>test_graph_planner</include>
28+
</includes>
29+
<outputDirectory>bin</outputDirectory>
30+
<fileMode>0755</fileMode>
31+
</fileSet>
32+
<fileSet>
33+
<directory>${project.parent.basedir}/compiler/target/libs/</directory>
34+
<outputDirectory>libs</outputDirectory>
35+
</fileSet>
36+
<fileSet>
37+
<directory>${project.parent.basedir}/compiler/target/</directory>
38+
<includes>
39+
<include>compiler-0.0.1-SNAPSHOT.jar</include>
40+
</includes>
41+
<outputDirectory>libs</outputDirectory>
42+
</fileSet>
43+
<fileSet>
44+
<directory>${project.parent.basedir}/compiler/conf</directory>
45+
<outputDirectory>conf</outputDirectory>
46+
<includes>
47+
<include>*</include>
48+
</includes>
49+
</fileSet>
50+
<fileSet>
51+
<directory>${project.parent.basedir}/../flex/interactive/examples/modern_graph/</directory>
52+
<outputDirectory>conf</outputDirectory>
53+
<includes>
54+
<include>graph.yaml</include>
55+
</includes>
56+
</fileSet>
57+
<fileSet>
58+
<directory>${project.parent.basedir}/compiler/src/test/resources/statistics/</directory>
59+
<outputDirectory>conf</outputDirectory>
60+
<includes>
61+
<include>modern_statistics.json</include>
62+
</includes>
63+
</fileSet>
64+
<fileSet>
65+
<directory>${project.parent.basedir}/compiler/src/test/resources/config/</directory>
66+
<outputDirectory>conf</outputDirectory>
67+
<includes>
68+
<include>gs_interactive_hiactor.yaml</include>
69+
</includes>
70+
</fileSet>
71+
</fileSets>
72+
</assembly>

interactive_engine/assembly/pom.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,22 @@
7171
</dependency>
7272
</dependencies>
7373
</profile>
74+
<profile>
75+
<id>graph-planner-jni</id>
76+
<build>
77+
<plugins>
78+
<plugin>
79+
<groupId>org.apache.maven.plugins</groupId>
80+
<artifactId>maven-assembly-plugin</artifactId>
81+
<configuration>
82+
<finalName>graph-planner-jni</finalName>
83+
<descriptors>
84+
<descriptor>graph-planner-jni.xml</descriptor>
85+
</descriptors>
86+
</configuration>
87+
</plugin>
88+
</plugins>
89+
</build>
90+
</profile>
7491
</profiles>
7592
</project>

0 commit comments

Comments
 (0)