drill apache
Apache Drill允许用户使用ANSI SQL探索任何类型的数据。 这很棒,但是Drill的作用远远不止于此,它允许您创建自定义函数来扩展查询引擎。 这些自定义函数具有Drill基本操作的所有性能,但是允许执行这些性能会使编写这些函数的技巧比您预期的要复杂。
在本文中,我将使用一个非常基本的示例逐步说明如何创建和部署新功能。 请注意,您可以在文档中找到许多有关“ 钻取自定义功能”的信息 。
让我们创建一个新函数,使您能够屏蔽字符串中的某些字符,并使它变得非常简单。 新功能将允许用户从头开始隐藏x个字符,然后替换为他们选择的任何字符。 看起来像:
MASK( 'PASSWORD' , '#' , 4 ) => ####WORD
- 您可以在以下Github存储库中找到完整的项目。
如前所述,我们可以想象许多高级功能,但是我的目标是专注于编写自定义功能的步骤,而不是功能的作用。
先决条件
为此,您将需要:
- Java Developer Kit 7或更高版本
- Apache Drill 1.1或更高版本
- Maven 3.0或更高版本
依存关系
以下Drill依赖项应添加到您的Maven项目中
<dependency>
<groupId>org.apache.drill.exec</groupId>
<artifactId>drill-java-exec</artifactId>
<version>1.1.0</version>
</dependency>
资源
Mask
函数是DrillSimpleFunc
的实现。
开发人员可以创建两种类型的自定义函数:
- 简单函数:这些函数将一行作为输入,并产生一个值作为输出
- 聚合函数:接受多行作为输入并产生一个值作为输出
简单功能通常称为UDF,代表用户定义的功能。 聚合功能称为UDAF,代表用户定义的聚合功能。
在此示例中,我们只需要转换每一行上一列的值,因此一个简单的函数就足够了。
创建功能
第一步是实现DrillSimpleFunc
接口。
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行)*函数名称 *函数范围 ,在我们的例子中为简单*值为NULL时的操作,在这种情况下,Reverse将仅返回NULL
现在,我们需要使用setup()
和eval()
方法来实现函数的逻辑。
-
setup
是不言自明的,在我们这里,我们不需要设置任何东西。 -
eval
是该功能的核心。 如您所见,此方法没有任何参数,并返回void。 那么它是怎样工作的?
实际上,该函数将动态生成(请参阅DrillSimpleFuncHolder ),并且输入参数和输出保持器是通过注释的保持器定义的。 让我们来看看这个。
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
批注定义。 此外,我们还必须使用@Output
批注定义返回的值。
我们的mask函数的参数是:
- 可为空的字符串
- 掩码字符或字符串
- 从第一个字符开始替换的字符数
该函数返回:
- 一串
对于每个参数,您都必须使用一个holder类。 对于String
,这由VarCharHolder
或NullableVarCharHolder
21、24,30行管理,该行提供了一种缓冲区,可以有效地管理较大的对象。 由于我们正在处理VarChar
您还必须注入另一个缓冲区,该缓冲区将用于输出行33-。 请注意,Drill实际上并不将Java堆用于查询中正在处理的数据,而是将这些数据保留在堆外,并为我们管理生命周期,而无需使用Java垃圾收集器。
因为我们有了适当的类(输入/输出对象),所以我们差不多完成了,只需要实现eval()
方法本身,并使用这些对象即可。
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可以利用Java的即时(JIT)编译器来达到最大速度。 要使此工作有效,您必须遵守一些基本规则:
- 不要使用import,而是使用完全限定的类名 ,这是在第10行使用
Strings
类完成的。 (来自Apache Drill中打包的Google Guava API) - 该
ValueHolders
类,在我们的例子VarCharHolder
和IntHolder
应该被操纵结构一样,所以你必须调用辅助方法,例如getStringFromVarCharHolder
和toStringFromUTF8
。 调用诸如toString
类的toString
将导致非常严重的问题。
现在,我们准备部署和测试此新功能。
包
再一次,由于Drill将生成源代码,因此您必须以在classpath中存在该函数的类和源代码的方式准备软件包 。 这不同于通常打包Java代码的方式,但是Drill能够进行必要的代码生成是必需的。 Drill使用编译后的代码访问注释,并使用源代码进行代码生成。
一种简单的方法是使用maven构建项目,尤其是在pom.xml
文件中使用如下所示的maven-source-plugin :
<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 )
- 第二个带有源的jar( drill-simple-mask-1.0-sources.jar )
最后,您必须在项目的resources文件夹中添加drill-module.conf
文件,以告诉Drill您的jar包含自定义函数。 如果您没有为功能设置特定的配置,则可以将此文件保留为空。
一切准备就绪,您现在可以打包和部署新功能,只需打包并将Jars复制到Drill 3rd party文件夹中即可; $ DRILL_HOME / jars / 3rdparty,其中$ DRILL_HOME是您的Drill安装文件夹。
mvn clean package
cp target/*.jar $DRILL_HOME/jars/3rdparty
重新开始练习。
跑 !
现在,您应该可以在查询中使用您的函数了:
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 Function。 现在,您可以扩展它以创建自己的函数。
扩展Apache Drill(使用自定义功能,存储插件或格式)时,要记住的一件事是Drill运行时动态生成大量代码。 这意味着在编写和部署扩展时可能必须使用非常特定的模式。 使用我们的基本功能,这意味着我们必须:
- 部署类和源
- 使用完全合格的班级名称
- 使用值持有者类和辅助方法来操纵参数*
翻译自: https://www.javacodegeeks.com/2015/07/apache-drill-how-to-create-a-new-function.html
drill apache