-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-9020][SQL] Support mutable state in code gen expressions #7392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,30 +46,47 @@ object GenerateOrdering extends CodeGenerator[Seq[SortOrder], Ordering[InternalR | |
| protected def create(ordering: Seq[SortOrder]): Ordering[InternalRow] = { | ||
| val ctx = newCodeGenContext() | ||
|
|
||
| val comparisons = ordering.zipWithIndex.map { case (order, i) => | ||
| val evalA = order.child.gen(ctx) | ||
| val evalB = order.child.gen(ctx) | ||
| val comparisons = ordering.map { order => | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In interpreted mode, we use the same expression to eval 2 rows, which means we only keep one copy of mutable states for that expression. However, in However, should we allow stateful expressions in order by?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have not figured out a case that need a stateful ordering, could we delay it until we really need it? |
||
| val eval = order.child.gen(ctx) | ||
| val asc = order.direction == Ascending | ||
| val isNullA = ctx.freshName("isNullA") | ||
| val primitiveA = ctx.freshName("primitiveA") | ||
| val isNullB = ctx.freshName("isNullB") | ||
| val primitiveB = ctx.freshName("primitiveB") | ||
| s""" | ||
| i = a; | ||
| ${evalA.code} | ||
| boolean $isNullA; | ||
| ${ctx.javaType(order.child.dataType)} $primitiveA; | ||
| { | ||
| ${eval.code} | ||
| $isNullA = ${eval.isNull}; | ||
| $primitiveA = ${eval.primitive}; | ||
| } | ||
| i = b; | ||
| ${evalB.code} | ||
| if (${evalA.isNull} && ${evalB.isNull}) { | ||
| boolean $isNullB; | ||
| ${ctx.javaType(order.child.dataType)} $primitiveB; | ||
| { | ||
| ${eval.code} | ||
| $isNullB = ${eval.isNull}; | ||
| $primitiveB = ${eval.primitive}; | ||
| } | ||
| if ($isNullA && $isNullB) { | ||
| // Nothing | ||
| } else if (${evalA.isNull}) { | ||
| } else if ($isNullA) { | ||
| return ${if (order.direction == Ascending) "-1" else "1"}; | ||
| } else if (${evalB.isNull}) { | ||
| } else if ($isNullB) { | ||
| return ${if (order.direction == Ascending) "1" else "-1"}; | ||
| } else { | ||
| int comp = ${ctx.genComp(order.child.dataType, evalA.primitive, evalB.primitive)}; | ||
| int comp = ${ctx.genComp(order.child.dataType, primitiveA, primitiveB)}; | ||
| if (comp != 0) { | ||
| return ${if (asc) "comp" else "-comp"}; | ||
| } | ||
| } | ||
| """ | ||
| }.mkString("\n") | ||
|
|
||
| val mutableStates = ctx.mutableStates.map { case (javaType, variableName, initialValue) => | ||
| s"private $javaType $variableName = $initialValue;" | ||
| }.mkString("\n ") | ||
| val code = s""" | ||
| public SpecificOrdering generate($exprType[] expr) { | ||
| return new SpecificOrdering(expr); | ||
|
|
@@ -78,6 +95,7 @@ object GenerateOrdering extends CodeGenerator[Seq[SortOrder], Ordering[InternalR | |
| class SpecificOrdering extends ${classOf[BaseOrdering].getName} { | ||
|
|
||
| private $exprType[] expressions = null; | ||
| $mutableStates | ||
|
|
||
| public SpecificOrdering($exprType[] expr) { | ||
| expressions = expr; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -151,79 +151,84 @@ object GenerateProjection extends CodeGenerator[Seq[Expression], Projection] { | |
| s"""if (!nullBits[$i]) arr[$i] = c$i;""" | ||
| }.mkString("\n ") | ||
|
|
||
| val mutableStates = ctx.mutableStates.map { case (javaType, variableName, initialValue) => | ||
| s"private $javaType $variableName = $initialValue;" | ||
| }.mkString("\n ") | ||
|
|
||
| val code = s""" | ||
| public SpecificProjection generate($exprType[] expr) { | ||
| return new SpecificProjection(expr); | ||
| } | ||
|
|
||
| class SpecificProjection extends ${classOf[BaseProject].getName} { | ||
| private $exprType[] expressions = null; | ||
| $mutableStates | ||
|
|
||
| public SpecificProjection($exprType[] expr) { | ||
| expressions = expr; | ||
| } | ||
|
|
||
| @Override | ||
| public Object apply(Object r) { | ||
| return new SpecificRow(expressions, (InternalRow) r); | ||
| return new SpecificRow((InternalRow) r); | ||
| } | ||
| } | ||
|
|
||
| final class SpecificRow extends ${classOf[MutableRow].getName} { | ||
| final class SpecificRow extends ${classOf[MutableRow].getName} { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made |
||
|
|
||
| $columns | ||
| $columns | ||
|
|
||
| public SpecificRow($exprType[] expressions, InternalRow i) { | ||
| $initColumns | ||
| } | ||
| public SpecificRow(InternalRow i) { | ||
| $initColumns | ||
| } | ||
|
|
||
| public int length() { return ${expressions.length};} | ||
| protected boolean[] nullBits = new boolean[${expressions.length}]; | ||
| public void setNullAt(int i) { nullBits[i] = true; } | ||
| public boolean isNullAt(int i) { return nullBits[i]; } | ||
| public int length() { return ${expressions.length};} | ||
| protected boolean[] nullBits = new boolean[${expressions.length}]; | ||
| public void setNullAt(int i) { nullBits[i] = true; } | ||
| public boolean isNullAt(int i) { return nullBits[i]; } | ||
|
|
||
| public Object get(int i) { | ||
| if (isNullAt(i)) return null; | ||
| switch (i) { | ||
| $getCases | ||
| public Object get(int i) { | ||
| if (isNullAt(i)) return null; | ||
| switch (i) { | ||
| $getCases | ||
| } | ||
| return null; | ||
| } | ||
| return null; | ||
| } | ||
| public void update(int i, Object value) { | ||
| if (value == null) { | ||
| setNullAt(i); | ||
| return; | ||
| public void update(int i, Object value) { | ||
| if (value == null) { | ||
| setNullAt(i); | ||
| return; | ||
| } | ||
| nullBits[i] = false; | ||
| switch (i) { | ||
| $updateCases | ||
| } | ||
| } | ||
| nullBits[i] = false; | ||
| switch (i) { | ||
| $updateCases | ||
| $specificAccessorFunctions | ||
| $specificMutatorFunctions | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = 37; | ||
| $hashUpdates | ||
| return result; | ||
| } | ||
| } | ||
| $specificAccessorFunctions | ||
| $specificMutatorFunctions | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = 37; | ||
| $hashUpdates | ||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object other) { | ||
| if (other instanceof SpecificRow) { | ||
| SpecificRow row = (SpecificRow) other; | ||
| $columnChecks | ||
| return true; | ||
| @Override | ||
| public boolean equals(Object other) { | ||
| if (other instanceof SpecificRow) { | ||
| SpecificRow row = (SpecificRow) other; | ||
| $columnChecks | ||
| return true; | ||
| } | ||
| return super.equals(other); | ||
| } | ||
| return super.equals(other); | ||
| } | ||
|
|
||
| @Override | ||
| public InternalRow copy() { | ||
| Object[] arr = new Object[${expressions.length}]; | ||
| ${copyColumns} | ||
| return new ${classOf[GenericInternalRow].getName}(arr); | ||
| @Override | ||
| public InternalRow copy() { | ||
| Object[] arr = new Object[${expressions.length}]; | ||
| ${copyColumns} | ||
| return new ${classOf[GenericInternalRow].getName}(arr); | ||
| } | ||
| } | ||
| } | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while you are at this, can you add scaladoc for GeneratedClass? It is not obvious what it does just by looking at it.