Представьте, что у вас есть хранилище данных с REST-интерфейсом. Пусть в нем хранится информация о книгах и вы хотите вывести список всех книг. Можно сделать метод «books», который будет возвращать нам список книг. Но при отображении списка обычно есть паджинация или ленивая подгрузка данных, а еще пользовать хочет фильтровать и сортировать данные. Когда мы добавляем поддержку мобильных устройств у нас появляется еще потребность как-то ограничить объем получаемых данных не передавая часть полей. Всю эту информацию должен уметь понимать почти любой метод получения списка объектов, т.к. списки отображаются с помощью специального виджета. И тут нам на помощь приходит Resource Query Language.
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 (<propеrty>,<array-of-values>) | Выбирает объекты, у которых значение указанного свойства входит в заданный массив свойств. |
in(name,(Silver,Gold)) |
| out (<propеrty>,<array-of-values>) | Выбирает объекты, у которых значение указанного свойства не входит в заданный массив свойств. | out(name,(Platinum,Gold)) |
| limit (<stаrt>,<numbеr>) | Возвращает заданное количество (number) объектов начиная с определённой (start) позиции. |
limit(0,2) |
| sort (<list of properties with + or — prefix>) | Сортирует список объектов по заданным свойствам (количество свойств неограниченно). Сначала список сортируется по первому из заданных свойств, затем по второму, и так далее. Порядок сортировки определяется префиксом: + — по возрастанию, — — по убыванию. |
sort(+hardware.memory,-hardware.diskspace) |
| select (<list оf attributes>) | Обрезает каждый объект до набора свойств, определенных в аргументах. | elect(name,hardware.memory,user) |
| values(<prоperty>) | Возвращает набор значений из указанного поля всех объектов | values(ve.name) |
| count() | Возвращает количество записей | in(name,(Silver,Gold))&count() |
| max(<prоperty?>) | Возвращает максимальное значение из указанного поля всех объектов | max(ve.memory) |
| min(<prоperty?>) | Возвращает минимальное значение из указанного поля всех объектов | min(ve.memory) |
Больше существующих операторов можно найти в официальной документации (http://www.persvr.org/rql/).
В технологии APS в RQL добавлена новая функция:
| Функция | Описание | Примеры |
|---|---|---|
| like (<prоperty>, <pаttern>) | Ищет заданный паттерн (pattern) в заданном свойстве (property). Эта функция аналогична оператору SQL LIKE, хоть и использует символ * вместо %. Чтобы определить в паттерне сам символ *, он должен быть процентно-кодированным, то есть надо писать %2A вместо *, см. примеры. Кроме того, в паттерне можно использовать символ ?, обозначающий, что любой символ в этой позиции является валидным. |
like(firstName,Jo*) |
И еще три специфичные для APS функции:
| Функция | Описание | Примеры |
|---|---|---|
| implementing (<basе-type>) | Возвращает список объектов (ресурсы и типы), реализующих базовый тип и включающих в себя сам базовый тип. | aps-standard.org/samples/user-mgmt/offer/1.0 |
| composing (<dеrived-type>) | Возвращает список типов, которые реализованы производным типом (derived type), включая сам производный тип. | aps-standard.org/samples/user-mgmt/offer/1.0 |
| linkedWith (<rеsource ID>) | Возвращает список ресурсов, которые связаны тем ресурсом, чей ID указан в качестве аргумента. APS-контроллер ищет все ссылки на ресурсы, включая внутренние системные ссылки. Например, актор admin/owner/referrer, имеющий доступ к ресурсу, тоже будет считаться «связанным» ресурсом. | linkedWith(220aa29a-4ff4-460b-963d-f4a3ba093a0a)
implementing(http://aps-standard.org/types/core/user/service/1.0), linkedWith(220aa29a-4ff4-460b-963d-f4a3ba093a0a) |
Логические операторы
Логические операторы позволяют посредством булевой логики объединить две и больше функций запроса. Все стандартные логические операторы имеют короткие алиасы.
| Оператор | Алиас | Примеры | Значение |
|---|---|---|---|
| and (<quеry>,<quеry>,...) | & , |
and (http://aps-standard.org/samples/user-mgmt/offer),like(name,*L*))
implementing(http://aps-standard.org/samples/user-mgmt/offer)&like(name,*L*)) implementing(http://aps-standard.org/samples/user-mgmt/offer),like(name,*L*)) |
Выбирает все предложения (offers), имена которых соответствуют нечувствительному к регистру паттерну *L* |
| or (<quеry>,<quеry>,...) | | ; |
implementing(http://aps-standard.org/samples/user-mgmt/offer1.0)&or(like(description,*free*),in(name,(Silver,Gold))) | Выбирает все предложения (offers), описания (description) которых соответствуют паттерну *free*, а также те, чьё имя Silver или Gold. |
В технологии APS в RQL также добавлен новый логический оператор отрицания:
| Оператор | Алиас | Примеры | Значение |
|---|---|---|---|
| not (<quеry>) | implementing(http://aps-standard.org/samples/user-mgmt/offer),not(like(name,*free*)) | Выбирает все предложения (offers), за исключением тех, чьё имя соответствует Хабр и Гиктаймс — 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 (<propеrty>,<valuе>) | =eq= | eq(aps.status,aps:ready) |
Выбирает все объекты, чей aps.status имеет значение aps:ready. |
| ne (<propеrty>,<valuе>) | =ne= | ne(aps.status,aps:ready) |
Выбирает все объекты, чей aps.status имеет значение aps:ready. |
| gt (<propеrty>,<valuе>) | =gt= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.memory=gt=1024) | Выбирает все предложения (offers), предоставляющие hardware.memory больше 1024. |
| ge (<propеrty>,<valuе>) | =ge= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.memory=ge=1024) | Выбирает все предложения (offers), предоставляющие hardware.memory больше или равно 1024. |
| lt (<propеrty>,<valuе>) | =lt= | implementing(http://aps-standard.org/samples/user-mgmt/offer),hardware.CPU.number=lt=16) | Выбирает все предложения (offers), предоставляющие hardware.CPU.number меньше 16. |
| le (<propеrty>,<valuе>) | =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
Автор: Odin (Ingram Micro)
