Coverage Report

Created: 2026-09-24 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/jsonb_parser_simd.h
Line
Count
Source
1
/*
2
 *  Copyright (c) 2014, Facebook, Inc.
3
 *  All rights reserved.
4
 *
5
 *  This source code is licensed under the BSD-style license found in the
6
 *  LICENSE file in the root directory of this source tree. An additional grant
7
 *  of patent rights can be found in the PATENTS file in the same directory.
8
 *
9
 */
10
11
/*
12
 * This file defines JsonbParserTSIMD (template) and JsonbParser.
13
 *
14
 * JsonbParserTSIMD is a template class which implements a JSON parser.
15
 * JsonbParserTSIMD parses JSON text, and serialize it to JSONB binary format
16
 * by using JsonbWriterT object. By default, JsonbParserTSIMD creates a new
17
 * JsonbWriterT object with an output stream object.  However, you can also
18
 * pass in your JsonbWriterT or any stream object that implements some basic
19
 * interface of std::ostream (see JsonbStream.h).
20
 *
21
 * JsonbParser specializes JsonbParserTSIMD with JsonbOutStream type (see
22
 * JsonbStream.h). So unless you want to provide own a different output stream
23
 * type, use JsonbParser object.
24
 *
25
 * ** Parsing JSON **
26
 * JsonbParserTSIMD parses JSON string, and directly serializes into JSONB
27
 * packed bytes. There are three ways to parse a JSON string: (1) using
28
 * c-string, (2) using string with len, (3) using std::istream object. You can
29
 * use custom streambuf to redirect output. JsonbOutBuffer is a streambuf used
30
 * internally if the input is raw character buffer.
31
 *
32
 * You can reuse an JsonbParserTSIMD object to parse/serialize multiple JSON
33
 * strings, and the previous JSONB will be overwritten.
34
 *
35
 * If parsing fails (returned false), the error code will be set to one of
36
 * JsonbErrType, and can be retrieved by calling getErrorCode().
37
 *
38
 * ** External dictionary **
39
 * During parsing a JSON string, you can pass a call-back function to map a key
40
 * string to an id, and store the dictionary id in JSONB to save space. The
41
 * purpose of using an external dictionary is more towards a collection of
42
 * documents (which has common keys) rather than a single document, so that
43
 * space saving will be significant.
44
 *
45
 * ** Endianness **
46
 * Note: JSONB serialization doesn't assume endianness of the server. However
47
 * you will need to ensure that the endianness at the reader side is the same
48
 * as that at the writer side (if they are on different machines). Otherwise,
49
 * proper conversion is needed when a number value is returned to the
50
 * caller/writer.
51
 *
52
 * @author Tian Xia <tianx@fb.com>
53
 * 
54
 * this file is copied from 
55
 * https://github.com/facebook/mysql-5.6/blob/fb-mysql-5.6.35/fbson/FbsonJsonParser.h
56
 * and modified by Doris
57
 */
58
59
#pragma once
60
#include <simdjson.h>
61
62
#include <cmath>
63
#include <limits>
64
#include <string_view>
65
66
#include "common/status.h"
67
#include "util/jsonb_document.h"
68
#include "util/jsonb_writer.h"
69
#include "util/string_parser.hpp"
70
71
namespace doris {
72
using int128_t = __int128;
73
struct JsonbParser {
74
private:
75
2.09M
    static bool is_json_whitespace(char c) {
76
2.09M
        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
77
2.09M
    }
78
79
1.04M
    static std::string_view trim_json_whitespace(std::string_view token) {
80
1.04M
        while (!token.empty() && is_json_whitespace(token.front())) {
81
2
            token.remove_prefix(1);
82
2
        }
83
1.04M
        while (!token.empty() && is_json_whitespace(token.back())) {
84
3
            token.remove_suffix(1);
85
3
        }
86
1.04M
        return token;
87
1.04M
    }
88
89
88
    static bool is_valid_json_number(std::string_view token) {
90
88
        size_t pos = 0;
91
88
        if (pos < token.size() && token[pos] == '-') {
92
8
            ++pos;
93
8
        }
94
88
        if (pos == token.size()) {
95
0
            return false;
96
0
        }
97
98
88
        if (token[pos] == '0') {
99
8
            ++pos;
100
80
        } else if (token[pos] >= '1' && token[pos] <= '9') {
101
492
            do {
102
492
                ++pos;
103
492
            } while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9');
104
80
        } else {
105
0
            return false;
106
0
        }
107
108
88
        if (pos < token.size() && token[pos] == '.') {
109
33
            ++pos;
110
33
            const size_t fraction_start = pos;
111
35
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
112
2
                ++pos;
113
2
            }
114
33
            if (pos == fraction_start) {
115
32
                return false;
116
32
            }
117
33
        }
118
119
56
        if (pos < token.size() && (token[pos] == 'e' || token[pos] == 'E')) {
120
3
            ++pos;
121
3
            if (pos < token.size() && (token[pos] == '+' || token[pos] == '-')) {
122
1
                ++pos;
123
1
            }
124
3
            const size_t exponent_start = pos;
125
7
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
126
4
                ++pos;
127
4
            }
128
3
            if (pos == exponent_start) {
129
2
                return false;
130
2
            }
131
3
        }
132
133
54
        return pos == token.size();
134
56
    }
135
136
    // According to https://github.com/simdjson/simdjson/pull/2139
137
    // For numbers larger than 64 bits, we can obtain the raw_json_token and parse it ourselves.
138
    // This allows handling numbers larger than 64 bits, such as int128.
139
    // For example, try to parse a 18446744073709551616, this number is just 1 greater than the maximum value of uint64_t, and simdjson will return a NUMBER_ERROR
140
    // If try to parse a 18446744073709551616231231, it is obviously a large integer, at this time simdjson will return a BIGINT_ERROR
141
1.04M
    static bool parse_number_success(simdjson::error_code error_code, std::string_view raw_token) {
142
1.04M
        if (error_code == simdjson::error_code::SUCCESS) {
143
1.04M
            return true;
144
1.04M
        }
145
88
        if (error_code != simdjson::error_code::NUMBER_ERROR &&
146
88
            error_code != simdjson::error_code::BIGINT_ERROR) {
147
0
            return false;
148
0
        }
149
88
        return is_valid_json_number(raw_token);
150
88
    }
151
152
public:
153
    // parse a UTF-8 JSON string with length
154
    // will reset writer before parse
155
821k
    static Status parse(const char* pch, size_t len, JsonbWriter& writer) {
156
821k
        if (!pch || len == 0) {
157
21
            return Status::InvalidArgument("Empty JSON document");
158
21
        }
159
821k
        writer.reset();
160
821k
        try {
161
821k
            simdjson::ondemand::parser simdjson_parser;
162
821k
            simdjson::padded_string json_str {pch, len};
163
821k
            simdjson::ondemand::document doc = simdjson_parser.iterate(json_str);
164
821k
            bool root_number_validated_from_raw_token = false;
165
166
            // simdjson process top level primitive types specially
167
            // so some repeated code here
168
821k
            switch (doc.type()) {
169
809k
            case simdjson::ondemand::json_type::object:
170
819k
            case simdjson::ondemand::json_type::array: {
171
819k
                RETURN_IF_ERROR(parse(doc.get_value(), writer));
172
819k
                break;
173
819k
            }
174
819k
            case simdjson::ondemand::json_type::null: {
175
131
                RETURN_IF_ERROR(write_null(doc, writer));
176
131
                break;
177
131
            }
178
136
            case simdjson::ondemand::json_type::boolean: {
179
136
                if (writer.writeBool(doc.get_bool()) == 0) {
180
0
                    return Status::InvalidArgument("writeBool failed");
181
0
                }
182
136
                break;
183
136
            }
184
361
            case simdjson::ondemand::json_type::string: {
185
361
                RETURN_IF_ERROR(write_string(doc.get_string(), writer));
186
361
                break;
187
361
            }
188
584
            case simdjson::ondemand::json_type::number: {
189
584
                simdjson::ondemand::number num;
190
584
                simdjson::error_code res = doc.get_number().get(num);
191
584
                const auto raw_token = trim_json_whitespace(doc.raw_json_token());
192
584
                if (!parse_number_success(res, raw_token)) {
193
68
                    return Status::InvalidArgument(fmt::format("simdjson get_number failed: {}",
194
68
                                                               simdjson::error_message(res)));
195
68
                }
196
                // simdjson get_number() returns a number object, which can be
197
516
                RETURN_IF_ERROR(write_number(num, doc.get_number_type(), raw_token, writer));
198
515
                if (res != simdjson::error_code::SUCCESS) {
199
8
                    const auto document = trim_json_whitespace(std::string_view {pch, len});
200
8
                    if (raw_token != document) {
201
0
                        return Status::InvalidArgument("JSON document was not fully consumed");
202
0
                    }
203
8
                    root_number_validated_from_raw_token = true;
204
8
                }
205
515
                break;
206
515
            }
207
821k
            }
208
820k
            if (!root_number_validated_from_raw_token && !doc.at_end()) {
209
0
                return Status::InvalidArgument("JSON document was not fully consumed");
210
0
            }
211
820k
        } catch (simdjson::simdjson_error& e) {
212
1.18k
            return Status::InvalidArgument(fmt::format("simdjson parse exception: {}", e.what()));
213
1.18k
        }
214
820k
        return Status::OK();
215
821k
    }
216
217
private:
218
    template <typename JsonValue>
219
11.9k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
220
11.9k
        if (!value.is_null()) {
221
0
            return Status::InvalidArgument("Invalid null literal");
222
0
        }
223
11.9k
        if (writer.writeNull() == 0) {
224
0
            return Status::InvalidArgument("writeNull failed");
225
0
        }
226
11.9k
        return Status::OK();
227
11.9k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand5valueEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
219
11.8k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
220
11.8k
        if (!value.is_null()) {
221
0
            return Status::InvalidArgument("Invalid null literal");
222
0
        }
223
11.8k
        if (writer.writeNull() == 0) {
224
0
            return Status::InvalidArgument("writeNull failed");
225
0
        }
226
11.8k
        return Status::OK();
227
11.8k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand8documentEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
219
131
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
220
131
        if (!value.is_null()) {
221
0
            return Status::InvalidArgument("Invalid null literal");
222
0
        }
223
131
        if (writer.writeNull() == 0) {
224
0
            return Status::InvalidArgument("writeNull failed");
225
0
        }
226
131
        return Status::OK();
227
131
    }
228
229
1.04M
    static Status parse_number(simdjson::ondemand::value& value, JsonbWriter& writer) {
230
1.04M
        simdjson::ondemand::number num;
231
1.04M
        const auto result = value.get_number().get(num);
232
1.04M
        const auto raw_token = trim_json_whitespace(value.raw_json_token());
233
1.04M
        if (!parse_number_success(result, raw_token)) {
234
4
            return Status::InvalidArgument(
235
4
                    fmt::format("simdjson get_number failed: {}", simdjson::error_message(result)));
236
4
        }
237
1.04M
        return write_number(num, value.get_number_type(), raw_token, writer);
238
1.04M
    }
239
240
    // parse json, recursively if necessary, by simdjson
241
    //  and serialize to binary format by writer
242
2.26M
    static Status parse(simdjson::ondemand::value value, JsonbWriter& writer) {
243
2.26M
        switch (value.type()) {
244
11.8k
        case simdjson::ondemand::json_type::null: {
245
11.8k
            return write_null(value, writer);
246
0
        }
247
29.2k
        case simdjson::ondemand::json_type::boolean: {
248
29.2k
            if (writer.writeBool(value.get_bool()) == 0) {
249
0
                return Status::InvalidArgument("writeBool failed");
250
0
            }
251
29.2k
            break;
252
29.2k
        }
253
298k
        case simdjson::ondemand::json_type::string: {
254
298k
            RETURN_IF_ERROR(write_string(value.get_string(), writer));
255
298k
            break;
256
298k
        }
257
1.04M
        case simdjson::ondemand::json_type::number: {
258
1.04M
            return parse_number(value, writer);
259
298k
        }
260
816k
        case simdjson::ondemand::json_type::object: {
261
816k
            if (!writer.writeStartObject()) {
262
0
                return Status::InvalidArgument("writeStartObject failed");
263
0
            }
264
265
870k
            for (auto kv : value.get_object()) {
266
870k
                std::string_view key;
267
870k
                simdjson::error_code e = kv.unescaped_key().get(key);
268
870k
                if (e != simdjson::SUCCESS) {
269
41
                    return Status::InvalidArgument(fmt::format("simdjson get key failed: {}", e));
270
41
                }
271
272
                // write key
273
870k
                if (key.size() > std::numeric_limits<uint8_t>::max()) {
274
268
                    return Status::InvalidArgument("key size exceeds max limit: {} , {}",
275
268
                                                   key.size(), std::numeric_limits<uint8_t>::max());
276
268
                }
277
870k
                if (!writer.writeKey(key.data(), (uint8_t)key.size())) {
278
0
                    return Status::InvalidArgument("writeKey failed : {}", key);
279
0
                }
280
281
                // parse object value
282
870k
                RETURN_IF_ERROR(parse(kv.value(), writer));
283
870k
            }
284
285
815k
            if (!writer.writeEndObject()) {
286
0
                return Status::InvalidArgument("writeEndObject failed");
287
0
            }
288
289
815k
            break;
290
815k
        }
291
815k
        case simdjson::ondemand::json_type::array: {
292
62.9k
            if (!writer.writeStartArray()) {
293
0
                return Status::InvalidArgument("writeStartArray failed");
294
0
            }
295
296
577k
            for (auto elem : value.get_array()) {
297
                // parse array element
298
577k
                RETURN_IF_ERROR(parse(elem.value(), writer));
299
577k
            }
300
301
62.7k
            if (!writer.writeEndArray()) {
302
0
                return Status::InvalidArgument("writeEndArray failed");
303
0
            }
304
62.7k
            break;
305
62.7k
        }
306
62.7k
        default: {
307
0
            return Status::InvalidArgument("unknown value type: ");
308
62.7k
        }
309
310
2.26M
        } // end of switch
311
1.20M
        return Status::OK();
312
2.26M
    }
313
314
    static Status write_floating_number(double number, std::string_view raw_string,
315
127k
                                        JsonbWriter& writer) {
316
        // When a double exceeds the precision that can be represented by a double type in
317
        // simdjson, it gets converted to 0. The correct approach is to truncate the value instead.
318
127k
        if (number == 0) {
319
13.7k
            StringParser::ParseResult result;
320
13.7k
            number = StringParser::string_to_float<double>(raw_string.data(), raw_string.size(),
321
13.7k
                                                           &result);
322
13.7k
            if (result != StringParser::PARSE_SUCCESS) {
323
0
                return Status::InvalidArgument("invalid number, raw string is: " +
324
0
                                               std::string(raw_string));
325
0
            }
326
13.7k
        }
327
127k
        if (!std::isfinite(number)) {
328
1
            return Status::InvalidArgument("non-finite number, raw string is: " +
329
1
                                           std::string(raw_string));
330
1
        }
331
127k
        if (writer.writeDouble(number) == 0) {
332
0
            return Status::InvalidArgument("writeDouble failed");
333
0
        }
334
127k
        return Status::OK();
335
127k
    }
336
337
15
    static Status write_big_integer(std::string_view raw_string, JsonbWriter& writer) {
338
15
        StringParser::ParseResult result;
339
15
        auto value = StringParser::string_to_int<int128_t>(raw_string.data(), raw_string.size(),
340
15
                                                           &result);
341
15
        if (result == StringParser::PARSE_SUCCESS) {
342
13
            if (!writer.writeInt128(value)) {
343
0
                return Status::InvalidArgument("writeInt128 failed");
344
0
            }
345
13
            return Status::OK();
346
13
        }
347
348
        // JSON text can represent integers beyond int128. Preserve the existing fallback to
349
        // double when it is finite, even though the conversion may lose precision.
350
2
        double double_value = StringParser::string_to_float<double>(raw_string.data(),
351
2
                                                                    raw_string.size(), &result);
352
2
        if (result != StringParser::PARSE_SUCCESS || !std::isfinite(double_value)) {
353
0
            return Status::InvalidArgument("invalid number, raw string is: " +
354
0
                                           std::string(raw_string));
355
0
        }
356
2
        if (!writer.writeDouble(double_value)) {
357
0
            return Status::InvalidArgument("writeDouble failed");
358
0
        }
359
2
        return Status::OK();
360
2
    }
361
362
299k
    static Status write_string(std::string_view str, JsonbWriter& writer) {
363
        // start writing string
364
299k
        if (!writer.writeStartString()) {
365
0
            return Status::InvalidArgument("writeStartString failed");
366
0
        }
367
368
        // write string
369
299k
        if (str.size() > 0) {
370
288k
            if (writer.writeString(str.data(), str.size()) == 0) {
371
0
                return Status::InvalidArgument("writeString failed");
372
0
            }
373
288k
        }
374
375
        // end writing string
376
299k
        if (!writer.writeEndString()) {
377
0
            return Status::InvalidArgument("writeEndString failed");
378
0
        }
379
299k
        return Status::OK();
380
299k
    }
381
382
    static Status write_number(simdjson::ondemand::number num,
383
                               simdjson ::ondemand::number_type num_type,
384
1.04M
                               std::string_view raw_string, JsonbWriter& writer) {
385
        // The simdjson library supports four types of numbers:
386
        // 1. floating_point_number: A binary64 number, which will be converted to jsonb's double type.
387
        // 2. signed_integer: A signed integer that fits in a 64-bit word using two's complement.
388
        // 3. unsigned_integer: A positive integer larger or equal to 1<<63.
389
        //    For these two integer types, we will convert them to jsonb's int8/int16/int32/int64/int128 types according to the specific value.
390
        // 4. big_integer: An integer that does not fit in a 64-bit word.
391
        //    For this type, simdjson cannot handle it directly. We first try to convert it to jsonb's int128 type.
392
        //    If conversion fails, we attempt to convert it to a double type.
393
        //    If conversion to double also fails, an error is returned.
394
395
1.04M
        switch (num_type) {
396
127k
        case simdjson::ondemand::number_type::floating_point_number: {
397
127k
            return write_floating_number(num.get_double(), raw_string, writer);
398
0
        }
399
920k
        case simdjson::ondemand::number_type::signed_integer:
400
920k
        case simdjson::ondemand::number_type::unsigned_integer: {
401
920k
            int128_t val = num.is_int64() ? (int128_t)num.get_int64() : (int128_t)num.get_uint64();
402
920k
            bool success = false;
403
920k
            if (val >= std::numeric_limits<int8_t>::min() &&
404
920k
                val <= std::numeric_limits<int8_t>::max()) {
405
62.3k
                success = writer.writeInt8((int8_t)val);
406
858k
            } else if (val >= std::numeric_limits<int16_t>::min() &&
407
858k
                       val <= std::numeric_limits<int16_t>::max()) {
408
260k
                success = writer.writeInt16((int16_t)val);
409
597k
            } else if (val >= std::numeric_limits<int32_t>::min() &&
410
597k
                       val <= std::numeric_limits<int32_t>::max()) {
411
536k
                success = writer.writeInt32((int32_t)val);
412
536k
            } else if (val >= std::numeric_limits<int64_t>::min() &&
413
61.2k
                       val <= std::numeric_limits<int64_t>::max()) {
414
61.1k
                success = writer.writeInt64((int64_t)val);
415
61.1k
            } else { // INT128
416
50
                success = writer.writeInt128(val);
417
50
            }
418
419
920k
            if (!success) {
420
0
                return Status::InvalidArgument("writeInt failed");
421
0
            }
422
920k
            return Status::OK();
423
920k
        }
424
15
        case simdjson::ondemand::number_type::big_integer: {
425
15
            return write_big_integer(raw_string, writer);
426
920k
        }
427
1.04M
        }
428
0
        return Status::InvalidArgument("unknown number type");
429
1.04M
    }
430
};
431
} // namespace doris