Введение в ReQL
ReQL — это язык запросов RethinkDB. Он предлагает очень мощный и удобный способ манипулирования JSON-документами. Этот документ — лёгкое введение в концепции ReQL. Вам не обязательно читать его, чтобы эффективно работать с RethinkDB, но понимание основ поможет.
Хотите сразу писать полезные запросы? Прочитайте краткое руководство (10 минут).
ReQL отличается от других языков запросов NoSQL. Он основан на трёх ключевых принципах:
- ReQL интегрируется в ваш язык программирования. Запросы строятся путём вызова функций на языке программирования, который вы уже знаете. Вам не нужно конкатенировать строки или создавать специализированные JSON-объекты для запросов к базе данных.
-
Все запросы ReQL могут быть объединены в цепочку. Вы начинаете с таблицы и поэтапно добавляете трансформаторы в конец запроса, используя оператор
.. -
Все запросы выполняются на сервере. Хотя запросы строятся на клиенте на знакомом языке программирования, они выполняются полностью на сервере базы данных после вызова команды
runи передачи ей активного соединения с базой данных.
Давайте рассмотрим эти концепции подробнее.
Примечание: в следующих примерах используется драйвер Python, но большинство из них также применимы к драйверам RethinkDB для других языков.
ReQL интегрируется в ваш язык программирования
Вы начинаете использовать ReQL в своей программе аналогично тому, как используете другие базы данных:
from rethinkdb import RethinkDB # import the RethinkDB package
r = RethinkDB() # create a RethinkDB object
conn = r.connect() # connect to the server on localhost and default port
Но на этом сходство заканчивается. Вместо того, чтобы создавать строки и передавать их на сервер базы данных, вы получаете доступ к ReQL, используя методы из пакета rethinkdb.
r.table_create('users').run(conn) # create a table `users`
r.table('users').run(conn) # get an iterable cursor to the `users` table
Каждый запрос ReQL, от фильтров до обновлений и объединений таблиц, выполняется путём вызова соответствующих методов.
Преимущества данного подхода:
- Вы можете использовать ту же среду программирования и те же инструменты, к которым вы уже привыкли.
- Изучение языка ничем не отличается от изучения любой другой библиотеки.
- Вероятность возникновения проблем с безопасностью, связанных с инъекциями строк, минимальна.
Все запросы ReQL могут быть объединены в цепочку
В ReQL вы можете объединять команды в цепочку в конце других команд с помощью оператора .:
# Get an iterable cursor to the `users` table (we've seen this above)
r.table('users').run(conn)
# Return only the `last_name` field of the documents
r.table('users').pluck('last_name').run(conn)
# Get all the distinct last names (remove duplicates)
r.table('users').pluck('last_name').distinct().run(conn)
# Count the number of distinct last names
r.table('users').pluck('last_name').distinct().count().run(conn)
Практически все операции ReQL могут быть объединены в цепочку. Оператор . можно представить себе аналогично конвейеру Unix. Вы выбираете данные из таблицы и передаёте их в команду, которая преобразует их. Вы можете продолжать объединять трансформаторы до тех пор, пока ваш запрос не будет завершён. В ReQL данные движутся слева направо.
Даже если у вас есть кластер узлов RethinkDB, вы можете отправлять запросы на любой узел, и кластер создаст и выполнит распределённые программы, которые получат данные из соответствующих узлов, выполнят необходимые вычисления и предоставят вам конечные результаты, не беспокоясь об этом.
Преимущества данного подхода:
- Язык легко изучать, читать и изменять.
- Это естественный и удобный способ выражать запросы.
- Вы можете создавать запросы поэтапно, объединяя преобразования и проверяя промежуточные результаты.
ReQL эффективен
Выполнение на сервере
Хотя запросы создаются на клиенте, они отправляются на сервер только после вызова команды run. Все вычисления происходят на сервере — запросы не выполняются на клиенте и не требуют промежуточных обменов данными между клиентом и сервером. Например, вы можете сохранять запросы в переменных и отправлять их на сервер позже:
# Create the query to get distinct last names
distinct_lastnames_query = r.table('users').pluck('last_name').distinct()
# Send it to the server and execute
distinct_lastnames_query.run(conn)
Подробнее о реализации этой технологии см. в этой статье.
Ленивость
Запросы ReQL выполняются лениво:
# Get up to five user documents that have the `age` field defined
r.table('users').has_fields('age').limit(5).run(conn)
Для этого запроса RethinkDB выполнит достаточно работы, чтобы получить пять документов, и остановится, когда запрос будет удовлетворён. Даже если у вас нет ограничения на количество запросов, но вы используете курсор, RethinkDB выполнит ровно столько работы, чтобы позволить вам прочитать запрашиваемые данные. Это позволяет запросам выполняться быстро, не тратя циклы процессора, полосу пропускания сети и ввод-вывод с диска.
Как и большинство систем баз данных, ReQL поддерживает первичные и вторичные индексы для эффективного доступа к данным. Вы также можете создавать составные индексы и индексы, основанные на произвольных выражениях ReQL, чтобы ускорить сложные запросы.
Узнайте, как использовать первичные и вторичные индексы в RethinkDB.
Параллелизм
Все запросы ReQL автоматически максимально параллелизуются на сервере RethinkDB. Всякий раз, когда это возможно, выполнение запроса разбивается на ядра процессора, серверы в кластере и даже на несколько центров обработки данных. Если у вас есть большие и сложные запросы, требующие нескольких этапов обработки, RethinkDB автоматически разделит их на этапы, выполнит каждый этап параллельно и объединит данные для получения полного результата.
Оптимизация запросов
Хотя в RethinkDB пока нет полностью функционального оптимизатора запросов, ReQL разработан с учётом его наличия. Например, сервер имеет достаточно информации для упорядочивания цепочки для повышения эффективности или для использования альтернативных планов реализации для улучшения производительности. Эта функция будет реализована в будущих версиях RethinkDB.
Запросы ReQL являются функциональными
Пока мы видели только простые запросы без условий. ReQL поддерживает знакомый синтаксис для построения более сложных запросов:
# Get all users older than 30
r.table('users').filter(lambda user: user['age'] > 30).run(conn)
# If you'd like to avoid writing lambdas, RethinkDB supports an
# alternative syntax:
r.table('users').filter(r.row['age'] > 30).run(conn)
Этот запрос выглядит так же, как и любой другой код Python, который вы обычно пишете. Обратите внимание, что RethinkDB выполнит этот запрос на сервере, и он не выполняет код Python.
Драйверы клиента выполняют большую работу для проверки кода и преобразования его в эффективный запрос ReQL, который будет выполнен на сервере:
- Всякий раз, когда это возможно, драйверы клиента используют перегрузку операторов для поддержки выражений, таких как
user['age'] > 30. - Выражение
lambdaвыполняется только один раз на клиенте. Внутренне драйвер передаёт специальный объект функцииlambda, что позволяет построить представление запроса. Это представление затем отправляется на сервер по сети и оценивается в кластере.
Подробнее о реализации этой технологии см. в этой статье.
Эта технология имеет ограничения. Хотя большинство операций позволяют писать знакомый код, вы не можете использовать операции родного языка, имеющие побочные эффекты (например, print) или управляющие блоки (например, if и for). Вместо этого вы должны использовать альтернативные команды ReQL:
# WRONG: Get all users older than 30 using the `if` statement
r.table('users').filter(lambda user:
True if user['age'] > 30 else False).run(conn)
# RIGHT: Get all users older than 30 using the `r.branch` command
r.table('users').filter(lambda user:
r.branch(user['age'] > 30, True, False)).run(conn)
Преимущества данного подхода:
- Для большинства запросов вы можете писать знакомый и лёгкий для изучения код, не изучая специальные команды.
- Запросы эффективно передаются на сервер (через протокол протоколов), и оцениваются в кластере.
- RethinkDB имеет доступ к структуре запроса, что позволяет применять оптимизационные методы, аналогичные методам, доступным в SQL. Эта функция будет добавлена в RethinkDB в будущем.
Ограничения этой технологии:
- Операции родного языка, имеющие побочные эффекты или управляющие блоки, не могут использоваться внутри
lambda. Подробнее о реализации этой конструкции см. в этой статье.
Запросы ReQL могут быть составными
Вы можете комбинировать несколько запросов ReQL для создания более сложных запросов.
Комбинирование простых команд
Начнём с простого примера. RethinkDB поддерживает выполнение JavaScript на сервере с использованием встроенного движка V8 (в защищённом пространстве, конечно):
# Evaluate a JavaScript expression on the server and get the result
r.js('1 + 1').run(conn)
Поскольку ReQL является составным, вы можете комбинировать команду r.js с любым другим запросом. Например, давайте воспользуемся ею в качестве альтернативного способа получения всех пользователей старше 30 лет:
# Get all users older than 30 (we've seen this above)
r.table('users').filter(lambda user: user['age'] > 30).run(conn)
# Get all users older than 30 using server-side JavaScript
r.table('users').filter(r.js('(function (user) { return user.age > 30; })')).run(conn)
RethinkDB будет беспрепятственно оценивать команду js, вызывая движок V8 во время оценки запроса filter. Таким образом, вы можете объединять большинство запросов в всё более сложные.
Подзапросы
Предположим, у нас есть ещё одна таблица authors, и мы хотим получить список авторов, чьи фамилии также присутствуют в таблице users , о которой мы говорили ранее. Мы можем сделать это, объединив два запроса:
# Find all authors whose last names are also in the `users` table
r.table('authors').filter(lambda author:
r.table('users').pluck('last_name').contains(author.pluck('last_name'))).
run(conn)
Здесь мы используем запрос r.table('users').pluck('last_name') в качестве внутреннего запроса в filter, объединяя два запроса для создания более сложного. Даже если у вас есть кластер серверов, и обе таблицы authors и users фрагментированы, RethinkDB выполнит правильные действия, оценивая соответствующие части запроса выше на соответствующих фрагментах, объединяя части данных по мере необходимости и возвращая полный результат.
Несколько моментов, которые следует учесть относительно этого запроса:
- Мы составляем запрос на клиенте и вызываем
runтолько один раз. Помните, что вы должны вызватьrunтолько один раз на сложном запросе, когда вы готовы к его выполнению.- Вы также можете выполнить этот запрос с помощью команды inner_join.
Выражения
Создание запросов не ограничивается простыми командами и внутренними запросами. Вы также можете использовать выражения для выполнения сложных операций. Например, предположим, что мы хотим найти всех пользователей, чья зарплата и бонус не превышают 90 000 долларов, и увеличить их зарплату на 10%:
r.table('users').filter(lambda user: user['salary'] + user['bonus'] < 90000)
.update(lambda user: {'salary': user['salary'] + user['salary'] * 0.1})
Богатый набор команд
Помимо описанных здесь команд, ReQL поддерживает ряд сложных команд, которые можно комбинировать аналогично описанным здесь командам. Для получения дополнительной информации см. следующую документацию:
- Узнайте, как использовать map-reduce в RethinkDB.
- Узнайте, как использовать соединения таблиц в RethinkDB.
- Просмотрите справочник по API для получения информации о других командах.
Эта конструкция имеет следующие преимущества:
- В отличие от большинства NoSQL-языков, вы можете использовать ReQL для создания запросов любой сложности.
- Для сложных запросов нет нового синтаксиса или новых команд. После понимания принципа компоновки вы можете писать новые запросы, не изучая ничего нового.
- Подзапросы можно абстрагировать в переменные, что позволяет использовать модульное программирование так же, как и в большинстве других современных языков программирования.
И просто для интереса, ReQL умеет делать математические вычисления!
Если вам нужен ещё один калькулятор, ReQL тоже с этим справится!
# Add two plus two
(r.expr(2) + r.expr(2)).run(conn)
# You only need to specify `r.expr` once for the driver to work
(r.expr(2) + 2).run(conn)
# More algebra
(r.expr(2) + 2 / 2).run(conn)
# Logic
(r.expr(2) > 3).run(conn)
# Branches
r.branch(r.expr(2) > 3,
1, # if True, return 1
2 # otherwise, return 2
).run(conn)
# Compute the Fibonacci sequence
r.table_create('fib').run(conn)
r.table('fib').insert([{'id': 0, 'value': 0}, {'id': 1, 'value': 1}]).run(conn)
r.expr([2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).for_each(lambda x:
r.table('fib').insert({'id': x,
'value': (r.table('fib').order_by('id').nth(x - 1)['value'] +
r.table('fib').order_by('id').nth(x - 2)['value'])
})).run(conn)
r.table('fib').order_by('id')['value'].run(conn)
Подробнее
Чтобы узнать больше о ReQL, ознакомьтесь со следующими ресурсами:
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/introduction-to-reql/