Quickstart to custom query module in Rust
In order to start working on a query module, you need to have a Memgraph instance running on a server or in a container. The container or the server where the instance is running will also be used as a development environment for your query module in this case.
This will provide you with an isolated environment for testing and developing your query module. In order to follow this guide, the best way would be to have Docker installed on your machine.
If you do not use Docker but have Memgraph on your Linux machine, the following steps will be the same. You will just run commands on your machine instead of the docker container. However, you'll need to ensure that system packages required to compile the query modules are present on your machine.
Run Memgraph
The easiest way to create and build new query modules is inside our
Memgraph MAGE development Docker image, since it contains necessary system packages. Make sure the image
tag is suffixed by -dev
to ensure development system packages are installed.
The container is run with the following command:
docker run -p 7687:7687 -p 7444:7444 --name mage memgraph/memgraph-mage:<version>-dev
Another thing to pay attention to is the version of Memgraph MAGE development image. You can check what is the latest version of the image on Dockerhub.
Enter into memgraph container shell
After you have started the Memgraph instance, you need to enter the shell. You can do this by running the following command:
docker exec -it -u root mage bash
This will start bash inside the container as the root user. Since we will need to install certain dependencies, we need to be the root user. Of course, if you are running a native Linux machine, you can skip this step.
Install Cargo for Rust
Query modules in Rust are compiled inside the Memgraph image using cargo
.
Cargo has a large overhead on the Docker image and it is not
installed by default. In order to install cargo and add it to the PATH
environment variable, we will use the following commands:
curl https://sh.rustup.rs -sSf | sh -s -- -y
export PATH="/root/.cargo/bin:${PATH}"
The installation of cargo
can last up to several minutes.
Create the file structure for the query module
Now that all of the required dependencies are installed, you can create a directory for your query module. In the rust directory (/mage/rust
), you
can copy the rsmgp-example
boilerplate file structure and modify the following information:
In the Cargo.toml
file, apply the changes:
[package]
name = "your-package-name"
[lib]
name = "your-query-module-top-level-name"
The complete Cargo.toml
file should look like this:
[package]
name = "rsmgp-my-query-module-package"
version = "0.1.0"
authors = ["Josip Mrden <josip.mrden@memgraph.io>"]
edition = "2018"
[dependencies]
c_str_macro = "1.0.2"
rsmgp-sys = { path = "../rsmgp-sys" }
[lib]
name = "my_rust_query_module"
crate-type = ["cdylib"]
Develop your Rust query module
Inside the lib.rs
file of the query module file structure, you can add your specific
module using the Rust API.
An example query module file would look like this
use c_str_macro::c_str;
use rsmgp_sys::list::*;
use rsmgp_sys::memgraph::*;
use rsmgp_sys::mgp::*;
use rsmgp_sys::property::*;
use rsmgp_sys::result::*;
use rsmgp_sys::rsmgp::*;
use rsmgp_sys::value::*;
use rsmgp_sys::{close_module, define_optional_type, define_procedure, define_type, init_module};
use std::ffi::CString;
use std::os::raw::c_int;
use std::panic;
init_module!(|memgraph: &Memgraph| -> Result<()> {
memgraph.add_read_procedure(
first_query_module,
c_str!("first_query_module"),
&[define_type!("input_string", Type::String)],
&[define_optional_type!(
"optional_input_int",
&MgpValue::make_int(0, &memgraph)?,
Type::Int
)],
&[
define_type!("output_string", Type::String),
define_type!("output_int", Type::Int),
],
)?;
Ok(())
});
define_procedure!(first_query_module, |memgraph: &Memgraph| -> Result<()> {
// This procedure just forwards the input parameters as procedure results.
let result = memgraph.result_record()?;
let args = memgraph.args()?;
let input_string = args.value_at(0)?;
let input_int = args.value_at(1)?;
result.insert_mgp_value(
c_str!("output_string"),
&input_string.to_mgp_value(&memgraph)?,
)?;
result.insert_mgp_value(c_str!("output_int"), &input_int.to_mgp_value(&memgraph)?)?;
Ok(())
});
close_module!(|| -> Result<()> { Ok(()) });
The three key functions in the file are init_module
, define_procedure
, and close_module
.
init_module
is used for registering procedures inside module. In this file, we registered the procedure
first_query_module
using the add_read_procedure
function. With each new query module you want to create, you will
need an additional add_read_procedure
or add_write_procedure
function in the initialization module.
memgraph.add_read_procedure(
first_query_module,
c_str!("first_query_module"),
&[define_type!("input_string", Type::String)],
&[define_optional_type!(
"optional_input_int",
&MgpValue::make_int(0, &memgraph)?,
Type::Int
)],
&[
define_type!("output_string", Type::String),
define_type!("output_int", Type::Int),
],
)?;
define_procedure
is the definition of our query module. With each query module you add, you need to define
an additional function for the logic of your query module.
Finally, the close_module
is a boilerplate code to finish the initialization of the query module.
Compile the query module
In order to build the query modules, you will need to run the following command:
python3 setup build -p /usr/lib/memgraph/query_modules/
The Rust query modules will be compiled into shared object files (.so
files) which can be dinamically linked
with Memgraph at runtime.
After compiling the modules, the setup script also copies the modules from /mage/dist
directory
to the appropriate location in the container. Memgraph already has a default path for query modules at
/usr/lib/memgraph/query_modules/
When the modules are available at the query module path, you can attach to mgconsole from the host, and reload
the modules in the /usr/lib/memgraph/query_modules
directory:
CALL mg.load_all();
The command ensures modules are dynamically initialized and registered as valid query module procedures. This can be verified by executing the following command and inspecting the name of your desired query module:
CALL mg.procedures() YIELD *;
If the module is not present in the output, please check the logs as they will display what happened during the registration of your query module.
Test the query module
Now that we have loaded the query module, we can test it. You can do this by running the following command:
Call the query module:
CALL my_rust_query_module.first_query_module("Hello World!") YIELD output_string
RETURN output_string;
+----------------+
| output_string |
+----------------+
| "Hello World!" |
+----------------+
If you experience any issues with loading the query module, it is recommended to check and follow the Memgraph logs and restart Memgraph.
If any of the problems persist and are keeping you away from developing Rust modules, you can always search for community support on our Discord servers (opens in a new tab).