12 breaking releases

0.15.0 Oct 8, 2024
0.12.0 Aug 15, 2024
0.7.0 Jul 30, 2024

#303 in Database interfaces

MIT license

435KB
1.5K SLoC

test crates docs cov cov

GruPHst

An in-memory graph database


GruPHst

Possible to persists on file (just because is something that we always expect from an in-memory databases).

Early state of development with lot of TODOs, just doing nerdy things with Graph Databases while trying to learn some Rust.

Documentation

Code Coverage


  1. Basic Usage
  2. Install
  3. Tests & Coverage & Benchmarking
  4. Configuration
    1. Configurable variables
    2. Maximum memory usage
    3. Level for logging
    4. Character delimiter for CSV file
  5. Save & Load
  6. Export & Import
    1. CSV
      1. File Format
      2. Export & Import Usage
  7. Cryptography
    1. Argon2 Hashes
  8. Examples

Basic Usage

use gruphst::{edge::Edge, graphs::Graphs, vertex::Vertex};
use std::error::Error;

// The idea it's to create some graph related with 
// the Middle-Earth, relating some characters and
// places

fn main() -> Result<(), Box<dyn Error>> {
    // Create a new vertex
    let frodo = Vertex::new("Frodo");
       
    // Let's create another vertex
    let mut gandalf = Vertex::new("Gandalf");
     
    // A vertex can have attributes
    gandalf.set_attr("known as", "The Gray");
    gandalf.set_attr("years old", 24000);

    // Now lets make a relation between these two friends
    // by creating an Edge
    let mut edge = Edge::create(&gandalf, "friend of", &frodo);

    // An Edge can have attributes
    edge.set_attr("duration in years", 42);

    // Now we need something to hold, and store the created Edge
    // and the new ones that we'll create later.
    // Lets init a Graphs, we could do this step at the begining 
    // of the main function.
    let mut graphs = Graphs::init("middle-earth");
    
    // Now we add the edge or relation between Gandalf and Frodo
    graphs.add_edge(&edge, None);

    // We can add another relation or Edge to the graphs
    // for these two friends, e.g.
    graphs.add_edge(&Edge::create(&frodo, "has best friend", &gandalf), None);

    // Lets create more vertices for places and characters and edges
    // for the relation between them
    let mut sam = Vertex::new("Samwise");
    sam.set_attr("surname", "Gamgee");
    graphs.add_edge(
        &Edge::create(
            &sam,
            "has best friend",
            &frodo),
        None);

    let mut vertex = Vertex::new("The Shire");

    // Vertices and Edges has a uuid generated on creation
    let id_vertex_the_shire = vertex.get_id();

    graphs.add_edge(&Edge::create(&frodo, "lives at", &vertex), None); 

    vertex = Vertex::new("Isengard");
    vertex.set_attr("type", "tower");

    graphs.add_edge(&Edge::create(&Vertex::new("Saruman"), "lives at", &vertex), None); 

    // we can use the id or the label to retrieve a Vertex that we have on Graph
    let the_shire = graphs.find_vertex_by_id(id_vertex_the_shire.as_str(), None)?;

    graphs.add_edge(&Edge::create(&sam, "lives at", &the_shire), None); 

    // Now we can do things like get stats of the Graphs
    let stats = graphs.get_stats();

    // and print it
    println!("{:#?}", stats);
    // GraphsStats {
    //    mem: 1578,
    //    total_edges: 6,
    //    total_graphs: 1,
    //    total_attr: 8,
    //    total_vertices: 12,
    //    uniq_rel: 3,
    //    max_mem: 104857600,
    // }
    
    // or get some value from stats
    // like the amount of vertices
    assert_eq!(stats.get_total_vertices(), 12);

    // We can print the current Graphs object
    println!("{:#?}", graphs);

    // We can retrieve the uniq relations from the graph
    let unique_relations_vertices = graphs.uniq_relations();
    assert_eq!(unique_relations_vertices, vec!["friend of", "has best friend", "lives at"]);

    // Also possible to retrieve the vertices that has a certain
    // relation in
    let vertices_with_relation_in = graphs.find_vertices_with_relation_in("lives at", None)?; 
    assert_eq!(vertices_with_relation_in[0].get_label(), "The Shire");
    assert_eq!(vertices_with_relation_in[1].get_label(), "Isengard");

    // Or get the edge that has a vertex with an attribute equals to
    let found = graphs.find_edges_with_vertex_attr_str_equals_to("years old", 24000, None)?;
    assert_eq!(found[0].get_from_vertex().get_label(), "Gandalf");

    // Since we have a humble middle-earth network
    // we can persists it for another day
    // a file called "middle-earth.grphst" will be created, 
    // later we can load it with:
    // let loaded_graphs = Graphs::load("middle-earth.grphst")?;
    graphs.save(None)?;

    Ok(())
}

Install

Run the following Cargo command in your project directory:

$ cargo add gruphst

Or add the following line to your Cargo.toml:

gruphst = "0.15.0"

Tests & Coverage & Benchmarking

To run tests locally This will show output, if a test name is provided as argument will run this tests

$ ./scripts/local-test.sh

If nodemon is installed, you can use the tests in watch mode:

$ ./scripts/dev-watch.sh

Coverage

$ ./scripts/test-coverage.sh

It will generate a report called tarpauling-report.html

Benchmarking

$ ./scripts/benchmarking.sh

Right now only covers add_edge method.

Configuration

GruPHst uses dotenv to deal with configurations. You can place a .env file in order to handle your configuration values or you can use environment variables instead to run your binary. The environmental variables will override the configuration from .env file.

e.g. override log level in your binary:

$ GRUPHST_LOG_LEVEL=trace cargo run

This is the currnet .env file:

# limit for memory usage in MB
GRUPHST_MAX_MEM_USAGE=100 

# log level, case insensitive, possible values:
# trace
# debug
# info
# warn 
# warning
# err
# error
GRUPHST_LOG_LEVEL=info

# delimiter character for CSV import-export 
GRUPHST_CSV_DELIMITER=;

Configurable variables

Maximum memory usage

Configures the maximum memory in MB that GruPHst will use. In case that this limit will reach, before panic will persists the current status.

GRUPHST_MAX_MEM_USAGE=100

Level for logging

Sets the level for logging in case insensitive, the possible values are:

  • trace
  • debug
  • info
  • warn
  • warning
  • err
  • error

GRUPHST_LOG_LEVEL=info

In order to use it on your binary:

// import from config and logger level
use gruphst::config::get_log_level;
use gruphst::logger::enable_logging;

// get the configured log level; on .env file or environmental
let log_level = get_log_level();

// enable logging
enable_logging(log_level);

Character delimiter for CSV file

Configures the character used to import and export for CSV format.

GRUPHST_CSV_DELIMITER=;

Save & Load

You can persists the data on a file in GruPHst format. And later load the saved data.

use gruphst::graphs::Graphs;
use gruphst::edge::Edge;
use gruphst::vertex::Vertex;

let mut graphs = Graphs::init("to_export");
let foo = Vertex::new("foo");
let bar = Vertex::new("bar");
graphs.add_edge(&Edge::create(&foo, "is related to", &bar), None);

// persists the graphs data on file, 
// with "./to_export.grphst"
graphs.save(Some("./"));

// load the saved data
let saved_graphs = Graphs::load("./to_export.grphst").unwrap();

Export & Import

CSV

The delimiter could be configured with GRUPHST_CSV_DELIMITER variable, via .env file or with environmental var usage. The default character is ';'.

File Format

Headers:

graphs_vault;from_label;from_attributes;relation;to_label;to_attributes

Row example:

shire-friendships;gandalf;known as: Gandalf the Gray | name: Gandalf;friend of;frodo;name: Frodo Bolson

Note: The different attributes are separated by '|' character and key followed by ':' and vaule.

Export & Import Usage

use gruphst::graphs::Graphs;
use gruphst::edge::Edge;
use gruphst::vertex::Vertex;
use gruphst::exporter_importer::csv::*;

let mut graphs = Graphs::init("to_export");
let foo = Vertex::new("foo");
let bar = Vertex::new("bar");
graphs.add_edge(&Edge::create(&foo, "is related to", &bar), None);

// export graphs to CSV file
export_to_csv_gruphst_format(&graphs, Some("./"), Some("export_csv_filename")).unwrap();

// import graphs from CSV file
let graphs: Graphs = import_from_csv_gruphst_format("./export_csv_filename.csv").unwrap();

Cryptography

Argon2 Hashes

You can use Argon2 to store passwords or whatever sensible data you are dealing with, and verify it.

use gruphst::vertex::Vertex;

// create a vertex
let mut vertex = Vertex::new("Brian");
// set an Argon2 hash
vertex.set_hash("password", "53cr37");

// Check if the provided value is valid
assert!(vertex.is_hash_valid("password", "53cr37").unwrap());
assert!(!vertex.is_hash_valid("password", "f00b4r").unwrap());

Examples

Check the Rock Paper Scissors Spock Lizard example.

Check the Middle-Earth example.

Also worth to check the tests folder.


Thanks @ChrisMcMStone for all the help and memory tips ;-)

Feedback from usage and contributions are very welcome. Also if you like it, please leave a ⭐ I would appreciate it ;)

Dependencies

~3–14MB
~109K SLoC