#include #include #include #include #include #include #include #include #include #if USE_MULTITARGET_CODE && defined(__BMI2__) #include #endif namespace DB { namespace ErrorCodes { extern const int ILLEGAL_TYPE_OF_ARGUMENT; extern const int ILLEGAL_COLUMN; extern const int ARGUMENT_OUT_OF_BOUND; } #define EXTRACT_VECTOR(INDEX) \ auto col##INDEX = ColumnUInt64::create(); \ auto & vec##INDEX = col##INDEX->getData(); \ vec##INDEX.resize(input_rows_count); #define DECODE(ND, ...) \ if (nd == (ND)) \ { \ for (size_t i = 0; i < input_rows_count; i++) \ { \ auto res = MortonND_##ND##D_Dec.Decode(col_code->getUInt(i)); \ __VA_ARGS__ \ } \ } #define MASK(IDX, ...) \ ((mask) ? shrink(mask->getColumn((IDX)).getUInt(0), std::get(__VA_ARGS__)) : std::get(__VA_ARGS__)) #define EXECUTE() \ size_t nd; \ const auto * col_const = typeid_cast(arguments[0].column.get()); \ const auto * mask = typeid_cast(col_const->getDataColumnPtr().get()); \ if (mask) \ nd = mask->tupleSize(); \ else \ nd = col_const->getUInt(0); \ auto non_const_arguments = arguments; \ non_const_arguments[1].column = non_const_arguments[1].column->convertToFullColumnIfConst(); \ const ColumnPtr & col_code = non_const_arguments[1].column; \ Columns tuple_columns(nd); \ EXTRACT_VECTOR(0) \ if (nd == 1) \ { \ if (mask) \ { \ for (size_t i = 0; i < input_rows_count; i++) \ { \ vec0[i] = shrink(mask->getColumn(0).getUInt(0), col_code->getUInt(i)); \ } \ tuple_columns[0] = std::move(col0); \ } \ else \ { \ for (size_t i = 0; i < input_rows_count; i++) \ { \ vec0[i] = col_code->getUInt(i); \ } \ tuple_columns[0] = std::move(col0); \ } \ return ColumnTuple::create(tuple_columns); \ } \ EXTRACT_VECTOR(1) \ DECODE(2, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res);) \ EXTRACT_VECTOR(2) \ DECODE(3, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res);) \ EXTRACT_VECTOR(3) \ DECODE(4, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res); \ vec3[i] = MASK(3, res);) \ EXTRACT_VECTOR(4) \ DECODE(5, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res); \ vec3[i] = MASK(3, res); \ vec4[i] = MASK(4, res);) \ EXTRACT_VECTOR(5) \ DECODE(6, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res); \ vec3[i] = MASK(3, res); \ vec4[i] = MASK(4, res); \ vec5[i] = MASK(5, res);) \ EXTRACT_VECTOR(6) \ DECODE(7, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res); \ vec3[i] = MASK(3, res); \ vec4[i] = MASK(4, res); \ vec5[i] = MASK(5, res); \ vec6[i] = MASK(6, res);) \ EXTRACT_VECTOR(7) \ DECODE(8, \ vec0[i] = MASK(0, res); \ vec1[i] = MASK(1, res); \ vec2[i] = MASK(2, res); \ vec3[i] = MASK(3, res); \ vec4[i] = MASK(4, res); \ vec5[i] = MASK(5, res); \ vec6[i] = MASK(6, res); \ vec7[i] = MASK(7, res);) \ switch (nd) \ { \ case 2: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ break; \ case 3: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ return ColumnTuple::create(tuple_columns); \ case 4: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ tuple_columns[3] = std::move(col3); \ return ColumnTuple::create(tuple_columns); \ case 5: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ tuple_columns[3] = std::move(col3); \ tuple_columns[4] = std::move(col4); \ return ColumnTuple::create(tuple_columns); \ case 6: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ tuple_columns[3] = std::move(col3); \ tuple_columns[4] = std::move(col4); \ tuple_columns[5] = std::move(col5); \ return ColumnTuple::create(tuple_columns); \ case 7: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ tuple_columns[3] = std::move(col3); \ tuple_columns[4] = std::move(col4); \ tuple_columns[5] = std::move(col5); \ tuple_columns[6] = std::move(col6); \ return ColumnTuple::create(tuple_columns); \ case 8: \ tuple_columns[0] = std::move(col0); \ tuple_columns[1] = std::move(col1); \ tuple_columns[2] = std::move(col2); \ tuple_columns[3] = std::move(col3); \ tuple_columns[4] = std::move(col4); \ tuple_columns[5] = std::move(col5); \ tuple_columns[6] = std::move(col6); \ tuple_columns[7] = std::move(col7); \ return ColumnTuple::create(tuple_columns); \ } \ return ColumnTuple::create(tuple_columns); DECLARE_DEFAULT_CODE( constexpr auto MortonND_2D_Dec = mortonnd::MortonNDLutDecoder<2, 32, 8>(); constexpr auto MortonND_3D_Dec = mortonnd::MortonNDLutDecoder<3, 21, 8>(); constexpr auto MortonND_4D_Dec = mortonnd::MortonNDLutDecoder<4, 16, 8>(); constexpr auto MortonND_5D_Dec = mortonnd::MortonNDLutDecoder<5, 12, 8>(); constexpr auto MortonND_6D_Dec = mortonnd::MortonNDLutDecoder<6, 10, 8>(); constexpr auto MortonND_7D_Dec = mortonnd::MortonNDLutDecoder<7, 9, 8>(); constexpr auto MortonND_8D_Dec = mortonnd::MortonNDLutDecoder<8, 8, 8>(); class FunctionMortonDecode : public IFunction { public: static constexpr auto name = "mortonDecode"; static FunctionPtr create(ContextPtr) { return std::make_shared(); } String getName() const override { return name; } size_t getNumberOfArguments() const override { return 2; } bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; } DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { UInt64 tuple_size = 0; const auto * col_const = typeid_cast(arguments[0].column.get()); if (!col_const) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column type {} of function {}, should be a constant (UInt or Tuple)", arguments[0].type->getName(), getName()); if (!WhichDataType(arguments[1].type).isNativeUInt()) throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column type {} of function {}, should be a native UInt", arguments[1].type->getName(), getName()); const auto * mask = typeid_cast(col_const->getDataColumnPtr().get()); if (mask) { tuple_size = mask->tupleSize(); } else if (WhichDataType(arguments[0].type).isNativeUInt()) { tuple_size = col_const->getUInt(0); } else throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Illegal column type {} of function {}, should be UInt or Tuple", arguments[0].type->getName(), getName()); if (tuple_size > 8 || tuple_size < 1) throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND, "Illegal first argument for function {}, should be a number in range 1-8 or a Tuple of such size", getName()); if (mask) { const auto * type_tuple = typeid_cast(arguments[0].type.get()); for (size_t i = 0; i < tuple_size; i++) { if (!WhichDataType(type_tuple->getElement(i)).isNativeUInt()) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of argument in tuple for function {}, should be a native UInt", type_tuple->getElement(i)->getName(), getName()); auto ratio = mask->getColumn(i).getUInt(0); if (ratio > 8 || ratio < 1) throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND, "Illegal argument {} in tuple for function {}, should be a number in range 1-8", ratio, getName()); } } DataTypes types(tuple_size); for (size_t i = 0; i < tuple_size; i++) { types[i] = std::make_shared(); } return std::make_shared(types); } static UInt64 shrink(UInt64 ratio, UInt64 value) { switch (ratio) { case 1: return value; case 2: return std::get<1>(MortonND_2D_Dec.Decode(value)); case 3: return std::get<2>(MortonND_3D_Dec.Decode(value)); case 4: return std::get<3>(MortonND_4D_Dec.Decode(value)); case 5: return std::get<4>(MortonND_5D_Dec.Decode(value)); case 6: return std::get<5>(MortonND_6D_Dec.Decode(value)); case 7: return std::get<6>(MortonND_7D_Dec.Decode(value)); case 8: return std::get<7>(MortonND_8D_Dec.Decode(value)); } return value; } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override { EXECUTE() } }; ) // DECLARE_DEFAULT_CODE #if defined(MORTON_ND_BMI2_ENABLED) #undef DECODE #define DECODE(ND, ...) \ if (nd == (ND)) \ { \ for (size_t i = 0; i < input_rows_count; i++) \ { \ auto res = MortonND_##ND##D::Decode(col_code->getUInt(i)); \ __VA_ARGS__ \ } \ } DECLARE_AVX2_SPECIFIC_CODE( using MortonND_2D = mortonnd::MortonNDBmi<2, uint64_t>; using MortonND_3D = mortonnd::MortonNDBmi<3, uint64_t>; using MortonND_4D = mortonnd::MortonNDBmi<4, uint64_t>; using MortonND_5D = mortonnd::MortonNDBmi<5, uint64_t>; using MortonND_6D = mortonnd::MortonNDBmi<6, uint64_t>; using MortonND_7D = mortonnd::MortonNDBmi<7, uint64_t>; using MortonND_8D = mortonnd::MortonNDBmi<8, uint64_t>; class FunctionMortonDecode: public TargetSpecific::Default::FunctionMortonDecode { static UInt64 shrink(UInt64 ratio, UInt64 value) { switch (ratio) { case 1: return value; case 2: return std::get<1>(MortonND_2D::Decode(value)); case 3: return std::get<2>(MortonND_3D::Decode(value)); case 4: return std::get<3>(MortonND_4D::Decode(value)); case 5: return std::get<4>(MortonND_5D::Decode(value)); case 6: return std::get<5>(MortonND_6D::Decode(value)); case 7: return std::get<6>(MortonND_7D::Decode(value)); case 8: return std::get<7>(MortonND_8D::Decode(value)); } return value; } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override { EXECUTE() } }; ) #endif // MORTON_ND_BMI2_ENABLED #undef DECODE #undef MASK #undef EXTRACT_VECTOR #undef EXECUTE class FunctionMortonDecode: public TargetSpecific::Default::FunctionMortonDecode { public: explicit FunctionMortonDecode(ContextPtr context) : selector(context) { selector.registerImplementation(); #if USE_MULTITARGET_CODE && defined(MORTON_ND_BMI2_ENABLED) selector.registerImplementation(); #endif } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override { return selector.selectAndExecute(arguments, result_type, input_rows_count); } static FunctionPtr create(ContextPtr context) { return std::make_shared(context); } private: ImplementationSelector selector; }; REGISTER_FUNCTION(MortonDecode) { factory.registerFunction({ R"( Decodes a Morton encoding (ZCurve) into the corresponding unsigned integer tuple The function has two modes of operation: - Simple - Expanded Simple: accepts a resulting tuple size as a first argument and the code as a second argument. [example:simple] Will decode into: `(1,2,3,4)` The resulting tuple size cannot be more than 8 Expanded: accepts a range mask (tuple) as a first argument and the code as a second argument. Each number in mask configures the amount of range shrink 1 - no shrink 2 - 2x shrink 3 - 3x shrink .... Up to 8x shrink. [example:range_shrank] Note: see mortonEncode() docs on why range change might be beneficial. Still limited to 8 numbers at most. Morton code for one argument is always the argument itself (as a tuple). [example:identity] Produces: `(1)` You can shrink one argument too: [example:identity_shrank] Produces: `(128)` The function accepts a column of codes as a second argument: [example:from_table] The range tuple must be a constant: [example:from_table_range] )", Documentation::Examples{ {"simple", "SELECT mortonDecode(4, 2149)"}, {"range_shrank", "SELECT mortonDecode((1,2), 1572864)"}, {"identity", "SELECT mortonDecode(1, 1)"}, {"identity_shrank", "SELECT mortonDecode(tuple(2), 32768)"}, {"from_table", "SELECT mortonDecode(2, code) FROM table"}, {"from_table_range", "SELECT mortonDecode((1,2), code) FROM table"}, }, Documentation::Categories {"ZCurve", "Morton coding"} }); } }