This is an automated email from the ASF dual-hosted git repository.
zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 14f7cd2247e [feature](function) Support murmur_hash3_128 function
(#63196)
14f7cd2247e is described below
commit 14f7cd2247e1023888dd1e51f2d5a426e6c221cf
Author: seawinde <[email protected]>
AuthorDate: Tue Jun 2 10:19:36 2026 +0800
[feature](function) Support murmur_hash3_128 function (#63196)
This PR adds a builtin 128-bit MurmurHash3 scalar function,
`murmur_hash3_128`, for callers that need a wider hash value than the
existing 32-bit and 64-bit variants.
| File | Change Description |
|------|--------------------|
| `be/src/exprs/function/function_hash.cpp` | Adds BE implementation for
`murmur_hash3_128`, returning LARGEINT. |
| `be/test/exprs/function/function_hash_test.cpp` | Adds unit coverage
for constant and multi-argument hash cases. |
| `BuiltinScalarFunctions.java` | Registers the Nereids scalar function.
|
| `MurmurHash3128.java` | Adds FE scalar function metadata and
signatures. |
| `ScalarFunctionVisitor.java` | Adds visitor entry for the new scalar
function. |
Design rationale: the function reuses the existing MurmurHash3 x64
128-bit processing path in BE and exposes the packed 128-bit result as
LARGEINT, matching Doris' existing signed 128-bit integer
representation.
BE execution logic:
`murmur_hash3_128` follows the same variadic hash execution model as the
existing Doris hash functions. The BE function framework does not
evaluate all arguments for one row in a single call. Instead,
`FunctionVariadicArgumentsBase` invokes the implementation once per
argument column:
```text
first_apply(arg0, result_column)
combine_apply(arg1, result_column)
combine_apply(arg2, result_column)
...
```
For a query such as:
```sql
SELECT murmur_hash3_128(k1, 'world') FROM t;
```
the first round processes the whole `k1` column with `execute<true>()`.
It creates one LARGEINT result slot per row and initializes each row's
MurmurHash3 128-bit state from seed `0`. The second round processes the
constant argument `'world'` with `execute<false>()`. It reads each row's
previous state from the result column, updates that state with the new
argument bytes, and writes the packed state back.
For example, if `k1` has two rows:
```text
row0: "hello"
row1: "apache"
```
the state evolves as:
```text
Initial result column:
row0: empty
row1: empty
Round 1: execute<true>() for k1
row0: init_hash("hello") -> state_hello
row1: init_hash("apache") -> state_apache
Result column:
row0: pack(state_hello)
row1: pack(state_apache)
Round 2: execute<false>() for 'world'
row0: unpack(state_hello) -> update_hash("world") -> state_hello_world
row1: unpack(state_apache) -> update_hash("world") -> state_apache_world
Final result column:
row0: pack(state_hello_world)
row1: pack(state_apache_world)
```
The underlying MurmurHash3 128-bit state is the pair `(h1, h2)`. For a
single argument, the implementation can call the existing
`murmur_hash3_x64_128(data, len, 0, out)` directly. For multiple
arguments, each later argument must continue from the previous `(h1,
h2)` state; calling `murmur_hash3_x64_128` independently for every
argument would restart from seed `0` and lose the effect of earlier
arguments.
Because the SQL return type is LARGEINT and the BE result column stores
one `__int128_t` value per row, the implementation packs `(h1, h2)` into
the result column between argument rounds and unpacks it before
processing the next argument:
```text
high 64 bits low 64 bits
+-------------------------+-------------------------+
| h2 | h1 |
+-------------------------+-------------------------+
```
---
be/src/exprs/function/function_hash.cpp | 158 ++++++++++++++++++++-
be/src/util/hash/murmur_hash3.cpp | 17 ++-
be/src/util/hash/murmur_hash3.h | 7 +-
be/test/exprs/function/function_hash_test.cpp | 137 ++++++++++++++++++
.../doris/catalog/BuiltinScalarFunctions.java | 4 +
.../functions/scalar/MurmurHash3128.java | 82 +++++++++++
.../functions/scalar/MurmurHash3U128.java | 81 +++++++++++
.../expressions/visitor/ScalarFunctionVisitor.java | 10 ++
.../hash_functions/test_hash_function.out | 123 +++++++++++++++-
.../hash_functions/test_hash_function.groovy | 65 ++++++++-
10 files changed, 671 insertions(+), 13 deletions(-)
diff --git a/be/src/exprs/function/function_hash.cpp
b/be/src/exprs/function/function_hash.cpp
index 7e3072a91e9..74eb06e297d 100644
--- a/be/src/exprs/function/function_hash.cpp
+++ b/be/src/exprs/function/function_hash.cpp
@@ -20,6 +20,8 @@
#include "exprs/function/function_hash.h"
+#include <vector>
+
#include "common/status.h"
#include "core/assert_cast.h"
#include "core/column/column.h"
@@ -29,7 +31,9 @@
#include "core/column/column_vector.h"
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
#include "core/field.h"
+#include "core/value/large_int_value.h"
#include "exec/common/template_helpers.hpp"
#include "exprs/function/function_helpers.h"
#include "exprs/function/function_variadic_arguments.h"
@@ -40,6 +44,78 @@
namespace doris {
constexpr uint64_t emtpy_value = 0xe28dbde7fe22e41c;
+namespace {
+
+__int128_t pack_murmur_hash3_128(uint64_t h1, uint64_t h2) {
+ static_assert(sizeof(__int128_t) == sizeof(uint64_t) * 2);
+ // Store the two MurmurHash3 x64 128-bit lanes in a single LARGEINT value.
Keep h1 in the
+ // low 64 bits and h2 in the high 64 bits to match murmur_hash3_x64_128's
out[0]/out[1].
+ const auto value =
+ (static_cast<unsigned __int128>(h2) << 64) | static_cast<unsigned
__int128>(h1);
+ return static_cast<__int128_t>(value);
+}
+
+void unpack_murmur_hash3_128(__int128_t value, uint64_t& h1, uint64_t& h2) {
+ static_assert(sizeof(__int128_t) == sizeof(uint64_t) * 2);
+ const auto unsigned_value = static_cast<unsigned __int128>(value);
+ h1 = static_cast<uint64_t>(unsigned_value);
+ h2 = static_cast<uint64_t>(unsigned_value >> 64);
+}
+
+void init_murmur_hash3_128(__int128_t& value, const void* data, size_t size) {
+ uint64_t hash[2] = {0, 0};
+ murmur_hash3_x64_128(data, size, 0, hash);
+ value = pack_murmur_hash3_128(hash[0], hash[1]);
+}
+
+void update_murmur_hash3_128(__int128_t& value, const void* data, size_t size)
{
+ uint64_t h1 = 0;
+ uint64_t h2 = 0;
+ unpack_murmur_hash3_128(value, h1, h2);
+ murmur_hash3_x64_process(data, size, h1, h2);
+ value = pack_murmur_hash3_128(h1, h2);
+}
+
+template <bool first, typename StateContainer>
+Status execute_murmur_hash3_128_column(const IColumn* column, size_t
input_rows_count,
+ StateContainer& state, const char*
function_name) {
+ if (const auto* col_from = check_and_get_column<ColumnString>(column)) {
+ const typename ColumnString::Chars& data = col_from->get_chars();
+ const typename ColumnString::Offsets& offsets =
col_from->get_offsets();
+ size_t size = offsets.size();
+ ColumnString::Offset current_offset = 0;
+ for (size_t i = 0; i < size; ++i) {
+ if constexpr (first) {
+ init_murmur_hash3_128(state[i],
+ reinterpret_cast<const
char*>(&data[current_offset]),
+ offsets[i] - current_offset);
+ } else {
+ update_murmur_hash3_128(state[i],
+ reinterpret_cast<const
char*>(&data[current_offset]),
+ offsets[i] - current_offset);
+ }
+ current_offset = offsets[i];
+ }
+ } else if (const ColumnConst* col_from_const =
+
check_and_get_column_const_string_or_fixedstring(column)) {
+ auto value = col_from_const->get_value<TYPE_STRING>();
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if constexpr (first) {
+ init_murmur_hash3_128(state[i], value.data(), value.size());
+ } else {
+ update_murmur_hash3_128(state[i], value.data(), value.size());
+ }
+ }
+ } else {
+ DCHECK(false);
+ return Status::NotSupported("Illegal column {} of argument of function
{}",
+ column->get_name(), function_name);
+ }
+ return Status::OK();
+}
+
+} // namespace
+
template <PrimitiveType ReturnType, bool is_mmh64_v2 = false>
struct MurmurHash3Impl {
static constexpr auto get_name() {
@@ -133,6 +209,84 @@ using FunctionMurmurHash3_64_V2 =
using FunctionMurmurHash3U64V2 =
FunctionVariadicArgumentsBase<DataTypeInt128,
MurmurHash3Impl<TYPE_LARGEINT, true>>;
+struct MurmurHash3128Impl {
+ static constexpr auto name = "murmur_hash3_128";
+
+ static Status empty_apply(IColumn& /*icolumn*/, size_t
/*input_rows_count*/) {
+ return Status::InvalidArgument("Function {} requires at least one
argument", name);
+ }
+
+ static Status first_apply(const IDataType* type, const IColumn* column,
size_t input_rows_count,
+ IColumn& icolumn) {
+ return execute<true>(type, column, input_rows_count, icolumn);
+ }
+
+ static Status combine_apply(const IDataType* type, const IColumn* column,
+ size_t input_rows_count, IColumn& icolumn) {
+ return execute<false>(type, column, input_rows_count, icolumn);
+ }
+
+ template <bool first>
+ static Status execute(const IDataType* type, const IColumn* column, size_t
input_rows_count,
+ IColumn& col_to) {
+ auto& to_column = assert_cast<ColumnVector<TYPE_LARGEINT>&>(col_to);
+ if constexpr (first) {
+ // The first argument initializes one 128-bit hash state per row.
Later arguments reuse
+ // the same result column and update the saved state in place.
+ to_column.insert_many_defaults(input_rows_count);
+ }
+ auto& col_to_data = to_column.get_data();
+ return execute_murmur_hash3_128_column<first>(column,
input_rows_count, col_to_data, name);
+ }
+};
+
+using FunctionMurmurHash3_128 = FunctionVariadicArgumentsBase<DataTypeInt128,
MurmurHash3128Impl>;
+
+class FunctionMurmurHash3U128 : public IFunction {
+public:
+ static constexpr auto name = "murmur_hash3_u128";
+
+ static FunctionPtr create() { return
std::make_shared<FunctionMurmurHash3U128>(); }
+
+ String get_name() const override { return name; }
+
+ bool is_variadic() const override { return true; }
+
+ size_t get_number_of_arguments() const override { return 0; }
+
+ DataTypePtr get_return_type_impl(const ColumnsWithTypeAndName&
/*arguments*/) const override {
+ return std::make_shared<DataTypeString>();
+ }
+
+ Status execute_impl(FunctionContext* /*context*/, Block& block, const
ColumnNumbers& arguments,
+ uint32_t result, size_t input_rows_count) const
override {
+ if (arguments.empty()) {
+ return Status::InvalidArgument("Function {} requires at least one
argument", name);
+ }
+
+ std::vector<__int128_t> state(input_rows_count);
+ const ColumnWithTypeAndName& first_col =
block.get_by_position(arguments[0]);
+
RETURN_IF_ERROR(execute_murmur_hash3_128_column<true>(first_col.column.get(),
+
input_rows_count, state, name));
+
+ for (size_t i = 1; i < arguments.size(); ++i) {
+ const ColumnWithTypeAndName& col =
block.get_by_position(arguments[i]);
+
RETURN_IF_ERROR(execute_murmur_hash3_128_column<false>(col.column.get(),
+
input_rows_count, state, name));
+ }
+
+ auto result_column = ColumnString::create();
+ result_column->reserve(input_rows_count);
+ for (const auto value : state) {
+ auto unsigned_value = static_cast<__uint128_t>(value);
+ std::string value_str = LargeIntValue::to_string(unsigned_value);
+ result_column->insert_data(value_str.data(), value_str.size());
+ }
+ block.get_by_position(result).column = std::move(result_column);
+ return Status::OK();
+ }
+};
+
#ifdef BE_TEST
const char* murmur_hash3_get_name_type_int_for_test() {
return MurmurHash3Impl<TYPE_INT>::get_name();
@@ -234,8 +388,10 @@ void register_function_hash(SimpleFunctionFactory&
factory) {
factory.register_function<FunctionMurmurHash3_64>();
factory.register_function<FunctionMurmurHash3_64_V2>();
factory.register_function<FunctionMurmurHash3U64V2>();
+ factory.register_function<FunctionMurmurHash3_128>();
+ factory.register_function<FunctionMurmurHash3U128>();
factory.register_function<FunctionXxHash_32>();
factory.register_function<FunctionXxHash_64>();
factory.register_alias("xxhash_64", "xxhash3_64");
}
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git a/be/src/util/hash/murmur_hash3.cpp
b/be/src/util/hash/murmur_hash3.cpp
index 7cd22dd59ec..d53bbc821b3 100644
--- a/be/src/util/hash/murmur_hash3.cpp
+++ b/be/src/util/hash/murmur_hash3.cpp
@@ -30,6 +30,8 @@
#include <stdlib.h>
#endif
+#include <glog/logging.h>
+
#include "util/unaligned.h"
namespace doris {
@@ -72,7 +74,7 @@ FORCE_INLINE uint32_t getblock32(const uint32_t* p, int i) {
return unaligned_load<uint32_t>(&p[i]);
}
-FORCE_INLINE uint64_t getblock64(const uint64_t* p, int i) {
+FORCE_INLINE uint64_t getblock64(const uint64_t* p, size_t i) {
return unaligned_load<uint64_t>(&p[i]);
}
@@ -334,9 +336,9 @@ void murmur_hash3_x86_128(const void* key, const int len,
uint32_t seed, void* o
//-----------------------------------------------------------------------------
// Helper function that implements the core MurmurHash3 128-bit hashing
algorithm
-void murmur_hash3_x64_process(const void* key, const int len, uint64_t& h1,
uint64_t& h2) {
+void murmur_hash3_x64_process(const void* key, const size_t len, uint64_t& h1,
uint64_t& h2) {
const uint8_t* data = (const uint8_t*)key;
- const int nblocks = len / 16;
+ const size_t nblocks = len / 16;
const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
@@ -346,7 +348,7 @@ void murmur_hash3_x64_process(const void* key, const int
len, uint64_t& h1, uint
const uint64_t* blocks = (const uint64_t*)(data);
- for (int i = 0; i < nblocks; i++) {
+ for (size_t i = 0; i < nblocks; i++) {
uint64_t k1 = getblock64(blocks, i * 2 + 0);
uint64_t k2 = getblock64(blocks, i * 2 + 1);
@@ -453,7 +455,7 @@ void murmur_hash3_x64_process(const void* key, const int
len, uint64_t& h1, uint
// The origin function `murmur_hash3_x64_128` is copied from:
https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
// And Doris modified it into function `murmur_hash3_x64_process`
// For this reason, this function is still retained even though it has no
calls.
-void murmur_hash3_x64_128(const void* key, const int len, const uint32_t seed,
void* out) {
+void murmur_hash3_x64_128(const void* key, const size_t len, const uint32_t
seed, void* out) {
uint64_t h1 = seed;
uint64_t h2 = seed;
murmur_hash3_x64_process(key, len, h1, h2);
@@ -468,9 +470,10 @@ void murmur_hash3_x64_128(const void* key, const int len,
const uint32_t seed, v
// Used for function mmh3_64_v2
void murmur_hash3_x64_64_shared(const void* key, const int64_t len, const
uint64_t seed,
void* out) {
+ DCHECK_GE(len, 0);
uint64_t h1 = seed;
uint64_t h2 = seed;
- murmur_hash3_x64_process(key, static_cast<int>(len), h1, h2);
+ murmur_hash3_x64_process(key, static_cast<size_t>(len), h1, h2);
((uint64_t*)out)[0] = h1;
}
@@ -546,4 +549,4 @@ void murmur_hash3_x64_64(const void* key, const int64_t
len, const uint64_t seed
((uint64_t*)out)[0] = h1;
}
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git a/be/src/util/hash/murmur_hash3.h b/be/src/util/hash/murmur_hash3.h
index f72f08336c7..bcb34a3ad98 100644
--- a/be/src/util/hash/murmur_hash3.h
+++ b/be/src/util/hash/murmur_hash3.h
@@ -20,6 +20,7 @@
#pragma once
+#include <cstddef>
#include <cstdint>
namespace doris {
@@ -27,12 +28,12 @@ void murmur_hash3_x86_32(const void* key, int64_t len,
uint32_t seed, void* out)
void murmur_hash3_x86_128(const void* key, int len, uint32_t seed, void* out);
-void murmur_hash3_x64_process(const void* key, const int len, uint64_t& h1,
uint64_t& h2);
+void murmur_hash3_x64_process(const void* key, size_t len, uint64_t& h1,
uint64_t& h2);
-void murmur_hash3_x64_128(const void* key, int len, uint32_t seed, void* out);
+void murmur_hash3_x64_128(const void* key, size_t len, uint32_t seed, void*
out);
void murmur_hash3_x64_64_shared(const void* key, const int64_t len, const
uint64_t seed, void* out);
void murmur_hash3_x64_64(const void* key, int64_t len, uint64_t seed, void*
out);
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git a/be/test/exprs/function/function_hash_test.cpp
b/be/test/exprs/function/function_hash_test.cpp
index ad4b47f5c5b..2d3f2ad1cc0 100644
--- a/be/test/exprs/function/function_hash_test.cpp
+++ b/be/test/exprs/function/function_hash_test.cpp
@@ -25,7 +25,9 @@
#include "common/status.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
#include "core/types.h"
+#include "core/value/large_int_value.h"
#include "exprs/function/function_test_util.h"
#include "gtest/gtest_pred_impl.h"
#include "testutil/any_type.h"
@@ -34,6 +36,30 @@
namespace doris {
using namespace ut_type;
+namespace {
+
+__int128_t pack_murmur_hash3_128_for_test(uint64_t h1, uint64_t h2) {
+ static_assert(sizeof(__int128_t) == sizeof(uint64_t) * 2);
+ const auto value =
+ (static_cast<unsigned __int128>(h2) << 64) | static_cast<unsigned
__int128>(h1);
+ return static_cast<__int128_t>(value);
+}
+
+__int128_t murmur_hash3_128_for_test(const std::vector<std::string>& values) {
+ uint64_t h1 = 0;
+ uint64_t h2 = 0;
+ for (const std::string& value : values) {
+ murmur_hash3_x64_process(value.data(), value.size(), h1, h2);
+ }
+ return pack_murmur_hash3_128_for_test(h1, h2);
+}
+
+std::string murmur_hash3_u128_for_test(const std::vector<std::string>& values)
{
+ return
LargeIntValue::to_string(static_cast<__uint128_t>(murmur_hash3_128_for_test(values)));
+}
+
+} // namespace
+
TEST(HashFunctionTest, murmur_hash_3_test) {
std::string func_name = "murmur_hash3_32";
@@ -114,6 +140,117 @@ TEST(HashFunctionTest, murmur_hash_3_64_v2_test) {
};
}
+TEST(HashFunctionTest, murmur_hash_3_128_test) {
+ std::string func_name = "murmur_hash3_128";
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {
+ {{Null()}, Null()},
+ {{std::string("hello world")},
+ pack_murmur_hash3_128_for_test(5998619086395760910ULL,
12364428806279881649ULL)}};
+
+ static_cast<void>(check_function<DataTypeInt128, true>(func_name,
input_types, data_set));
+ check_function_all_arg_comb<DataTypeInt128, true>(func_name,
input_types, data_set);
+ };
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {{{std::string("hello"), std::string("world")},
+ murmur_hash3_128_for_test({"hello", "world"})},
+ {{std::string("hello"), Null()}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt128, true>(func_name,
input_types, data_set));
+ check_function_all_arg_comb<DataTypeInt128, true>(func_name,
input_types, data_set);
+ };
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR,
+ PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {{{std::string("hello"), std::string("world"),
std::string("!")},
+ murmur_hash3_128_for_test({"hello", "world",
"!"})},
+ {{std::string("hello"), std::string("world"),
Null()}, Null()}};
+
+ check_function_all_arg_comb<DataTypeInt128, true>(func_name,
input_types, data_set);
+ };
+}
+
+TEST(HashFunctionTest, murmur_hash_3_128_empty_arguments_test) {
+ auto check_empty_arguments = [](const std::string& func_name, const
DataTypePtr& return_type) {
+ Block block;
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(),
return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ const auto st = func->execute(fn_ctx, block, {}, 0, 1);
+ EXPECT_FALSE(st.ok());
+ EXPECT_NE(st.to_string().find("requires at least one argument"),
std::string::npos);
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ };
+
+ check_empty_arguments("murmur_hash3_128",
std::make_shared<DataTypeInt128>());
+ check_empty_arguments("murmur_hash3_u128",
std::make_shared<DataTypeString>());
+}
+
+TEST(HashFunctionTest, murmur_hash_3_u128_test) {
+ std::string func_name = "murmur_hash3_u128";
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {
+ {{Null()}, Null()},
+ {{std::string("")}, std::string("0")},
+ {{std::string("hello world")},
murmur_hash3_u128_for_test({"hello world"})}};
+
+ static_cast<void>(check_function<DataTypeString, true>(func_name,
input_types, data_set));
+ check_function_all_arg_comb<DataTypeString, true>(func_name,
input_types, data_set);
+ };
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {{{std::string("hello"), std::string("world")},
+ murmur_hash3_u128_for_test({"hello", "world"})},
+ {{std::string("hello"), Null()}, Null()}};
+
+ static_cast<void>(check_function<DataTypeString, true>(func_name,
input_types, data_set));
+ check_function_all_arg_comb<DataTypeString, true>(func_name,
input_types, data_set);
+ };
+
+ {
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR,
+ PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {{{std::string("hello"), std::string("world"),
std::string("!")},
+ murmur_hash3_u128_for_test({"hello", "world",
"!"})},
+ {{std::string("hello"), std::string("world"),
Null()}, Null()}};
+
+ check_function_all_arg_comb<DataTypeString, true>(func_name,
input_types, data_set);
+ };
+}
+
+TEST(HashFunctionTest, murmur_hash_3_u128_unsigned_range_test) {
+ std::string func_name = "murmur_hash3_u128";
+
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+ DataSet data_set = {
+ {{std::string("hello world")},
std::string("228083453807047072434243676435732455694")}};
+
+ static_cast<void>(check_function<DataTypeString, true>(func_name,
input_types, data_set));
+}
+
TEST(HashFunctionTest, murmur_hash_get_name_test) {
EXPECT_STREQ(murmur_hash3_get_name_type_int_for_test(), "murmur_hash3_32");
EXPECT_STREQ(murmur_hash3_get_name_type_bigint_for_test(),
"murmur_hash3_64");
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
index 0883b93e5a8..4478bc9bcfe 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
@@ -376,9 +376,11 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatch;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatchAny;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiSearchAllPositions;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3128;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash332;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364V2;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3U128;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3U64V2;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Negative;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NextDay;
@@ -953,9 +955,11 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(MultiMatch.class, "multi_match"),
scalar(MultiMatchAny.class, "multi_match_any"),
scalar(MultiSearchAllPositions.class,
"multi_search_all_positions"),
+ scalar(MurmurHash3128.class, "murmur_hash3_128"),
scalar(MurmurHash332.class, "murmur_hash3_32"),
scalar(MurmurHash364.class, "murmur_hash3_64"),
scalar(MurmurHash364V2.class, "murmur_hash3_64_v2"),
+ scalar(MurmurHash3U128.class, "murmur_hash3_u128"),
scalar(MurmurHash3U64V2.class, "murmur_hash3_u64_v2"),
scalar(Negative.class, "negative"),
scalar(NextDay.class, "next_day"),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3128.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3128.java
new file mode 100644
index 00000000000..edc6be080fe
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3128.java
@@ -0,0 +1,82 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.LargeIntType;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.VarcharType;
+import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * ScalarFunction 'murmur_hash3_128'.
+ */
+public class MurmurHash3128 extends ScalarFunction
+ implements ExplicitlyCastableSignature, PropagateNullable {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(LargeIntType.INSTANCE).varArgs(VarcharType.SYSTEM_DEFAULT),
+
FunctionSignature.ret(LargeIntType.INSTANCE).varArgs(StringType.INSTANCE)
+ );
+
+ /**
+ * constructor with 1 or more arguments.
+ */
+ public MurmurHash3128(Expression arg, Expression... varArgs) {
+ this(ExpressionUtils.mergeArguments(arg, varArgs));
+ }
+
+ /** constructor with list arguments. */
+ public MurmurHash3128(List<Expression> args) {
+ super("murmur_hash3_128", Utils.fastToImmutableList(args));
+ }
+
+ /** constructor for withChildren and reuse signature */
+ private MurmurHash3128(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public MurmurHash3128 withChildren(List<Expression> children) {
+ Preconditions.checkArgument(!children.isEmpty());
+ return new MurmurHash3128(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitMurmurHash3128(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3U128.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3U128.java
new file mode 100644
index 00000000000..34e8ad67e53
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MurmurHash3U128.java
@@ -0,0 +1,81 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.VarcharType;
+import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.Utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * ScalarFunction 'murmur_hash3_u128'.
+ */
+public class MurmurHash3U128 extends ScalarFunction
+ implements ExplicitlyCastableSignature, PropagateNullable {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(StringType.INSTANCE).varArgs(VarcharType.SYSTEM_DEFAULT),
+
FunctionSignature.ret(StringType.INSTANCE).varArgs(StringType.INSTANCE)
+ );
+
+ /**
+ * constructor with 1 or more arguments.
+ */
+ public MurmurHash3U128(Expression arg, Expression... varArgs) {
+ this(ExpressionUtils.mergeArguments(arg, varArgs));
+ }
+
+ /** constructor with list arguments. */
+ public MurmurHash3U128(List<Expression> args) {
+ super("murmur_hash3_u128", Utils.fastToImmutableList(args));
+ }
+
+ /** constructor for withChildren and reuse signature */
+ private MurmurHash3U128(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public MurmurHash3U128 withChildren(List<Expression> children) {
+ Preconditions.checkArgument(!children.isEmpty());
+ return new MurmurHash3U128(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitMurmurHash3U128(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
index ce9deb2776d..1f82a48f1d7 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
@@ -397,9 +397,11 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatch;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatchAny;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiSearchAllPositions;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3128;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash332;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364V2;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3U128;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash3U64V2;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Negative;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NextDay;
@@ -2012,6 +2014,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(function, context);
}
+ default R visitMurmurHash3128(MurmurHash3128 murmurHash3128, C context) {
+ return visitScalarFunction(murmurHash3128, context);
+ }
+
default R visitMurmurHash332(MurmurHash332 murmurHash332, C context) {
return visitScalarFunction(murmurHash332, context);
}
@@ -2024,6 +2030,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(murmurHash364V2, context);
}
+ default R visitMurmurHash3U128(MurmurHash3U128 murmurHash3U128, C context)
{
+ return visitScalarFunction(murmurHash3U128, context);
+ }
+
default R visitMurmurHash3U64V2(MurmurHash3U64V2 murmurHash3U64V2, C
context) {
return visitScalarFunction(murmurHash3U64V2, context);
}
diff --git
a/regression-test/data/query_p0/sql_functions/hash_functions/test_hash_function.out
b/regression-test/data/query_p0/sql_functions/hash_functions/test_hash_function.out
index 764d533fcc7..b98d9b506fa 100644
---
a/regression-test/data/query_p0/sql_functions/hash_functions/test_hash_function.out
+++
b/regression-test/data/query_p0/sql_functions/hash_functions/test_hash_function.out
@@ -87,6 +87,50 @@
8 5163374697039953916
9 16930717985386452140
+-- !sql_mmh3_128_table --
+1 160552765667853844864347215091851402511
+2 -112198913113891391029130930996035755762
+3 \N
+4 0
+5 125233622341202073067337261280912046255
+6 15723305950287370021067100420381546638
+7 76022033372587150664028094316560832338
+8 8282804273666544992604160676428939260
+9 54500626245739954189896806014374040748
+
+-- !sql_mmh3_u128_table --
+1 160552765667853844864347215091851402511
+2 228083453807047072434243676435732455694
+3 \N
+4 0
+5 125233622341202073067337261280912046255
+6 15723305950287370021067100420381546638
+7 76022033372587150664028094316560832338
+8 8282804273666544992604160676428939260
+9 54500626245739954189896806014374040748
+
+-- !sql_mmh3_128_multi_arg_table --
+1 1446959605745449161743580155912574278
-56339891497867408245721289945506420485 -168178611131198900113957651047418886169
+2 -149549192126671924717567585844879967555
-60587747024077554617701559639572348746 65424866033221268138215169303830582651
+3 \N \N \N
+4 -78565033930154308766756204499853146902
84714210717646297788662261898201230080 0
+5 -39544922153624419472698581057092341686
164681627131843042222834911849678645237 -461669328540671960194073097226353499
+6 3855286205383178813738041956665736806
114705208091273276245241207548307136670 126143460685998379802738880954496866607
+7 -66251693712752822782446614605307054187
145660169740749413061118106551153383205 167010716176867357320321384372081403147
+8 -53522276451386554290637598501892217568
-49434810126805792985863702972638694950 -162781926366258959505488386157422138285
+9 46144780234418243372325741019404497451
109924268220979943366442352659211725694 -68242619748682641450662548496031489232
+
+-- !sql_mmh3_u128_multi_arg_table --
+1 1446959605745449161743580155912574278
283942475423071055217653317486261790971 172103755789739563349416956384349325287
+2 190733174794266538745807021586888243901
279694619896860908845673047792195862710 65424866033221268138215169303830582651
+3 \N \N \N
+4 261717332990784154696618402931915064554
84714210717646297788662261898201230080 0
+5 300737444767314043990676026374675869770
164681627131843042222834911849678645237 339820697592397791503180534334541857957
+6 3855286205383178813738041956665736806
114705208091273276245241207548307136670 126143460685998379802738880954496866607
+7 274030673208185640680927992826461157269
145660169740749413061118106551153383205 167010716176867357320321384372081403147
+8 286760090469551909172737008929875993888
290847556794132670477510904459129516506 177500440554679503957886221274346073171
+9 46144780234418243372325741019404497451
109924268220979943366442352659211725694 272039747172255822012712058935736722224
+
-- !mmh3_64_v2_fold_1 --
-6017608668500074082
@@ -99,6 +143,84 @@
-- !mmh3_u64_v2_fold_2 --
14339120766958605182
+-- !sql_mmh3_128_null --
+\N
+
+-- !sql_mmh3_128_empty --
+0
+
+-- !sql_mmh3_128_hello --
+121118445609844952839898260755277781762
+
+-- !sql_mmh3_128_hello_world --
+-112198913113891391029130930996035755762
+
+-- !sql_mmh3_128_apache_doris --
+125233622341202073067337261280912046255
+
+-- !sql_mmh3_128_two_args --
+-17367660094379006912106945534038101931
+
+-- !sql_mmh3_128_three_args --
+9994430460069927257443176797242139063
+
+-- !sql_mmh3_128_null_second --
+\N
+
+-- !sql_mmh3_128_null_first --
+\N
+
+-- !sql_mmh3_128_all_null --
+\N
+
+-- !sql_mmh3_128_unicode_zh --
+8282804273666544992604160676428939260
+
+-- !sql_mmh3_128_unicode_ja --
+54500626245739954189896806014374040748
+
+-- !sql_mmh3_128_other_types --
+88386560642490731012209186590589091690 112229221047929881211004856197893237700
6504046186406760443866950209489696482
+
+-- !sql_mmh3_u128_null --
+\N
+
+-- !sql_mmh3_u128_empty --
+0
+
+-- !sql_mmh3_u128_hello --
+121118445609844952839898260755277781762
+
+-- !sql_mmh3_u128_hello_world --
+228083453807047072434243676435732455694
+
+-- !sql_mmh3_u128_apache_doris --
+125233622341202073067337261280912046255
+
+-- !sql_mmh3_u128_two_args --
+322914706826559456551267661897730109525
+
+-- !sql_mmh3_u128_three_args --
+9994430460069927257443176797242139063
+
+-- !sql_mmh3_u128_null_second --
+\N
+
+-- !sql_mmh3_u128_null_first --
+\N
+
+-- !sql_mmh3_u128_all_null --
+\N
+
+-- !sql_mmh3_u128_unicode_zh --
+8282804273666544992604160676428939260
+
+-- !sql_mmh3_u128_unicode_ja --
+54500626245739954189896806014374040748
+
+-- !sql_mmh3_u128_other_types --
+88386560642490731012209186590589091690 112229221047929881211004856197893237700
6504046186406760443866950209489696482
+
-- !sql --
\N
@@ -116,4 +238,3 @@
-- !sql --
7001965798170371843
-
diff --git
a/regression-test/suites/query_p0/sql_functions/hash_functions/test_hash_function.groovy
b/regression-test/suites/query_p0/sql_functions/hash_functions/test_hash_function.groovy
index ace3379c99e..0a15cc73e3b 100644
---
a/regression-test/suites/query_p0/sql_functions/hash_functions/test_hash_function.groovy
+++
b/regression-test/suites/query_p0/sql_functions/hash_functions/test_hash_function.groovy
@@ -91,8 +91,26 @@ suite("test_hash_function", "arrow_flight_sql") {
qt_mmh3_64_v2_table "SELECT id, MURMUR_HASH3_64_V2(str_col) FROM
test_hash_tbl ORDER BY id;"
qt_mmh3_u64_v2_table "SELECT id, MURMUR_HASH3_U64_V2(str_col) FROM
test_hash_tbl ORDER BY id;"
+ qt_sql_mmh3_128_table "SELECT id, MURMUR_HASH3_128(str_col) FROM
test_hash_tbl ORDER BY id;"
+ qt_sql_mmh3_u128_table "SELECT id, MURMUR_HASH3_U128(str_col) FROM
test_hash_tbl ORDER BY id;"
+
+ qt_sql_mmh3_128_multi_arg_table """
+ SELECT id,
+ MURMUR_HASH3_128(str_col, 'world'),
+ MURMUR_HASH3_128('hello', str_col),
+ MURMUR_HASH3_128(str_col, str_col)
+ FROM test_hash_tbl
+ ORDER BY id;
+ """
- sql "DROP TABLE IF EXISTS test_hash_tbl;"
+ qt_sql_mmh3_u128_multi_arg_table """
+ SELECT id,
+ MURMUR_HASH3_U128(str_col, 'world'),
+ MURMUR_HASH3_U128('hello', str_col),
+ MURMUR_HASH3_U128(str_col, str_col)
+ FROM test_hash_tbl
+ ORDER BY id;
+ """
// Constant folding tests
qt_mmh3_64_v2_fold_1 "SELECT MURMUR_HASH3_64_V2('test') + 1;"
@@ -100,6 +118,51 @@ suite("test_hash_function", "arrow_flight_sql") {
qt_mmh3_u64_v2_fold_1 "SELECT MURMUR_HASH3_U64_V2('test') + 1;"
qt_mmh3_u64_v2_fold_2 "SELECT MURMUR_HASH3_U64_V2('a', 'b') * 2;"
+ qt_sql_mmh3_128_null "SELECT MURMUR_HASH3_128(NULL);"
+ qt_sql_mmh3_128_empty "SELECT MURMUR_HASH3_128('');"
+ qt_sql_mmh3_128_hello "SELECT MURMUR_HASH3_128('hello');"
+ qt_sql_mmh3_128_hello_world "SELECT MURMUR_HASH3_128('hello world');"
+ qt_sql_mmh3_128_apache_doris "SELECT MURMUR_HASH3_128('apache doris');"
+ qt_sql_mmh3_128_two_args "SELECT MURMUR_HASH3_128('hello', 'world');"
+ qt_sql_mmh3_128_three_args "SELECT MURMUR_HASH3_128('hello', 'world',
'!');"
+ qt_sql_mmh3_128_null_second "SELECT MURMUR_HASH3_128('hello', NULL);"
+ qt_sql_mmh3_128_null_first "SELECT MURMUR_HASH3_128(NULL, 'hello');"
+ qt_sql_mmh3_128_all_null "SELECT MURMUR_HASH3_128(NULL, NULL);"
+ qt_sql_mmh3_128_unicode_zh "SELECT MURMUR_HASH3_128('你好🤣');"
+ qt_sql_mmh3_128_unicode_ja "SELECT MURMUR_HASH3_128('アパッチドリス');"
+ qt_sql_mmh3_128_other_types """
+ SELECT MURMUR_HASH3_128(123),
+ MURMUR_HASH3_128(CAST(123.45 AS DECIMAL(9, 2))),
+ MURMUR_HASH3_128(CAST('2026-05-28' AS DATE));
+ """
+ qt_sql_mmh3_u128_null "SELECT MURMUR_HASH3_U128(NULL);"
+ qt_sql_mmh3_u128_empty "SELECT MURMUR_HASH3_U128('');"
+ qt_sql_mmh3_u128_hello "SELECT MURMUR_HASH3_U128('hello');"
+ qt_sql_mmh3_u128_hello_world "SELECT MURMUR_HASH3_U128('hello world');"
+ qt_sql_mmh3_u128_apache_doris "SELECT MURMUR_HASH3_U128('apache doris');"
+ qt_sql_mmh3_u128_two_args "SELECT MURMUR_HASH3_U128('hello', 'world');"
+ qt_sql_mmh3_u128_three_args "SELECT MURMUR_HASH3_U128('hello', 'world',
'!');"
+ qt_sql_mmh3_u128_null_second "SELECT MURMUR_HASH3_U128('hello', NULL);"
+ qt_sql_mmh3_u128_null_first "SELECT MURMUR_HASH3_U128(NULL, 'hello');"
+ qt_sql_mmh3_u128_all_null "SELECT MURMUR_HASH3_U128(NULL, NULL);"
+ qt_sql_mmh3_u128_unicode_zh "SELECT MURMUR_HASH3_U128('你好🤣');"
+ qt_sql_mmh3_u128_unicode_ja "SELECT MURMUR_HASH3_U128('アパッチドリス');"
+ qt_sql_mmh3_u128_other_types """
+ SELECT MURMUR_HASH3_U128(123),
+ MURMUR_HASH3_U128(CAST(123.45 AS DECIMAL(9, 2))),
+ MURMUR_HASH3_U128(CAST('2026-05-28' AS DATE));
+ """
+
+ test {
+ sql "SELECT MURMUR_HASH3_128();"
+ exception "0 arity"
+ }
+
+ test {
+ sql "SELECT MURMUR_HASH3_U128();"
+ exception "0 arity"
+ }
+
qt_sql "SELECT xxhash_32(null);"
qt_sql "SELECT xxhash_32(\"hello\");"
qt_sql "SELECT xxhash_32(\"hello\", \"world\");"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]