Представьте, что у вас есть хранилище данных с 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)