/* Copyright (c) 2011, 2024, Oracle and/or its affiliates. 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 designed to work 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 either included with the program or referenced in the documentation. 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 */ #ifndef OPT_TRACE_INCLUDED #define OPT_TRACE_INCLUDED #include #include #include #include "my_compiler.h" #include "my_inttypes.h" #include "my_sqlcommand.h" // enum_sql_command #include "sql/opt_trace_context.h" // Opt_trace_context class Cost_estimate; class Item; class THD; class set_var_base; class sp_head; class sp_printable; struct CHARSET_INFO; class Table_ref; template class List; /** @file sql/opt_trace.h API for the Optimizer trace (WL#5257) */ /** @page PAGE_OPT_TRACE The Optimizer Trace @section INTRODUCTION Introduction This optimizer trace is aimed at producing output, which is readable by humans and by programs, to aid understanding of decisions and actions taken by the MySQL Optimizer. @section OUTPUT_FORMAT Output format The chosen output format is JSON (JavaScript Object Notation). In JSON there are: @li "objects" (unordered set of key-value pairs); equivalent to Python's dictionary or Perl's associative array or hash or STL's hash_map. @li "arrays" (ordered set of values); equivalent to Python's and Perl's list or STL's vector. @li "values": a value can be a string, number, boolean, null, which we all call "scalars", or be an object, array. For example (explanations after "<<" are not part of output): @verbatim { << start of top object "first_name": "Gustave", << key/value pair (value is string) "last_name": "Eiffel", << key/value pair (value is string) "born": 1832, << key/value pair (value is integer) "contributions_to": [ << key/value pair (value is array) { << 1st item of array is an object (a building) "name": "Eiffel tower", "location": Paris }, << end of 1st item of array { "name": "Liberty statue", "location": "New York" } << end of 2nd item of array ] << end of array } << end of top object @endverbatim For more details, have a look at the syntax at json.org. Note that indentation and newlines are superfluous, useful only for human-readability. Note also that there is nothing like a "named object": an object, array or value has no name; but if it is the value of a key/value pair in an enclosing, outer object, then the key can be seen as the inner object's "name". @section USER_ENABLE_TRACING How a user enables/views the trace @verbatim SET SESSION OPTIMIZER_TRACE="enabled=on"; # enable tracing ; # like SELECT, EXPLAIN SELECT, UPDATE, DELETE... SELECT * FROM information_schema.OPTIMIZER_TRACE; [ repeat last two steps at will ] SET SESSION OPTIMIZER_TRACE="enabled=off"; # disable tracing @endverbatim @c SELECT and @c EXPLAIN SELECT produce the same trace. But there are exceptions regarding subqueries because the two commands treat subqueries differently, for example in @verbatim SELECT ... WHERE x IN (subq1) AND y IN (subq2) @endverbatim SELECT terminates after executing the first subquery if the related IN predicate is false, so we won't see @c JOIN::optimize() tracing for subq2; whereas EXPLAIN SELECT analyzes all subqueries (see loop at the end of @c select_describe()). @section USER_SELECT_TRACING_STATEMENTS How a user traces only certain statements When tracing is in force, each SQL statement generates a trace; more exactly, so does any of SELECT, EXPLAIN SELECT, INSERT or REPLACE ( with VALUES or SELECT), UPDATE/DELETE and their multi-table variants, SET (unless it manipulates @@@@optimizer_trace), DO, DECLARE/CASE/IF/RETURN (stored routines language elements), CALL. If a command above is prepared and executed in separate steps, preparation and execution are separately traced. By default each new trace overwrites the previous trace. Thus, if a statement contains sub-statements (example: invokes stored procedures, stored functions, triggers), the top statement and sub-statements each generate traces, but at the execution's end only the last sub-statement's trace is visible. If the user wants to see the trace of another sub-statement, she/he can enable/disable tracing around the desired sub-statement, but this requires editing the routine's code, which may not be possible. Another solution is to use @verbatim SET optimizer_trace_offset=, optimizer_trace_limit= @endverbatim where OFFSET is a signed integer, and LIMIT is a positive integer. The effect of this SET is the following: @li all remembered traces are cleared @li a later SELECT on OPTIMIZER_TRACE returns the first LIMIT traces of the OFFSET oldest remembered traces (if OFFSET >= 0), or the first LIMIT traces of the -OFFSET newest remembered traces (if OFFSET < 0). For example, a combination of OFFSET=-1 and LIMIT=1 will make the last trace be shown (as is default), OFFSET=-2 and LIMIT=1 will make the next-to-last be shown, OFFSET=-5 and LIMIT=5 will make the last five traces be shown. Such negative OFFSET can be useful when one knows that the interesting sub-statements are the few last ones of a stored routine, like this: @verbatim SET optimizer_trace_offset=-5, optimizer_trace_limit=5; CALL stored_routine(); # more than 5 sub-statements in this routine SELECT * FROM information_schema.OPTIMIZER_TRACE; # see only last 5 traces @endverbatim On the opposite, a positive OFFSET can be useful when one knows that the interesting sub-statements are the few first ones of a stored routine. The more those two variables are accurately adjusted, the less memory is used. For example, OFFSET=0 and LIMIT=5 will use memory to remember 5 traces, so if only the three first are needed, OFFSET=0 and LIMIT=3 is better (tracing stops after the LIMITth trace, so the 4th and 5th trace are not created and don't take up memory). A stored routine may have a loop which executes many sub-statements and thus generates many traces, which would use a lot of memory; proper OFFSET and LIMIT can restrict tracing to one iteration of the loop for example. This also gains speed, as tracing a sub-statement impacts performance. If OFFSET>=0, only LIMIT traces are kept in memory. If OFFSET<0, that is not true: instead, (-OFFSET) traces are kept in memory; indeed even if LIMIT is smaller than (-OFFSET), so excludes the last statement, the last statement must still be traced because it will be inside LIMIT after executing one more statement (remember than OFFSET<0 is counted from the end: the "window" slides as more statements execute). Such memory and speed gains are the reason why optimizer_trace_offset/limit, which are restrictions at the trace producer level, are offered. They are better than using @verbatim SELECT * FROM OPTIMIZER_TRACE LIMIT OFFSET ; @endverbatim which is a restriction on the trace consumer level, which saves almost nothing. @section USER_SELECT_TRACING_FEATURES How a user traces only certain optimizer features @verbatim SET OPTIMIZER_TRACE_FEATURES="feature1=on|off,..."; @endverbatim where "feature1" is one optimizer feature. For example "greedy_search": a certain Opt_trace_array at the start of @c Optimize_table_order::choose_table_order() has a flag "GREEDY_SEARCH" passed to its constructor: this means that if the user has turned tracing of greedy search off, this array will not be written to the I_S trace, neither will any children structures. All this disabled "trace chunk" will be replaced by an ellipsis "...". @section DEV_ADDS_TRACING How a developer adds tracing to a function Check @c Opt_trace* usage in @c advance_sj_state(): @verbatim Opt_trace_array trace_choices(trace, "semijoin_strategy_choice"); @endverbatim This creates an array for key "semijoin_strategy_choice". We are going to list possible semijoin strategy choices. @verbatim Opt_trace_object trace_one_strategy(trace); @endverbatim This creates an object without key (normal, it's in an array). This object will describe one single strategy choice. @verbatim trace_one_strategy.add_alnum("strategy", "FirstMatch"); @endverbatim This adds a key/value pair to the just-created object: key is "strategy", value is "FirstMatch". This is the strategy to be described in the just-created object. @verbatim trace_one_strategy.add("cost", *current_read_time). add("records", *current_record_count); trace_one_strategy.add("chosen", (pos->sj_strategy == SJ_OPT_FIRST_MATCH)); @endverbatim This adds 3 key/value pairs: cost of strategy, number of records produced by this strategy, and whether this strategy is chosen. After that, there is similar code for other semijoin strategies. The resulting trace piece (seen in @c information_schema.OPTIMIZER_TRACE) is @verbatim "semijoin_strategy_choice": [ { "strategy": "FirstMatch", "cost": 1, "records": 1, "chosen": true }, { "strategy": "DuplicatesWeedout", "cost": 1.1, "records": 1, "duplicate_tables_left": false, "chosen": false } ] @endverbatim For more output examples, check result files of the opt_trace suite in @c mysql-test. Feature can be un-compiled with @code cmake -DOPTIMIZER_TRACE=0 @endcode. @section WITH_DBUG Interaction between trace and DBUG We don't want to have to duplicate code like this: @verbatim DBUG_PRINT("info",("cost %g",cost)); Opt_trace_object(thd->opt_trace).add("cost",cost); @endverbatim Thus, any optimizer trace operation, *even* if tracing is run-time disabled, has an implicit DBUG_PRINT("opt",...) inside. This way, only the second line above is needed, and several DBUG_PRINT() could be removed from the Optimizer code. When tracing is run-time disabled, in a debug binary, traces are still created in order to catch the @c add() calls and write their text to DBUG, but those traces are not visible into INFORMATION_SCHEMA.OPTIMIZER_TRACE: we then say that they "don't support I_S". A debug binary without optimizer trace compiled in, will intentionally not compile. Because opening an object or array, or add()-ing to it, writes to DBUG immediately, a key/value pair and its outer object may be 100 lines apart in the DBUG log. @section ADDING_TRACING Guidelines for adding tracing @li Try to limit the number of distinct "words". For example, when describing an optimizer's decision, the words "chosen" (true/false value, tells whether we are choosing the said optimization), "cause" (free text value, tells why we are making this choice, when it's not obvious) can and should often be used. Having a restricted vocabulary helps consistency. Use "row" instead of "record". Use "tmp" instead of "temporary". @li Use only simple characters for key names: a-ZA-Z_#, and no space. '#' serves to denote a number, like in "select#" . @li Keep in mind than in an object, keys are not ordered; an application may parse the JSON output and output it again with keys order changed; thus when order matters, use an array (which may imply having anonymous objects as items of the array, with keys inside the anonymous objects, see how it's done in @c JOIN::optimize()). Keep in mind that in an object keys should be unique, an application may lose duplicate keys. @section OOM_HANDLING Handling of "out-of-memory" errors All memory allocations (with exceptions: see below) in the Optimizer trace use @c my_error() to report errors, which itself calls @c error_handler_hook. It is the responsibility of the API user to set up a proper @c error_handler_hook which will alert her/him of the OOM problem. When in the server, this is already the case (@c error_handler_hook is @c my_message_sql() which makes the statement fail). Note that the debug binary may crash if OOM (OOM can cause syntax errors...). @section TRACE_SECURITY Description of trace-induced security checks. A trace exposes information. For example if one does SELECT on a view, the trace contains the view's body. So, the user should be allowed to see the trace only if she/he has privilege to see the body, i.e. privilege to do SHOW CREATE VIEW. There are similar issues with stored procedures, functions, triggers. We implement this by doing additional checks on SQL objects when tracing is on: @li stored procedures, functions, triggers: checks are done when executing those objects @li base tables and views. Base tables or views are listed in some @c LEX::query_tables. The LEX may be of the executing statement (statement executed by @c mysql_execute_command(), or by @c sp_lex_keeper::reset_lex_and_exec_core()), we check this LEX in the constructor of Opt_trace_start. Or it may be a LEX describing a view, we check this LEX when opening the view (@c open_and_read_view()). Those checks are greatly simplified by disabling traces in case of security context changes. @see opt_trace_disable_if_no_security_context_access(). Those checks must be done with the security context of the connected user. Checks with the SUID context would be useless: assume the design is that the basic user does not have DML privileges on tables, but only EXECUTE on SUID-highly-privileged routines (which implement _controlled_ _approved_ DMLs): then the SUID context would successfully pass all additional privilege checks, routine would generate tracing, and the connected user would view the trace after the routine's execution, seeing secret information. @section NEXT What a developer should read next The documentation of those classes, in order @code Opt_trace_context Opt_trace_context_impl Opt_trace_stmt Opt_trace_struct Opt_trace_object Opt_trace_array @endcode and then @ref opt_trace.h as a whole. */ class Opt_trace_stmt; // implementation detail local to opt_trace.cc /** User-visible information about a trace. @sa Opt_trace_iterator. */ struct Opt_trace_info { /** String containing trace. If trace has been end()ed, this is 0-terminated, which is only to aid debugging or unit testing; this property is not relied upon in normal server usage. If trace has not been ended, this is not 0-terminated. That rare case can happen when a substatement reads OPTIMIZER_TRACE (at that stage, the top statement is still executing so its trace is not ended yet, but may still be read by the sub-statement). */ const char *trace_ptr; size_t trace_length; ///< length of trace string //// String containing original query. 0-termination: like trace_ptr. const char *query_ptr; size_t query_length; ///< length of query string const CHARSET_INFO *query_charset; ///< charset of query string /** How many bytes this trace is missing (for traces which were truncated because of @@@@optimizer-trace-max-mem-size). */ size_t missing_bytes; bool missing_priv; ///< whether user lacks privilege to see this trace }; /** Iterator over the list of remembered traces. @note due to implementation, the list must not change during an iterator's lifetime, or results may be unexpected (no crash though). */ class Opt_trace_iterator { public: /** @param ctx context */ Opt_trace_iterator(Opt_trace_context *ctx); void next(); ///< Advances iterator to next trace. /** Provides information about the trace on which the iterator is positioned. @param[out] info information returned. The usage pattern is 1) instantiate the iterator 2) test at_end(), if false: call get_value() and then next() 3) repeat (2) until at_end() is true. */ void get_value(Opt_trace_info *info) const; /// @returns whether iterator is positioned to the end. bool at_end() const { return cursor == nullptr; } private: /// Pointer to context, from which traces are retrieved Opt_trace_context *ctx; const Opt_trace_stmt *cursor; ///< trace which the iterator is positioned on long row_count; ///< how many traces retrieved so far }; /** Object and array are both "structured data" and have lots in common, so the Opt_trace_struct is a base class for them. When you want to add a structure to the trace, you create an instance of Opt_trace_object or Opt_trace_array, then you add information to it with add(), then the destructor closes the structure (we use RAII, Resource Acquisition Is Initialization). */ class Opt_trace_struct { protected: /** @param ctx_arg Optimizer trace context for this structure @param requires_key_arg whether this structure requires/forbids keys for values put inside it (an object requires them, an array forbids them) @param key key if this structure is the value of a key/value pair, NULL otherwise. This pointer must remain constant and valid until the object is destroyed (to support @ref saved_key). @param feature optimizer feature to which this structure belongs This constructor is never called directly, only from subclasses. */ Opt_trace_struct(Opt_trace_context *ctx_arg, bool requires_key_arg, const char *key, Opt_trace_context::feature_value feature) : started(false), requires_key(false), has_disabled_I_S(false), empty(false), stmt(nullptr), saved_key(nullptr) { // A first inlined test if (unlikely(ctx_arg->is_started())) { // Tracing enabled: must fully initialize the structure. do_construct(ctx_arg, requires_key_arg, key, feature); } /* Otherwise, just leave "started" to false, it marks that the structure is dummy. */ } ~Opt_trace_struct() { if (unlikely(started)) do_destruct(); } public: /** The exception to RAII: this function is an explicit way of ending a structure before it goes out of scope. Don't use it unless RAII mandates a new scope which mandates re-indenting lots of code lines. */ void end() { if (unlikely(started)) do_destruct(); } /** Adds a value (of string type) to the structure. A key is specified, so it adds the key/value pair (the structure must thus be an object). There are two "add_*" variants to add a string value. If the value is 0-terminated and each character - is ASCII 7-bit - has ASCII code >=32 and is neither '"' nor '\\' then add_alnum() should be used. That should be the case for all fixed strings like add_alnum("cause", "cost"). Otherwise, add_utf8() should be used; it accepts any UTF8-encoded character in 'value' and will escape characters which JSON requires (and is thus slower than add_alnum()). It should be used for all strings which we get from the server's objects (indeed a table's name, a WHERE condition, may contain "strange" characters). @param key key @param value value @returns a reference to the structure, useful for chaining like this: @verbatim add(x,y).add(z,t).add(u,v) @endverbatim String-related add() variants are named add_[something](): - to avoid confusing the compiler between: add(const char *value, size_t val_length) and add(const char *key, ulonglong value) - and because String::length() returns uint32 and not size_t, so for add(str.ptr(), str.length()) compiler may pick add(const char *key, double value) instead of add(const char *value, size_t val_length). */ Opt_trace_struct &add_alnum(const char *key, const char *value) { if (likely(!started)) return *this; return do_add(key, value, strlen(value), false); } /** Adds a value (of string type) to the structure. No key is specified, so it adds only the value (the structure must thus be an array). @param value value @returns a reference to the structure */ Opt_trace_struct &add_alnum(const char *value) { if (likely(!started)) return *this; return do_add(nullptr, value, strlen(value), false); } /** Like add_alnum() but supports any UTF8 characters in 'value'. Will "escape" 'value' to be JSON-compliant. @param key key @param value value @param val_length length of string 'value' */ Opt_trace_struct &add_utf8(const char *key, const char *value, size_t val_length) { if (likely(!started)) return *this; return do_add(key, value, val_length, true); } /// Variant of add_utf8() for adding to an array (no key) Opt_trace_struct &add_utf8(const char *value, size_t val_length) { if (likely(!started)) return *this; return do_add(nullptr, value, val_length, true); } /// Variant of add_utf8() where 'value' is 0-terminated Opt_trace_struct &add_utf8(const char *key, const char *value) { if (likely(!started)) return *this; return do_add(key, value, strlen(value), true); } /// Variant of add_utf8() where 'value' is 0-terminated Opt_trace_struct &add_utf8(const char *value) { if (likely(!started)) return *this; return do_add(nullptr, value, strlen(value), true); } /** Add a value (of Item type) to the structure. The Item should be a condition (like a WHERE clause) which will be pretty-printed into the trace. This is useful for showing condition transformations (equality propagation etc). @param key key @param item the Item @return a reference to the structure */ Opt_trace_struct &add(const char *key, const Item *item) { if (likely(!started)) return *this; return do_add(key, item); } Opt_trace_struct &add(const Item *item) { if (likely(!started)) return *this; return do_add(nullptr, item); } Opt_trace_struct &add(const char *key, bool value) { if (likely(!started)) return *this; return do_add(key, value); } Opt_trace_struct &add(bool value) { if (likely(!started)) return *this; return do_add(nullptr, value); } Opt_trace_struct &add(const char *key, int value) { if (likely(!started)) return *this; return do_add(key, static_cast(value)); } Opt_trace_struct &add(int value) { if (likely(!started)) return *this; return do_add(nullptr, static_cast(value)); } Opt_trace_struct &add(const char *key, uint value) { if (likely(!started)) return *this; return do_add(key, static_cast(value)); } Opt_trace_struct &add(uint value) { if (likely(!started)) return *this; return do_add(nullptr, static_cast(value)); } Opt_trace_struct &add(const char *key, ulong value) { if (likely(!started)) return *this; return do_add(key, static_cast(value)); } Opt_trace_struct &add(ulong value) { if (likely(!started)) return *this; return do_add(nullptr, static_cast(value)); } Opt_trace_struct &add(const char *key, longlong value) { if (likely(!started)) return *this; return do_add(key, value); } Opt_trace_struct &add(longlong value) { if (likely(!started)) return *this; return do_add(nullptr, value); } Opt_trace_struct &add(const char *key, ulonglong value) { if (likely(!started)) return *this; return do_add(key, value); } Opt_trace_struct &add(ulonglong value) { if (likely(!started)) return *this; return do_add(nullptr, value); } Opt_trace_struct &add(const char *key, double value) { if (likely(!started)) return *this; return do_add(key, value); } Opt_trace_struct &add(double value) { if (likely(!started)) return *this; return do_add(nullptr, value); } /// Adds a JSON null object (==Python's "None") Opt_trace_struct &add_null(const char *key) { if (likely(!started)) return *this; return do_add_null(key); } /** Helper to put the database/table name in an object. @param tab TABLE* pointer */ Opt_trace_struct &add_utf8_table(const Table_ref *tab) { if (likely(!started)) return *this; return do_add_utf8_table(tab); } /** Helper to put the number of query_block in an object. @param select_number number of query_block */ Opt_trace_struct &add_select_number(uint select_number) { return add("select#", select_number); } /** Add a value to the structure. @param key key @param cost the value of Cost_estimate @return a reference to the structure */ Opt_trace_struct &add(const char *key, const Cost_estimate &cost) { if (likely(!started)) return *this; return do_add(key, cost); } /** Informs this structure that we are adding data (scalars, structures) to it. This is used only if sending to I_S. @returns whether the structure was empty so far. @note this is reserved for use by Opt_trace_stmt. */ bool set_not_empty() { const bool old_empty = empty; empty = false; return old_empty; } /** Validates the key about to be added. @note this is reserved for use by Opt_trace_stmt. When adding a value (or array or object) to an array, or a key/value pair to an object, we need to know this outer array or object. It would be possible, when trying to add a key to an array, which is wrong in JSON, or similarly when trying to add a value without any key to an object, to catch it at compilation time, if the adder received, as function parameter, the type of the structure (like @c Opt_trace_array*). Then the @c add(key,val) call would not compile as Opt_trace_array wouldn't feature it. But as explained in comment of class Opt_trace_context we cannot pass down the object, have to maintain a "current object or array" in the Opt_trace_context context (pointer to an instance of Opt_trace_struct), and the adder grabs it from the context. As this current structure is of type "object or array", we cannot do compile-time checks that only suitable functions are used. A call to @c add(key,value) is necessarily legal for the compiler as the structure may be an object, though it will be wrong in case the structure is actually an array at run-time. Thus we have the risk of an untested particular situation where the current structure is not an object (but an array) though the code expected it to be one. This happens in practice, because subqueries are evaluated in many possible places of code, not all of them being known. Same happens, to a lesser extent, with calls to the range optimizer. So at run-time, in check_key(), we detect wrong usage, like adding a value to an object without specifying a key, and then remove the unnecessary key, or add an autogenerated key. */ const char *check_key(const char *key); private: /// Not implemented, use add_alnum() instead. Opt_trace_struct &add(const char *key, const char *value); Opt_trace_struct &add(const char *key); /// Full initialization. @sa Opt_trace_struct::Opt_trace_struct void do_construct(Opt_trace_context *ctx, bool requires_key, const char *key, Opt_trace_context::feature_value feature); /// Really does destruction void do_destruct(); /** Really adds to the object. @sa add(). @note add() has an up-front if(), hopefully inlined, so that in the common case - tracing run-time disabled - we have no function call. If tracing is enabled, we call do_add(). In a 20-table plan search (as in BUG#50595), the execution time was decreased from 2.6 to 2.0 seconds thanks to this inlined-if trick. @param key key @param value value @param val_length length of string 'value' @param escape do JSON-compliant escaping of 'value'. If 'escape' is false, 'value' should be ASCII. Otherwise, should be UTF8. */ Opt_trace_struct &do_add(const char *key, const char *value, size_t val_length, bool escape); Opt_trace_struct &do_add(const char *key, const Item *item); Opt_trace_struct &do_add(const char *key, bool value); Opt_trace_struct &do_add(const char *key, longlong value); Opt_trace_struct &do_add(const char *key, ulonglong value); Opt_trace_struct &do_add(const char *key, double value); Opt_trace_struct &do_add_null(const char *key); Opt_trace_struct &do_add_utf8_table(const Table_ref *tab); Opt_trace_struct &do_add(const char *key, const Cost_estimate &value); Opt_trace_struct(const Opt_trace_struct &); ///< not defined Opt_trace_struct &operator=(const Opt_trace_struct &); ///< not defined bool started; ///< Whether the structure does tracing or is dummy /** Whether the structure requires/forbids keys for values inside it. true: this is an object. false: this is an array. @note The canonical way would be to not have such bool per instance, but rather have a pure virtual member function Opt_trace_struct::requires_key(), overloaded by Opt_trace_object (returning true) and by Opt_trace_array (returning false). But Opt_trace_object::requires_key() would not be accessible from Opt_trace_struct::do_construct() (which would complicate coding), whereas the bool is. */ bool requires_key; /** Whether this structure caused tracing to be disabled in this statement because belonging to a not-traced optimizer feature, in accordance with the value of @@@@optimizer_trace_features. */ bool has_disabled_I_S; bool empty; ///< @see set_not_empty() Opt_trace_stmt *stmt; ///< Trace owning the structure /// Key if the structure is the value of a key/value pair, NULL otherwise const char *saved_key; #ifndef NDEBUG /** Fixed-length prefix of previous key in this structure, if this structure is an object. Serves to detect when adding two same consecutive keys to an object, which would be wrong. */ char previous_key[25]; #endif }; /** A JSON object (unordered set of key/value pairs). Defines only a constructor, all the rest is inherited from Opt_trace_struct. */ class Opt_trace_object : public Opt_trace_struct { public: /** Constructs an object. Key is specified, so the object is the value of a key/value pair. @param ctx context for this object @param key key @param feature optimizer feature to which this structure belongs */ Opt_trace_object( Opt_trace_context *ctx, const char *key, Opt_trace_context::feature_value feature = Opt_trace_context::MISC) : Opt_trace_struct(ctx, true, key, feature) {} /** Constructs an object. No key is specified, so the object is just a value (serves for the single root object or for adding the object to an array). @param ctx context for this object @param feature optimizer feature to which this structure belongs */ Opt_trace_object( Opt_trace_context *ctx, Opt_trace_context::feature_value feature = Opt_trace_context::MISC) : Opt_trace_struct(ctx, true, nullptr, feature) {} }; /** A JSON array (ordered set of values). Defines only a constructor, all the rest in inherited from Opt_trace_struct. */ class Opt_trace_array : public Opt_trace_struct { public: /** Constructs an array. Key is specified, so the array is the value of a key/value pair. @param ctx context for this array @param key key @param feature optimizer feature to which this structure belongs */ Opt_trace_array( Opt_trace_context *ctx, const char *key, Opt_trace_context::feature_value feature = Opt_trace_context::MISC) : Opt_trace_struct(ctx, false, key, feature) {} /** Constructs an array. No key is specified, so the array is just a value (serves for adding the object to an array). @param ctx context for this array @param feature optimizer feature to which this structure belongs */ Opt_trace_array( Opt_trace_context *ctx, Opt_trace_context::feature_value feature = Opt_trace_context::MISC) : Opt_trace_struct(ctx, false, nullptr, feature) {} }; /** Instantiate an instance of this class for specific cases where optimizer trace, in a certain section of Optimizer code, should write only to DBUG and not I_S. Example: see sql_select.cc. Note that this class should rarely be used; the "feature" parameter of Opt_trace_struct is a good alternative. */ class Opt_trace_disable_I_S { public: /** @param ctx_arg Context. @param disable_arg Whether the instance should really disable anything. If false, the object is dummy. If true, tracing-to-I_S is disabled at construction and re-enabled at destruction. @details A dummy instance is there only for RAII reasons. Imagine we want to do this: @verbatim { if (x) disable tracing; code; } // tracing should be re-enabled here @endverbatim As we use RAII, we cannot put the instance declaration inside if(x): @verbatim { if (x) Opt_trace_disable_I_S instance(ctx); code; } @endverbatim because it would be destroyed as soon as the if() block is left, so tracing would be re-enabled before @c code;. It should rather be written as: @verbatim { Opt_trace_disable_I_S instance(ctx, x); // if !x, does nothing code; } // re-enabling happens here, if x is true @endverbatim */ Opt_trace_disable_I_S(Opt_trace_context *ctx_arg, bool disable_arg) { if (disable_arg) { ctx = ctx_arg; ctx->disable_I_S_for_this_and_children(); } else ctx = nullptr; } /// Destructor. Restores trace's "enabled" property to its previous value. ~Opt_trace_disable_I_S() { if (ctx != nullptr) ctx->restore_I_S(); } private: /** Context. Non-NULL if and only if this instance really does disabling */ Opt_trace_context *ctx; Opt_trace_disable_I_S(const Opt_trace_disable_I_S &); // not defined Opt_trace_disable_I_S &operator=( const Opt_trace_disable_I_S &); // not defined }; /** @name Helpers connecting the optimizer trace to THD or Information Schema. */ //@{ class Opt_trace_start { public: /** Instantiate this class to start tracing a THD's actions (generally at a statement's start), and to set the "original" query (not transformed, as sent by client) for the new trace. Destructor will end the trace. If in a routine's instruction, there is no "query". To be helpful to the user, we craft a query using the instruction's print(). We don't leave this to the caller, because it would be inefficient if tracing is off. @param thd_arg the THD @param tbl list of tables read/written by the statement. @param sql_command SQL command being prepared or executed @param set_vars what variables are set by this command (only used if sql_command is SQLCOM_SET_OPTION) @param query query @param query_length query's length @param instr routine's instruction, if applicable; if so, 'query' and 'query_length' above are ignored @param query_charset charset which was used to encode this query @note Each sub-statement is responsible for ending the trace which it has started; this class achieves this by keeping some memory inside. */ Opt_trace_start(THD *thd_arg, Table_ref *tbl, enum enum_sql_command sql_command, List *set_vars, const char *query, size_t query_length, sp_printable *instr, const CHARSET_INFO *query_charset); ~Opt_trace_start(); private: Opt_trace_context *const ctx; bool error; ///< whether trace start() had an error }; class Query_block; /** Prints SELECT query to optimizer trace. It is not the original query (as in @c Opt_trace_context::set_query()) but a printout of the parse tree (Item-s). @param thd the THD @param query_block query's parse tree @param trace_object Opt_trace_object to which the query will be added */ void opt_trace_print_expanded_query(const THD *thd, Query_block *query_block, Opt_trace_object *trace_object); /** If the security context is not that of the connected user, inform the trace system that a privilege is missing. With one exception: see below. @param thd the THD This serves to eliminate the following issue. Any information readable by a SELECT may theoretically end up in the trace. And a SELECT may read information from other places than tables: - from views (reading their bodies) - from stored routines (reading their bodies) - from files (reading their content), with LOAD_FILE() - from the list of connections (reading their queries...), with I_S.PROCESSLIST. If the connected user has EXECUTE privilege on a routine which does a security context change, the routine can retrieve information internally (if allowed by the SUID context's privileges), and present only a portion of it to the connected user. But with tracing on, all information is possibly in the trace. So the connected user receives more information than the routine's definer intended to provide. Fixing this issue would require adding, near many privilege checks in the server, a new optimizer-trace-specific check done against the connected user's context, to verify that the connected user has the right to see the retrieved information. Instead, our chosen simpler solution is that if we see a security context change where SUID user is not the connected user, we disable tracing. With only one safe exception: if the connected user has all global privileges (because then she/he can find any information anyway). By "all global privileges" we mean everything but WITH GRANT OPTION (that latter one isn't related to information gathering). Read access to I_S.OPTIMIZER_TRACE by another user than the connected user is restricted: @see fill_optimizer_trace_info(). */ void opt_trace_disable_if_no_security_context_access(THD *thd); /** If tracing is on, checks additional privileges for a view, to make sure that the user has the right to do SHOW CREATE VIEW. For that: - this function checks SHOW VIEW - SELECT is tested in opt_trace_disable_if_no_tables_access() - SELECT + SHOW VIEW is sufficient for SHOW CREATE VIEW. We also check underlying tables. If a privilege is missing, notifies the trace system. This function should be called when the view's underlying tables have not yet been merged. @param thd THD context @param view view to check @param underlying_tables underlying tables/views of 'view' */ void opt_trace_disable_if_no_view_access(THD *thd, Table_ref *view, Table_ref *underlying_tables); /** If tracing is on, checks additional privileges on a stored routine, to make sure that the user has the right to do SHOW CREATE PROCEDURE/FUNCTION. For that, we use the same checks as in those SHOW commands. If a privilege is missing, notifies the trace system. This function is not redundant with opt_trace_disable_if_no_security_context_access(). Indeed, for a SQL SECURITY INVOKER routine, there is no context change, but we must still verify that the invoker can do SHOW CREATE. For triggers, see note in sp_head::execute_trigger(). @param thd The THD @param sp routine to check */ void opt_trace_disable_if_no_stored_proc_func_access(THD *thd, sp_head *sp); /** Fills information_schema.OPTIMIZER_TRACE with rows (one per trace) @retval 0 ok @retval 1 error */ int fill_optimizer_trace_info(THD *thd, Table_ref *tables, Item *); //@} /** Helper for defining query-transformation-related trace objects in one code line. This produces { "transformation": { "select#": @, "from": @, "to": @ The objects are left open, so that one can add more to them (often a "chosen" property after making some computation). Objects get closed when going out of scope as usual. @param trace optimizer trace @param object_level0 name of the outer Opt_trace_object C++ object @param object_level1 name of the inner Opt_trace_object C++ object @param select_number number of the being-transformed Query_block @param from description of the before-transformation state @param to description of the after-transformation state */ #define OPT_TRACE_TRANSFORM(trace, object_level0, object_level1, \ select_number, from, to) \ const Opt_trace_object object_level0(trace); \ Opt_trace_object object_level1(trace, "transformation"); \ object_level1.add_select_number(select_number); \ object_level1.add_alnum("from", from).add_alnum("to", to); #endif /* OPT_TRACE_INCLUDED */