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 thename
field value equal to"Maria"
and thecity
field value is equal to"London"
.as(person)
— records iterated byv()
(more precisely, theirid
s) are stored in theperson
variable.**select(person)
— records from theperson
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 theid
field value equal to"person099"
.out(friend)
— iterates over records connected with the previously selected one via thefriend
link (for records selecting byout()
, the link is incoming ).as(person)
— records iterating byout()
(more precisely, theirid
s are stored in theperson
variable.select(person)
— records from theperson
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 themodel
field value equal to"Bugatti"
.out(owner)
— iterates over records connected with the ones iterating byv()
via theowner
link (the link is outgoing for records iterating byv()
).in(friend)
— iterates records connected with the ones iterated byout()
via thefriend
link (the link is incoming for records iterated byout
).distinct()
— makes records iterated byin()
"unique": records with the sameid
are skipped. In particular, duplicate records wouldn't be stored to theperson
record at the next step.as(person)
— unique records iterating byin()
are stored in theperson
variable.select(person)
— records from theperson
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 theid
field value equal to "person099".as(p1)
— record previously found is stored in theperson
variable.get(city).as(city1)
— stores thecity
field value of records iterating byv()
in thecity1
variable.out(friend, p2)
— iterates over records connected to ones iterating byv()
via thefriend
link and stores them in thep2
variable.get(city).as(city2)
— stores thecity
field value of records iterating byout()
in thecity2
variable.compare(city1 == city2 && p1 != p2)
— skips records that do not satisfy filtering condition.select(p2)
— records from thep2
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)
id
field value.
2. If string value is passed, it is treated as the 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)