Graph-IT

This document describes the Graph-IT query language. In the examples below, we use the database described in the Multi-modelity and Demo database documents.

Graph-IT is both a client-side query language and the internal NitrosBase language. For example, SQL optimizer translates SQL queries into Graph-IT.

Currently, Graph-IT is available as a С++ API only. The main class of the API is CQueryConstructor which has methods that allow you to build queries (or query execution plans, in the case of querying in other languages). In the future, Graph-IT interpreter will be implemented, which allows to you parse Graph-IT queries as text strings.

The distribution contains sample C++ code (the sample2 directory) that performs Graph-IT queries to NitrosBase.

Quick start

Example queries

Simple filtering

graph
    .v(name == "Maria" && town == "London")
    .as(person)
    .select(person)

The above sequence of instructions finds and selects records corresponding to citizens from towns named "London" with the name "Maria":

  • graph — keyword which means that the Graph-IT query is started.
  • v(name == "Maria" && city == "London") — iterates over records where the name field value equal to "Maria" and the city field value is equal to "London".
  • as(person) — records iterated by v() (more precisely, their ids) are stored in the person variable.
  • **select(person) — records from the person variable are placed in results.

Link traversing

graph
    .v("person099")
    .out(friend).as(person)
    .select(person)

The above sequence selects friends of a particular person:

  • graph — keyword which means that the Graph-IT query is started.
  • v("person099") — finds record which has the id field value equal to "person099".
  • out(friend) — iterates over records connected with the previously selected one via the friend link (for records selecting by out(), the link is incoming ).
  • as(person) — records iterating by out() (more precisely, their ids are stored in the person variable.
  • select(person) — records from the person variable are placed in results.

Who has friends who own a Bugatti?

graph
    .v(model == "Bugatti")
    .out(owner)
    .in(friend)
    .distinct()
    .as(person)
    .select(person)

The above sequence selects persons who have friends with cars of a particular model:

  • graph — keyword which means that the Graph-IT query is started.
  • v(model == "Bugatti") — iterates over records which have the model field value equal to "Bugatti".
  • out(owner) — iterates over records connected with the ones iterating by v() via the owner link (the link is outgoing for records iterating by v()).
  • in(friend) — iterates records connected with the ones iterated by out() via the friend link (the link is incoming for records iterated by out).
  • distinct() — makes records iterated by in() "unique": records with the same id are skipped. In particular, duplicate records wouldn't be stored to the person record at the next step.
  • as(person) — unique records iterating by in() are stored in the person variable.
  • select(person) — records from the person variable are placed in results.

Friends from the same city

graph
    .v("person999").as(p1)
    .get(city).as(city1)
    .out(friend, p2)
    .get(city).as(city2)
    .compare(city1 == city2 && p1 != p2)
    .select(p2)

The above sequence selects friends of a particular person who live in the same city:

  • graph — keyword which means that the Graph-IT query is started.
  • v("person999") — finds record which has the id field value equal to "person099".
  • as(p1) — record previously found is stored in the person variable.
  • get(city).as(city1) — stores the city field value of records iterating by v() in the city1 variable.
  • out(friend, p2) — iterates over records connected to ones iterating by v() via the friend link and stores them in the p2 variable.
  • get(city).as(city2) — stores the city field value of records iterating by out() in the city2 variable.
  • compare(city1 == city2 && p1 != p2) — skips records that do not satisfy filtering condition.
  • select(p2) — records from the p2 variable are placed in results.

Reference

v(...)

The v() method finds records and iterates over them, if necessary.

::: Important Any sequence of methods has to start with the v() method invocation. :::

There are the following three forms of the method:

1. If arguments are omitted, all graph records are selected.

graph.v().count().as(count).select(count)

2. If string value is passed, it is treated as the id field value.

graph.v("person099").as(person).select(person)

3. If filtering condition is passed, then records satisfying that condition will be selected.

graph.v(name="Maria").as(maria).select(maria)

Filtering conditions inside v uses indexes. If indexes are missing, one should use the compare method (see below).

out(...)

Proceeds to records connected with the current one by outgoing link and iterates over them. The name of the link can be passed as an argument.

graph.v("person099").out(friend).as(f).select(f)

In the example above, we get to the record identified as "person099". Then we follow the link friend to get to another entry (its identifier is stored in the variable f).

graph.v("person099").out(friend, f).select(f)

in(...)

Proceeds to records connected with the current one by outgoing link and iterates over them. The name of the link can be passed as an argument.

graph.v("person099").in(friend).as(g).select(g)

In the example above, we get to the record identified as "person099". Then we follow the incoming link friend to get to another entry (its identifier is stored in the variable g).

The method can be invoked with two arguments, the second one is a variable name. The example below is equivalent to the previous one.

compare(...)

The method stops graph traversal for combinations of variable values that don't meet the condition passed as argument.

graph
    .v("person099").as(p1)
    .in(owner).out(owner).as(p2)
    .compare(p1 != p2)
    .select(p2)

In the example above, all "co-owners" of a particular person's cars are selected.

get(...)

The method stores field values of iterated records in variables.

graph
    .v("person099").get(name, lastname)
    .select(name, lastname)

In the example above, values of the name and lastname fields are stored in the name and lastname variables respectively.

In order to use variables with non-default names (e.g., 'lastname' variable is named 'lastname'), as() should be invoked:

graph
    .v("person099").get(name, lastname.as(surname))
    .select(name, surname)

as(...)

Places field value obtained by get into variable passed as argument.

graph
    .v("person099")
    .get(name).as(firstname)
    .get(lastname).as(surname)
    .select(name, surname)

With a large number of fields, it is possible to use a more compact form:

graph
    .v("person099")
    .get(name.as(firstname), lastname.as(surname))
    .select(name, surname)

Additionally, the method allows you to create a reference to records in order to address them via gotovar() afterwards:

graph
    .v("person099").as(a).out(friend).as(b)
    .gotovar(a).get(name.as(name1))
    .gotovar(b).get(name.as(name2))
    .select(name1, name2)

gotovar

Traverses graph back to records references created via as():

graph
    .v("person099").as(a).out(friend).as(b)
    .gotovar(a).get(name.as(name1))
    .gotovar(b).get(name.as(name2))
    .select(name1, name2)

select

Variables passed as arguments are placed in results. Prior to this, values should be places in variables via as(). Otherwise, the method attempts to extract values of fields with the same names as variables of currently iterated records.

graph
    .v("person099").get(name, lastname.as(surname))
    .select(name, surname)

orderby

The method is invoked after selects() and order results by values of variables passed as arguments. By default, ordering is ascending.

graph
    .v(type="person").get(name, age).select(name, age)
    .orderby(name, age.desc())

limit

The method is invoked after orderby() and limits the number of results.

graph
    .v(type="person").get(name, age).select(name, age)
    .orderby(name.asc(), age.desc()).limit(10)

offset

The method is invoked after orderby() and excludes the first set of results.

graph
    .v(type="person").get(name, age).select(name, age)
    .orderby(name.asc(), age.desc()).limit(10).offset(100)

distinct

Makes unique iterated records or field values. In the example below, distinct() is used to reduce exhaustion.

graph
    .v("person009").out(friend).out(friend).distinct()
    .in(owner).distinct()
    .get(model).select(model)

The method can be invoked after select(). In that case, variables can be passed as arguments. Values of these variables are claimed to be unique in results.

graph
    .v(city="London").get(name, lastname).select(name, lastname)
    .distinct(lastname)

outexist

The method stops graph traversal for records that don't have an outgoing link to a particular record.

The first argument is a link name; the second one is a record id.

graph
    .v(type="car").outexist(owner, "person099").get(number)
    .select(number)

In the example above, plate numbers of cars that belong to a particular owner will be selected.

inexist

The method stops graph traversal for records that don't have an incoming link from a particular record.

The first argument is a link name, the second one is a record id.

graph.v(type="person").inexist(owner, "car001").get(name).select(name)

In the example above, all owners of a particular car are selected.

groupby

The method is invoked after select() and allows you to group results and calculate functions on aggregates. Aggregate functions are passed as shown below.

graph
    .v(type="person").get(city, age)
    .select(city, avg(age), count(age), min(age), max(age), sum(age))
    .groupby(city).orderby(city)

join

The method performs a quick search of records with the same field values.

In the example below, all namesakes of a particular car owner are selected. The join() method iterates over records that have the name field value being the same as that of records selected by get().

graph
    .v("car001").out(owner).get(name)
    .join(name).as(person)

inE & outE

The methods iterates over links instead of records. There are two ways to use these methods: count links and get their names. The methods can be invoked with an argument which is treated as a link name.

In the query below, persons who have more incoming "friend" links than outcoming ones are selected.

graph
    .v(type="person").as(p)
    .outE(friend).count().as(c1)
    .gotovar(p).
    .inE(friend).count().as(c2)
    .compare(c1 > c2)

optional & endopt

The optional() and endopt() methods mark blocks where link traversal is not possible for all records. For a record that doesn't have appropriate link(s), graph traversal will be continued with the method after the endopt(). Such blocks can be nested.

Let's select peoples' last names and their car numbers, assuming that not every person has a car:

graph
    .v(type="person")
    .get(lastname)
    .optional()
    .in(owner).get(number)
    .endopt().select(lastname, number)

start_union, next_union & end_union

These methods allow you to split a method sequence into a few sequences whose results should be taken together.

Let's say one is interested in cars that belong to a particular person or have a particular model.

graph.v(type="car").
    .start_union()
    .get(model).compare(model="Bugatti").as(c)
    .next_union()
    .outexist(owner, "person099").as(c)
    .end_union()
    .select(c)