Coverage Report

Created: 2026-08-04 11:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exprs/function/function_hamming_distance.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include <algorithm>
19
#include <vector>
20
21
#include "common/status.h"
22
#include "core/column/column_nullable.h"
23
#include "core/column/column_string.h"
24
#include "core/data_type/data_type_number.h"
25
#include "core/string_ref.h"
26
#include "exprs/function/simple_function_factory.h"
27
#include "util/simd/vstring_function.h"
28
29
namespace doris {
30
31
class FunctionHammingDistance : public IFunction {
32
public:
33
    using ResultDataType = DataTypeInt64;
34
    using ResultPaddedPODArray = PaddedPODArray<Int64>;
35
    using ResultColumnType = ColumnVector<ResultDataType::PType>;
36
37
    static constexpr auto name = "hamming_distance";
38
39
45
    static FunctionPtr create() { return std::make_shared<FunctionHammingDistance>(); }
40
41
1
    String get_name() const override { return name; }
42
37
    size_t get_number_of_arguments() const override { return 2; }
43
44
37
    DataTypePtr get_return_type_impl(const DataTypes& arguments) const override {
45
37
        const bool has_nullable = std::ranges::any_of(
46
65
                arguments, [](const DataTypePtr& type) { return type->is_nullable(); });
47
37
        if (has_nullable) {
48
14
            return make_nullable(std::make_shared<ResultDataType>());
49
14
        }
50
23
        return std::make_shared<ResultDataType>();
51
37
    }
52
53
74
    bool use_default_implementation_for_nulls() const override { return false; }
54
55
    Status execute_impl(FunctionContext* /*context*/, Block& block, const ColumnNumbers& arguments,
56
37
                        uint32_t result, size_t input_rows_count) const override {
57
37
        const auto& [left_col, left_const] =
58
37
                unpack_if_const(block.get_by_position(arguments[0]).column);
59
37
        const auto& [right_col, right_const] =
60
37
                unpack_if_const(block.get_by_position(arguments[1]).column);
61
62
37
        const auto* left_nullable = check_and_get_column<ColumnNullable>(left_col.get());
63
37
        const auto* right_nullable = check_and_get_column<ColumnNullable>(right_col.get());
64
65
37
        const IColumn* left_nested =
66
37
                left_nullable ? &left_nullable->get_nested_column() : left_col.get();
67
37
        const IColumn* right_nested =
68
37
                right_nullable ? &right_nullable->get_nested_column() : right_col.get();
69
70
37
        const auto* left_str_col = assert_cast<const ColumnString*>(left_nested);
71
37
        const auto* right_str_col = assert_cast<const ColumnString*>(right_nested);
72
73
37
        auto res_col = ResultColumnType::create(input_rows_count);
74
37
        auto& res_data = res_col->get_data();
75
76
37
        const NullMap* left_null_map =
77
37
                left_nullable ? &left_nullable->get_null_map_data() : nullptr;
78
37
        const NullMap* right_null_map =
79
37
                right_nullable ? &right_nullable->get_null_map_data() : nullptr;
80
37
        const bool has_nullable = left_null_map != nullptr || right_null_map != nullptr;
81
82
37
        if (!has_nullable) {
83
23
            if (left_const) {
84
3
                RETURN_IF_ERROR(
85
3
                        scalar_vector(left_str_col->get_data_at(0), *right_str_col, res_data));
86
20
            } else if (right_const) {
87
4
                RETURN_IF_ERROR(
88
4
                        vector_scalar(*left_str_col, right_str_col->get_data_at(0), res_data));
89
16
            } else {
90
16
                RETURN_IF_ERROR(vector_vector(*left_str_col, *right_str_col, res_data));
91
16
            }
92
15
            block.replace_by_position(result, std::move(res_col));
93
15
            return Status::OK();
94
23
        }
95
96
14
        auto null_col = ColumnUInt8::create(input_rows_count, 0);
97
14
        auto& null_map = null_col->get_data();
98
14
        if (left_const) {
99
3
            if (left_null_map && (*left_null_map)[0]) {
100
0
                std::fill(null_map.begin(), null_map.end(), 1);
101
0
                block.replace_by_position(
102
0
                        result, ColumnNullable::create(std::move(res_col), std::move(null_col)));
103
0
                return Status::OK();
104
0
            }
105
106
3
            const auto left = left_str_col->get_data_at(0);
107
3
            RETURN_IF_ERROR(scalar_vector_nullable(left, *right_str_col, right_null_map, res_data,
108
3
                                                   null_map));
109
11
        } else if (right_const) {
110
4
            if (right_null_map && (*right_null_map)[0]) {
111
0
                std::fill(null_map.begin(), null_map.end(), 1);
112
0
                block.replace_by_position(
113
0
                        result, ColumnNullable::create(std::move(res_col), std::move(null_col)));
114
0
                return Status::OK();
115
0
            }
116
117
4
            RETURN_IF_ERROR(vector_scalar_nullable(*left_str_col, right_str_col->get_data_at(0),
118
4
                                                   left_null_map, res_data, null_map));
119
7
        } else {
120
20
            for (size_t i = 0; i < input_rows_count; ++i) {
121
13
                const bool left_is_null = left_null_map && (*left_null_map)[i];
122
13
                const bool right_is_null = right_null_map && (*right_null_map)[i];
123
13
                if (left_is_null || right_is_null) {
124
7
                    null_map[i] = 1;
125
7
                    res_data[i] = 0;
126
7
                    continue;
127
7
                }
128
129
6
                RETURN_IF_ERROR(hamming_distance(left_str_col->get_data_at(i),
130
6
                                                 right_str_col->get_data_at(i), res_data[i], i));
131
6
            }
132
7
        }
133
134
11
        block.replace_by_position(result,
135
11
                                  ColumnNullable::create(std::move(res_col), std::move(null_col)));
136
11
        return Status::OK();
137
14
    }
138
139
private:
140
    static Status vector_vector(const ColumnString& lcol, const ColumnString& rcol,
141
16
                                ResultPaddedPODArray& res) {
142
16
        DCHECK_EQ(lcol.size(), rcol.size());
143
144
16
        const size_t size = lcol.size();
145
16
        res.resize(size);
146
16
        std::vector<size_t> left_offsets;
147
16
        std::vector<size_t> right_offsets;
148
30
        for (size_t i = 0; i < size; ++i) {
149
19
            const auto left = lcol.get_data_at(i);
150
19
            const auto right = rcol.get_data_at(i);
151
19
            RETURN_IF_ERROR(hamming_distance_with_offsets(
152
19
                    left, left_offsets, false, simd::VStringFunctions::is_ascii(left), right,
153
19
                    right_offsets, false, simd::VStringFunctions::is_ascii(right), res[i], i));
154
19
        }
155
11
        return Status::OK();
156
16
    }
157
158
    static Status vector_scalar(const ColumnString& lcol, const StringRef& rdata,
159
4
                                ResultPaddedPODArray& res) {
160
4
        const size_t size = lcol.size();
161
4
        res.resize(size);
162
4
        const bool right_ascii = simd::VStringFunctions::is_ascii(rdata);
163
4
        std::vector<size_t> right_offsets;
164
4
        simd::VStringFunctions::get_utf8_char_offsets(rdata, right_offsets);
165
4
        std::vector<size_t> left_offsets;
166
6
        for (size_t i = 0; i < size; ++i) {
167
4
            const auto left = lcol.get_data_at(i);
168
4
            RETURN_IF_ERROR(hamming_distance_with_offsets(
169
4
                    left, left_offsets, false, simd::VStringFunctions::is_ascii(left), rdata,
170
4
                    right_offsets, true, right_ascii, res[i], i));
171
4
        }
172
2
        return Status::OK();
173
4
    }
174
175
    static Status scalar_vector(const StringRef& ldata, const ColumnString& rcol,
176
3
                                ResultPaddedPODArray& res) {
177
3
        const size_t size = rcol.size();
178
3
        res.resize(size);
179
3
        const bool left_ascii = simd::VStringFunctions::is_ascii(ldata);
180
3
        std::vector<size_t> left_offsets;
181
3
        simd::VStringFunctions::get_utf8_char_offsets(ldata, left_offsets);
182
3
        std::vector<size_t> right_offsets;
183
5
        for (size_t i = 0; i < size; ++i) {
184
3
            const auto right = rcol.get_data_at(i);
185
3
            RETURN_IF_ERROR(hamming_distance_with_offsets(
186
3
                    ldata, left_offsets, true, left_ascii, right, right_offsets, false,
187
3
                    simd::VStringFunctions::is_ascii(right), res[i], i));
188
3
        }
189
2
        return Status::OK();
190
3
    }
191
192
    static Status vector_scalar_nullable(const ColumnString& lcol, const StringRef& rdata,
193
                                         const NullMap* left_null_map, ResultPaddedPODArray& res,
194
4
                                         NullMap& null_map) {
195
4
        const size_t size = lcol.size();
196
4
        res.resize(size);
197
4
        const bool right_ascii = simd::VStringFunctions::is_ascii(rdata);
198
4
        std::vector<size_t> right_offsets;
199
4
        simd::VStringFunctions::get_utf8_char_offsets(rdata, right_offsets);
200
4
        std::vector<size_t> left_offsets;
201
8
        for (size_t i = 0; i < size; ++i) {
202
6
            if (left_null_map && (*left_null_map)[i]) {
203
1
                null_map[i] = 1;
204
1
                res[i] = 0;
205
1
                continue;
206
1
            }
207
208
5
            const auto left = lcol.get_data_at(i);
209
5
            RETURN_IF_ERROR(hamming_distance_with_offsets(
210
5
                    left, left_offsets, false, simd::VStringFunctions::is_ascii(left), rdata,
211
5
                    right_offsets, true, right_ascii, res[i], i));
212
5
        }
213
2
        return Status::OK();
214
4
    }
215
216
    static Status scalar_vector_nullable(const StringRef& ldata, const ColumnString& rcol,
217
                                         const NullMap* right_null_map, ResultPaddedPODArray& res,
218
3
                                         NullMap& null_map) {
219
3
        const size_t size = rcol.size();
220
3
        res.resize(size);
221
3
        const bool left_ascii = simd::VStringFunctions::is_ascii(ldata);
222
3
        std::vector<size_t> left_offsets;
223
3
        simd::VStringFunctions::get_utf8_char_offsets(ldata, left_offsets);
224
3
        std::vector<size_t> right_offsets;
225
7
        for (size_t i = 0; i < size; ++i) {
226
5
            if (right_null_map && (*right_null_map)[i]) {
227
1
                null_map[i] = 1;
228
1
                res[i] = 0;
229
1
                continue;
230
1
            }
231
232
4
            const auto right = rcol.get_data_at(i);
233
4
            RETURN_IF_ERROR(hamming_distance_with_offsets(
234
4
                    ldata, left_offsets, true, left_ascii, right, right_offsets, false,
235
4
                    simd::VStringFunctions::is_ascii(right), res[i], i));
236
4
        }
237
2
        return Status::OK();
238
3
    }
239
240
    static Status hamming_distance_ascii(const StringRef& left, const StringRef& right,
241
24
                                         Int64& result, size_t row) {
242
24
        if (left.size != right.size) {
243
7
            return Status::InvalidArgument(
244
7
                    "hamming_distance requires strings of the same length at row {}", row);
245
7
        }
246
247
17
        Int64 distance = 0;
248
69
        for (size_t i = 0; i < left.size; ++i) {
249
52
            distance += static_cast<Int64>(left.data[i] != right.data[i]);
250
52
        }
251
17
        result = distance;
252
17
        return Status::OK();
253
24
    }
254
255
    static Status hamming_distance_utf8(const StringRef& left,
256
                                        const std::vector<size_t>& left_offsets,
257
                                        const StringRef& right,
258
                                        const std::vector<size_t>& right_offsets, Int64& result,
259
17
                                        size_t row) {
260
17
        if (left_offsets.size() != right_offsets.size()) {
261
4
            return Status::InvalidArgument(
262
4
                    "hamming_distance requires strings of the same length at row {}", row);
263
4
        }
264
265
13
        Int64 distance = 0;
266
13
        const size_t len = left_offsets.size();
267
31
        for (size_t i = 0; i + 1 < len; ++i) {
268
18
            const size_t left_off = left_offsets[i];
269
18
            const size_t left_next = left_offsets[i + 1];
270
18
            const size_t right_off = right_offsets[i];
271
18
            const size_t right_next = right_offsets[i + 1];
272
18
            distance += static_cast<Int64>(!simd::VStringFunctions::utf8_char_equal(
273
18
                    left, left_off, left_next, right, right_off, right_next));
274
18
        }
275
13
        if (len > 0) {
276
13
            const size_t left_off = left_offsets[len - 1];
277
13
            const size_t right_off = right_offsets[len - 1];
278
13
            distance += static_cast<Int64>(!simd::VStringFunctions::utf8_char_equal(
279
13
                    left, left_off, left.size, right, right_off, right.size));
280
13
        }
281
282
13
        result = distance;
283
13
        return Status::OK();
284
17
    }
285
286
    static Status hamming_distance_with_offsets(
287
            const StringRef& left, std::vector<size_t>& left_offsets, bool left_offsets_ready,
288
            bool left_ascii, const StringRef& right, std::vector<size_t>& right_offsets,
289
41
            bool right_offsets_ready, bool right_ascii, Int64& result, size_t row) {
290
41
        if (left_ascii && right_ascii) {
291
24
            return hamming_distance_ascii(left, right, result, row);
292
24
        }
293
294
17
        if (!left_offsets_ready) {
295
15
            simd::VStringFunctions::get_utf8_char_offsets(left, left_offsets);
296
15
        }
297
17
        if (!right_offsets_ready) {
298
13
            simd::VStringFunctions::get_utf8_char_offsets(right, right_offsets);
299
13
        }
300
17
        return hamming_distance_utf8(left, left_offsets, right, right_offsets, result, row);
301
41
    }
302
303
    static Status hamming_distance(const StringRef& left, const StringRef& right, Int64& result,
304
6
                                   size_t row) {
305
6
        std::vector<size_t> left_offsets;
306
6
        std::vector<size_t> right_offsets;
307
6
        return hamming_distance_with_offsets(
308
6
                left, left_offsets, false, simd::VStringFunctions::is_ascii(left), right,
309
6
                right_offsets, false, simd::VStringFunctions::is_ascii(right), result, row);
310
6
    }
311
};
312
313
7
void register_function_hamming_distance(SimpleFunctionFactory& factory) {
314
7
    factory.register_function<FunctionHammingDistance>();
315
7
}
316
317
} // namespace doris