Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8ddb9fc
WIP: Plugin implementation of array_intersect using setIntersect, sor…
NVnavkumar Jun 28, 2022
0367a78
WIP: array_intersect
NVnavkumar Jun 29, 2022
7743f45
array_intersect implementation
NVnavkumar Jun 30, 2022
64946b2
implementation of 3 other array set operations
NVnavkumar Jul 4, 2022
ef54c68
add integration tests
NVnavkumar Jul 5, 2022
3362c08
working versions of array_difference and arrays_overlap
NVnavkumar Jul 5, 2022
728f15a
array_union passing tests but for one strange bug in GPU partitioning
NVnavkumar Jul 5, 2022
18ad1c0
Merge branch 'branch-22.08' of github.com:NVIDIA/spark-rapids into ar…
NVnavkumar Jul 5, 2022
3bfe106
cleanup arrays_overlap integration test
NVnavkumar Jul 5, 2022
c8e21b6
Add reference to issue filed regarding partitioning and collect()
NVnavkumar Jul 6, 2022
e7f978c
Merge branch 'branch-22.08' of github.com:NVIDIA/spark-rapids into ar…
NVnavkumar Jul 6, 2022
fab15a1
Add clarifying comment for GpuArraysOverlap
NVnavkumar Jul 6, 2022
f1003c4
Update integration_tests/src/main/python/array_test.py
NVnavkumar Jul 6, 2022
eb5e6f4
make generated arrays nullable in integration tests
NVnavkumar Jul 7, 2022
06af5eb
remove the plugin code for null return value handling since this is n…
NVnavkumar Jul 8, 2022
62fcdba
Merge branch 'branch-22.08' into array_intersect_string_array
NVnavkumar Jul 20, 2022
cd6beb2
Refactor to using GpuComplexTypeMergingExpression
NVnavkumar Jul 22, 2022
4587238
Add decimal_gens to testing here
NVnavkumar Jul 22, 2022
d2c8911
ensure all other special cases for floats and doubles are here except…
NVnavkumar Jul 22, 2022
893f7d0
Updated docs and xfail Double and Float tests on Spark versions < 3.1.3
NVnavkumar Jul 25, 2022
494ad54
Merge branch 'branch-22.08' into array_intersect_string_array
NVnavkumar Jul 25, 2022
3a83257
Updated references to methods in cudf and added some scalar tests
NVnavkumar Jul 26, 2022
5736e1e
Add incompat documentation, and update tests to handle pre Spark 3.1.…
NVnavkumar Aug 1, 2022
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
77 changes: 77 additions & 0 deletions integration_tests/src/main/python/array_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,80 @@ def test_array_max_q1():
def q1(spark):
return spark.sql('SELECT ARRAY_MAX(TRANSFORM(ARRAY_REPEAT(STRUCT(1, 2), 0), s -> s.col2))')
assert_gpu_and_cpu_are_equal_collect(q1)


@pytest.mark.parametrize('data_gen', [byte_gen, short_gen, int_gen, long_gen,
FloatGen(special_cases=[]), DoubleGen(special_cases=[]), string_gen, boolean_gen, date_gen, timestamp_gen], ids=idfn)
Comment thread
jlowe marked this conversation as resolved.
Outdated
Comment thread
jlowe marked this conversation as resolved.
Outdated
def test_array_intersect(data_gen):
gen = StructGen(
[('a', ArrayGen(data_gen, nullable=False)),
('b', ArrayGen(data_gen, nullable=False))],
Comment thread
ttnghia marked this conversation as resolved.
Outdated
nullable=False)

assert_gpu_and_cpu_are_equal_collect(
lambda spark: gen_df(spark, gen).selectExpr(
'sort_array(array_intersect(a, b))',
'sort_array(array_intersect(b, a))',
'sort_array(array_intersect(a, array()))',
'sort_array(array_intersect(array(), b))',
'sort_array(array_intersect(a, a))',
)
)

@pytest.mark.parametrize('data_gen', [byte_gen, short_gen, int_gen, long_gen,
FloatGen(special_cases=[]), DoubleGen(special_cases=[]), string_gen, boolean_gen, date_gen, timestamp_gen], ids=idfn)
def test_array_union(data_gen):
gen = StructGen(
[('a', ArrayGen(data_gen, nullable=False)),
('b', ArrayGen(data_gen, nullable=False))],
nullable=False)

# The 4th item in this integration test here is left commmented out here
Comment thread
NVnavkumar marked this conversation as resolved.
Outdated
# There is an issue with running that item with collect(), which affects
# the integration test here.
# See this issue (https://github.com/NVIDIA/spark-rapids/issues/5957)
assert_gpu_and_cpu_are_equal_collect(
lambda spark: gen_df(spark, gen).selectExpr(
'sort_array(array_union(a, b))',
'sort_array(array_union(b, a))',
'sort_array(array_union(a, array()))',
# 'sort_array(array_union(array(), b))', # see https://github.com/NVIDIA/spark-rapids/issues/5957
Comment thread
NVnavkumar marked this conversation as resolved.
Outdated
'sort_array(array_union(a, a))',
)
)

@pytest.mark.parametrize('data_gen', [byte_gen, short_gen, int_gen, long_gen,
FloatGen(special_cases=[]), DoubleGen(special_cases=[]), string_gen, boolean_gen, date_gen, timestamp_gen], ids=idfn)
def test_array_except(data_gen):
gen = StructGen(
[('a', ArrayGen(data_gen, nullable=False)),
('b', ArrayGen(data_gen, nullable=False))],
nullable=False)

assert_gpu_and_cpu_are_equal_collect(
lambda spark: gen_df(spark, gen).selectExpr(
'sort_array(array_except(a, b))',
'sort_array(array_except(b, a))',
'sort_array(array_except(a, array()))',
'sort_array(array_except(array(), b))',
'sort_array(array_except(a, a))',
)
)

@pytest.mark.parametrize('data_gen', [byte_gen, short_gen, int_gen, long_gen,
FloatGen(special_cases=[]), DoubleGen(special_cases=[]), string_gen, boolean_gen, date_gen, timestamp_gen], ids=idfn)
def test_arrays_overlap(data_gen):
gen = StructGen(
[('a', ArrayGen(data_gen, nullable=False)),
('b', ArrayGen(data_gen, nullable=False))],
nullable=False)

assert_gpu_and_cpu_are_equal_collect(
lambda spark: gen_df(spark, gen).selectExpr(
'arrays_overlap(a, b)',
'arrays_overlap(b, a)',
'arrays_overlap(a, array())',
'arrays_overlap(array(), b)',
'arrays_overlap(a, a)',
)
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There should be an empty line at the end of the file as an implicit convention.

Suggested change
)
)

Original file line number Diff line number Diff line change
Expand Up @@ -2907,7 +2907,75 @@ object GpuOverrides extends Logging {
}
}
),

expr[ArrayExcept](
"Returns an array of the elements in array1 but not in array2, without duplicates",
ExprChecks.binaryProject(
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all),
("array1",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all)),
("array2",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all))),
(in, conf, p, r) => new BinaryExprMeta[ArrayExcept](in, conf, p, r) {
override def convertToGpu(lhs: Expression, rhs: Expression): GpuExpression = {
GpuArrayExcept(lhs, rhs)
}
Comment thread
revans2 marked this conversation as resolved.
}
),
expr[ArrayIntersect](
"Returns an array of the elements in the intersection of array1 and array2, without" +
" duplicates",
ExprChecks.binaryProject(
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all),
("array1",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all)),
("array2",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all))),
(in, conf, p, r) => new BinaryExprMeta[ArrayIntersect](in, conf, p, r) {
override def convertToGpu(lhs: Expression, rhs: Expression): GpuExpression = {
GpuArrayIntersect(lhs, rhs)
}
}
),
expr[ArrayUnion](
"Returns an array of the elements in the union of array1 and array2, without duplicates.",
ExprChecks.binaryProject(
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all),
("array1",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all)),
("array2",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all))),
(in, conf, p, r) => new BinaryExprMeta[ArrayUnion](in, conf, p, r) {
override def convertToGpu(lhs: Expression, rhs: Expression): GpuExpression = {
GpuArrayUnion(lhs, rhs)
}
}
),
expr[ArraysOverlap](
"Returns true if a1 contains at least a non-null element present also in a2. If the arrays " +
"have no common element and they are both non-empty and either of them contains a null " +
"element null is returned, false otherwise.",
ExprChecks.binaryProject(TypeSig.BOOLEAN, TypeSig.BOOLEAN,
("array1",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all)),
("array2",
TypeSig.ARRAY.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 + TypeSig.NULL),
TypeSig.ARRAY.nested(TypeSig.all))),
(in, conf, p, r) => new BinaryExprMeta[ArraysOverlap](in, conf, p, r) {
override def convertToGpu(lhs: Expression, rhs: Expression): GpuExpression = {
GpuArraysOverlap(lhs, rhs)
}
}
),
expr[TransformKeys](
"Transform keys in a map using a transform function",
ExprChecks.projectOnly(TypeSig.MAP.nested(TypeSig.commonCudfTypes + TypeSig.DECIMAL_128 +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,171 @@ case class GpuArraysZip(children: Seq[Expression]) extends GpuExpression with Sh
}
}

trait GpuArraySetOperation extends GpuBinaryExpression {

override def doColumnar(lhs: GpuScalar, rhs: GpuColumnVector): ColumnVector = {
withResource(GpuColumnVector.from(lhs, rhs.getRowCount.toInt, lhs.dataType)) { left =>
doColumnar(left, rhs)
}
}

override def doColumnar(lhs: GpuColumnVector, rhs: GpuScalar): ColumnVector = {
withResource(GpuColumnVector.from(rhs, lhs.getRowCount.toInt, rhs.dataType)) { right =>
doColumnar(lhs, right)
}
}

override def doColumnar(numRows: Int, lhs: GpuScalar, rhs: GpuScalar): ColumnVector = {
withResource(GpuColumnVector.from(lhs, numRows, lhs.dataType)) { left =>
withResource(GpuColumnVector.from(rhs, numRows, rhs.dataType)) { right =>
doColumnar(left, right)
}
}
}
Comment thread
jlowe marked this conversation as resolved.
Outdated
}

case class GpuArrayExcept(left: Expression, right: Expression)
extends GpuArraySetOperation with ExpectsInputTypes {

override def inputTypes: Seq[AbstractDataType] = Seq(ArrayType, ArrayType)

override def checkInputDataTypes(): TypeCheckResult =
(left.dataType, right.dataType) match {
case (ArrayType(ldt, _), ArrayType(rdt, _)) =>
if (ldt.sameType(rdt)) {
TypeCheckResult.TypeCheckSuccess
} else {
TypeCheckResult.TypeCheckFailure(
s"Array_intersect requires both array params to have the same subType: $ldt != $rdt")
}
Comment thread
ttnghia marked this conversation as resolved.
case dt =>
TypeCheckResult.TypeCheckFailure(s"$prettyName only supports array input, but found $dt")
}

override def dataType: DataType = left.dataType

override def nullable: Boolean = false

override def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
ColumnView.setDifference(lhs.getBase, rhs.getBase)
}
}

case class GpuArrayIntersect(left: Expression, right: Expression)
extends GpuArraySetOperation with ExpectsInputTypes {

override def inputTypes: Seq[AbstractDataType] = Seq(ArrayType, ArrayType)

override def checkInputDataTypes(): TypeCheckResult =
(left.dataType, right.dataType) match {
case (ArrayType(ldt, _), ArrayType(rdt, _)) =>
if (ldt.sameType(rdt)) {
TypeCheckResult.TypeCheckSuccess
} else {
TypeCheckResult.TypeCheckFailure(
s"Array_intersect requires both array params to have the same subType: $ldt != $rdt")
}
case dt =>
TypeCheckResult.TypeCheckFailure(s"$prettyName only supports array input, but found $dt")
}

override def dataType: DataType = left.dataType

override def nullable: Boolean = false

override def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
ColumnView.setIntersect(lhs.getBase, rhs.getBase)
}
}

case class GpuArrayUnion(left: Expression, right: Expression)
extends GpuArraySetOperation with ExpectsInputTypes {

override def inputTypes: Seq[AbstractDataType] = Seq(ArrayType, ArrayType)

override def checkInputDataTypes(): TypeCheckResult =
(left.dataType, right.dataType) match {
case (ArrayType(ldt, _), ArrayType(rdt, _)) =>
if (ldt.sameType(rdt)) {
TypeCheckResult.TypeCheckSuccess
} else {
TypeCheckResult.TypeCheckFailure(
s"Array_union requires both array params to have the same subType: $ldt != $rdt")
}
case dt =>
TypeCheckResult.TypeCheckFailure(s"$prettyName only supports array input, but found $dt")
}

override def dataType: DataType = left.dataType
Comment thread
NVnavkumar marked this conversation as resolved.
Outdated

override def nullable: Boolean = false

override def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
ColumnView.setUnion(lhs.getBase, rhs.getBase)
}
}

case class GpuArraysOverlap(left: Expression, right: Expression)
extends GpuArraySetOperation with ExpectsInputTypes {

override def inputTypes: Seq[AbstractDataType] = Seq(ArrayType, ArrayType)

override def checkInputDataTypes(): TypeCheckResult =
(left.dataType, right.dataType) match {
case (ArrayType(ldt, _), ArrayType(rdt, _)) =>
if (ldt.sameType(rdt)) {
TypeCheckResult.TypeCheckSuccess
} else {
TypeCheckResult.TypeCheckFailure(
s"Array_union requires both array params to have the same subType: $ldt != $rdt")
}
case dt =>
TypeCheckResult.TypeCheckFailure(s"$prettyName only supports array input, but found $dt")
}

override def dataType: DataType = BooleanType

override def nullable: Boolean = true

override def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
// Spark's arrays_overlap() function will return null when both arrays are non-empty,
// they have no-non null overlapping elements and either array contains at least one null
// value, so we have to handle that here.
val bothNonEmpty = withResource(lhs.getBase.countElements) { leftCount =>
withResource(rhs.getBase.countElements) { rightCount =>
withResource(Scalar.fromInt(0)) { zero =>
withResource(leftCount.greaterThan(zero)) { leftNonEmpty =>
withResource(rightCount.greaterThan(zero)) { rightNonEmpty =>
leftNonEmpty.and(rightNonEmpty)
}
}
}
}
}
val eitherHasNulls = withResource(lhs.getBase.listContainsNulls) { leftHasNulls =>
withResource(rhs.getBase.listContainsNulls) { rightHasNulls =>
leftHasNulls.or(rightHasNulls)
}
}
withResource(bothNonEmpty) { _ =>
withResource(eitherHasNulls) { _ =>
withResource(ColumnView.listOverlap(lhs.getBase, rhs.getBase)) { overlaps =>
withResource(overlaps.not) { notOverlap =>
withResource(bothNonEmpty.and(eitherHasNulls)) { nonEmptyAndHasNull =>
withResource(nonEmptyAndHasNull.and(notOverlap)) { returnNull =>
returnNull.ifElse(
GpuColumnVector.columnVectorFromNull(lhs.getRowCount.toInt, BooleanType),
overlaps
)
}
}
}
}
}
}
}
}

class GpuSequenceMeta(
expr: Sequence,
conf: RapidsConf,
Expand Down