Coverage Report

Created: 2026-09-25 19:11

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>
65
#include <string_view>
66
67
#include "common/status.h"
68
#include "util/jsonb_document.h"
69
#include "util/jsonb_writer.h"
70
#include "util/string_parser.hpp"
71
72
namespace doris {
73
using int128_t = __int128;
74
struct JsonbParser {
75
    // parse a UTF-8 JSON string with length
76
    // will reset writer before parse
77
6.26k
    static Status parse(const char* pch, size_t len, JsonbWriter& writer) {
78
6.26k
        if (!pch || len == 0) {
79
11
            return Status::InvalidArgument("Empty JSON document");
80
11
        }
81
6.25k
        writer.reset();
82
6.25k
        try {
83
6.25k
            simdjson::ondemand::parser simdjson_parser;
84
6.25k
            simdjson::padded_string json_str {pch, len};
85
6.25k
            simdjson::ondemand::document doc = simdjson_parser.iterate(json_str);
86
87
            // simdjson process top level primitive types specially
88
            // so some repeated code here
89
6.25k
            switch (doc.type()) {
90
3.88k
            case simdjson::ondemand::json_type::object:
91
5.46k
            case simdjson::ondemand::json_type::array: {
92
5.46k
                RETURN_IF_ERROR(parse(doc.get_value(), writer));
93
5.17k
                break;
94
5.46k
            }
95
5.17k
            case simdjson::ondemand::json_type::null: {
96
56
                if (writer.writeNull() == 0) {
97
0
                    return Status::InvalidArgument("writeNull failed");
98
0
                }
99
56
                break;
100
56
            }
101
71
            case simdjson::ondemand::json_type::boolean: {
102
71
                if (writer.writeBool(doc.get_bool()) == 0) {
103
0
                    return Status::InvalidArgument("writeBool failed");
104
0
                }
105
71
                break;
106
71
            }
107
324
            case simdjson::ondemand::json_type::string: {
108
324
                RETURN_IF_ERROR(write_string(doc.get_string(), writer));
109
324
                break;
110
324
            }
111
324
            case simdjson::ondemand::json_type::number: {
112
324
                simdjson::ondemand::number num;
113
324
                simdjson::error_code res = doc.get_number().get(num);
114
324
                std::string_view token = doc.raw_json_token();
115
                // For a root number simdjson reports NUMBER_ERROR / BIGINT_ERROR before it
116
                // checks for trailing content, and the raw token stops at the next token, so
117
                // `18446744073709551616 0` would otherwise be accepted as its first token.
118
                // A root number must reach the end of the document.
119
324
                if (token.data() + token.size() != json_str.data() + json_str.size()) {
120
10
                    return Status::InvalidArgument(
121
10
                            "simdjson get_number failed: trailing content after root number "
122
10
                            "{}",
123
10
                            quote_token(token));
124
10
                }
125
314
                RETURN_IF_ERROR(write_number(res, num, token, writer));
126
284
                break;
127
314
            }
128
6.25k
            }
129
6.25k
        } catch (simdjson::simdjson_error& e) {
130
28
            return Status::InvalidArgument(fmt::format("simdjson parse exception: {}", e.what()));
131
28
        }
132
5.89k
        return Status::OK();
133
6.25k
    }
134
135
    // Error messages quote the offending input so that the bad value can be located, but
136
    // the input may be a multi-megabyte value (e.g. a malformed digit run) and tolerant
137
    // callers such as json_valid, the error-to-null variants or non-strict CAST discard the
138
    // message right away. Keep the quoted part bounded and report the full length instead.
139
62
    static std::string bounded_quote(std::string_view text) {
140
62
        constexpr size_t kMaxQuotedLen = 64;
141
62
        if (text.size() <= kMaxQuotedLen) {
142
54
            return std::string(text);
143
54
        }
144
8
        return fmt::format("{}... (truncated, {} bytes)", text.substr(0, kMaxQuotedLen),
145
8
                           text.size());
146
62
    }
147
148
private:
149
    // parse json, recursively if necessary, by simdjson
150
    //  and serialize to binary format by writer
151
570k
    static Status parse(simdjson::ondemand::value value, JsonbWriter& writer) {
152
570k
        switch (value.type()) {
153
11.7k
        case simdjson::ondemand::json_type::null: {
154
11.7k
            if (writer.writeNull() == 0) {
155
0
                return Status::InvalidArgument("writeNull failed");
156
0
            }
157
11.7k
            break;
158
11.7k
        }
159
20.5k
        case simdjson::ondemand::json_type::boolean: {
160
20.5k
            if (writer.writeBool(value.get_bool()) == 0) {
161
0
                return Status::InvalidArgument("writeBool failed");
162
0
            }
163
20.5k
            break;
164
20.5k
        }
165
250k
        case simdjson::ondemand::json_type::string: {
166
250k
            RETURN_IF_ERROR(write_string(value.get_string(), writer));
167
250k
            break;
168
250k
        }
169
250k
        case simdjson::ondemand::json_type::number: {
170
227k
            simdjson::ondemand::number num;
171
227k
            simdjson::error_code res = value.get_number().get(num);
172
227k
            RETURN_IF_ERROR(write_number(res, num, value.raw_json_token(), writer));
173
227k
            break;
174
227k
        }
175
227k
        case simdjson::ondemand::json_type::object: {
176
7.45k
            if (!writer.writeStartObject()) {
177
0
                return Status::InvalidArgument("writeStartObject failed");
178
0
            }
179
180
11.2k
            for (auto kv : value.get_object()) {
181
11.2k
                std::string_view key;
182
11.2k
                simdjson::error_code e = kv.unescaped_key().get(key);
183
11.2k
                if (e != simdjson::SUCCESS) {
184
2
                    return Status::InvalidArgument(fmt::format("simdjson get key failed: {}", e));
185
2
                }
186
187
                // write key
188
11.2k
                if (key.size() > std::numeric_limits<uint8_t>::max()) {
189
268
                    return Status::InvalidArgument("key size exceeds max limit: {} , {}",
190
268
                                                   key.size(), std::numeric_limits<uint8_t>::max());
191
268
                }
192
10.9k
                if (!writer.writeKey(key.data(), (uint8_t)key.size())) {
193
0
                    return Status::InvalidArgument("writeKey failed : {}", key);
194
0
                }
195
196
                // parse object value
197
10.9k
                RETURN_IF_ERROR(parse(kv.value(), writer));
198
10.9k
            }
199
200
7.09k
            if (!writer.writeEndObject()) {
201
0
                return Status::InvalidArgument("writeEndObject failed");
202
0
                break;
203
0
            }
204
205
7.09k
            break;
206
7.09k
        }
207
51.8k
        case simdjson::ondemand::json_type::array: {
208
51.8k
            if (!writer.writeStartArray()) {
209
0
                return Status::InvalidArgument("writeStartArray failed");
210
0
            }
211
212
553k
            for (auto elem : value.get_array()) {
213
                // parse array element
214
553k
                RETURN_IF_ERROR(parse(elem.value(), writer));
215
553k
            }
216
217
51.6k
            if (!writer.writeEndArray()) {
218
0
                return Status::InvalidArgument("writeEndArray failed");
219
0
            }
220
51.6k
            break;
221
51.6k
        }
222
51.6k
        default: {
223
0
            return Status::InvalidArgument("unknown value type: ");
224
51.6k
        }
225
226
570k
        } // end of switch
227
569k
        return Status::OK();
228
570k
    }
229
230
250k
    static Status write_string(std::string_view str, JsonbWriter& writer) {
231
        // start writing string
232
250k
        if (!writer.writeStartString()) {
233
0
            return Status::InvalidArgument("writeStartString failed");
234
0
        }
235
236
        // write string
237
250k
        if (str.size() > 0) {
238
239k
            if (writer.writeString(str.data(), str.size()) == 0) {
239
0
                return Status::InvalidArgument("writeString failed");
240
0
            }
241
239k
        }
242
243
        // end writing string
244
250k
        if (!writer.writeEndString()) {
245
0
            return Status::InvalidArgument("writeEndString failed");
246
0
        }
247
250k
        return Status::OK();
248
250k
    }
249
250
    // raw_json_token() spans up to the start of the next token, so it may end with JSON
251
    // whitespace that is not part of the number.
252
123
    static std::string_view trim_trailing_whitespace(std::string_view token) {
253
136
        while (!token.empty() && (token.back() == ' ' || token.back() == '\t' ||
254
136
                                  token.back() == '\n' || token.back() == '\r')) {
255
13
            token.remove_suffix(1);
256
13
        }
257
123
        return token;
258
123
    }
259
260
58
    static std::string quote_token(std::string_view token) {
261
58
        return bounded_quote(trim_trailing_whitespace(token));
262
58
    }
263
264
    // Matches the JSON number grammar exactly:
265
    //   -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?
266
65
    static bool is_json_number(std::string_view token) {
267
65
        size_t i = 0;
268
65
        const size_t n = token.size();
269
89
        auto skip_digits = [&]() {
270
89
            const size_t start = i;
271
4.19M
            while (i < n && token[i] >= '0' && token[i] <= '9') {
272
4.19M
                ++i;
273
4.19M
            }
274
89
            return i > start;
275
89
        };
276
65
        if (i < n && token[i] == '-') {
277
10
            ++i;
278
10
        }
279
65
        if (i < n && token[i] == '0') {
280
9
            ++i;
281
56
        } else if (!skip_digits()) {
282
2
            return false;
283
2
        }
284
63
        if (i < n && token[i] == '.') {
285
20
            ++i;
286
20
            if (!skip_digits()) {
287
9
                return false;
288
9
            }
289
20
        }
290
54
        if (i < n && (token[i] == 'e' || token[i] == 'E')) {
291
13
            ++i;
292
13
            if (i < n && (token[i] == '+' || token[i] == '-')) {
293
3
                ++i;
294
3
            }
295
13
            if (!skip_digits()) {
296
7
                return false;
297
7
            }
298
13
        }
299
47
        return i == n;
300
54
    }
301
302
    // According to https://github.com/simdjson/simdjson/pull/2139, integers that do not fit
303
    // in 64 bits can be handled by parsing the raw_json_token ourselves: simdjson returns
304
    // NUMBER_ERROR for 18446744073709551616 (one above uint64 max) and BIGINT_ERROR for
305
    // longer integers such as 18446744073709551616231231.
306
    // However NUMBER_ERROR is also what simdjson returns for malformed tokens (leading
307
    // zeros like 01, a trailing dot like 1., 1e, trailing garbage like 1x) and for values
308
    // beyond the double range. `num` carries nothing usable in any of these cases, so the
309
    // raw token is first checked against the JSON number grammar and then parsed as int128
310
    // or double.
311
    static Status write_number_from_token(simdjson::error_code res, std::string_view raw_string,
312
65
                                          JsonbWriter& writer) {
313
65
        std::string_view token = trim_trailing_whitespace(raw_string);
314
65
        if (!is_json_number(token)) {
315
41
            return Status::InvalidArgument("simdjson get_number failed: {}, raw string is: {}",
316
41
                                           simdjson::error_message(res), quote_token(token));
317
41
        }
318
319
        // StringParser::string_to_int silently truncates a fraction, so only a token made of
320
        // digits may be parsed as an integer.
321
24
        if (token.find_first_of(".eE") == std::string_view::npos) {
322
17
            StringParser::ParseResult result;
323
17
            auto val = StringParser::string_to_int<int128_t>(token.data(), token.size(), &result);
324
17
            if (result == StringParser::PARSE_SUCCESS) {
325
10
                if (!writer.writeInt128(val)) {
326
0
                    return Status::InvalidArgument("writeInt128 failed");
327
0
                }
328
10
                return Status::OK();
329
10
            }
330
17
        }
331
332
        // Either a floating point number or an integer beyond int128. Converting it to double
333
        // may lose precision, but for JSON, exchanging data as plain text between different
334
        // systems may inherently cause precision loss.
335
14
        StringParser::ParseResult result;
336
14
        double double_val =
337
14
                StringParser::string_to_float<double>(token.data(), token.size(), &result);
338
14
        if (result != StringParser::PARSE_SUCCESS || !std::isfinite(double_val)) {
339
7
            return Status::InvalidArgument("invalid number, raw string is: {}", quote_token(token));
340
7
        }
341
7
        if (!writer.writeDouble(double_val)) {
342
0
            return Status::InvalidArgument("writeDouble failed");
343
0
        }
344
7
        return Status::OK();
345
7
    }
346
347
    static Status write_number(simdjson::error_code res, simdjson::ondemand::number num,
348
228k
                               std::string_view raw_string, JsonbWriter& writer) {
349
228k
        switch (res) {
350
228k
        case simdjson::error_code::SUCCESS:
351
228k
            break;
352
52
        case simdjson::error_code::NUMBER_ERROR:
353
65
        case simdjson::error_code::BIGINT_ERROR:
354
65
            return write_number_from_token(res, raw_string, writer);
355
0
        default:
356
            // simdjson reports no other error for a number token (a root number followed by
357
            // another token is already rejected by the end-of-document check in parse()), so
358
            // anything else is reported as is.
359
0
            return Status::InvalidArgument("simdjson get_number failed: {}, raw string is: {}",
360
0
                                           simdjson::error_message(res), quote_token(raw_string));
361
228k
        }
362
363
        // On success simdjson yields one of three number types:
364
        // 1. floating_point_number: A binary64 number, which will be converted to jsonb's double type.
365
        // 2. signed_integer: A signed integer that fits in a 64-bit word using two's complement.
366
        // 3. unsigned_integer: A positive integer larger or equal to 1<<63.
367
        //    For these two integer types, we will convert them to jsonb's int8/int16/int32/int64/int128 types according to the specific value.
368
228k
        switch (num.get_number_type()) {
369
115k
        case simdjson::ondemand::number_type::floating_point_number: {
370
115k
            if (writer.writeDouble(num.get_double()) == 0) {
371
0
                return Status::InvalidArgument("writeDouble failed");
372
0
            }
373
115k
            break;
374
115k
        }
375
115k
        case simdjson::ondemand::number_type::signed_integer:
376
112k
        case simdjson::ondemand::number_type::unsigned_integer: {
377
112k
            int128_t val = num.is_int64() ? (int128_t)num.get_int64() : (int128_t)num.get_uint64();
378
112k
            bool success = false;
379
112k
            if (val >= std::numeric_limits<int8_t>::min() &&
380
112k
                val <= std::numeric_limits<int8_t>::max()) {
381
51.5k
                success = writer.writeInt8((int8_t)val);
382
61.3k
            } else if (val >= std::numeric_limits<int16_t>::min() &&
383
61.3k
                       val <= std::numeric_limits<int16_t>::max()) {
384
353
                success = writer.writeInt16((int16_t)val);
385
61.0k
            } else if (val >= std::numeric_limits<int32_t>::min() &&
386
61.0k
                       val <= std::numeric_limits<int32_t>::max()) {
387
30
                success = writer.writeInt32((int32_t)val);
388
60.9k
            } else if (val >= std::numeric_limits<int64_t>::min() &&
389
60.9k
                       val <= std::numeric_limits<int64_t>::max()) {
390
60.9k
                success = writer.writeInt64((int64_t)val);
391
60.9k
            } else { // INT128
392
13
                success = writer.writeInt128(val);
393
13
            }
394
395
112k
            if (!success) {
396
0
                return Status::InvalidArgument("writeInt failed");
397
0
            }
398
112k
            break;
399
112k
        }
400
112k
        case simdjson::ondemand::number_type::big_integer: {
401
            // simdjson never parses a big_integer successfully; integers beyond 64 bits
402
            // arrive as NUMBER_ERROR / BIGINT_ERROR and are handled by
403
            // write_number_from_token above.
404
0
            __builtin_unreachable();
405
112k
        }
406
228k
        }
407
228k
        return Status::OK();
408
228k
    }
409
};
410
} // namespace doris