Graph-IT

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

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

ЗАМЕЧАНИЕ

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

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

Graph-IT является как клиентским языком формирования запросов так и внутренним языком для сервера базы данных. Например, обработчик SQL запросов строит план выполнения запроса на языке 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)

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


В данном примере мы встаем на запись с идентификатором "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)