1#include <Functions/IFunctionAdaptors.h>
2
3#include <Common/config.h>
4#include <Common/typeid_cast.h>
5#include <Common/assert_cast.h>
6#include <Common/LRUCache.h>
7#include <Columns/ColumnConst.h>
8#include <Columns/ColumnNullable.h>
9#include <Columns/ColumnArray.h>
10#include <Columns/ColumnTuple.h>
11#include <Columns/ColumnLowCardinality.h>
12#include <DataTypes/DataTypeNothing.h>
13#include <DataTypes/DataTypeNullable.h>
14#include <DataTypes/DataTypeTuple.h>
15#include <DataTypes/Native.h>
16#include <DataTypes/DataTypeLowCardinality.h>
17#include <DataTypes/getLeastSupertype.h>
18#include <Functions/FunctionHelpers.h>
19#include <Interpreters/ExpressionActions.h>
20#include <IO/WriteHelpers.h>
21#include <ext/range.h>
22#include <ext/collection_cast.h>
23#include <cstdlib>
24#include <memory>
25#include <optional>
26
27#if USE_EMBEDDED_COMPILER
28#pragma GCC diagnostic push
29#pragma GCC diagnostic ignored "-Wunused-parameter"
30#include <llvm/IR/IRBuilder.h>
31#pragma GCC diagnostic pop
32#endif
33
34
35namespace DB
36{
37
38namespace ErrorCodes
39{
40 extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
41 extern const int ILLEGAL_COLUMN;
42}
43
44
45/// Cache for functions result if it was executed on low cardinality column.
46/// It's LRUCache which stores function result executed on dictionary and index mapping.
47/// It's expected that cache_size is a number of reading streams (so, will store single cached value per thread).
48class ExecutableFunctionLowCardinalityResultCache
49{
50public:
51 /// Will assume that dictionaries with same hash has the same keys.
52 /// Just in case, check that they have also the same size.
53 struct DictionaryKey
54 {
55 UInt128 hash;
56 UInt64 size;
57
58 bool operator== (const DictionaryKey & other) const { return hash == other.hash && size == other.size; }
59 };
60
61 struct DictionaryKeyHash
62 {
63 size_t operator()(const DictionaryKey & key) const
64 {
65 SipHash hash;
66 hash.update(key.hash.low);
67 hash.update(key.hash.high);
68 hash.update(key.size);
69 return hash.get64();
70 }
71 };
72
73 struct CachedValues
74 {
75 /// Store ptr to dictionary to be sure it won't be deleted.
76 ColumnPtr dictionary_holder;
77 ColumnUniquePtr function_result;
78 /// Remap positions. new_pos = index_mapping->index(old_pos);
79 ColumnPtr index_mapping;
80 };
81
82 using CachedValuesPtr = std::shared_ptr<CachedValues>;
83
84 explicit ExecutableFunctionLowCardinalityResultCache(size_t cache_size) : cache(cache_size) {}
85
86 CachedValuesPtr get(const DictionaryKey & key) { return cache.get(key); }
87 void set(const DictionaryKey & key, const CachedValuesPtr & mapped) { cache.set(key, mapped); }
88 CachedValuesPtr getOrSet(const DictionaryKey & key, const CachedValuesPtr & mapped)
89 {
90 return cache.getOrSet(key, [&]() { return mapped; }).first;
91 }
92
93private:
94 using Cache = LRUCache<DictionaryKey, CachedValues, DictionaryKeyHash>;
95 Cache cache;
96};
97
98
99void ExecutableFunctionAdaptor::createLowCardinalityResultCache(size_t cache_size)
100{
101 if (!low_cardinality_result_cache)
102 low_cardinality_result_cache = std::make_shared<ExecutableFunctionLowCardinalityResultCache>(cache_size);
103}
104
105
106ColumnPtr wrapInNullable(const ColumnPtr & src, const Block & block, const ColumnNumbers & args, size_t result, size_t input_rows_count)
107{
108 ColumnPtr result_null_map_column;
109
110 /// If result is already nullable.
111 ColumnPtr src_not_nullable = src;
112
113 if (src->onlyNull())
114 return src;
115 else if (auto * nullable = checkAndGetColumn<ColumnNullable>(*src))
116 {
117 src_not_nullable = nullable->getNestedColumnPtr();
118 result_null_map_column = nullable->getNullMapColumnPtr();
119 }
120
121 for (const auto & arg : args)
122 {
123 const ColumnWithTypeAndName & elem = block.getByPosition(arg);
124 if (!elem.type->isNullable())
125 continue;
126
127 /// Const Nullable that are NULL.
128 if (elem.column->onlyNull())
129 {
130 auto result_type = block.getByPosition(result).type;
131 assert(result_type->isNullable());
132 return result_type->createColumnConstWithDefaultValue(input_rows_count);
133 }
134
135 if (isColumnConst(*elem.column))
136 continue;
137
138 if (auto * nullable = checkAndGetColumn<ColumnNullable>(*elem.column))
139 {
140 const ColumnPtr & null_map_column = nullable->getNullMapColumnPtr();
141 if (!result_null_map_column)
142 {
143 result_null_map_column = null_map_column;
144 }
145 else
146 {
147 MutableColumnPtr mutable_result_null_map_column = (*std::move(result_null_map_column)).mutate();
148
149 NullMap & result_null_map = assert_cast<ColumnUInt8 &>(*mutable_result_null_map_column).getData();
150 const NullMap & src_null_map = assert_cast<const ColumnUInt8 &>(*null_map_column).getData();
151
152 for (size_t i = 0, size = result_null_map.size(); i < size; ++i)
153 if (src_null_map[i])
154 result_null_map[i] = 1;
155
156 result_null_map_column = std::move(mutable_result_null_map_column);
157 }
158 }
159 }
160
161 if (!result_null_map_column)
162 return makeNullable(src);
163
164 return ColumnNullable::create(src_not_nullable->convertToFullColumnIfConst(), result_null_map_column);
165}
166
167
168namespace
169{
170
171struct NullPresence
172{
173 bool has_nullable = false;
174 bool has_null_constant = false;
175};
176
177NullPresence getNullPresense(const Block & block, const ColumnNumbers & args)
178{
179 NullPresence res;
180
181 for (const auto & arg : args)
182 {
183 const auto & elem = block.getByPosition(arg);
184
185 if (!res.has_nullable)
186 res.has_nullable = elem.type->isNullable();
187 if (!res.has_null_constant)
188 res.has_null_constant = elem.type->onlyNull();
189 }
190
191 return res;
192}
193
194NullPresence getNullPresense(const ColumnsWithTypeAndName & args)
195{
196 NullPresence res;
197
198 for (const auto & elem : args)
199 {
200 if (!res.has_nullable)
201 res.has_nullable = elem.type->isNullable();
202 if (!res.has_null_constant)
203 res.has_null_constant = elem.type->onlyNull();
204 }
205
206 return res;
207}
208
209bool allArgumentsAreConstants(const Block & block, const ColumnNumbers & args)
210{
211 for (auto arg : args)
212 if (!isColumnConst(*block.getByPosition(arg).column))
213 return false;
214 return true;
215}
216}
217
218bool ExecutableFunctionAdaptor::defaultImplementationForConstantArguments(
219 Block & block, const ColumnNumbers & args, size_t result, size_t input_rows_count, bool dry_run)
220{
221 ColumnNumbers arguments_to_remain_constants = impl->getArgumentsThatAreAlwaysConstant();
222
223 /// Check that these arguments are really constant.
224 for (auto arg_num : arguments_to_remain_constants)
225 if (arg_num < args.size() && !isColumnConst(*block.getByPosition(args[arg_num]).column))
226 throw Exception("Argument at index " + toString(arg_num) + " for function " + getName() + " must be constant", ErrorCodes::ILLEGAL_COLUMN);
227
228 if (args.empty() || !impl->useDefaultImplementationForConstants() || !allArgumentsAreConstants(block, args))
229 return false;
230
231 Block temporary_block;
232 bool have_converted_columns = false;
233
234 size_t arguments_size = args.size();
235 for (size_t arg_num = 0; arg_num < arguments_size; ++arg_num)
236 {
237 const ColumnWithTypeAndName & column = block.getByPosition(args[arg_num]);
238
239 if (arguments_to_remain_constants.end() != std::find(arguments_to_remain_constants.begin(), arguments_to_remain_constants.end(), arg_num))
240 {
241 temporary_block.insert({column.column->cloneResized(1), column.type, column.name});
242 }
243 else
244 {
245 have_converted_columns = true;
246 temporary_block.insert({ assert_cast<const ColumnConst *>(column.column.get())->getDataColumnPtr(), column.type, column.name });
247 }
248 }
249
250 /** When using default implementation for constants, the function requires at least one argument
251 * not in "arguments_to_remain_constants" set. Otherwise we get infinite recursion.
252 */
253 if (!have_converted_columns)
254 throw Exception("Number of arguments for function " + getName() + " doesn't match: the function requires more arguments",
255 ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
256
257 temporary_block.insert(block.getByPosition(result));
258
259 ColumnNumbers temporary_argument_numbers(arguments_size);
260 for (size_t i = 0; i < arguments_size; ++i)
261 temporary_argument_numbers[i] = i;
262
263 executeWithoutLowCardinalityColumns(temporary_block, temporary_argument_numbers, arguments_size, temporary_block.rows(), dry_run);
264
265 ColumnPtr result_column;
266 /// extremely rare case, when we have function with completely const arguments
267 /// but some of them produced by non isDeterministic function
268 if (temporary_block.getByPosition(arguments_size).column->size() > 1)
269 result_column = temporary_block.getByPosition(arguments_size).column->cloneResized(1);
270 else
271 result_column = temporary_block.getByPosition(arguments_size).column;
272
273 block.getByPosition(result).column = ColumnConst::create(result_column, input_rows_count);
274 return true;
275}
276
277
278bool ExecutableFunctionAdaptor::defaultImplementationForNulls(
279 Block & block, const ColumnNumbers & args, size_t result, size_t input_rows_count, bool dry_run)
280{
281 if (args.empty() || !impl->useDefaultImplementationForNulls())
282 return false;
283
284 NullPresence null_presence = getNullPresense(block, args);
285
286 if (null_presence.has_null_constant)
287 {
288 auto & result_column = block.getByPosition(result).column;
289 auto result_type = block.getByPosition(result).type;
290 // Default implementation for nulls returns null result for null arguments,
291 // so the result type must be nullable.
292 assert(result_type->isNullable());
293
294 result_column = result_type->createColumnConstWithDefaultValue(input_rows_count);
295 return true;
296 }
297
298 if (null_presence.has_nullable)
299 {
300 Block temporary_block = createBlockWithNestedColumns(block, args, result);
301 executeWithoutLowCardinalityColumns(temporary_block, args, result, temporary_block.rows(), dry_run);
302 block.getByPosition(result).column = wrapInNullable(temporary_block.getByPosition(result).column, block, args,
303 result, input_rows_count);
304 return true;
305 }
306
307 return false;
308}
309
310void ExecutableFunctionAdaptor::executeWithoutLowCardinalityColumns(
311 Block & block, const ColumnNumbers & args, size_t result, size_t input_rows_count, bool dry_run)
312{
313 if (defaultImplementationForConstantArguments(block, args, result, input_rows_count, dry_run))
314 return;
315
316 if (defaultImplementationForNulls(block, args, result, input_rows_count, dry_run))
317 return;
318
319 if (dry_run)
320 impl->executeDryRun(block, args, result, input_rows_count);
321 else
322 impl->execute(block, args, result, input_rows_count);
323}
324
325static const ColumnLowCardinality * findLowCardinalityArgument(const Block & block, const ColumnNumbers & args)
326{
327 const ColumnLowCardinality * result_column = nullptr;
328
329 for (auto arg : args)
330 {
331 const ColumnWithTypeAndName & column = block.getByPosition(arg);
332 if (auto * low_cardinality_column = checkAndGetColumn<ColumnLowCardinality>(column.column.get()))
333 {
334 if (result_column)
335 throw Exception("Expected single dictionary argument for function.", ErrorCodes::LOGICAL_ERROR);
336
337 result_column = low_cardinality_column;
338 }
339 }
340
341 return result_column;
342}
343
344static ColumnPtr replaceLowCardinalityColumnsByNestedAndGetDictionaryIndexes(
345 Block & block, const ColumnNumbers & args, bool can_be_executed_on_default_arguments, size_t input_rows_count)
346{
347 size_t num_rows = input_rows_count;
348 ColumnPtr indexes;
349
350 /// Find first LowCardinality column and replace it to nested dictionary.
351 for (auto arg : args)
352 {
353 ColumnWithTypeAndName & column = block.getByPosition(arg);
354 if (auto * low_cardinality_column = checkAndGetColumn<ColumnLowCardinality>(column.column.get()))
355 {
356 /// Single LowCardinality column is supported now.
357 if (indexes)
358 throw Exception("Expected single dictionary argument for function.", ErrorCodes::LOGICAL_ERROR);
359
360 auto * low_cardinality_type = checkAndGetDataType<DataTypeLowCardinality>(column.type.get());
361
362 if (!low_cardinality_type)
363 throw Exception("Incompatible type for low cardinality column: " + column.type->getName(),
364 ErrorCodes::LOGICAL_ERROR);
365
366 if (can_be_executed_on_default_arguments)
367 {
368 /// Normal case, when function can be executed on values's default.
369 column.column = low_cardinality_column->getDictionary().getNestedColumn();
370 indexes = low_cardinality_column->getIndexesPtr();
371 }
372 else
373 {
374 /// Special case when default value can't be used. Example: 1 % LowCardinality(Int).
375 /// LowCardinality always contains default, so 1 % 0 will throw exception in normal case.
376 auto dict_encoded = low_cardinality_column->getMinimalDictionaryEncodedColumn(0, low_cardinality_column->size());
377 column.column = dict_encoded.dictionary;
378 indexes = dict_encoded.indexes;
379 }
380
381 num_rows = column.column->size();
382 column.type = low_cardinality_type->getDictionaryType();
383 }
384 }
385
386 /// Change size of constants.
387 for (auto arg : args)
388 {
389 ColumnWithTypeAndName & column = block.getByPosition(arg);
390 if (auto * column_const = checkAndGetColumn<ColumnConst>(column.column.get()))
391 {
392 column.column = column_const->removeLowCardinality()->cloneResized(num_rows);
393 column.type = removeLowCardinality(column.type);
394 }
395 }
396
397#ifndef NDEBUG
398 block.checkNumberOfRows(true);
399#endif
400
401 return indexes;
402}
403
404static void convertLowCardinalityColumnsToFull(Block & block, const ColumnNumbers & args)
405{
406 for (auto arg : args)
407 {
408 ColumnWithTypeAndName & column = block.getByPosition(arg);
409
410 column.column = recursiveRemoveLowCardinality(column.column);
411 column.type = recursiveRemoveLowCardinality(column.type);
412 }
413}
414
415void ExecutableFunctionAdaptor::execute(Block & block, const ColumnNumbers & args, size_t result, size_t input_rows_count, bool dry_run)
416{
417 if (impl->useDefaultImplementationForLowCardinalityColumns())
418 {
419 auto & res = block.safeGetByPosition(result);
420 Block block_without_low_cardinality = block.cloneWithoutColumns();
421
422 for (auto arg : args)
423 block_without_low_cardinality.safeGetByPosition(arg).column = block.safeGetByPosition(arg).column;
424
425 if (auto * res_low_cardinality_type = typeid_cast<const DataTypeLowCardinality *>(res.type.get()))
426 {
427 const auto * low_cardinality_column = findLowCardinalityArgument(block, args);
428 bool can_be_executed_on_default_arguments = impl->canBeExecutedOnDefaultArguments();
429 bool use_cache = low_cardinality_result_cache && can_be_executed_on_default_arguments
430 && low_cardinality_column && low_cardinality_column->isSharedDictionary();
431 ExecutableFunctionLowCardinalityResultCache::DictionaryKey key;
432
433 if (use_cache)
434 {
435 const auto & dictionary = low_cardinality_column->getDictionary();
436 key = {dictionary.getHash(), dictionary.size()};
437
438 auto cached_values = low_cardinality_result_cache->get(key);
439 if (cached_values)
440 {
441 auto indexes = cached_values->index_mapping->index(low_cardinality_column->getIndexes(), 0);
442 res.column = ColumnLowCardinality::create(cached_values->function_result, indexes, true);
443 return;
444 }
445 }
446
447 block_without_low_cardinality.safeGetByPosition(result).type = res_low_cardinality_type->getDictionaryType();
448 ColumnPtr indexes = replaceLowCardinalityColumnsByNestedAndGetDictionaryIndexes(
449 block_without_low_cardinality, args, can_be_executed_on_default_arguments, input_rows_count);
450
451 executeWithoutLowCardinalityColumns(block_without_low_cardinality, args, result, block_without_low_cardinality.rows(), dry_run);
452
453 auto keys = block_without_low_cardinality.safeGetByPosition(result).column->convertToFullColumnIfConst();
454
455 auto res_mut_dictionary = DataTypeLowCardinality::createColumnUnique(*res_low_cardinality_type->getDictionaryType());
456 ColumnPtr res_indexes = res_mut_dictionary->uniqueInsertRangeFrom(*keys, 0, keys->size());
457 ColumnUniquePtr res_dictionary = std::move(res_mut_dictionary);
458
459 if (indexes)
460 {
461 if (use_cache)
462 {
463 auto cache_values = std::make_shared<ExecutableFunctionLowCardinalityResultCache::CachedValues>();
464 cache_values->dictionary_holder = low_cardinality_column->getDictionaryPtr();
465 cache_values->function_result = res_dictionary;
466 cache_values->index_mapping = res_indexes;
467
468 cache_values = low_cardinality_result_cache->getOrSet(key, cache_values);
469 res_dictionary = cache_values->function_result;
470 res_indexes = cache_values->index_mapping;
471 }
472
473 res.column = ColumnLowCardinality::create(res_dictionary, res_indexes->index(*indexes, 0), use_cache);
474 }
475 else
476 {
477 res.column = ColumnLowCardinality::create(res_dictionary, res_indexes);
478 }
479 }
480 else
481 {
482 convertLowCardinalityColumnsToFull(block_without_low_cardinality, args);
483 executeWithoutLowCardinalityColumns(block_without_low_cardinality, args, result, input_rows_count, dry_run);
484 res.column = block_without_low_cardinality.safeGetByPosition(result).column;
485 }
486 }
487 else
488 executeWithoutLowCardinalityColumns(block, args, result, input_rows_count, dry_run);
489}
490
491void FunctionOverloadResolverAdaptor::checkNumberOfArguments(size_t number_of_arguments) const
492{
493 if (isVariadic())
494 return;
495
496 size_t expected_number_of_arguments = getNumberOfArguments();
497
498 if (number_of_arguments != expected_number_of_arguments)
499 throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
500 + toString(number_of_arguments) + ", should be " + toString(expected_number_of_arguments),
501 ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
502}
503
504DataTypePtr FunctionOverloadResolverAdaptor::getReturnTypeWithoutLowCardinality(const ColumnsWithTypeAndName & arguments) const
505{
506 checkNumberOfArguments(arguments.size());
507
508 if (!arguments.empty() && impl->useDefaultImplementationForNulls())
509 {
510 NullPresence null_presence = getNullPresense(arguments);
511
512 if (null_presence.has_null_constant)
513 {
514 return makeNullable(std::make_shared<DataTypeNothing>());
515 }
516 if (null_presence.has_nullable)
517 {
518 Block nested_block = createBlockWithNestedColumns(Block(arguments), ext::collection_cast<ColumnNumbers>(ext::range(0, arguments.size())));
519 auto return_type = impl->getReturnType(ColumnsWithTypeAndName(nested_block.begin(), nested_block.end()));
520 return makeNullable(return_type);
521
522 }
523 }
524
525 return impl->getReturnType(arguments);
526}
527
528#if USE_EMBEDDED_COMPILER
529
530static std::optional<DataTypes> removeNullables(const DataTypes & types)
531{
532 for (const auto & type : types)
533 {
534 if (!typeid_cast<const DataTypeNullable *>(type.get()))
535 continue;
536 DataTypes filtered;
537 for (const auto & sub_type : types)
538 filtered.emplace_back(removeNullable(sub_type));
539 return filtered;
540 }
541 return {};
542}
543
544bool IFunction::isCompilable(const DataTypes & arguments) const
545{
546 if (useDefaultImplementationForNulls())
547 if (auto denulled = removeNullables(arguments))
548 return isCompilableImpl(*denulled);
549 return isCompilableImpl(arguments);
550}
551
552llvm::Value * IFunction::compile(llvm::IRBuilderBase & builder, const DataTypes & arguments, ValuePlaceholders values) const
553{
554 if (useDefaultImplementationForNulls())
555 {
556 if (auto denulled = removeNullables(arguments))
557 {
558 /// FIXME: when only one column is nullable, this can actually be slower than the non-jitted version
559 /// because this involves copying the null map while `wrapInNullable` reuses it.
560 auto & b = static_cast<llvm::IRBuilder<> &>(builder);
561 auto * fail = llvm::BasicBlock::Create(b.GetInsertBlock()->getContext(), "", b.GetInsertBlock()->getParent());
562 auto * join = llvm::BasicBlock::Create(b.GetInsertBlock()->getContext(), "", b.GetInsertBlock()->getParent());
563 auto * zero = llvm::Constant::getNullValue(toNativeType(b, makeNullable(getReturnTypeImpl(*denulled))));
564 for (size_t i = 0; i < arguments.size(); i++)
565 {
566 if (!arguments[i]->isNullable())
567 continue;
568 /// Would be nice to evaluate all this lazily, but that'd change semantics: if only unevaluated
569 /// arguments happen to contain NULLs, the return value would not be NULL, though it should be.
570 auto * value = values[i]();
571 auto * ok = llvm::BasicBlock::Create(b.GetInsertBlock()->getContext(), "", b.GetInsertBlock()->getParent());
572 b.CreateCondBr(b.CreateExtractValue(value, {1}), fail, ok);
573 b.SetInsertPoint(ok);
574 values[i] = [value = b.CreateExtractValue(value, {0})]() { return value; };
575 }
576 auto * result = b.CreateInsertValue(zero, compileImpl(builder, *denulled, std::move(values)), {0});
577 auto * result_block = b.GetInsertBlock();
578 b.CreateBr(join);
579 b.SetInsertPoint(fail);
580 auto * null = b.CreateInsertValue(zero, b.getTrue(), {1});
581 b.CreateBr(join);
582 b.SetInsertPoint(join);
583 auto * phi = b.CreatePHI(result->getType(), 2);
584 phi->addIncoming(result, result_block);
585 phi->addIncoming(null, fail);
586 return phi;
587 }
588 }
589 return compileImpl(builder, arguments, std::move(values));
590}
591
592#endif
593
594DataTypePtr FunctionOverloadResolverAdaptor::getReturnType(const ColumnsWithTypeAndName & arguments) const
595{
596 if (impl->useDefaultImplementationForLowCardinalityColumns())
597 {
598 bool has_low_cardinality = false;
599 size_t num_full_low_cardinality_columns = 0;
600 size_t num_full_ordinary_columns = 0;
601
602 ColumnsWithTypeAndName args_without_low_cardinality(arguments);
603
604 for (ColumnWithTypeAndName & arg : args_without_low_cardinality)
605 {
606 bool is_const = arg.column && isColumnConst(*arg.column);
607 if (is_const)
608 arg.column = assert_cast<const ColumnConst &>(*arg.column).removeLowCardinality();
609
610 if (auto * low_cardinality_type = typeid_cast<const DataTypeLowCardinality *>(arg.type.get()))
611 {
612 arg.type = low_cardinality_type->getDictionaryType();
613 has_low_cardinality = true;
614
615 if (!is_const)
616 ++num_full_low_cardinality_columns;
617 }
618 else if (!is_const)
619 ++num_full_ordinary_columns;
620 }
621
622 for (auto & arg : args_without_low_cardinality)
623 {
624 arg.column = recursiveRemoveLowCardinality(arg.column);
625 arg.type = recursiveRemoveLowCardinality(arg.type);
626 }
627
628 auto type_without_low_cardinality = getReturnTypeWithoutLowCardinality(args_without_low_cardinality);
629
630 if (impl->canBeExecutedOnLowCardinalityDictionary() && has_low_cardinality
631 && num_full_low_cardinality_columns <= 1 && num_full_ordinary_columns == 0
632 && type_without_low_cardinality->canBeInsideLowCardinality())
633 return std::make_shared<DataTypeLowCardinality>(type_without_low_cardinality);
634 else
635 return type_without_low_cardinality;
636 }
637
638 return getReturnTypeWithoutLowCardinality(arguments);
639}
640}
641