Skip to content

Writing queries

Introduction

In the following examples, we will utilize the domain model presented by Kelvin Lawrence in their book PRACTICAL GREMLIN: An Apache TinkerPop Tutorial. The book provides insights into a fictional airline network graph, where airports are represented as vertices and flights between airports as edges. You can find the document here. With this domain model as our foundation, we will explore various Gremlinq queries to extract valuable insights from the airline network graph.

We'll assume the existence of two POCOs, Airport and Route, representing airports and routes between them and some instances of these classes:

C#
public class Airport
{
    public string Code { get; set; }
    public string Name { get; set; }
}

public class Route
{
    public double Dist { get; set; }
}

// Define a few airports
var airports = new List<Airport>
{
    new Airport { Code = "JFK", Name = "John F. Kennedy International Airport" },
    new Airport { Code = "LAX", Name = "Los Angeles International Airport" },
    new Airport { Code = "ORD", Name = "Chicago O'Hare International Airport" },
    new Airport { Code = "DFW", Name = "Dallas/Fort Worth International Airport" },
    new Airport { Code = "MIA", Name = "Miami International Airport" }
};

// Define connections between airports along with their distances in miles
var connections = new Dictionary<(string, string), double>
{
    { ("JFK", "LAX"), 2475 },
    { ("JFK", "ORD"), 740 },
    { ("JFK", "DFW"), 1370 },
    { ("LAX", "ORD"), 1745 },
    { ("ORD", "DFW"), 802 },
    { ("ORD", "MIA"), 1197 },
    { ("DFW", "MIA"), 1121 },
    { ("MIA", "JFK"), 1090 }
};

Populate the graph

In the upcoming code, we'll illustrate the process of populating an airport network graph, connecting various airports with routes between them.

Create the airports
C#
foreach (var airport in airports)
{
    await g
        .AddV(airport)
        .FirstAsync();
}
Add connections between airports
C#
foreach (var ((fromAirport, toAirport), dist) in connections)
{
    await g
        .V<Airport>()
        .Where(a => a.Code == fromAirport)
        .AddE(new Route { Dist = dist })
        .To(__ => __
            .V<Airport>()
            .Where(a => a.Code == toAirport))
        .FirstAsync();
}
Alternative way of adding elements

There is an overload of AddE (and AddV) that takes no argument. Instead, properties of the newly added element can be added after the fact:

C#
foreach (var ((fromAirport, toAirport), dist) in connections)
{
    await g
        .V<Airport>()
        .Where(a => a.Code == fromAirport)
        .AddE<Route>()
        .Property(r => r.Dist, dist)
        .To(__ => __
            .V<Airport>()
            .Where(a => a.Code == toAirport))
        .FirstAsync();
}

Simple queries

Retrieve names of airport vertices
C#
var results = await g
    .V<Airport>()
    .Values(x => x.Name)
    .ToArrayAsync();
Which airport have their name starting with the letter 'J' ?
C#
var results = await g
    .V<Airport>()
    .Where(x => x.Name.StartsWith("J"))
    .ToArrayAsync();
How many airports are in the database
C#
var results = await g
    .V<Airport>()
    .Count()
    .FirstAsync();

Walk the graph

Find all destination airports from JFK
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .Out<Route>()
    .OfType<Airport>()
    .ToArrayAsync();
Find all airports connected to JFK within 1500 miles
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .OutE<Route>()
    .Where(r => r.Dist <= 1500)
    .InV<Airport>()
    .ToArrayAsync();
Find all airports that can reach JFK
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .In<Route>()
    .OfType<Airport>()
    .ToArrayAsync();
Find all airports that are reachable from JFK by taking two flights
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .Out<Route>()
    .Out<Route>()
    .OfType<Airport>()
    .ToArrayAsync();

Using sub-queries

Find all airports that are reachable from JFK by taking one or two flights
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .Union(
        __ => __
            .Out<Route>(),
        __ => __
            .Out<Route>()
            .Out<Route>())
    .OfType<Airport>()
    .ToArrayAsync();

Simpler:

C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .Out<Route>()
    .Union(
        __ => __,
        __ => __
            .Out<Route>())
    .OfType<Airport>()
    .ToArrayAsync();
Find all destinations from JFK that have a connection to Heathrow
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .Out<Route>()
    .OfType<Airport>()
    .Where(__ => __
        .Out<Route>()
        .OfType<Airport>()
        .Where(a => a.Code == "LHR"))
    .ToArrayAsync();

Orderings

This section marks the first introduction of a special of a sub-domain-specific language (sub-DSL), enabling developers to ensure that only sensible methods are within scope. This sub-DSL streamlines the programming process by providing a focused and tailored set of methods relevant to the specific context by providing a builder instance.

Order all airports lexically by their code
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .By(x => x.Code))
    .ToArrayAsync();
Order all airports by their longest route
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist)))
    .ToArrayAsync();
Order all airports by their longest route, then lexically by their code
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist))
        .By(x => x.Code))
    .ToArrayAsync();

Limiting results

Get the five airports with the longest routes
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist)))
    .Limit(5)
    .Values(x => x.Name)
    .ToArrayAsync();
Using Range instead
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist)))
    .Range(0, 5)
    .Values(x => x.Name)
    .ToArrayAsync();
Skip the first five results
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist)))
    .Range(0, 5)
    .Values(x => x.Name)
    .ToArrayAsync();
Take only the latter five
C#
var results = await g
    .V<Airport>()
    .Order(orderBuilder => orderBuilder
        .ByDescending(__ => __
            .OutE<Route>()
            .Order(o => o
                .ByDescending(x => x.Dist))
            .Values(x => x.Dist)))
    .Tail(5)
    .Values(x => x.Name)
    .ToArrayAsync();

Step labels

Avoid returning to the departure airport right away
C#
var results = await g
    .V<Airport>()
    .Where(a => a.Code == "JFK")
    .As((__, jfk) => __
        .Out<Route>()
        .Out<Route>()
        .OfType<Airport>()
        .Where(destination => destination != jfk.Value))
    .ToArrayAsync();

Projections

Project to value tuples
C#
var results = await g
    .V<Airport>()
    .Project(p => p
        .ToTuple()
        .By(x => x.Code)
        .By(x => x.Name))
    .ToArrayAsync();
Project to value tuples with sub-query
C#
var results = await g
    .V<Airport>()
    .Project(p => p
        .ToTuple()
        .By(x => x.Code)
        .By(__ => __
            .Out<Route>()
            .OfType<Airport>()
            .Values(x => x.Code)
            .Fold()))
    .ToArrayAsync();
Project to dynamics
C#
var results = await g
   .V<Airport>()
   .Project(p => p
       .ToDynamic()
       .By(x => x.Code)
       .By(x => x.Name))
   .ToArrayAsync();
Project to dynamics with explicit names
C#
var results = await g
   .V<Airport>()
   .Project(p => p
       .ToDynamic()
       .By(x => x.Code)
       .By(x => x.Name))
   .ToArrayAsync();
Project to custom data structure instances
C#
var results = await g
    .V<Airport>()
    .Project(p => p
        .To<DepartureAndDestinationRecord>()
        .By(
            x => x.DepartureCode,
            x => x.Code)
        .By(
            x => x.DestinationCodes,
            __ => __
                .Out<Route>()
                .OfType<Airport>()
                .Values(x => x.Code)
                .Fold()))
   .ToArrayAsync();

Aggregates

Fold
C#
var results = await g
    .V<Airport>()
    .Map(__ => __
        .Out<Route>()
        .OfType<Airport>()
        .Values(x => x.Code)
        .Fold())
    .ToArrayAsync();
Sum
C#
var results = await g
    .V<Airport>()
    .Where(x => x.Code == "JFK")
    .OutE<Route>()
    .Values(x => x.Dist)
    .Sum()
    .FirstAsync();
Max
C#
var results = await g
    .V<Airport>()
    .Where(x => x.Code == "JFK")
    .Map(__ => __
        .OutE<Route>()
        .Values(x => x.Dist)
        .Max())
    .FirstAsync();

Groupings

Building upon the previously introduced concept of sub-DSL, building groups is another case where such a sub-DSL is employed:

Group airports by number of routes
C#
var results = await g
    .V<Airport>()
    .Group(g => g
        .ByKey(__ => __
            .OutE<Route>()
            .Count()))
    .ToArrayAsync();
Project to specific group values

The previous example illustrated a grouping by numbers without explicitly specifying a value, resulting in the projection of values to the airports themselves. However, it's worth noting that we can also project values specifically to the airport codes:

C#
var results = await g
    .V<Airport>()
    .Group(g => g
        .ByKey(__ => __
            .OutE<Route>()
            .Count())
        .ByValue(__ => __
            .Values(x => x.Code)))
    .ToArrayAsync();

Loops

Again, there is a specific DSL for loops that, through a builder, lets the user chain reasonable combinations of the Emit, Repeat, Until and Times operators:

Where can we go with three flights?
C#
var results = await g
    .V<Airport>()
    .Map(__ => __
        .Loop(l => l
            .Repeat(__ => __
                .Out<Route>()
                .OfType<Airport>())
            .Times(3))
        .Values(x => x.Code)
        .Fold())
    .ToArrayAsync();
Unroll all the paths from JFK that don't become cyclic
C#
var results = await g
    .V<Airport>()
    .Where(x => x.Code == "JFK")
    .Loop(l => l
        .Repeat(__ => __
            .Out<Route>()
            .OfType<Airport>())
        .Emit()
        .Until(__ => __
            .CyclicPath()))
    .Dedup()
    .Values(x => x.Code)
    .ToArrayAsync();