mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-16 03:12:43 +00:00
465 lines
19 KiB
C++
465 lines
19 KiB
C++
#include <Columns/ColumnArray.h>
|
|
#include <Columns/ColumnFunction.h>
|
|
#include <Columns/ColumnMap.h>
|
|
#include <Columns/ColumnTuple.h>
|
|
#include <Columns/ColumnConst.h>
|
|
|
|
#include <DataTypes/DataTypeArray.h>
|
|
#include <DataTypes/DataTypeFunction.h>
|
|
#include <DataTypes/DataTypeMap.h>
|
|
#include <DataTypes/DataTypeTuple.h>
|
|
|
|
#include <Functions/FunctionHelpers.h>
|
|
#include <Functions/like.h>
|
|
#include <Functions/array/arrayConcat.h>
|
|
#include <Functions/array/arrayFilter.h>
|
|
#include <Functions/array/arrayMap.h>
|
|
#include <Functions/array/arraySort.h>
|
|
#include <Functions/array/arrayIndex.h>
|
|
#include <Functions/array/arrayExists.h>
|
|
#include <Functions/array/arrayAll.h>
|
|
#include <Functions/identity.h>
|
|
#include <Functions/FunctionFactory.h>
|
|
|
|
#include <base/map.h>
|
|
|
|
namespace DB
|
|
{
|
|
|
|
namespace ErrorCodes
|
|
{
|
|
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
|
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
|
}
|
|
|
|
/** An adapter that allows to execute array* functions over Map types arguments.
|
|
* E.g. transform mapConcat to arrayConcat.
|
|
*
|
|
* Impl - the implementation of function that is applied
|
|
* to internal column of Map arguments (e.g. 'arrayConcat').
|
|
*
|
|
* Adapter - a struct that determines the way how to extract the internal array columns
|
|
* from Map arguments and possibly modify other columns.
|
|
*/
|
|
template <typename Impl, typename Adapter, typename Name>
|
|
class FunctionMapToArrayAdapter : public IFunction
|
|
{
|
|
public:
|
|
static constexpr auto name = Name::name;
|
|
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionMapToArrayAdapter>(); }
|
|
String getName() const override { return name; }
|
|
|
|
bool isVariadic() const override { return impl.isVariadic(); }
|
|
size_t getNumberOfArguments() const override { return impl.getNumberOfArguments(); }
|
|
bool useDefaultImplementationForConstants() const override { return impl.useDefaultImplementationForConstants(); }
|
|
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo &) const override { return false; }
|
|
|
|
void getLambdaArgumentTypes(DataTypes & arguments) const override
|
|
{
|
|
Adapter::extractNestedTypes(arguments);
|
|
impl.getLambdaArgumentTypes(arguments);
|
|
}
|
|
|
|
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
|
{
|
|
if (arguments.empty())
|
|
throw Exception(
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
|
|
"Function {} requires at least one argument, passed {}", getName(), arguments.size());
|
|
|
|
auto nested_arguments = arguments;
|
|
Adapter::extractNestedTypesAndColumns(nested_arguments);
|
|
|
|
constexpr bool impl_has_get_return_type = requires
|
|
{
|
|
impl.getReturnTypeImpl(nested_arguments);
|
|
};
|
|
|
|
/// If method is not overloaded in the implementation call default implementation
|
|
/// from IFunction. Here inheritance cannot be used for template parameterized field.
|
|
if constexpr (impl_has_get_return_type)
|
|
return Adapter::wrapType(impl.getReturnTypeImpl(nested_arguments));
|
|
else
|
|
return Adapter::wrapType(dynamic_cast<const IFunction &>(impl).getReturnTypeImpl(nested_arguments));
|
|
}
|
|
|
|
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override
|
|
{
|
|
auto nested_arguments = arguments;
|
|
Adapter::extractNestedTypesAndColumns(nested_arguments);
|
|
return Adapter::wrapColumn(impl.executeImpl(nested_arguments, Adapter::extractResultType(result_type), input_rows_count));
|
|
}
|
|
|
|
private:
|
|
Impl impl;
|
|
};
|
|
|
|
|
|
template <typename Derived, typename Name>
|
|
struct MapAdapterBase
|
|
{
|
|
static void extractNestedTypes(DataTypes & types)
|
|
{
|
|
bool has_map_column = false;
|
|
for (auto & type : types)
|
|
{
|
|
if (const auto * type_map = typeid_cast<const DataTypeMap *>(type.get()))
|
|
{
|
|
has_map_column = true;
|
|
type = Derived::extractNestedType(*type_map);
|
|
}
|
|
}
|
|
|
|
if (!has_map_column)
|
|
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
|
|
"Function {} requires at least one argument of type Map", Name::name);
|
|
}
|
|
|
|
static void extractNestedTypesAndColumns(ColumnsWithTypeAndName & arguments)
|
|
{
|
|
bool has_map_column = false;
|
|
for (auto & argument : arguments)
|
|
{
|
|
if (const auto * type_map = typeid_cast<const DataTypeMap *>(argument.type.get()))
|
|
{
|
|
has_map_column = true;
|
|
argument.type = Derived::extractNestedType(*type_map);
|
|
|
|
if (argument.column)
|
|
{
|
|
if (const auto * const_map = checkAndGetColumnConstData<ColumnMap>(argument.column.get()))
|
|
argument.column = ColumnConst::create(Derived::extractNestedColumn(*const_map), argument.column->size());
|
|
else
|
|
argument.column = Derived::extractNestedColumn(assert_cast<const ColumnMap &>(*argument.column));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!has_map_column)
|
|
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
|
|
"Function {} requires at least one argument of type Map", Name::name);
|
|
}
|
|
};
|
|
|
|
/// Adapter that extracts nested Array(Tuple(key, value)) from Map columns.
|
|
template <typename Name, bool returns_map = true>
|
|
struct MapToNestedAdapter : public MapAdapterBase<MapToNestedAdapter<Name, returns_map>, Name>
|
|
{
|
|
using MapAdapterBase<MapToNestedAdapter, Name>::extractNestedTypes;
|
|
using MapAdapterBase<MapToNestedAdapter, Name>::extractNestedTypesAndColumns;
|
|
|
|
static DataTypePtr extractNestedType(const DataTypeMap & type_map)
|
|
{
|
|
return type_map.getNestedTypeWithUnnamedTuple();
|
|
}
|
|
|
|
static ColumnPtr extractNestedColumn(const ColumnMap & column_map)
|
|
{
|
|
return column_map.getNestedColumnPtr();
|
|
}
|
|
|
|
static DataTypePtr extractResultType(const DataTypePtr & result_type)
|
|
{
|
|
if constexpr (returns_map)
|
|
return assert_cast<const DataTypeMap &>(*result_type).getNestedType();
|
|
return result_type;
|
|
}
|
|
|
|
static DataTypePtr wrapType(DataTypePtr type)
|
|
{
|
|
if constexpr (returns_map)
|
|
return std::make_shared<DataTypeMap>(std::move(type));
|
|
return type;
|
|
}
|
|
|
|
static ColumnPtr wrapColumn(ColumnPtr column)
|
|
{
|
|
if constexpr (returns_map)
|
|
return ColumnMap::create(std::move(column));
|
|
return column;
|
|
}
|
|
};
|
|
|
|
/// Adapter that extracts array with keys or values from Map columns.
|
|
template <typename Name, size_t position>
|
|
struct MapToSubcolumnAdapter : public MapAdapterBase<MapToSubcolumnAdapter<Name, position>, Name>
|
|
{
|
|
static_assert(position <= 1);
|
|
using MapAdapterBase<MapToSubcolumnAdapter, Name>::extractNestedTypes;
|
|
using MapAdapterBase<MapToSubcolumnAdapter, Name>::extractNestedTypesAndColumns;
|
|
|
|
static DataTypePtr extractNestedType(const DataTypeMap & type_map)
|
|
{
|
|
const auto & array_type = assert_cast<const DataTypeArray &>(*type_map.getNestedType());
|
|
const auto & tuple_type = assert_cast<const DataTypeTuple &>(*array_type.getNestedType());
|
|
return std::make_shared<DataTypeArray>(tuple_type.getElement(position));
|
|
}
|
|
|
|
static ColumnPtr extractNestedColumn(const ColumnMap & column_map)
|
|
{
|
|
const auto & array_column = column_map.getNestedColumn();
|
|
const auto & tuple_column = column_map.getNestedData();
|
|
return ColumnArray::create(tuple_column.getColumnPtr(position), array_column.getOffsetsPtr());
|
|
}
|
|
|
|
static DataTypePtr extractResultType(const DataTypePtr & result_type) { return result_type; }
|
|
static DataTypePtr wrapType(DataTypePtr type) { return type; }
|
|
static ColumnPtr wrapColumn(ColumnPtr column) { return column; }
|
|
};
|
|
|
|
/// A special function that works like the following:
|
|
/// mapKeyLike(pattern, key, value) <=> key LIKE pattern
|
|
/// It is used to mimic lambda: (key, value) -> key LIKE pattern.
|
|
class FunctionMapKeyLike : public IFunction
|
|
{
|
|
public:
|
|
String getName() const override { return "mapKeyLike"; }
|
|
size_t getNumberOfArguments() const override { return 3; }
|
|
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
|
|
bool useDefaultImplementationForNulls() const override { return false; }
|
|
|
|
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
|
|
{
|
|
DataTypes new_arguments{arguments[1], arguments[0]};
|
|
return impl.getReturnTypeImpl(new_arguments);
|
|
}
|
|
|
|
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override
|
|
{
|
|
ColumnsWithTypeAndName new_arguments{arguments[1], arguments[0]};
|
|
return impl.executeImpl(new_arguments, result_type, input_rows_count);
|
|
}
|
|
|
|
private:
|
|
FunctionLike impl;
|
|
};
|
|
|
|
/// Adapter for map*KeyLike functions.
|
|
/// It extracts nested Array(Tuple(key, value)) from Map columns
|
|
/// and prepares ColumnFunction as first argument which works
|
|
/// like lambda (k, v) -> k LIKE pattern to pass it to the nested
|
|
/// function derived from FunctionArrayMapped.
|
|
template <typename Name, bool returns_map>
|
|
struct MapKeyLikeAdapter
|
|
{
|
|
static void checkTypes(const DataTypes & types)
|
|
{
|
|
if (types.size() != 2)
|
|
throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
|
|
"Number of arguments for function {} doesn't match: passed {}, should be 2",
|
|
Name::name, types.size());
|
|
|
|
const auto * map_type = checkAndGetDataType<DataTypeMap>(types[0].get());
|
|
if (!map_type)
|
|
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "First argument for function {} must be a Map", Name::name);
|
|
|
|
if (!isStringOrFixedString(types[1]))
|
|
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Second argument for function {} must be String or FixedString", Name::name);
|
|
|
|
if (!isStringOrFixedString(map_type->getKeyType()))
|
|
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Key type of map for function {} must be String or FixedString", Name::name);
|
|
}
|
|
|
|
static void extractNestedTypes(DataTypes & types)
|
|
{
|
|
checkTypes(types);
|
|
const auto & map_type = assert_cast<const DataTypeMap &>(*types[0]);
|
|
|
|
DataTypes lambda_argument_types{types[1], map_type.getKeyType(), map_type.getValueType()};
|
|
auto result_type = FunctionMapKeyLike().getReturnTypeImpl(lambda_argument_types);
|
|
|
|
DataTypes argument_types{map_type.getKeyType(), map_type.getValueType()};
|
|
auto function_type = std::make_shared<DataTypeFunction>(argument_types, result_type);
|
|
|
|
types = {function_type, types[0]};
|
|
MapToNestedAdapter<Name, returns_map>::extractNestedTypes(types);
|
|
}
|
|
|
|
static void extractNestedTypesAndColumns(ColumnsWithTypeAndName & arguments)
|
|
{
|
|
checkTypes(collections::map<DataTypes>(arguments, [](const auto & elem) { return elem.type; }));
|
|
|
|
const auto & map_type = assert_cast<const DataTypeMap &>(*arguments[0].type);
|
|
const auto & pattern_arg = arguments[1];
|
|
|
|
ColumnPtr function_column;
|
|
auto function = std::make_shared<FunctionMapKeyLike>();
|
|
|
|
DataTypes lambda_argument_types{pattern_arg.type, map_type.getKeyType(), map_type.getValueType()};
|
|
auto result_type = function->getReturnTypeImpl(lambda_argument_types);
|
|
|
|
DataTypes argument_types{map_type.getKeyType(), map_type.getValueType()};
|
|
auto function_type = std::make_shared<DataTypeFunction>(argument_types, result_type);
|
|
|
|
if (pattern_arg.column)
|
|
{
|
|
/// Here we create ColumnFunction with already captured pattern column.
|
|
/// Nested function will append keys and values column and it will work as desired lambda.
|
|
auto function_base = std::make_shared<FunctionToFunctionBaseAdaptor>(function, lambda_argument_types, result_type);
|
|
function_column = ColumnFunction::create(pattern_arg.column->size(), std::move(function_base), ColumnsWithTypeAndName{pattern_arg});
|
|
}
|
|
|
|
ColumnWithTypeAndName function_arg{function_column, function_type, "__function_map_key_like"};
|
|
arguments = {function_arg, arguments[0]};
|
|
MapToNestedAdapter<Name, returns_map>::extractNestedTypesAndColumns(arguments);
|
|
}
|
|
|
|
static DataTypePtr extractResultType(const DataTypePtr & result_type)
|
|
{
|
|
return MapToNestedAdapter<Name, returns_map>::extractResultType(result_type);
|
|
}
|
|
|
|
static DataTypePtr wrapType(DataTypePtr type)
|
|
{
|
|
return MapToNestedAdapter<Name, returns_map>::wrapType(std::move(type));
|
|
}
|
|
|
|
static ColumnPtr wrapColumn(ColumnPtr column)
|
|
{
|
|
return MapToNestedAdapter<Name, returns_map>::wrapColumn(std::move(column));
|
|
}
|
|
};
|
|
|
|
struct NameMapConcat { static constexpr auto name = "mapConcat"; };
|
|
using FunctionMapConcat = FunctionMapToArrayAdapter<FunctionArrayConcat, MapToNestedAdapter<NameMapConcat>, NameMapConcat>;
|
|
|
|
struct NameMapKeys { static constexpr auto name = "mapKeys"; };
|
|
using FunctionMapKeys = FunctionMapToArrayAdapter<FunctionIdentity, MapToSubcolumnAdapter<NameMapKeys, 0>, NameMapKeys>;
|
|
|
|
struct NameMapValues { static constexpr auto name = "mapValues"; };
|
|
using FunctionMapValues = FunctionMapToArrayAdapter<FunctionIdentity, MapToSubcolumnAdapter<NameMapValues, 1>, NameMapValues>;
|
|
|
|
struct NameMapContains { static constexpr auto name = "mapContains"; };
|
|
using FunctionMapContains = FunctionMapToArrayAdapter<FunctionArrayIndex<HasAction, NameMapContains>, MapToSubcolumnAdapter<NameMapKeys, 0>, NameMapContains>;
|
|
|
|
struct NameMapFilter { static constexpr auto name = "mapFilter"; };
|
|
using FunctionMapFilter = FunctionMapToArrayAdapter<FunctionArrayFilter, MapToNestedAdapter<NameMapFilter>, NameMapFilter>;
|
|
|
|
struct NameMapApply { static constexpr auto name = "mapApply"; };
|
|
using FunctionMapApply = FunctionMapToArrayAdapter<FunctionArrayMap, MapToNestedAdapter<NameMapApply>, NameMapApply>;
|
|
|
|
struct NameMapExists { static constexpr auto name = "mapExists"; };
|
|
using FunctionMapExists = FunctionMapToArrayAdapter<FunctionArrayExists, MapToNestedAdapter<NameMapExists, false>, NameMapExists>;
|
|
|
|
struct NameMapAll { static constexpr auto name = "mapAll"; };
|
|
using FunctionMapAll = FunctionMapToArrayAdapter<FunctionArrayAll, MapToNestedAdapter<NameMapAll, false>, NameMapAll>;
|
|
|
|
struct NameMapContainsKeyLike { static constexpr auto name = "mapContainsKeyLike"; };
|
|
using FunctionMapContainsKeyLike = FunctionMapToArrayAdapter<FunctionArrayExists, MapKeyLikeAdapter<NameMapContainsKeyLike, false>, NameMapContainsKeyLike>;
|
|
|
|
struct NameMapExtractKeyLike { static constexpr auto name = "mapExtractKeyLike"; };
|
|
using FunctionMapExtractKeyLike = FunctionMapToArrayAdapter<FunctionArrayFilter, MapKeyLikeAdapter<NameMapExtractKeyLike, true>, NameMapExtractKeyLike>;
|
|
|
|
struct NameMapSort { static constexpr auto name = "mapSort"; };
|
|
struct NameMapReverseSort { static constexpr auto name = "mapReverseSort"; };
|
|
struct NameMapPartialSort { static constexpr auto name = "mapPartialSort"; };
|
|
struct NameMapPartialReverseSort { static constexpr auto name = "mapPartialReverseSort"; };
|
|
|
|
using FunctionMapSort = FunctionMapToArrayAdapter<FunctionArraySort, MapToNestedAdapter<NameMapSort>, NameMapSort>;
|
|
using FunctionMapReverseSort = FunctionMapToArrayAdapter<FunctionArrayReverseSort, MapToNestedAdapter<NameMapReverseSort>, NameMapReverseSort>;
|
|
using FunctionMapPartialSort = FunctionMapToArrayAdapter<FunctionArrayPartialSort, MapToNestedAdapter<NameMapPartialSort>, NameMapPartialSort>;
|
|
using FunctionMapPartialReverseSort = FunctionMapToArrayAdapter<FunctionArrayPartialReverseSort, MapToNestedAdapter<NameMapPartialReverseSort>, NameMapPartialReverseSort>;
|
|
|
|
REGISTER_FUNCTION(MapMiscellaneous)
|
|
{
|
|
factory.registerFunction<FunctionMapConcat>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayConcat.",
|
|
.examples{{"mapConcat", "SELECT mapConcat(map('k1', 'v1'), map('k2', 'v2'))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapKeys>(
|
|
FunctionDocumentation{
|
|
.description="Returns an array with the keys of map.",
|
|
.examples{{"mapKeys", "SELECT mapKeys(map('k1', 'v1', 'k2', 'v2'))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapValues>(
|
|
FunctionDocumentation{
|
|
.description="Returns an array with the values of map.",
|
|
.examples{{"mapValues", "SELECT mapValues(map('k1', 'v1', 'k2', 'v2'))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapContains>(
|
|
FunctionDocumentation{
|
|
.description="Checks whether the map has the specified key.",
|
|
.examples{{"mapContains", "SELECT mapContains(map('k1', 'v1', 'k2', 'v2'), 'k1')", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapFilter>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayFilter.",
|
|
.examples{{"mapFilter", "SELECT mapFilter((k, v) -> v > 1, map('k1', 1, 'k2', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapApply>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayMap.",
|
|
.examples{{"mapApply", "SELECT mapApply((k, v) -> (k, v * 2), map('k1', 1, 'k2', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapExists>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayExists.",
|
|
.examples{{"mapExists", "SELECT mapExists((k, v) -> v = 1, map('k1', 1, 'k2', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapAll>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayAll.",
|
|
.examples{{"mapAll", "SELECT mapAll((k, v) -> v = 1, map('k1', 1, 'k2', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapSort>(
|
|
FunctionDocumentation{
|
|
.description="The same as arraySort.",
|
|
.examples{{"mapSort", "SELECT mapSort((k, v) -> v, map('k1', 3, 'k2', 1, 'k3', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapReverseSort>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayReverseSort.",
|
|
.examples{{"mapReverseSort", "SELECT mapReverseSort((k, v) -> v, map('k1', 3, 'k2', 1, 'k3', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapPartialSort>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayReverseSort.",
|
|
.examples{{"mapPartialSort", "SELECT mapPartialSort((k, v) -> v, 2, map('k1', 3, 'k2', 1, 'k3', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapPartialReverseSort>(
|
|
FunctionDocumentation{
|
|
.description="The same as arrayPartialReverseSort.",
|
|
.examples{{"mapPartialReverseSort", "SELECT mapPartialReverseSort((k, v) -> v, 2, map('k1', 3, 'k2', 1, 'k3', 2))", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapContainsKeyLike>(
|
|
FunctionDocumentation{
|
|
.description="Checks whether map contains key LIKE specified pattern.",
|
|
.examples{{"mapContainsKeyLike", "SELECT mapContainsKeyLike(map('k1-1', 1, 'k2-1', 2), 'k1%')", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
|
|
factory.registerFunction<FunctionMapExtractKeyLike>(
|
|
FunctionDocumentation{
|
|
.description="Returns a map with elements which key matches the specified pattern.",
|
|
.examples{{"mapExtractKeyLike", "SELECT mapExtractKeyLike(map('k1-1', 1, 'k2-1', 2), 'k1%')", ""}},
|
|
.categories{"Map"},
|
|
});
|
|
}
|
|
|
|
}
|