Coverage Report

Created: 2026-09-24 18:02

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 <limits>
63
#include <string_view>
64
65
#include "common/status.h"
66
#include "util/jsonb_document.h"
67
#include "util/jsonb_writer.h"
68
#include "util/string_parser.hpp"
69
70
namespace doris {
71
using int128_t = __int128;
72
struct JsonbParser {
73
private:
74
456k
    static bool is_json_whitespace(char c) {
75
456k
        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
76
456k
    }
77
78
228k
    static std::string_view trim_json_whitespace(std::string_view token) {
79
228k
        while (!token.empty() && is_json_whitespace(token.front())) {
80
2
            token.remove_prefix(1);
81
2
        }
82
228k
        while (!token.empty() && is_json_whitespace(token.back())) {
83
3
            token.remove_suffix(1);
84
3
        }
85
228k
        return token;
86
228k
    }
87
88
23
    static bool is_valid_json_number(std::string_view token) {
89
23
        size_t pos = 0;
90
23
        if (pos < token.size() && token[pos] == '-') {
91
2
            ++pos;
92
2
        }
93
23
        if (pos == token.size()) {
94
0
            return false;
95
0
        }
96
97
23
        if (token[pos] == '0') {
98
4
            ++pos;
99
19
        } else if (token[pos] >= '1' && token[pos] <= '9') {
100
539
            do {
101
539
                ++pos;
102
539
            } while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9');
103
19
        } else {
104
0
            return false;
105
0
        }
106
107
23
        if (pos < token.size() && token[pos] == '.') {
108
8
            ++pos;
109
8
            const size_t fraction_start = pos;
110
12
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
111
4
                ++pos;
112
4
            }
113
8
            if (pos == fraction_start) {
114
5
                return false;
115
5
            }
116
8
        }
117
118
18
        if (pos < token.size() && (token[pos] == 'e' || token[pos] == 'E')) {
119
6
            ++pos;
120
6
            if (pos < token.size() && (token[pos] == '+' || token[pos] == '-')) {
121
1
                ++pos;
122
1
            }
123
6
            const size_t exponent_start = pos;
124
20
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
125
14
                ++pos;
126
14
            }
127
6
            if (pos == exponent_start) {
128
2
                return false;
129
2
            }
130
6
        }
131
132
16
        return pos == token.size();
133
18
    }
134
135
    // According to https://github.com/simdjson/simdjson/pull/2139
136
    // For numbers larger than 64 bits, we can obtain the raw_json_token and parse it ourselves.
137
    // This allows handling numbers larger than 64 bits, such as int128.
138
    // 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
139
    // If try to parse a 18446744073709551616231231, it is obviously a large integer, at this time simdjson will return a BIGINT_ERROR
140
228k
    static bool parse_number_success(simdjson::error_code error_code, std::string_view raw_token) {
141
228k
        if (error_code == simdjson::error_code::SUCCESS) {
142
228k
            return true;
143
228k
        }
144
23
        if (error_code != simdjson::error_code::NUMBER_ERROR &&
145
23
            error_code != simdjson::error_code::BIGINT_ERROR) {
146
0
            return false;
147
0
        }
148
23
        return is_valid_json_number(raw_token);
149
23
    }
150
151
public:
152
    // parse a UTF-8 JSON string with length
153
    // will reset writer before parse
154
6.21k
    static Status parse(const char* pch, size_t len, JsonbWriter& writer) {
155
6.21k
        if (!pch || len == 0) {
156
11
            return Status::InvalidArgument("Empty JSON document");
157
11
        }
158
6.20k
        writer.reset();
159
6.20k
        try {
160
6.20k
            simdjson::ondemand::parser simdjson_parser;
161
6.20k
            simdjson::padded_string json_str {pch, len};
162
6.20k
            simdjson::ondemand::document doc = simdjson_parser.iterate(json_str);
163
6.20k
            bool root_number_validated_from_raw_token = false;
164
165
            // simdjson process top level primitive types specially
166
            // so some repeated code here
167
6.20k
            switch (doc.type()) {
168
3.87k
            case simdjson::ondemand::json_type::object:
169
5.45k
            case simdjson::ondemand::json_type::array: {
170
5.45k
                RETURN_IF_ERROR(parse(doc.get_value(), writer));
171
5.17k
                break;
172
5.45k
            }
173
5.17k
            case simdjson::ondemand::json_type::null: {
174
58
                RETURN_IF_ERROR(write_null(doc, writer));
175
58
                break;
176
58
            }
177
71
            case simdjson::ondemand::json_type::boolean: {
178
71
                if (writer.writeBool(doc.get_bool()) == 0) {
179
0
                    return Status::InvalidArgument("writeBool failed");
180
0
                }
181
71
                break;
182
71
            }
183
324
            case simdjson::ondemand::json_type::string: {
184
324
                RETURN_IF_ERROR(write_string(doc.get_string(), writer));
185
324
                break;
186
324
            }
187
324
            case simdjson::ondemand::json_type::number: {
188
286
                simdjson::ondemand::number num;
189
286
                simdjson::error_code res = doc.get_number().get(num);
190
286
                const auto raw_token = trim_json_whitespace(doc.raw_json_token());
191
286
                if (!parse_number_success(res, raw_token)) {
192
10
                    return Status::InvalidArgument(fmt::format("simdjson get_number failed: {}",
193
10
                                                               simdjson::error_message(res)));
194
10
                }
195
                // simdjson get_number() returns a number object, which can be
196
276
                RETURN_IF_ERROR(write_number(num, doc.get_number_type(), raw_token, writer));
197
276
                if (res != simdjson::error_code::SUCCESS) {
198
6
                    const auto document = trim_json_whitespace(std::string_view {pch, len});
199
6
                    if (raw_token != document) {
200
0
                        return Status::InvalidArgument("JSON document was not fully consumed");
201
0
                    }
202
6
                    root_number_validated_from_raw_token = true;
203
6
                }
204
276
                break;
205
276
            }
206
6.20k
            }
207
5.88k
            if (!root_number_validated_from_raw_token && !doc.at_end()) {
208
2
                return Status::InvalidArgument("JSON document was not fully consumed");
209
2
            }
210
5.88k
        } catch (simdjson::simdjson_error& e) {
211
33
            return Status::InvalidArgument(fmt::format("simdjson parse exception: {}", e.what()));
212
33
        }
213
5.88k
        return Status::OK();
214
6.20k
    }
215
216
private:
217
    template <typename JsonValue>
218
11.7k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
11.7k
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
11.7k
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
11.7k
        return Status::OK();
226
11.7k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand5valueEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
218
11.7k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
11.7k
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
11.7k
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
11.7k
        return Status::OK();
226
11.7k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand8documentEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
218
58
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
58
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
58
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
58
        return Status::OK();
226
58
    }
227
228
227k
    static Status parse_number(simdjson::ondemand::value& value, JsonbWriter& writer) {
229
227k
        simdjson::ondemand::number num;
230
227k
        const auto result = value.get_number().get(num);
231
227k
        const auto raw_token = trim_json_whitespace(value.raw_json_token());
232
227k
        if (!parse_number_success(result, raw_token)) {
233
4
            return Status::InvalidArgument(
234
4
                    fmt::format("simdjson get_number failed: {}", simdjson::error_message(result)));
235
4
        }
236
227k
        return write_number(num, value.get_number_type(), raw_token, writer);
237
227k
    }
238
239
    // parse json, recursively if necessary, by simdjson
240
    //  and serialize to binary format by writer
241
570k
    static Status parse(simdjson::ondemand::value value, JsonbWriter& writer) {
242
570k
        switch (value.type()) {
243
11.7k
        case simdjson::ondemand::json_type::null: {
244
11.7k
            return write_null(value, writer);
245
0
        }
246
20.5k
        case simdjson::ondemand::json_type::boolean: {
247
20.5k
            if (writer.writeBool(value.get_bool()) == 0) {
248
0
                return Status::InvalidArgument("writeBool failed");
249
0
            }
250
20.5k
            break;
251
20.5k
        }
252
250k
        case simdjson::ondemand::json_type::string: {
253
250k
            RETURN_IF_ERROR(write_string(value.get_string(), writer));
254
250k
            break;
255
250k
        }
256
250k
        case simdjson::ondemand::json_type::number: {
257
227k
            return parse_number(value, writer);
258
250k
        }
259
7.45k
        case simdjson::ondemand::json_type::object: {
260
7.45k
            if (!writer.writeStartObject()) {
261
0
                return Status::InvalidArgument("writeStartObject failed");
262
0
            }
263
264
11.2k
            for (auto kv : value.get_object()) {
265
11.2k
                std::string_view key;
266
11.2k
                simdjson::error_code e = kv.unescaped_key().get(key);
267
11.2k
                if (e != simdjson::SUCCESS) {
268
2
                    return Status::InvalidArgument(fmt::format("simdjson get key failed: {}", e));
269
2
                }
270
271
                // write key
272
11.2k
                if (key.size() > std::numeric_limits<uint8_t>::max()) {
273
268
                    return Status::InvalidArgument("key size exceeds max limit: {} , {}",
274
268
                                                   key.size(), std::numeric_limits<uint8_t>::max());
275
268
                }
276
10.9k
                if (!writer.writeKey(key.data(), (uint8_t)key.size())) {
277
0
                    return Status::InvalidArgument("writeKey failed : {}", key);
278
0
                }
279
280
                // parse object value
281
10.9k
                RETURN_IF_ERROR(parse(kv.value(), writer));
282
10.9k
            }
283
284
7.09k
            if (!writer.writeEndObject()) {
285
0
                return Status::InvalidArgument("writeEndObject failed");
286
0
            }
287
288
7.09k
            break;
289
7.09k
        }
290
51.8k
        case simdjson::ondemand::json_type::array: {
291
51.8k
            if (!writer.writeStartArray()) {
292
0
                return Status::InvalidArgument("writeStartArray failed");
293
0
            }
294
295
553k
            for (auto elem : value.get_array()) {
296
                // parse array element
297
553k
                RETURN_IF_ERROR(parse(elem.value(), writer));
298
553k
            }
299
300
51.6k
            if (!writer.writeEndArray()) {
301
0
                return Status::InvalidArgument("writeEndArray failed");
302
0
            }
303
51.6k
            break;
304
51.6k
        }
305
51.6k
        default: {
306
0
            return Status::InvalidArgument("unknown value type: ");
307
51.6k
        }
308
309
570k
        } // end of switch
310
329k
        return Status::OK();
311
570k
    }
312
313
    static Status write_floating_number(double number, std::string_view raw_string,
314
115k
                                        JsonbWriter& writer) {
315
        // When a double exceeds the precision that can be represented by a double type in
316
        // simdjson, it gets converted to 0. The correct approach is to truncate the value instead.
317
115k
        if (number == 0) {
318
13.7k
            StringParser::ParseResult result;
319
13.7k
            number = StringParser::string_to_float<double>(raw_string.data(), raw_string.size(),
320
13.7k
                                                           &result);
321
13.7k
            if (result != StringParser::PARSE_SUCCESS) {
322
0
                return Status::InvalidArgument("invalid number, raw string is: " +
323
0
                                               std::string(raw_string));
324
0
            }
325
13.7k
        }
326
115k
        if (writer.writeDouble(number) == 0) {
327
0
            return Status::InvalidArgument("writeDouble failed");
328
0
        }
329
115k
        return Status::OK();
330
115k
    }
331
332
5
    static Status write_big_integer(std::string_view raw_string, JsonbWriter& writer) {
333
5
        StringParser::ParseResult result;
334
5
        auto value = StringParser::string_to_int<int128_t>(raw_string.data(), raw_string.size(),
335
5
                                                           &result);
336
5
        if (result == StringParser::PARSE_SUCCESS) {
337
3
            if (!writer.writeInt128(value)) {
338
0
                return Status::InvalidArgument("writeInt128 failed");
339
0
            }
340
3
            return Status::OK();
341
3
        }
342
343
        // JSON text can represent integers beyond int128. Preserve the existing fallback to
344
        // double, even though the conversion may lose precision.
345
2
        double double_value = StringParser::string_to_float<double>(raw_string.data(),
346
2
                                                                    raw_string.size(), &result);
347
2
        if (result != StringParser::PARSE_SUCCESS) {
348
0
            return Status::InvalidArgument("invalid number, raw string is: " +
349
0
                                           std::string(raw_string));
350
0
        }
351
2
        if (!writer.writeDouble(double_value)) {
352
0
            return Status::InvalidArgument("writeDouble failed");
353
0
        }
354
2
        return Status::OK();
355
2
    }
356
357
250k
    static Status write_string(std::string_view str, JsonbWriter& writer) {
358
        // start writing string
359
250k
        if (!writer.writeStartString()) {
360
0
            return Status::InvalidArgument("writeStartString failed");
361
0
        }
362
363
        // write string
364
250k
        if (str.size() > 0) {
365
239k
            if (writer.writeString(str.data(), str.size()) == 0) {
366
0
                return Status::InvalidArgument("writeString failed");
367
0
            }
368
239k
        }
369
370
        // end writing string
371
250k
        if (!writer.writeEndString()) {
372
0
            return Status::InvalidArgument("writeEndString failed");
373
0
        }
374
250k
        return Status::OK();
375
250k
    }
376
377
    static Status write_number(simdjson::ondemand::number num,
378
                               simdjson ::ondemand::number_type num_type,
379
228k
                               std::string_view raw_string, JsonbWriter& writer) {
380
        // The simdjson library supports four types of numbers:
381
        // 1. floating_point_number: A binary64 number, which will be converted to jsonb's double type.
382
        // 2. signed_integer: A signed integer that fits in a 64-bit word using two's complement.
383
        // 3. unsigned_integer: A positive integer larger or equal to 1<<63.
384
        //    For these two integer types, we will convert them to jsonb's int8/int16/int32/int64/int128 types according to the specific value.
385
        // 4. big_integer: An integer that does not fit in a 64-bit word.
386
        //    For this type, simdjson cannot handle it directly. We first try to convert it to jsonb's int128 type.
387
        //    If conversion fails, we attempt to convert it to a double type.
388
        //    If conversion to double also fails, an error is returned.
389
390
228k
        switch (num_type) {
391
115k
        case simdjson::ondemand::number_type::floating_point_number: {
392
115k
            return write_floating_number(num.get_double(), raw_string, writer);
393
0
        }
394
112k
        case simdjson::ondemand::number_type::signed_integer:
395
112k
        case simdjson::ondemand::number_type::unsigned_integer: {
396
112k
            int128_t val = num.is_int64() ? (int128_t)num.get_int64() : (int128_t)num.get_uint64();
397
112k
            bool success = false;
398
112k
            if (val >= std::numeric_limits<int8_t>::min() &&
399
112k
                val <= std::numeric_limits<int8_t>::max()) {
400
51.5k
                success = writer.writeInt8((int8_t)val);
401
61.3k
            } else if (val >= std::numeric_limits<int16_t>::min() &&
402
61.3k
                       val <= std::numeric_limits<int16_t>::max()) {
403
353
                success = writer.writeInt16((int16_t)val);
404
61.0k
            } else if (val >= std::numeric_limits<int32_t>::min() &&
405
61.0k
                       val <= std::numeric_limits<int32_t>::max()) {
406
30
                success = writer.writeInt32((int32_t)val);
407
60.9k
            } else if (val >= std::numeric_limits<int64_t>::min() &&
408
60.9k
                       val <= std::numeric_limits<int64_t>::max()) {
409
60.9k
                success = writer.writeInt64((int64_t)val);
410
60.9k
            } else { // INT128
411
12
                success = writer.writeInt128(val);
412
12
            }
413
414
112k
            if (!success) {
415
0
                return Status::InvalidArgument("writeInt failed");
416
0
            }
417
112k
            return Status::OK();
418
112k
        }
419
5
        case simdjson::ondemand::number_type::big_integer: {
420
5
            return write_big_integer(raw_string, writer);
421
112k
        }
422
228k
        }
423
0
        return Status::InvalidArgument("unknown number type");
424
228k
    }
425
};
426
} // namespace doris