#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int ILLEGAL_COLUMN; extern const int ILLEGAL_TYPE_OF_ARGUMENT; } using NullMap = PaddedPODArray; /// ConcreteActions -- what to do when the index was found. struct HasAction { using ResultType = UInt8; static constexpr const bool resume_execution = false; static constexpr void apply(ResultType& current, size_t) noexcept { current = 1; } }; /// The index is returned starting from 1. struct IndexOfAction { using ResultType = UInt64; static constexpr const bool resume_execution = false; static constexpr void apply(ResultType& current, size_t j) noexcept { current = j + 1; } }; struct CountEqualAction { using ResultType = UInt64; static constexpr const bool resume_execution = true; static constexpr void apply(ResultType & current, size_t) noexcept { ++current; } }; /// How to perform the search depending on the arguments data types. namespace Impl { template < typename ConcreteAction, bool RightArgIsConstant = false, typename IntegralInitial = UInt64, typename IntegralResult = UInt64> struct Main { private: using Initial = IntegralInitial; using Result = IntegralResult; using ResultType = typename ConcreteAction::ResultType; using ResultArr = PaddedPODArray; using ArrOffset = ColumnArray::Offset; using ArrOffsets = ColumnArray::Offsets; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-compare" static constexpr bool compare(const Initial & left, const PaddedPODArray & right, size_t, size_t i) noexcept { return left == right[i]; } static constexpr bool compare(const PaddedPODArray & left, const Result & right, size_t i, size_t) noexcept { return left[i] == right; } static constexpr bool compare( const PaddedPODArray & left, const PaddedPODArray & right, size_t i, size_t j) noexcept { return left[i] == right[j]; } /// LowCardinality static bool compare(const IColumn & left, const Result & right, size_t i, size_t) { return left.getUInt(i) == right; } /// Generic static bool compare(const IColumn & left, const IColumn & right, size_t i, size_t j) { return 0 == left.compareAt(i, RightArgIsConstant ? 0 : j, right, 1); } #pragma clang diagnostic pop static constexpr bool hasNull(const NullMap * const null_map, size_t i) noexcept { return (*null_map)[i]; } template static void process( const Data & data, const ArrOffsets & offsets, const Target & target, ResultArr & result, [[maybe_unused]] const NullMap * const null_map_data, [[maybe_unused]] const NullMap * const null_map_item) { if constexpr (std::is_same_v && std::is_same_v) { /// Generic variant is using IColumn::compare function that only allows to compare columns of identical types. if (typeid(data) != typeid(target)) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Columns {} and {} cannot be compared", data.getName(), target.getName()); } const size_t size = offsets.size(); result.resize(size); ArrOffset current_offset = 0; for (size_t i = 0; i < size; ++i) { const size_t array_size = offsets[i] - current_offset; ResultType current = 0; for (size_t j = 0; j < array_size; ++j) { if constexpr (Case == 2) /// Right arg is Nullable if (hasNull(null_map_item, i)) continue; if constexpr (Case == 3) /// Left arg is an array of Nullables if (hasNull(null_map_data, current_offset + j)) continue; if constexpr (Case == 4) /// Both args are nullable { const bool right_is_null = hasNull(null_map_data, current_offset + j); const bool left_is_null = hasNull(null_map_item, i); if (right_is_null != left_is_null) continue; if (!right_is_null && !compare(data, target, current_offset + j, i)) continue; } else if (!compare(data, target, current_offset + j, i)) continue; ConcreteAction::apply(current, j); if constexpr (!ConcreteAction::resume_execution) break; } result[i] = current; current_offset = offsets[i]; } } public: template static void vector( const Data & data, const ArrOffsets & offsets, const Target & value, ResultArr & result, const NullMap * const null_map_data, const NullMap * const null_map_item) { if (!null_map_data && !null_map_item) process<1>(data, offsets, value, result, null_map_data, null_map_item); else if (!null_map_data && null_map_item) process<2>(data, offsets, value, result, null_map_data, null_map_item); else if (null_map_data && !null_map_item) process<3>(data, offsets, value, result, null_map_data, null_map_item); else process<4>(data, offsets, value, result, null_map_data, null_map_item); } }; /// When the 2nd function argument is a NULL value. template struct Null { using ResultType = typename ConcreteAction::ResultType; static void process( const ColumnArray::Offsets & offsets, PaddedPODArray & result, [[maybe_unused]] const NullMap * null_map_data) { const size_t size = offsets.size(); if (!null_map_data) { result.resize_fill(size); return; } result.resize(size); ColumnArray::Offset current_offset = 0; for (size_t i = 0; i < size; ++i) { ResultType current = 0; const size_t array_size = offsets[i] - current_offset; for (size_t j = 0; j < array_size; ++j) { if (!(*null_map_data)[current_offset + j]) continue; ConcreteAction::apply(current, j); if constexpr (!ConcreteAction::resume_execution) break; } result[i] = current; current_offset = offsets[i]; } } }; template struct String { private: using Offset = ColumnString::Offset; template using OffsetT = std::conditional_t; using ArrayOffset = ColumnArray::Offset; using ResultType = typename ConcreteAction::ResultType; template static void processImpl( const ColumnString::Chars & data, const ColumnArray::Offsets & offsets, const ColumnString::Offsets & string_offsets, const ColumnString::Chars & item_values, OffsetT item_offsets, PaddedPODArray & result, [[maybe_unused]] const NullMap * data_map, [[maybe_unused]] const NullMap * item_map) { const size_t size = offsets.size(); result.resize(size); ArrayOffset current_offset = 0; for (size_t i = 0; i < size; ++i) { const ArrayOffset array_size = offsets[i] - current_offset; [[maybe_unused]] Offset value_pos = 0; [[maybe_unused]] Offset value_size = 0; if constexpr (!IsConst) // workaround because ?: ternary operator is not constexpr { if (0 != i) value_pos = item_offsets[i - 1]; value_size = item_offsets[i] - value_pos; } ResultType current = 0; for (size_t j = 0; j < array_size; ++j) { const ArrayOffset string_pos = current_offset + j == 0 ? 0 : string_offsets[current_offset + j - 1]; const ArrayOffset string_size = string_offsets[current_offset + j] - string_pos - IsConst * 1; if constexpr (IsConst) { if constexpr (HasNullMapData) if ((*data_map)[current_offset + j]) continue; if (!memequalSmallAllowOverflow15(item_values.data(), item_offsets, &data[string_pos], string_size)) continue; } else if constexpr (HasNullMapData) { if ((*data_map)[current_offset + j]) { if constexpr (!HasNullMapItem) continue; if (!(*item_map)[i]) continue; } else if (!memequalSmallAllowOverflow15(&item_values[value_pos], value_size, &data[string_pos], string_size)) continue; } else if (!memequalSmallAllowOverflow15(&item_values[value_pos], value_size, &data[string_pos], string_size)) continue; ConcreteAction::apply(current, j); if constexpr (!ConcreteAction::resume_execution) break; } result[i] = current; current_offset = offsets[i]; } } template static inline void invokeCheckNullMaps( const ColumnString::Chars & data, const ColumnArray::Offsets & offsets, const ColumnString::Offsets & str_offsets, const ColumnString::Chars & values, OffsetT item_offsets, PaddedPODArray & result, const NullMap * data_map, const NullMap * item_map) { if (data_map && item_map) processImpl(data, offsets, str_offsets, values, item_offsets, result, data_map, item_map); else if (data_map) processImpl(data, offsets, str_offsets, values, item_offsets, result, data_map, item_map); else if (item_map) processImpl(data, offsets, str_offsets, values, item_offsets, result, data_map, item_map); else processImpl(data, offsets, str_offsets, values, item_offsets, result, data_map, item_map); } public: static inline void process( const ColumnString::Chars & data, const ColumnArray::Offsets & offsets, const ColumnString::Offsets & string_offsets, const ColumnString::Chars & item_values, Offset item_offsets, PaddedPODArray & result, const NullMap * data_map, const NullMap * item_map) { invokeCheckNullMaps(data, offsets, string_offsets, item_values, item_offsets, result, data_map, item_map); } static inline void process( const ColumnString::Chars & data, const ColumnArray::Offsets & offsets, const ColumnString::Offsets & string_offsets, const ColumnString::Chars & item_values, const ColumnString::Offsets & item_offsets, PaddedPODArray & result, const NullMap * data_map, const NullMap * item_map) { invokeCheckNullMaps(data, offsets, string_offsets, item_values, item_offsets, result, data_map, item_map); } }; } template class FunctionArrayIndex : public IFunction { public: static constexpr auto name = Name::name; static FunctionPtr create(ContextPtr) { return std::make_shared(); } /// Get function name. String getName() const override { return name; } bool useDefaultImplementationForNulls() const override { return false; } bool useDefaultImplementationForLowCardinalityColumns() const override { return false; } bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; } size_t getNumberOfArguments() const override { return 2; } DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { auto first_argument_type = arguments[0].type; auto second_argument_type = arguments[1].type; const DataTypeArray * array_type = checkAndGetDataType(first_argument_type.get()); const DataTypeMap * map_type = checkAndGetDataType(first_argument_type.get()); DataTypePtr inner_type; /// If map is first argument only has(map_column, key) function is supported if constexpr (std::is_same_v) { if (!array_type && !map_type) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "First argument for function {} must be an array or map. Actual {}", getName(), first_argument_type->getName()); inner_type = map_type ? map_type->getKeyType() : array_type->getNestedType(); } else { if (!array_type) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "First argument for function {} must be an array. Actual {}", getName(), first_argument_type->getName()); inner_type = array_type->getNestedType(); } if (!second_argument_type->onlyNull() && !allowArguments(inner_type, second_argument_type)) { const char * first_argument_type_name = map_type ? "map" : "array"; throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Types of {} and 2nd argument of function `{}` must be identical up to nullability, cardinality, " "numeric types, or Enum and numeric type. Passed: {} and {}.", first_argument_type_name, getName(), first_argument_type->getName(), second_argument_type->getName()); } return std::make_shared>(); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t /*input_rows_count*/) const override { if constexpr (std::is_same_v) { if (isMap(arguments[0].type)) { auto non_const_map_column = arguments[0].column->convertToFullColumnIfConst(); const auto & map_column = assert_cast(*non_const_map_column); const auto & map_array_column = map_column.getNestedColumn(); auto offsets = map_array_column.getOffsetsPtr(); auto keys = map_column.getNestedData().getColumnPtr(0); auto array_column = ColumnArray::create(keys, offsets); const auto & type_map = assert_cast(*arguments[0].type); auto array_type = std::make_shared(type_map.getKeyType()); auto arguments_copy = arguments; arguments_copy[0].column = std::move(array_column); arguments_copy[0].type = std::move(array_type); arguments_copy[0].name = arguments[0].name; return executeArrayImpl(arguments_copy, result_type); } } return executeArrayImpl(arguments, result_type); } private: using ResultType = typename ConcreteAction::ResultType; using ResultColumnType = ColumnVector; using ResultColumnPtr = decltype(ResultColumnType::create()); using NullMaps = std::pair; struct ExecutionData { const IColumn& left; const IColumn& right; const ColumnArray::Offsets& offsets; ColumnPtr result_column; NullMaps maps; ResultColumnPtr result { ResultColumnType::create() }; inline void moveResult() { result_column = std::move(result); } }; static inline bool allowArguments(const DataTypePtr & inner_type, const DataTypePtr & arg) { auto inner_type_decayed = removeNullable(removeLowCardinality(inner_type)); auto arg_decayed = removeNullable(removeLowCardinality(arg)); return ((isNativeNumber(inner_type_decayed) || isEnum(inner_type_decayed)) && isNativeNumber(arg_decayed)) || getLeastSupertype(DataTypes{inner_type_decayed, arg_decayed}); } /** * If one or both arguments passed to this function are nullable, * we create a new column that contains non-nullable arguments: * * - if the 1st argument is a non-constant array of nullable values, * it is turned into a non-constant array of ordinary values + a null * byte map; * - if the 2nd argument is a nullable value, it is turned into an * ordinary value + a null byte map. * * Note that since constant arrays have quite a specific structure * (they are vectors of Fields, which may represent the NULL value), * they do not require any preprocessing. */ ColumnPtr executeArrayImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type) const { const ColumnPtr & ptr = arguments[0].column; /** * The columns here have two general cases, either being Array(T) or Const(Array(T)). * The last type will return nullptr after casting to ColumnArray, so we leave the casting * to execute* functions. */ const ColumnArray * col_array = checkAndGetColumn(ptr.get()); const ColumnNullable * nullable = nullptr; if (col_array) nullable = checkAndGetColumn(col_array->getData()); const auto & arg_column = arguments[1].column; const ColumnNullable * arg_nullable = checkAndGetColumn(*arg_column); if (!nullable && !arg_nullable) { return executeOnNonNullable(arguments, result_type); } else { /** * To correctly process the Nullable values (either #col_array, #arg_column or both) we create a new columns * and operate on it. The columns structure follows: * {0, 1, 2, 3, 4} * {data (array) argument, "value" argument, data null map, "value" null map, function result}. */ ColumnsWithTypeAndName source_columns(4); if (nullable) { const auto & nested_col = nullable->getNestedColumnPtr(); auto & data = source_columns[0]; data.column = ColumnArray::create(nested_col, col_array->getOffsetsPtr()); data.type = std::make_shared( static_cast( *static_cast( *arguments[0].type ).getNestedType() ).getNestedType()); auto & null_map = source_columns[2]; null_map.column = nullable->getNullMapColumnPtr(); null_map.type = std::make_shared(); } else { auto & data = source_columns[0]; data = arguments[0]; } if (arg_nullable) { auto & arg = source_columns[1]; arg.column = arg_nullable->getNestedColumnPtr(); arg.type = static_cast( *arguments[1].type ).getNestedType(); auto & null_map = source_columns[3]; null_map.column = arg_nullable->getNullMapColumnPtr(); null_map.type = std::make_shared(); } else { auto & arg = source_columns[1]; arg = arguments[1]; } /// Now perform the function. return executeOnNonNullable(source_columns, result_type); } } #define INTEGRAL_TPL_PACK UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64, Float32, Float64 ColumnPtr executeOnNonNullable(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type) const { if (const auto* const left_arr = checkAndGetColumn(arguments[0].column.get())) { if (checkAndGetColumn(&left_arr->getData())) { if (auto res = executeLowCardinality(arguments)) return res; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal internal type of first argument of function {}", getName()); } } ColumnPtr res; if (!((res = executeIntegral(arguments)) || (res = executeConst(arguments, result_type)) || (res = executeString(arguments)) || (res = executeGeneric(arguments)))) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal internal type of first argument of function {}", getName()); return res; } /** * The Array's internal data type may be quite tricky (containing a Nullable type somewhere). To process the * Nullable types correctly, for each data type specialisation we provide two null maps (one for the data and one * for the items). By convention they are passed as the third and the fourth argument, respectively * (counting from 1). * * @return {nullptr, nullptr} if there are less than 3 arguments. * @return {null_map_data, nullptr} if there are three arguments * @return {nullptr, null_map_item} if there are four arguments but the third is missing. * @return {null_map_data, null_map_item} if there are four arguments. */ static NullMaps getNullMaps(const ColumnsWithTypeAndName & arguments) noexcept { if (arguments.size() < 3) return {nullptr, nullptr}; const NullMap * null_map_data = nullptr; const NullMap * null_map_item = nullptr; if (const auto & data_map = arguments[2].column; data_map) null_map_data = &assert_cast(*data_map).getData(); if (const auto & item_map = arguments[3].column; item_map) null_map_item = &assert_cast(*item_map).getData(); return {null_map_data, null_map_item}; } /** * Given a variadic pack #Integral, apply executeIntegralExpanded with such parameters: * Integral s = {s1, s2, ...} * (s1, s1, s2, ...), (s2, s1, s2, ...), (s3, s1, s2, ...) */ template static inline ColumnPtr executeIntegral(const ColumnsWithTypeAndName & arguments) { const ColumnArray * const left = checkAndGetColumn(arguments[0].column.get()); if (!left) return nullptr; const ColumnPtr right_converted_ptr = arguments[1].column->convertToFullColumnIfLowCardinality(); const IColumn& right = *right_converted_ptr.get(); ExecutionData data = { left->getData(), right, left->getOffsets(), nullptr, getNullMaps(arguments) }; if (executeIntegral(data)) return data.result_column; return nullptr; } template static inline bool executeIntegral(ExecutionData& data) { return (executeIntegralExpanded(data) || ...); } /// Invoke executeIntegralImpl with such parameters: (A, other1), (A, other2), ... template static inline bool executeIntegralExpanded(ExecutionData& data) { return (executeIntegralImpl(data) || ...); } /** * The internal data type of the first argument (target array), if it's integral, like UInt8, may differ from the * second argument, namely, the @e value, so it's possible to invoke the has(Array(Int8), UInt64) e.g. * so we have to check all possible variants for #Initial and #Resulting types. */ template static bool executeIntegralImpl(ExecutionData& data) { const ColumnVector * col_nested = checkAndGetColumn>(&data.left); if (!col_nested) return false; const auto [null_map_data, null_map_item] = data.maps; if (data.right.onlyNull()) Impl::Null::process( data.offsets, data.result->getData(), null_map_data); else if (const auto item_arg_const = checkAndGetColumnConst>(&data.right)) Impl::Main::vector( col_nested->getData(), data.offsets, item_arg_const->template getValue(), data.result->getData(), null_map_data, nullptr); else if (const auto item_arg_vector = checkAndGetColumn>(&data.right)) Impl::Main::vector( col_nested->getData(), data.offsets, item_arg_vector->getData(), data.result->getData(), null_map_data, null_map_item); else return false; data.moveResult(); return true; } /** * Catches arguments of type LowCardinality(T) (left) and U (right). * * The perftests showed that the amount of action needed to convert the non-constant right argument to the index column * (similar to the left one's) is significantly higher than converting the array itself to an ordinary column. * * So, in terms of performance it's more optimal to fall back to default implementation and catch only constant * right arguments. * * Tips and tricks tried can be found at https://github.com/ClickHouse/ClickHouse/pull/12550 . */ static ColumnPtr executeLowCardinality(const ColumnsWithTypeAndName & arguments) { const ColumnArray * const col_array = checkAndGetColumn(arguments[0].column.get()); if (!col_array) return nullptr; const ColumnLowCardinality * const col_lc = checkAndGetColumn(&col_array->getData()); if (!col_lc) return nullptr; const auto [null_map_data, null_map_item] = getNullMaps(arguments); if (const ColumnConst * col_arg_const = checkAndGetColumn(*arguments[1].column)) { const IColumnUnique & col_lc_dict = col_lc->getDictionary(); const DataTypeArray * const array_type = checkAndGetDataType(arguments[0].type.get()); const DataTypePtr target_type_ptr = recursiveRemoveLowCardinality(array_type->getNestedType()); ColumnPtr col_arg_cloned = castColumn( {col_arg_const->getDataColumnPtr(), arguments[1].type, arguments[1].name}, target_type_ptr); ResultColumnPtr col_result = ResultColumnType::create(); UInt64 index = 0; if (!col_arg_cloned->isNullAt(0)) { if (col_arg_cloned->isNullable()) col_arg_cloned = checkAndGetColumn(*col_arg_cloned)->getNestedColumnPtr(); StringRef elem = col_arg_cloned->getDataAt(0); if (std::optional maybe_index = col_lc_dict.getOrFindValueIndex(elem); maybe_index) { index = *maybe_index; } else { const size_t offsets_size = col_array->getOffsets().size(); auto & data = col_result->getData(); data.resize_fill(offsets_size); return col_result; } } Impl::Main::vector( col_lc->getIndexes(), col_array->getOffsets(), index, /** Assuming LowCardinality has index of NULL always as zero. */ col_result->getData(), null_map_data, null_map_item); return col_result; } else if (col_lc->nestedIsNullable()) // LowCardinality(Nullable(T)) and U { const ColumnPtr left_casted = col_lc->convertToFullColumnIfLowCardinality(); // Nullable(T) const ColumnNullable& left_nullable = *checkAndGetColumn(left_casted.get()); const NullMap * const null_map_left_casted = &left_nullable.getNullMapColumn().getData(); const IColumn & left_ptr = left_nullable.getNestedColumn(); const ColumnPtr right_casted = arguments[1].column->convertToFullColumnIfLowCardinality(); const ColumnNullable * const right_nullable = checkAndGetColumn(right_casted.get()); const NullMap * const null_map_right_casted = right_nullable ? &right_nullable->getNullMapColumn().getData() : null_map_item; const IColumn& right_ptr = right_nullable ? right_nullable->getNestedColumn() : *right_casted.get(); ExecutionData data = { left_ptr, right_ptr, col_array->getOffsets(), nullptr, {null_map_left_casted, null_map_right_casted}}; if (dispatchConvertedLowCardinalityColumns(data)) return data.result_column; } else // LowCardinality(T) and U, T not Nullable { if (arguments[1].column->isNullable()) return nullptr; if (const auto* const arg_lc = checkAndGetColumn(arguments[1].column.get()); arg_lc && arg_lc->isNullable()) return nullptr; // LowCardinality(T) and U (possibly LowCardinality(V)) const ColumnPtr left_casted = col_lc->convertToFullColumnIfLowCardinality(); const ColumnPtr right_casted = arguments[1].column->convertToFullColumnIfLowCardinality(); ExecutionData data = { *left_casted.get(), *right_casted.get(), col_array->getOffsets(), nullptr, {null_map_data, null_map_item} }; if (dispatchConvertedLowCardinalityColumns(data)) return data.result_column; } return nullptr; } static bool dispatchConvertedLowCardinalityColumns(ExecutionData & data) { if (data.left.isNumeric() && data.right.isNumeric()) // ColumnArrays return executeIntegral(data); if (checkAndGetColumn(&data.left)) return executeStringImpl(data); Impl::Main::vector( data.left, data.offsets, data.right, data.result->getData(), data.maps.first, data.maps.second); data.moveResult(); return true; } #undef INTEGRAL_TPL_PACK static ColumnPtr executeString(const ColumnsWithTypeAndName & arguments) { const ColumnArray * array = checkAndGetColumn(arguments[0].column.get()); if (!array) return nullptr; const ColumnString * left = checkAndGetColumn(&array->getData()); if (!left) return nullptr; const ColumnPtr right_ptr = arguments[1].column->convertToFullColumnIfLowCardinality(); const IColumn & right = *right_ptr.get(); ExecutionData data = { *left, right, array->getOffsets(), nullptr, getNullMaps(arguments), std::move(ResultColumnType::create()) }; if (executeStringImpl(data)) return data.result_column; return nullptr; } static bool executeStringImpl(ExecutionData& data) { const auto [null_map_data, null_map_item] = data.maps; const ColumnString& left = *typeid_cast(&data.left); if (data.right.onlyNull()) Impl::Null::process( data.offsets, data.result->getData(), null_map_data); else if (const auto *const item_arg_const = checkAndGetColumnConstStringOrFixedString(&data.right)) { const ColumnString * item_const_string = checkAndGetColumn(&item_arg_const->getDataColumn()); const ColumnFixedString * item_const_fixedstring = checkAndGetColumn(&item_arg_const->getDataColumn()); if (item_const_string) Impl::String::process( left.getChars(), data.offsets, left.getOffsets(), item_const_string->getChars(), item_const_string->getDataAt(0).size, data.result->getData(), null_map_data, null_map_item); else if (item_const_fixedstring) Impl::String::process( left.getChars(), data.offsets, left.getOffsets(), item_const_fixedstring->getChars(), item_const_fixedstring->getN(), data.result->getData(), null_map_data, null_map_item); else throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Logical error: ColumnConst contains not String nor FixedString column"); } else if (const auto *const item_arg_vector = checkAndGetColumn(&data.right)) { Impl::String::process( left.getChars(), data.offsets, left.getOffsets(), item_arg_vector->getChars(), item_arg_vector->getOffsets(), data.result->getData(), null_map_data, null_map_item); } else return false; data.moveResult(); return true; } static ColumnPtr executeConst(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type) { const ColumnConst * col_array = checkAndGetColumnConst(arguments[0].column.get()); if (!col_array) return nullptr; Array arr = col_array->getValue(); const ColumnPtr right_ptr = arguments[1].column->convertToFullColumnIfLowCardinality(); const IColumn * item_arg = right_ptr.get(); if (isColumnConst(*item_arg)) { ResultType current = 0; const auto & value = (*item_arg)[0]; for (size_t i = 0, size = arr.size(); i < size; ++i) { if (!applyVisitor(FieldVisitorAccurateEquals(), arr[i], value)) continue; ConcreteAction::apply(current, i); if constexpr (!ConcreteAction::resume_execution) break; } return result_type->createColumnConst(item_arg->size(), static_cast(current)); } else { /// Null map of the 2nd function argument, if it applies. const NullMap * null_map = nullptr; if (arguments.size() > 2) if (const auto & col = arguments[3].column; col) null_map = &assert_cast(*col).getData(); const size_t size = item_arg->size(); auto col_res = ResultColumnType::create(size); auto & data = col_res->getData(); for (size_t row = 0; row < size; ++row) { const auto & value = (*item_arg)[row]; data[row] = 0; for (size_t i = 0, arr_size = arr.size(); i < arr_size; ++i) { if (arr[i].isNull()) { if (!null_map) continue; if (!(*null_map)[row]) continue; } else if (!applyVisitor(FieldVisitorAccurateEquals(), arr[i], value)) continue; ConcreteAction::apply(data[row], i); if constexpr (!ConcreteAction::resume_execution) break; } } return col_res; } } static ColumnPtr executeGeneric(const ColumnsWithTypeAndName & arguments) { const ColumnArray * col = checkAndGetColumn(arguments[0].column.get()); if (!col) return nullptr; DataTypePtr array_elements_type = assert_cast(*arguments[0].type).getNestedType(); const DataTypePtr & index_type = arguments[1].type; DataTypePtr common_type = getLeastSupertype(DataTypes{array_elements_type, index_type}); ColumnPtr col_nested = castColumn({ col->getDataPtr(), array_elements_type, "" }, common_type); const ColumnPtr right_ptr = arguments[1].column->convertToFullColumnIfLowCardinality(); ColumnPtr item_arg = castColumn({ right_ptr, removeLowCardinality(index_type), "" }, common_type); auto col_res = ResultColumnType::create(); auto [null_map_data, null_map_item] = getNullMaps(arguments); if (item_arg->onlyNull()) Impl::Null::process( col->getOffsets(), col_res->getData(), null_map_data); else if (isColumnConst(*item_arg)) Impl::Main::vector( *col_nested, col->getOffsets(), typeid_cast(*item_arg).getDataColumn(), col_res->getData(), /// TODO This is wrong. null_map_data, nullptr); else Impl::Main::vector( *col_nested, col->getOffsets(), *item_arg, col_res->getData(), null_map_data, null_map_item); return col_res; } }; }