Resource Query Language (RQL) — это язык запросов, разработанный для использования в URI при работе с объекто-подобными структурами данных. С помощью RQL клиент может запрашивать у сервера список объектов соответствующих определенным правилам, т.е., по сути, это синтаксис, который описывает как запрашивать данные. Например, запрос выбирающий все книги авторства Перумова может быть записан как
eq(author,Перумов)
или в обычном формате URL: author=Перумов
.RQL можно рассматривать, в основном, как набор вложенных операторов, каждый из которых имеет множество аргументов. Он рассчитан на очень простую, но расширяемую грамматику, которая может быть использована в валидном URL запросе.
Как использовать RQL
RQL выражение содержит функции запроса или операторы сравнения, соединенные логическими операторами.
Типовые сценарии
Сначала разберем типовые сценарии. Для лучшего понимания будем сравнивать с аналогичными командами в MongoDB.
Пусть у нас есть список книг:
[
{ title: "Эльфийский клинок", year: 1993, series: "Кольцо тьмы" },
{ title: "Чёрное копьё", year: 1993, series: "Кольцо тьмы" },
{ title: "Адамант Хенны", year: 1995, series: "Кольцо тьмы" },
{ title: "Воин Великой Тьмы", year: 1995, series: "Летописи Хьёрварда" }
]
Выведем все книги из серии «Кольцо тьмы». В MongoDB мы бы сделали это так:
db.users.find({series: "Кольцо тьмы"})
в RQL это будет выглядеть так:
eq(series, "Кольцо тьмы")
или
series="Кольцо тьмы"
Такой запрос вернет нам три книги.
Теперь более сложный запрос: нам надо вывести все книги серии «Кольцо тьмы» изданные в 1995 году.
В MongoDB:
db.users.find({series: "Кольцо тьмы", year: 1995})
В RQL:
eq(series, "Кольцо тьмы"),eq(year, 1995)
Предыдущие запросы применялись к простым объектам. Но документы могут быть очень сложными по структуре. Например, добавим в нашу библиотеку новую книгу:
{ title: "Воин Великой Тьмы", year: 1995, series: "Летописи Хьёрварда", translations: { language: "English", title: "Godsdoom" } }
Здесь определяется вложенный объект с ключом
translations
. И чтобы найти все книги переведенные на английский язык, нам надо использовать точку.В MongoDB:
db.users.find({"translations.language": "English"})
В RQL:
eq(translations.language, "English")
Со временем наша библиотека выросла. Список книг не помещается на экран и мы решили показывать его постранично.
В MongoDB мы можем получить второй десяток записей следующим образом:
db.users.find().skip(10).limit(10)
В RQL:
limit(10,10)
Но показывать постранично мало. Еще хочется сортировать данные.
В MongoDB это будет:
db.users.find().sort({title: 1})
В RQL:
sort(+title)
Функции
Базовые функции стандарта:
Функция | Описание |
---|---|
Выбирает объекты, у которых значение указанного свойства входит в заданный массив свойств. Пример: in(name,(Silver,Gold)) |
|
Выбирает объекты, у которых значение указанного свойства не входит в заданный массив свойств. Пример: out(name,(Platinum,Gold)) |
|
Возвращает заданное количество (number) объектов начиная с определённой (start) позиции. Пример: limit(0,2) |
|
sort (<list of properties with + or — prefix>) | Сортирует список объектов по заданным свойствам (количество свойств неограниченно). Сначала список сортируется по первому из заданных свойств, затем по второму, и так далее. Порядок сортировки определяется префиксом: + — по возрастанию, — — по убыванию. Пример: sort(+memory,-diskspace) |
Обрезает каждый объект до набора свойств, определенных в аргументах. Пример: select(name,user) |
|
Возвращает набор значений из указанного поля всех объектов. Пример: values(ve.name) |
|
Возвращает количество записей. Пример: in(name,(Silver,Gold))&count() |
|
Возвращает максимальное значение из указанного поля всех объектов. Пример: max(ve.memory) |
|
Возвращает минимальное значение из указанного поля всех объектов. Пример: min(ve.memory) |
Больше существующих операторов можно найти в официальной документации.
В технологии APS в RQL добавлена новая функция:
Функция | Описание |
---|---|
Ищет заданный паттерн (pattern) в заданном свойстве (property). Эта функция аналогична оператору SQL LIKE, хоть и использует символ * вместо % . Чтобы определить в паттерне сам символ * , он должен быть процентно-кодированным, то есть надо писать %2A вместо * , см. примеры. Кроме того, в паттерне можно использовать символ ? , обозначающий, что любой символ в этой позиции является валидным.Примеры: like(firstName,Jo*) |
И еще три специфичные для APS функции:
Функция | Описание |
---|---|
Возвращает список объектов (ресурсы и типы), реализующих базовый тип и включающих в себя сам базовый тип. Пример: implementing (http://aps-standard.org/samples/user-mgmt/offer/1.0) |
|
Возвращает список типов, которые реализованы производным типом (derived type), включая сам производный тип. Пример: composing(http://aps-standard.org/samples/user-mgmt/offer/1.0) |
|
Возвращает список ресурсов, которые связаны тем ресурсом, чей ID указан в качестве аргумента. APS-контроллер ищет все ссылки на ресурсы, включая внутренние системные ссылки. Например, актор admin/owner/referrer, имеющий доступ к ресурсу, тоже будет считаться «связанным» ресурсом. Примеры: linkedWith(220aa29a-4ff4-460b-963d-f4a3ba093a0a) |
Логические операторы
Логические операторы позволяют посредством булевой логики объединить две и больше функций запроса. Все стандартные логические операторы имеют короткие алиасы.
Оператор | Алиас | Примеры |
---|---|---|
& , |
and (limit(0,2),like(name,*L*)) Значение Выбирает первые два предложения, имена которых соответствуют нечувствительному к регистру паттерну *L* |
|
or (<quеry>,<quеry>,...) | | ; |
or(like(description,*free*),in(name,(Silver,Gold))) Значение Выбирает все предложения, описания (description) которых соответствуют паттерну *free* , а также те, чьё имя Silver или Gold . |
В технологии APS в RQL также добавлен новый логический оператор отрицания:
Оператор | Примеры |
---|---|
not (<quеry>) | not(like(name,*free*)) Значение Выбирает все предложения, за исключением тех, чьё имя соответствует Хабр и Гиктаймс — RQL паттерну *free* . |
Примечание
- Оператор
and
является неявным RQL-оператором верхнего уровня. Например, выражениеhttp://hosting.com?and(arg1,arg2,arg3)
эквивалентноhttp://hosting.com?arg1,arg2,arg3
. - У оператора and приоритет выше, чем у or. Поэтому при использовании алиасов нужно заключать объединённые запросы в круглые скобки, тем самым определяя необходимый порядок обработки. Например,
implementing(<typе>),(prop1=eq=1|prop2=ge=2)
.
Операторы сравнения
Оператор сравнения используется для фильтрации объектов посредством сравнения одного из их свойств с заданным значением.
Оператор | Алиас | Примеры |
---|---|---|
=eq= | eq(aps.status,aps:ready) Значение Выбирает все объекты, чей aps.status имеет значение aps:ready . |
|
=ne= | ne(aps.status,aps:ready) Значение Выбирает все объекты, чей aps.status имеет значение aps:ready . |
|
=gt= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.memory=gt=1024) Значение Выбирает все предложения (offers), предоставляющие hardware.memory больше 1024. |
|
=ge= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.memory=ge=1024) Значение Выбирает все предложения (offers), предоставляющие hardware.memory больше или равно 1024. |
|
=lt= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.CPU.number=lt=16) Значение Выбирает все предложения (offers), предоставляющие hardware.CPU.number меньше 16. |
|
=le= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.CPU.number=le=16) Значение Выбирает все предложения (offers), предоставляющие hardware.CPU.number меньше или равно 16. |
Строковые типы сравниваются в лексикографическом порядке.
Значения
Функции запросов и операторы сравнения могут содержать следующие значения:
- Строковые (с использованием URL-кодирования)
- Числа
- Даты (в формате ISO UTC без двоеточия)
- Булевы
- Функции-значения (Value functions)
Функции-значения — это функции, возвращающие особые значения вроде
null
, true
, false
или пустое строковое значение. Все они применимы к определённым типам данных.Функция-значение | Применимые типы | Описания | Примеры |
---|---|---|---|
null() | Любой тип | Задаётся, если значение null |
name=eq=null() |
true() false() |
Булевы | Задаётся, если значение true или false |
disabled=eq=false() |
empty() | Строковые | Задаётся, если строковое значение является пустым (не null , но не содержит никаких символов) |
addressPostal.extendedAddress=eq=empty() |
Использование
Существует большое количество реализации парсеров RQL на различных языках программирования.
Реализация на JavaScript кроме парсера содержит еще и движок, который умеет применять RQL-запрос к массиву объектов.
Оригинальная реализация RQL на JavaScript есть в npmjs: https://www.npmjs.com/package/rql
Реализация с добавленной нами функциональностью также доступна через npm: https://www.npmjs.com/package/aps-rql
Комментарии (19)
raveclassic
04.07.2017 13:55+2А чем odata не устраивает?
garex
04.07.2017 13:58+3Чем ваш вариант лучше http://graphql.org/ ?
С допущением, что у проекта нет уже написанного REST-api?
raveclassic
04.07.2017 14:11+1У меня даже получилось заставить работать вот эту штуку
garex
04.07.2017 15:20Месье знает толк ))
А по сути — прикольно: клиент точно будет делать один запрос, а уже адаптер локально кучу. По сети улетит один результат.
raveclassic
04.07.2017 15:23Ну у нас просто REST заказчиком навязан, так как есть еще сервисы, которые будут им пользоваться. А под свое приложение хочется чего-то более высокоуровнего и чтобы еще и генерилось само.
Спойлер: graphql все-равно не пропустили, так что пока генерим ts через swagger-codegen.
BuranLcme
04.07.2017 15:49Наш проект старше чем graphql, а сам по себе RQL еще старше.
Плюс для нас удобно, что RQL запрос это просто строка. Например, в APS приложении структура экранов описывается XML-метафайлом и приложение может указать какие данные нужны для отображения этого View. Тогда в момент показа они уже будут загружены, а если обязательных данных нет, то View будет скрыто из меню и пользователь не сможет на него перейти. В виде строчного query это удобно описать, а в graphql формате, похоже, это будет выглядит весьма громоздко.arvitaly
04.07.2017 18:33+1GraphQL documents are expressed as a sequence of Unicode characters. However, with few exceptions, most of GraphQL is expressed only in the original non?control ASCII range so as to be as widely compatible with as many existing tools, languages, and serialization formats as possible and avoid display issues in text editors and source control.
в graphql формате, похоже, это будет выглядит весьма громоздко.
Не будет, а вернее, будет так, как вы опишите схему.
Простейший запросquery1
BuranLcme
04.07.2017 18:44Понятно что в строку можно записать почти что угодно. Минификатор в том числе этим и занимается. Но, судя по большинству примеров, запросы GraphQL это все-таки структуры и их лучше писать в отформатированном виде.
alibertino
05.07.2017 03:37+1Речь о том, что в GraphQL нет ничего громоздкого, скорее наборот, это один из самых минималистичных способов записи запроса данных (или представления данных). Мультидревовидная структура с поддержкой функций и директив покрывает большинство сценариев, как показали дальнейшие исследования, не хватает только последовательных запросов (batching), что легко решается.
https://dev-blog.apollodata.com/new-features-in-graphql-batch-defer-stream-live-and-subscribe-7585d0c28b07
hensew
04.07.2017 15:40Предусматривается ли возможность подписаться на изменения?
Как отреагирует limit(n, m) на добавление/удаление записей до n+m?BuranLcme
04.07.2017 15:52+1RQL это просто запросы к хранилищу. Никакого байндинга не подразумевается.
В APS UI отслеживание можно делать на уровне хранилищ. Повесить observer на хранилище и по его событиям обновлять виджеты, которые отображают данные из него.
shude
Таблички что-то совсем разъехались.
babylon
Чем вас JSONPath не устраивает?
shude
А причем JsonPath? Я про оформление поста…
BuranLcme
Посмотрел документацию по JSONPath. Не увидел там возможность поддержки функций типа like и логических операторов.