Использование современных языков программирования в базе данных Oracle с помощью GraalVM

tl; dr: GraalVM - универсальная виртуальная машина для запуска множества языков программирования с высокой производительностью на одной платформе. Его можно легко встроить в существующие системы, такие как базы данных. Встраивая GraalVM в базу данных Oracle, мы можем предоставить множество современных языков и их экосистем для обработки транзакций, анализа данных и машинного обучения. Нам нужно поддерживать только одну среду выполнения независимо от количества поддерживаемых языков, в отличие от встраивания одной среды выполнения для каждого поддерживаемого языка. Базовые компоненты, такие как преобразование типов данных или внутренний драйвер SQL сервера, могут использоваться на всех языках с помощью GraalVM.

Вступление

В этой статье мы рассмотрим, что происходит, когда GraalVM и Oracle Database объединяются. Читатели, знакомые с Oracle Database, могут знать, что ее можно расширить с помощью определяемых пользователем функций, написанных на PL / SQL. Однако GraalVM, встроенный в Oracle Database, позволяет писать пользовательские функции также на различных основных языках программирования, таких как JavaScript и Python. Давайте посмотрим на короткий пример сеанса в SQL * Plus:

С помощью первой команды мы создаем новый исходный объект с именем hello.js в базе данных. Он содержит исходный код функции JavaScript greet (). Вторая команда необходима для преодоления разрыва между динамически типизированным миром JavaScript и статически типизированным миром Oracle Database. В результате мы можем вызывать функцию JavaScript greet (), как любую другую определяемую пользователем функцию в базе данных Oracle. Например, мы можем вызвать его из запроса SQL, как показано в примере выше.

GraalVM - универсальная и встраиваемая виртуальная машина

GraalVM - это универсальная виртуальная машина, которая запускает приложения, написанные на различных языках программирования (JavaScript, Python 3, Ruby, R, языки на основе JVM и языки на основе LLVM) с высокой производительностью на одной платформе. Поддержка нескольких языков программирования позволяет GraalVM совместно использовать базовую инфраструктуру, такую ​​как JIT-компиляция, управление памятью, конфигурация и инструменты, среди всех поддерживаемых языков. Совместное использование конфигурации и инструментов приводит к единообразию опыта разработчиков в современных проектах, использующих несколько языков программирования.

Поддержка многих языков - не единственное преимущество GraalVM. Хотя GraalVM в основном работает как часть JVM, он предлагает решение для создания собственных образов, написанных на языке программирования Java. Эта функция собственного образа позволяет скомпилировать все приложение Java, возможно, встраивая несколько гостевых языков, поддерживаемых GraalVM, и включая необходимую инфраструктуру виртуальной машины, в собственный исполняемый файл или общую библиотеку. Основными преимуществами собственного образа GraalVM являются сокращение времени запуска и уменьшение объема памяти. Это позволяет существующим приложениям и платформам, написанным не на Java, встраивать GraalVM в качестве разделяемой библиотеки и извлекать выгоду из ее универсальной технологии виртуальных машин.

Программирование баз данных

Когда дело доходит до баз данных, за последние десятилетия было доказано, что язык SQL является предпочтительным языком для запросов к данным. Тот факт, что в основном все новые языки запросов (например, для обработки потоков, графиков или документов) разработаны как диалекты SQL, лишь подчеркивает его успех. Однако ограничения SQL достигаются, как только необходимо реализовать более сложную бизнес-логику. Сообщество баз данных признало этот ранний и расширенный SQL с процедурными функциями, которые позволяют объединять несколько операторов SQL и вычислений в произвольно сложные рабочие процессы обработки данных. Наиболее популярным из таких расширений является PL / SQL, процедурное расширение Oracle для SQL. Эти процедурные расширения SQL специфичны для домена базы данных. Это объясняет, почему сообщества вокруг них довольно малы по сравнению с сообществами языков программирования общего назначения, таких как JavaScript и Python. Группа разработчиков JavaScript и Python значительно больше, чем группа разработчиков PL / SQL. Поэтому найти разработчиков на JavaScript или Python намного проще, чем разработчиков на PL / SQL. Более того, популярные современные языки включают огромные экосистемы легкодоступных библиотек с открытым исходным кодом в общедоступных реестрах программного обеспечения (например, NPM, PyPI). В этих реестрах легко найти высококачественные библиотеки практически для любой задачи.

Привлекательность современных языков программирования общего назначения в конечном итоге подтолкнула системы баз данных к их поддержке. Примеры варьируются от Java в базе данных Oracle до JavaScript в Microsoft Azure Cosmos DB и Python в Amazon Redshift. До сих пор интеграция нового языка программирования с системой баз данных предполагала внедрение совершенно новой среды выполнения с собственным управлением памятью, механизмом потоковой передачи и т. Д. Это не только большая работа. Это также значительно увеличивает сложность архитектуры и кодовой базы систем баз данных. GraalVM с его возможностями многоязычного программирования и поддержкой встраивания обеспечивает решение этой проблемы. Необходимо встраивать только одну среду выполнения, чтобы обеспечить высокопроизводительную реализацию нескольких языков программирования в системе баз данных.

Многоязычный движок

В Oracle мы в настоящее время работаем над встраиванием GraalVM в Oracle Database и MySQL. Мы называем эти расширения Multilingual Engine (MLE). В этой статье мы сосредоточимся только на MLE для Oracle Database. Oracle Database MLE на данный момент является экспериментальной функцией.

Помимо встраивания GraalVM в базу данных Oracle, мы также работаем над инструментами, которые делают работу с MLE максимально удобной. Например, в настоящее время мы разрабатываем инструменты, которые автоматически упаковывают все приложение и развертывают его в базе данных Oracle с помощью одной команды.

Еще одна важная область работы - это языки, которые мы делаем доступными в Oracle Database. Чтобы языки стали полезными, их нужно расширять. Например, нам нужен механизм преобразования, который может преобразовывать между типами базы данных и типами языков, а также мост между механизмом SQL базы данных Oracle и SQL API нового языка.

MLE предлагает два разных способа выполнения кода, написанного на языках, поддерживаемых MLE. Во-первых, хранимые процедуры и определяемые пользователем функции могут быть написаны на языке MLE. Во-вторых, предоставляется новый пакет PL / SQL под названием DBMS_MLE для динамических сценариев, т. Е. Определения анонимных сценариев во время выполнения и их выполнения. Сначала мы обсудим выполнение динамического сценария с помощью DBMS_MLE в следующем разделе. В последующих разделах объясняется, как создавать определяемые пользователем расширения, которые можно использовать из SQL и PL / SQL.

Специальное выполнение скриптов с динамическим MLE

DBMS_MLE может выполнять сценарии, которые представлены в виде строк в PL / SQL. Обмен данными со сценарием в обоих направлениях (внутрь и наружу) осуществляется через так называемые переменные связывания. Наконец, сценарий может печатать сообщения, которые будут помещены в выходной
буфер
базы данных. Давайте посмотрим на конкретный пример:

Анонимный блок PL / SQL в примере использует переменную script_source для хранения фрагмента кода JavaScript. Эта переменная передается в функцию DBMS_MLE.CREATE_SCRIPT () для создания нового динамического сценария MLE, который затем может быть выполнен с помощью функции DBMS_MLE.EXECUTE_SCRIPT (). Перед выполнением нашего сценария мы определяем и устанавливаем переменную связывания с именем hello через DBMS_MLE.BIND_VARIABLE (). Переменные связывания могут быть определены, установлены и прочитаны как в окружающей программе PL / SQL, так и в динамическом скрипте MLE. Сценарий использует встроенный драйвер MLE SQL (автоматически доступный как mle.sql) для запроса заработной платы всех сотрудников в таблице EMP. В демонстрационных целях мы создаем простую гистограмму для зарплат и помещаем ее в выходной буфер (console.log ()). Перед передачей управления обратно в PL / SQL сценарий манипулирует переменной связывания hello. Затем блок PL / SQL извлекает значение переменной связывания с помощью функции DBMS_MLE.VARIABLE_VALUE () и печатает его в выходной буфер. Чтобы выполнить блок PL / SQL, мы можем отправить его как один оператор в базу данных от любого клиента. Например, весь блок можно скопировать в сеанс SQL * Plus и выполнить, введя символ косой черты.

После выполнения анонимного блока PL / SQL, приведенного выше (например, в SQL * Plus), выходной буфер базы данных будет иметь следующее содержимое (показать с помощью SET SERVEROUTPUT ON в SQL * Plus или использовать DBMS_OUTPUT.GET_LINE () для получения):

Конечно, мы могли бы так же легко использовать Python для достижения той же цели:

При выполнении этого блока PL / SQL в выходной буфер помещаются следующие строки:

Выполнение динамического скрипта в MLE можно использовать, например, для переноса JavaScript и Python в APEX.

Совместное использование компонентов на разных языках

В наших первых примерах мы представили драйвер MLE SQL и показали, как его использовать из JavaScript и Python. Похоже, что модуль реализован на используемом языке, но это не так. Вместо того, чтобы реализовывать полный мост между языковым API SQL и механизмом SQL Oracle Database для каждого добавляемого языка, мы должны выполнить основную часть работы только один раз благодаря функции полиглотов GraalVM. Вкратце, функция полиглота позволяет языку, работающему на GraalVM, получать доступ к объектам и вызывать функции, принадлежащие другому языку. Поэтому мы реализовали базовые компоненты, необходимые для всех языков, такие как преобразование данных и драйвер MLE SQL, как новые внутренние языки, которые можно использовать непосредственно со всех других языков. Для реализации новых языков GraalVM предоставляет фреймворк Truffle, который мы использовали для этой цели. Мы просто добавили тонкий слой, зависящий от языка, поверх каждого языка MLE, чтобы скрыть некоторые внутренние компоненты и сделать их по-настоящему родными. Платформа Truffle позволяет не только реализовывать совместно используемые компоненты, но и полностью использовать спекулятивный JIT-компилятор GraalVM. В контексте баз данных последнее имеет первостепенное значение, поскольку преобразование данных часто является основным фактором затрат.

Хранимые процедуры MLE

Хотя запуск сценария, написанного на современном языке, на лету удобен во многих ситуациях, он не идеален для разработки больших и сложных приложений. Для динамического MLE требуется каркас в PL / SQL, и сторонние библиотеки нельзя использовать напрямую. Кроме того, код лучше всего управляется базой данных, как и данные. Чтобы раскрыть всю мощь MLE, мы позволяем постоянно хранить и поддерживать код пользователя в базе данных в виде модулей, состоящих из определяемых пользователем функций и хранимых процедур. Для безболезненной упаковки и развертывания модулей мы планируем предоставить внешние инструменты, которые делают все с помощью одной команды.

Хранимые процедуры позволяют разработчикам запускать код, который требует выполнения нескольких операторов SQL внутри процесса сервера базы данных. Это позволяет избежать дорогостоящих сетевых циклов между клиентом базы данных - обычно промежуточным программным обеспечением - и базой данных. Сегодня Oracle Database позволяет разработчикам реализовывать хранимые процедуры на PL / SQL или Java. С помощью MLE разработчики также могут реализовывать хранимые процедуры на JavaScript и Python.

Предположим, мы хотим поднять зарплату сотруднику, но запрещаем лицам, не являющимся руководителями, иметь зарплату более 10 000 долларов. Мы можем начать с функции JavaScript, которая обновляет зарплату сотрудника и возвращает новую зарплату:

Обратите внимание, что для повышения безопасности и производительности мы используем переменные связывания в операторе SQL. В этом конкретном случае мы устанавливаем значения связываемых переменных, задавая массив значений. Это означает, что позиция значения в массиве [raise, empno] определяет переменную связывания, которую оно заменяет (т. Е. Для первой переменной связывания будет установлено значение raise а второй переменной связывания будет присвоено значение empno). В качестве альтернативы связываемые переменные можно задавать по имени.

Затем мы можем определить функцию для проверки, является ли сотрудник менеджером:

С помощью этих двух вспомогательных функций мы теперь можем реализовать нашу бизнес-логику:

Назначение module.exports используется для экспорта функции salraise () в базу данных. Собирая все вместе в файл с именем load_salraise.js, мы можем добавить дополнительный код, который выполняет развертывание в базе данных:

Теперь мы можем запустить сценарий, который развертывает код модуля и регистрирует функцию salraise () как хранимую процедуру через Node.js:

Вновь созданную хранимую процедуру JavaScript можно вызывать как любую другую процедуру. Например, из SQL * Plus:

Пользовательские функции MLE

MLE также позволяет разработчикам реализовывать определяемые пользователем функции на JavaScript и Python. Разница в том, что UDF может использоваться как любая другая функция SQL, тогда как хранимая процедура не может быть вызвана из оператора SQL. Сообщество JavaScript создало большой набор программных пакетов. MLE позволяет разработчикам баз данных легко повторно использовать код из реестров программного обеспечения, таких как NPM.

Например, предположим, что мы хотим отфильтровать синтаксически недопустимые адреса электронной почты из большой таблицы базы данных. Для этого мы бы хотели повторно использовать пакет validator JavaScript. В частности, мы хотели бы использовать из него функцию isEmail ():

Сначала мы можем загрузить пакет валидатора с помощью диспетчера пакетов NPM:

Поскольку модуль MLE JavaScript всегда описывается как один файл, содержащий весь код, мы можем использовать Webpack для создания пакета, который экспортирует все функции:

Следующий код JavaScript, хранящийся, например, в файле с именем load_validator.js, можно использовать для развертывания модуля и обеспечения доступности функции isEmail ():

Затем мы можем выполнить сценарий развертывания через Node.js:

После развертывания мы можем вызвать функцию следующим образом:

Заключение

Мы рассмотрели, как Multilingual Engine (MLE) позволяет вам с помощью GraalVM использовать JavaScript и Python в базе данных Oracle, привнося их огромные экосистемы в ваши вычисления с большим объемом данных. С GraalVM мы не только можем быстро вводить новые языки в базу данных Oracle, но и иметь под рукой высокопроизводительный спекулятивный JIT-компилятор. Его можно использовать для генерации эффективного кода для критических частей запросов, таких как преобразование данных во время выполнения.

Загрузите предварительную версию Oracle Database MLE на базе Oracle Database 12.2 из Oracle Technology Network. Поэкспериментируйте с Multilingual Engine и ознакомьтесь с документацией.

Оставляйте нам любые отзывы в нашем OTN-сообществе, все предложения, идеи или проблемы приветствуются.