【软件工程实践】Hive研究-Blog11
2021SC@SDUSC
研究内容介绍
本人负责的是负责的是将查询块QB转换成逻辑查询计划(OP Tree)
如下的代码出自apaceh-hive-3.1.2-src/ql/src/java/org/apache/hadoop/hive/ql/plan中,也就是我的分析目标代码。之前的Hive研究-Blog10中已经解析了ptf 文件夹下的OrderDef.java文件以及OrderExpressionDef.java文件,PartionDef.java文件还有WindowTableFunctionDef.java文件,做足了准备工作。那么这周我们就继续研究在ptf文件夹下剩余代码。
PartitionedTableFunctionDef.java文件代码解析
我们首先附上整个java文件代码
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hive.ql.plan.ptf;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.hive.ql.parse.PTFInvocationSpec;
import org.apache.hadoop.hive.ql.plan.Explain;
import org.apache.hadoop.hive.ql.plan.Explain.Level;
import org.apache.hadoop.hive.ql.udf.ptf.TableFunctionEvaluator;
@Explain(displayName = "Partition table definition", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public class PartitionedTableFunctionDef extends PTFInputDef {
private String name;
private String resolverClassName;
private ShapeDetails rawInputShape;
private boolean carryForwardNames;
private PTFInputDef input;
private List<PTFExpressionDef> args;
private PartitionDef partition;
private OrderDef order;
private TableFunctionEvaluator tFunction;
boolean transformsRawInput;
private transient List<String> referencedColumns;
@Explain(displayName = "name", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ShapeDetails getRawInputShape() {
return rawInputShape;
}
@Explain(displayName = "raw input shape")
public ShapeDetails getRawInputShapeExplain() {
return rawInputShape;
}
public void setRawInputShape(ShapeDetails rawInputShape) {
this.rawInputShape = rawInputShape;
}
public boolean isCarryForwardNames() {
return carryForwardNames;
}
public void setCarryForwardNames(boolean carryForwardNames) {
this.carryForwardNames = carryForwardNames;
}
@Override
public PTFInputDef getInput() {
return input;
}
public void setInput(PTFInputDef input) {
this.input = input;
}
public PartitionDef getPartition() {
return partition;
}
@Explain(displayName = "partition by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getPartitionExplain() {
if (partition == null || partition.getExpressions() == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (PTFExpressionDef expression : partition.getExpressions()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
}
return builder.toString();
}
public void setPartition(PartitionDef partition) {
this.partition = partition;
}
public OrderDef getOrder() {
return order;
}
public void setOrder(OrderDef order) {
this.order = order;
}
@Explain(displayName = "order by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getOrderExplain() {
if (order == null || order.getExpressions() == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (OrderExpressionDef expression : order.getExpressions()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
builder.append(" ");
if (expression.getOrder() == PTFInvocationSpec.Order.ASC) {
builder.append("ASC ");
} else {
builder.append("DESC ");
}
if (expression.getNullOrder() == PTFInvocationSpec.NullOrder.NULLS_FIRST) {
builder.append("NULLS FIRST");
} else {
builder.append("NULLS LAST");
}
}
return builder.toString();
}
public TableFunctionEvaluator getTFunction() {
return tFunction;
}
public void setTFunction(TableFunctionEvaluator tFunction) {
this.tFunction = tFunction;
}
public List<PTFExpressionDef> getArgs() {
return args;
}
public void setArgs(List<PTFExpressionDef> args) {
this.args = args;
}
@Explain(displayName = "arguments")
public String getArgsExplain() {
if (args == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (PTFExpressionDef expression : args) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
}
return builder.toString();
}
public void addArg(PTFExpressionDef arg) {
args = args == null ? new ArrayList<PTFExpressionDef>() : args;
args.add(arg);
}
public PartitionedTableFunctionDef getStartOfChain() {
if (input instanceof PartitionedTableFunctionDef ) {
return ((PartitionedTableFunctionDef)input).getStartOfChain();
}
return this;
}
@Explain(displayName = "transforms raw input", displayOnlyOnTrue=true)
public boolean isTransformsRawInput() {
return transformsRawInput;
}
public void setTransformsRawInput(boolean transformsRawInput) {
this.transformsRawInput = transformsRawInput;
}
public String getResolverClassName() {
return resolverClassName;
}
public void setResolverClassName(String resolverClassName) {
this.resolverClassName = resolverClassName;
}
@Explain(displayName = "referenced columns")
public List<String> getReferencedColumns() {
return referencedColumns;
}
public void setReferencedColumns(List<String> referencedColumns) {
this.referencedColumns = referencedColumns;
}
}
开始解析。
全局变量解析(1)
还是像以前一样,我们先从解析导入的包开始。
org.apache.hadoop.hive.ql.parse.PTFInvocationSpec导入包解析
我们首先到官网的API上直接查找对于这个包的解析:
我们可以看出,这相当于一个包,而这个包里面包含着很多的类,其中的NullOrder类以及Order类我们已经在Blog10中接触过了。在这个类中也有一些方法,但大多都是getter和setter方,故没有太多的解析必要,等到下文需要使用到时我们再回过头来进行解析。
org.apache.hadoop.hive.ql.plan.Explain导入包解析
我们首先到官网的API上直接查找对于这个包的解析:
这是一个类而不是一个包,与上一个包是不同的,它里面并没有包含着其他的类。其中并没有方法,有的只是一些基本变量,还有引入了其他类的变量。同样的,我们等到需要使用到这个类的时候再返回这里进行详细的解析。
org.apache.hadoop.hive.ql.plan.Explain.Level导入包解析
我们首先到官网的API上直接查找对于这个包的解析:
这个也是一个类。我们在先前的代码解析中已经接触过这个类了。等到下文中调用到这个类的内部变量或者方法时我们再返回这里进行详细的代码解析。
org.apache.hadoop.hive.ql.udf.ptf.TableFunctionEvaluator导入包解析
我们首先到官网的API上直接查找对于这个包的解析:
这个类的方法以及变量都非常的多,不过大部分都是getter与setter方法,等到我们下文需要用到这里面的方法或者变量时我们再返回到这里进行解析。
全局变量解析(2)
我们首先来看一下,这个类有哪一些全局变量:
private String name;
private String resolverClassName;
private ShapeDetails rawInputShape;
private boolean carryForwardNames;
private PTFInputDef input;
private List<PTFExpressionDef> args;
private PartitionDef partition;
private OrderDef order;
private TableFunctionEvaluator tFunction;
boolean transformsRawInput;
private transient List<String> referencedColumns;
首先对于这个ShapeDetails类型变量,这是一个什么样类型的变量?我们先从ptf文件夹下寻找是否有这么一个类。果不其然我们在ptf文件夹中找到了我们的目标:
然后时对于PTFInputDef类型的变量。同样的,我们在ptf文件夹下寻找,找到了该类:
然后是List类型变量args,其中List内部的元素类型为PTFExpressionDef类型,也在ptf文件夹中可以找到:
接着是PartitionDef类型的变量,也在ptf文件中可以找到:
对于OrderDef类型以及TableFunctionEvaluator类型的变量,那是从引入包中导入的变量类型,当我们需要使用到其中的内部变量或方法时我们再进行解析。
至此,准备工作已经全部完成,我们可以正式的开始解析代码了。
Explain设置(1)
@Explain(displayName = "name", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
这是一个什么样的语句?我们先来观看@Expain这个关键字。@Explain其实是在调用我们的导入包org.apache.hadoop.hive.ql.plan.Explain
。而在这个包里,有着其string类型的displayName变量。前面的displayName = "name"
语句是在将变量displayName的值设置为"name"。而explainLevels其实也是其类型为Explain.Level[]的变量explainLevels。而Explain.level也是一个导入的包,我们取看一下其内容。我们在查看其详细介绍时,发现了一个关键信息:
类Explain.level具有一个枚举型变量,其内容值分别为DEFAULT,EXTENDED,USER。而语句explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED }
则是设置explainLevels数组的值分别为USER,DEFAULT,EXTENDED。
参数name的getter与setter方法
@Explain(displayName = "name", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
对于参数name的getter与setter方法,用于得到和设置name参数。
方法gerRwaInputShape
public ShapeDetails getRawInputShape() {
return rawInputShape;
}
对于参数rawInputShape的getter方法,用于得到rawInputShape参数。
Explain设置(2)
@Explain(displayName = "raw input shape")
这次的Expain设置则是将变量displayName的值设置为"raw input shape"。
方法getRawInputShapeExplain
@Explain(displayName = "raw input shape")
public ShapeDetails getRawInputShapeExplain() {
return rawInputShape;
}
这个方法和上一个getter方法是不一样的,区别就在于这个方法上面的Explain设置。也就是说,返回的rawInputShape的Explain内部参数是不一样的。
方法setRawInputShape
public void setRawInputShape(ShapeDetails rawInputShape) {
this.rawInputShape = rawInputShape;
}
对于参数rawInputShape的setter方法,用于设置rawInputShape参数。
参数carryForwardNames的getter与setter方法
public boolean isCarryForwardNames() {
return carryForwardNames;
}
public void setCarryForwardNames(boolean carryForwardNames) {
this.carryForwardNames = carryForwardNames;
}
对于参数carryForwardNames的getter与setter方法,用于得到和设置carryForwardNames参数。
参数input的getter与setter方法
@Override
public PTFInputDef getInput() {
return input;
}
public void setInput(PTFInputDef input) {
this.input = input;
}
对于参数input的getter与setter方法,用于得到和设置input参数。
参数partition的getter与setter方法
public PartitionDef getPartition() {
return partition;
}
public void setPartition(PartitionDef partition) {
this.partition = partition;
}
对于参数partition的getter与setter方法,用于得到和设置partition参数。
Explain设置(3)
@Explain(displayName = "partition by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
这次的Expain设置则是将变量displayName的值设置为"partition by",而explainLevels设置为Explain类中的三个枚举型变量USER,DEFAULT,EXTENDED。
方法getPartitionExplain
@Explain(displayName = "partition by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getPartitionExplain() {
if (partition == null || partition.getExpressions() == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (PTFExpressionDef expression : partition.getExpressions()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
}
return builder.toString();
}
我们来看一下if语句的判断条件:首先是查看变量partition是否为空,或者partion的方法getExpressions()返回的结果是否为空。如果两者之中有一者为NULL值,则判断为true,返回NULL值。我们来看一下这个getExpressions()方法。我们来到PartitionDef.java文件中寻找getExpressions方法,找到了源码:
public List<PTFExpressionDef> getExpressions() {
return expressions;
}
而expressions的定义语句:
private List<PTFExpressionDef> expressions;
也就是说,这个getExpressions方法得到的结果是一个List列表,列表内部的变量类型为PTFExpressionDef类型。
回到源码,如果上述的两个语句均不为空,则进入到后续的处理方法体中。首先定义一个StringBuilder类型的变量。我们第一次遇到这个StringBuilder类型,我们先查阅一些资料来学习这个变量类型。大家要知道字符串(String)在进行拼接操作时,每一次拼接,都会构建一个新的String对象,这样耗时又浪费内存。那么解决方法就是 StringBuilder 类,就可以解决这个问题,且StringBuilder类中方法String类基本一样。我们来举个简单的例子来直观的认识这种变量类型:
//创建StringBuilder
StringBuilder sb = new StringBuilder("HIVE");
//添加
sb.append("是");
sb.append("研究大数据的");
sb.append("工具");
sb.append("。");
System.out.println(sb);//输出
输出:
HIVE是研究大数据的工具。
也就是说,这个StringBuilder是为了节约内存空间而定义的变量,其用法和作用与String类型变量差不多。
回到源码中来,我们继续往下看。下面进入到了for循环语句。首先调用partition.getExpressions方法,得到List<PTFExpressionDef>
变量,然后用遍历表达式遍历其所有元素,将每个元素赋予PTFExpressionDef类型变量expression。
接着进入到for循环的处理语句块中。首先是一个if语句,判断条件为builder的长度。如果builder的长度大于0,也就是有值在内部的,则添加上一个","。结束了if语句后,就直接向builder里面加入了expression.getExprNode().getExprString()返回值。我们一个个方法来解析,首先是这个getExprNode()方法,我们到PTFExpressionDef.java文件源码中寻找:
public ExprNodeDesc getExprNode() {
return exprNode;
}
这个exprNode是一个什么样子的变量,能干什么呢?我们再来查看其定义:
ExprNodeDesc exprNode;
这个ExprNodeDesc是什么样的一个类?经过查阅上下文,我们发现这是导入的一个包:org.apache.hadoop.hive.ql.plan.ExprNodeDesc;
这在Blog9中已经解释过了。我们直接引用:
我们直接查看调用其中的方法:getExprString()方法。
得到一个String类型的变量。
回到源码中来,得到这个exprString内部变量后,就添加至builder中。如此反复直到遍历完列表内的所有元素。
最后的return builder.toString()方法就是吧里面的所有语句整合成一个String类型的变量返回。
order变量的getter与setter方法:
public OrderDef getOrder() {
return order;
}
public void setOrder(OrderDef order) {
this.order = order;
}
对于参数order的getter与setter方法,用于得到和设置order参数。
Explain设置(4)
@Explain(displayName = "order by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
这次的Expain设置则是将变量displayName的值设置为"order by",而explainLevels设置为Explain类中的三个枚举型变量USER,DEFAULT,EXTENDED。
方法getOrderExplain
@Explain(displayName = "order by", explainLevels = { Level.USER, Level.DEFAULT, Level.EXTENDED })
public String getOrderExplain() {
if (order == null || order.getExpressions() == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (OrderExpressionDef expression : order.getExpressions()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
builder.append(" ");
if (expression.getOrder() == PTFInvocationSpec.Order.ASC) {
builder.append("ASC ");
} else {
builder.append("DESC ");
}
if (expression.getNullOrder() == PTFInvocationSpec.NullOrder.NULLS_FIRST) {
builder.append("NULLS FIRST");
} else {
builder.append("NULLS LAST");
}
}
return builder.toString();
}
首先方法开头是一个if判断语句。我们先来看一下if判断语句:如果order等于null或者是order.getExpressions()方法的返回值等于null则判断为true。那么这个order是什么类型的变量?我们来看一下order的定义语句:
private OrderDef order;
这是OrderDef类型的变量。我们再来从OrderDef.java文件源码中看一下方法order.getExpressions()的具体细节:
public List<OrderExpressionDef> getExpressions() {
return expressions;
}
这里的expressions变量的定义语句为:List<OrderExpressionDef> expressions;
。
回到源码。如果二者有一者为空,则返回null值。如果二者均不为空则进入方法体的第二部分。首先又是通过语句StringBuilder builder = new StringBuilder();
创建了一个StringBuilder,用于字符串的添加拼接操作。接着进入for循环语句。首先是从order.getExpressions()方法中得到List<>,而列表里面的变量类型为OrderExpressionDef类型,将其赋予中间变量expression。首先判断builder是否被使用过,如果被使用过则在后面添加分隔符",",否则跳过。接着往builder里面添加expression.getExprNode().getExprString()返回的结果。我们来看一下getExprNode()方法,它位于OrderExpressionDef.java文件内部:
public Order getOrder() {
return order;
}
public NullOrder getNullOrder() {
return nullOrder;
}
在文件内部并没有getExprNode()方法。但是我们观察一下构造方法:
public OrderExpressionDef() {}
public OrderExpressionDef(PTFExpressionDef e) {
super(e);
order = Order.ASC;
nullOrder = NullOrder.NULLS_FIRST;
}
从这里可以得出结论了:这里的expression.getExprNode().getExprString()返回的结果与上面的getPartitionExplain方法中同样的调用是一样的,这里就不再赘述。
回到源码,我们接着往下看。又遇到了多个if语句。首先第一个if语句的判断条件是判断expression.getOrder()的返回值是否是PTFInvocationSpec.Order.ASC。这很好理解,getOrder()方法已经在上面给出了,返回一个Order类型的变量。而PTFInvocationSpec.Order.ASC就是从Order类型的枚举集合中取出ASC实例,判断他们是否相等。如下图是Order的官方API接口的展示
如果相等,则在builder里面添加字符串"ASC "。否则则在builder里面添加字符串"DESC "。
对于后面的if语句里面的判断条件,其实是类似的。我们直接来看NullOrder的官方类解释:
同样的道理,如果判断为true,则往bulider内添加"NULLS FIRST",否则添加"NULLS LAST"。最后返回builder.toString(),也就是把builder里面的字符串全部拼接成一个字符串返回。
参数tFunction的getter与setter方法:
public TableFunctionEvaluator getTFunction() {
return tFunction;
}
public void setTFunction(TableFunctionEvaluator tFunction) {
this.tFunction = tFunction;
}
对于参数tFunction的getter与setter方法,用于得到和设置tFunction参数。
参数args的getter与setter方法
public List<PTFExpressionDef> getArgs() {
return args;
}
public void setArgs(List<PTFExpressionDef> args) {
this.args = args;
}
对于参数args的getter与setter方法,用于得到和设置args参数。
Explain设置(5)
@Explain(displayName = "arguments")
这次的Expain设置则是将变量displayName的值设置为"arguments"
方法getArgsExplain
@Explain(displayName = "arguments")
public String getArgsExplain() {
if (args == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (PTFExpressionDef expression : args) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(expression.getExprNode().getExprString());
}
return builder.toString();
}
方法体开头首先是一个if语句,判断args参数是否为空,如果为空则返回null,否则继续往下走。接着StringBuilder builder = new StringBuilder();
定义了一个StringBuilder,用于字符串的拼接。然后是一个for循环语句,遍历args内的参数,其类型为PTFExpressionDef并将值赋予临时变量expression。然后是if语句,判断builder是否被使用了,如果被使用了则添加一个分隔符",",否则将继续往下走。往下走则是往builder里边添加expression.getExprNode().getExprString()的返回值。而上面这个函数我们已经介绍过了,在这里我们不再赘述。方法最后,返回builder.toString(),即返回builder里面所有字符串拼接为一个字符串并返回。
方法addArg
public void addArg(PTFExpressionDef arg) {
args = args == null ? new ArrayList<PTFExpressionDef>() : args;
args.add(arg);
}
方法开头是一个经典的判断赋值语句。先判断args是否为空。如果为空,则将new ArrayList<PTFExpressionDef>()
即新建一个列表,列表内部元素类型为PTFExpressionDef类型的变量,然后赋予args。如果不为空,则不发生改变。然后往args里面添加传参arg。
方法getStartofchain
public PartitionedTableFunctionDef getStartOfChain() {
if (input instanceof PartitionedTableFunctionDef ) {
return ((PartitionedTableFunctionDef)input).getStartOfChain();
}
return this;
}
这个input是我们在开头定义的全局变量,其初始化语句为private PTFInputDef input;
,然后判断其是否为PartitionedTableFunctionDef类的一个实例。如果是,则先将input转化为一个PartitionedTableFunctionDef对象,然后再返回input.getStartofchain()方法的返回值,而这个方法我们在上文已经解析过了,在这里我们不再赘述。否则直接返回input。
Explain设置(6)
@Explain(displayName = "transforms raw input", displayOnlyOnTrue=true)
这次的Expain设置则是将变量displayName的值设置为"transforms raw input",并把自带的boolean类型的变量displayOnlyOnTrue的值设置为true。
方法isTransformsRawInput
@Explain(displayName = "transforms raw input", displayOnlyOnTrue=true)
public boolean isTransformsRawInput() {
return transformsRawInput;
}
参数transformsRawInput的getter方法,不过设置了Explain的一些变量。
方法setTransformsRawInput
public void setTransformsRawInput(boolean transformsRawInput) {
this.transformsRawInput = transformsRawInput;
}
参数transformsRawInput的setter方法,用于设置transformsRawInput参数。
参数resolverClassName的getter与setter方法
public String getResolverClassName() {
return resolverClassName;
}
public void setResolverClassName(String resolverClassName) {
this.resolverClassName = resolverClassName;
}
对于参数resolverClassName的getter与setter方法,用于得到和设置resolverClassName参数。
Explain设置(7)
@Explain(displayName = "referenced columns")
这次的Expain设置则是将变量displayName的值设置为"referenced columns"
方法getReferencedColumns
@Explain(displayName = "referenced columns")
public List<String> getReferencedColumns() {
return referencedColumns;
}
参数referencedColumns的getter方法,不过设置了Explain的一些变量。
方法setReferencedColumns
public void setReferencedColumns(List<String> referencedColumns) {
this.referencedColumns = referencedColumns;
}
参数referencedColumns的setter方法,用于设置referencedColumns参数。
至此,整个PartitionedTableFunctionDef.java文件代码我们全部解析完毕。
小结
通过本周的学习,我认识到了提前做好准备工作,分析代码时才能有条不紊,分析到位。上一周的准备工作做得十分的足够,所以这周的代码分析工作进展很快。通过本周的学习,我对HIVE有了更加深刻的认识,希望能够在下周的学习中学习收获到新的关于HIVE的知识并学以致用。