mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-03 14:33:32 +03:00 
			
		
		
		
	The reason for the failure was a bug in an include file on debian that causes 'struct stat' to have different sized depending on the environment. This patch fixes so that we always include my_global.h or my_config.h before we include any other files. Other things: - Removed #include <my_global.h> in some include files; Better to always do this at the top level to have as few "always-include-this-file-first' files as possible. - Removed usage of some include files that where already included by my_global.h or by other files. client/mysql_plugin.c: Use my_global.h first client/mysqlslap.c: Remove duplicated include files extra/comp_err.c: Remove duplicated include files include/m_string.h: Remove duplicated include files include/maria.h: Remove duplicated include files libmysqld/emb_qcache.cc: Use my_global.h first plugin/semisync/semisync.h: Use my_pthread.h first sql/datadict.cc: Use my_global.h first sql/debug_sync.cc: Use my_global.h first sql/derror.cc: Use my_global.h first sql/des_key_file.cc: Use my_global.h first sql/discover.cc: Use my_global.h first sql/event_data_objects.cc: Use my_global.h first sql/event_db_repository.cc: Use my_global.h first sql/event_parse_data.cc: Use my_global.h first sql/event_queue.cc: Use my_global.h first sql/event_scheduler.cc: Use my_global.h first sql/events.cc: Use my_global.h first sql/field.cc: Use my_global.h first Remove duplicated include files sql/field_conv.cc: Use my_global.h first sql/filesort.cc: Use my_global.h first Remove duplicated include files sql/gstream.cc: Use my_global.h first sql/ha_ndbcluster.cc: Use my_global.h first sql/ha_ndbcluster_binlog.cc: Use my_global.h first sql/ha_ndbcluster_cond.cc: Use my_global.h first sql/ha_partition.cc: Use my_global.h first sql/handler.cc: Use my_global.h first sql/hash_filo.cc: Use my_global.h first sql/hostname.cc: Use my_global.h first sql/init.cc: Use my_global.h first sql/item.cc: Use my_global.h first sql/item_buff.cc: Use my_global.h first sql/item_cmpfunc.cc: Use my_global.h first sql/item_create.cc: Use my_global.h first sql/item_geofunc.cc: Use my_global.h first sql/item_inetfunc.cc: Use my_global.h first sql/item_row.cc: Use my_global.h first sql/item_strfunc.cc: Use my_global.h first sql/item_subselect.cc: Use my_global.h first sql/item_sum.cc: Use my_global.h first sql/item_timefunc.cc: Use my_global.h first sql/item_xmlfunc.cc: Use my_global.h first sql/key.cc: Use my_global.h first sql/lock.cc: Use my_global.h first sql/log.cc: Use my_global.h first sql/log_event.cc: Use my_global.h first sql/log_event_old.cc: Use my_global.h first sql/mf_iocache.cc: Use my_global.h first sql/mysql_install_db.cc: Remove duplicated include files sql/mysqld.cc: Remove duplicated include files sql/net_serv.cc: Remove duplicated include files sql/opt_range.cc: Use my_global.h first sql/opt_subselect.cc: Use my_global.h first sql/opt_sum.cc: Use my_global.h first sql/parse_file.cc: Use my_global.h first sql/partition_info.cc: Use my_global.h first sql/procedure.cc: Use my_global.h first sql/protocol.cc: Use my_global.h first sql/records.cc: Use my_global.h first sql/records.h: Don't include my_global.h Better to do this at the upper level sql/repl_failsafe.cc: Use my_global.h first sql/rpl_filter.cc: Use my_global.h first sql/rpl_gtid.cc: Use my_global.h first sql/rpl_handler.cc: Use my_global.h first sql/rpl_injector.cc: Use my_global.h first sql/rpl_record.cc: Use my_global.h first sql/rpl_record_old.cc: Use my_global.h first sql/rpl_reporting.cc: Use my_global.h first sql/rpl_rli.cc: Use my_global.h first sql/rpl_tblmap.cc: Use my_global.h first sql/rpl_utility.cc: Use my_global.h first sql/set_var.cc: Added comment sql/slave.cc: Use my_global.h first sql/sp.cc: Use my_global.h first sql/sp_cache.cc: Use my_global.h first sql/sp_head.cc: Use my_global.h first sql/sp_pcontext.cc: Use my_global.h first sql/sp_rcontext.cc: Use my_global.h first sql/spatial.cc: Use my_global.h first sql/sql_acl.cc: Use my_global.h first sql/sql_admin.cc: Use my_global.h first sql/sql_analyse.cc: Use my_global.h first sql/sql_audit.cc: Use my_global.h first sql/sql_base.cc: Use my_global.h first sql/sql_binlog.cc: Use my_global.h first sql/sql_bootstrap.cc: Use my_global.h first Use my_global.h first sql/sql_cache.cc: Use my_global.h first sql/sql_class.cc: Use my_global.h first sql/sql_client.cc: Use my_global.h first sql/sql_connect.cc: Use my_global.h first sql/sql_crypt.cc: Use my_global.h first sql/sql_cursor.cc: Use my_global.h first sql/sql_db.cc: Use my_global.h first sql/sql_delete.cc: Use my_global.h first sql/sql_derived.cc: Use my_global.h first sql/sql_do.cc: Use my_global.h first sql/sql_error.cc: Use my_global.h first sql/sql_explain.cc: Use my_global.h first sql/sql_expression_cache.cc: Use my_global.h first sql/sql_handler.cc: Use my_global.h first sql/sql_help.cc: Use my_global.h first sql/sql_insert.cc: Use my_global.h first sql/sql_lex.cc: Use my_global.h first sql/sql_load.cc: Use my_global.h first sql/sql_locale.cc: Use my_global.h first sql/sql_manager.cc: Use my_global.h first sql/sql_parse.cc: Use my_global.h first sql/sql_partition.cc: Use my_global.h first sql/sql_plugin.cc: Added comment sql/sql_prepare.cc: Use my_global.h first sql/sql_priv.h: Added error if we use this before including my_global.h This check is here becasue so many files includes sql_priv.h first. sql/sql_profile.cc: Use my_global.h first sql/sql_reload.cc: Use my_global.h first sql/sql_rename.cc: Use my_global.h first sql/sql_repl.cc: Use my_global.h first sql/sql_select.cc: Use my_global.h first sql/sql_servers.cc: Use my_global.h first sql/sql_show.cc: Added comment sql/sql_signal.cc: Use my_global.h first sql/sql_statistics.cc: Use my_global.h first sql/sql_table.cc: Use my_global.h first sql/sql_tablespace.cc: Use my_global.h first sql/sql_test.cc: Use my_global.h first sql/sql_time.cc: Use my_global.h first sql/sql_trigger.cc: Use my_global.h first sql/sql_udf.cc: Use my_global.h first sql/sql_union.cc: Use my_global.h first sql/sql_update.cc: Use my_global.h first sql/sql_view.cc: Use my_global.h first sql/sys_vars.cc: Added comment sql/table.cc: Use my_global.h first sql/thr_malloc.cc: Use my_global.h first sql/transaction.cc: Use my_global.h first sql/uniques.cc: Use my_global.h first sql/unireg.cc: Use my_global.h first sql/unireg.h: Removed inclusion of my_global.h storage/archive/ha_archive.cc: Added comment storage/blackhole/ha_blackhole.cc: Use my_global.h first storage/csv/ha_tina.cc: Use my_global.h first storage/csv/transparent_file.cc: Use my_global.h first storage/federated/ha_federated.cc: Use my_global.h first storage/federatedx/federatedx_io.cc: Use my_global.h first storage/federatedx/federatedx_io_mysql.cc: Use my_global.h first storage/federatedx/federatedx_io_null.cc: Use my_global.h first storage/federatedx/federatedx_txn.cc: Use my_global.h first storage/heap/ha_heap.cc: Use my_global.h first storage/innobase/handler/handler0alter.cc: Use my_global.h first storage/maria/ha_maria.cc: Use my_global.h first storage/maria/unittest/ma_maria_log_cleanup.c: Remove duplicated include files storage/maria/unittest/test_file.c: Added comment storage/myisam/ha_myisam.cc: Move sql_plugin.h first as this includes my_global.h storage/myisammrg/ha_myisammrg.cc: Use my_global.h first storage/oqgraph/oqgraph_thunk.cc: Use my_config.h and my_global.h first One could not include my_global.h before oqgraph_thunk.h (don't know why) storage/spider/ha_spider.cc: Use my_global.h first storage/spider/hs_client/config.cpp: Use my_global.h first storage/spider/hs_client/escape.cpp: Use my_global.h first storage/spider/hs_client/fatal.cpp: Use my_global.h first storage/spider/hs_client/hstcpcli.cpp: Use my_global.h first storage/spider/hs_client/socket.cpp: Use my_global.h first storage/spider/hs_client/string_util.cpp: Use my_global.h first storage/spider/spd_conn.cc: Use my_global.h first storage/spider/spd_copy_tables.cc: Use my_global.h first storage/spider/spd_db_conn.cc: Use my_global.h first storage/spider/spd_db_handlersocket.cc: Use my_global.h first storage/spider/spd_db_mysql.cc: Use my_global.h first storage/spider/spd_db_oracle.cc: Use my_global.h first storage/spider/spd_direct_sql.cc: Use my_global.h first storage/spider/spd_i_s.cc: Use my_global.h first storage/spider/spd_malloc.cc: Use my_global.h first storage/spider/spd_param.cc: Use my_global.h first storage/spider/spd_ping_table.cc: Use my_global.h first storage/spider/spd_sys_table.cc: Use my_global.h first storage/spider/spd_table.cc: Use my_global.h first storage/spider/spd_trx.cc: Use my_global.h first storage/xtradb/handler/handler0alter.cc: Use my_global.h first storage/xtradb/handler/i_s.cc: Use my_global.h first
		
			
				
	
	
		
			326 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright (C) 2010-2011 Monty Program Ab & Oleksandr Byelkin
 | 
						|
 | 
						|
   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
 | 
						|
 | 
						|
#include <my_global.h>
 | 
						|
#include "sql_base.h"
 | 
						|
#include "sql_select.h"
 | 
						|
#include "sql_expression_cache.h"
 | 
						|
 | 
						|
/**
 | 
						|
  Minimum hit ration to proceed on disk if in memory table overflowed.
 | 
						|
  hit_rate = hit / (miss + hit);
 | 
						|
*/
 | 
						|
#define EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE 0.7
 | 
						|
/**
 | 
						|
  Minimum hit ratio to keep in memory table (do not switch cache off)
 | 
						|
  hit_rate = hit / (miss + hit);
 | 
						|
*/
 | 
						|
#define EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE  0.2
 | 
						|
/**
 | 
						|
  Number of cache miss to check hit ratio (maximum cache performance
 | 
						|
  impact in the case when the cache is not applicable)
 | 
						|
*/
 | 
						|
#define EXPCACHE_CHECK_HIT_RATIO_AFTER 200
 | 
						|
 | 
						|
/*
 | 
						|
  Expression cache is used only for caching subqueries now, so its statistic
 | 
						|
  variables we call subquery_cache*.
 | 
						|
*/
 | 
						|
ulong subquery_cache_miss, subquery_cache_hit;
 | 
						|
 | 
						|
Expression_cache_tmptable::Expression_cache_tmptable(THD *thd,
 | 
						|
                                                     List<Item> &dependants,
 | 
						|
                                                     Item *value)
 | 
						|
  :cache_table(NULL), table_thd(thd), items(dependants), val(value),
 | 
						|
   hit(0), miss(0), inited (0)
 | 
						|
{
 | 
						|
  DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable");
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Disable cache
 | 
						|
*/
 | 
						|
 | 
						|
void Expression_cache_tmptable::disable_cache()
 | 
						|
{
 | 
						|
  if (cache_table->file->inited)
 | 
						|
    cache_table->file->ha_index_end();
 | 
						|
  free_tmp_table(table_thd, cache_table);
 | 
						|
  cache_table= NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Field enumerator for TABLE::add_tmp_key
 | 
						|
 | 
						|
  @param arg             reference variable with current field number
 | 
						|
 | 
						|
  @return field number
 | 
						|
*/
 | 
						|
 | 
						|
static uint field_enumerator(uchar *arg)
 | 
						|
{
 | 
						|
  return ((uint*)arg)[0]++;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Initialize temporary table and auxiliary structures for the expression
 | 
						|
  cache
 | 
						|
 | 
						|
  @details
 | 
						|
  The function creates a temporary table for the expression cache, defines
 | 
						|
  the search index and initializes auxiliary search structures used to check
 | 
						|
  whether a given set of of values of the expression parameters is in some
 | 
						|
  cache entry.
 | 
						|
*/
 | 
						|
 | 
						|
void Expression_cache_tmptable::init()
 | 
						|
{
 | 
						|
  List_iterator<Item> li(items);
 | 
						|
  Item_iterator_list it(li);
 | 
						|
  uint field_counter;
 | 
						|
  DBUG_ENTER("Expression_cache_tmptable::init");
 | 
						|
  DBUG_ASSERT(!inited);
 | 
						|
  inited= TRUE;
 | 
						|
  cache_table= NULL;
 | 
						|
 | 
						|
  if (items.elements == 0)
 | 
						|
  {
 | 
						|
    DBUG_PRINT("info", ("All parameters were removed by optimizer."));
 | 
						|
    DBUG_VOID_RETURN;
 | 
						|
  }
 | 
						|
 | 
						|
  /* add result field */
 | 
						|
  items.push_front(val);
 | 
						|
 | 
						|
  cache_table_param.init();
 | 
						|
  /* dependent items and result */
 | 
						|
  cache_table_param.field_count= items.elements;
 | 
						|
  /* postpone table creation to index description */
 | 
						|
  cache_table_param.skip_create_table= 1;
 | 
						|
 | 
						|
  if (!(cache_table= create_tmp_table(table_thd, &cache_table_param,
 | 
						|
                                      items, (ORDER*) NULL,
 | 
						|
                                      FALSE, TRUE,
 | 
						|
                                      ((table_thd->variables.option_bits |
 | 
						|
                                        TMP_TABLE_ALL_COLUMNS) &
 | 
						|
                                        ~TMP_TABLE_FORCE_MYISAM),
 | 
						|
                                      HA_POS_ERROR,
 | 
						|
                                      (char *)"subquery-cache-table",
 | 
						|
                                      TRUE)))
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("create_tmp_table failed, caching switched off"));
 | 
						|
    DBUG_VOID_RETURN;
 | 
						|
  }
 | 
						|
 | 
						|
  if (cache_table->s->db_type() != heap_hton)
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("we need only heap table"));
 | 
						|
    goto error;
 | 
						|
  }
 | 
						|
 | 
						|
  field_counter= 1;
 | 
						|
 | 
						|
  if (cache_table->alloc_keys(1) ||
 | 
						|
      cache_table->add_tmp_key(0, items.elements - 1, &field_enumerator,
 | 
						|
                                (uchar*)&field_counter, TRUE) ||
 | 
						|
      ref.tmp_table_index_lookup_init(table_thd, cache_table->key_info, it,
 | 
						|
                                      TRUE, 1 /* skip result field*/))
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("creating index failed"));
 | 
						|
    goto error;
 | 
						|
  }
 | 
						|
  cache_table->s->keys= 1;
 | 
						|
  ref.null_rejecting= 1;
 | 
						|
  ref.disable_cache= FALSE;
 | 
						|
  ref.has_record= 0;
 | 
						|
  ref.use_count= 0;
 | 
						|
 | 
						|
 | 
						|
  if (open_tmp_table(cache_table))
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("Opening (creating) temporary table failed"));
 | 
						|
    goto error;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!(cached_result= new Item_field(cache_table->field[0])))
 | 
						|
  {
 | 
						|
    DBUG_PRINT("error", ("Creating Item_field failed"));
 | 
						|
    goto error;
 | 
						|
  }
 | 
						|
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
 | 
						|
error:
 | 
						|
  disable_cache();
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
Expression_cache_tmptable::~Expression_cache_tmptable()
 | 
						|
{
 | 
						|
  /* Add accumulated statistics */
 | 
						|
  statistic_add(subquery_cache_miss, miss, &LOCK_status);
 | 
						|
  statistic_add(subquery_cache_hit, hit, &LOCK_status);
 | 
						|
 | 
						|
  if (cache_table)
 | 
						|
    disable_cache();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Check if a given set of parameters of the expression is in the cache
 | 
						|
 | 
						|
  @param [out] value     the expression value found in the cache if any
 | 
						|
 | 
						|
  @details
 | 
						|
  For a given set of the parameters of the expression the function
 | 
						|
  checks whether it can be found in some entry of the cache. If so
 | 
						|
  the function returns the result of the expression extracted from
 | 
						|
  the cache.
 | 
						|
 | 
						|
  @retval Expression_cache::HIT if the set of parameters is in the cache
 | 
						|
  @retval Expression_cache::MISS - otherwise
 | 
						|
*/
 | 
						|
 | 
						|
Expression_cache::result Expression_cache_tmptable::check_value(Item **value)
 | 
						|
{
 | 
						|
  int res;
 | 
						|
  DBUG_ENTER("Expression_cache_tmptable::check_value");
 | 
						|
 | 
						|
  if (cache_table)
 | 
						|
  {
 | 
						|
    DBUG_PRINT("info", ("status: %u  has_record %u",
 | 
						|
                        (uint)cache_table->status, (uint)ref.has_record));
 | 
						|
    if ((res= join_read_key2(table_thd, NULL, cache_table, &ref)) == 1)
 | 
						|
      DBUG_RETURN(ERROR);
 | 
						|
 | 
						|
    if (res)
 | 
						|
    {
 | 
						|
      if (((++miss) == EXPCACHE_CHECK_HIT_RATIO_AFTER) &&
 | 
						|
          ((double)hit / ((double)hit + miss)) <
 | 
						|
          EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
 | 
						|
      {
 | 
						|
        DBUG_PRINT("info",
 | 
						|
                   ("Early check: hit rate is not so good to keep the cache"));
 | 
						|
        disable_cache();
 | 
						|
      }
 | 
						|
 | 
						|
      DBUG_RETURN(MISS);
 | 
						|
    }
 | 
						|
 | 
						|
    hit++;
 | 
						|
    *value= cached_result;
 | 
						|
    DBUG_RETURN(Expression_cache::HIT);
 | 
						|
  }
 | 
						|
  DBUG_RETURN(Expression_cache::MISS);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Put a new entry into the expression cache
 | 
						|
 | 
						|
  @param value     the result of the expression to be put into the cache
 | 
						|
 | 
						|
  @details
 | 
						|
  The function evaluates 'value' and puts the result into the cache as the
 | 
						|
  result of the expression for the current set of parameters.
 | 
						|
 | 
						|
  @retval FALSE OK
 | 
						|
  @retval TRUE  Error
 | 
						|
*/
 | 
						|
 | 
						|
my_bool Expression_cache_tmptable::put_value(Item *value)
 | 
						|
{
 | 
						|
  int error;
 | 
						|
  DBUG_ENTER("Expression_cache_tmptable::put_value");
 | 
						|
  DBUG_ASSERT(inited);
 | 
						|
 | 
						|
  if (!cache_table)
 | 
						|
  {
 | 
						|
    DBUG_PRINT("info", ("No table so behave as we successfully put value"));
 | 
						|
    DBUG_RETURN(FALSE);
 | 
						|
  }
 | 
						|
 | 
						|
  *(items.head_ref())= value;
 | 
						|
  fill_record(table_thd, cache_table, cache_table->field, items, TRUE, TRUE);
 | 
						|
  if (table_thd->is_error())
 | 
						|
    goto err;;
 | 
						|
 | 
						|
  if ((error= cache_table->file->ha_write_tmp_row(cache_table->record[0])))
 | 
						|
  {
 | 
						|
    /* create_myisam_from_heap will generate error if needed */
 | 
						|
    if (cache_table->file->is_fatal_error(error, HA_CHECK_DUP))
 | 
						|
      goto err;
 | 
						|
    else
 | 
						|
    {
 | 
						|
      double hit_rate= ((double)hit / ((double)hit + miss));
 | 
						|
      DBUG_ASSERT(miss > 0);
 | 
						|
      if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_MEM_TABLE)
 | 
						|
      {
 | 
						|
        DBUG_PRINT("info", ("hit rate is not so good to keep the cache"));
 | 
						|
        disable_cache();
 | 
						|
        DBUG_RETURN(FALSE);
 | 
						|
      }
 | 
						|
      else if (hit_rate < EXPCACHE_MIN_HIT_RATE_FOR_DISK_TABLE)
 | 
						|
      {
 | 
						|
        DBUG_PRINT("info", ("hit rate is not so good to go to disk"));
 | 
						|
        if (cache_table->file->ha_delete_all_rows() ||
 | 
						|
            cache_table->file->ha_write_tmp_row(cache_table->record[0]))
 | 
						|
          goto err;
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
        if (create_internal_tmp_table_from_heap(table_thd, cache_table,
 | 
						|
                                                cache_table_param.start_recinfo,
 | 
						|
                                                &cache_table_param.recinfo,
 | 
						|
                                                error, 1, NULL))
 | 
						|
          goto err;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  cache_table->status= 0; /* cache_table->record contains an existed record */
 | 
						|
  ref.has_record= TRUE; /* the same as above */
 | 
						|
  DBUG_PRINT("info", ("has_record: TRUE  status: 0"));
 | 
						|
 | 
						|
  DBUG_RETURN(FALSE);
 | 
						|
 | 
						|
err:
 | 
						|
  disable_cache();
 | 
						|
  DBUG_RETURN(TRUE);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void Expression_cache_tmptable::print(String *str, enum_query_type query_type)
 | 
						|
{
 | 
						|
  List_iterator<Item> li(items);
 | 
						|
  Item *item;
 | 
						|
  bool is_first= TRUE;
 | 
						|
 | 
						|
  str->append('<');
 | 
						|
  li++;  // skip result field
 | 
						|
  while ((item= li++))
 | 
						|
  {
 | 
						|
    if (!is_first)
 | 
						|
      str->append(',');
 | 
						|
    item->print(str, query_type);
 | 
						|
    is_first= FALSE;
 | 
						|
  }
 | 
						|
  str->append('>');
 | 
						|
}
 |