用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1433 lines
48 KiB

/* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */
#include <gtest/gtest.h>
#include <cstring>
#include <memory>
#include <string>
#include "my_byteorder.h"
#include "my_inttypes.h"
#include "sql/error_handler.h"
#include "sql/json_binary.h"
#include "sql/json_dom.h"
#include "sql/sql_class.h"
#include "sql/sql_time.h"
#include "sql_string.h"
#include "unittest/gunit/benchmark.h"
#include "unittest/gunit/test_utils.h"
namespace json_binary_unittest {
using namespace json_binary;
class JsonBinaryTest : public ::testing::Test {
protected:
virtual void SetUp() { initializer.SetUp(); }
virtual void TearDown() { initializer.TearDown(); }
my_testing::Server_initializer initializer;
THD *thd() const { return initializer.thd(); }
};
/**
Get a copy of the string value represented by val.
*/
static std::string get_string(const Value &val) {
return std::string(val.get_data(), val.get_data_length());
}
static my_decimal create_decimal(double d) {
my_decimal dec;
EXPECT_EQ(E_DEC_OK, double2my_decimal(E_DEC_FATAL_ERROR, d, &dec));
return dec;
}
static Json_dom_ptr parse_json(const char *json_text) {
auto dom = Json_dom::parse(json_text, strlen(json_text), nullptr, nullptr);
EXPECT_NE(nullptr, dom);
return dom;
}
TEST_F(JsonBinaryTest, BasicTest) {
Json_dom_ptr dom = parse_json("false");
String buf;
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val1 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val1.is_valid());
EXPECT_EQ(Value::LITERAL_FALSE, val1.type());
dom = parse_json("-123");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val2 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val2.is_valid());
EXPECT_EQ(Value::INT, val2.type());
EXPECT_EQ(-123LL, val2.get_int64());
dom = parse_json("3.14");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val3 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val3.is_valid());
EXPECT_EQ(Value::DOUBLE, val3.type());
EXPECT_EQ(3.14, val3.get_double());
dom = parse_json("18446744073709551615");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val4 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val4.is_valid());
EXPECT_EQ(Value::UINT, val4.type());
EXPECT_EQ(18446744073709551615ULL, val4.get_uint64());
dom = parse_json("\"abc\"");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val5 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val5.is_valid());
EXPECT_EQ(Value::STRING, val5.type());
EXPECT_EQ("abc", get_string(val5));
dom = parse_json("[ 1, 2, 3 ]");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val6 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val6.is_valid());
EXPECT_EQ(Value::ARRAY, val6.type());
EXPECT_FALSE(val6.large_format());
EXPECT_EQ(3U, val6.element_count());
for (int i = 0; i < 3; i++) {
Value v = val6.element(i);
EXPECT_EQ(Value::INT, v.type());
EXPECT_EQ(i + 1, v.get_int64());
}
EXPECT_EQ(Value::ERROR, val6.element(3).type());
dom = parse_json("[ 1, [ \"a\", [ 3.14 ] ] ]");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
// Top-level doc is an array of size 2.
Value val7 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val7.is_valid());
EXPECT_EQ(Value::ARRAY, val7.type());
EXPECT_EQ(2U, val7.element_count());
// First element is the integer 1.
Value v7_1 = val7.element(0);
EXPECT_TRUE(v7_1.is_valid());
EXPECT_EQ(Value::INT, v7_1.type());
EXPECT_EQ(1, v7_1.get_int64());
// The second element is a nested array of size 2.
Value v7_2 = val7.element(1);
EXPECT_TRUE(v7_2.is_valid());
EXPECT_EQ(Value::ARRAY, v7_2.type());
EXPECT_EQ(2U, v7_2.element_count());
// The first element of the nested array is the string "a".
Value v7_3 = v7_2.element(0);
EXPECT_TRUE(v7_3.is_valid());
EXPECT_EQ(Value::STRING, v7_3.type());
EXPECT_EQ("a", get_string(v7_3));
// The second element of the nested array is yet another array.
Value v7_4 = v7_2.element(1);
EXPECT_TRUE(v7_4.is_valid());
EXPECT_EQ(Value::ARRAY, v7_4.type());
// The second nested array has one element, the double 3.14.
EXPECT_EQ(1U, v7_4.element_count());
Value v7_5 = v7_4.element(0);
EXPECT_TRUE(v7_5.is_valid());
EXPECT_EQ(Value::DOUBLE, v7_5.type());
EXPECT_EQ(3.14, v7_5.get_double());
dom = parse_json("{\"key\" : \"val\"}");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val8 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val8.is_valid());
EXPECT_EQ(Value::OBJECT, val8.type());
EXPECT_FALSE(val8.large_format());
EXPECT_EQ(1U, val8.element_count());
Value val8_k = val8.key(0);
EXPECT_TRUE(val8_k.is_valid());
EXPECT_EQ(Value::STRING, val8_k.type());
EXPECT_EQ("key", get_string(val8_k));
Value val8_v = val8.element(0);
EXPECT_TRUE(val8_v.is_valid());
EXPECT_EQ(Value::STRING, val8_v.type());
EXPECT_EQ("val", get_string(val8_v));
EXPECT_EQ(Value::ERROR, val8.key(1).type());
EXPECT_EQ(Value::ERROR, val8.element(1).type());
Value v8_v1 = val8.lookup("key");
EXPECT_EQ(Value::STRING, v8_v1.type());
EXPECT_TRUE(v8_v1.is_valid());
EXPECT_EQ("val", get_string(v8_v1));
dom = parse_json("{ \"a\" : \"b\", \"c\" : [ \"d\" ] }");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val9 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val9.is_valid());
EXPECT_EQ(Value::OBJECT, val9.type());
EXPECT_EQ(2U, val9.element_count());
Value v9_k1 = val9.key(0);
EXPECT_EQ(Value::STRING, v9_k1.type());
EXPECT_EQ("a", get_string(v9_k1));
Value v9_v1 = val9.element(0);
EXPECT_EQ(Value::STRING, v9_v1.type());
EXPECT_EQ("b", get_string(v9_v1));
Value v9_k2 = val9.key(1);
EXPECT_EQ(Value::STRING, v9_k2.type());
EXPECT_EQ("c", get_string(v9_k2));
Value v9_v2 = val9.element(1);
EXPECT_EQ(Value::ARRAY, v9_v2.type());
EXPECT_EQ(1U, v9_v2.element_count());
Value v9_v2_1 = v9_v2.element(0);
EXPECT_EQ(Value::STRING, v9_v2_1.type());
EXPECT_EQ("d", get_string(v9_v2_1));
EXPECT_EQ("b", get_string(val9.lookup("a")));
Value v9_c = val9.lookup("c");
EXPECT_EQ(Value::ARRAY, v9_c.type());
EXPECT_EQ(1U, v9_c.element_count());
Value v9_c1 = v9_c.element(0);
EXPECT_EQ(Value::STRING, v9_c1.type());
EXPECT_EQ("d", get_string(v9_c1));
char blob[4];
int4store(blob, 0xCAFEBABEU);
Json_opaque opaque(MYSQL_TYPE_TINY_BLOB, blob, 4);
EXPECT_FALSE(serialize(thd(), &opaque, &buf));
Value val10 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val10.is_valid());
EXPECT_EQ(Value::OPAQUE, val10.type());
EXPECT_EQ(MYSQL_TYPE_TINY_BLOB, val10.field_type());
EXPECT_EQ(4U, val10.get_data_length());
EXPECT_EQ(0xCAFEBABEU, uint4korr(val10.get_data()));
dom = parse_json("[true,false,null,0,\"0\",\"\",{},[]]");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val11 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val11.is_valid());
EXPECT_EQ(Value::ARRAY, val11.type());
EXPECT_EQ(8U, val11.element_count());
EXPECT_EQ(Value::LITERAL_TRUE, val11.element(0).type());
EXPECT_EQ(Value::LITERAL_FALSE, val11.element(1).type());
EXPECT_EQ(Value::LITERAL_NULL, val11.element(2).type());
EXPECT_EQ(Value::INT, val11.element(3).type());
EXPECT_EQ(Value::STRING, val11.element(4).type());
EXPECT_EQ(Value::STRING, val11.element(5).type());
EXPECT_EQ(Value::OBJECT, val11.element(6).type());
EXPECT_EQ(Value::ARRAY, val11.element(7).type());
EXPECT_EQ(0, val11.element(3).get_int64());
EXPECT_EQ("0", get_string(val11.element(4)));
EXPECT_EQ("", get_string(val11.element(5)));
EXPECT_EQ(0U, val11.element(6).element_count());
EXPECT_EQ(0U, val11.element(7).element_count());
dom = parse_json("{}");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val12 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val12.is_valid());
EXPECT_EQ(Value::OBJECT, val12.type());
EXPECT_EQ(0U, val12.element_count());
EXPECT_EQ(Value::ERROR, val12.lookup("").type());
EXPECT_EQ(Value::ERROR, val12.lookup("key").type());
EXPECT_FALSE(val12.lookup("no such key").is_valid());
dom = parse_json("[]");
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val13 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val13.is_valid());
EXPECT_EQ(Value::ARRAY, val13.type());
EXPECT_EQ(0U, val13.element_count());
dom = parse_json(
"{\"key1\":1, \"key2\":2, \"key1\":3, \"key1\\u0000x\":4, "
"\"key1\\u0000y\":5, \"a\":6, \"ab\":7, \"b\":8, \"\":9, "
"\"\":10}");
const std::string expected_keys[] = {"",
"a",
"b",
"ab",
"key1",
"key2",
std::string("key1\0x", 6),
std::string("key1\0y", 6)};
const int64 expected_values[] = {10, 6, 8, 7, 3, 2, 4, 5};
EXPECT_FALSE(serialize(thd(), dom.get(), &buf));
Value val14 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val14.is_valid());
EXPECT_EQ(Value::OBJECT, val14.type());
EXPECT_EQ(8U, val14.element_count());
for (size_t i = 0; i < val14.element_count(); i++) {
EXPECT_EQ(expected_keys[i], get_string(val14.key(i)));
Value val = val14.element(i);
EXPECT_EQ(Value::INT, val.type());
EXPECT_EQ(expected_values[i], val.get_int64());
Value val_lookup = val14.lookup(expected_keys[i]);
EXPECT_EQ(Value::INT, val_lookup.type());
EXPECT_EQ(expected_values[i], val_lookup.get_int64());
}
// Store a decimal.
my_decimal md = create_decimal(123.45);
EXPECT_EQ(5U, md.precision());
EXPECT_EQ(2, md.frac);
Json_decimal jd(md);
EXPECT_FALSE(serialize(thd(), &jd, &buf));
Value val15 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val15.is_valid());
EXPECT_EQ(Value::OPAQUE, val15.type());
EXPECT_EQ(MYSQL_TYPE_NEWDECIMAL, val15.field_type());
my_decimal md_out;
EXPECT_FALSE(Json_decimal::convert_from_binary(
val15.get_data(), val15.get_data_length(), &md_out));
EXPECT_EQ(5U, md_out.precision());
EXPECT_EQ(2, md_out.frac);
double d_out;
EXPECT_EQ(E_DEC_OK, my_decimal2double(E_DEC_FATAL_ERROR, &md_out, &d_out));
EXPECT_EQ(123.45, d_out);
}
TEST_F(JsonBinaryTest, EmptyDocument) {
/*
An empty binary document is interpreted as the JSON null literal.
This is a special case to handle NULL values inserted into NOT
NULL columns using INSERT IGNORE or similar mechanisms.
*/
Value val = parse_binary("", 0);
EXPECT_EQ(Value::LITERAL_NULL, val.type());
}
static MYSQL_TIME create_time() {
const char *tstr = "13:14:15.654321";
MYSQL_TIME t;
MYSQL_TIME_STATUS status;
EXPECT_FALSE(
str_to_time(&my_charset_utf8mb4_bin, tstr, strlen(tstr), &t, 0, &status));
return t;
}
static MYSQL_TIME create_date() {
const char *dstr = "20140517";
MYSQL_TIME d;
MYSQL_TIME_STATUS status;
EXPECT_FALSE(str_to_datetime(&my_charset_utf8mb4_bin, dstr, strlen(dstr), &d,
0, &status));
return d;
}
static MYSQL_TIME create_datetime() {
const char *dtstr = "2015-01-15 15:16:17.123456";
MYSQL_TIME dt;
MYSQL_TIME_STATUS status;
EXPECT_FALSE(str_to_datetime(&my_charset_utf8mb4_bin, dtstr, strlen(dtstr),
&dt, 0, &status));
return dt;
}
/*
Test storing of TIME, DATE and DATETIME.
*/
TEST_F(JsonBinaryTest, DateAndTimeTest) {
// Create an array that contains a TIME, a DATE and a DATETIME.
Json_array array;
Json_datetime tt(create_time(), MYSQL_TYPE_TIME);
Json_datetime td(create_date(), MYSQL_TYPE_DATE);
Json_datetime tdt(create_datetime(), MYSQL_TYPE_DATETIME);
array.append_clone(&tt);
array.append_clone(&td);
array.append_clone(&tdt);
// Store the array ...
String buf;
EXPECT_FALSE(serialize(thd(), &array, &buf));
// ... and read it back.
Value val = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val.is_valid());
EXPECT_EQ(Value::ARRAY, val.type());
EXPECT_EQ(3U, val.element_count());
// The first element should be the TIME "13:14:15.654321".
Value t_val = val.element(0);
EXPECT_EQ(Value::OPAQUE, t_val.type());
EXPECT_EQ(MYSQL_TYPE_TIME, t_val.field_type());
const size_t json_datetime_packed_size = Json_datetime::PACKED_SIZE;
EXPECT_EQ(json_datetime_packed_size, t_val.get_data_length());
MYSQL_TIME t_out;
Json_datetime::from_packed(t_val.get_data(), t_val.field_type(), &t_out);
EXPECT_EQ(13U, t_out.hour);
EXPECT_EQ(14U, t_out.minute);
EXPECT_EQ(15U, t_out.second);
EXPECT_EQ(654321U, t_out.second_part);
EXPECT_FALSE(t_out.neg);
EXPECT_EQ(MYSQL_TIMESTAMP_TIME, t_out.time_type);
// The second element should be the DATE "2014-05-17".
Value d_val = val.element(1);
EXPECT_EQ(Value::OPAQUE, d_val.type());
EXPECT_EQ(MYSQL_TYPE_DATE, d_val.field_type());
EXPECT_EQ(json_datetime_packed_size, d_val.get_data_length());
MYSQL_TIME d_out;
Json_datetime::from_packed(d_val.get_data(), d_val.field_type(), &d_out);
EXPECT_EQ(2014U, d_out.year);
EXPECT_EQ(5U, d_out.month);
EXPECT_EQ(17U, d_out.day);
EXPECT_FALSE(d_out.neg);
EXPECT_EQ(MYSQL_TIMESTAMP_DATE, d_out.time_type);
// The third element should be the DATETIME "2015-01-15 15:16:17.123456".
Value dt_val = val.element(2);
EXPECT_EQ(Value::OPAQUE, dt_val.type());
EXPECT_EQ(MYSQL_TYPE_DATETIME, dt_val.field_type());
EXPECT_EQ(json_datetime_packed_size, dt_val.get_data_length());
MYSQL_TIME dt_out;
Json_datetime::from_packed(dt_val.get_data(), dt_val.field_type(), &dt_out);
EXPECT_EQ(2015U, dt_out.year);
EXPECT_EQ(1U, dt_out.month);
EXPECT_EQ(15U, dt_out.day);
EXPECT_EQ(15U, dt_out.hour);
EXPECT_EQ(16U, dt_out.minute);
EXPECT_EQ(17U, dt_out.second);
EXPECT_EQ(123456U, dt_out.second_part);
EXPECT_FALSE(dt_out.neg);
EXPECT_EQ(MYSQL_TIMESTAMP_DATETIME, dt_out.time_type);
}
/*
Validate that the contents of an array are as expected. The array
should contain values that alternate between literal true, literal
false, literal null and the string "a".
*/
void validate_array_contents(const Value &array, size_t expected_size) {
EXPECT_EQ(Value::ARRAY, array.type());
EXPECT_TRUE(array.is_valid());
EXPECT_EQ(expected_size, array.element_count());
for (size_t i = 0; i < array.element_count(); i++) {
Value val = array.element(i);
EXPECT_TRUE(val.is_valid());
Value::enum_type t = val.type();
if (i % 4 == 0)
EXPECT_EQ(Value::LITERAL_TRUE, t);
else if (i % 4 == 1)
EXPECT_EQ(Value::LITERAL_FALSE, t);
else if (i % 4 == 2)
EXPECT_EQ(Value::LITERAL_NULL, t);
else {
EXPECT_EQ(Value::STRING, t);
EXPECT_EQ("a", get_string(val));
}
}
}
/*
Test some arrays and objects that exceed 64KB. Arrays and objects
are stored in a different format if more than two bytes are required
for the internal offsets.
*/
TEST_F(JsonBinaryTest, LargeDocumentTest) {
Json_array array;
Json_boolean literal_true(true);
Json_boolean literal_false(false);
Json_null literal_null;
Json_string string("a");
for (int i = 0; i < 20000; i++) {
array.append_clone(&literal_true);
array.append_clone(&literal_false);
array.append_clone(&literal_null);
array.append_clone(&string);
}
EXPECT_EQ(80000U, array.size());
String buf;
EXPECT_FALSE(serialize(thd(), &array, &buf));
Value val = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val.large_format());
{
SCOPED_TRACE("");
validate_array_contents(val, array.size());
}
/*
Extract the raw binary representation of the large array, and verify
that it is valid.
*/
String raw;
EXPECT_FALSE(val.raw_binary(thd(), &raw));
{
SCOPED_TRACE("");
validate_array_contents(parse_binary(raw.ptr(), raw.length()),
array.size());
}
Json_array array2;
array2.append_clone(&array);
array2.append_clone(&array);
EXPECT_FALSE(serialize(thd(), &array2, &buf));
Value val2 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val2.is_valid());
EXPECT_EQ(Value::ARRAY, val2.type());
EXPECT_EQ(2U, val2.element_count());
{
SCOPED_TRACE("");
validate_array_contents(val2.element(0), array.size());
}
{
SCOPED_TRACE("");
validate_array_contents(val2.element(1), array.size());
}
Json_object object;
object.add_clone("a", &array);
Json_string s_c("c");
object.add_clone("b", &s_c);
EXPECT_FALSE(serialize(thd(), &object, &buf));
Value val3 = parse_binary(buf.ptr(), buf.length());
EXPECT_TRUE(val3.is_valid());
EXPECT_TRUE(val3.large_format());
EXPECT_EQ(Value::OBJECT, val3.type());
EXPECT_EQ(2U, val3.element_count());
EXPECT_EQ("a", get_string(val3.key(0)));
{
SCOPED_TRACE("");
validate_array_contents(val3.element(0), array.size());
}
EXPECT_EQ("b", get_string(val3.key(1)));
EXPECT_EQ(Value::STRING, val3.element(1).type());
EXPECT_EQ("c", get_string(val3.element(1)));
{
SCOPED_TRACE("");
validate_array_contents(val3.lookup("a"), array.size());
}
EXPECT_EQ("c", get_string(val3.lookup("b")));
/*
Extract the raw binary representation of the large object, and verify
that it is valid.
*/
EXPECT_FALSE(val3.raw_binary(thd(), &raw));
{
SCOPED_TRACE("");
Value val_a = parse_binary(raw.ptr(), raw.length()).lookup("a");
validate_array_contents(val_a, array.size());
}
/*
Bug#23031146: INSERTING 64K SIZE RECORDS TAKE TOO MUCH TIME
If a big (>64KB) sub-document was located at a deep nesting level,
serialization used to be very slow.
*/
{
SCOPED_TRACE("");
// Wrap "array" in 50 more levels of arrays.
constexpr size_t depth = 50;
Json_array deeply_nested_array;
Json_array *current_array = &deeply_nested_array;
for (size_t i = 1; i < depth; i++) {
Json_array *a = new (std::nothrow) Json_array();
ASSERT_FALSE(current_array->append_alias(a));
current_array = a;
}
current_array->append_clone(&array);
// Serialize it. This used to take "forever".
ASSERT_FALSE(serialize(thd(), &deeply_nested_array, &buf));
// Parse the serialized DOM and verify its contents.
Value val = parse_binary(buf.ptr(), buf.length());
for (size_t i = 0; i < depth; i++) {
ASSERT_EQ(Value::ARRAY, val.type());
ASSERT_EQ(1U, val.element_count());
val = val.element(0);
}
validate_array_contents(val, array.size());
// Now test the same with object.
Json_object deeply_nested_object;
Json_object *current_object = &deeply_nested_object;
for (size_t i = 1; i < depth; i++) {
Json_object *o = new (std::nothrow) Json_object();
ASSERT_FALSE(current_object->add_alias("key", o));
current_object = o;
}
current_object->add_clone("key", &array);
ASSERT_FALSE(serialize(thd(), &deeply_nested_object, &buf));
val = parse_binary(buf.ptr(), buf.length());
for (size_t i = 0; i < depth; i++) {
ASSERT_EQ(Value::OBJECT, val.type());
ASSERT_EQ(1U, val.element_count());
ASSERT_EQ("key", get_string(val.key(0)));
val = val.element(0);
}
validate_array_contents(val, array.size());
}
}
/*
Various tests for the Value::raw_binary() function.
*/
TEST_F(JsonBinaryTest, RawBinaryTest) {
Json_array array;
Json_string as("a string");
array.append_clone(&as);
Json_int ji(-123);
array.append_clone(&ji);
Json_uint jui(42);
array.append_clone(&jui);
Json_double jd(1.5);
array.append_clone(&jd);
Json_null jn;
array.append_clone(&jn);
Json_boolean jbt(true);
array.append_clone(&jbt);
Json_boolean jbf(false);
array.append_clone(&jbf);
Json_opaque jo(MYSQL_TYPE_BLOB, "abcd", 4);
array.append_clone(&jo);
Json_object object;
object.add_clone("key", &jbt);
array.append_clone(&object);
Json_array array2;
array2.append_clone(&jbf);
array.append_clone(&array2);
String buf;
EXPECT_FALSE(json_binary::serialize(thd(), &array, &buf));
Value v1 = parse_binary(buf.ptr(), buf.length());
String raw;
EXPECT_FALSE(v1.raw_binary(thd(), &raw));
Value v1_copy = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::ARRAY, v1_copy.type());
EXPECT_EQ(array.size(), v1_copy.element_count());
EXPECT_FALSE(v1.element(0).raw_binary(thd(), &raw));
Value v1_0 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::STRING, v1_0.type());
EXPECT_EQ("a string", std::string(v1_0.get_data(), v1_0.get_data_length()));
EXPECT_FALSE(v1.element(1).raw_binary(thd(), &raw));
Value v1_1 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::INT, v1_1.type());
EXPECT_EQ(-123, v1_1.get_int64());
EXPECT_FALSE(v1.element(2).raw_binary(thd(), &raw));
Value v1_2 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::UINT, v1_2.type());
EXPECT_EQ(42U, v1_2.get_uint64());
EXPECT_FALSE(v1.element(3).raw_binary(thd(), &raw));
Value v1_3 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::DOUBLE, v1_3.type());
EXPECT_EQ(1.5, v1_3.get_double());
EXPECT_FALSE(v1.element(4).raw_binary(thd(), &raw));
Value v1_4 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::LITERAL_NULL, v1_4.type());
EXPECT_FALSE(v1.element(5).raw_binary(thd(), &raw));
Value v1_5 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::LITERAL_TRUE, v1_5.type());
EXPECT_FALSE(v1.element(6).raw_binary(thd(), &raw));
Value v1_6 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::LITERAL_FALSE, v1_6.type());
EXPECT_FALSE(v1.element(7).raw_binary(thd(), &raw));
Value v1_7 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::OPAQUE, v1_7.type());
EXPECT_EQ(MYSQL_TYPE_BLOB, v1_7.field_type());
EXPECT_EQ("abcd", std::string(v1_7.get_data(), v1_7.get_data_length()));
EXPECT_FALSE(v1.element(8).raw_binary(thd(), &raw));
Value v1_8 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::OBJECT, v1_8.type());
EXPECT_EQ(object.cardinality(), v1_8.element_count());
EXPECT_EQ(Value::LITERAL_TRUE, v1_8.lookup("key").type());
EXPECT_FALSE(v1.element(8).key(0).raw_binary(thd(), &raw));
Value v1_8_key = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::STRING, v1_8_key.type());
EXPECT_EQ("key",
std::string(v1_8_key.get_data(), v1_8_key.get_data_length()));
EXPECT_FALSE(v1.element(8).element(0).raw_binary(thd(), &raw));
Value v1_8_val = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::LITERAL_TRUE, v1_8_val.type());
EXPECT_FALSE(v1.element(9).raw_binary(thd(), &raw));
Value v1_9 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::ARRAY, v1_9.type());
EXPECT_EQ(array2.size(), v1_9.element_count());
EXPECT_EQ(Value::LITERAL_FALSE, v1_9.element(0).type());
EXPECT_FALSE(v1.element(9).element(0).raw_binary(thd(), &raw));
Value v1_9_0 = parse_binary(raw.ptr(), raw.length());
EXPECT_EQ(Value::LITERAL_FALSE, v1_9_0.type());
}
/*
Create a JSON string of the given size, serialize it as a JSON binary, and
then deserialize it and verify that we get the same string back.
*/
void serialize_deserialize_string(const THD *thd, size_t size) {
SCOPED_TRACE(testing::Message() << "size = " << size);
char *str = new char[size];
memset(str, 'a', size);
Json_string jstr(str, size);
String buf;
EXPECT_FALSE(json_binary::serialize(thd, &jstr, &buf));
Value v = parse_binary(buf.ptr(), buf.length());
EXPECT_EQ(Value::STRING, v.type());
EXPECT_EQ(size, v.get_data_length());
EXPECT_EQ(0, memcmp(str, v.get_data(), size));
delete[] str;
}
/*
Test strings of variable length. Test especially around the boundaries
where the representation of the string length changes:
- Strings of length 0-127 use 1 byte length fields.
- Strings of length 128-16383 use 2 byte length fields.
- Strings of length 16384-2097151 use 3 byte length fields.
- Strings of length 2097152-268435455 use 4 byte length fields.
- Strings of length 268435456-... use 5 byte length fields.
We probably don't have enough memory to test the last category here...
*/
TEST_F(JsonBinaryTest, StringLengthTest) {
const THD *thd = this->thd();
serialize_deserialize_string(thd, 0);
serialize_deserialize_string(thd, 1);
serialize_deserialize_string(thd, 127);
serialize_deserialize_string(thd, 128);
serialize_deserialize_string(thd, 16383);
serialize_deserialize_string(thd, 16384);
serialize_deserialize_string(thd, 2097151);
serialize_deserialize_string(thd, 2097152);
serialize_deserialize_string(thd, 3000000);
}
/**
Error handler which registers if an error has been raised. If an error is
raised, it asserts that the error is ER_INVALID_JSON_BINARY_DATA.
*/
class Invalid_binary_handler : public Internal_error_handler {
public:
Invalid_binary_handler(THD *thd)
: m_thd(thd), m_called(false), m_orig_handler(error_handler_hook) {
error_handler_hook = my_message_sql;
thd->push_internal_handler(this);
}
~Invalid_binary_handler() override {
EXPECT_EQ(this, m_thd->pop_internal_handler());
error_handler_hook = m_orig_handler;
}
bool handle_condition(THD *, uint err, const char *,
Sql_condition::enum_severity_level *,
const char *) override {
uint expected = ER_INVALID_JSON_BINARY_DATA;
EXPECT_EQ(expected, err);
m_called = true;
return true;
}
bool is_called() const { return m_called; }
private:
THD *m_thd;
bool m_called;
decltype(error_handler_hook) m_orig_handler;
};
/**
Run various operations on a corrupted binary value, to see that the
json_binary library doesn't fall over when encountering a corrupted value.
*/
static void check_corrupted_binary(THD *thd, const char *data, size_t length) {
/*
A corrupted value may still be valid, so we cannot assert on the return
value. Just exercise the code to see that nothing very bad happens.
*/
Value val = parse_binary(data, length);
val.is_valid();
{
/*
Value::get_free_space() may or may not raise an error. If there is an
error, we expect it to be ER_INVALID_JSON_BINARY_DATA.
*/
Invalid_binary_handler handler(thd);
size_t space;
bool err = val.get_free_space(thd, &space);
// If it returns true, an error should have been raised.
EXPECT_EQ(err, handler.is_called());
}
/*
Call Value::has_space() on every element if it is an array or object.
*/
if (val.type() == Value::ARRAY || val.type() == Value::OBJECT) {
for (size_t i = 0; i < val.element_count(); ++i) {
size_t offset;
val.has_space(i, 100, &offset);
}
}
}
/**
Check that a corrupted binary value doesn't upset the parser in any serious
way.
@param thd THD handle
@param dom a JSON DOM from which corrupted binary values are created
*/
static void check_corruption(THD *thd, const Json_dom *dom) {
// First create a valid binary representation of the DOM.
String buf;
EXPECT_FALSE(json_binary::serialize(thd, dom, &buf));
EXPECT_TRUE(json_binary::parse_binary(buf.ptr(), buf.length()).is_valid());
/*
Truncated values should always be detected by is_valid(). Except
if it's truncated to an empty string, since parse_binary()
interprets the empty string as the JSON null literal.
*/
for (size_t i = 1; i < buf.length() - 1; ++i) {
EXPECT_FALSE(json_binary::parse_binary(buf.ptr(), i).is_valid());
check_corrupted_binary(thd, buf.ptr(), i);
}
/*
Test various 1, 2 and 3 byte data corruptions. is_valid() may return true
or false (not all corrupted documents are ill-formed), but we should not
have any crashes or valgrind/asan warnings.
*/
for (size_t i = 0; i < buf.length(); ++i) {
String copy;
copy.append(buf);
char *data = copy.c_ptr_safe();
for (size_t j = 1; j < 3 && i + j < buf.length(); ++j) {
memset(data + i, 0x00, j);
check_corrupted_binary(thd, data, copy.length());
memset(data + i, 0x80, j);
check_corrupted_binary(thd, data, copy.length());
memset(data + i, 0xff, j);
check_corrupted_binary(thd, data, copy.length());
}
}
}
/**
Test that the parser is well-behaved when a binary value is corrupted.
*/
TEST_F(JsonBinaryTest, CorruptedBinaryTest) {
Json_array a;
a.append_alias(new (std::nothrow) Json_null);
a.append_alias(new (std::nothrow) Json_boolean(true));
a.append_alias(new (std::nothrow) Json_boolean(false));
a.append_alias(new (std::nothrow) Json_uint(0));
a.append_alias(new (std::nothrow) Json_uint(123));
a.append_alias(new (std::nothrow) Json_uint(123000));
a.append_alias(new (std::nothrow) Json_uint(123000000));
a.append_alias(new (std::nothrow) Json_int(0));
a.append_alias(new (std::nothrow) Json_int(123));
a.append_alias(new (std::nothrow) Json_int(123000));
a.append_alias(new (std::nothrow) Json_int(123000000));
a.append_alias(new (std::nothrow) Json_int(-123000000));
a.append_alias(new (std::nothrow) Json_string());
a.append_alias(new (std::nothrow) Json_string(300, 'a'));
a.append_alias(new (std::nothrow) Json_decimal(create_decimal(3.14)));
Json_object *o = new (std::nothrow) Json_object;
a.append_alias(o);
o->add_clone("a1", &a);
o->add_alias("s", new (std::nothrow) Json_opaque(MYSQL_TYPE_BLOB, 32, 'x'));
o->add_alias("d", new (std::nothrow) Json_double(3.14));
a.append_clone(&a);
o->add_clone("a2", &a);
check_corruption(thd(), &a);
for (size_t i = 0; i < a.size(); ++i) {
SCOPED_TRACE("");
check_corruption(thd(), a[i]);
}
}
/// How big is the serialized version of a Json_dom?
static size_t binary_size(const THD *thd, const Json_dom *dom) {
StringBuffer<256> buf;
EXPECT_FALSE(json_binary::serialize(thd, dom, &buf));
return buf.length();
}
/// A tuple used by SpaceNeededTest for testing json_binary::space_needed().
struct SpaceNeededTuple {
/**
Constructor for test case with different space requirements in the
large and small storage formats.
*/
SpaceNeededTuple(Json_dom *dom, bool result, size_t needed_small,
size_t needed_large)
: m_value(dom),
m_result(result),
m_needed_small(needed_small),
m_needed_large(needed_large) {}
/**
Constructor for test case with same space requirement in the large
and small storage formats.
*/
SpaceNeededTuple(Json_dom *dom, bool result, size_t needed)
: SpaceNeededTuple(dom, result, needed, needed) {}
/// The value to pass to space_needed().
Json_wrapper m_value;
/// The expected return value from the function.
bool m_result;
/// The expected bytes needed to store the value in the small storage format.
size_t m_needed_small;
/// The expected bytes needed to store the value in the large storage format.
size_t m_needed_large;
};
/// A class used for testing json_binary::space_needed().
class SpaceNeededTest : public ::testing::TestWithParam<SpaceNeededTuple> {
my_testing::Server_initializer initializer;
protected:
void SetUp() override { initializer.SetUp(); }
void TearDown() override { initializer.TearDown(); }
THD *thd() const { return initializer.thd(); }
};
/*
Define our own PrintTo because Google Test's default implementation causes
valgrind warnings for reading uninitialized memory. (It reads every byte of
the struct, but the struct contains some uninitialized bytes because of
alignment.)
*/
void PrintTo(SpaceNeededTuple const &tuple, std::ostream *os) {
*os << '{' << static_cast<uint>(tuple.m_value.type()) << ", "
<< tuple.m_result << ", " << tuple.m_needed_small << ", "
<< tuple.m_needed_large << '}';
}
/// Test json_binary::space_needed() for a given input.
TEST_P(SpaceNeededTest, SpaceNeeded) {
SpaceNeededTuple param = GetParam();
/*
If the large and small storage size differ, it must mean that the
value can be inlined in the large storage format.
*/
if (param.m_needed_small != param.m_needed_large) {
EXPECT_EQ(0U, param.m_needed_large);
}
size_t needed = 0;
if (param.m_result) {
for (bool large : {true, false}) {
Invalid_binary_handler handler(thd());
EXPECT_TRUE(space_needed(thd(), &param.m_value, large, &needed));
EXPECT_TRUE(handler.is_called());
}
return;
}
needed = 0;
EXPECT_FALSE(space_needed(thd(), &param.m_value, false, &needed));
EXPECT_EQ(param.m_needed_small, needed);
needed = 0;
EXPECT_FALSE(space_needed(thd(), &param.m_value, true, &needed));
EXPECT_EQ(param.m_needed_large, needed);
const auto dom = param.m_value.to_dom(thd());
if (param.m_needed_small > 0) {
/*
Not inlined. The size does not include the type byte, so expect
one more byte.
*/
EXPECT_EQ(param.m_needed_small + 1, binary_size(thd(), dom));
} else {
/*
Inlined in the small storage format. Find the difference in size
between an empty array and one with the value added. Expect the
size of a small value entry, which is 3 bytes (1 byte for the
type, 2 bytes for the inlined value).
*/
Json_array a;
size_t base_size = binary_size(thd(), &a);
a.append_clone(dom);
size_t full_size = binary_size(thd(), &a);
EXPECT_EQ(base_size + 3, full_size);
}
if (param.m_needed_small > 0 && param.m_needed_large == 0) {
/*
Inlined in the large storage format only. See how much space is
added. Expect the size of a large value entry, which is 5 bytes
(1 byte for the type, 4 bytes for the inlined value).
*/
Json_array a;
a.append_alias(new (std::nothrow) Json_string(64 * 1024, 'a'));
size_t base_size = binary_size(thd(), &a);
a.append_clone(dom);
size_t full_size = binary_size(thd(), &a);
EXPECT_EQ(base_size + 5, full_size);
}
}
static const SpaceNeededTuple space_needed_tuples[] = {
/*
Strings need space for the actual data and a variable length field
that holds the length of the string. Each byte in the variable
length field holds seven bits of the length value, so testing
lengths around 2^(7*N) is important.
*/
{new (std::nothrow) Json_string(""), false, 1}, // 2^0-1
{new (std::nothrow) Json_string("a"), false, 2}, // 2^0
{new (std::nothrow) Json_string(127, 'a'), false, 128}, // 2^7-1
{new (std::nothrow) Json_string(128, 'a'), false, 130}, // 2^7
{new (std::nothrow) Json_string(16383, 'a'), false, 16385}, // 2^14-1
{new (std::nothrow) Json_string(16384, 'a'), false, 16387}, // 2^14
{new (std::nothrow) Json_string(2097151, 'a'), false, 2097154}, // 2^21-1
{new (std::nothrow) Json_string(2097152, 'a'), false, 2097156}, // 2^21
// Literals are always inlined.
{new (std::nothrow) Json_null, false, 0},
{new (std::nothrow) Json_boolean(false), false, 0},
{new (std::nothrow) Json_boolean(true), false, 0},
// 16-bit integers are always inlined.
{new (std::nothrow) Json_int(0), false, 0},
{new (std::nothrow) Json_int(-1), false, 0},
{new (std::nothrow) Json_int(1), false, 0},
{new (std::nothrow) Json_int(INT_MIN16), false, 0},
{new (std::nothrow) Json_int(INT_MAX16), false, 0},
{new (std::nothrow) Json_uint(0), false, 0},
{new (std::nothrow) Json_uint(1), false, 0},
{new (std::nothrow) Json_uint(UINT_MAX16), false, 0},
// 32-bit integers are inlined only in the large storage format.
{new (std::nothrow) Json_int(INT_MIN32), false, 4, 0},
{new (std::nothrow) Json_int(INT_MIN16 - 1), false, 4, 0},
{new (std::nothrow) Json_int(INT_MAX16 + 1), false, 4, 0},
{new (std::nothrow) Json_int(INT_MAX32), false, 4, 0},
{new (std::nothrow) Json_uint(UINT_MAX16 + 1), false, 4, 0},
{new (std::nothrow) Json_uint(UINT_MAX32), false, 4, 0},
// Larger integers and doubles require 8 bytes.
{new (std::nothrow) Json_int(INT_MIN64), false, 8},
{new (std::nothrow) Json_int(static_cast<longlong>(INT_MIN32) - 1), false,
8},
{new (std::nothrow) Json_int(static_cast<longlong>(INT_MAX32) + 1), false,
8},
{new (std::nothrow) Json_int(INT_MAX64), false, 8},
{new (std::nothrow) Json_uint(static_cast<ulonglong>(UINT_MAX32) + 1),
false, 8},
{new (std::nothrow) Json_uint(0xFFFFFFFFFFFFFFFFULL), false, 8},
{new (std::nothrow) Json_double(0), false, 8},
{new (std::nothrow) Json_double(3.14), false, 8},
/*
Opaque values need space for type info (one byte), a variable
length field, and the actual data.
*/
{new (std::nothrow) Json_opaque(MYSQL_TYPE_BLOB, ""), false, 2},
{new (std::nothrow) Json_opaque(MYSQL_TYPE_BLOB, "a"), false, 3},
{new (std::nothrow) Json_opaque(MYSQL_TYPE_BLOB, 127, 'a'), false, 129},
{new (std::nothrow) Json_opaque(MYSQL_TYPE_BLOB, 128, 'a'), false, 131},
{new (std::nothrow) Json_datetime(create_time(), MYSQL_TYPE_TIME), false,
Json_datetime::PACKED_SIZE + 2},
{new (std::nothrow) Json_datetime(create_date(), MYSQL_TYPE_DATE), false,
Json_datetime::PACKED_SIZE + 2},
{new (std::nothrow) Json_datetime(create_datetime(), MYSQL_TYPE_DATETIME),
false, Json_datetime::PACKED_SIZE + 2},
{new (std::nothrow) Json_datetime(create_datetime(), MYSQL_TYPE_TIMESTAMP),
false, Json_datetime::PACKED_SIZE + 2},
{new (std::nothrow) Json_decimal(create_decimal(12.3)), false, 6},
// Arrays.
{new (std::nothrow) Json_array, false, 4},
{parse_json("[1.5]").release(), false, 15},
// Objects
{new (std::nothrow) Json_object, false, 4},
{parse_json("{\"a\":1.5}").release(), false, 20},
// Handle type == ERROR.
{nullptr, true, 0},
};
INSTANTIATE_TEST_CASE_P(JsonBinary, SpaceNeededTest,
::testing::ValuesIn(space_needed_tuples));
/**
Helper function for testing Value::has_space(). Serializes a JSON
array or JSON object and checks if has_space() reports the correct
size and offset for an element in the array or object.
*/
static void test_has_space(THD *thd, const Json_dom *container,
Value::enum_type type, size_t size, size_t element,
size_t expected_offset) {
StringBuffer<100> buf;
EXPECT_FALSE(json_binary::serialize(thd, container, &buf));
Value v1 = parse_binary(buf.ptr(), buf.length());
Value v2 = v1.element(element);
EXPECT_EQ(type, v2.type());
size_t offset = 0;
if (size > 0) {
EXPECT_TRUE(v1.has_space(element, size - 1, &offset));
EXPECT_EQ(expected_offset, offset);
}
offset = 0;
EXPECT_TRUE(v1.has_space(element, size, &offset));
EXPECT_EQ(expected_offset, offset);
offset = 0;
EXPECT_FALSE(v1.has_space(element, size + 1, &offset));
EXPECT_EQ(0U, offset);
offset = 0;
EXPECT_TRUE(v1.has_space(element, 0, &offset));
EXPECT_EQ(expected_offset, offset);
}
/**
Test Value::has_size() by inserting a value into an array or an
object and checking that has_size() returns the correct size and
offset.
*/
static void test_has_space(THD *thd, const Json_dom *dom, Value::enum_type type,
size_t size) {
{
SCOPED_TRACE("array");
Json_array a;
a.append_clone(dom);
/*
The array contains the element count (2 bytes), byte size (2
bytes) and a value entry (3 bytes) before the value.
*/
size_t expected_offset = 2 + 2 + 3;
{
SCOPED_TRACE("first element");
test_has_space(thd, &a, type, size, 0, expected_offset);
}
/*
Insert a literal at the beginning of the array. The offset
should increase by 3 (size of the value entry).
*/
expected_offset += 3;
{
SCOPED_TRACE("second element");
a.insert_alias(0, create_dom_ptr<Json_null>());
test_has_space(thd, &a, type, size, 1, expected_offset);
}
/*
Insert a double at the beginning of the array. Expect the offset
to increase by 3 (size of the value entry) + 8 (size of the
double).
*/
expected_offset += 3 + 8;
{
SCOPED_TRACE("third element");
a.insert_alias(0, create_dom_ptr<Json_double>(123.0));
test_has_space(thd, &a, type, size, 2, expected_offset);
}
/*
Insert some values at the end of the array. The offset should
increase by 3 (one value entry) per added value.
*/
expected_offset += 3;
{
SCOPED_TRACE("append literal");
a.append_alias(new (std::nothrow) Json_boolean(true));
test_has_space(thd, &a, type, size, 2, expected_offset);
}
expected_offset += 3;
{
SCOPED_TRACE("append double");
a.append_alias(new (std::nothrow) Json_double(1.23));
test_has_space(thd, &a, type, size, 2, expected_offset);
}
}
/*
Now test the same with an object.
*/
{
SCOPED_TRACE("object");
Json_object o;
o.add_clone("k", dom);
/*
The object contains the element count (2 bytes), byte size (2
bytes), a key entry (4 bytes), a value entry (3 bytes) and a key
(1 byte) before the value.
*/
size_t expected_offset = 2 + 2 + 4 + 3 + 1;
{
SCOPED_TRACE("first element");
test_has_space(thd, &o, type, size, 0, expected_offset);
}
/*
Add a literal at the beginning of the object. The offset should
increase by 4 (size of the key entry) + 3 (size of the value
entry) + 1 (size of the key).
*/
expected_offset += 4 + 3 + 1;
{
SCOPED_TRACE("second element");
o.add_alias("b", new (std::nothrow) Json_null);
test_has_space(thd, &o, type, size, 1, expected_offset);
}
/*
Add a double at the beginning of the object. Expect the offset
to increase by 4 (size of the key entry) + 3 (size of the value
entry) + 1 (size of the key) + 8 (size of the double).
*/
expected_offset += 4 + 3 + 1 + 8;
{
SCOPED_TRACE("third element");
o.add_alias("a", new (std::nothrow) Json_double(123.0));
test_has_space(thd, &o, type, size, 2, expected_offset);
}
/*
Add some values at the end of the array. The offset should
increase by 4 (one key entry) + 3 (one value entry) + 1 (one
key) per added member.
*/
expected_offset += 4 + 3 + 1;
{
SCOPED_TRACE("add literal");
o.add_alias("x", new (std::nothrow) Json_boolean(true));
test_has_space(thd, &o, type, size, 2, expected_offset);
}
expected_offset += 4 + 3 + 1;
{
SCOPED_TRACE("add double");
o.add_alias("y", new (std::nothrow) Json_double(1.23));
test_has_space(thd, &o, type, size, 2, expected_offset);
}
}
}
/**
Various tests for Value::has_space().
*/
TEST_F(JsonBinaryTest, HasSpace) {
{
SCOPED_TRACE("empty string");
Json_string jstr;
test_has_space(thd(), &jstr, Value::STRING, 1);
}
{
// Test longest possible string with 1-byte length field.
SCOPED_TRACE("string(127)");
Json_string jstr(127, 'a');
test_has_space(thd(), &jstr, Value::STRING, 128);
}
{
// Test shortest possible string with 2-byte length field.
SCOPED_TRACE("string(128)");
Json_string jstr(128, 'a');
test_has_space(thd(), &jstr, Value::STRING, 130);
}
{
SCOPED_TRACE("null literal");
Json_null jnull;
test_has_space(thd(), &jnull, Value::LITERAL_NULL, 0);
}
{
SCOPED_TRACE("true literal");
Json_boolean jtrue(true);
test_has_space(thd(), &jtrue, Value::LITERAL_TRUE, 0);
}
{
SCOPED_TRACE("false literal");
Json_boolean jfalse(false);
test_has_space(thd(), &jfalse, Value::LITERAL_FALSE, 0);
}
{
SCOPED_TRACE("inlined uint");
Json_uint u(123);
EXPECT_TRUE(u.is_16bit());
test_has_space(thd(), &u, Value::UINT, 0);
}
{
SCOPED_TRACE("32-bit uint");
Json_uint u(100000);
EXPECT_FALSE(u.is_16bit());
EXPECT_TRUE(u.is_32bit());
test_has_space(thd(), &u, Value::UINT, 4);
}
{
SCOPED_TRACE("64-bit uint");
Json_uint u(5000000000ULL);
EXPECT_FALSE(u.is_32bit());
test_has_space(thd(), &u, Value::UINT, 8);
}
{
SCOPED_TRACE("inlined int");
Json_int i(123);
EXPECT_TRUE(i.is_16bit());
test_has_space(thd(), &i, Value::INT, 0);
}
{
SCOPED_TRACE("32-bit int");
Json_int i(100000);
EXPECT_FALSE(i.is_16bit());
EXPECT_TRUE(i.is_32bit());
test_has_space(thd(), &i, Value::INT, 4);
}
{
SCOPED_TRACE("64-bit uint");
Json_int i(5000000000LL);
EXPECT_FALSE(i.is_32bit());
test_has_space(thd(), &i, Value::INT, 8);
}
{
SCOPED_TRACE("double");
Json_double d(3.14);
test_has_space(thd(), &d, Value::DOUBLE, 8);
}
{
SCOPED_TRACE("opaque");
Json_opaque o(MYSQL_TYPE_BLOB, "abc", 3);
// 1 byte for type, 1 byte for length, 3 bytes of blob data
test_has_space(thd(), &o, Value::OPAQUE, 5);
}
{
SCOPED_TRACE("empty array");
Json_array a;
/*
An empty array has two bytes for element count and two bytes for
total size in bytes.
*/
test_has_space(thd(), &a, Value::ARRAY, 4);
}
{
SCOPED_TRACE("non-empty array");
auto a = parse_json("[null]");
// Here we have an additional 3 bytes for the value entry.
test_has_space(thd(), a.get(), Value::ARRAY, 4 + 3);
}
{
SCOPED_TRACE("empty object");
Json_object o;
/*
An empty object has two bytes for element count and two bytes for
total size in bytes.
*/
test_has_space(thd(), &o, Value::OBJECT, 4);
}
{
SCOPED_TRACE("non-empty object");
Json_object o;
o.add_alias("a", new (std::nothrow) Json_null);
/*
Here we have an additional 4 bytes for the key entry, 3 bytes
for the value entry, and 1 byte for the key.
*/
test_has_space(thd(), &o, Value::OBJECT, 4 + 4 + 3 + 1);
}
}
/**
Helper function for microbenchmarks that test the performance of
json_binary::serialize().
@param dom the Json_dom to serialize
@param num_iterations the number of iterations in the test
*/
static void serialize_benchmark(const Json_dom *dom, size_t num_iterations) {
my_testing::Server_initializer initializer;
initializer.SetUp();
const THD *thd = initializer.thd();
StartBenchmarkTiming();
for (size_t i = 0; i < num_iterations; ++i) {
String buf;
EXPECT_FALSE(json_binary::serialize(thd, dom, &buf));
}
StopBenchmarkTiming();
initializer.TearDown();
}
/**
Microbenchmark which tests the performance of serializing a JSON
array with 10000 integers.
*/
static void BM_JsonBinarySerializeIntArray(size_t num_iterations) {
StopBenchmarkTiming();
Json_array array;
for (int i = 0; i < 10000; ++i)
array.append_alias(create_dom_ptr<Json_int>(i * 1000));
serialize_benchmark(&array, num_iterations);
}
BENCHMARK(BM_JsonBinarySerializeIntArray)
/**
Microbenchmark which tests the performance of serializing a JSON
array with 10000 double values.
*/
static void BM_JsonBinarySerializeDoubleArray(size_t num_iterations) {
StopBenchmarkTiming();
Json_array array;
for (int i = 0; i < 10000; ++i)
array.append_alias(create_dom_ptr<Json_double>(i * 1000));
serialize_benchmark(&array, num_iterations);
}
BENCHMARK(BM_JsonBinarySerializeDoubleArray)
/**
Microbenchmark which tests the performance of serializing a JSON
array with 10000 strings.
*/
static void BM_JsonBinarySerializeStringArray(size_t num_iterations) {
StopBenchmarkTiming();
Json_array array;
for (int i = 0; i < 10000; ++i)
array.append_alias(create_dom_ptr<Json_string>(std::to_string(i)));
serialize_benchmark(&array, num_iterations);
}
BENCHMARK(BM_JsonBinarySerializeStringArray)
} // namespace json_binary_unittest