Apache Drill позволяет пользователям исследовать любые типы данных, используя ANSI SQL. Это замечательно, но Drill идет еще дальше и позволяет создавать пользовательские функции для расширения механизма запросов. Эти пользовательские функции обладают всей производительностью любой из примитивных операций Drill, но их использование делает написание этих функций немного сложнее, чем можно было ожидать.
В этой статье я шаг за шагом объясню, как создать и развернуть новую функцию, используя очень простой пример. Обратите внимание, что вы можете найти много информации о пользовательских функциях Drill в документации .
Давайте создадим новую функцию, которая позволит вам маскировать некоторые символы в строке, и давайте сделаем это очень просто. Новая функция позволит пользователю скрыть x символов в начале и заменить их на любые символы по своему выбору. Это будет выглядеть так:
1
|
MASK( 'PASSWORD' , '#' , 4 ) => ####WORD |
- Вы можете найти полный проект в следующем репозитории Github .
Как упоминалось ранее, мы могли бы представить множество дополнительных возможностей для этого, но моя цель — сосредоточиться на этапах написания пользовательской функции, а не столько на том, что делает функция.
Предпосылки
Для этого вам понадобится:
- Java Developer Kit 7 или более поздняя версия
- Apache Drill 1.1 или более поздняя версия
- Maven 3.0 или более поздняя версия
зависимости
Следующая зависимость Drill должна быть добавлена в ваш проект maven
1
2
3
4
5
|
< dependency > < groupId >org.apache.drill.exec</ groupId > < artifactId >drill-java-exec</ artifactId > < version >1.1.0</ version > </ dependency > |
Источник
Функция Mask
является реализацией DrillSimpleFunc
.
Разработчики могут создавать 2 типа пользовательских функций:
- Простые функции: эти функции имеют одну строку в качестве входных данных и выдают одно значение в качестве выходных
- Функции агрегации: которые будут принимать несколько строк в качестве входных данных и выдают одно значение в качестве выходных
Простые функции часто называют UDF, которые обозначают пользовательские функции. Функции агрегирования называются UDAF, что обозначает пользовательскую функцию агрегирования.
В этом примере нам просто нужно преобразовать значение столбца в каждой строке, поэтому достаточно простой функции.
Создать функцию
Первым шагом является реализация интерфейса DrillSimpleFunc
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
package org.apache.drill.contrib.function; import org.apache.drill.exec.expr.DrillSimpleFunc; import org.apache.drill.exec.expr.annotations.FunctionTemplate; @FunctionTemplate ( name= "mask" , scope= FunctionTemplate.FunctionScope.SIMPLE, nulls = FunctionTemplate.NullHandling.NULL_IF_NULL ) public class SimpleMaskFunc implements DrillSimpleFunc{ public void setup() { } public void eval() { } } |
Поведение функции определяется аннотациями (строка 6-10) * Имя функции * Область действия функции, в нашем случае Simple * Что делать, если значение равно NULL, в этом случае Reverse просто возвращает NULL
Теперь нам нужно реализовать логику функции, используя методы setup()
и eval()
.
-
setup
не требует пояснений, и в нашем случае нам не нужно ничего настраивать. -
eval
который является ядром функции. Как видите, этот метод не имеет никакого параметра и возвращает void. Итак, как это работает?
Фактически функция будет сгенерирована динамически (см. DrillSimpleFuncHolder ), а входные параметры и выходные держатели определяются с помощью держателей с помощью аннотаций. Давайте посмотрим на это.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import io.netty.buffer.DrillBuf; import org.apache.drill.exec.expr.DrillSimpleFunc; import org.apache.drill.exec.expr.annotations.FunctionTemplate; import org.apache.drill.exec.expr.annotations.Output; import org.apache.drill.exec.expr.annotations.Param; import org.apache.drill.exec.expr.holders.IntHolder; import org.apache.drill.exec.expr.holders.NullableVarCharHolder; import org.apache.drill.exec.expr.holders.VarCharHolder; import javax.inject.Inject; @FunctionTemplate ( name = "mask" , scope = FunctionTemplate.FunctionScope.SIMPLE, nulls = FunctionTemplate.NullHandling.NULL_IF_NULL ) public class SimpleMaskFunc implements DrillSimpleFunc { @Param NullableVarCharHolder input; @Param (constant = true ) VarCharHolder mask; @Param (constant = true ) IntHolder toReplace; @Output VarCharHolder out; @Inject DrillBuf buffer; public void setup() { } public void eval() { } } |
Нам нужно определить параметры функции. В этом случае у нас есть 3 параметра, каждый из которых определяется с @Param
аннотации @Param
. Кроме того, мы также должны определить возвращаемое значение с @Output
аннотации @Output
.
Параметры нашей функции маски:
- Обнуляемая строка
- Маска чар или строка
- Количество символов для замены, начиная с первого
Функция возвращает:
- Строка
Для каждого из этих параметров вы должны использовать класс держателя. Для String
это управляется с помощью VarCharHolder
или NullableVarCharHolder
-lines 21, 24,30-, который обеспечивает буфер для эффективного управления большими объектами. Так как мы манипулируем VarChar
вы также должны VarChar
другой буфер, который будет использоваться для выходной строки 33-. Обратите внимание, что Drill на самом деле не использует кучу Java для данных, обрабатываемых в запросе, но вместо этого сохраняет эти данные вне кучи и управляет жизненным циклом для нас без использования сборщика мусора Java.
Мы почти закончили, так как у нас есть подходящий класс, объект ввода / вывода, нам просто нужно реализовать сам метод eval()
и использовать эти объекты.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public void eval() { // get the value and replace with String maskValue = org.apache.drill.exec.expr.fn.impl.StringFunctionHelpers.getStringFromVarCharHolder(mask); String stringValue = org.apache.drill.exec.expr.fn.impl.StringFunctionHelpers.toStringFromUTF8(input.start, input.end, input.buffer); int numberOfCharToReplace = Math.min(toReplace.value, stringValue.length()); // build the mask substring String maskSubString = com.google.common.base.Strings.repeat(maskValue, numberOfCharToReplace); String outputValue = ( new StringBuilder(maskSubString)).append(stringValue.substring(numberOfCharToReplace)).toString(); // put the output value in the out buffer out.buffer = buffer; out.start = 0 ; out.end = outputValue.getBytes().length; buffer.setBytes( 0 , outputValue.getBytes()); } |
Код довольно прост:
- Получи саму маску — строка 4
- Получить значение — строка 5
- Получить номер символа для замены — строка 7
- Генерация новой строки с замаскированными значениями — строки 10/11
- Создайте и заполните выходной буфер — строки с 14 по 17
Этот код, однако, выглядит немного странным для того, кто привык читать код Java. Эта странность возникает из-за того, что окончательный код, который выполняется в запросе, будет фактически создан на лету. Это позволяет Drill использовать компилятор JIT (Just-in-Time) для максимальной скорости. Чтобы сделать это, вы должны соблюдать некоторые основные правила:
- Не используйте импорт, но вместо этого используйте полное имя класса , это то, что делается в строке 10 с классом
Strings
. (из API Google Guava, упакованного в Apache Drill) - Классы
ValueHolders
, в нашем случаеVarCharHolder
иIntHolder
должны управляться как структуры, поэтому вы должны вызывать вспомогательные методы, напримерgetStringFromVarCharHolder
иtoStringFromUTF8
. Вызов таких методов, какtoString
, приведет к очень серьезным проблемам.
Теперь мы готовы развернуть и протестировать эту новую функцию.
пакет
Еще раз, поскольку Drill будет генерировать исходный код, вы должны подготовить свой пакет таким образом, чтобы классы и источники функции присутствовали в пути к классам . Это отличается от способа, которым Java-код обычно упаковывается, но необходимо, чтобы Drill мог выполнять необходимую генерацию кода. Drill использует скомпилированный код для доступа к аннотациям и использует исходный код для генерации кода.
Самый простой способ сделать это — использовать maven для создания вашего проекта и, в частности, использовать maven-source-plugin, например, в своем файле pom.xml
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-source-plugin</ artifactId > < version >2.4</ version > < executions > < execution > < id >attach-sources</ id > < phase >package</ phase > < goals > < goal >jar-no-fork</ goal > </ goals > </ execution > </ executions > </ plugin > |
Теперь при сборке с использованием mvn package
Maven сгенерирует 2 файла jar:
- JAR по умолчанию с классами и ресурсами ( drill-simple-mask-1.0.jar )
- Вторая банка с источниками ( drie-simple-mask-1.0-sources.jar )
Наконец, вы должны добавить файл drill-module.conf
в папку ресурсов вашего проекта, чтобы сообщить Drill, что ваш jar-файл содержит пользовательскую функцию. Если у вас нет конкретной конфигурации для вашей функции, вы можете оставить этот файл пустым.
У нас все готово, теперь вы можете упаковать и развернуть новую функцию, просто упакуйте и скопируйте Jars в папку Drill стороннего производителя; $ DRILL_HOME / jars / 3rdparty, где $ DRILL_HOME — ваша папка установки Drill.
1
2
3
|
mvn clean package cp target/*.jar $DRILL_HOME/jars/3rdparty |
Перезапустите тренировку.
Бег !
Теперь вы сможете использовать свою функцию в своих запросах:
01
02
03
04
05
06
07
08
09
10
|
SELECT MASK(first_name, '*' , 3) FIRST , MASK(last_name, '#' , 7) LAST FROM cp.`employee.json` LIMIT 5; + ----------+------------+ | FIRST | LAST | + ----------+------------+ | ***ri | ###### | | ***rick | ####### | | ***hael | ###### | | ***a | #######ez | | ***erta | ####### | + ----------+------------+ |
Вывод
В этом простом проекте вы научились писать, развертывать и использовать пользовательские функции Apache Drill. Теперь вы можете расширить это, чтобы создать свою собственную функцию.
При расширении Apache Drill следует помнить одну важную вещь (используя пользовательскую функцию, плагин хранилища или формат): динамическая среда Drill генерирует динамически много кода. Это означает, что вам, возможно, придется использовать очень специфический шаблон при написании и развертывании ваших расширений. С нашей основной функцией это означало, что мы должны были:
- Развертывание классов И источников
- использовать полностью квалифицированные имена классов
- использовать классы держателей значений и вспомогательные методы для управления параметрами *
Ссылка: | Apache Drill: как создать новую функцию? от нашего партнера JCG Tugdual Grall в блоге Tug’s Blog . |