-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-2053][SQL] Add Catalyst expressions for CASE WHEN. #1055
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
a31d782
7d81e95
efd019b
5906f75
f2bcb9d
be54bc8
3f9ef0a
db51a85
9f84b40
2cf08bb
7392f3a
96870a8
aea3195
bb3d109
47d406a
7d2b7e2
1c1fbfc
f47ae7b
7ef284f
788a0d9
9d26ab8
caf9383
79d26fc
4226eb9
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 |
|---|---|---|
|
|
@@ -19,7 +19,6 @@ package org.apache.spark.sql.catalyst.expressions | |
|
|
||
| import org.apache.spark.sql.catalyst.analysis.UnresolvedException | ||
| import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan | ||
| import org.apache.spark.sql.catalyst.trees | ||
| import org.apache.spark.sql.catalyst.types.BooleanType | ||
|
|
||
|
|
||
|
|
@@ -202,3 +201,78 @@ case class If(predicate: Expression, trueValue: Expression, falseValue: Expressi | |
|
|
||
| override def toString = s"if ($predicate) $trueValue else $falseValue" | ||
| } | ||
|
|
||
| // scalastyle:off | ||
|
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. Why are your turning off style checking?
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. For the long url only; note that I turn it back on after this comment block.
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 usually use tinyurl for this :)
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 slightly prefer putting the original links in source code -- it'd be On Thu, Jun 12, 2014 at 1:12 PM, Cheng Lian notifications@github.com
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. Yeah, I didn't realize you turned it right back on. I'm OK now (really we should fix the style checker as long URLs are ok). Can we check that the turning the style off doesn't break the scala doc?
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.e. using Jekyll to build the docs & verify results? On Thu, Jun 12, 2014 at 1:33 PM, Michael Armbrust notifications@github.com
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. It would probably be faster to run
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. Just checked it locally & looks good. Spark has other similar places where scalastyle is switched off. |
||
| /** | ||
| * Case statements of the form "CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END". | ||
| * Refer to this link for the corresponding semantics: | ||
| * https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-ConditionalFunctions | ||
| * | ||
| * The other form of case statements "CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END" gets | ||
| * translated to this form at parsing time. Namely, such a statement gets translated to | ||
| * "CASE WHEN a=b THEN c [WHEN a=d THEN e]* [ELSE f] END". | ||
| * | ||
| * Note that `branches` are considered in consecutive pairs (cond, val), and the optional last | ||
| * element is the value for the default catch-all case (if provided). Hence, `branches` consists of | ||
| * at least two elements, and can have an odd or even length. | ||
| */ | ||
| // scalastyle:on | ||
| case class CaseWhen(branches: Seq[Expression]) extends Expression { | ||
| type EvaluatedType = Any | ||
| def children = branches | ||
| def references = children.flatMap(_.references).toSet | ||
| def dataType = { | ||
| if (!resolved) { | ||
| throw new UnresolvedException(this, "cannot resolve due to differing types in some branches") | ||
| } | ||
| branches(1).dataType | ||
| } | ||
|
|
||
| @transient private[this] lazy val branchesArr = branches.toArray | ||
| @transient private[this] lazy val predicates = | ||
| branches.sliding(2, 2).collect { case Seq(cond, _) => cond }.toSeq | ||
| @transient private[this] lazy val values = | ||
| branches.sliding(2, 2).collect { case Seq(_, value) => value }.toSeq | ||
|
|
||
| override def nullable = { | ||
| // If no value is nullable and no elseValue is provided, the whole statement defaults to null. | ||
| values.exists(_.nullable) || (values.length % 2 == 0) | ||
| } | ||
|
|
||
| override lazy val resolved = { | ||
| if (!childrenResolved) { | ||
| false | ||
| } else { | ||
| val allCondBooleans = predicates.forall(_.dataType == BooleanType) | ||
| val dataTypesEqual = values.map(_.dataType).distinct.size <= 1 | ||
| allCondBooleans && dataTypesEqual | ||
| } | ||
| } | ||
|
|
||
| /** Written in imperative fashion for performance considerations. Same for CaseKeyWhen. */ | ||
| override def eval(input: Row): Any = { | ||
| val len = branchesArr.length | ||
| var i = 0 | ||
| // If all branches fail and an elseVal is not provided, the whole statement | ||
| // defaults to null, according to Hive's semantics. | ||
| var res: Any = null | ||
| while (i < len - 1) { | ||
| if (branchesArr(i).eval(input) == true) { | ||
| res = branchesArr(i + 1).eval(input) | ||
| return res | ||
| } | ||
| i += 2 | ||
| } | ||
| if (i == len - 1) { | ||
| res = branchesArr(i).eval(input) | ||
| } | ||
| res | ||
| } | ||
|
|
||
| override def toString = { | ||
| "CASE" + branches.sliding(2, 2).map { | ||
| case Seq(cond, value) => s" WHEN $cond THEN $value" | ||
| case Seq(elseValue) => s" ELSE $elseValue" | ||
| }.mkString | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -811,6 +811,8 @@ private[hive] object HiveQl { | |
| val IN = "(?i)IN".r | ||
| val DIV = "(?i)DIV".r | ||
| val BETWEEN = "(?i)BETWEEN".r | ||
| val WHEN = "(?i)WHEN".r | ||
| val CASE = "(?i)CASE".r | ||
|
|
||
| protected def nodeToExpr(node: Node): Expression = node match { | ||
| /* Attribute References */ | ||
|
|
@@ -917,6 +919,21 @@ private[hive] object HiveQl { | |
| case Token(OR(), left :: right:: Nil) => Or(nodeToExpr(left), nodeToExpr(right)) | ||
| case Token(NOT(), child :: Nil) => Not(nodeToExpr(child)) | ||
|
|
||
| /* Case statements */ | ||
| case Token("TOK_FUNCTION", Token(WHEN(), Nil) :: branches) => | ||
| CaseWhen(branches.map(nodeToExpr)) | ||
| case Token("TOK_FUNCTION", Token(CASE(), Nil) :: branches) => | ||
| val transformed = branches.drop(1).sliding(2, 2).map { | ||
| case Seq(condVal, value) => | ||
| // FIXME (SPARK-2155): the key will get evaluated for multiple times in CaseWhen's eval(). | ||
| // Hence effectful / non-deterministic key expressions are *not* supported at the moment. | ||
| // We should consider adding new Expressions to get around this. | ||
| Seq(Equals(nodeToExpr(branches(0)), nodeToExpr(condVal)), | ||
|
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. Besides that can be optimized, it brings bug in some corner case. If the first child expression is deterministic-less, ( e.g. UDF rand() ), as computing it multiple times may get different values, which is not what we want here semantically.
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. Yeah, that is a very good point. I have updated the comment accordingly & added this JIRA to track this. |
||
| nodeToExpr(value)) | ||
| case Seq(elseVal) => Seq(nodeToExpr(elseVal)) | ||
| }.toSeq.reduce(_ ++ _) | ||
| CaseWhen(transformed) | ||
|
|
||
| /* Complex datatype manipulation */ | ||
| case Token("[", child :: ordinal :: Nil) => | ||
| GetItem(nodeToExpr(child), nodeToExpr(ordinal)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| NULL | ||
| 3 | ||
| 3 | ||
| 3 | ||
| NULL | ||
| NULL | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| NULL | ||
| 3 | ||
| 3 | ||
| 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| 4 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 4 | ||
| 4 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 4 | ||
| 3 | ||
| 3 | ||
| 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| 2 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 2 | ||
| 2 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| NULL | ||
| 3 | ||
| 3 | ||
| 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| 2 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 2 | ||
| 2 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 3 | ||
| 0 | ||
| 3 | ||
| 3 | ||
| 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 3 | ||
| 0 | ||
| 0 | ||
| 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| NULL | ||
| 3 | ||
| NULL | ||
| NULL | ||
| NULL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 0 | ||
| 3 | ||
| 0 | ||
| 0 | ||
| 0 |
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.
This is minor so I went ahead and merged anyway, but I think it would be better to enumerate these certain cases instead of leaving it up to the reader to understand the code below.
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.
Thanks for the note!