diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index 57a0b383a67..712db14ff1f 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -717,6 +717,8 @@ set(ARROW_COMPUTE_SRCS compute/row/compare_internal.cc compute/row/grouper.cc compute/row/row_internal.cc + compute/special_form.cc + compute/special_forms/if_else_special_form.cc compute/util.cc compute/util_internal.cc) diff --git a/cpp/src/arrow/compute/exec.cc b/cpp/src/arrow/compute/exec.cc index f2e45783831..65b5dd14305 100644 --- a/cpp/src/arrow/compute/exec.cc +++ b/cpp/src/arrow/compute/exec.cc @@ -1345,20 +1345,41 @@ const CpuInfo* ExecContext::cpu_info() const { return CpuInfo::GetInstance(); } SelectionVector::SelectionVector(std::shared_ptr data) : data_(std::move(data)) { - DCHECK_EQ(Type::INT32, data_->type->id()); + DCHECK_EQ(Type::BOOL, data_->type->id()); DCHECK_EQ(0, data_->GetNullCount()); - indices_ = data_->GetValues(1); } SelectionVector::SelectionVector(const Array& arr) : SelectionVector(arr.data()) {} -int32_t SelectionVector::length() const { return static_cast(data_->length); } +Result> SelectionVector::Copy( + const std::shared_ptr& mm) const { + ARROW_ASSIGN_OR_RAISE(auto copy, data_->CopyTo(mm)); + return std::make_unique(std::move(copy)); +} + +Status SelectionVector::Intersect(const ArrayData& other) { + DCHECK_EQ(Type::BOOL, other.type->id()); + DCHECK_EQ(data_->length, other.length); + ::arrow::internal::BitmapAnd(data_->buffers[1]->data(), data_->offset, + other.buffers[1]->data(), other.offset, data_->length, + data_->offset, data_->buffers[1]->mutable_data()); + return Status::OK(); +} -Result> SelectionVector::FromMask( - const BooleanArray& arr) { - return Status::NotImplemented("FromMask"); +Status SelectionVector::Intersect(const SelectionVector& other) { + return Intersect(*other.data()); } +Status SelectionVector::Invert() { + ::arrow::internal::InvertBitmap(data_->buffers[1]->data(), data_->offset, data_->length, + data_->buffers[1]->mutable_data(), data_->offset); + return Status::OK(); +} + +std::shared_ptr SelectionVector::data() const { return data_; } + +int32_t SelectionVector::length() const { return static_cast(data_->length); } + Result CallFunction(const std::string& func_name, const std::vector& args, const FunctionOptions* options, ExecContext* ctx) { if (ctx == nullptr) { diff --git a/cpp/src/arrow/compute/exec.h b/cpp/src/arrow/compute/exec.h index 3fbefe4a1ab..be5ec7b4df7 100644 --- a/cpp/src/arrow/compute/exec.h +++ b/cpp/src/arrow/compute/exec.h @@ -140,15 +140,20 @@ class ARROW_EXPORT SelectionVector { explicit SelectionVector(const Array& arr); - /// \brief Create SelectionVector from boolean mask - static Result> FromMask(const BooleanArray& arr); + Result> Copy( + const std::shared_ptr& mm) const; - const int32_t* indices() const { return indices_; } + Status Intersect(const ArrayData& other); + + Status Intersect(const SelectionVector& other); + + Status Invert(); + + std::shared_ptr data() const; int32_t length() const; private: std::shared_ptr data_; - const int32_t* indices_; }; /// An index to represent that a batch does not belong to an ordered stream @@ -422,6 +427,8 @@ struct ARROW_EXPORT ExecSpan { int64_t length = 0; std::vector values; + // A non-owning selection vector to be used by selection-vector-aware kernels. + SelectionVector* selection_vector = NULLPTR; }; /// \defgroup compute-call-function One-shot calls to compute functions diff --git a/cpp/src/arrow/compute/expression.cc b/cpp/src/arrow/compute/expression.cc index 532869b3453..0ade611a7ab 100644 --- a/cpp/src/arrow/compute/expression.cc +++ b/cpp/src/arrow/compute/expression.cc @@ -24,9 +24,9 @@ #include "arrow/chunked_array.h" #include "arrow/compute/api_vector.h" -#include "arrow/compute/exec_internal.h" #include "arrow/compute/expression_internal.h" #include "arrow/compute/function_internal.h" +#include "arrow/compute/special_form.h" #include "arrow/compute/util.h" #include "arrow/io/memory.h" #include "arrow/ipc/reader.h" @@ -72,11 +72,12 @@ Expression field_ref(FieldRef ref) { } Expression call(std::string function, std::vector arguments, - std::shared_ptr options) { + std::shared_ptr options, bool is_special_form) { Expression::Call call; call.function_name = std::move(function); call.arguments = std::move(arguments); call.options = std::move(options); + call.is_special_form = is_special_form; return Expression(std::move(call)); } @@ -119,6 +120,16 @@ const DataType* Expression::type() const { return CallNotNull(*this)->type.type; } +bool Expression::selection_vector_aware() const { + DCHECK(IsBound()); + + if (literal() || field_ref()) { + return true; + } + + return CallNotNull(*this)->selection_vector_aware; +} + namespace { std::string PrintDatum(const Datum& datum) { @@ -550,6 +561,11 @@ Result BindNonRecursive(Expression::Call call, bool insert_implicit_ ARROW_ASSIGN_OR_RAISE( call.type, call.kernel->signature->out_type().Resolve(&kernel_context, types)); + + if (call.is_special_form) { + ARROW_ASSIGN_OR_RAISE(call.special_form, SpecialForm::Make(call.function_name)); + } + return Status::OK(); }; @@ -572,7 +588,11 @@ Result BindNonRecursive(Expression::Call call, bool insert_implicit_ types = GetTypesWithSmallestLiteralRepresentation(call.arguments); ARROW_ASSIGN_OR_RAISE(call.kernel, call.function->DispatchBest(&types)); + call.selection_vector_aware = call.kernel->selection_vector_aware; + for (size_t i = 0; i < types.size(); ++i) { + call.selection_vector_aware &= call.arguments[i].selection_vector_aware(); + if (types[i] == call.arguments[i].type()) continue; if (const Datum* lit = call.arguments[i].literal()) { @@ -716,6 +736,30 @@ Result ExecuteScalarExpression(const Expression& expr, const Schema& full return ExecuteScalarExpression(expr, input, exec_context); } +namespace { + +// Execute a scalar expression that is not fully selection-vector-aware on a batch +// carrying a valid selection vector using the slow path: gathering the input and +// scattering the output. +Result ExecuteScalarExpressionWithSelSlowPath(const Expression& expr, + const ExecBatch& input, + compute::ExecContext* exec_context) { + DCHECK(!expr.selection_vector_aware() && input.selection_vector); + auto values = input.values; + for (auto& value : values) { + if (value.is_scalar()) continue; + ARROW_ASSIGN_OR_RAISE( + value, Filter(value, input.selection_vector->data(), FilterOptions::Defaults())); + } + ARROW_ASSIGN_OR_RAISE(ExecBatch selected_batch, ExecBatch::Make(std::move(values))); + ARROW_ASSIGN_OR_RAISE(auto partial_result, + ExecuteScalarExpression(expr, selected_batch, exec_context)); + // TODO: Scatter. + return partial_result; +} + +} // namespace + Result ExecuteScalarExpression(const Expression& expr, const ExecBatch& input, compute::ExecContext* exec_context) { if (exec_context == nullptr) { @@ -732,6 +776,10 @@ Result ExecuteScalarExpression(const Expression& expr, const ExecBatch& i "ExecuteScalarExpression cannot Execute non-scalar expression ", expr.ToString()); } + if (!expr.selection_vector_aware() && input.selection_vector) { + return ExecuteScalarExpressionWithSelSlowPath(expr, input, exec_context); + } + if (auto lit = expr.literal()) return *lit; if (auto param = expr.parameter()) { @@ -757,44 +805,20 @@ Result ExecuteScalarExpression(const Expression& expr, const ExecBatch& i auto call = CallNotNull(expr); - std::vector arguments(call->arguments.size()); - - bool all_scalar = true; - for (size_t i = 0; i < arguments.size(); ++i) { - ARROW_ASSIGN_OR_RAISE( - arguments[i], ExecuteScalarExpression(call->arguments[i], input, exec_context)); - if (arguments[i].is_array()) { - all_scalar = false; - } - } - - int64_t input_length; - if (!arguments.empty() && all_scalar) { - // all inputs are scalar, so use a 1-long batch to avoid - // computing input.length equivalent outputs - input_length = 1; + if (call->is_special_form) { + // Let the special form take over the execution using its own logic of argument + // evaluation. + DCHECK(call->special_form); + return call->special_form->Execute(*call, input, exec_context); } else { - input_length = input.length; + // Eagerly evaluate all the arguments. + std::vector arguments(call->arguments.size()); + for (size_t i = 0; i < arguments.size(); ++i) { + ARROW_ASSIGN_OR_RAISE( + arguments[i], ExecuteScalarExpression(call->arguments[i], input, exec_context)); + } + return ExecuteCallNonRecursive(*call, input, arguments, exec_context); } - - auto executor = compute::detail::KernelExecutor::MakeScalar(); - - compute::KernelContext kernel_context(exec_context, call->kernel); - kernel_context.SetState(call->kernel_state.get()); - - const Kernel* kernel = call->kernel; - std::vector types = GetTypes(arguments); - auto options = call->options.get(); - RETURN_NOT_OK(executor->Init(&kernel_context, {kernel, types, options})); - - compute::detail::DatumAccumulator listener; - RETURN_NOT_OK( - executor->Execute(ExecBatch(std::move(arguments), input_length), &listener)); - const auto out = executor->WrapResults(arguments, listener.values()); -#ifndef NDEBUG - DCHECK_OK(executor->CheckResultType(out, call->function_name.c_str())); -#endif - return out; } namespace { diff --git a/cpp/src/arrow/compute/expression.h b/cpp/src/arrow/compute/expression.h index 9a36a6d3368..f0f78316676 100644 --- a/cpp/src/arrow/compute/expression.h +++ b/cpp/src/arrow/compute/expression.h @@ -48,6 +48,9 @@ class ARROW_EXPORT Expression { std::string function_name; std::vector arguments; std::shared_ptr options; + // Whether this call is a special form (e.g. if-else). If true, the `special_form` + // field will be resolved in binding. + bool is_special_form = false; // Cached hash value size_t hash; @@ -56,6 +59,9 @@ class ARROW_EXPORT Expression { const Kernel* kernel = NULLPTR; std::shared_ptr kernel_state; TypeHolder type; + // Whether the entire call (including all its arguments) is selection-vector-aware + bool selection_vector_aware = false; + std::shared_ptr special_form = NULLPTR; void ComputeHash(); }; @@ -118,6 +124,12 @@ class ARROW_EXPORT Expression { // XXX someday // NullGeneralization::type nullable() const; + /// Whether the entire expression (including all its subexpressions) is + /// selection-vector-aware. If true, then the expression can be executed using the "fast + /// path" - all kernels directly working on the selection vector. Otherwise the + /// execution takes the "slow path" - gathering the input and scattering the output. + bool selection_vector_aware() const; + struct Parameter { FieldRef ref; @@ -159,14 +171,15 @@ Expression field_ref(FieldRef ref); ARROW_EXPORT Expression call(std::string function, std::vector arguments, - std::shared_ptr options = NULLPTR); + std::shared_ptr options = NULLPTR, + bool is_special_form = false); template ::value>::type> -Expression call(std::string function, std::vector arguments, - Options options) { +Expression call(std::string function, std::vector arguments, Options options, + bool is_special_form = false) { return call(std::move(function), std::move(arguments), - std::make_shared(std::move(options))); + std::make_shared(std::move(options)), is_special_form); } /// Assemble a list of all fields referenced by an Expression at any depth. diff --git a/cpp/src/arrow/compute/expression_internal.h b/cpp/src/arrow/compute/expression_internal.h index 2048ef96651..919a5f0569b 100644 --- a/cpp/src/arrow/compute/expression_internal.h +++ b/cpp/src/arrow/compute/expression_internal.h @@ -24,6 +24,7 @@ #include "arrow/compute/api_scalar.h" #include "arrow/compute/cast.h" #include "arrow/compute/cast_internal.h" +#include "arrow/compute/exec_internal.h" #include "arrow/compute/registry.h" #include "arrow/record_batch.h" #include "arrow/table.h" @@ -290,5 +291,41 @@ inline Result> GetFunction( return GetCastFunction(*to_type); } +// Execute a call expression with all arguments already evaluated. +inline Result ExecuteCallNonRecursive(const Expression::Call& call, + const ExecBatch& input, + const std::vector& arguments, + ExecContext* exec_context) { + auto executor = compute::detail::KernelExecutor::MakeScalar(); + + compute::KernelContext kernel_context(exec_context, call.kernel); + kernel_context.SetState(call.kernel_state.get()); + + const Kernel* kernel = call.kernel; + std::vector types = GetTypes(arguments); + auto options = call.options.get(); + RETURN_NOT_OK(executor->Init(&kernel_context, {kernel, types, options})); + + bool all_scalar = std::all_of(arguments.begin(), arguments.end(), + [](const Datum& value) { return value.is_scalar(); }); + int64_t input_length; + if (!arguments.empty() && all_scalar) { + // all inputs are scalar, so use a 1-long batch to avoid + // computing input.length equivalent outputs + input_length = 1; + } else { + input_length = input.length; + } + ExecBatch arguments_batch(std::move(arguments), input_length); + arguments_batch.selection_vector = input.selection_vector; + compute::detail::DatumAccumulator listener; + RETURN_NOT_OK(executor->Execute(std::move(arguments_batch), &listener)); + const auto out = executor->WrapResults(arguments, listener.values()); +#ifndef NDEBUG + DCHECK_OK(executor->CheckResultType(out, call.function_name.c_str())); +#endif + return out; +} + } // namespace compute } // namespace arrow diff --git a/cpp/src/arrow/compute/expression_test.cc b/cpp/src/arrow/compute/expression_test.cc index 30bd882b2c0..af04e137014 100644 --- a/cpp/src/arrow/compute/expression_test.cc +++ b/cpp/src/arrow/compute/expression_test.cc @@ -32,6 +32,7 @@ #include "arrow/compute/registry.h" #include "arrow/testing/gtest_util.h" #include "arrow/testing/matchers.h" +#include "arrow/util/vector.h" using testing::Eq; using testing::HasSubstr; @@ -1775,5 +1776,31 @@ TEST(Projection, AugmentWithKnownValues) { })); } +TEST(Expression, IfElseSpecialForm) { + const std::shared_ptr input_schema = + schema({field("a", int64()), field("b", int64()), field("c", int64()), + field("d", int64())}); + auto input = RecordBatchFromJSON(input_schema, R"([ + {"a": 0, "b": -1, "c": 0, "d": -1}, + {"a": 1, "b": -1, "c": 0, "d": -1}, + {"a": 0, "b": -1, "c": 1, "d": -1}, + {"a": 1, "b": -1, "c": 1, "d": -1} + ])"); + auto a = field_ref("a"); + auto b = field_ref("b"); + auto c = field_ref("c"); + auto d = field_ref("d"); + auto zero = literal(0ll); + auto one = literal(1ll); + auto a_not_zero = not_equal(a, zero); + auto one_div_a = call("divide", {one, a}); + auto if_else_inner = call("if_else", {a_not_zero, one_div_a, zero}, nullptr, true); + auto if_else_inner_not_zero = not_equal(if_else_inner, zero); + auto if_else_outer = + call("if_else", {if_else_inner_not_zero, one_div_a, zero}, nullptr, true); + ASSERT_OK_AND_ASSIGN(auto expr, if_else_outer.Bind(*input_schema)); + ASSERT_OK_AND_ASSIGN(auto result, ExecuteScalarExpression(expr, *input_schema, input)); +} + } // namespace compute } // namespace arrow diff --git a/cpp/src/arrow/compute/kernel.h b/cpp/src/arrow/compute/kernel.h index 1adb3e96c97..4f0738aa0b3 100644 --- a/cpp/src/arrow/compute/kernel.h +++ b/cpp/src/arrow/compute/kernel.h @@ -524,6 +524,12 @@ struct ARROW_EXPORT Kernel { // Additional kernel-specific data std::shared_ptr data; + + /// \brief Indicates whether the kernel is selection-vector-aware. A + /// selection-vector-aware kernel is able to emit a partial result based on a given + /// selection vector without having to gathering the input and scattering the output, + /// aka. the "slow path", in the caller. + bool selection_vector_aware = false; }; /// \brief The scalar kernel execution API that must be implemented for SCALAR diff --git a/cpp/src/arrow/compute/special_form.cc b/cpp/src/arrow/compute/special_form.cc new file mode 100644 index 00000000000..488b6c76874 --- /dev/null +++ b/cpp/src/arrow/compute/special_form.cc @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// NOTE: API is EXPERIMENTAL and will change without going through a +// deprecation cycle. + +#include "arrow/compute/special_form.h" +#include "arrow/compute/api_vector.h" +#include "arrow/compute/exec.h" +#include "arrow/compute/special_forms/if_else_special_form.h" +#include "arrow/util/logging.h" + +namespace arrow { +namespace compute { + +Result> SpecialForm::Make(const std::string& name) { + if (name == "if_else") { + return std::make_unique(); + } + return Status::Invalid("Unknown special form: ", name); +} + +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/special_form.h b/cpp/src/arrow/compute/special_form.h new file mode 100644 index 00000000000..b538151bb6e --- /dev/null +++ b/cpp/src/arrow/compute/special_form.h @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// NOTE: API is EXPERIMENTAL and will change without going through a +// deprecation cycle. + +#pragma once + +#include "arrow/compute/expression.h" +#include "arrow/util/visibility.h" + +#include + +namespace arrow { +namespace compute { + +/// The concept "special form" is borrowed from Lisp +/// (https://courses.cs.northwestern.edu/325/readings/special-forms.html). Velox also uses +/// the same term. A special form behaves like a function call except that it has special +/// evaluation rules, mostly for arguments. +/// For example, the `if_else(cond, expr1, expr2)` special form first evaluates the +/// argument `cond` and obtains a boolean array: +/// [true, false, true, false] +/// then the argument `expr1` should ONLY be evaluated for row: +/// [0, 2] +/// and the argument `expr2` should ONLY be evaluated for row: +/// [1, 3] +/// Consider, if `expr1`/`expr2` has some observable side-effects (e.g., division by zero +/// error) on row [1, 3]/[0, 2], these side-effects would be undesirably observed if +/// evaluated using a regular function call, which always evaluates all its arguments +/// eagerly. +/// Other special forms include `case_when`, `and`, and `or`, etc. +/// In a vectorized execution engine, a special form normally takes advantage of +/// "selection vector" to mask rows of arguments to be evaluated. +class ARROW_EXPORT SpecialForm { + public: + /// A poor man's factory method to create a special form by name. + /// TODO: More formal factory, a registry maybe? + static Result> Make(const std::string& name); + + virtual ~SpecialForm() = default; + + virtual Result Execute(const Expression::Call& call, const ExecBatch& input, + ExecContext* exec_context) = 0; +}; + +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/special_forms/if_else_special_form.cc b/cpp/src/arrow/compute/special_forms/if_else_special_form.cc new file mode 100644 index 00000000000..cade1a3ed79 --- /dev/null +++ b/cpp/src/arrow/compute/special_forms/if_else_special_form.cc @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// NOTE: API is EXPERIMENTAL and will change without going through a +// deprecation cycle. + +#include "arrow/compute/special_forms/if_else_special_form.h" +#include "arrow/compute/exec.h" +#include "arrow/compute/expression.h" +#include "arrow/compute/expression_internal.h" + +namespace arrow { +namespace compute { + +Result IfElseSpecialForm::Execute(const Expression::Call& call, + const ExecBatch& input, + ExecContext* exec_context) { + // The kernel (if_else) is not selection-vector-aware, so the input should not have a + // selection vector. + DCHECK(!call.kernel->selection_vector_aware && !input.selection_vector); + + std::vector arguments(call.arguments.size()); + ARROW_ASSIGN_OR_RAISE(arguments[0], + ExecuteScalarExpression(call.arguments[0], input, exec_context)); + // Use cond as selection vector for IF branch. + // TODO: Consider chunked array for arguments[0]. + auto if_sel = std::make_shared(arguments[0].array()); + // Duplicate and invert cond as selection vector for ELSE branch. + ARROW_ASSIGN_OR_RAISE( + auto else_sel, + if_sel->Copy(CPUDevice::memory_manager(exec_context->memory_pool()))); + RETURN_NOT_OK(else_sel->Invert()); + + ExecBatch if_input = input; + if_input.selection_vector = std::move(if_sel); + ARROW_ASSIGN_OR_RAISE( + arguments[1], ExecuteScalarExpression(call.arguments[1], if_input, exec_context)); + ExecBatch else_input = input; + else_input.selection_vector = std::move(else_sel); + ARROW_ASSIGN_OR_RAISE( + arguments[2], ExecuteScalarExpression(call.arguments[2], else_input, exec_context)); + + // Leveraging if_else kernel with all arguments evaluated. + return ExecuteCallNonRecursive(call, input, arguments, exec_context); +} + +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/special_forms/if_else_special_form.h b/cpp/src/arrow/compute/special_forms/if_else_special_form.h new file mode 100644 index 00000000000..f03042356b3 --- /dev/null +++ b/cpp/src/arrow/compute/special_forms/if_else_special_form.h @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// NOTE: API is EXPERIMENTAL and will change without going through a +// deprecation cycle. + +#pragma once + +#include "arrow/compute/special_form.h" + +namespace arrow { +namespace compute { + +class ARROW_EXPORT IfElseSpecialForm : public SpecialForm { + public: + Result Execute(const Expression::Call& call, const ExecBatch& input, + ExecContext* exec_context) override; +}; + +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/type_fwd.h b/cpp/src/arrow/compute/type_fwd.h index 89f32ceb0f9..6c9b4cc8a6f 100644 --- a/cpp/src/arrow/compute/type_fwd.h +++ b/cpp/src/arrow/compute/type_fwd.h @@ -50,6 +50,7 @@ struct VectorKernel; struct KernelState; class Expression; +class SpecialForm; ARROW_EXPORT ExecContext* default_exec_context(); ARROW_EXPORT ExecContext* threaded_exec_context();