DEV Community

Marco Aurélio Silva de Souza Júnior
Marco Aurélio Silva de Souza Júnior

Posted on

Developing a Barbell Graph Function with Apache AGE

Now that we saw how to develop a function to create a complete graph on Apache AGE, we are going to create a function to develop a Barbell graph - two complete graphs (cliques) connected by a bridge path. We will guide you through each step, explaining the components of the function to help you understand how it works.

We start by declaring our function with the macro PG_FUNCTION_INFO_V1. This is used in PostgreSQL to bind our C function to SQL commands. In our case, we're defining a function called age_create_barbell_graph.

PG_FUNCTION_INFO_V1(age_create_barbell_graph);
Enter fullscreen mode Exit fullscreen mode

This function takes a graph name, the number of vertices in each complete graph (m), the number of vertices in the bridge path (n), labels for vertices and edges, and properties for vertices and edges.

Our main function is age_create_barbell_graph, and it uses the PG_FUNCTION_ARGS macro as a shortcut to pass arguments from SQL commands to our C function.

Datum age_create_barbell_graph(PG_FUNCTION_ARGS)
Enter fullscreen mode Exit fullscreen mode

We need to declare all variables in the beginning of the function, otherwise it will issue an warning when compiling: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]

    FunctionCallInfo arguments;
    Oid graph_oid;
    Name graph_name;
    char* graph_name_str;

    int64 start_node_index, end_node_index, nextval;

    Name node_label_name = NULL;
    int32 node_label_id;
    char* node_label_str;

    Name edge_label_name;
    int32 edge_label_id;
    char* edge_label_str;

    graphid object_graph_id;
    graphid start_node_graph_id;
    graphid end_node_graph_id;

    graph_cache_data* graph_cache;
    label_cache_data* edge_cache;

    agtype* properties = NULL;

    arguments = fcinfo;
Enter fullscreen mode Exit fullscreen mode

We have to check if any mandatory arguments are null. This is essential because it prevents any errors from occurring later in our function. For example, if the graph name or size is null or invalid, the function throws an error.
Here we check the arguments and process it immediately after:

    // Checking for possible NULL arguments 
    // Name graph_name
    if (PG_ARGISNULL(0))
    {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("Graph name cannot be NULL")));
    }
    graph_name = PG_GETARG_NAME(0);
    graph_name_str = NameStr(*graph_name);

    // int graph size (number of nodes in each complete graph)
    if (PG_ARGISNULL(1) && PG_GETARG_INT32(1) < 3)
    {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("Graph size cannot be NULL or lower than 3")));
    }

    /*
     * int64 bridge_size: currently only stays at zero.
     * to do: implement bridge with variable number of nodes.
    */ 
    if (PG_ARGISNULL(2) || PG_GETARG_INT32(2) < 0 )
    {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("Bridge size cannot be NULL or lower than 0")));
    }

    // node label: if null, gets default label, which is "_ag_label_vertex"
    if (PG_ARGISNULL(3)) 
    {
        namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX);
    }
    else 
    {
        node_label_name = PG_GETARG_NAME(3);
    }
    node_label_str = NameStr(*node_label_name);

    /* 
    * Name edge_label 
    */
    if (PG_ARGISNULL(5))
    {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("edge label can not be NULL")));
    }
    edge_label_name = PG_GETARG_NAME(5);
    edge_label_str = NameStr(*edge_label_name);
Enter fullscreen mode Exit fullscreen mode

We use DirectFunctionCall4 to call the function create_complete_graph twice - once for each complete graph in our Barbell Graph. This function creates a complete graph with a given name, size, edge label, and vertex label.

DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
                                               arguments->arg[1],
                                               arguments->arg[5], 
                                               arguments->arg[3]);
Enter fullscreen mode Exit fullscreen mode

We need to get the unique identifiers (oid) for our graph and labels. This is done using the get_graph_oid and get_label_id functions.

graph_oid = get_graph_oid(graph_name_str);
node_label_id = get_label_id(node_label_str, graph_oid);
edge_label_id = get_label_id(edge_label_str, graph_oid);
Enter fullscreen mode Exit fullscreen mode

The graph and edge caches are then searched to provide access to necessary nodes to be connected with edges later.

graph_cache = search_graph_name_cache(graph_name_str);
edge_cache = search_label_name_graph_cache(edge_label_str,graph_oid);
Enter fullscreen mode Exit fullscreen mode

We use the get_nextval_internal function to get the next internal value in the graph, so a new object (node or edge) graph id can be composed.

nextval = get_nextval_internal(graph_cache, edge_cache);
Enter fullscreen mode Exit fullscreen mode

Finally, we use the insert_edge_simple function to connect two nodes - one from each complete graph. This creates the bridge that links our two complete graphs into a Barbell Graph.

insert_edge_simple(graph_oid, edge_label_str,
                       object_graph_id, start_node_graph_id,
                       end_node_graph_id, properties);
Enter fullscreen mode Exit fullscreen mode

And that's it! Our function ends by returning void using PG_RETURN_VOID(), meaning it doesn't return any value. The graph is directly created in the database.

Including the Function in the PostgreSQL Catalog

The function you've just created needs to be recognized and executed by PostgreSQL. To achieve this, we have to add a corresponding entry in PostgreSQL's catalog. This is done by including the function in the age--1.3.0.sql file. This file is the SQL script that sets up the PostgreSQL functions when Apache AGE is installed.

Here's the code you need to add to the age--1.3.0.sql file:

CREATE FUNCTION ag_catalog.age_create_barbell_graph(
    graph_name name,
    graph_size int,
    bridge_size int,
    node_label name = NULL,
    node_properties agtype = NULL,
    edge_label name = NULL,
    edge_properties agtype = NULL)
RETURNS void
LANGUAGE c
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
Enter fullscreen mode Exit fullscreen mode

This CREATE FUNCTION command adds our age_create_barbell_graph function to the ag_catalog, which is the PostgreSQL catalog for Apache AGE.

The structure of the CREATE FUNCTION command matches the structure of the C function we've defined earlier. The parameters, their types, and their default values are all listed here, along with the function's return type (void).

LANGUAGE c specifies that the function is written in C.

CALLED ON NULL INPUT means the function will still be called even if some of its arguments are null.

PARALLEL SAFE indicates that this function can be safely run in parallel mode by PostgreSQL.

Finally, 'MODULE_PATHNAME' is a placeholder for the shared library where the C function is located.

After adding this to the age--1.3.0.sql file and installing Apache AGE, PostgreSQL will recognize and be able to execute your age_create_barbell_graph function. Now you can use this function to create barbell graphs directly from your SQL commands!

Calling the Function from the psql Interface

Now that our function is defined and registered with PostgreSQL, let's go over how to call it from the psql interface.

Here are the commands to load the Apache AGE extension and set the search path:

CREATE EXTENSION age;

LOAD 'age';
SET search_path TO ag_catalog;
Enter fullscreen mode Exit fullscreen mode

SET search_path TO ag_catalog; is setting the search path to ag_catalog, the PostgreSQL catalog for Apache AGE. This means that the function we're going to call is located in this catalog.

Now we can call our age_create_barbell_graph function. Here's the command to do so:

SELECT age_create_barbell_graph('barbell',5,0,'vlabel',NULL,'elabel',NULL);
Enter fullscreen mode Exit fullscreen mode

We use a SELECT statement to call the function. The parameters passed are the name of the graph ('barbell'), the number of vertices in each complete graph (5), the number of vertices in the bridge path (0), and the labels for the vertices and edges ('vlabel' and 'elabel', respectively). We're passing NULL for the properties of vertices and edges as we haven't defined any.

As you might notice, the bridge size and properties arguments are not implemented in this function. That's an exercise for you - try modifying the function to include these features, to further enhance the functionality and flexibility of your barbell graphs!

This concludes our guide on developing a Barbell graph function using Apache AGE. Remember that practice is key in mastering these concepts, so don't hesitate to experiment and try implementing additional features yourself!


I make these posts in order to guide people into the development of a new technology. If you find anything incorrect, I urge you to comment below so I can fix it. Thanks!


Check Apache AGE: https://age.apache.org/.

Overview — Apache AGE master documentation: https://age.apache.org/age-manual/master/intro/overview.html.

GitHub - apache/age: https://github.com/apache/age

Top comments (0)