Spec-Zone.ru › RethinkDB java

Введение в ReQL

ReQL — это язык запросов RethinkDB. Он предоставляет очень мощный и удобный способ манипулирования JSON-документами. Этот документ — краткое введение в концепции ReQL. Вам не нужно его читать, чтобы эффективно работать с RethinkDB, но понимание основных принципов поможет.

Хотите сразу писать полезные запросы? Прочитайте краткое руководство (10 минут).

  • ReQL интегрируется в ваш язык программирования
  • Все запросы ReQL можно объединять
  • ReQL эффективен
  • Запросы ReQL функциональны
  • Запросы ReQL композируемы
  • И просто для интереса, ReQL умеет выполнять математические операции!
  • Подробнее

ReQL отличается от других языков запросов NoSQL. Он построен на трех ключевых принципах:

  1. ReQL интегрируется в ваш язык программирования. Запросы создаются путем вызова функций на языке программирования, который вы уже знаете. Вам не нужно конкатенировать строки или создавать специализированные JSON-объекты для запроса к базе данных.
  2. Все запросы ReQL можно объединять. Вы начинаете с таблицы и поэтапно добавляете трансформаторы в конец запроса, используя оператор ..
  3. Все запросы выполняются на сервере. Хотя запросы строятся на клиенте на знакомом языке программирования, они полностью выполняются на сервере базы данных после вызова команды 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)

Преимущества этого подхода:

  • Для большинства запросов вы можете писать знакомый, легко усваиваемый код, не изучая специальные команды.
  • Запросы эффективно передаются на сервер (через протокол Protobuf) и оцениваются в кластере.
  • 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:

  • Lambda-функции в RethinkDB
  • Введение в map-reduce
  • Введение в соединения
  • Справочник по API

© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/introduction-to-reql/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API