summaryrefslogtreecommitdiffstats
path: root/sql/my_json_writer.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
commit3f619478f796eddbba6e39502fe941b285dd97b1 (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/my_json_writer.h
parentInitial commit. (diff)
downloadmariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz
mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/my_json_writer.h')
-rw-r--r--sql/my_json_writer.h793
1 files changed, 793 insertions, 0 deletions
diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h
new file mode 100644
index 00000000..87d1a7fa
--- /dev/null
+++ b/sql/my_json_writer.h
@@ -0,0 +1,793 @@
+/* Copyright (C) 2014 SkySQL Ab, MariaDB Corporation Ab
+
+ 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 JSON_WRITER_INCLUDED
+#define JSON_WRITER_INCLUDED
+
+#include "my_base.h"
+#include "sql_string.h"
+
+#if !defined(NDEBUG) || defined(JSON_WRITER_UNIT_TEST) || defined ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+#include <set>
+#include <stack>
+#include <string>
+#include <vector>
+#endif
+
+#ifdef JSON_WRITER_UNIT_TEST
+// Also, mock objects are defined in my_json_writer-t.cc
+#define VALIDITY_ASSERT(x) if (!(x)) this->invalid_json= true;
+#else
+#include "sql_class.h" // For class THD
+#include "log.h" // for sql_print_error
+#define VALIDITY_ASSERT(x) DBUG_ASSERT(x)
+#endif
+
+#include <type_traits>
+
+class Opt_trace_stmt;
+class Opt_trace_context;
+class Json_writer;
+
+struct TABLE;
+struct st_join_table;
+using JOIN_TAB= struct st_join_table;
+
+/*
+ Single_line_formatting_helper is used by Json_writer to do better formatting
+ of JSON documents.
+
+ The idea is to catch arrays that can be printed on one line:
+
+ arrayName : [ "boo", 123, 456 ]
+
+ and actually print them on one line. Arrrays that occupy too much space on
+ the line, or have nested members cannot be printed on one line.
+
+ We hook into JSON printing functions and try to detect the pattern. While
+ detecting the pattern, we will accumulate "boo", 123, 456 as strings.
+
+ Then,
+ - either the pattern is broken, and we print the elements out,
+ - or the pattern lasts till the end of the array, and we print the
+ array on one line.
+*/
+
+class Single_line_formatting_helper
+{
+ enum enum_state
+ {
+ INACTIVE,
+ ADD_MEMBER,
+ IN_ARRAY,
+ DISABLED
+ };
+
+ /*
+ This works like a finite automaton.
+
+ state=DISABLED means the helper is disabled - all on_XXX functions will
+ return false (which means "not handled") and do nothing.
+
+ +->-+
+ | v
+ INACTIVE ---> ADD_MEMBER ---> IN_ARRAY--->-+
+ ^ |
+ +------------------<--------------------+
+
+ For other states:
+ INACTIVE - initial state, we have nothing.
+ ADD_MEMBER - add_member() was called, the buffer has "member_name\0".
+ IN_ARRAY - start_array() was called.
+
+
+ */
+ enum enum_state state;
+ enum { MAX_LINE_LEN= 80 };
+ char buffer[80];
+
+ /* The data in the buffer is located between buffer[0] and buf_ptr */
+ char *buf_ptr;
+ uint line_len;
+
+ Json_writer *owner;
+public:
+ Single_line_formatting_helper() : state(INACTIVE), buf_ptr(buffer) {}
+
+ void init(Json_writer *owner_arg) { owner= owner_arg; }
+
+ bool on_add_member(const char *name, size_t len);
+
+ bool on_start_array();
+ bool on_end_array();
+ void on_start_object();
+ // on_end_object() is not needed.
+
+ bool on_add_str(const char *str, size_t num_bytes);
+
+ /*
+ Returns true if the helper is flushing its buffer and is probably
+ making calls back to its Json_writer. (The Json_writer uses this
+ function to avoid re-doing the processing that it has already done
+ before making a call to fmt_helper)
+ */
+ bool is_making_writer_calls() const { return state == DISABLED; }
+
+private:
+ void flush_on_one_line();
+ void disable_and_flush();
+};
+
+
+/*
+ Something that looks like class String, but has an internal limit of
+ how many bytes one can append to it.
+
+ Bytes that were truncated due to the size limitation are counted.
+*/
+
+class String_with_limit
+{
+public:
+
+ String_with_limit() : size_limit(SIZE_T_MAX), truncated_len(0)
+ {
+ str.length(0);
+ }
+
+ size_t get_truncated_bytes() const { return truncated_len; }
+ size_t get_size_limit() { return size_limit; }
+
+ void set_size_limit(size_t limit_arg)
+ {
+ // Setting size limit to be shorter than length will not have the desired
+ // effect
+ DBUG_ASSERT(str.length() < size_limit);
+ size_limit= limit_arg;
+ }
+
+ void append(const char *s, size_t size)
+ {
+ if (str.length() + size <= size_limit)
+ {
+ // Whole string can be added, just do it
+ str.append(s, size);
+ }
+ else
+ {
+ // We cannot add the whole string
+ if (str.length() < size_limit)
+ {
+ // But we can still add something
+ size_t bytes_to_add = size_limit - str.length();
+ str.append(s, bytes_to_add);
+ truncated_len += size - bytes_to_add;
+ }
+ else
+ truncated_len += size;
+ }
+ }
+
+ void append(const char *s)
+ {
+ append(s, strlen(s));
+ }
+
+ void append(char c)
+ {
+ if (str.length() + 1 > size_limit)
+ truncated_len++;
+ else
+ str.append(c);
+ }
+
+ const String *get_string() { return &str; }
+ size_t length() { return str.length(); }
+private:
+ String str;
+
+ // str must not get longer than this many bytes.
+ size_t size_limit;
+
+ // How many bytes were truncated from the string
+ size_t truncated_len;
+};
+
+/*
+ A class to write well-formed JSON documents. The documents are also formatted
+ for human readability.
+*/
+
+class Json_writer
+{
+#if !defined(NDEBUG) || defined(JSON_WRITER_UNIT_TEST)
+ /*
+ In debug mode, Json_writer will fail and assertion if one attempts to
+ produce an invalid JSON document (e.g. JSON array having named elements).
+ */
+ std::vector<bool> named_items_expectation;
+ std::stack<std::set<std::string> > named_items;
+
+ bool named_item_expected() const;
+
+ bool got_name;
+
+#ifdef JSON_WRITER_UNIT_TEST
+public:
+ // When compiled for unit test, creating invalid JSON will set this to true
+ // instead of an assertion.
+ bool invalid_json= false;
+#endif
+#endif
+
+public:
+ /* Add a member. We must be in an object. */
+ Json_writer& add_member(const char *name);
+ Json_writer& add_member(const char *name, size_t len);
+
+ /* Add atomic values */
+
+ /* Note: the add_str methods do not do escapes. Should this change? */
+ void add_str(const char* val);
+ void add_str(const char* val, size_t num_bytes);
+ void add_str(const String &str);
+ void add_str(Item *item);
+ void add_table_name(const JOIN_TAB *tab);
+ void add_table_name(const TABLE* table);
+
+ void add_ll(longlong val);
+ void add_ull(ulonglong val);
+ void add_size(longlong val);
+ void add_double(double val);
+ void add_bool(bool val);
+ void add_null();
+
+private:
+ void add_unquoted_str(const char* val);
+ void add_unquoted_str(const char* val, size_t len);
+
+ bool on_add_str(const char *str, size_t num_bytes);
+ void on_start_object();
+
+public:
+ /* Start a child object */
+ void start_object();
+ void start_array();
+
+ void end_object();
+ void end_array();
+
+ /*
+ One can set a limit of how large a JSON document should be.
+ Writes beyond that size will be counted, but will not be collected.
+ */
+ void set_size_limit(size_t mem_size) { output.set_size_limit(mem_size); }
+
+ size_t get_truncated_bytes() { return output.get_truncated_bytes(); }
+
+ Json_writer() :
+#if !defined(NDEBUG) || defined(JSON_WRITER_UNIT_TEST)
+ got_name(false),
+#endif
+ indent_level(0), document_start(true), element_started(false),
+ first_child(true)
+ {
+ fmt_helper.init(this);
+ }
+private:
+ // TODO: a stack of (name, bool is_object_or_array) elements.
+ int indent_level;
+ enum { INDENT_SIZE = 2 };
+
+ friend class Single_line_formatting_helper;
+ friend class Json_writer_nesting_guard;
+ bool document_start;
+ bool element_started;
+ bool first_child;
+
+ Single_line_formatting_helper fmt_helper;
+
+ void append_indent();
+ void start_element();
+ void start_sub_element();
+
+public:
+ String_with_limit output;
+};
+
+/* A class to add values to Json_writer_object and Json_writer_array */
+class Json_value_helper
+{
+ Json_writer* writer;
+
+public:
+ void init(Json_writer *my_writer) { writer= my_writer; }
+ void add_str(const char* val)
+ {
+ writer->add_str(val);
+ }
+ void add_str(const char* val, size_t length)
+ {
+ writer->add_str(val, length);
+ }
+ void add_str(const String &str)
+ {
+ writer->add_str(str.ptr(), str.length());
+ }
+ void add_str(const LEX_CSTRING &str)
+ {
+ writer->add_str(str.str, str.length);
+ }
+ void add_str(Item *item)
+ {
+ writer->add_str(item);
+ }
+
+ void add_ll(longlong val)
+ {
+ writer->add_ll(val);
+ }
+ void add_size(longlong val)
+ {
+ writer->add_size(val);
+ }
+ void add_double(double val)
+ {
+ writer->add_double(val);
+ }
+ void add_bool(bool val)
+ {
+ writer->add_bool(val);
+ }
+ void add_null()
+ {
+ writer->add_null();
+ }
+ void add_table_name(const JOIN_TAB *tab)
+ {
+ writer->add_table_name(tab);
+ }
+ void add_table_name(const TABLE* table)
+ {
+ writer->add_table_name(table);
+ }
+};
+
+/* A common base for Json_writer_object and Json_writer_array */
+class Json_writer_struct
+{
+ Json_writer_struct(const Json_writer_struct&)= delete;
+ Json_writer_struct& operator=(const Json_writer_struct&)= delete;
+
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ static thread_local std::vector<bool> named_items_expectation;
+#endif
+protected:
+ Json_writer* my_writer;
+ Json_value_helper context;
+ /*
+ Tells when a json_writer_struct has been closed or not
+ */
+ bool closed;
+
+ explicit Json_writer_struct(Json_writer *writer)
+ : my_writer(writer)
+ {
+ context.init(my_writer);
+ closed= false;
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ named_items_expectation.push_back(expect_named_children);
+#endif
+ }
+ explicit Json_writer_struct(THD *thd)
+ : Json_writer_struct(thd->opt_trace.get_current_json())
+ {
+ }
+
+public:
+
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ virtual ~Json_writer_struct()
+ {
+ named_items_expectation.pop_back();
+ }
+#else
+ virtual ~Json_writer_struct() = default;
+#endif
+
+ bool trace_started() const
+ {
+ return my_writer != 0;
+ }
+
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ bool named_item_expected() const
+ {
+ return named_items_expectation.size() > 1
+ && *(named_items_expectation.rbegin() + 1);
+ }
+#endif
+};
+
+
+/*
+ RAII-based class to start/end writing a JSON object into the JSON document
+
+ There is "ignore mode": one can initialize Json_writer_object with a NULL
+ Json_writer argument, and then all its calls will do nothing. This is used
+ by optimizer trace which can be enabled or disabled.
+*/
+
+class Json_writer_object : public Json_writer_struct
+{
+private:
+ void add_member(const char *name)
+ {
+ my_writer->add_member(name);
+ }
+public:
+ explicit Json_writer_object(Json_writer* writer, const char *str= nullptr)
+ : Json_writer_struct(writer)
+ {
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ DBUG_ASSERT(named_item_expected());
+#endif
+ if (unlikely(my_writer))
+ {
+ if (str)
+ my_writer->add_member(str);
+ my_writer->start_object();
+ }
+ }
+
+ explicit Json_writer_object(THD* thd, const char *str= nullptr)
+ : Json_writer_object(thd->opt_trace.get_current_json(), str)
+ {
+ }
+
+ ~Json_writer_object()
+ {
+ if (my_writer && !closed)
+ my_writer->end_object();
+ closed= TRUE;
+ }
+
+ Json_writer_object& add(const char *name, bool value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_bool(value);
+ }
+ return *this;
+ }
+
+ Json_writer_object& add(const char *name, ulonglong value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ my_writer->add_ull(value);
+ }
+ return *this;
+ }
+
+ template<class IntT,
+ typename= typename ::std::enable_if<std::is_integral<IntT>::value>::type
+ >
+ Json_writer_object& add(const char *name, IntT value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_ll(value);
+ }
+ return *this;
+ }
+
+ Json_writer_object& add(const char *name, double value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_double(value);
+ }
+ return *this;
+ }
+
+ Json_writer_object& add(const char *name, const char *value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_str(value);
+ }
+ return *this;
+ }
+ Json_writer_object& add(const char *name, const char *value, size_t num_bytes)
+ {
+ add_member(name);
+ context.add_str(value, num_bytes);
+ return *this;
+ }
+ Json_writer_object& add(const char *name, const LEX_CSTRING &value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_str(value.str, value.length);
+ }
+ return *this;
+ }
+ Json_writer_object& add(const char *name, Item *value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_str(value);
+ }
+ return *this;
+ }
+ Json_writer_object& add_null(const char*name)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member(name);
+ context.add_null();
+ }
+ return *this;
+ }
+ Json_writer_object& add_table_name(const JOIN_TAB *tab)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member("table");
+ context.add_table_name(tab);
+ }
+ return *this;
+ }
+ Json_writer_object& add_table_name(const TABLE *table)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member("table");
+ context.add_table_name(table);
+ }
+ return *this;
+ }
+ Json_writer_object& add_select_number(uint select_number)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ {
+ add_member("select_id");
+ if (unlikely(select_number == FAKE_SELECT_LEX_ID))
+ context.add_str("fake");
+ else
+ context.add_ll(static_cast<longlong>(select_number));
+ }
+ return *this;
+ }
+ void end()
+ {
+ DBUG_ASSERT(!closed);
+ if (unlikely(my_writer))
+ my_writer->end_object();
+ closed= TRUE;
+ }
+};
+
+
+/*
+ RAII-based class to start/end writing a JSON array into the JSON document
+
+ There is "ignore mode": one can initialize Json_writer_array with a NULL
+ Json_writer argument, and then all its calls will do nothing. This is used
+ by optimizer trace which can be enabled or disabled.
+*/
+
+class Json_writer_array : public Json_writer_struct
+{
+public:
+ explicit Json_writer_array(Json_writer *writer, const char *str= nullptr)
+ : Json_writer_struct(writer)
+ {
+#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS
+ DBUG_ASSERT(!named_item_expected());
+#endif
+ if (unlikely(my_writer))
+ {
+ if (str)
+ my_writer->add_member(str);
+ my_writer->start_array();
+ }
+ }
+
+ explicit Json_writer_array(THD *thd, const char *str= nullptr)
+ : Json_writer_array(thd->opt_trace.get_current_json(), str)
+ {
+ }
+
+ ~Json_writer_array()
+ {
+ if (unlikely(my_writer && !closed))
+ {
+ my_writer->end_array();
+ closed= TRUE;
+ }
+ }
+
+ void end()
+ {
+ DBUG_ASSERT(!closed);
+ if (unlikely(my_writer))
+ my_writer->end_array();
+ closed= TRUE;
+ }
+
+ Json_writer_array& add(bool value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_bool(value);
+ return *this;
+ }
+ Json_writer_array& add(ulonglong value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_ll(static_cast<longlong>(value));
+ return *this;
+ }
+ Json_writer_array& add(longlong value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_ll(value);
+ return *this;
+ }
+ Json_writer_array& add(double value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_double(value);
+ return *this;
+ }
+ #ifndef _WIN64
+ Json_writer_array& add(size_t value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_ll(static_cast<longlong>(value));
+ return *this;
+ }
+ #endif
+ Json_writer_array& add(const char *value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_str(value);
+ return *this;
+ }
+ Json_writer_array& add(const char *value, size_t num_bytes)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_str(value, num_bytes);
+ return *this;
+ }
+ Json_writer_array& add(const LEX_CSTRING &value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_str(value.str, value.length);
+ return *this;
+ }
+ Json_writer_array& add(Item *value)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_str(value);
+ return *this;
+ }
+ Json_writer_array& add_null()
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_null();
+ return *this;
+ }
+ Json_writer_array& add_table_name(const JOIN_TAB *tab)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_table_name(tab);
+ return *this;
+ }
+ Json_writer_array& add_table_name(const TABLE *table)
+ {
+ DBUG_ASSERT(!closed);
+ if (my_writer)
+ context.add_table_name(table);
+ return *this;
+ }
+};
+
+/*
+ RAII-based class to disable writing into the JSON document
+ The tracing is disabled as soon as the object is created.
+ The destuctor is called as soon as we exit the scope of the object
+ and the tracing is enabled back.
+*/
+
+class Json_writer_temp_disable
+{
+public:
+ Json_writer_temp_disable(THD *thd_arg);
+ ~Json_writer_temp_disable();
+ THD *thd;
+};
+
+/*
+ RAII-based helper class to detect incorrect use of Json_writer.
+
+ The idea is that a function typically must leave Json_writer at the same
+ identation level as it was when it was invoked. Leaving it at a different
+ level typically means we forgot to close an object or an array
+
+ So, here is a way to guard
+ void foo(Json_writer *writer)
+ {
+ Json_writer_nesting_guard(writer);
+ .. do something with writer
+
+ // at the end of the function, ~Json_writer_nesting_guard() is called
+ // and it makes sure that the nesting is the same as when the function was
+ // entered.
+ }
+*/
+
+class Json_writer_nesting_guard
+{
+#ifdef DBUG_OFF
+public:
+ Json_writer_nesting_guard(Json_writer *) {}
+#else
+ Json_writer* writer;
+ int indent_level;
+public:
+ Json_writer_nesting_guard(Json_writer *writer_arg) :
+ writer(writer_arg),
+ indent_level(writer->indent_level)
+ {}
+
+ ~Json_writer_nesting_guard()
+ {
+ DBUG_ASSERT(indent_level == writer->indent_level);
+ }
+#endif
+};
+
+#endif