/* Copyright (c) 2011, 2012, 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 */ #ifndef OPT_TRACE_CONTEXT_INCLUDED #define OPT_TRACE_CONTEXT_INCLUDED #include "my_config.h" // OPTIMIZER_TRACE #include "sql_array.h" // Dynamic_array /** @file This contains the declaration of class Opt_trace_context, which is needed to declare THD. It is recommend to read opt_trace.h first. */ #ifdef OPTIMIZER_TRACE class Opt_trace_stmt; // implementation detail local to opt_trace.cc /** @class Opt_trace_context A per-session context which is always available at any point of execution, because in practice it's accessible from THD which contains: @verbatim Opt_trace_context opt_trace; @endverbatim It maintains properties of the session's regarding tracing: enabled/disabled state, style (all trace on one line, or not, etc), a list of all remembered traces of previous and current SQL statement (as restricted by OFFSET/LIMIT), and a pointer to the current (being-generated) trace (which itself has a pointer to its current open object/array). Here is why the context needs to provide the current open object/array: @li When adding a value (scalar or array or object) to an array, or adding a key/value pair to an object, we need this outer object or array (from now on, we will use the term "structure" for "object or array", as both are structured types). @li The most natural way would be that the outer object would be passed in argument to the adder (the function which wants to add the value or key/value). @li But tracing is sometimes produced from deep down the stack trace, with many intermediate frames doing no tracing (writing nothing to the trace), so it would require passing the outer structure through many levels, thus modifying many function prototypes. Example (in gdb "backtrace" notation: inner frames first): @verbatim #0 Item_in_subselect::single_value_transformer - opens an object for key "transformation" #1 Item_in_subselect::select_in_like_transformer - does no tracing #2 Item_allany_subselect::select_transformer - does no tracing #3 JOIN::prepare - opens an object for key "join_preparation" @endverbatim So the object opened in #3 would have to be passed in argument to #2 and #1 in order to finally reach #0 where object "transformation" would be added to it. @li So, as we cannot practically pass the object down, we rather maintain a "current object or array" accessible from the Opt_trace_context context; it's a pointer to an instance of Opt_trace_struct, and the function deep down (frame #0) grabs it from the context, where it was depositted by the function high up (frame #3 in the last example). */ class Opt_trace_context { public: Opt_trace_context() : pimpl(NULL), I_S_disabled(0) {} ~Opt_trace_context(); /** Starts a new trace. @param support_I_S Whether this statement should have its trace in information_schema @param support_dbug_or_missing_priv 'true' if this statement should have its trace in the dbug log (--debug), or if missing_privilege() may be called on this trace @param end_marker For a key/(object|array) pair, should the key be repeated in a comment when the object|array closes? like @verbatim "key_foo": { multi-line blah } / * key_foo * / @endverbatim This is for human-readability only, not valid in JSON. Note that YAML supports #-prefixed comments (we would just need to put the next item's "," before the current item's "#"). @param one_line Should the trace be on a single line without indentation? (More compact for network transfer to programs, less human-readable.) @param offset Offset for restricting trace production. @param limit Limit for restricting trace production. @param max_mem_size Maximum allowed for cumulated size of all remembered traces. @param features Only these optimizer features should be traced. @retval false ok @retval true error (OOM): instance is unusable, so only destructor is permitted on it; any other member function has undefined effects. */ bool start(bool support_I_S, bool support_dbug_or_missing_priv, bool end_marker, bool one_line, long offset, long limit, ulong max_mem_size, ulonglong features); /** Ends the current (=open, unfinished, being-generated) trace. If @c missing_privilege() has been called between start() and end(), end() restores I_S support to what it was before the call to @c missing_privilege(). This is the key to ensure that missing_privilege() does not disable I_S support for the rest of the connection's life! */ void end(); /// Returns whether there is a current trace bool is_started() const { return unlikely(pimpl != NULL) && pimpl->current_stmt_in_gen != NULL; } /** @returns whether the current trace writes to I_S. This function should rarely be used. Don't you use this for some clever optimizations bypassing opt trace! */ bool support_I_S() const; /** Set the "original" query (not transformed, as sent by client) for the current trace. @param query query @param length query's length @param charset charset which was used to encode this query */ void set_query(const char* query, size_t length, const CHARSET_INFO *charset); /** Brainwash: deletes all remembered traces and resets counters regarding OFFSET/LIMIT (so that the next statement is considered as "at offset 0"). Does not reset the @@@@optimizer_trace_offset/limit variables. */ void reset(); /// @sa parameters of Opt_trace_context::start() bool get_end_marker() const { return pimpl->end_marker; } /// @sa parameters of Opt_trace_context::start() bool get_one_line() const { return pimpl->one_line; } /** Names of flags for @@@@optimizer_trace variable of @c sys_vars.cc : @li "enabled" = tracing enabled @li "one_line"= see parameter of @ref Opt_trace_context::start @li "default". */ static const char *flag_names[]; /** Flags' numeric values for @@@@optimizer_trace variable */ enum { FLAG_DEFAULT= 0, FLAG_ENABLED= 1 << 0, FLAG_ONE_LINE= 1 << 1 }; /** Features' names for @@@@optimizer_trace_features variable of @c sys_vars.cc: @li "greedy_search" = the greedy search for a plan @li "range_optimizer" = the cost analysis of accessing data through ranges in indices @li "dynamic_range" = the range optimization performed for each record when access method is dynamic range @li "repeated_subselect" = the repeated execution of subselects @li "default". */ static const char *feature_names[]; /** Features' numeric values for @@@@optimizer_trace_features variable */ enum feature_value { GREEDY_SEARCH= 1 << 0, RANGE_OPTIMIZER= 1 << 1, DYNAMIC_RANGE= 1 << 2, REPEATED_SUBSELECT= 1 << 3, /* If you add here, update feature_value of empty implementation and default_features! */ /** Anything unclassified, including the top object (thus, by "inheritance from parent", disabling MISC makes an empty trace). This feature cannot be disabled by the user; for this it is important that it always has biggest flag; flag's value itself does not matter. */ MISC= 1 << 7 }; /** User lacks privileges to see the current trace. Make the trace appear empty in Opt_trace_info, and disable I_S for all its upcoming children. Once a call to this function has been made, subsequent calls to it before @c end() have no effects. */ void missing_privilege(); /// Optimizer features which are traced by default. static const feature_value default_features; /** @returns whether an optimizer feature should be traced. @param f feature */ bool feature_enabled (feature_value f) const { return unlikely(pimpl != NULL) && (pimpl->features & f); } /** Opt_trace_struct is passed Opt_trace_context*, and needs to know to which statement's trace to attach, so Opt_trace_context must provide this information. */ Opt_trace_stmt *get_current_stmt_in_gen() { return pimpl->current_stmt_in_gen; } /** @returns the next statement to show in I_S. @param[in,out] got_so_far How many statements the caller got so far (by previous calls to this function); function updates this count. @note traces are returned from oldest to newest. */ const Opt_trace_stmt *get_next_stmt_for_I_S(long *got_so_far) const; /// Temporarily disables I_S for this trace and its children. void disable_I_S_for_this_and_children() { ++I_S_disabled; if (unlikely(pimpl != NULL)) pimpl->disable_I_S_for_this_and_children(); } /** Restores I_S support to what it was before the previous call to disable_I_S_for_this_and_children(). */ void restore_I_S() { --I_S_disabled; DBUG_ASSERT(I_S_disabled >= 0); if (unlikely(pimpl != NULL)) pimpl->restore_I_S(); } private: /** To have the smallest impact on THD's size, most of the implementation is moved to a separate class Opt_trace_context_impl which is instantiated on the heap when really needed. So if a connection never sets @@@@optimizer_trace to "enabled=on" and does not use --debug, this heap allocation never happens. This class is declared here so that frequently called functions like Opt_trace_context::is_started() can be inlined. */ class Opt_trace_context_impl { public: Opt_trace_context_impl() : current_stmt_in_gen(NULL), features(feature_value(0)), offset(0), limit(0), since_offset_0(0) {} void disable_I_S_for_this_and_children(); void restore_I_S(); /** Trace which is currently being generated, where structures are being added. "in_gen" stands for "in generation", being-generated. In simple cases it is equal to the last element of array all_stmts_for_I_S. But it can be prior to it, for example when calling a stored routine: @verbatim CALL statement starts executing create trace of CALL (call it "trace #1") add structure to trace #1 add structure to trace #1 First sub-statement executing create trace of sub-statement (call it "trace #2") add structure to trace #2 add structure to trace #2 First sub-statement ends add structure to trace #1 @endverbatim In the beginning, the CALL statement's trace is the newest and current; when the first sub-statement is executing, that sub-statement's trace is the newest and current; when the first sub-statement ends, it is still the newest but it's not the current anymore: the current is then again the CALL's one, where structures will be added, until a second sub-statement is executed. Another case is when the current statement sends only to DBUG: all_stmts_for_I_S lists only traces shown in OPTIMIZER_TRACE. */ Opt_trace_stmt *current_stmt_in_gen; /** To keep track of what is the current statement, as execution goes into a sub-statement, and back to the upper statement, we have a stack of successive values of current_stmt_in_gen: when in a statement we enter a substatement (a new trace), we push the statement's trace on the stack and change current_stmt_in_gen to the substatement's trace; when leaving the substatement we pop from the stack and set current_stmt_in_gen to the popped value. */ Dynamic_array stack_of_current_stmts; /** List of remembered traces for putting into the OPTIMIZER_TRACE table. Element 0 is the one created first, will be first row of OPTIMIZER_TRACE table. The array structure fullfills those needs: - to output traces "oldest first" in OPTIMIZER_TRACE - to preserve traces "newest first" when @@@@optimizer_trace_offset<0 - to delete a trace in the middle of the list when it is permanently out of the offset/limit showable window. */ Dynamic_array all_stmts_for_I_S; /** List of traces which are unneeded because of OFFSET/LIMIT, and scheduled for deletion from memory. */ Dynamic_array all_stmts_to_del; bool end_marker; ///< copy of parameter of Opt_trace_context::start bool one_line; feature_value features; long offset; long limit; size_t max_mem_size; /** Number of statements traced so far since "offset 0", for comparison with OFFSET and LIMIT, when OFFSET >= 0. */ long since_offset_0; }; Opt_trace_context_impl *pimpl; /// Dynamically allocated implementation. /** <>0 <=> any to-be-created statement's trace should not be in information_schema. This applies to next statements, their substatements, etc. */ int I_S_disabled; /** Find and delete unneeded traces. For example if OFFSET=-1,LIMIT=1, only the last trace is needed. When a new trace is started, the previous traces becomes unneeded and this function deletes them which frees memory. @param purge_all if true, ignore OFFSET and thus delete everything */ void purge_stmts(bool purge_all); /** Compute maximum allowed memory size for current trace. The current trace is the only one active. Other traces break down in two groups: - the finished ones (from previously executed statements), - the "started but not finished ones": they are not current, are not being updated at this moment: this must be the trace of a top statement calling a substatement which is the current trace now: trace's top statement is not being updated at this moment. So the current trace can grow in the room left by all traces above. */ size_t allowed_mem_size_for_current_stmt() const; /// Not defined copy constructor, to disallow copy. Opt_trace_context(const Opt_trace_context&); /// Not defined assignment operator, to disallow assignment. Opt_trace_context& operator=(const Opt_trace_context&); }; #else /* OPTIMIZER_TRACE */ /** Empty implementation used when optimizer trace is not compiled in */ class Opt_trace_context { public: /// We need those enums even if tracing is disabled enum feature_value { GREEDY_SEARCH= 1 << 0, RANGE_OPTIMIZER= 1 << 1, DYNAMIC_RANGE= 1 << 2, REPEATED_SUBSELECT= 1 << 3, MISC= 1 << 7 }; enum { FLAG_DEFAULT= 0, FLAG_ENABLED= 1 << 0, FLAG_ONE_LINE= 1 << 1 }; static bool is_started() { return false; } }; #endif /* OPTIMIZER_TRACE */ #endif /* OPT_TRACE_CONTEXT_INCLUDED */