Developing a Barbell Graph Function with Apache AGE
Tags: apacheage, apache, postgres
Published: 2023-06-22 14:16:05

Now that we saw how to develop a function to [create a complete graph on Apache AGE](https://dev.to/markgomer/apache-age-and-postgres-source-code-tour-understanding-the-complete-graph-function-2p4m), we are going to create a function to develop a [Barbell graph](https://en.wikipedia.org/wiki/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`.

```c
PG_FUNCTION_INFO_V1(age_create_barbell_graph);
```


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.
```c
Datum age_create_barbell_graph(PG_FUNCTION_ARGS)
```

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]`

```c
    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;
```

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:
```c
    // 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);
```

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.

```c
DirectFunctionCall4(create_complete_graph, arguments->arg[0], 
                                               arguments->arg[1],
                                               arguments->arg[5], 
                                               arguments->arg[3]);
```

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.
```c
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);
```

The graph and edge caches are then searched to provide access to necessary nodes to be connected with edges later.
```c
graph_cache = search_graph_name_cache(graph_name_str);
edge_cache = search_label_name_graph_cache(edge_label_str,graph_oid);
```

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.
```c
nextval = get_nextval_internal(graph_cache, edge_cache);
```

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.
```c
insert_edge_simple(graph_oid, edge_label_str,
                       object_graph_id, start_node_graph_id,
                       end_node_graph_id, properties);
```

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:

```sql
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';
```

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:

```sql
CREATE EXTENSION age;

LOAD 'age';
SET search_path TO ag_catalog;
```

`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:

```sql
SELECT age_create_barbell_graph('barbell',5,0,'vlabel',NULL,'elabel',NULL);
```
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