Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
45fe128
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 14, 2020
25fcb78
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 15, 2020
7a02c0b
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 17, 2020
ec66df6
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 17, 2020
b00abfe
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 20, 2020
f338a47
[SPARK-32290][SQL] NotInSubquery SingleColumn Optimize
leanken-zz Jul 20, 2020
519acd8
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 20, 2020
eebe303
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 21, 2020
39c2abe
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 21, 2020
924ed2b
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 21, 2020
b964535
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 21, 2020
d541067
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 22, 2020
b5831ad
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 23, 2020
4d26695
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 23, 2020
1bca273
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 24, 2020
f2cc24a
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 24, 2020
cdc04b2
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 24, 2020
19de983
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 27, 2020
36b024c
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 27, 2020
5bfec22
1. extract common trait NullAwareHashedRelation.
leanken-zz Jul 27, 2020
dcccdeb
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 27, 2020
7824c45
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 27, 2020
5e050c4
comment refine.
leanken-zz Jul 27, 2020
233eff6
[SPARK-32290][SQL] SingleColumn Null Aware Anti Join Optimize
leanken-zz Jul 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import org.apache.spark.internal.Logging
import org.apache.spark.sql.AnalysisException
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.expressions.aggregate.AggregateExpression
import org.apache.spark.sql.catalyst.optimizer.JoinSelectionHelper
import org.apache.spark.sql.catalyst.plans._
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.internal.SQLConf

trait OperationHelper {
type ReturnType = (Seq[NamedExpression], Seq[Expression], LogicalPlan)
Expand Down Expand Up @@ -388,3 +390,37 @@ object PhysicalWindow {
case _ => None
}
}

object ExtractSingleColumnNullAwareAntiJoin extends JoinSelectionHelper with PredicateHelper {

// TODO support multi column NULL-aware anti join in future.
// See. http://www.vldb.org/pvldb/vol2/vldb09-423.pdf Section 6
// multi-column null aware anti join is much more complicated than single column ones.

// streamedSideKeys, buildSideKeys
private type ReturnType = (Seq[Expression], Seq[Expression])

/**
* See. [SPARK-32290]
* LeftAnti(condition: Or(EqualTo(a=b), IsNull(EqualTo(a=b)))
* will almost certainly be planned as a Broadcast Nested Loop join,
* which is very time consuming because it's an O(M*N) calculation.
* But if it's a single column case O(M*N) calculation could be optimized into O(M)
* using hash lookup instead of loop lookup.
*/
def unapply(join: Join): Option[ReturnType] = join match {
case Join(left, right, LeftAnti,
Some(Or(e @ EqualTo(leftAttr: AttributeReference, rightAttr: AttributeReference),
IsNull(e2 @ EqualTo(_, _)))), _)
if SQLConf.get.optimizeNullAwareAntiJoin &&
e.semanticEquals(e2) =>
if (canEvaluate(leftAttr, left) && canEvaluate(rightAttr, right)) {
Some(Seq(leftAttr), Seq(rightAttr))
} else if (canEvaluate(leftAttr, right) && canEvaluate(rightAttr, left)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this. Is this covered in the positive/negative test case below ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, added a negative case.

Some(Seq(rightAttr), Seq(leftAttr))
} else {
None
}
case _ => None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2672,6 +2672,17 @@ object SQLConf {
.checkValue(_ >= 0, "The value must be non-negative.")
.createWithDefault(8)

val OPTIMIZE_NULL_AWARE_ANTI_JOIN =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to add version(). Here is the follow up PR #29335

buildConf("spark.sql.optimizeNullAwareAntiJoin")
.internal()
.doc("When true, NULL-aware anti join execution will be planed into " +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is only possible when we can buildSide data is small. Can you add to this doc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's single column NAAj, even if the buildSide is bigger than the autoBroadcaseThreshold, I think it's better to just using the optimize, because BroadcastNestedLoopJoin will be still worst than this optimization.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw SPARK-32644, so it is not always able to use this optimization, isn't?

"BroadcastHashJoinExec with flag isNullAwareAntiJoin enabled, " +
"optimized from O(M*N) calculation into O(M) calculation " +
"using Hash lookup instead of Looping lookup." +
"Only support for singleColumn NAAJ for now.")
.booleanConf
.createWithDefault(true)

/**
* Holds information about keys that have been deprecated.
*
Expand Down Expand Up @@ -3277,6 +3288,9 @@ class SQLConf extends Serializable with Logging {
def coalesceBucketsInJoinMaxBucketRatio: Int =
getConf(SQLConf.COALESCE_BUCKETS_IN_JOIN_MAX_BUCKET_RATIO)

def optimizeNullAwareAntiJoin: Boolean =
getConf(SQLConf.OPTIMIZE_NULL_AWARE_ANTI_JOIN)

/** ********************** SQLConf functionality methods ************ */

/** Set Spark SQL configuration properties. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ abstract class SparkStrategies extends QueryPlanner[SparkPlan] {
.orElse { if (hintToShuffleReplicateNL(hint)) createCartesianProduct() else None }
.getOrElse(createJoinWithoutHint())

case j @ ExtractSingleColumnNullAwareAntiJoin(leftKeys, rightKeys) =>
Seq(joins.BroadcastHashJoinExec(leftKeys, rightKeys, LeftAnti, BuildRight,
None, planLater(j.left), planLater(j.right), isNullAwareAntiJoin = true))

// If it is not an equi-join, we first look at the join hints w.r.t. the following order:
// 1. broadcast hint: pick broadcast nested loop join. If both sides have the broadcast
// hints, choose the smaller side (based on stats) to broadcast for inner and full joins,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ package org.apache.spark.sql.execution.adaptive
import org.apache.spark.sql.Strategy
import org.apache.spark.sql.catalyst.expressions.PredicateHelper
import org.apache.spark.sql.catalyst.optimizer.{BuildLeft, BuildRight}
import org.apache.spark.sql.catalyst.planning.ExtractEquiJoinKeys
import org.apache.spark.sql.catalyst.planning.{ExtractEquiJoinKeys, ExtractSingleColumnNullAwareAntiJoin}
import org.apache.spark.sql.catalyst.plans.LeftAnti
import org.apache.spark.sql.catalyst.plans.logical.{Join, LogicalPlan}
import org.apache.spark.sql.execution.SparkPlan
import org.apache.spark.sql.execution.{joins, SparkPlan}
import org.apache.spark.sql.execution.joins.{BroadcastHashJoinExec, BroadcastNestedLoopJoinExec}

/**
Expand All @@ -48,6 +49,11 @@ object LogicalQueryStageStrategy extends Strategy with PredicateHelper {
Seq(BroadcastHashJoinExec(
leftKeys, rightKeys, joinType, buildSide, condition, planLater(left), planLater(right)))

case j @ ExtractSingleColumnNullAwareAntiJoin(leftKeys, rightKeys)
if isBroadcastStage(j.right) =>
Seq(joins.BroadcastHashJoinExec(leftKeys, rightKeys, LeftAnti, BuildRight,
None, planLater(j.left), planLater(j.right), isNullAwareAntiJoin = true))

case j @ Join(left, right, joinType, condition, _)
if isBroadcastStage(left) || isBroadcastStage(right) =>
val buildSide = if (isBroadcastStage(left)) BuildLeft else BuildRight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ case class PlanDynamicPruningFilters(sparkSession: SparkSession)
// the first to be applied (apart from `InsertAdaptiveSparkPlan`).
val canReuseExchange = SQLConf.get.exchangeReuseEnabled && buildKeys.nonEmpty &&
plan.find {
case BroadcastHashJoinExec(_, _, _, BuildLeft, _, left, _) =>
case BroadcastHashJoinExec(_, _, _, BuildLeft, _, left, _, _) =>
left.sameResult(sparkPlan)
case BroadcastHashJoinExec(_, _, _, BuildRight, _, _, right) =>
case BroadcastHashJoinExec(_, _, _, BuildRight, _, _, right, _) =>
right.sameResult(sparkPlan)
case _ => false
}.isDefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,23 @@ case class BroadcastHashJoinExec(
buildSide: BuildSide,
condition: Option[Expression],
left: SparkPlan,
right: SparkPlan)
right: SparkPlan,
isNullAwareAntiJoin: Boolean = false)
extends HashJoin with CodegenSupport {

if (isNullAwareAntiJoin) {
require(leftKeys.length == 1, "leftKeys length should be 1")
require(rightKeys.length == 1, "rightKeys length should be 1")
require(joinType == LeftAnti, "joinType must be LeftAnti.")
require(buildSide == BuildRight, "buildSide must be BuildRight.")
require(condition.isEmpty, "null aware anti join optimize condition should be empty.")
}

override lazy val metrics = Map(
"numOutputRows" -> SQLMetrics.createMetric(sparkContext, "number of output rows"))

override def requiredChildDistribution: Seq[Distribution] = {
val mode = HashedRelationBroadcastMode(buildBoundKeys)
val mode = HashedRelationBroadcastMode(buildBoundKeys, isNullAwareAntiJoin)
buildSide match {
case BuildLeft =>
BroadcastDistribution(mode) :: UnspecifiedDistribution :: Nil
Expand Down Expand Up @@ -133,10 +142,37 @@ case class BroadcastHashJoinExec(
val numOutputRows = longMetric("numOutputRows")

val broadcastRelation = buildPlan.executeBroadcast[HashedRelation]()
streamedPlan.execute().mapPartitions { streamedIter =>
val hashed = broadcastRelation.value.asReadOnlyCopy()
TaskContext.get().taskMetrics().incPeakExecutionMemory(hashed.estimatedSize)
join(streamedIter, hashed, numOutputRows)
if (isNullAwareAntiJoin) {
streamedPlan.execute().mapPartitionsInternal { streamedIter =>
val hashed = broadcastRelation.value.asReadOnlyCopy()
TaskContext.get().taskMetrics().incPeakExecutionMemory(hashed.estimatedSize)
if (hashed == EmptyHashedRelation) {
streamedIter
} else if (hashed == EmptyHashedRelationWithAllNullKeys) {
Iterator.empty
} else {
val keyGenerator = UnsafeProjection.create(
BindReferences.bindReferences[Expression](
leftKeys,
AttributeSeq(left.output))
)
streamedIter.filter(row => {
val lookupKey: UnsafeRow = keyGenerator(row)
if (lookupKey.anyNull()) {
false
} else {
// Anti Join: Drop the row on the streamed side if it is a match on the build
hashed.get(lookupKey) == null
}
})
}
}
} else {
streamedPlan.execute().mapPartitions { streamedIter =>
val hashed = broadcastRelation.value.asReadOnlyCopy()
TaskContext.get().taskMetrics().incPeakExecutionMemory(hashed.estimatedSize)
Comment thread
cloud-fan marked this conversation as resolved.
join(streamedIter, hashed, numOutputRows)
}
}
}

Expand Down Expand Up @@ -454,6 +490,40 @@ case class BroadcastHashJoinExec(
val (matched, checkCondition, _) = getJoinCondition(ctx, input)
val numOutput = metricTerm(ctx, "numOutputRows")

if (isNullAwareAntiJoin) {
if (broadcastRelation.value == EmptyHashedRelation) {
return s"""
|// If the right side is empty, NAAJ simply returns the left side.
|$numOutput.add(1);
|${consume(ctx, input)}
""".stripMargin
} else if (broadcastRelation.value == EmptyHashedRelationWithAllNullKeys) {
return s"""
|// If the right side contains any all-null key, NAAJ simply returns Nothing.
""".stripMargin
} else {
val found = ctx.freshName("found")
return s"""
|boolean $found = false;
|// generate join key for stream side
|${keyEv.code}
|if ($anyNull) {
| $found = true;
|} else {
| UnsafeRow $matched = (UnsafeRow)$relationTerm.getValue(${keyEv.value});
| if ($matched != null) {
| $found = true;
| }
|}
|
|if (!$found) {
| $numOutput.add(1);
| ${consume(ctx, input)}
|}
Comment on lines +506 to +522

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems we can get rid of found variable and move this two lines to above if/else. found looks not correct in its semantics too. anyNull is true, doesn't mean we found matched row.

@leanken-zz leanken-zz Aug 19, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about

s"""
           |// generate join key for stream side
           |${keyEv.code}
           |if (!$anyNull && $relationTerm.getValue(${keyEv.value}) == null) {
           |  $numOutput.add(1);
           |  ${consume(ctx, input)}
           |}
         """.stripMargin

maybe I could update these code as well with the new HashedRelation Name in next PR.

""".stripMargin
}
}

if (uniqueKeyCodePath) {
val found = ctx.freshName("found")
s"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ private[execution] object HashedRelation {
input: Iterator[InternalRow],
key: Seq[Expression],
sizeEstimate: Int = 64,
taskMemoryManager: TaskMemoryManager = null): HashedRelation = {
taskMemoryManager: TaskMemoryManager = null,
isNullAware: Boolean = false): HashedRelation = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming this to storeRowsWithNullKeys ?

Or adding a comment about what it truly means, either here or on line 386 below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

val mm = Option(taskMemoryManager).getOrElse {
new TaskMemoryManager(
new UnifiedMemoryManager(
Expand All @@ -107,10 +108,12 @@ private[execution] object HashedRelation {
0)
}

if (key.length == 1 && key.head.dataType == LongType) {
LongHashedRelation(input, key, sizeEstimate, mm)
if (isNullAware && !input.hasNext) {
EmptyHashedRelation
} else if (key.length == 1 && key.head.dataType == LongType) {
LongHashedRelation(input, key, sizeEstimate, mm, isNullAware)
} else {
UnsafeHashedRelation(input, key, sizeEstimate, mm)
UnsafeHashedRelation(input, key, sizeEstimate, mm, isNullAware)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous version of this PR: isNullAware used to be a flag to the hashed relation to make it not skip over null keys.

I am not sure if isNullAware is still used in the UnsafeHashedRelation and the LongHashedRelation ? Apologies if I missed where it is being used still.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now, i am using EmptyHashedRelation and EmptyHashedRelationWithAllNullKeys to represent isOriginalInputEmpty, allNullColumnKeyExistsInOriginalInput, isNullAware should be passed into UnsafeHashedRelation and LongHashedRelation, and during building the relation, it might just return a EmptyHashedRelationWithAllNullKeys if a row with all null column key hits.

}
}
}
Expand Down Expand Up @@ -310,7 +313,8 @@ private[joins] object UnsafeHashedRelation {
input: Iterator[InternalRow],
key: Seq[Expression],
sizeEstimate: Int,
taskMemoryManager: TaskMemoryManager): HashedRelation = {
taskMemoryManager: TaskMemoryManager,
isNullAware: Boolean = false): HashedRelation = {
Comment thread
cloud-fan marked this conversation as resolved.

val pageSizeBytes = Option(SparkEnv.get).map(_.memoryManager.pageSizeBytes)
.getOrElse(new SparkConf().get(BUFFER_PAGESIZE).getOrElse(16L * 1024 * 1024))
Expand Down Expand Up @@ -338,6 +342,8 @@ private[joins] object UnsafeHashedRelation {
throw new SparkOutOfMemoryError("There is not enough memory to build hash map")
// scalastyle:on throwerror
}
} else if (isNullAware) {
return EmptyHashedRelationWithAllNullKeys
}
}

Expand Down Expand Up @@ -889,7 +895,8 @@ private[joins] object LongHashedRelation {
input: Iterator[InternalRow],
key: Seq[Expression],
sizeEstimate: Int,
taskMemoryManager: TaskMemoryManager): LongHashedRelation = {
taskMemoryManager: TaskMemoryManager,
isNullAware: Boolean = false): HashedRelation = {
Comment thread
cloud-fan marked this conversation as resolved.

val map = new LongToUnsafeRowMap(taskMemoryManager, sizeEstimate)
val keyGenerator = UnsafeProjection.create(key)
Expand All @@ -903,15 +910,61 @@ private[joins] object LongHashedRelation {
if (!rowKey.isNullAt(0)) {
val key = rowKey.getLong(0)
map.append(key, unsafeRow)
} else if (isNullAware) {
return EmptyHashedRelationWithAllNullKeys
}
}
map.optimize()
new LongHashedRelation(numFields, map)
}
}

/**
* Common trait with dummy implementation for NAAJ special HashedRelation
* EmptyHashedRelation
* EmptyHashedRelationWithAllNullKeys
*/
trait NullAwareHashedRelation extends HashedRelation with Externalizable {
override def get(key: InternalRow): Iterator[InternalRow] = {
throw new UnsupportedOperationException
}

override def getValue(key: InternalRow): InternalRow = {
throw new UnsupportedOperationException
}

override def keyIsUnique: Boolean = true

override def keys(): Iterator[InternalRow] = {
throw new UnsupportedOperationException
}

override def close(): Unit = {}

override def writeExternal(out: ObjectOutput): Unit = {}

override def readExternal(in: ObjectInput): Unit = {}

override def estimatedSize: Long = 0
}

/**
* A special HashedRelation indicates it built from a empty input:Iterator[InternalRow].
*/
object EmptyHashedRelation extends NullAwareHashedRelation {
override def asReadOnlyCopy(): EmptyHashedRelation.type = this
}

/**
* A special HashedRelation indicates it built from a non-empty input:Iterator[InternalRow],
* which contains all null columns key.
*/
object EmptyHashedRelationWithAllNullKeys extends NullAwareHashedRelation {
override def asReadOnlyCopy(): EmptyHashedRelationWithAllNullKeys.type = this
Comment on lines +962 to +963

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This object name really confuses. EmptyHashedRelation is from empty input, and EmptyHashedRelationWithAllNullKeys is from non-empty input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed, but I can't come out with better naming, could you please help with the naming, and i will create a new PR to do code refine, since this PR is closed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably just remove Empty to make it HashedRelationWithAllNullKeys?

}

/** The HashedRelationBroadcastMode requires that rows are broadcasted as a HashedRelation. */
case class HashedRelationBroadcastMode(key: Seq[Expression])
case class HashedRelationBroadcastMode(key: Seq[Expression], isNullAware: Boolean = false)
Comment thread
cloud-fan marked this conversation as resolved.
extends BroadcastMode {

override def transform(rows: Array[InternalRow]): HashedRelation = {
Expand All @@ -923,9 +976,9 @@ case class HashedRelationBroadcastMode(key: Seq[Expression])
sizeHint: Option[Long]): HashedRelation = {
sizeHint match {
case Some(numRows) =>
HashedRelation(rows, canonicalized.key, numRows.toInt)
HashedRelation(rows, canonicalized.key, numRows.toInt, isNullAware = isNullAware)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should this simply be isNullAware (ie passed in without a named argument)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is still a taskMemoryManager: TaskMemoryManager = null (with default value) just before isNullAware, I've had to using the named argument. ^_^

case None =>
HashedRelation(rows, canonicalized.key)
HashedRelation(rows, canonicalized.key, isNullAware = isNullAware)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
-- Test filter clause for aggregate expression.

--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=true
--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=false

Comment on lines +3 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these. It gives us more confidence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks to @cloud-fan I know of this better way to do e2e case coverage when adding a new feature.

-- Test data.
CREATE OR REPLACE TEMPORARY VIEW testData AS SELECT * FROM VALUES
(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2), (null, 1), (3, null), (null, null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=true
--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=false

create temporary view tab_a as select * from values (1, 1) as tab_a(a1, b1);
create temporary view tab_b as select * from values (1, 1) as tab_b(a2, b2);
create temporary view struct_tab as select struct(col1 as a, col2 as b) as record from
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
-- A test suite for IN HAVING in parent side, subquery, and both predicate subquery
-- It includes correlated cases.

--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=true
--CONFIG_DIM1 spark.sql.optimizeNullAwareAntiJoin=false

create temporary view t1 as select * from values
("val1a", 6S, 8, 10L, float(15.0), 20D, 20E2, timestamp '2014-04-04 01:00:00.000', date '2014-04-04'),
("val1b", 8S, 16, 19L, float(17.0), 25D, 26E2, timestamp '2014-05-04 01:01:00.000', date '2014-05-04'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
--CONFIG_DIM2 spark.sql.codegen.wholeStage=false,spark.sql.codegen.factoryMode=CODEGEN_ONLY
--CONFIG_DIM2 spark.sql.codegen.wholeStage=false,spark.sql.codegen.factoryMode=NO_CODEGEN

--CONFIG_DIM3 spark.sql.optimizeNullAwareAntiJoin=true
--CONFIG_DIM3 spark.sql.optimizeNullAwareAntiJoin=false

create temporary view t1 as select * from values
("val1a", 6S, 8, 10L, float(15.0), 20D, 20E2, timestamp '2014-04-04 01:00:00.000', date '2014-04-04'),
("val1b", 8S, 16, 19L, float(17.0), 25D, 26E2, timestamp '2014-05-04 01:01:00.000', date '2014-05-04'),
Expand Down
Loading