Coverage Report

Created: 2026-09-28 10:29

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
2.53M
    static bool is_json_whitespace(char c) {
75
2.53M
        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
76
2.53M
    }
77
78
1.27M
    static std::string_view trim_json_whitespace(std::string_view token) {
79
1.27M
        while (!token.empty() && is_json_whitespace(token.front())) {
80
4
            token.remove_prefix(1);
81
4
        }
82
1.27M
        while (!token.empty() && is_json_whitespace(token.back())) {
83
6
            token.remove_suffix(1);
84
6
        }
85
1.27M
        return token;
86
1.27M
    }
87
88
115
    static bool is_valid_json_number(std::string_view token) {
89
115
        size_t pos = 0;
90
115
        if (pos < token.size() && token[pos] == '-') {
91
10
            ++pos;
92
10
        }
93
115
        if (pos == token.size()) {
94
0
            return false;
95
0
        }
96
97
115
        if (token[pos] == '0') {
98
12
            ++pos;
99
103
        } else if (token[pos] >= '1' && token[pos] <= '9') {
100
1.43k
            do {
101
1.43k
                ++pos;
102
1.43k
            } while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9');
103
103
        } else {
104
0
            return false;
105
0
        }
106
107
115
        if (pos < token.size() && token[pos] == '.') {
108
43
            ++pos;
109
43
            const size_t fraction_start = pos;
110
51
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
111
8
                ++pos;
112
8
            }
113
43
            if (pos == fraction_start) {
114
37
                return false;
115
37
            }
116
43
        }
117
118
78
        if (pos < token.size() && (token[pos] == 'e' || token[pos] == 'E')) {
119
12
            ++pos;
120
12
            if (pos < token.size() && (token[pos] == '+' || token[pos] == '-')) {
121
2
                ++pos;
122
2
            }
123
12
            const size_t exponent_start = pos;
124
40
            while (pos < token.size() && token[pos] >= '0' && token[pos] <= '9') {
125
28
                ++pos;
126
28
            }
127
12
            if (pos == exponent_start) {
128
4
                return false;
129
4
            }
130
12
        }
131
132
74
        return pos == token.size();
133
78
    }
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
1.27M
    static bool parse_number_success(simdjson::error_code error_code, std::string_view raw_token) {
141
1.27M
        if (error_code == simdjson::error_code::SUCCESS) {
142
1.27M
            return true;
143
1.27M
        }
144
18.4E
        if (error_code != simdjson::error_code::NUMBER_ERROR &&
145
18.4E
            error_code != simdjson::error_code::BIGINT_ERROR) {
146
0
            return false;
147
0
        }
148
18.4E
        return is_valid_json_number(raw_token);
149
18.4E
    }
150
151
public:
152
    // parse a UTF-8 JSON string with length
153
    // will reset writer before parse
154
812k
    static Status parse(const char* pch, size_t len, JsonbWriter& writer) {
155
812k
        if (!pch || len == 0) {
156
32
            return Status::InvalidArgument("Empty JSON document");
157
32
        }
158
812k
        writer.reset();
159
812k
        try {
160
812k
            simdjson::ondemand::parser simdjson_parser;
161
812k
            simdjson::padded_string json_str {pch, len};
162
812k
            simdjson::ondemand::document doc = simdjson_parser.iterate(json_str);
163
812k
            bool root_number_validated_from_raw_token = false;
164
165
            // simdjson process top level primitive types specially
166
            // so some repeated code here
167
812k
            switch (doc.type()) {
168
805k
            case simdjson::ondemand::json_type::object:
169
817k
            case simdjson::ondemand::json_type::array: {
170
817k
                RETURN_IF_ERROR(parse(doc.get_value(), writer));
171
816k
                break;
172
817k
            }
173
816k
            case simdjson::ondemand::json_type::null: {
174
189
                RETURN_IF_ERROR(write_null(doc, writer));
175
189
                break;
176
189
            }
177
207
            case simdjson::ondemand::json_type::boolean: {
178
207
                if (writer.writeBool(doc.get_bool()) == 0) {
179
0
                    return Status::InvalidArgument("writeBool failed");
180
0
                }
181
207
                break;
182
207
            }
183
685
            case simdjson::ondemand::json_type::string: {
184
685
                RETURN_IF_ERROR(write_string(doc.get_string(), writer));
185
685
                break;
186
685
            }
187
872
            case simdjson::ondemand::json_type::number: {
188
872
                simdjson::ondemand::number num;
189
872
                simdjson::error_code res = doc.get_number().get(num);
190
872
                const auto raw_token = trim_json_whitespace(doc.raw_json_token());
191
872
                if (!parse_number_success(res, raw_token)) {
192
78
                    return Status::InvalidArgument(fmt::format("simdjson get_number failed: {}",
193
78
                                                               simdjson::error_message(res)));
194
78
                }
195
                // simdjson get_number() returns a number object, which can be
196
794
                RETURN_IF_ERROR(write_number(num, doc.get_number_type(), raw_token, writer));
197
794
                if (res != simdjson::error_code::SUCCESS) {
198
17
                    const auto document = trim_json_whitespace(std::string_view {pch, len});
199
17
                    if (raw_token != document) {
200
0
                        return Status::InvalidArgument("JSON document was not fully consumed");
201
0
                    }
202
17
                    root_number_validated_from_raw_token = true;
203
17
                }
204
794
                break;
205
794
            }
206
812k
            }
207
815k
            if (!root_number_validated_from_raw_token && !doc.at_end()) {
208
4
                return Status::InvalidArgument("JSON document was not fully consumed");
209
4
            }
210
815k
        } catch (simdjson::simdjson_error& e) {
211
1.21k
            return Status::InvalidArgument(fmt::format("simdjson parse exception: {}", e.what()));
212
1.21k
        }
213
823k
        return Status::OK();
214
812k
    }
215
216
private:
217
    template <typename JsonValue>
218
23.7k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
23.7k
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
23.7k
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
23.7k
        return Status::OK();
226
23.7k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand5valueEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
218
23.5k
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
23.5k
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
23.5k
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
23.5k
        return Status::OK();
226
23.5k
    }
_ZN5doris11JsonbParser10write_nullIN8simdjson8fallback8ondemand8documentEEENS_6StatusERT_RNS_12JsonbWriterTINS_14JsonbOutStreamEEE
Line
Count
Source
218
189
    static Status write_null(JsonValue& value, JsonbWriter& writer) {
219
189
        if (!value.is_null()) {
220
0
            return Status::InvalidArgument("Invalid null literal");
221
0
        }
222
189
        if (writer.writeNull() == 0) {
223
0
            return Status::InvalidArgument("writeNull failed");
224
0
        }
225
189
        return Status::OK();
226
189
    }
227
228
1.26M
    static Status parse_number(simdjson::ondemand::value& value, JsonbWriter& writer) {
229
1.26M
        simdjson::ondemand::number num;
230
1.26M
        const auto result = value.get_number().get(num);
231
1.26M
        const auto raw_token = trim_json_whitespace(value.raw_json_token());
232
1.26M
        if (!parse_number_success(result, raw_token)) {
233
8
            return Status::InvalidArgument(
234
8
                    fmt::format("simdjson get_number failed: {}", simdjson::error_message(result)));
235
8
        }
236
1.26M
        return write_number(num, value.get_number_type(), raw_token, writer);
237
1.26M
    }
238
239
    // parse json, recursively if necessary, by simdjson
240
    //  and serialize to binary format by writer
241
2.80M
    static Status parse(simdjson::ondemand::value value, JsonbWriter& writer) {
242
2.80M
        switch (value.type()) {
243
23.5k
        case simdjson::ondemand::json_type::null: {
244
23.5k
            return write_null(value, writer);
245
0
        }
246
49.8k
        case simdjson::ondemand::json_type::boolean: {
247
49.8k
            if (writer.writeBool(value.get_bool()) == 0) {
248
0
                return Status::InvalidArgument("writeBool failed");
249
0
            }
250
49.8k
            break;
251
49.8k
        }
252
549k
        case simdjson::ondemand::json_type::string: {
253
549k
            RETURN_IF_ERROR(write_string(value.get_string(), writer));
254
549k
            break;
255
549k
        }
256
1.26M
        case simdjson::ondemand::json_type::number: {
257
1.26M
            return parse_number(value, writer);
258
549k
        }
259
815k
        case simdjson::ondemand::json_type::object: {
260
815k
            if (!writer.writeStartObject()) {
261
0
                return Status::InvalidArgument("writeStartObject failed");
262
0
            }
263
264
880k
            for (auto kv : value.get_object()) {
265
880k
                std::string_view key;
266
880k
                simdjson::error_code e = kv.unescaped_key().get(key);
267
880k
                if (e != simdjson::SUCCESS) {
268
43
                    return Status::InvalidArgument(fmt::format("simdjson get key failed: {}", e));
269
43
                }
270
271
                // write key
272
880k
                if (key.size() > std::numeric_limits<uint8_t>::max()) {
273
536
                    return Status::InvalidArgument("key size exceeds max limit: {} , {}",
274
536
                                                   key.size(), std::numeric_limits<uint8_t>::max());
275
536
                }
276
880k
                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
880k
                RETURN_IF_ERROR(parse(kv.value(), writer));
282
880k
            }
283
284
815k
            if (!writer.writeEndObject()) {
285
0
                return Status::InvalidArgument("writeEndObject failed");
286
0
            }
287
288
815k
            break;
289
815k
        }
290
815k
        case simdjson::ondemand::json_type::array: {
291
114k
            if (!writer.writeStartArray()) {
292
0
                return Status::InvalidArgument("writeStartArray failed");
293
0
            }
294
295
1.13M
            for (auto elem : value.get_array()) {
296
                // parse array element
297
1.13M
                RETURN_IF_ERROR(parse(elem.value(), writer));
298
1.13M
            }
299
300
114k
            if (!writer.writeEndArray()) {
301
0
                return Status::InvalidArgument("writeEndArray failed");
302
0
            }
303
114k
            break;
304
114k
        }
305
114k
        default: {
306
0
            return Status::InvalidArgument("unknown value type: ");
307
114k
        }
308
309
2.80M
        } // end of switch
310
1.52M
        return Status::OK();
311
2.80M
    }
312
313
    static Status write_floating_number(double number, std::string_view raw_string,
314
243k
                                        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
243k
        if (number == 0) {
318
27.4k
            StringParser::ParseResult result;
319
27.4k
            number = StringParser::string_to_float<double>(raw_string.data(), raw_string.size(),
320
27.4k
                                                           &result);
321
27.4k
            if (result != StringParser::PARSE_SUCCESS) {
322
0
                return Status::InvalidArgument("invalid number, raw string is: " +
323
0
                                               std::string(raw_string));
324
0
            }
325
27.4k
        }
326
243k
        if (writer.writeDouble(number) == 0) {
327
0
            return Status::InvalidArgument("writeDouble failed");
328
0
        }
329
243k
        return Status::OK();
330
243k
    }
331
332
21
    static Status write_big_integer(std::string_view raw_string, JsonbWriter& writer) {
333
21
        StringParser::ParseResult result;
334
21
        auto value = StringParser::string_to_int<int128_t>(raw_string.data(), raw_string.size(),
335
21
                                                           &result);
336
21
        if (result == StringParser::PARSE_SUCCESS) {
337
16
            if (!writer.writeInt128(value)) {
338
0
                return Status::InvalidArgument("writeInt128 failed");
339
0
            }
340
16
            return Status::OK();
341
16
        }
342
343
        // JSON text can represent integers beyond int128. Preserve the existing fallback to
344
        // double, even though the conversion may lose precision.
345
5
        double double_value = StringParser::string_to_float<double>(raw_string.data(),
346
5
                                                                    raw_string.size(), &result);
347
5
        if (result != StringParser::PARSE_SUCCESS) {
348
0
            return Status::InvalidArgument("invalid number, raw string is: " +
349
0
                                           std::string(raw_string));
350
0
        }
351
5
        if (!writer.writeDouble(double_value)) {
352
0
            return Status::InvalidArgument("writeDouble failed");
353
0
        }
354
5
        return Status::OK();
355
5
    }
356
357
550k
    static Status write_string(std::string_view str, JsonbWriter& writer) {
358
        // start writing string
359
550k
        if (!writer.writeStartString()) {
360
0
            return Status::InvalidArgument("writeStartString failed");
361
0
        }
362
363
        // write string
364
550k
        if (str.size() > 0) {
365
528k
            if (writer.writeString(str.data(), str.size()) == 0) {
366
0
                return Status::InvalidArgument("writeString failed");
367
0
            }
368
528k
        }
369
370
        // end writing string
371
550k
        if (!writer.writeEndString()) {
372
0
            return Status::InvalidArgument("writeEndString failed");
373
0
        }
374
550k
        return Status::OK();
375
550k
    }
376
377
    static Status write_number(simdjson::ondemand::number num,
378
                               simdjson ::ondemand::number_type num_type,
379
1.27M
                               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
1.27M
        switch (num_type) {
391
243k
        case simdjson::ondemand::number_type::floating_point_number: {
392
243k
            return write_floating_number(num.get_double(), raw_string, writer);
393
0
        }
394
1.02M
        case simdjson::ondemand::number_type::signed_integer:
395
1.02M
        case simdjson::ondemand::number_type::unsigned_integer: {
396
1.02M
            int128_t val = num.is_int64() ? (int128_t)num.get_int64() : (int128_t)num.get_uint64();
397
1.02M
            bool success = false;
398
1.02M
            if (val >= std::numeric_limits<int8_t>::min() &&
399
1.02M
                val <= std::numeric_limits<int8_t>::max()) {
400
113k
                success = writer.writeInt8((int8_t)val);
401
913k
            } else if (val >= std::numeric_limits<int16_t>::min() &&
402
913k
                       val <= std::numeric_limits<int16_t>::max()) {
403
258k
                success = writer.writeInt16((int16_t)val);
404
654k
            } else if (val >= std::numeric_limits<int32_t>::min() &&
405
654k
                       val <= std::numeric_limits<int32_t>::max()) {
406
531k
                success = writer.writeInt32((int32_t)val);
407
531k
            } else if (val >= std::numeric_limits<int64_t>::min() &&
408
122k
                       val <= std::numeric_limits<int64_t>::max()) {
409
122k
                success = writer.writeInt64((int64_t)val);
410
122k
            } else { // INT128
411
305
                success = writer.writeInt128(val);
412
305
            }
413
414
1.02M
            if (!success) {
415
0
                return Status::InvalidArgument("writeInt failed");
416
0
            }
417
1.02M
            return Status::OK();
418
1.02M
        }
419
21
        case simdjson::ondemand::number_type::big_integer: {
420
21
            return write_big_integer(raw_string, writer);
421
1.02M
        }
422
1.27M
        }
423
0
        return Status::InvalidArgument("unknown number type");
424
1.27M
    }
425
};
426
} // namespace doris