mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
Json_writer had two separate code paths: add_unquoted_str() for numbers/bool/null, and add_escaped_str() which added the surrounding quotes itself for strings. Single_line_formatting_helper, which buffers consecutive array/object elements to decide if they fit on one line, assumed only strings could ever be buffered and always wrapped the flushed values in quotes. As a result, arrays of numbers (e.g. "depends_on_map_bits", "rec_per_key") were incorrectly rendered with their elements quoted as strings. Unify both paths into add_escaped_quoted_str(): the caller now hands over bytes that are already in their final on-the-wire form. String escaping (json_escape_to_string) writes its own surrounding quotes, while numbers/bool/null are passed through unquoted, so the one-line helper just concatenates the buffered payloads on flush instead of adding quotes itself. A DBUG_ASSERT in add_escaped_quoted_str() now checks that every payload is already a quoted string or a bare number/bool/null token, so a caller that violates the contract trips an assertion in debug builds instead of silently producing invalid JSON. Also: - Fix Json_writer_array::add(ulonglong)/(size_t), which went through add_ll() with a cast to longlong and corrupted large unsigned values (e.g. ULLONG_MAX); route them through add_ull() instead. - Fix mysql-test/include/opt_context_schema.inc: "subquery_runs" was nested inside the preceding object instead of being a sibling member, and "rec_per_key" items are now declared as "number" to match the corrected output. - Update recorded .result files for opt_trace, opt_context_*, and subselect_mat_analyze_json to reflect numbers/booleans no longer being quoted inside JSON arrays. - Extend unittest/sql/my_json_writer-t.cc with coverage for arrays of primitives: plain integers, mixed types, sizes, multi-line arrays, values flushed before a nested object, strings that were already escaped by the one-line buffer, and invalid utf8mb4 input through add_str(). - Rewrite the "multi-line array of integers" test to actually overflow the one-line buffer (9 seven-digit numbers instead of 7), since the previous version fit on one line and never exercised the element-per-line flush path it claimed to test. - Drop the now-redundant single-argument add_escaped_quoted_str() overload; all call sites already know their length. - Update json_escape_to_string()'s doc comments (my_json_writer.h, sql_json_lib.h) to state that it quotes its output, not just escapes it.
391 lines
10 KiB
C++
391 lines
10 KiB
C++
/*
|
|
Copyright (c) 2025, MariaDB
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335
|
|
USA */
|
|
|
|
#ifndef SQL_JSON_LIB
|
|
#define SQL_JSON_LIB
|
|
|
|
#include "json_lib.h"
|
|
#include "sql_string.h"
|
|
#include "mysqld.h" /* system_charset_info */
|
|
|
|
/*
|
|
A syntax sugar interface to json_string_t
|
|
*/
|
|
class Json_string
|
|
{
|
|
json_string_t str;
|
|
|
|
public:
|
|
explicit Json_string(const char *name)
|
|
{
|
|
json_string_set_str(&str, (const uchar *) name,
|
|
(const uchar *) name + strlen(name));
|
|
json_string_set_cs(&str, system_charset_info);
|
|
}
|
|
json_string_t *get() { return &str; }
|
|
};
|
|
|
|
/*
|
|
This [partially] saves the JSON parser state and then can rollback the parser
|
|
to it.
|
|
The goal of this is to be able to make multiple json_key_matches() calls:
|
|
Json_saved_parser_state save(je);
|
|
if (json_key_matches(je, KEY_NAME_1)) {
|
|
...
|
|
return;
|
|
}
|
|
save.restore_to(je);
|
|
if (json_key_matches(je, KEY_NAME_2)) {
|
|
...
|
|
}
|
|
This allows one to parse JSON objects where [optional] members come in any
|
|
order.
|
|
*/
|
|
class Json_saved_parser_state
|
|
{
|
|
const uchar *c_str;
|
|
my_wc_t c_next;
|
|
int state;
|
|
|
|
public:
|
|
explicit Json_saved_parser_state(const json_engine_t *je)
|
|
: c_str(je->s.c_str), c_next(je->s.c_next), state(je->state)
|
|
{
|
|
}
|
|
void restore_to(json_engine_t *je)
|
|
{
|
|
je->s.c_str= c_str;
|
|
je->s.c_next= c_next;
|
|
je->state= state;
|
|
}
|
|
};
|
|
|
|
/*
|
|
@brief
|
|
Un-escape a JSON string and save it into *out.
|
|
*/
|
|
bool json_unescape_to_string(const char *val, int val_len, String *out);
|
|
|
|
/*
|
|
@brief
|
|
Escape and quote a JSON string and save it into *out.
|
|
*/
|
|
int json_escape_to_string(const String *str, String *out);
|
|
|
|
namespace json_reader
|
|
{
|
|
class Read_value;
|
|
};
|
|
|
|
/*
|
|
Description of a JSON object member that is to be read by json_read_object().
|
|
Intended usage:
|
|
|
|
char *var1;
|
|
int var2;
|
|
Read_named_member memb[]= {
|
|
{"member1", Read_string(thd->mem_root, &var1), false},
|
|
{"member2", Read_double(&var2), false},
|
|
{NULL, Read_double(NULL), true}
|
|
};
|
|
json_read_object(je, memb, err);
|
|
*/
|
|
|
|
class Read_named_member
|
|
{
|
|
public:
|
|
const char *name; /* JSON object name */
|
|
/*
|
|
Reader object holding the datatype and place for the value.
|
|
It's an lvalue reference so we can have both inherited classes
|
|
and refer to unnamed objects on the stack.
|
|
*/
|
|
json_reader::Read_value &&value;
|
|
|
|
const bool is_optional; /* Can this member be omitted in JSON? */
|
|
|
|
bool value_assigned= false; /* Filled and checked by json_read_object() */
|
|
};
|
|
|
|
/* Read an object from JSON according to description in *members */
|
|
int json_read_object(json_engine_t *je, Read_named_member *members,
|
|
String *err_buf);
|
|
|
|
namespace json_reader
|
|
{ /* Things to use with Read_named_member */
|
|
|
|
bool read_string(MEM_ROOT *mem_root, json_engine_t *je, const char *read_elem_key,
|
|
String *err_buf, char *&value);
|
|
|
|
bool read_double(json_engine_t *je, const char *read_elem_key, String *err_buf,
|
|
double &value);
|
|
bool read_ha_rows_and_check_limit(json_engine_t *je, const char *read_elem_key,
|
|
String *err_buf, ha_rows &value,
|
|
ha_rows LIMIT_VAL,
|
|
const char *limit_val_type,
|
|
bool unescape_required);
|
|
|
|
/*
|
|
Interface to read a value with value_name from Json,
|
|
while in the process of parsing the Json document
|
|
*/
|
|
class Read_value
|
|
{
|
|
public:
|
|
virtual bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf)= 0;
|
|
virtual ~Read_value() {};
|
|
};
|
|
|
|
class Read_string : public Read_value
|
|
{
|
|
char **ptr;
|
|
MEM_ROOT *mem_root; /* The string will be allocated on thd->mem_root */
|
|
|
|
public:
|
|
Read_string(MEM_ROOT *mem_root_arg, char **ptr_arg) :
|
|
ptr(ptr_arg), mem_root(mem_root_arg)
|
|
{}
|
|
bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf) override
|
|
{
|
|
return read_string(mem_root, je, value_name, err_buf, *ptr);
|
|
}
|
|
};
|
|
|
|
class Read_double : public Read_value
|
|
{
|
|
double *ptr;
|
|
|
|
public:
|
|
Read_double(double *ptr_arg) : ptr(ptr_arg) {}
|
|
bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf) override
|
|
{
|
|
return read_double(je, value_name, err_buf, *ptr);
|
|
}
|
|
};
|
|
|
|
template <typename T, ha_rows MAX_VALUE>
|
|
class Read_non_neg_integer : public Read_value
|
|
{
|
|
T *ptr;
|
|
|
|
public:
|
|
Read_non_neg_integer(T *ptr_arg) : ptr(ptr_arg) {}
|
|
bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf) override
|
|
{
|
|
ha_rows temp_val;
|
|
const char *type= "";
|
|
|
|
switch (MAX_VALUE)
|
|
{
|
|
case 1:
|
|
type= "boolean";
|
|
break;
|
|
case UINT_MAX:
|
|
type= "unsigned int";
|
|
break;
|
|
case LONGLONG_MAX:
|
|
type= "longlong";
|
|
break;
|
|
case ULONGLONG_MAX:
|
|
type= "unsigned longlong";
|
|
break;
|
|
default:
|
|
err_buf->append(STRING_WITH_LEN("wrong MAX_VALUE provided i.e.: "));
|
|
err_buf->q_append_int64(MAX_VALUE);
|
|
return 1;
|
|
}
|
|
|
|
if (read_ha_rows_and_check_limit(je, value_name, err_buf, temp_val,
|
|
MAX_VALUE, type, false))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
switch (MAX_VALUE)
|
|
{
|
|
case 1:
|
|
*ptr= temp_val == 1;
|
|
break;
|
|
case UINT_MAX:
|
|
case LONGLONG_MAX:
|
|
case ULONGLONG_MAX:
|
|
*ptr= (T) temp_val;
|
|
default:;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Read a JSON object into a value of type T.
|
|
|
|
Object construction is done in reader_func, which is expected to
|
|
constcut object T and invoke json_read_object() for it.
|
|
*/
|
|
template <class T>
|
|
class Read_object : public Read_value
|
|
{
|
|
MEM_ROOT *mem_root;
|
|
T **ptr;
|
|
using reader_func_t= T* (*) (MEM_ROOT *mem_root,
|
|
json_engine_t *je,
|
|
String *err_buf);
|
|
reader_func_t reader_func;
|
|
|
|
public:
|
|
Read_object(MEM_ROOT *mem_arg, T **ptr_arg, reader_func_t reader_arg) :
|
|
mem_root(mem_arg), ptr(ptr_arg), reader_func(reader_arg) {}
|
|
bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf) override
|
|
{
|
|
*ptr = reader_func(mem_root, je, err_buf);
|
|
/* NULL return value means an error */
|
|
return (*ptr == NULL);
|
|
}
|
|
};
|
|
|
|
/*
|
|
Extends the Read_value interface to read an array of elements.
|
|
|
|
This class will just start reading the JSON array.
|
|
Reading of array members is done by descendant classes in read_container().
|
|
*/
|
|
class Read_array : public Read_value
|
|
{
|
|
int read_array_start(json_engine_t *je, const char *value_name,
|
|
String *err_buf)
|
|
{
|
|
if (json_scan_next(je) || je->state != JST_ARRAY_START)
|
|
{
|
|
err_buf->append(STRING_WITH_LEN("error reading "));
|
|
err_buf->append(value_name, strlen(value_name));
|
|
err_buf->append(STRING_WITH_LEN(" value"));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
// here rc == -1 means we've caught array_end...
|
|
int after_read(int rc) { return rc > 0; }
|
|
|
|
public:
|
|
bool read_value(json_engine_t *je, const char *value_name,
|
|
String *err_buf) override
|
|
{
|
|
int rc= read_array_start(je, value_name, err_buf);
|
|
if (rc <= 0)
|
|
rc= read_container(je, value_name, err_buf);
|
|
return after_read(rc);
|
|
}
|
|
virtual int read_container(json_engine_t *je, const char *name,
|
|
String *err_buf)= 0;
|
|
};
|
|
|
|
|
|
class Read_array_of_strings : public Read_array
|
|
{
|
|
MEM_ROOT *mem_root;
|
|
List<char> *strings;
|
|
|
|
public:
|
|
Read_array_of_strings(MEM_ROOT *mem_root_arg, List<char> *ranges_arg)
|
|
: mem_root(mem_root_arg), strings(ranges_arg)
|
|
{}
|
|
int read_container(json_engine_t *je, const char *name, String *err_buf)
|
|
override
|
|
{
|
|
if (json_scan_next(je))
|
|
return 1;
|
|
|
|
while (je->state != JST_ARRAY_END)
|
|
{
|
|
char *value;
|
|
if (read_string(mem_root, je, name, err_buf, value))
|
|
return 1;
|
|
|
|
strings->push_back(value, mem_root);
|
|
if (json_scan_next(je))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Parse a JSON array of objects info a List<T>.
|
|
The constructor accepts reader_func argument which reads one JSON object
|
|
into an object of type T.
|
|
*/
|
|
|
|
template <class T>
|
|
class Read_object_array : public Read_array
|
|
{
|
|
MEM_ROOT *mem_root;
|
|
List<T> *list_ctx;
|
|
|
|
/*
|
|
A function to read individual array element. It's an object of type T.
|
|
*/
|
|
using reader_func_t= T* (*) (MEM_ROOT *mem_root,
|
|
json_engine_t *je,
|
|
String *err_buf);
|
|
reader_func_t reader_func;
|
|
|
|
public:
|
|
Read_object_array(MEM_ROOT *mem_root_arg, List<T> *list_arg, reader_func_t reader_arg)
|
|
: mem_root(mem_root_arg), list_ctx(list_arg), reader_func(reader_arg)
|
|
{}
|
|
|
|
int read_container(json_engine_t *je, const char *name, String *err_buf)
|
|
override
|
|
{
|
|
DBUG_ASSERT(je->state == JST_ARRAY_START);
|
|
int rc= json_scan_next(je);
|
|
|
|
while (1)
|
|
{
|
|
if (je->state == JST_ARRAY_END)
|
|
return -1; // End of array
|
|
|
|
/* We get JST_VALUE state before each element. Check and consume it */
|
|
if (je->state != JST_VALUE)
|
|
return 1; // Error
|
|
if (json_scan_next(je))
|
|
return 1;
|
|
|
|
/* Read the element */
|
|
T *elem= reader_func(mem_root, je, err_buf);
|
|
if (!elem)
|
|
return 1; // Error
|
|
if (list_ctx->push_back(elem, mem_root))
|
|
return 1;
|
|
|
|
/* Consume JST_OBJ_END that is left after reading an object */
|
|
if (je->state == JST_OBJ_END && json_scan_next(je))
|
|
return 1; // Error
|
|
}
|
|
return rc;
|
|
}
|
|
};
|
|
|
|
}; /* namespace json_reader */
|
|
|
|
#endif
|