Graph-IT

A graph consists of nodes and edges. Nodes are concrete entities, like person or car, and edges connect those nodes with each other. In example database we have two types of edges: friends and owner.

Both nodes and edges are stored in tables. Связь owner представлена в виде протого поля Foreign Key. Связь многие-ко-многим friends представлена в виде таблицы специального типа EDGES (См. «Демонстрационная база данных»).

Запросы

NitrosBase SQL Graph extensions позволяют выполнять графовые запросы. Начнем с простого пример. Мы хотим найти всех людей имеющих автомобиль и у которых есть друзья имеющие автомобиль той же модели.

SELECT p1.name, p1.lastname, p2.name, p2.lastname, c1.model 
MATCH (car c1)-[owner]->(person p1)-[friends]->(person p2)<-[owner]-(car c2)
WHERE c1.model = c2.model

Новое выражение MATCH является паттерном для описания какие объекты и связи нам нужны для выполнения запроса. MATCH заменяет выражение FROM в SQL запросах. Остальные выражения в запросе SELECT, ORDER BY, HAVING, INTO являются обычными выражениями SQL.

В данном примере выражение match описывает что:

  • есть некий узел c1 типа car
  • c1 по связи owner ссылается на узел p1 типа person

В документе описан поддерживаемый NitrosBase язык запросов Graph-IT. Во всех примерах в данном документе используется база данных, описанная в документах «Мультимодельность» и «Демонстрационная база данных»

Graph-IT - реализован в виде С++ API для построения запросов к базе данных. Основу языка составляет класс CQueryConstructor. Методы этого класса позволяют описать и построить план выполнения запроса.

В следующей версии NitrosBase будет добавлен интерпретатор Graph_IT для представления и передачи запросов в текстовом формате.

Быстрый старт

Примеры запросов

Простая фильтрация записей

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

В приведенной выше последовательности инструкций отбираются записи, соответствующие жителям Лондона с именем «Мария»:

  • graph — ключевое слово, обозначающее, что последующее являются запросом на языке Graph-it
  • v(name == "Maria" && city == "London") — перебираются все записи, имеющие значением поля name строку "Maria", а значением city — строку "London".
  • as(person) — записи (точнее, id записей), перебираемые вызовом v(), помещаются в переменную person.
  • select(person) — записи из переменной person помещаются в результаты.

Переход по связям

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

В приведенной выше последовательности инструкций отбираются записи, соответствующие друзьям определенного человека:

  • graph — ключевое слово, обозначающее начало запроса на Graph-it
  • v("person099") — отбирается запись, имеющая значением поля id строку "person099".
  • out(friend) — перебираются записи, связанные с последней отобранной записью связью friend (причем для отобранной записи эта связь является исходящей).
  • as(person) —.записи, перебираемые out(), (точнее, значения их id) помещаются в переменную person.
  • select(person) — записи из переменной person помещаются в результаты.

У кого есть друзья на «Бугатти»?

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

В приведенной выше последовательности инструкций отбираются записи, соответствующие людям, имеющим друзей — владельцев автомобилей «Бугатти»:

  • graph — ключевое слово, обозначающее начало запроса на Graph-it.
  • v(model == "Bugatti") — перебор записей со значением "Bugatti" поля model.
  • out(owner) — перебор записей, связанных с текущей записью из числа перебираемых методом v() исходящей для нее связью owner.
  • in(friend) — перебор записей, связанных с текущей записью из числа перебираемых методом out(), входящей для нее связью friend.
  • distinct() — перебираемые методом .in() записи «уникализуются»: при переборе пропускаются записи с уже встречавшимися id. В частности, такие записи не будут помещены в переменную person на следующем шаге.
  • as(person) — помещение записей, перебираемых на предыдущем шаге, в переменную person.
  • select(person) — помещение записей из переменной person в результаты.

Друзья человека, живущие с ним в одном городе

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

В приведенной выше последовательности инструкций отбираются записи, соответствующие друзьям определенного человека, живущим в одном с ним городе:

  • graph — ключевое слово, обозначающее начало запроса на Graph-it.
  • v("person999") — отбор записи с идентификатором "person999" (уникальный идентификатор записи — значение поля id).
  • as(p1) — помещение записи, отобранной на предыдущем шаге, в переменную p1.
  • get(city1) — помещение значения поля city записи, отобранной вызовом v(), в переменную city1.
  • out(friend, p2) — перебираются записи, связанные с текущей записью из числа перебираемых вызовом v() исходящей для нее связью friend; перебираемые записи помещаются в переменную p2.
  • get(city2) — значения поля city у записей, перебираемых out(), помещаются в переменную city2.
  • compare(city == city2 && p1 != p2) — при переборе, производимом out(), пропускаются записи, не удовлетворяющие условию фильтрации (значения переменных соответствуют значениям на текущем шаге перебора в каждом из итераторов).
  • select(p2) — записи из переменной p2 помещаются в результаты

Пример приложения

В комплект поставки входит пример C++ программы (sample2), выполняющей запросы к базе данных с использованием языка Graph-IT.

Справочник

v(...)

Метод v() производит отбор записей — узлов графа данных.

ВАЖНО

Любая последовательность вызовов начинается с вызова этого метода

Реализованы три различные формы метода v().

1. Если аргументы не указаны, отбираются все записи графа

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

В примере выше произведен подсчет общего числа записей в графе.

2. Если указано строковое значение, оно трактуется как требуемое значение поля id

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

В примере выше отобрана запись со значением id, равным "person099"..

3. Если указано условие фильтрации, отбираются записи, удовлетворяющие условию

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

В примере выше отобраны записи со значением name, равным "Maria".

ВАЖНО

Условия фильтрации, задаваемый внутри функции v используют индексы. Если Вы хотите задать простую проверку записей по некоторым условиям, то воспользуйтесь нижеописанной функцией compare

out(...)

Перебирает записи, с которыми текущая запись из числа последних перебираемых имеет исходящую связь. Название связи может быть указано в скобках.

Записи, перебираемые out(), становятся «последними перебираемыми»: последовательно становятся «текущими» для последующих операций.

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

В примере выше перебираются и помещаются в переменную f записи, связанные с записью с идентификатором "person099" связью "friend", причем для связи с идентификатором "person099" эта связь является исходящей.


В данном примере мы встаем на запись с идентификатором "person099". Далее переходим по ссылке friend на другую запись (ее идентификатор сохраняется в переменной f).

Метод может быть вызван сразу с двумя аргументами, вторым аргументом является переменная, в которую помещаются отобранные записи. Пример ниже эквивалентен предыдущему.

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

in(...)

Перебирает записи, с которыми текущая запись из числа последних перебираемых имеет входящую для неё связь. Название связи может быть указано в скобках.

Записи, перебираемые in(), становятся «последними перебираемыми»: последовательно становятся «текущими» для последующих операций.

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

В данном примере мы встаем на запись с идентификатором "person099". Далее переходим по входящей ссылке friend на другую запись (ее идентификатор сохраняется в переменной g).

Метод может быть вызван сразу с двумя аргументами, вторым аргументом является переменная, в которую помещаются отобранные записи. Пример ниже эквивалентен предыдущему.

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

compare(...)

Метод приостанавливает обход графа для тех сочетаний значений переменных, для которых не выполняется условие фильтрации, указываемое в качестве аргумента.

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

В примере выше перебираются записи, которые соответствуют «совладельцам» всех автомобилей определенного владельца.

get(...)

У последних перебираемых записей значения полей, указанных в качестве аргументов, помещаются в одноименные переменные.

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

В примере выше значения полей name и lastname записи с идентификатором "person099" помещены в переменные name и lastname соответственно.

Для того чтобы отобрать значения полей в переменные с другими названиями, следует использовать as():

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

as(...)

Помещает значения полей у последних перебираемых записей в переменные, названия которых передаются в качестве аргументов.

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

При большом числе значений полей можно использовать другую форму записи:

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

Кроме того, позволяет запоминать ссылки на записи для последующего обращения к ним с помощью gotovar():

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

Производится возврат к записям, помещенным в переменные на предыдущих шагах с помощью 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

Переменные, указанные в качестве аргументов, помещаются в результаты запроса. Предварительно переменные должны быть отобраны с помощью as(), в противном случае будет предпринята попытка поместить в переменные значения одноименных полей у последних перебираемых записей.

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

orderby

Метод вызывается после вызова select(), упорядочивает выдачу по значениям переменных, передаваемых в качестве аргументов. По умолчанию упорядочивает по возрастанию, способ упорядочения может быть задан явно с помощью asc() и desc().

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

В примере выше перечень всех людей упорядочен по алфавиту (по возрастанию), при совпадении фамилий вначале идут обладатели большего age.

limit

Метод вызывается после вызова orderby(), ограничивает выдачу количеством результатов, указанным в качестве аргумента.

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

offset

Метод вызывается после вызова limit(), в выдаче опущено передаваемое в качестве аргумента количество результатов, идущих первыми в соответствии с упорядочением, заданным orderby().

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

distinct

Вызов метода «уникализует» последние отобранные записи или значения полей.
В примере ниже метод используется для сокращения перебора:

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

Метод может вызываться и после select(), в этом случае ему могут быть переданы аргументы: переменные, комбинации значений которых должны быть уникальными.

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

outexist

Метод приостанавливает обход графа для тех записей из числа последних перебираемых, у которых отсутствует исходящая связь с определенной записью.

Связь указывается первым аргументом, идентификатор записи — вторым.

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

В примере выше будут выведены номера всех автомобилей определенного владельца.

inexist

Метод приостанавливает обход графа для тех записей из числа последних перебираемых, у которых отсутствует входящая связь с определенной записью.

Связь указывается первым аргументом, идентификатор записи — вторым.

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

В примере выше будут выведены имена всех владельцев определенного автомобиля.

groupby

Метод вызывается после select() и позволяет сгруппировать результаты с вычислением агрегирующих функций. При этом сами агрегирующие функции указываются внутри select() как показано в примере ниже.

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

join

Вызов метода выполняет быстрый поиск записей по совпадению значений полей.

В примере ниже производится поиск однофамильцев всех владельцев определенного автомобиля. Вызов .join() перебирает записи, имеющие значением поля name текущее значение, отобранное вызовом get(name).

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

inE и outE

Вызов методов приводит к перебору ссылок графа данных, а не записей. Единственный способ использования — получение названий или подсчет связей.

Методы могут вызываться с аргументом — названием связи. В примере перебираются люди, имеющие исходящих связей «друг» больше, чем входящих.

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

optional и endopt

Вызовы optional() и end_opt() выделяют блок с вызовами методов, производящие переходы по связям, возможные не для всех записей, отобранных перед optional().

При отсутствии обход графа не прекращается, а продолжается методом, следующим за end_opt(). Такие блоки могут быть вложенными.

В примере ниже отбираются и выводятся фамилии людей и номера их автомобилей в предположении, что автомобили есть не у всех.

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

start_union, next_union и end_union

Вызовы методов позволяют разбить последовательность инструкций на несколько последовательностей, результаты выполнения которых следует объединить.

Пусть пользователя интересуют автомобили, принадлежащие определенному лицу либо имеющие определенную модель:

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