RPC API

This document mainly introduces the details of calling TuGraph’s RPC API.

1.Introduction

TuGraph provides rich RPC APIs for developers to remotely call the services provided by TuGraph through RPC requests.

RPC (Remote Procedure Call) is a protocol for requesting services from remote computer programs through a network without the need to understand the underlying network technology. Compared with REST, RPC is method-oriented and mainly used for calling function methods. It is suitable for more complex communication scenarios and has higher performance. brpc is an industrial-grade RPC framework written in C++. Based on brpc, TuGraph provides rich RPC APIs. This document describes how to use TuGraph’s RPC API.

2.Request

2.1.Establishing a Connection

When developers send RPC requests to TuGraph services, they must first establish a connection. Taking C++ as an example, developers create a channel with the specified URL and create a specified service stub (LGraphRPCService_Stub) from the channel. Subsequently, they can send requests to the remote server through the stub like calling local methods.

    std::shared_ptr<lgraph_rpc::m_channel_options> options = std::make_shared<lgraph_rpc::m_channel_options>();
    options->protocol = "baidu_std";
    options->connection_type = "";
    options->timeout_ms = 60 * 60 * 1000 /*milliseconds*/;
    options->max_retry = 3;
    std::string load_balancer = "";
    std::shared_ptr<lgraph_rpc::m_channel> channel = std::make_shared<lgraph_rpc::m_channel>();
    if (channel->Init(url.c_str(), load_balancer, options.get()) != 0)
        throw RpcException("Fail to initialize channel");
    LGraphRPCService_Stub stub(channel.get());

2.2.Request Types

TuGraph supports 10 types of RPC requests, and each request’s functionality is shown in the following table:

Request

Functionality

GraphApiRequest

Vertex-Edge Index

CypherRequest

cypher

PluginRequest

Stored Procedures

HARequest

High Availability

ImportRequest

Data Import

GraphRequest

Subgraph Operations

AclRequest

Access Control

ConfigRequest

Configuration

RestoreRequest

Backup

SchemaRequest

Schema Management

When a user sends a request, the following parameters need to be passed in:

  • client_version: an optional parameter, in HA mode, it can prevent outdated responses by comparing client_version and server_version

  • token: a necessary parameter, the client obtains the token after logging in, and the token is passed in with each request to verify the user’s identity

  • is_write_op: an optional parameter, indicating whether the request is a write request

  • user: an optional parameter, set user when synchronizing requests between master and slave in HA mode, and no token verification is required After the service processes the RPC request, it sends back a response.

In addition to containing separate response information for each request, the response message also includes the following parameters:

  • error_code: a necessary parameter, indicating the processing status of the request

  • redirect: an optional parameter, when processing fails to send a write request to a follower in HA mode, set redirect as the request forwarding address, that is, the leader address

  • error: an optional parameter, indicating the error information of the request

  • server_version: an optional parameter, set server_version in the HA mode request response to avoid reverse time travel when client reads data

:warning: Except for CypherRequest, PluginRequest, HARequest and AclRequest, all other RPC interfaces will be gradually deprecated, and their functions will be unified into the CypherRequest interface.

3.Login

The login request message contains the following parameters:

  • user: a necessary parameter, the username

  • pass: a necessary parameter, the password

Taking C++ as an example, the user sends a login request using the constructed service stub:

    auto* req = request.mutable_acl_request();
    auto* auth = req->mutable_auth_request()->mutable_login();
    auth->set_user(user);
    auth->set_password(pass);
    // send data
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    req->set_client_version(server_version);
    req->set_token(token);
    LGraphRPCService_Stub stub(channel.get());
    LGraphResponse res;
    stub.HandleRequest(cntl.get(), req, &resp, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    server_version = std::max(server_version, res.server_version());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());
    token = res.acl_response().auth_response().token();

The login response message contains the following parameters:

  • token: a necessary parameter. After successful login, a signed token, namely Json Web Token, will be received. The client stores the token and uses it for each subsequent request. If the login fails, an “Authentication failed” error will be received.

4.Query

Users can interact with TuGraph through Cypher queries. The Cypher request message contains the following parameters:

  • query: a necessary parameter, the Cypher query statement

  • param_names: an optional parameter, the parameter name

  • param_values: an optional parameter, the parameter value

  • result_in_json_format: a necessary parameter, whether to return the query results in JSON format

  • graph: an optional parameter, the subgraph name for executing the Cypher statement

  • timeout: an optional parameter, the timeout for executing the Cypher statement

Taking C++ as an example, the user sends a Cypher request as follows:

    LGraphResponse res;
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    LGraphRequest req;
    req.set_client_version(server_version);
    req.set_token(token);
    lgraph::CypherRequest* cypher_req = req.mutable_cypher_request();
    cypher_req->set_graph(graph);
    cypher_req->set_query(query);
    cypher_req->set_timeout(timeout);
    cypher_req->set_result_in_json_format(true);
    LGraphRPCService_Stub stub(channel.get());
    stub.HandleRequest(cntl.get(), &req, &res, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());
    server_version = std::max(server_version, res.server_version());
    CypherResponse cypher_res = res.cypher_response();

The Cypher request response contains one of the following two parameters:

  • json_result: the Cypher query result in JSON format

  • binary_result: the Cypher query result in the CypherResult format

5.Stored Procedures

To meet users’ more complex query/update logic, TuGraph supports stored procedures written in C and Python. Users can use RPC requests to perform CRUD operations on stored procedures.

5.1.Load Stored Procedures

The request for loading stored procedures contains the following parameters:

  • name: a necessary parameter, the stored procedure name

  • read_only: a necessary parameter, whether it is read-only

  • code: a necessary parameter, the ByteString generated by reading the stored procedure file

  • desc: an optional parameter, the stored procedure description

  • code_type: an optional parameter, the stored procedure code type, which can be PY, SO, CPP, or ZIP

Taking C++ as an example, the user loads the stored procedure as follows:

    std::string content;
    if (!FieldSpecSerializer::FileReader(source_file, content)) {
        std::swap(content, result);
        return false;
    }
    LGraphRequest req;
    req.set_is_write_op(true);
    lgraph::PluginRequest* pluginRequest = req.mutable_plugin_request();
    pluginRequest->set_graph(graph);
    pluginRequest->set_type(procedure_type == "CPP" ? lgraph::PluginRequest::CPP
                                                    : lgraph::PluginRequest::PYTHON);
    pluginRequest->set_version(version);
    lgraph::LoadPluginRequest* loadPluginRequest = pluginRequest->mutable_load_plugin_request();
    loadPluginRequest->set_code_type([](const std::string& type) {
        std::unordered_map<std::string, lgraph::LoadPluginRequest_CodeType> um{
            {"SO", lgraph::LoadPluginRequest::SO},
            {"PY", lgraph::LoadPluginRequest::PY},
            {"ZIP", lgraph::LoadPluginRequest::ZIP},
            {"CPP", lgraph::LoadPluginRequest::CPP}};
        return um[type];
    }(code_type));
    loadPluginRequest->set_name(procedure_name);
    loadPluginRequest->set_desc(procedure_description);
    loadPluginRequest->set_read_only(read_only);
    loadPluginRequest->set_code(content);
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    req.set_client_version(server_version);
    req.set_token(token);
    LGraphRPCService_Stub stub(channel.get());
    LGraphResponse res;
    stub.HandleRequest(cntl.get(), &req, &res, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    server_version = std::max(server_version, res.server_version());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());

The response for loading the stored procedure does not contain parameters, and if the loading fails, a BadInput exception will be thrown.

5.2.Invoke Stored Procedures

The request for invoking stored procedures contains the following parameters:

  • name: a necessary parameter, the stored procedure name

  • param: a necessary parameter, the stored procedure parameters

  • result_in_json_format: an optional parameter, whether to return the invocation result in JSON format

  • in_process: an optional parameter, to be supported in the future

  • timeout: an optional parameter, the timeout for invoking the stored procedure

Taking C++ as an example, the user invokes the stored procedure as follows:

    LGraphRequest req;
    lgraph::PluginRequest* pluginRequest = req.mutable_plugin_request();
    pluginRequest->set_graph(graph);
    pluginRequest->set_type(procedure_type == "CPP" ? lgraph::PluginRequest::CPP
                                                    : lgraph::PluginRequest::PYTHON);
    lgraph::CallPluginRequest *cpRequest = pluginRequest->mutable_call_plugin_request();
    cpRequest->set_name(procedure_name);
    cpRequest->set_in_process(in_process);
    cpRequest->set_param(param);
    cpRequest->set_timeout(procedure_time_out);
    cpRequest->set_result_in_json_format(json_format);
    LGraphResponse res;
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    req.set_client_version(server_version);
    req.set_token(token);
    LGraphRPCService_Stub stub(channel.get());
    stub.HandleRequest(cntl.get(), &req, &res, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    server_version = std::max(server_version, res.server_version());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());
    if (json_format) {
        result = res.mutable_plugin_response()->mutable_call_plugin_response()->json_result();
    } else {
        result = res.mutable_plugin_response()->mutable_call_plugin_response()->reply();
    }

The response for invoking the stored procedure contains one of the following two parameters:

  • reply: the stored procedure invocation result in the ByteString format

  • json_result: the stored procedure invocation result in JSON format

5.3.Delete Stored Procedures

The request for deleting stored procedures contains the following parameters:

  • name: a necessary parameter, the stored procedure name

Taking C++ as an example, the user deletes the stored procedure as follows:

    LGraphRequest req;
    req.set_is_write_op(true);
    lgraph::PluginRequest* pluginRequest = req.mutable_plugin_request();
    pluginRequest->set_graph(graph);
    pluginRequest->set_type(procedure_type == "CPP" ? lgraph::PluginRequest::CPP
                                                    : lgraph::PluginRequest::PYTHON);
    lgraph::DelPluginRequest* dpRequest = pluginRequest->mutable_del_plugin_request();
    dpRequest->set_name(procedure_name);
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    req.set_client_version(server_version);
    req.set_token(token);
    LGraphRPCService_Stub stub(channel.get());
    LGraphResponse res;
    stub.HandleRequest(cntl.get(), &req, &res, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    server_version = std::max(server_version, res.server_version());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());

The response for deleting the stored procedure does not contain parameters, and if the deletion fails, a BadInput exception will be thrown.

5.4.List Stored Procedures

The request for listing stored procedures does not require parameters. Taking C++ as an example, the user lists the stored procedures as follows:

    LGraphRequest req;
    req.set_is_write_op(false);
    lgraph::PluginRequest* pluginRequest = req.mutable_plugin_request();
    pluginRequest->set_graph(graph);
    pluginRequest->set_type(procedure_type == "CPP" ? lgraph::PluginRequest::CPP
                                                    : lgraph::PluginRequest::PYTHON);
    pluginRequest->mutable_list_plugin_request();
    cntl->Reset();
    cntl->request_attachment().append(FLAGS_attachment);
    req.set_client_version(server_version);
    req.set_token(token);
    LGraphRPCService_Stub stub(channel.get());
    LGraphResponse res;
    stub.HandleRequest(cntl.get(), &req, &res, nullptr);
    if (cntl->Failed()) throw RpcConnectionException(cntl->ErrorText());
    server_version = std::max(server_version, res.server_version());
    if (res.error_code() != LGraphResponse::SUCCESS) throw RpcStatusException(res.error());
    result = res.mutable_plugin_response()->mutable_list_plugin_response()->reply();

The response for listing the stored procedures contains the following parameter:

  • reply: the procedure list in JSON format