com.nvidia.spark.rapids.GpuExpressions.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rapids-4-spark_2.13 Show documentation
Show all versions of rapids-4-spark_2.13 Show documentation
Creates the distribution package of the RAPIDS plugin for Apache Spark
/*
* Copyright (c) 2019-2024, NVIDIA CORPORATION.
*
* Licensed 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 com.nvidia.spark.rapids
import ai.rapids.cudf.{ast, BinaryOp, BinaryOperable, ColumnVector, DType, Scalar, UnaryOp}
import com.nvidia.spark.Retryable
import com.nvidia.spark.rapids.Arm.{withResource, withResourceIfAllowed}
import com.nvidia.spark.rapids.RapidsPluginImplicits._
import com.nvidia.spark.rapids.shims.{ShimBinaryExpression, ShimExpression, ShimTernaryExpression, ShimUnaryExpression}
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, ExprCode}
import org.apache.spark.sql.types.StringType
import org.apache.spark.sql.vectorized.ColumnarBatch
import org.apache.spark.unsafe.types.UTF8String
object GpuExpressionsUtils {
def getTrimString(trimStr: Option[Expression]): String = trimStr match {
case Some(GpuLiteral(data, StringType)) =>
if (data == null) {
null
} else {
data.asInstanceOf[UTF8String].toString
}
case Some(GpuAlias(GpuLiteral(data, StringType), _)) =>
if (data == null) {
null
} else {
data.asInstanceOf[UTF8String].toString
}
case None => " "
case _ =>
throw new IllegalStateException("Internal Error GPU support for this data type is not " +
"implemented and should have been disabled")
}
/**
* Tries to resolve a `GpuColumnVector` from a Scala `Any`.
*
* This is a common handling of the result from the `columnarEval`, allowing one of
* - A GpuColumnVector
* - A GpuScalar
* For other types, it will blow up.
*
* It is recommended to return only a `GpuScalar` or a `GpuColumnVector` from a GPU
* expression's `columnarEval`, to keep the result handling simple. Besides, `GpuScalar` can
* be created from a cudf Scalar or a Scala value, So the 'GpuScalar' and 'GpuColumnVector'
* should cover all the cases for GPU pipelines.
*
* @param any the input value. It will be closed if it is a closeable after the call done.
* @param numRows the expected row number of the output column, used when 'any' is a GpuScalar.
* @return a `GpuColumnVector` if it succeeds. Users should close the column vector to avoid
* memory leak.
*/
def resolveColumnVector(any: Any, numRows: Int): GpuColumnVector = {
withResourceIfAllowed(any) {
case c: GpuColumnVector => c.incRefCount()
case s: GpuScalar => GpuColumnVector.from(s, numRows, s.dataType)
case other =>
throw new IllegalArgumentException(s"Cannot resolve a ColumnVector from the value:" +
s" $other. Please convert it to a GpuScalar or a GpuColumnVector before returning.")
}
}
/**
* Extract the GpuLiteral
* @param exp the input expression to be extracted
* @return an optional GpuLiteral
*/
@scala.annotation.tailrec
def extractGpuLit(exp: Expression): Option[GpuLiteral] = exp match {
case gl: GpuLiteral => Some(gl)
case ga: GpuAlias => extractGpuLit(ga.child)
case _ => None
}
/**
* Collect the Retryables from a Seq of expression.
*/
def collectRetryables(expressions: Seq[Expression]): Seq[Retryable] = {
// There should be no dependence between expression and its children for
// the checkpoint and restore operations.
expressions.flatMap { expr =>
expr.collect {
case r: Retryable => r
}
}
}
}
/**
* An Expression that cannot be evaluated in the traditional row-by-row sense (hence Unevaluable)
* but instead can be evaluated on an entire column batch at once.
*/
trait GpuExpression extends Expression {
// copied from Unevaluable to avoid inheriting final foldable
//
final override def eval(input: InternalRow = null): Any =
throw new UnsupportedOperationException(s"Cannot evaluate expression: $this")
final override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode =
throw new UnsupportedOperationException(s"Cannot generate code for expression: $this")
/**
* Override this if your expression cannot allow combining of data from multiple files
* into a single batch before it operates on them. These are for things like getting
* the input file name. Which for spark is stored in a thread local variable which means
* we have to jump through some hoops to make this work.
*/
def disableCoalesceUntilInput(): Boolean =
children.exists {
case c: GpuExpression => c.disableCoalesceUntilInput()
case _ => false // This path should never really happen
}
/**
* Returns the result of evaluating this expression on the entire
* `ColumnarBatch`. The result of calling this may be a single `GpuColumnVector` or a scalar
* value. Scalar values typically happen if they are a part of the expression i.e. col("a") + 100.
* In this case the 100 is a literal that Add would have to be able to handle.
*
* By convention any `AutoCloseable` returned by [[columnarEvalAny]] is owned by the caller and
* will need to be closed by them.
*/
def columnarEvalAny(batch: ColumnarBatch): Any = columnarEval(batch)
/**
* Returns the result of evaluating this expression on the entire
* `ColumnarBatch`. The result of calling this is a `GpuColumnVector`.
*
* By convention any `GpuColumnVector` returned by [[columnarEval]]
* is owned by the caller and will need to be closed by them. This can happen by putting it into
* a `ColumnarBatch` and closing the batch or by closing the vector directly if it is a
* temporary value.
*/
def columnarEval(batch: ColumnarBatch): GpuColumnVector
override lazy val canonicalized: Expression = {
val canonicalizedChildren = children.map(_.canonicalized)
GpuCanonicalize.execute(withNewChildren(canonicalizedChildren))
}
/**
* Build an equivalent representation of this expression in a cudf AST.
* @param numFirstTableColumns number of columns in the leftmost input table. Spark places the
* columns of all inputs in a single sequence, while cudf AST uses an
* explicit table reference to make column indices unique. This
* parameter helps translate input column references from Spark's
* single sequence into cudf's separate sequences.
* @return top node of the equivalent AST
*/
def convertToAst(numFirstTableColumns: Int): ast.AstExpression =
throw new IllegalStateException(s"Cannot convert ${this.getClass.getSimpleName} to AST")
/** Could evaluating this expression cause side-effects, such as throwing an exception? */
def hasSideEffects: Boolean =
children.exists {
case c: GpuExpression => c.hasSideEffects
case _ => false // This path should never really happen
}
/**
* If this returns true then tiered project will stop looking to combine expressions when
* this is seen.
*/
def disableTieredProjectCombine: Boolean = false
/**
* Whether an expression itself is non-deterministic when its "deterministic" is false,
* no matter whether it has any non-deterministic children.
* An expression is actually a tree, and deterministic being false means there is at
* least one tree node is non-deterministic, but we need to know the exact nodes which
* are non-deterministic to check if it implements the Retryable.
*
* Default to false because Spark checks only children by default in Expression. So it
* is non-deterministic iff it has non-deterministic children.
*
* NOTE When overriding "deterministic", this should be taken care of.
*/
val selfNonDeterministic: Boolean = false
/**
* true means this expression can be used inside a retry block, otherwise false.
* An expression is retryable when
* - it is deterministic, or
* - when being non-deterministic, it is a Retryable and its children are all retryable.
*/
lazy val retryable: Boolean = deterministic || {
val childrenAllRetryable = children.forall(_.asInstanceOf[GpuExpression].retryable)
if (selfNonDeterministic || children.forall(_.deterministic)) {
// self is non-deterministic, so need to check if it is a Retryable.
//
// "selfNonDeterministic" should be reliable enough, but it is still good to
// do this check for one case we are 100% sure self is non-deterministic (its
// "deterministic" is false but its children are all deterministic). This can
// minimize the possibility of missing expressions that happen to forget
// overriding "selfNonDeterministic" correctly.
this.isInstanceOf[Retryable] && childrenAllRetryable
} else {
childrenAllRetryable
}
}
}
abstract class GpuLeafExpression extends GpuExpression with ShimExpression {
override final def children: Seq[Expression] = Nil
/* no children, so only self can be non-deterministic */
override final val selfNonDeterministic: Boolean = !deterministic
}
trait GpuUnevaluable extends GpuExpression {
final override def columnarEvalAny(batch: ColumnarBatch): Any =
throw new UnsupportedOperationException(s"Cannot columnar evaluate expression: $this")
final override def columnarEval(batch: ColumnarBatch): GpuColumnVector =
throw new UnsupportedOperationException(s"Cannot columnar evaluate expression: $this")
}
abstract class GpuUnevaluableUnaryExpression extends GpuUnaryExpression with GpuUnevaluable {
final override def doColumnar(input: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(s"Cannot columnar evaluate expression: $this")
}
abstract class GpuUnaryExpression extends ShimUnaryExpression with GpuExpression {
protected def doColumnar(input: GpuColumnVector): ColumnVector
def outputTypeOverride: DType = null
private[this] def doItColumnar(input: GpuColumnVector): GpuColumnVector = {
withResource(doColumnar(input)) { vec =>
if (outputTypeOverride != null && !outputTypeOverride.equals(vec.getType)) {
GpuColumnVector.from(vec.castTo(outputTypeOverride), dataType)
} else {
GpuColumnVector.from(vec.incRefCount(), dataType)
}
}
}
override def columnarEval(batch: ColumnarBatch): GpuColumnVector = {
withResource(child.columnarEval(batch)) { col =>
doItColumnar(col)
}
}
}
object CudfUnaryExpression {
lazy val opToAstMap: Map[UnaryOp, ast.UnaryOperator] = Map(
UnaryOp.ABS -> ast.UnaryOperator.ABS,
UnaryOp.ARCSIN -> ast.UnaryOperator.ARCSIN,
UnaryOp.ARCSINH -> ast.UnaryOperator.ARCSINH,
UnaryOp.ARCCOS -> ast.UnaryOperator.ARCCOS,
UnaryOp.ARCCOSH -> ast.UnaryOperator.ARCCOSH,
UnaryOp.ARCTAN -> ast.UnaryOperator.ARCTAN,
UnaryOp.ARCTANH -> ast.UnaryOperator.ARCTANH,
UnaryOp.BIT_INVERT -> ast.UnaryOperator.BIT_INVERT,
UnaryOp.CBRT -> ast.UnaryOperator.CBRT,
UnaryOp.COS -> ast.UnaryOperator.COS,
UnaryOp.COSH -> ast.UnaryOperator.COSH,
UnaryOp.EXP -> ast.UnaryOperator.EXP,
UnaryOp.NOT -> ast.UnaryOperator.NOT,
UnaryOp.RINT -> ast.UnaryOperator.RINT,
UnaryOp.SIN -> ast.UnaryOperator.SIN,
UnaryOp.SINH -> ast.UnaryOperator.SINH,
UnaryOp.SQRT -> ast.UnaryOperator.SQRT,
UnaryOp.TAN -> ast.UnaryOperator.TAN,
UnaryOp.TANH -> ast.UnaryOperator.TANH)
}
trait CudfUnaryExpression extends GpuUnaryExpression {
def unaryOp: UnaryOp
override def doColumnar(input: GpuColumnVector): ColumnVector = input.getBase.unaryOp(unaryOp)
override def convertToAst(numFirstTableColumns: Int): ast.AstExpression = {
val astOp = CudfUnaryExpression.opToAstMap.getOrElse(unaryOp,
throw new IllegalStateException(s"${this.getClass.getSimpleName} is not supported by AST"))
new ast.UnaryOperation(astOp,
child.asInstanceOf[GpuExpression].convertToAst(numFirstTableColumns))
}
}
trait GpuBinaryExpression extends ShimBinaryExpression with GpuExpression {
def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector
def doColumnar(lhs: GpuScalar, rhs: GpuColumnVector): ColumnVector
def doColumnar(lhs: GpuColumnVector, rhs: GpuScalar): ColumnVector
def doColumnar(numRows: Int, lhs: GpuScalar, rhs: GpuScalar): ColumnVector
override def columnarEval(batch: ColumnarBatch): GpuColumnVector = {
withResourceIfAllowed(left.columnarEvalAny(batch)) { lhs =>
withResourceIfAllowed(right.columnarEvalAny(batch)) { rhs =>
(lhs, rhs) match {
case (l: GpuColumnVector, r: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(l, r), dataType)
case (l: GpuScalar, r: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(l, r), dataType)
case (l: GpuColumnVector, r: GpuScalar) =>
GpuColumnVector.from(doColumnar(l, r), dataType)
case (l: GpuScalar, r: GpuScalar) =>
GpuColumnVector.from(doColumnar(batch.numRows(), l, r), dataType)
case (l, r) =>
throw new UnsupportedOperationException(s"Unsupported data '($l: " +
s"${l.getClass}, $r: ${r.getClass})' for GPU binary expression.")
}
}
}
}
}
/**
* Expressions subclassing this trait guarantee that they implement:
* doColumnar(GpuScalar, GpuScalar)
* doColumnar(GpuColumnVector, GpuScalar)
*
* The default implementation throws for all other permutations.
*
* The binary expression must fallback to the CPU for the doColumnar cases
* that would throw. The default implementation here should never execute.
*/
trait GpuBinaryExpressionArgsAnyScalar extends GpuBinaryExpression {
protected val anyScalarExceptionMessage: String =
s"$prettyName: LHS can be a column or scalar and " +
s"RHS has to be a scalar (got left: $left, right: $right)"
override final def doColumnar(lhs: GpuScalar, rhs: GpuColumnVector): ColumnVector = {
throw new UnsupportedOperationException(anyScalarExceptionMessage)
}
override final def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
throw new UnsupportedOperationException(anyScalarExceptionMessage)
}
}
trait GpuBinaryOperator extends BinaryOperator with GpuBinaryExpression
trait CudfBinaryExpression extends GpuBinaryExpression {
def binaryOp: BinaryOp
def outputTypeOverride: DType = null
def castOutputAtEnd: Boolean = false
def astOperator: Option[ast.BinaryOperator] = None
def outputType(l: BinaryOperable, r: BinaryOperable): DType = {
val over = outputTypeOverride
if (over == null) {
BinaryOperable.implicitConversion(binaryOp, l, r)
} else {
over
}
}
def doColumnar(lhs: BinaryOperable, rhs: BinaryOperable): ColumnVector = {
val outType = if (castOutputAtEnd) {
BinaryOperable.implicitConversion(binaryOp, lhs, rhs)
} else {
outputType(lhs, rhs)
}
val tmp = lhs.binaryOp(binaryOp, rhs, outType)
// In some cases the output type is ignored
val castType = outputType(lhs, rhs)
if (!castType.equals(tmp.getType) && castOutputAtEnd) {
withResource(tmp) { tmp =>
tmp.castTo(castType)
}
} else {
tmp
}
}
override def doColumnar(lhs: GpuColumnVector, rhs: GpuColumnVector): ColumnVector = {
doColumnar(lhs.getBase, rhs.getBase)
}
override def doColumnar(lhs: GpuScalar, rhs: GpuColumnVector): ColumnVector = {
doColumnar(lhs.getBase, rhs.getBase)
}
override def doColumnar(lhs: GpuColumnVector, rhs: GpuScalar): ColumnVector = {
doColumnar(lhs.getBase, rhs.getBase)
}
override def doColumnar(numRows: Int, lhs: GpuScalar, rhs: GpuScalar): ColumnVector = {
withResource(GpuColumnVector.from(lhs, numRows, left.dataType)) { expandedLhs =>
doColumnar(expandedLhs, rhs)
}
}
override def convertToAst(numFirstTableColumns: Int): ast.AstExpression = {
val astOp = astOperator.getOrElse(
throw new IllegalStateException(s"$this is not supported by AST"))
assert(left.dataType == right.dataType)
new ast.BinaryOperation(astOp,
left.asInstanceOf[GpuExpression].convertToAst(numFirstTableColumns),
right.asInstanceOf[GpuExpression].convertToAst(numFirstTableColumns))
}
}
abstract class CudfBinaryOperator extends GpuBinaryOperator with CudfBinaryExpression
trait GpuString2TrimExpression extends String2TrimExpression with GpuExpression
with ShimExpression {
override def srcStr: Expression
override def trimStr: Option[Expression]
override def children: Seq[Expression] = srcStr +: trimStr.toSeq
def strippedColumnVector(value: GpuColumnVector, scalarValue: Scalar): GpuColumnVector
override def sql: String = if (trimStr.isDefined) {
s"TRIM($direction ${trimStr.get.sql} FROM ${srcStr.sql})"
} else {
super.sql
}
override def columnarEval(batch: ColumnarBatch): GpuColumnVector = {
val trim = GpuExpressionsUtils.getTrimString(trimStr)
withResourceIfAllowed(srcStr.columnarEval(batch)) { column =>
if (trim == null) {
GpuColumnVector.fromNull(column.getRowCount.toInt, StringType)
} else if (trim.isEmpty) {
column.incRefCount() // This is a noop
} else {
withResource(Scalar.fromString(trim)) { t =>
strippedColumnVector(column, t)
}
}
}
}
protected def doEval(
srcString: org.apache.spark.unsafe.types.UTF8String,
trimString: org.apache.spark.unsafe.types.UTF8String
): org.apache.spark.unsafe.types.UTF8String = {
throw new UnsupportedOperationException("TODO: Columnar only message!")
}
protected def doEval(
srcString: org.apache.spark.unsafe.types.UTF8String
): org.apache.spark.unsafe.types.UTF8String = {
throw new UnsupportedOperationException("TODO: Columnar only message!")
}
}
trait GpuTernaryExpression extends ShimTernaryExpression with GpuExpression {
def doColumnar(
val0: GpuColumnVector, val1: GpuColumnVector, val2: GpuColumnVector): ColumnVector
def doColumnar(val0: GpuScalar, val1: GpuColumnVector, val2: GpuColumnVector): ColumnVector
def doColumnar(val0: GpuScalar, val1: GpuScalar, val2: GpuColumnVector): ColumnVector
def doColumnar(val0: GpuScalar, val1: GpuColumnVector, val2: GpuScalar): ColumnVector
def doColumnar(val0: GpuColumnVector, val1: GpuScalar, val2: GpuColumnVector): ColumnVector
def doColumnar(val0: GpuColumnVector, val1: GpuScalar, val2: GpuScalar): ColumnVector
def doColumnar(val0: GpuColumnVector, val1: GpuColumnVector, val2: GpuScalar): ColumnVector
def doColumnar(numRows: Int, val0: GpuScalar, val1: GpuScalar, val2: GpuScalar): ColumnVector
override def columnarEval(batch: ColumnarBatch): GpuColumnVector = {
val Seq(child0, child1, child2) = children
withResourceIfAllowed(child0.columnarEvalAny(batch)) { val0 =>
withResourceIfAllowed(child1.columnarEvalAny(batch)) { val1 =>
withResourceIfAllowed(child2.columnarEvalAny(batch)) { val2 =>
(val0, val1, val2) match {
case (v0: GpuColumnVector, v1: GpuColumnVector, v2: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuScalar, v1: GpuColumnVector, v2: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuColumnVector, v1: GpuScalar, v2: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuColumnVector, v1: GpuColumnVector, v2: GpuScalar) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuScalar, v1: GpuScalar, v2: GpuColumnVector) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuScalar, v1: GpuColumnVector, v2: GpuScalar) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuColumnVector, v1: GpuScalar, v2: GpuScalar) =>
GpuColumnVector.from(doColumnar(v0, v1, v2), dataType)
case (v0: GpuScalar, v1: GpuScalar, v2: GpuScalar) =>
GpuColumnVector.from(doColumnar(batch.numRows(), v0, v1, v2), dataType)
case (v0, v1, v2) =>
throw new UnsupportedOperationException(s"Unsupported data '($v0: ${v0.getClass}," +
s" $v1: ${v1.getClass}, $v2: ${v2.getClass})' for GPU ternary expression.")
}
}
}
}
}
}
/**
* Expressions subclassing this trait guarantee that they implement:
* doColumnar(GpuScalar, GpuScalar, GpuScalar)
* doColumnar(GpuColumnVector, GpuScalar, GpuScalar)
*
* The default implementation throws for all other permutations.
*
* The ternary expression must fallback to the CPU for the doColumnar cases
* that would throw. The default implementation here should never execute.
*/
trait GpuTernaryExpressionArgsAnyScalarScalar extends GpuTernaryExpression {
protected val anyScalarScalarErrorMessage: String =
s"$prettyName: first argument can be a column or a scalar, second and third arguments " +
s"have to be scalars (got first: $first, second: $second, third: $third)"
final override def doColumnar(
strExpr: GpuColumnVector,
searchExpr: GpuColumnVector,
replaceExpr: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
final override def doColumnar(
strExpr: GpuScalar,
searchExpr: GpuColumnVector,
replaceExpr: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
final override def doColumnar(
strExpr: GpuScalar,
searchExpr: GpuScalar,
replaceExpr: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
final override def doColumnar(
strExpr: GpuScalar,
searchExpr: GpuColumnVector,
replaceExpr: GpuScalar): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
final override def doColumnar(
strExpr: GpuColumnVector,
searchExpr: GpuScalar,
replaceExpr: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
final override def doColumnar(
strExpr: GpuColumnVector,
searchExpr: GpuColumnVector,
replaceExpr: GpuScalar): ColumnVector =
throw new UnsupportedOperationException(anyScalarScalarErrorMessage)
}
/**
* Expressions subclassing this trait guarantee that they implement:
* doColumnar(GpuScalar, GpuScalar, GpuScalar)
* doColumnar(GpuScalar, GpuColumnVector, GpuScalar)
*
* The default implementation throws for all other permutations.
*
* The ternary expression must fallback to the CPU for the doColumnar cases
* that would throw. The default implementation here should never execute.
*/
trait GpuTernaryExpressionArgsScalarAnyScalar extends GpuTernaryExpression {
protected val scalarAnyScalarExceptionMessage: String =
s"$prettyName: first argument has to be a scalar, second argument can be a column " +
s"or a scalar, and third argument has to be a scalar " +
s"(got first: $first, second: $second, third: $third)"
final override def doColumnar(
val0: GpuColumnVector,
val1: GpuColumnVector,
val2: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
final override def doColumnar(
val0: GpuScalar,
val1: GpuColumnVector,
val2: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
final override def doColumnar(
val0: GpuScalar,
val1: GpuScalar,
val2: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
final override def doColumnar(
val0: GpuColumnVector,
val1: GpuScalar,
val2: GpuColumnVector): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
final override def doColumnar(
val0: GpuColumnVector,
val1: GpuScalar,
val2: GpuScalar): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
final override def doColumnar(
val0: GpuColumnVector,
val1: GpuColumnVector,
val2: GpuScalar): ColumnVector =
throw new UnsupportedOperationException(scalarAnyScalarExceptionMessage)
}
trait GpuComplexTypeMergingExpression extends ComplexTypeMergingExpression
with GpuExpression with ShimExpression {
def columnarEval(batch: ColumnarBatch): GpuColumnVector
}