The Software Workshop inc. - software that fits! ™                  ExtSQL - Extended SQL
.
 
. Home   Contact Us    login
.
 
 
 

    Home

    Documentation

    Downloads

    FAQ

    News & Events

    Privacy Policy

    Report Problem

    Support

    Terms & Conditions


Quick Feedback!
This is a new project,
feedback is welcome
(and will be read!)

Source for 8.4.2.0 version of extsql.c

/* Copyright (C) 2006, 2007, 2008, 2009, 2010 Software Workshop Incorporated

   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; either version 2 of the License, or
   (at your option) any later version.

   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 */

#define PGSQL 1

#ifdef PGSQL
#include "utils/extsql.h"

#else // MYSQL

#include "mysql_priv.h"
#include "stacktrace.h"

#endif  // PGSQL or MYSQL

//  DEBUG FLAGS  -- we show decimal, has to be used in SET GLOBAL extsql_debug= val
#define STATS_DUMP           1    // dump stats internal data on every SHOW command
#define STATS_DUMP_ONCE      2    // just dump it once and clear stats_debug ZZ
#define STATS_SHOW_PARAMS    4    // show parameters to SHOW STATISTICS
#define STATS_INC_PARAMS     8    // print warning on bad param to extsql_inc
#define STATS_ADMIN          16   // allow write/read/reset commands
#define STATS_TEST_TWO       32   // testing
#define STATS_INC_VAR        64   // show details on stats increment
#define STATS_THD_INIT_PARAMS  128  // show parameters on thread init
#define STATS_THD_INIT_LOOP    256  // show search for matching/new instance of class
#define STATS_DISABLE_INC      512  // disable extsql_inc processing
#define STATS_THREAD_CHK      1024  // do a check of the thread on each inc, only print errors
#define STATS_DUMP_START      2048  // dump all tracking info at server start 
#define STATS_TIME_DETAIL     4096  // show start secs/minutes if min/hour are units
#define STATS_EXTRA_VARS      8192  // allow user to select untested vars for tracking
#define STATS_MEM_ALLOC      16384  // show startup mem allocs
#define STATS_TIME_CHANGE    32768  // show start of time roll over processing
#define STATS_RELOAD         65536  // show reload processing
#define STATS_TEST_ONE      131072  // testing
#define STATS_PID           262144  // show server pid as part of show statistics, for gdb

#define PROG_POINT(x,y)   sql_print_information("ExtSQL: PROGRESS POINT %s (0x%x)", x, y)

// LIMITS
#define STATS_MAX_TIME_LABEL   20
#define STATS_WARNING_LIMIT 10

#define MAX_BUFF_SIZE 2048  // used for temporary buffs of user input (product of next two)
#define MAX_VAR_SIZE 45     // max length we allows for a Var name that is tracked.
#define MAX_VARS_IN_CLASS 50  // max number of vars in single class def & row of output
#define DATE_SIZE 30

// MISC
#define ARRAY_INDEX(i,j,size_j,k,size_k) ( (i * size_j +j) * size_k + k)


// just testing

time_t StartTime;

// GLOBAL's defined in GLOBAL struct, extsql.h, 
//  few vars here where compiler needs static addresses. Set global in extsql_init_globals

// these are globals initially set in postgresql.conf -- extsql_init has not been called yet,
// need to have a static address allocated, extsql_init_globals will then copy to Shared.
ulong extsql_active=0, extsql_counter=0, extsql_debug=0, queries=0, connects=0;
char  *extsql_class_list=NULL, *extsql_reload_file=NULL;


#ifdef PGSQL

pGLOBAL Shared; // Used for shared memory/globals - only dynamic allocation at start.

#else // MYSQL

GLOBAL GShared;  // can be static, shared between threads
pGLOBAL Shared = &GShared;

#endif


// CONSTANT GLOBALS HERE 

char *extsqlClassesSupported[STATS_MAX_CLASSES] = {"condb", "conuser", "db", "host", "server", "user", NULL};
char *extsql_key, *extsql_users; 
static char *extsql_version = "\nExtSQL version: __STATS_VERSION__\n"; // keep \n at start and end!


// vars we have tested and confirmed with, they can set STATS_EXTRA_VARS to override.
static  char *allowedVarsArray[MAX_VARS] = {"Com_begin", "Com_commit", "Com_create_table", 
            "Com_delete", "Com_drop_table", "Com_insert", "Com_lock_tables", "Com_replace", 
            "Com_rollback", "Com_select", "Com_set_option", "Com_show_fields", "Com_stmt_execute", 
            "Com_stmt_prepare", "Com_update", "Connections", "Created_tmp_tables", 
            "Created_tmp_disk_tables", "Handler_read_rnd", "Handler_read_rnd_next", 
            "Qcache_hits", "Questions", "Select_full_join", "Select_range_check", 
            "Select_scan", "Slow_queries", NULL };

static  char *startTypeName[] = { "FAILURE", "NORMAL", "RESET", "RELOAD" };

char **allowedVars = allowedVarsArray;

static char *adminDisabled = "ExtSQL: In this release STATISTICS RESET/READ/WRITE are disabled by default due \
to incomplete testing. If you wish to use these commands you must added the following to the server \
config file and restart the server:   extsql_debug = 16";


// FUNCTION prototypes

int extsql_init_globals(void);
static void extsql_stats_dump(void);
static void extsql_thread_dump(THD *thd, int doDump);
static int extsql_store_var_addr(char *varAddr);
static void extsql_init_proc_vars(int *threadID, int *command, char **user, char **db, char **host,
                                  char **proc_info, int **indexPtr, int **statVerify);


#ifdef PGSQL

ulong Com_begin, Com_commit, Com_create_table, Com_delete, 
  Com_drop_table, Com_insert, Com_rollback, Com_select, Com_update, Connections, Questions;

char *ShmemPtr = NULL;  // for shared memory
static slock_t  *MemLock = NULL; // spin lock needs to be in shared mem region.
char **NextFree = NULL;  // for shared memory!

struct show_var_st status_vars[] = {
  {"Com_begin", &Com_begin},
  {"Com_commit", &Com_commit},
  {"Com_create_table", &Com_create_table},
  {"Com_delete", &Com_delete},
  {"Com_drop_table", &Com_drop_table},
  {"Com_insert", &Com_insert},
  {"Com_rollback", &Com_rollback},
  {"Com_select", &Com_select},
  {"Com_update", &Com_update},
  {"Connections", &Connections},
  {"Questions", &Questions},
  {NullS, (ulong *) 0}
};
char *command_name[] = {"n/a for pgsql", "n/a for pgsql", NULL};

// change in number of args from 7.4.19 -> 8.4
#ifdef PGSQL_8
#define TupleDescInitEntry(a,b,c,d,e,f,g)  TupleDescInitEntry(a,b,c,d,e,f)
#endif

int mysql_show_extsql(THD *thd, const char *stats_class, DestReceiver *dest, List *stats_vars,
                      uint stats_hourly, const char *wild, char *stats_order_var,
                      const uint stats_order_dir, const uint stats_order_limit, char *stats_where_var,
                      const uint stats_where_num, const uint stats_where_char);

#define SHMEM_SIZE    1000000   // ZZ - need to predict based on class list for pgsql
#define SHMEM_RESERVE  100000

// need our own my_malloc to zero alloc'd memory
// NOTE - this allocs from our limited shared memory area, we don't FREE. This should
// only be used for extsql data
// Temporary buffers used for i/o should be alloced with just "malloc/free"
char * extsql_malloc(int, uint);
char * extsql_malloc(int size, uint flags) {  // from our shared memory segment, pointed at by ShmemPtr;

  char 	   *newStart; // similiar code in shmem.c
  char 	   *newFree;
  char	   *addr;


  if (ShmemPtr == NULL || NextFree == NULL) { // not allocated yet
    sql_print_error("ExtSQL extsql_malloc failure, shared memory not allocated!");
    return(NULL);
  }

  size = MAXALIGN(size);

  SpinLockAcquire(MemLock);
  
  newStart = *NextFree;
  
   /* extra alignment for large requests, since they are probably buffers */
  if (size >= BLCKSZ) {
    newStart = (char *) BUFFERALIGN(newStart);
  }
  
  newFree = newStart + size;
  if (newFree <= SHMEM_SIZE + ShmemPtr) {
    addr = newStart;
    *NextFree = newFree;

  } else {
    addr = NULL;
  }
  
  SpinLockRelease(MemLock);
  
  if (!addr)
    ereport(WARNING,
            (errcode(ERRCODE_OUT_OF_MEMORY),
             errmsg("ExtSQL - out of shared memory")));

  bzero(addr, size);
 
  if STATS_DEBUG(STATS_MEM_ALLOC ) {
    sql_print_information("ExtSQL: alloc at 0x%x, of %d bytes",
                          (uint)addr, size);
  }

  return(addr);
  
} // end my_malloc for PGSQL

// need our own strdup for pgsql
// NOTE - this allocs from our limited shared memory area, we don't FREE. This should
// only be used for extsql data
// Temporary buffers used for i/o should be alloced with just "strdup"
char * extsql_strdup(char *, uint flags);
char * extsql_strdup(char *srcStr, uint flags) {

  int size = strlen(srcStr);
  char *newStr = NULL;

  newStr = extsql_malloc(size+1, 0);  // second param doesn't matter for PGSQL

  if (!newStr) {
    sql_print_error("ExtSQL my_strdup: failed to allocate size (%d)", size);
    return(NULL);
  } else {
    strcpy(newStr, srcStr);

  }

  return(newStr);

} // end my_strdup for PGSQL


static void my_datetime_to_str(time_t *, char *);
static void my_datetime_to_str(time_t *secsNow, char *date) {

  struct tm *timeNow;
  timeNow=localtime(secsNow);

  strftime(date, DATE_SIZE , "%m/%d/%y %H:%M:%S", timeNow);

}

// wild_case_compare -- from MySQL source, not ours.  We don't use
// charset info for PGSQL.
int wild_case_compare(int cs, const char *str,const char *wildstr);
int wild_case_compare(int cs, const char *str,const char *wildstr)
{
  char wild_many='%', wild_one='_', wild_prefix='\\'; 
  int flag;

  while (*wildstr) {

    while (*wildstr && *wildstr != wild_many && *wildstr != wild_one) {

      if (*wildstr == wild_prefix && wildstr[1])
        wildstr++;
      
      if (toupper(*wildstr++) !=
          toupper(*str++)) 
        return(1);
    }

    if (! *wildstr ) return(*str != 0);

    if (*wildstr++ == wild_one) {
      if (! *str++) 
        return(1);    /* One char; skip */
      
    } else {                                           /* Found '*' */
      if (!*wildstr) 
        return(0);            /* '*' as last char: OK */

      flag=(*wildstr != wild_many && *wildstr != wild_one);
      do {

        if (flag) {
          char cmp;
            if ((cmp= *wildstr) == wild_prefix && wildstr[1])
              cmp=wildstr[1];
            cmp=toupper(cmp);
            while (*str && toupper(*str) != cmp)
              str++;
            if (!*str) 
              return(1);
        }

        if (wild_case_compare(0, str,wildstr) == 0) 
          return(0);
        
      } while (*str++);
      return(1);
    }
  }
  return(*str != '\0');
} // end wild_case_compare


#else  // MYSQL


// for mysql we just use what they have
#define extsql_malloc(a,b)  my_malloc(a,b)
#define extsql_strdup(a,b)  my_strdup(a,b)

static int extsql_output_prep_2_col(char *col1, char *col2, Protocol *protocol,
                             List field_list, char *fill2);
static int extsql_output_2_col(char *col1Str, int col1Int, char *col2Str,
                               int col2Int, Protocol *protocol, char *fill1);

#endif // PGSQL or MYSQL build



#ifdef EXTSQL_50
#define net_printf  net_printf_error
#define PROTOCOL    Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF
#else
#define PROTOCOL    1
#endif


// remove this define if you are building from source, will skip license checks.
// #define EXTSQL_BINARY


#ifndef DBUG_RETURN
#define DBUG_RETURN(x) return(x)
#endif

//
// START ACTUAL EXTSQL FUNCTION DEFINITIONS
//

#ifdef PGSQL
int extsql_usage(THD* thd, DestReceiver *protocol) {

#ifdef NEVER__
}
#endif

#else
int extsql_usage(THD* thd) {
#endif

#ifdef PGSQL
  TupOutputState *tstate;
  TupleDesc tupdesc = NULL;
#else
  Item* item;
  List field_list;
  Protocol *protocol = thd->protocol;
  char *tstate = NULL, *tupdesc = '\0';
#endif

  char* enableHelp  = "Turn statistics on or off";
  char* enableUsage = "STATISTICS ON|OFF";
  char* resetHelp   = "Reset statistics [reread conf file]";
  char* resetUsage  = "STATISTICS RESET ['conf file']";
  char* readHelp    = "Read saved statistics data";
  char* readUsage   = "STATISTICS READ [CONFIRM] 'data file'";
  char* writeHelp   = "Write out statistics data";
  char* writeUsage  = "STATISTICS WRITE 'data file'";

  DBUG_ENTER("extsql_usage");

  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 1)) DBUG_RETURN(1);

#ifdef PGSQL
  if (extsql_output_prep_2_col("Function", "Usage", protocol, &tstate,
                               tupdesc)) {
    DBUG_RETURN(1);
  }
#else
  if (extsql_output_prep_2_col("Function", "Usage", protocol, field_list,
                               tupdesc)) {
    DBUG_RETURN(1);
  }
#endif

  if (extsql_output_2_col(enableHelp, 0, enableUsage, 0, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  if (extsql_output_2_col(resetHelp, 0, resetUsage, 0, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  if (extsql_output_2_col(readHelp, 0, readUsage, 0, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  if (extsql_output_2_col(writeHelp, 0, writeUsage, 0, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  extsql_output_complete(thd, tstate);

  DBUG_RETURN(0);
}

// simple routine to turn extsql on or off using "STATISTICS ON|OFF"
// grammer
#ifdef PGSQL
int extsql_enable(THD* thd, DestReceiver *protocol, uint status) {

#ifdef NEVER__
}
#endif

#else
int extsql_enable(THD* thd, uint status) {
#endif
  
#ifdef PGSQL
  TupOutputState *tstate;
  TupleDesc tupdesc = NULL;
#else
  Item* item;
  List field_list;
  Protocol *protocol = thd->protocol;
  char *tstate = NULL, *tupdesc = '\0';
#endif
  DBUG_ENTER("extsql_enable");

  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 1)) DBUG_RETURN(1);

  // accept any positive integer
  if (status > 0) { status = 1; }

  Shared->extsql_active = status;

#ifdef PGSQL
  if (extsql_output_prep_2_col("extsql_active", "status", protocol, &tstate,
                               tupdesc)) {
    DBUG_RETURN(1);
  }
#else
  if (extsql_output_prep_2_col("extsql_active", "status", protocol, field_list,
                              tupdesc)) {
    DBUG_RETURN(1);
  }
#endif

  if (extsql_output_2_col(NULL, Shared->extsql_active, NULL, 1, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  extsql_output_complete(thd, tstate);

  DBUG_RETURN(0);
}

// handler for "STATISTICS RESET"
#ifdef PGSQL
int extsql_reset(THD* thd, DestReceiver *protocol, const char* conf) {

#ifdef NEVER__
}
#endif

#else
int extsql_reset(THD* thd, const char* conf) {
#endif

#ifdef PGSQL
  TupOutputState *tstate;
  TupleDesc tupdesc = NULL;
#else
  Item* item;
  List field_list;
  Protocol *protocol = thd->protocol;
  char *tstate = NULL, *tupdesc = '\0';
#endif

  int startType = STATS_INIT_RESET;

  DBUG_ENTER("extsql_reset");

  if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
    net_printf(thd, 0, adminDisabled);
    return(0);
  }

  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 0)) DBUG_RETURN(1);

  // in this case, extsql_prep will parse and process the conf
  // file.
  if (conf != NULL) {
    extsql_prep(&startType, conf, NULL);
    if (startType == STATS_INIT_FAILURE) {
      net_printf(thd, 0, "ExtSQL: Error reading config file");
      DBUG_RETURN(1);
    }
  }

  if (extsql_init(Shared->extsql_class_list, startType, Shared->extsql_reload_file)) {
    net_printf(thd, 0, "ExtSQL: Reset failed");
    DBUG_RETURN(1);
  }

#ifdef PGSQL
  if (extsql_output_prep_2_col("message", "status", protocol, &tstate,
                               tupdesc)) {
    DBUG_RETURN(1);
  }
#else
  if (extsql_output_prep_2_col("message", "status", protocol, field_list,
                              tupdesc)) {
    DBUG_RETURN(1);
  }
#endif

  if (extsql_output_2_col("Statistics reset", 0, NULL, 1, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  extsql_output_complete(thd, tstate);

  return 0;
}

// handler for "STATISTICS READ|WRITE"
#ifdef PGSQL
int extsql_file_io(THD* thd, DestReceiver *protocol, uint io_mode, uint confirm,
                   char* reload_file) {

#ifdef NEVER__
}
#endif

#else
int extsql_file_io(THD* thd, uint io_mode, uint confirm,
                   char* reload_file) {
#endif

#ifdef PGSQL
  TupOutputState *tstate;
  TupleDesc tupdesc = NULL;
#else
  Item* item;
  List field_list;
  Protocol *protocol = thd->protocol;
  char *tstate = NULL, *tupdesc = '\0';
#endif

  int startType = STATS_INIT_RELOAD;
  int prevState = Shared->extsql_active;

  char* reloadFileCopy = extsql_strdup(reload_file, MYF(MY_WME));
  char* message = extsql_strdup((io_mode ? "Reload file written successfully." :
                             "Reload file read successfully. Statistics are disabled. "), 
                            MYF(MY_WME));

  DBUG_ENTER("extsql_file-io");

  if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
    net_printf(thd, 0, adminDisabled);
    return(0);
  }

  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 0)) DBUG_RETURN(1);

  Shared->extsql_active = 0;


  // writing file
  if (io_mode) {

    if (extsql_reload(STATS_WRITE_RELOAD, reloadFileCopy)) {
      net_printf(thd, 0, "ExtSQL: Error encountered during reload WRITE");
      Shared->extsql_active = prevState;
      DBUG_RETURN(1);
    }

    Shared->extsql_active = 1;
    
  // reading file
  } else {

    extsql_prep(&startType, NULL, reloadFileCopy);

    if (startType == STATS_INIT_FAILURE) {
      net_printf(thd, 0, "Error reading reload file or version incompatibility.");
      Shared->extsql_active = prevState;
      DBUG_RETURN(1);
    }

    if (startType == STATS_INIT_CONFIRM && !confirm) {
      net_printf(thd, 0,
                 "Class data in reload file or hostname does not match.  Use STATISTICS READ CONFIRM '%s' to override.",
                 reload_file);
      Shared->extsql_active = prevState;
      DBUG_RETURN(1);
    }

    if (extsql_init(Shared->extsql_class_list, STATS_INIT_RELOAD, reloadFileCopy)) {
      net_printf(thd, 0, "ExtSQL: Read failed");
      Shared->extsql_active = prevState;
      DBUG_RETURN(1);
    }

    Shared->extsql_active = 0;
  }


#ifdef PGSQL
  if (extsql_output_prep_2_col("message", "status", protocol, &tstate,
                               tupdesc)) {
    DBUG_RETURN(1);
  }
#else
  if (extsql_output_prep_2_col("message", "status", protocol, field_list,
                              tupdesc)) {
    DBUG_RETURN(1);
  }
#endif

  if (extsql_output_2_col(message, 0, NULL, 1, protocol, tstate)) {
    DBUG_RETURN(1);
  }

  extsql_output_complete(thd, tstate);  

  return 0;
}

// convenience function to check user authorization
// checks to see if the specified 'user' is in the string containing
// authorized users 'authUsers', set by admin with extsql_users=".."
// return 1 if authorized, 0 otherwise
static int extsql_user_auth(char *user, char *authUsers) {

  char *tPtr, *token;
  char tmpUser[STATS_TMP_BUFF_SIZE];
  int found;

  DBUG_ENTER("extsql_user_auth");

  // we search allowed users each time in case list changes
  if (strlen(authUsers) >= STATS_TMP_BUFF_SIZE-1) {
    sql_print_error("ExtSQL: user list has exceeded storage limits");
    return(0);
  }

  strcpy(tmpUser, authUsers);
  tPtr = tmpUser;
  found = 0;

  while ( (token = strtok(tPtr, ", ")) ) {
    
    tPtr = NULL;
    if (!strcmp(token, user) || !strcmp("root", user)) { // match
      found = 1;
      break;
    }
  } // end while - more user names

  if (!found) {    
    return(0);
  }

  return(1); // they are okay!

} // end extsql_user_auth

// Check for authorized user, enabled and active extsql
int extsql_check(THD* thd, int overrideActive) {

  char *user;

  DBUG_ENTER("extsql_check");

  if (Shared->extsql_disabled) { // just get out
    net_printf(thd, 0, "ExtSQL: disabled");
    DBUG_RETURN(1);
  }
  
  if (!(Shared->extsql_active || overrideActive)) { // just get out
    net_printf(thd, 0, "ExtSQL: deactivated");
    DBUG_RETURN(1);
  }

#ifdef PGSQL
  user = MyProcPort->user_name;
#else

#ifdef EXTSQL_50
  user = thd->security_ctx->user;
#else
  user = thd->user;
#endif

#endif

  // are they authorized
  if (!extsql_user_auth(user, extsql_users)) {

    // let the DBA know just in case.
    sql_print_warning("ExtSQL: attempted access by user: %s", user);
    net_printf(thd, 0, "ExtSQL: access not allowed to user: %s",
               user);
    DBUG_RETURN(1);
  }

  DBUG_RETURN(0);
}

// generic function to do output prep, identify column headers, types
// INPUT: col1 and col2 (col2 can be NULL)
// non-zero return on failure, one or two columns
// NOTE dest,tstate, and tupdesc are declared in the calling function,
// same vars will be used in call to extsql_output_2_col
// fill1 and fill2 are placeholders to keep function calls the same
int extsql_output_prep_2_col(char *col1, char *col2

#ifdef PGSQL
                             ,DestReceiver *dest, TupOutputState **tstate, TupleDesc tupdesc
#else
                             ,Protocol *protocol, List field_list,   char *fill2
#endif

) {

  if (!col1) {
    sql_print_error("ExtSQL: extsql_output_prep_2_col missing col1.");
    return(1);
  }
  
#ifdef PGSQL

  // tuple descriptor for up to two cols
  if (col2) {

    tupdesc = CreateTemplateTupleDesc(2, false);

    TupleDescInitEntry(tupdesc, (AttrNumber) 1, col1,
                       TEXTOID, -1, 0, false);
    TupleDescInitEntry(tupdesc, (AttrNumber) 2, col2,
                       TEXTOID, -1, 0, false);
  } else {
    tupdesc = CreateTemplateTupleDesc(1, false);
    TupleDescInitEntry(tupdesc, (AttrNumber) 1, col1,
                       TEXTOID, -1, 0, false);
  }

  // prepare for projection of tuples
  *tstate = begin_tup_output_tupdesc(dest, tupdesc);
  
#else
  Item *item;
  
  // column headers
  field_list.push_back(item = new Item_empty_string(col1, MAX_VAR_SIZE));
  if (col2) {
    field_list.push_back(item = new Item_empty_string(col2, MAX_VAR_SIZE));
  }
  
  // output the column headers
  if (protocol->send_fields(&field_list, PROTOCOL)) {
    sql_print_error("ExtSQL: output_prep_2_col protocol write failed");
    return(1);
  }

#endif

  return(0);

} // end extsql_output_prep_2_col


// generic function to send output rows (up to 2 cols, char data)
// INPUT: col1Str (value), col2Str maybe NULL
// NOTE tstate is declared in the calling function,
// same vars will be used in call to extsql_output_prep_2_col
// null strings will result in using integer values
int extsql_output_2_col(char *col1Str, int col1Int, char *col2Str, int col2Int

#ifdef PGSQL
                        ,DestReceiver *dest, TupOutputState *tstate
#else
                        ,Protocol *protocol, char *fill1
#endif


) {

  bool cleanup1 = false;
  bool cleanup2 = false;

#ifdef PGSQL
  char	   *values[2];
#endif

  // Convert ints to char*
  if (col1Str == NULL) {
    cleanup1 = true;
    col1Str = (char*)my_malloc(8 * sizeof (char), MYF(MY_WME | MY_ZEROFILL));
    sprintf(col1Str, "%d", col1Int);
  }

  if (col2Str == NULL) {
    cleanup2 = true;
    col2Str = (char*)my_malloc(8 * sizeof (char), MYF(MY_WME | MY_ZEROFILL));
    sprintf(col2Str, "%d", col2Int);
  }

#ifdef PGSQL


  // assign to the values array ZZ - not done
  if (col1Str) {
    values[0] = col1Str;
  }

  if (col2Str) {
    values[1] = col2Str;
  }

  // send it to dest 
  do_tup_output(tstate, values);
 

#else
  protocol->prepare_for_resend();
  if (col1Str) {
    protocol->store(col1Str, strlen(col1Str), system_charset_info);
  } else { // must be int
    protocol->store((longlong) col1Int);
  }

  if (col2Str) { 
    protocol->store(col2Str, strlen(col2Str), system_charset_info);
  } else { // must be int
    protocol->store((longlong) col2Int);
  }
  
  if (protocol->write()) {
    sql_print_error("ExtSQL: output_2_col protocol write failed");
    return(1);
  }  
#endif

  // cleanup dynamic memory allocation
  if (cleanup1) { my_free(col1Str, MYF(0)); }
  if (cleanup2) { my_free(col2Str, MYF(0)); }
  
  return(0);
  
} // end extsql_output_2_col

// generic function to handle transmission end
void extsql_output_complete(

#ifdef PGSQL
             THD *thd, TupOutputState *tstate
#else
             THD *thd, char *tstate
#endif

             ) {

#ifdef PGSQL
  end_tup_output(tstate);
#else
  
  send_eof(thd);

#endif

} // end extsql_output_complete


// called from the parser in response to a SHOW STATISTICS command.
// wild is what is given in the LIKE 'wild'
int mysql_show_extsql(THD *thd, 
                           const char *stats_class, 
#ifdef PGSQL
                           DestReceiver *protocol,
                           List *stats_vars,
#else
                           List  &stats_vars,
#endif
                           uint stats_hourly,
                           const char *wild, 
#ifdef PGSQL
                           char *stats_order_var,
#else
                           const char *stats_order_var,
#endif
                           const uint stats_order_dir,
                           const uint stats_order_limit,
#ifdef PGSQL
                           char *stats_where_var,
#else
                           const char *stats_where_var,
#endif
                           const uint stats_where_num,
                           const uint stats_where_char)  
{
#ifdef PGSQL

#define VALUE_LIMIT 100
  TupOutputState *tstate;
  TupleDesc	tupdesc =  NULL;

#ifdef PGSQL_8
  ListCell *item;
#else
  List *item;
#endif

  int listCount;
  char *values[MAX_VARS_IN_CLASS], *valuesPtr[VALUE_LIMIT], valueBuff[256]; // output
  int ptrIndex = 0;
  int system_charset_info = 0; // not used in PGSQL, just placeholder, call our func.

#else

  Item *item;
  List field_list;  // has column headings, all strings.
  List    check_vars; 
  Protocol *protocol= thd->protocol;
  LEX_COLUMN *column;
  char *tstate = NULL; char *tupdesc = '\0'; // placeholders for tstate and tupdesc

#endif

  char *columnName; // used in output for MySQL PGSQL 
  int res = 0;
  int namedVars = 0;  // are they asking for specific vars
  char listBuff[MAX_BUFF_SIZE], timeBuff[DATE_SIZE];  
  char *varListPtr = listBuff;
  int timeLimit = 0, currentTime = 0, indexTime = 0, varIndex = 0;
  char *varName, *termChar;
  int varNameLength = 0, timeCount = 0, rowCount = 0, whereVarAddedList = 0;

  pSTATS_CLASS_DATA stats_class_data;
  STAT_VAR **statPtr;
  int i, maxVar, maxTime, instance, maxInstance, var, found=0, value = 0, skip = 0,
    rowHasData;
  char *user;
  MYSQL_TIME mysql_time;

#ifdef EXTSQL_50
  user = thd->security_ctx->user;
#else

#ifdef PGSQL
  user = MyProcPort->user_name; 
#else
  user = thd->user;
#endif
#endif

  DBUG_ENTER("mysql_show_extsql");

  // CHECK FOR DATA DUMPS OR TESTS
  if STATS_DEBUG(STATS_DUMP) {
    extsql_stats_dump();
    extsql_thread_dump(thd, 1);

  } else if STATS_DEBUG(STATS_DUMP_ONCE) { // just once
    extsql_stats_dump();
    extsql_thread_dump(thd, 1);
    Shared->extsql_debug -= STATS_DUMP_ONCE; // clear 

  } 

  if STATS_DEBUG(STATS_TEST_ONE) {
    
    Shared->extsql_active = 0;
    Shared->extsql_debug -= STATS_TEST_ONE; // clear 
    extsql_stats_dump();
    if (extsql_reload(STATS_WRITE_RELOAD, Shared->extsql_reload_file)) {
      sql_print_error("ExtSQL: Error encountered during reload WRITE");
    }
    extsql_stats_dump();
    Shared->extsql_active = 1;
    
  } 
  
  if STATS_DEBUG(STATS_TEST_TWO) {
    
    Shared->extsql_debug -= STATS_TEST_TWO; // clear 
    extsql_stats_dump();

    if (0) {
      extsql_init(Shared->extsql_class_list, STATS_INIT_RELOAD, Shared->extsql_reload_file);
    } else {
      extsql_init(Shared->extsql_class_list, STATS_INIT_RESET, Shared->extsql_reload_file);
    }
    
    extsql_stats_dump();
    
  } // end if-else testing


  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 0)) DBUG_RETURN(1);

  // okay, statistics are active, prepare for output.  
  if STATS_DEBUG(STATS_SHOW_PARAMS) {
    sql_print_information("ExtSQL: internal variables class %s,\
  hourly %d, wild %s, ORDER BY %s, direction %d, LIMIT %d, \
  WHERE  var %s, compare %d, value %d",
                          stats_class, stats_hourly, wild,
                          stats_order_var,
                              stats_order_dir, stats_order_limit,
                          stats_where_var, 
                          stats_where_char, stats_where_num);
  }

  if (stats_class == NULL) { // they want help/status
    
    pSTATS_CLASS_DATA stats_class_data;
    STAT_VAR **statPtr;


    char *Usage = "Usage";
    char *Help  = "SHOW STATISTICS (* | Var[,Var]) FROM Class [LIKE 'Instance']";
    char *Help2 = "     [WHERE Var ('<'|'>'|'=') num] [ORDER BY Var] [HISTORY] [LIMIT rows_or_time]";
#ifdef EXTSQL_50
    char *Help3 = "also available from INFORMATION_SCHEMA, SHOW TABLES FROM INFORMATION_SCHEMA for list.\
ExtSQL reported as part of EXTSTATS_(Class) tables.";
#endif 
    char *Version = "Version";
#ifdef EXTSQL_BINARY
    char *Key   = "License Key";
#endif
    char *Users = "Stat Users";
    char *Blank = " ";
    int maxVar, maxTime, maxInstance, var, instance, classCount;

#ifdef PGSQL
    if (extsql_output_prep_2_col("Item", "Value", protocol, &tstate, tupdesc)) {
      DBUG_RETURN(1);
    }
#else
     if (extsql_output_prep_2_col("Item", "Value", protocol, field_list, tupdesc)) {
      DBUG_RETURN(1);
    }   
#endif

    if (extsql_output_2_col(Usage, 0, Help, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }  

    if (extsql_output_2_col(Usage, 0, Help2, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }                   
 
#ifdef EXTSQL_50
    if (extsql_output_2_col(Usage, 0, Help3, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }                   
#endif                 

    if (extsql_output_2_col(Version, 0, Shared->extsql_version+1, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }  
    
#ifdef EXTSQL_BINARY
    if (extsql_output_2_col(Key, 0, extsql_key, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }  
#endif

    if (extsql_output_2_col(Users, 0, extsql_users, 0, protocol, tstate)) {
      DBUG_RETURN(1);
    }  

    if STATS_DEBUG(STATS_PID) { // show the pid of the server for gdb
      sprintf(listBuff, "(%d)", getpid());
      if (extsql_output_2_col("Server PID", 0, listBuff, 0, protocol, tstate)) {
        DBUG_RETURN(1);
      }  
    }
      
    // now we show them the class list stuff
    classCount = 0;
    for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
         stats_class_data++, classCount++) {
      
      maxVar = stats_class_data->maxVar; 
      maxTime = stats_class_data->maxTime-1; 
      maxInstance = stats_class_data->maxInstance;

      if (extsql_output_2_col(Blank, 0, Blank, 0, protocol, tstate)) {
        DBUG_RETURN(1);
      }

      // how many instances are in use
      for (instance=0; 
           instance < maxInstance && stats_class_data->instanceIndex[instance]; 
           instance++) {
      }

      sprintf(listBuff, "Max instances/in use (%d/%d), Max vars (%d), Max time (%d), Time units(%c)",
              maxInstance, instance, maxVar, maxTime, stats_class_data->timeUnits);

      if (extsql_output_2_col(stats_class_data->name, 0, listBuff, 0, protocol, tstate)) {
        DBUG_RETURN(1);
      }  

      strcpy(listBuff, " ");
      
      for (var=0, statPtr = stats_class_data->varIndex; 
           (var < maxVar) && *statPtr ; 
           var++, statPtr++) {
        
        sprintf(listBuff+strlen(listBuff),"%s ", (*statPtr)->name);
      }
      
      if (extsql_output_2_col(stats_class_data->name, 0, listBuff, 0, protocol, tstate)) {
        DBUG_RETURN(1);
      }

    } // end for - all clasees


    extsql_output_complete(thd, tstate);
    DBUG_RETURN(0);  

  } // end if - just showing help
  // NOTE - normal RETURN ONLY here!


  // find the instance list for the class we are looking for
  found = 0;
  for (stats_class_data = Shared->statData; stats_class_data->maxInstance; 
       stats_class_data++) {
    if (!strcmp(stats_class_data->name, stats_class)) { // found it
      found = 1;
      break;
    }
  } // end for 
  
  if (!found) {
    net_printf(thd, 0, "ExtSQL: can't find requested class %s, enter SHOW STATISTICS for usage.",
               stats_class);
    DBUG_RETURN(1);
  }

  // AT THIS POINT stats_class_data points to the requested class 
  maxVar = stats_class_data->maxVar; 
  maxTime = stats_class_data->maxTime; 
  maxInstance = stats_class_data->maxInstance; // needed for array indexing


  // NOTE - a LOT of checking first to make sure inputs make sense.

  if (stats_order_var) { // they used ORDER BY
    net_printf(thd, 0, "ExtSQL:  ORDER BY not supported in this version.");
    DBUG_RETURN(1);
  }
  
  // CHECK that the limit is NOT larger than allowed if done for time
  if (stats_hourly && stats_order_limit >= maxTime) {
    net_printf(thd, 0, "ExtSQL, limit of %d is greater than stored time for this class of %d.",
               stats_order_limit, maxTime-1);
    DBUG_RETURN(1);
  }

  // CHECK BLOCK - check the where character
  if (stats_where_char) {

    switch (stats_where_char) {
      
    case '>':
    case '<':
    case '=':
      break;
      
    default:
      net_printf(thd, 0, "ExtSQL: unsupported comparison (%c), enter SHOW_STATISTICS for usage.", stats_where_char);
      DBUG_RETURN(1);
      
    } // end switch
  }

  // Set listCount to the number of Var column headings we will have
#ifdef PGSQL

  // For PGSQL we need to count the columns what we have ahead of time
  listCount = 0; namedVars = 0;
  foreach(item, stats_vars) {
    
    char *var1 = strVal(lfirst(item));

    if (!strcmp(var1, "statistics") ) { // ZZ - not really best way to check, default list

      // loop through for count
      for (var=0, statPtr = stats_class_data->varIndex; 
           (var < stats_class_data->maxVar) && *statPtr ; 
           var++, statPtr++) {  
        listCount++;
      }
      break;
    }

    listCount++;   
    namedVars = 1; // redundant
  } // end for - all vars in list

#else

  if (stats_vars.elements) {
    namedVars = 1;
  }

#endif

  // CHECK BLOCK - if they gave us specific columns (namedVars=1) to display, 
  // check those vars are actually being tracked
  if (namedVars) {



#ifdef PGSQL

    foreach(item, stats_vars) { // output the column heading

      columnName = strVal(lfirst(item));
#else

    List_iterator  stats_column(stats_vars);
    
    while ((column=stats_column++)) { // search for a match in class

      columnName = (char *)column->column.ptr();
#endif

      found = 0;
      for (var = 0, statPtr = stats_class_data->varIndex;
           (var < maxVar) && *statPtr ; 
           var++, statPtr++) {

        if (strlen(columnName) > MAX_VAR_SIZE - 5) {
          net_printf(thd, 0, "ExtSQL: Var is too long");
          DBUG_RETURN(1);
        }

        if (!strcasecmp((*statPtr)->name, columnName)) { // ZZ - low case input in postgres?
          found = 1;
          break;
        }
      } // end for - all vars
      
      if (!found) {
        net_printf(thd, 0, "ExtSQL: can't find Var %s, SHOW STATISTICS for usage.",
                   columnName);
        DBUG_RETURN(1);
      }

    } // end while/for - more columns

#ifdef NEVER__  
    }  // to keep indent level correct in emacs
#endif

  } // end if - named vars are being tracked.


  // CHECK BLOCK - if we have an ORDER by or WHERE clause variable, check those also
  if (stats_where_var || stats_order_var) {

#ifdef PGSQL

    List *checkList;

    if (stats_where_var && stats_order_var) {
      checkList = (List *) list_make2(makeString(stats_where_var), makeString(stats_order_var));

    } else if (stats_where_var) {
      checkList = (List *) list_make1(makeString(stats_where_var));

    } else {
      checkList = (List *) list_make1(makeString(stats_order_var));
    }

    foreach(item, checkList) { // check the list
      
      columnName = strVal(lfirst(item));  

#else
    if (stats_where_var) {
      String *tmpStr = new (thd->mem_root) String ((const char *)stats_where_var, strlen(stats_where_var), system_charset_info);
      check_vars.push_back(new LEX_COLUMN (*tmpStr, 0));
    }
    
    if (stats_order_var) {
      String *tmpStr = new (thd->mem_root) String ((const char *)stats_order_var, strlen(stats_where_var), system_charset_info);
      check_vars.push_back(new LEX_COLUMN (*tmpStr, 0));
    }
  
    List_iterator  stats_column(stats_vars);
    
    while ((column=stats_column++)) { // search for a match in class
      
      columnName = (char *)column->column.ptr();

#endif

      found = 0;
      for (var = 0, statPtr = stats_class_data->varIndex;
           (var < stats_class_data->maxVar) && *statPtr ; 
           var++, statPtr++) {
        
        // make sure the name isn't too long
        if (strlen(columnName) > MAX_VAR_SIZE - 5) {
          net_printf(thd, 0, "ExtSQL: Var (%s) is too long, check entry.",
                     columnName);
          DBUG_RETURN(1);
        }
        
        if (!strcasecmp((*statPtr)->name, columnName)) {
          found = 1;
        break;
        }

      } // end for - all vars       
    
      if (!found) {
        net_printf(thd, 0, "ExtSQL: can't find Var %s, enter SHOW STATISTICS for usage.",
                   columnName);
        DBUG_RETURN(1);
      }
    } // end while - more columns

#ifdef NEVER__  
    }  // to keep indent level correct in emacs
#endif

  } // end if - specified where or order by var
  
  // START OUTPUT - prepare and output the header columns
  // The class name (always FIRST column)
  // PGSQL uses a predefined array of column headers, need to keep count
  // MYSQL uses a stack approach.

#ifdef PGSQL
  if (stats_hourly) {
    listCount += 2;  // class name and time
  } else {
    listCount++; // add just one for the class name
  }
  i = 1;  // current param position

  tupdesc = CreateTemplateTupleDesc(listCount, false);

  TupleDescInitEntry(tupdesc, (AttrNumber) i++, stats_class, TEXTOID, -1, 0, false); 

#else
  field_list.push_back(item = new Item_empty_string(stats_class, STATS_MAX_NAME));
#endif

  // if HOURLY is asked for, it is always the SECOND column
  if (stats_hourly) {
    char *units;
    switch (stats_class_data->timeUnits) {
    case 'm':
      units = "minutes";
      break;
    case 'h':
      units = "hours";
      break;
    case 'd':
      units = "days";
      break;
    default:
      units = "UNKNOWN";
    }

#ifdef PGSQL
    TupleDescInitEntry(tupdesc, (AttrNumber) i++, units, TEXTOID, -1, 0, false);
#else
    field_list.push_back(item = new Item_empty_string(units, 30)); // max size of 'minutes'
#endif

  }

  // Then the variable list, this will either be what they specified, or
  // the conf list.  We do a SPECIAL check to see if they have included a
  // WHERE clause.  If they have, but it is not included in the column list
  // we will add it, but it will not be displayed on output

  // LARGE else block here depending on whether they specified list, or we use default!
  strcpy(varListPtr,","); // init the list

  if (namedVars) { // they identified the columns they want

    char tempName[MAX_VAR_SIZE];   // ZZ

#ifdef PGSQL
    foreach(item, stats_vars) { // output the column heading

      columnName = strVal(lfirst(item));
#else

    List_iterator  stats_column2(stats_vars);    
    while ((column=stats_column2++)) { // search for a match in class

      columnName = (char *)column->column.ptr();
#endif

      if (strlen(columnName) > MAX_VAR_SIZE - 5) {
        net_printf(thd, 0, "ExtSQL: Var is too long");
        DBUG_RETURN(1);
      }
     

#ifdef PGSQL
       TupleDescInitEntry(tupdesc, (AttrNumber) i++, columnName, TEXTOID, -1, 0, false);
      
#else
      field_list.push_back(item = new Item_uint(columnName, MY_INT64_NUM_DECIMAL_DIGITS ));
#endif

      strcat(varListPtr, columnName);
      strcat(varListPtr, ",");
      
    } // end while - more columns
#ifdef NEVER__  
    }  // to keep indent level correct in emacs
#endif

    // is the where var on the list
    if (stats_where_var) {
      if (strlen(stats_where_var) > MAX_VAR_SIZE - 5) {
        net_printf(thd, 0, "ExtSQL: WHERE Var too long");
        DBUG_RETURN(1);
      }
      strcpy(tempName, ",");
      strcat(tempName, stats_where_var);
      strcat(tempName, ",");
      if (strstr(varListPtr,tempName)) {
        whereVarAddedList = 0;
      } else {
        whereVarAddedList = 1;
        strcat(varListPtr, stats_where_var);
        strcat(varListPtr,",");
      }
    } // end if - where clause


  } else  { //  we output default column headings and create varListPtr
          
    // loop through the var names for headings
    for (var=0, statPtr = stats_class_data->varIndex; 
         (var < stats_class_data->maxVar) && *statPtr ; 
         var++, statPtr++) {      
      
#ifdef PGSQL
      TupleDescInitEntry(tupdesc, (AttrNumber) i++, (*statPtr)->name, TEXTOID, -1, 0, false); 
#else
      field_list.push_back(item = new Item_uint((*statPtr)->name, MY_INT64_NUM_DECIMAL_DIGITS  ));
      item->maybe_null=1;
#endif
      strcat(varListPtr,(*statPtr)->name );
      strcat(varListPtr, ",");

      // check to make sure we don't bust string
      if (strlen(varListPtr) > MAX_BUFF_SIZE - 50) {
        net_printf(thd, 0, "ExtSQL: Var list too long");
        DBUG_RETURN(1);
      }
    } // end for - all instances
  } // end if - use defaults

  // output the column headers
#ifdef PGSQL
  tstate = begin_tup_output_tupdesc(protocol, tupdesc);
#else
  if (protocol->send_fields(&field_list, PROTOCOL)) {
    DBUG_RETURN(1); 
  }

#endif

  // AT THIS POINT the output columns are in varListPtr
  // next, if they will be asking for hourly output, we prepare an array
  // of hour indexes.
  if STATS_DEBUG(STATS_SHOW_PARAMS) {
    sql_print_information("ExtSQL: varListPtr is (%s), whereVarAddedList(%d)", 
                          varListPtr, whereVarAddedList);
  }

  timeLimit = 1; // by default we only show one time.
  currentTime = stats_class_data->time; // time we are currently logging to.

  if (stats_hourly) { // reset the proper inner loop limits
    if (stats_order_limit) {
      timeLimit = stats_order_limit; // whatever thy wanted
    } else {
      timeLimit = maxTime-1; // they get it all
    } // end if else - time limit
  } // end if - user wants hours displayed.
 

  // AT THIS POINT - we have the varListPtr area with the oolumns in the
  // display order we want.  Remember, zero index holds the total. The
  // currentTime has our present location and it is circular, i.e. if
  // maxTime for the class is 4, we store current + 3.  If the currentTime
  // is 2, the proper way to display all hours, starting with the most
  // recent is to show 2(current),1(prior),3(more prior) - skip 0

  // the currentTime has no correlation to actual clock time, it is just
  // a position within a circular buffer of times, each class could have
  // a different limit.
  
  // START outputing data for the every instance within the matching class  
  // we now go through all the instances.
  for (instance=0; instance < maxInstance && stats_class_data->instanceIndex[instance] ; 
       instance++) {
    
    if (wild && wild_case_compare(system_charset_info,
                                  stats_class_data->instanceIndex[instance], wild) ) {
      
      continue;  // skip this one
    } // end if - wild match
    
    // are we over our output LIMIT
    if (!stats_hourly && stats_order_limit) { // not doing times, put limit on general output
      if (rowCount >= stats_order_limit) {
        break;
      }
    }

    // loop through the times, if stats_hourly is not set we just print the stuff for zero
    // time, the grand totals
    // if stats_timely is set we will output a number of hours starting at currentTime, and
    // limited by timeLimit (starts at 1).
    for (timeCount = 0, indexTime = stats_hourly ? currentTime : 0 ;
         timeCount < timeLimit  && indexTime >= 0;
         timeCount++, indexTime--  )  {
      
      // if we are outputing times, we have to adjust our indexTime
      if (stats_hourly && !indexTime) { // we have gone back to the total 
        indexTime = maxTime - 1; // pick the last
      }

      // prepare  the data row
      skip = 0;

#ifdef PGSQL
      // nothing special required here
      // NOTE, variable i keeps our index in values array
      i = 0;
#else
      protocol->prepare_for_resend();
#endif
      res = 0;
      rowHasData = 0;  // do we have anything to show, only on HISTORY (stats_hourly set)

      // first column - instance name - always
#ifdef PGSQL
      values[i++] = stats_class_data->instanceIndex[instance];
#else
      protocol->store(stats_class_data->instanceIndex[instance], 
                      strlen(stats_class_data->instanceIndex[instance]), system_charset_info);
#endif

      // if we are doing times, second column is times - always, we use indexTime
      if (stats_hourly) {
        value = (int) stats_class_data->timeLabels[indexTime];

        // convert to local time and then to a string for output
#ifdef PGSQL
        // ZZ need time conversion/zone
        mysql_time = value;
#else
        thd->variables.time_zone->gmt_sec_to_TIME(&mysql_time, (my_time_t) value);
#endif

        my_datetime_to_str(&mysql_time, timeBuff);
        
        // we normally chop seconds off the time, from: 2008-04-04 14:59:48  TO 2008-04-04 14:59
        if (!STATS_DEBUG(STATS_TIME_DETAIL)) {
          timeBuff[strlen(timeBuff) - 3] = '\0';
        }
#ifdef PGSQL
        values[i++] = timeBuff; 
#else
        protocol->store(timeBuff, strlen(timeBuff), system_charset_info);
#endif
   
      } // end if - hourly

      // loop through varListPtr, output the vars in the proper order
      // varListPtr = ",bytes,selects," varName will move through this
      for ( varName = varListPtr; *varName ; varName = strstr(varName, ",")) {

        varName++; // move off the comma
        //sql_print_information("   target varName is (%s)", varName);

        if (! *varName) { // we are done
          break;
        }

        varNameLength = strchr(varName, ',') - varName;
    
        // okay, varName points at a name, lets go through the index looking
        // for a match
        found = 0;
        value = 0;

        for (varIndex=0, statPtr = stats_class_data->varIndex; 
             varIndex < maxVar && *statPtr;
             varIndex++, statPtr++) { 
          
          termChar = (*statPtr)->name + varNameLength +1; // point to end of the current var name

          if (!strncasecmp( (*statPtr)->name, varName, varNameLength)) {
            found = 1;
            value = stats_class_data->data [ARRAY_INDEX(instance, varIndex, maxVar,
                                                        indexTime, maxTime)]; 

            // before we send, check the value if they have set a WHERE condition
            if (stats_where_var && !strncasecmp((*statPtr)->name, stats_where_var, varNameLength)) { // match

              switch (stats_where_char) {

              case '>':
                if (value <= stats_where_num) skip = 1;  // this row is no good
                break;
                
              case '<':
                if (value >= stats_where_num) skip = 1;  // this row is no good
                break;

              case '=':
                if (value != stats_where_num) skip = 1;  // this row is no good
                break;

              default:

                sql_print_warning("ExtSQL: unsupported comparison (%d)", stats_where_char);

              } // end switch

              rowHasData = 1;

            } // end if - match using WHERE clause

            if (skip) {
              break;
            }

            if (whereVarAddedList && !strncasecmp((*statPtr)->name, stats_where_var, varNameLength)) {
              break; // don't output the where column, they didn't ask for it
            }

#ifdef PGSQL
            // convert to text, store ptr addr and then free
            // after send
            sprintf(valueBuff, "%d",  value);
            values[i] = strdup(valueBuff);
            valuesPtr[ptrIndex++] = values[i++]; // need to free after output
            if (ptrIndex >= VALUE_LIMIT) {
              sql_print_error("ExtSQL, output value limit reached");
              DBUG_RETURN(1);
            }
#else
            protocol->store((longlong) value);
#endif

            if (value) { // is it non-zero
              rowHasData = 1;
            }
            break;
          } // end if - matching

        } // end for - a matching var in the list

        if (skip) {
          break;
        }

        if (!found) {

#ifdef PGSQL
          values[i++] = "n/a"; 
#else
          value=999999;
          protocol->store((longlong) value);
#endif

        }

      } // end for - requested values

      if (skip || (stats_hourly && !rowHasData)) { // free it up!

        // if PGSQL we allocate some value space above, make sure we free it in all situations.
#ifdef PGSQL
        if (ptrIndex) {
          for (ptrIndex--; ptrIndex; ptrIndex--) {
            if (valuesPtr[ptrIndex]) {
              free(valuesPtr[ptrIndex]);
            }
          } 
        }
#endif   
        continue;
      }


      // send the row to the user,

#ifdef PGSQL
      do_tup_output(tstate, values);

      if (ptrIndex) {
        for (ptrIndex--; ptrIndex; ptrIndex--) {
          if (valuesPtr[ptrIndex]) {
            free(valuesPtr[ptrIndex]);
          }
        }
      }
#else
      if (protocol->write()) {
        sql_print_error("ExtSQL, protocol write failed");
        DBUG_RETURN(1);
      }
#endif

      rowCount++; // keep track of the count if have a general LIMIT in effect.
  
    } // end for - all requested times

  } // end for - all instances
  

  extsql_output_complete(thd, tstate);
  DBUG_RETURN(0);

} // end mysql_show_statistics


// record a statistic_increment call
void extsql_inc(ulong *V, ulong C)
{

#ifdef PGSQL
  THD *thd = MyProc;
  time_t log_sec, now_sec;
#else
  THD *thd = current_thd;
  my_time_t log_sec, now_sec;
#endif

  pSTATS_CLASS_DATA stats_class_data;
  STAT_VAR **var_ptr;
    int i, var, offset, offsetLimit, classCount, found = 0, instance, maxVar, maxTime;
  int threadID, command;
  char *user = "", *host = "", *db = "", *proc_info = "";
  int *statVerify = 0, *indexPtr = 0;

  if (!Shared->extsql_active || Shared->extsql_disabled || STATS_DEBUG(STATS_DISABLE_INC)) {
    return;
  }

#ifdef EXTSQL_50
  // during init we stored just offsets from THD structure status_var to each stat
  // for nonglobal items -- we got an address in V, need to determine if it is a real global
  // or something incremented as part of a thread.  We check to see if it is in the 
  // address range of the status_var structure of the current thread
  char *off;
  off = (char *) V;
  if (off >= (char *)&(thd->status_var) && off < (char *)&(thd->status_var)+sizeof(thd->status_var)) {
    V =  (ulong *)(off - (char *)&(thd->status_var)) ;
    if STATS_DEBUG(STATS_INC_VAR) sql_print_information("         altered to: 0x%x, base: 0x%x", 
                                                        V, &(thd->status_var));
  }
#endif

  // check against the list of Vars we are tracking
  for (i = 0; i < Shared->varAddrCount; i++) {
    if (Shared->varAddrTrackArray[i] == (char *) V) { // one we are tracking
      found = 1;
      break;
    }
  } // end for - all tracked vars

  if (!found) {
    return;
  }

#ifndef PGSQL
  // ZZ ignore slow_launch_threads and early byte counts - unstable at this point
  if (V == (ulong *) &slow_launch_threads || thd->command == COM_CONNECT) {
    return;
  }
#endif

  // input checks 
  if (!thd 
#ifndef PGSQL 
      || !thd->thread_id
#endif
      ) {  // don't do thread zero for now
    if STATS_DEBUG(STATS_INC_PARAMS) {
      sql_print_information("ExtSQL: Got null thread or thd 0");
    }
    return;
  }

  extsql_init_proc_vars(&threadID, &command, &user, &db, &host, &proc_info, &indexPtr, &statVerify);

 // we need something for these values
  if (!user) {
    user="(null)";
  }

  if (!host) {
    host="(null)";
  }
   
  if (*statVerify != STATS_MAGIC_NUM) { 
    Shared->extsql_counter++; // ZZ - how many do we get?
    return;
  } // end if - badmagic


  // too many warnings, turn it off
  if (Shared->extsql_counter > STATS_WARNING_LIMIT) {
    Shared->extsql_active = 0;
    sql_print_warning("ExtSQL: deactivated due to high internal warning count(%d), dumping internals",
                      (int)Shared->extsql_counter);
    extsql_stats_dump();
    return;
  } 

  if STATS_DEBUG(STATS_THREAD_CHK) {
    extsql_thread_dump(thd, 0); // do a check of indexes, no output unless error
  }
  
  // we get time in seconds, for performance we only call localtime once/minute to do the min,hour,day conversions
  // could use thd->start_time, always avail? ZZ
  now_sec = time(NULL);
  
  if (Shared->lastMin != (int)(now_sec/60)) { // we rolled over

    // let's try and get the lock, if we fail, someone else doing rollover
    if  (pthread_mutex_trylock(&Shared->LOCK_stats_clock)) { 
      // nothing to do
 
    } else {
      
      struct tm now_date;
      localtime_r(&now_sec, &now_date);

      Shared->lastMin = (int)(now_sec/60);      
      
      // check all time for classes
      classCount = 0;
      for (stats_class_data = Shared->statData; 
           classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
           stats_class_data++, classCount++) {
        
        int now_time;
        
        switch (stats_class_data->timeUnits) {
        case 'm':
          now_time = now_date.tm_min;
          break;
      
        case 'h':
          now_time = now_date.tm_hour;
          break;
      
        case 'd':
          now_time = now_date.tm_mday;
          break;
      
        default:
          now_time = now_date.tm_hour;
          Shared->extsql_active = 0;
          sql_print_error("ExtSQL: deactivated, got invalid time unit (%d)", stats_class_data->timeUnits);

#ifdef PGSQL
          pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
          VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
          return;
        }
      
        if (now_time != stats_class_data->lastTime) { // rolled over
          stats_class_data->lastTime = now_time;
        
          // increment time and do checks        
          stats_class_data->time++;
        
          if (stats_class_data->time >= stats_class_data->maxTime) { // reset to start
            stats_class_data->time = 1;   // zero index is used for totals  
          }

          // create label, MM/DD HH:MM
        
          if STATS_DEBUG(STATS_TIME_CHANGE) {
            sql_print_information("ExtSQL, class %s, time change: cur clock time is %d, time index s %d",
                                  stats_class_data->name, now_time, stats_class_data->time);
          }
        
          // reset counters for that time to zero for all variables
          for (instance=0, maxVar = stats_class_data->maxVar, maxTime = stats_class_data->maxTime; 
               instance < stats_class_data->maxInstance; instance++) { 
            for (var=0; var < maxVar; var++) {
            
              offset = ARRAY_INDEX(instance, var, maxVar, stats_class_data->time, maxTime);
              offsetLimit = stats_class_data->dataEnd - stats_class_data->data;
            

              // check to make sure offset isn't out of limits
              if (offset < 0 || offset >= offsetLimit ) {
                sql_print_warning("ExtSQL: warning, TIME reset offset of 0x%x (%d,%d,%d,%d,%d) for 0x%x class %s of thd %d/%s exceeds bound of 0x%x(0x%x)",
                                  offset, instance, var, maxVar, stats_class_data->time, maxTime, 
                                  (uint)V, stats_class_data->name, threadID, command_name[command], offsetLimit, *statVerify);
                Shared->extsql_counter++;
                extsql_thread_dump(thd, 1);
#ifdef PGSQL
                pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
                VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
                return;
              }
            
              stats_class_data->data[offset] = 0;

              // store unix time
              // we normally round it off to minutes/hours/days depending on units
              if (!STATS_DEBUG(STATS_TIME_DETAIL)) {

                switch (stats_class_data->timeUnits) {
                case 'm':
                  log_sec = now_sec - (now_sec % 60);
                  break;
                  
                case 'h':
                  log_sec = now_sec - (now_sec % 3600);
                  break;
                  
                case 'd':
                  log_sec = now_sec - (now_sec % 3600);
                  break;
                  
                default:
                  now_time = now_date.tm_hour;
                  Shared->extsql_active = 0;
                  sql_print_error("ExtSQL: deactivated, got invalid time unit (%d)", stats_class_data->timeUnits);
                  return;
                }

              } else {

                log_sec = now_sec;

              } // end if/else  - don't round off time
                
              stats_class_data->timeLabels[stats_class_data->time] = log_sec;            
            
            } // end for - vars
          } // end for - instances
        } // end if - new time 
      
      } // end for - all classes
      
#ifdef PGSQL
      pthread_mutex_unlock(&Shared->LOCK_stats_clock);
#else
      VOID(pthread_mutex_unlock(&Shared->LOCK_stats_clock));
#endif
    } // end if-else - got the lock
  } //end if - time rolled over
  

  // ready to increment value
  if STATS_DEBUG(STATS_INC_VAR)   sql_print_information("ExtSQL: 0x%x by %d", (uint)V,(int)C);

  // look for the value being tracked in all classes
  classCount = 0;
  for (stats_class_data = Shared->statData; 
       classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
       stats_class_data++, classCount++) {

    var_ptr = stats_class_data->varIndex;
    for (var = 0,  maxVar = stats_class_data->maxVar, maxTime = stats_class_data->maxTime
           ; var < maxVar ; var_ptr++,var++) {
      if ((*var_ptr)->addr == (char *)V) { // we are tracking it
       
        i = indexPtr[(int)classCount];

        if (i < 0 || i >= stats_class_data->maxInstance) { // bound exceeded
          sql_print_warning("ExtSQL: index (%d) bad value (0x%x/0x%x), V(0x%x), classCount(%d), thd(%d/%s/%s) user(%s), host(%s) db(%s) magic(0x%x)",
                            classCount, i, stats_class_data->maxInstance, (uint) V, classCount, 
                            threadID, proc_info, command_name[command],
                            user, host, db, *statVerify);
          Shared->extsql_counter++;
          extsql_thread_dump(thd, 1);
          return;
        }

        // increment it for the class for the 0 (summary time) and the current time
        offset = ARRAY_INDEX(i, var, maxVar, stats_class_data->time, maxTime);
        offsetLimit = stats_class_data->dataEnd - stats_class_data->data;

        if STATS_DEBUG(STATS_INC_VAR) {
          sql_print_information("ExtSQL, class %s, i (%d) incrementing %s by (%d), offset/Limit(%d/%d)", 
                                stats_class_data->name, i, (*var_ptr)->name, (int) *V, offset, offsetLimit);
        }
        
        // check to make sure offset isn't out of limits
        if (offset < 0 || offset >= offsetLimit ) {
           sql_print_warning("ExtSQL: warning, DATA inc offset of 0x%x (%d,%d,%d,%d,%d) for 0x%x class %s of thd %d/%s exceeds bound of 0x%x(0x%x)",
                             offset, i, var, maxVar, stats_class_data->time, maxTime, 
                             (uint)V, stats_class_data->name, threadID, command_name[command], offsetLimit, *statVerify);
          Shared->extsql_counter++;
          extsql_thread_dump(thd, 1);
          return;
        }

        stats_class_data->data[offset] += C; 
        
        offset = ARRAY_INDEX(i, var, maxVar, 0, maxTime);
        stats_class_data->data[offset] += C; 
        break;  // check the next class
      } // end if - match
    } // end for - all vars

  } // end for - all classes

} // end function extsql_inc

#ifdef EXTSQL_BINARY
#include "../../../../../../../bin/checkParam.h"
#endif


// extsql_prep - invoked to set/confirm start type
// OUTPUT: startType - STATS_INIT_FAILURE, _NORMAL, _RESET, _RELOAD
//         it will log an error message on _FAILURE
// INPUT:  confFile - may be NULL (startType = NORMAL).  If present the files is scanned
//         for new global extsql vars: _class_list, _users, _reload_file, _debug, _license_key
//         and the startType is set to _RESET.
//         reload_file - may be NULL.  If present and contents is valid, startType
//         set to _RELOAD, file is scanned to make sure hostname and class list matches.
//         NO global vars are changed!!
// NOTE:   At present only ONE of confFile and realodFile may be set, error otherwise.
//         
void extsql_prep(int *startType, const char *confFile, char *reloadFile) {

  // if no extsql_reload_file is present:
  //   mysql: reloadFile == 0
  //   pgsql: reloadFile[0] == 0

  extsql_init_globals();

  if (! STATS_DEBUG(STATS_ADMIN) ) { // always normal
    *startType = STATS_INIT_NORMAL;
    return;
  }

#ifdef PGSQL
  if (!*startType && *reloadFile) *startType = STATS_INIT_RELOAD;
#else
  if (!*startType && reloadFile) *startType = STATS_INIT_RELOAD;
#endif

  if (*startType == STATS_INIT_RELOAD && reloadFile != NULL) {

    // read reload file
    File reloadHandle;
    char *memPtr = NULL, *srcPtr = NULL, *tmpPtr = NULL, *tmpPtr2 = NULL;
    int size, i, fieldLength;
    char *rfVersion = NULL, *rfClassList = NULL, *rfHostname = NULL;
    char *extsql_class_listNoWS;    char hostname[FN_REFLEN];

    if (gethostname(hostname, FN_REFLEN) < 0) {
      strcpy(hostname, "localhost");
    }

    if ((reloadHandle = my_open(reloadFile, O_RDONLY|O_BINARY, MYF(0))) < 0 ) {
      sql_print_information("ExtSQL: Specified reload_file could not be opened: %s", reloadFile);
      *startType = STATS_INIT_FAILURE;
      return;
    }

    if (!(size = extsql_read_section_bytes(reloadFile, reloadHandle, &memPtr, 
                                           "RELOAD", 0))) {
      sql_print_error("ExtSQL: Error reading from file %s", reloadFile);
      *startType = STATS_INIT_FAILURE;
      return;
    }  

    // find version, classlist and hostname
    for (srcPtr = memPtr, i = 0; i < size; srcPtr++, i++) {
 
      if (!strncmp(srcPtr, "host: ", 6)) { // found hostname
        tmpPtr = srcPtr + strlen("host: ");
        fieldLength = 0;
        while (*tmpPtr != '\n') {
          ++fieldLength;
          ++tmpPtr;
        }

        rfHostname = strndup(srcPtr + strlen("host: "), fieldLength);
      }

      if (!strncmp(srcPtr, "EXT     version: ", 17)) { // found extsql version
        tmpPtr = srcPtr + strlen("EXT     version: ");
        fieldLength = 0;
        while (*tmpPtr != '\n') {
          ++fieldLength;
          ++tmpPtr;
        }

        ++fieldLength; // we want the trailing '\n' here

        rfVersion = strndup(srcPtr + strlen("EXT     version: "), fieldLength);
      }

      if (!strncmp(srcPtr, "extsql_class_list: ", 19)) { // found class list
        tmpPtr = srcPtr + strlen("extsql_class_list: ");
        fieldLength = 0;

        while (*tmpPtr != '\n') {
          ++fieldLength;
          ++tmpPtr;
        }

        rfClassList = strndup(srcPtr + strlen("extsql_class_list: "),
                              fieldLength);
      }
    }

    // by here, all pointers should be set, or we've got a badly
    // formatted reload file
    if (!(rfVersion && rfClassList && rfHostname)) {
      sql_print_error("ExtSQL: Malformatted reload file %s", reloadFile);
      *startType = STATS_INIT_FAILURE;
      return;
    }

    // check for mismatches that are okay, but require confirmation
    if (strcmp(rfHostname, hostname)) {
      sql_print_information("ExtSQL: hostname mismatch (file: %s, server: %s)",
                            rfHostname, hostname);
      *startType = STATS_INIT_CONFIRM;
      return;
    }

    if (strcmp(rfVersion, Shared->extsql_version+1)) {
      sql_print_information("ExtSQL: version mismatch (file: %s, server: %s)",
                            rfVersion, Shared->extsql_version+1);
      *startType = STATS_INIT_CONFIRM;
      return;
    }


    // we need a duplicate of extsql_class_list without whitespace
    extsql_class_listNoWS = extsql_strdup(Shared->extsql_class_list, MYF(MY_WME));
    for (tmpPtr = Shared->extsql_class_list, tmpPtr2 = extsql_class_listNoWS;
         *tmpPtr != '\0'; ++tmpPtr) {

      if (!(*tmpPtr == ' ' || *tmpPtr == '\t'))
        *tmpPtr2++ = *tmpPtr;       
    }

    *tmpPtr2 = '\0';

    if (strcmp(rfClassList, extsql_class_listNoWS)) {
      sql_print_information("ExtSQL: class list mismatch (file: %s, server: %s)",
                            rfClassList, extsql_class_listNoWS);
      *startType = STATS_INIT_CONFIRM;
      return;
    }

    my_free(extsql_class_listNoWS, MYF(0));
    my_free(rfHostname, MYF(0));
    my_free(rfVersion, MYF(0));
    my_free(rfClassList, MYF(0));
    my_free(memPtr, MYF(0));
    
  } else if (*startType == STATS_INIT_RESET && confFile != NULL) {

    char line[2048];
    FILE* conf = fopen(confFile, "r");

    // parse conf file
    if (conf == NULL) {
      sql_print_error("ExtSQL: Unable to open conf file %s", confFile);
      *startType = STATS_INIT_FAILURE;
      return;
    }

    while (fgets(line, 1024, conf) != NULL) {

      // lowercase directive name and strip all whitespace
      bool foundEqual = false;
      int equalPos = 0, readPos = 0, writePos = 0;
      char *directive, *data;

      while (line[readPos] != '\0') {

        if (line[readPos] == '=') {
          foundEqual = true;
          equalPos = writePos;
        }

        if (!foundEqual && line[readPos] > 64 && line[readPos] < 91)
          line[readPos] += 32;

        if (!(line[readPos] == '\t' || line[readPos] == ' ' ||
              line[readPos] == '\n' || line[readPos] == '"' ||
              line[readPos] == '\'')) {
          line[writePos] = line[readPos];
          ++writePos;
        }

        ++readPos;        
      } // end while line

      line[writePos] = '\0';

      if (strncmp(line, "extsql_", 7)) continue;

      directive = strndup(line, equalPos);
      data = line + (equalPos + 1) * (sizeof(char));

      // set the appropriate data member
      if (!strcmp(directive, "extsql_class_list"))
        Shared->extsql_class_list = extsql_strdup(data, MYF(MY_WME));
      else if (!strcmp(directive, "extsql_users"))
        extsql_users = extsql_strdup(data, MYF(MY_WME));
      else if (!strcmp(directive, "extsql_reload_file"))
        Shared->extsql_reload_file = extsql_strdup(data, MYF(MY_WME));
      else if (!strcmp(directive, "extsql_debug"))
        Shared->extsql_debug = atoi(extsql_strdup(data, MYF(MY_WME)));
      else if (!strcmp(directive, "extsql_key")) {
        extsql_key = extsql_strdup(data, MYF(MY_WME));
        sql_print_information("ExtSQL: extsql_key %s", data);
      }
      else {
        sql_print_error("ExtSQL: Unknown configuration directive: %s",
                        directive);
        *startType = STATS_INIT_FAILURE;
      }
      
    } // end while read conf

  } else {
    *startType = STATS_INIT_NORMAL;
  }
}



// extsql_init_globals - MySQL uses threads for each server, Postgres uses a separate process.
// Shared memory is used to speed our operations, data seg is shared between threads in MySQL,
// but separate in postgres.  To keep common code we need to alloc the area for our globals,
// store in section //GLOBALS a top of this file.  Not really needed to MySQL, but doesn't hurt.
// BIG code change, need to switch all refs to pointers!
// my_malloc is ifdef'd for PGSQL to do the shared memory magic!
// Return 1 on failure
int extsql_init_globals() {

#ifdef PGSQL   
  // we allocate a big hunk of shared memory
  bool memFound = false;

  ShmemPtr = (char *) ShmemInitStruct("ExtSQL Data", SHMEM_SIZE, &memFound);

  if (!ShmemPtr) {
    sql_print_error("ExtSQL extsql_init_globals: can't init shared mem area of %d bytes", SHMEM_SIZE);
    return(1);
  }

  if STATS_DEBUG(STATS_DUMP_START) {
    sql_print_information("ExtSQL extsql_init_globals:  ShmemPtr at 0x%x, memFound(%d)", (uint)ShmemPtr, memFound);
  }

  if (memFound) { // issue warning, should have only been called once!

    // set our two pointers again
    MemLock = (slock_t *) ShmemPtr; // already initialized.
    NextFree = (char **) ShmemPtr + sizeof(slock_t *); 
    return(0);  // ZZ - not fatal?
  }

  // if we get here, FIRST and ONLY time through for original allocation.
    
  // our MemLock and NextFree live at the start of the region.
  // this is needed for the PGSQL version of extsql_malloc
  MemLock = (slock_t *) ShmemPtr;
  SpinLockInit(MemLock);
  NextFree = (char **)ShmemPtr + sizeof(slock_t *); 
  *NextFree = (char *)NextFree + sizeof(NextFree);

  // okay alloc the global space
  Shared = (pGLOBAL) extsql_malloc(sizeof(GLOBAL), MYF(MY_WME | MY_ZEROFILL) );
  if (!Shared) {
    sql_print_error("ExtSQL extsql_init_globals: can't alloc shared area.");
    return(1);
  }

  Shared->extsql_class_list = extsql_strdup(extsql_class_list, MYF(MY_WME));
  Shared->extsql_key = extsql_strdup(extsql_key, MYF(MY_WME));
  Shared->extsql_users = extsql_strdup(extsql_users, MYF(MY_WME));
  Shared->extsql_version = extsql_strdup(extsql_version, MYF(MY_WME));      
  Shared->extsql_reload_file = extsql_strdup(extsql_reload_file, MYF(MY_WME));

#else
  static int didInit = 0;

  if (!didInit) {
    // THIS IS MYSQL, the Shared structure is static and already declared.
    Shared->extsql_class_list = extsql_class_list;
    Shared->extsql_key = extsql_key;
    Shared->extsql_users = extsql_users;
    Shared->extsql_version = extsql_version;      
    Shared->extsql_reload_file = extsql_reload_file;
    didInit = 1;
  } else {
    return(0);
  }

  (void) pthread_mutex_init(&Shared->LOCK_stats_clock,MY_MUTEX_INIT_FAST);
#endif

  // take care of copying some startup global values
  Shared->extsql_active = extsql_active;
  Shared->extsql_counter = extsql_counter;
  Shared->extsql_debug = extsql_debug;


  if STATS_DEBUG(STATS_DUMP_START) {
    sql_print_information("ExtSQL: Globals are active(%d), debug(%d), extsql_class_list(%s), extsql_version(%s), extsql_users(%s), extsql_reload_file(%s)",
                          (int)Shared->extsql_active, (int)Shared->extsql_debug, Shared->extsql_class_list,
                          Shared->extsql_version, Shared->extsql_users, Shared->extsql_reload_file);
  }


  return(0);

} // end extsql_init_globals


// Called to initialize tracking data structures, potentially reload with prior data.
// We may be here from a NORMAL init, first time, or a RESET in the middle of a server run 
// or a RELOAD from a start or the middle of server run.
// extsql_prep has already been invoked, so global extsql_ vars are set
int extsql_init(char *extsql_classlist, int startType, char *reloadFile) 
{
  int extsql_willbe_active = 1; // track state during init process, global extsql_active is
                                // always set to off during init.
  int reloadFail = 0;
  
  //extsql_class_list is a comma separated list of which define what we will
  // be tracking, number of instances, times of data, and which variables
  //     "db,max-5,time-10,units-d(Com_show_tables,Com_show_variables),
  //      user,max-50,time-24,units-m(Bytes_sent,Bytes_received)"
  //   class_name, max-instance_limit, max-time_limit, units-(min|day|hour) (var_list)
  char *parse_string, *malloc_addr;
  char *token, *tmpPtr;
  pSTATS_CLASS_DATA stats_class_data;
  STAT_VAR **var_ptr = 0;
  struct show_var_st *status_var_ptr = status_vars;
  int found = 0, skip = 0, foundParen = 0, num = 0, i = 0, j = 0, size = 0, classCount = 0, totalSize = 0;
  int thisDebug = 0; char timeUnit;
  
  sql_print_information("ExtSQL start type(%s): %s",  startTypeName[startType], Shared->extsql_version+1);

  // allocate our global area
  if (extsql_init_globals()) {
    sql_print_error("ExtSQL: start aborted.  Failed to alloc global shared mem.");
    return(1);
  }

  // Shared->extsql_class_list = my_strdup(extsql_class_list, MYF(MY_WME));
  // Shared->extsql_reload_file = my_strdup(extsql_reload_file, MYF(MY_WME));


  Shared->extsql_active = 0; // will be set at end of this function if returned to active


  if (startType == STATS_INIT_FAILURE) {
    sql_print_error("ExtSQL: disabled.  Start aborted due to prior errors");

    // We do not set Shared->extsql_disabled, may just be a failed reload/reset, okay to try again.
    return(1);
  }


#ifdef EXTSQL_BINARY

  int status, days;

  // license sequence is ONLY area for returns before end of function!
  if (!extsql_key) {
    sql_print_error("ExtSQL: disabled. No license key found for binary build, make sure to define ExtSQL_key in /etc/my.cnf.");
    Shared->extsql_active = 0;  // turn it all off
    Shared->extsql_disabled = 1;
    return(1);
  }

  if (status = checkParam(Shared->extsql_version+1, extsql_key, &days)) {
    sql_print_error("ExtSQL: disabled.  Improper license key found(%s), code %d.", 
                    Shared->extsql_version+1, status);
    Shared->extsql_active = 0;  // turn it all off
    Shared->extsql_disabled = 1;  // turn it all off
    return(1);
  }
    
  if (days == 99999) { 
    sql_print_information("ExtSQL: Permanent license verified.");
    
  } else if (days < 31) {

    // limited time
    sql_print_warning("ExtSQL: Evaluation license good for server startup only %d more days.", days);

  } else {

    // expired
    sql_print_error("ExtSQL: disabled.  Evaluation period has expired.");
    Shared->extsql_disabled = 1;  // turn it all off
    Shared->extsql_active = 0;  // turn it all off
    return(1);

  }    

#endif

  if (!extsql_users) {
    sql_print_information("ExtSQL: no defined user list, only root user allowed");
    extsql_users="root";
  } else {
    sql_print_information("ExtSQL: access allowed only to users: %s", extsql_users);
  }

  if (!extsql_class_list) {
    extsql_willbe_active = 0;  // turn it all off
    Shared->extsql_disabled = 1;  // turn it all off
    Shared->extsql_active = 0;  // turn it all off
    sql_print_information("ExtSQL: deactivated by user, no extsql_class_list defined");
    return(1);

  } else {
    extsql_willbe_active = 1;
  }
  
  //init our var addr tracking array to unstored
  bzero((char *) Shared->varAddrTrackArray, sizeof(Shared->varAddrTrackArray));
  Shared->varAddrCount = 0;

  // if we are here because of a RESET, there should be something in the statAllocArray
  // from prior - check to make sure -- if _NORMAL, we zero the whole thing.
  if (startType != STATS_INIT_NORMAL) {   // _RELOAD or _RESET
    
    // wait just a bit for all threads to know we've stopped
    Shared->extsql_reload_in_progress = 1;
    sleep(2);
   
    if (startType == STATS_INIT_RESET && ( ! Shared->statsAllocArray[0] || ! Shared->allocIndex) ) { // empty!
      sql_print_error("ExtSQL: deactivated, RESET request failed, no prior data found.");
      extsql_willbe_active = 0;
      
    } else { // free up any existing for RELOAD or RESET
      
      char *addr;
      for (i = 0, addr = Shared->statsAllocArray[i]; (addr = Shared->statsAllocArray[i]) && i < STATS_MAX_NUM_ALLOC; i++) {
        my_free(addr, MYF(0)); 
      }
    } // end if - memory clear necessary

  } else {

    Shared->extsql_reload_in_progress = 0;

  } // end if-else startType other than NORMAL


  bzero((char *) Shared->statsAllocArray, sizeof(Shared->statsAllocArray));
  Shared->allocIndex = 0; // for recording mem allocations
  bzero((char *) &Shared->statData, sizeof(Shared->statData)); // zero out all the contents, support restart - reload
  
  if STATS_DEBUG(STATS_DUMP_START) sql_print_information("ExtSQL tracking classes: %s", extsql_class_list); //DEBUG
  
  if (!(parse_string = extsql_strdup(extsql_class_list, MYF(MY_WME)))) {
    Shared->extsql_active = 0;
    Shared->extsql_disabled = 1;
    Shared->extsql_reload_in_progress = 0;
    sql_print_error( "ExtSQL: disabled, unable to dup extsql_class_list(%s)",
                     extsql_class_list);
    return(1);
    
  } 
    
  if (startType == STATS_INIT_NORMAL || startType == STATS_INIT_RESET) { // init structures on RESET/NORMAL starts, skip on RELOAD
      
    int i = 0; // track our position in statVars
    Shared->statsAllocArray[Shared->allocIndex++] = parse_string;
    malloc_addr = parse_string;
    
    
    while ( (token = strtok(parse_string, ", ")) ) { // start new loop on each class name
      
      char **tPtr;
      parse_string = NULL;
      found = 0; num = 0;
      
      // should have a class name
      if (thisDebug) sql_print_information("ExtSQL, Class token is (%s)", token);
      
        
      // is it allowable
      for (tPtr = extsqlClassesSupported; *tPtr; tPtr++) {
        if (!strcmp(token, *tPtr)) {
          found = 1;
          break;
        }
      } // end for - allowable class names

      if (! found) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, bad class name (%s)", token);
        break;
      }

      // we have a valid class name, make sure it is not a dup
      found = 0;
      for (stats_class_data = Shared->statData; stats_class_data->name[0]; stats_class_data++) {
        if (!strcmp(token, stats_class_data->name)) {
          found = 1;
          break;
        }
      } // end for - existing classes

      if (found) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, duplicate class name (%s)", token);
        break;
      }

      // okay we have a good name, start loading the structure
      strncpy(stats_class_data->name, token, STATS_MAX_NAME-1);

      // set our starting time
      stats_class_data->time = 0;


      // get the next token, should be instance limit
      if (! (token = strtok(parse_string, ", "))) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, incomplete class definitions, started with (%s)", 
                        extsql_class_list);
        break;
      }

      // should start with max-
      if (strncmp(token, "max-", 4)) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing max instance amount, got (%s)", 
                        token);
        break;
      }

      // get the number for max instances and allocate space
      num = atoi(token+4);
      if (!num) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing valid instance amount, got (%s)", 
                        token);
        break;
      }
      stats_class_data->maxInstance = num;

      // we null term it by getting one more than required
      if (!(stats_class_data->instanceIndex =  (char **)extsql_malloc( ((num+1) * sizeof(char *)),
                                                                   MYF(MY_WME | MY_ZEROFILL)) )) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, can't allocate class instance memory");
        break;
      }
      Shared->statsAllocArray[Shared->allocIndex++] = (char *)stats_class_data->instanceIndex;


      // good instance limit, now get time limit
      if (! (token = strtok(parse_string, ", "))) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, incomplete class definitions, missing time limit, started with (%s)", 
                        extsql_class_list);
        break;
      }

      // should start with time-
      if (strncmp(token, "time-", 5)) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing max time amount, got (%s)", 
                        token);
        break;
      }

      // get the number for max times
      num = atoi(token+5);
      num += 1; // always the zero hour - totals
      if (!num || num < 2) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing valid time amount, got (%s)", 
                        token);
        break;
      }
      stats_class_data->maxTime = num;

      // allocate space  ZZ add to size count reported
      if (!(stats_class_data->timeLabels = (my_time_t *)extsql_malloc( ((num+1) *  sizeof(my_time_t)),
                                                                   MYF(MY_WME | MY_ZEROFILL)) )) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, can't allocate class time label memory");
        break;
      }
      Shared->statsAllocArray[Shared->allocIndex++] = (char *) stats_class_data->timeLabels;

      // get the time Units, m - minutes, h - hours, d - days
      if (! (token = strtok(parse_string, ", "))) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, incomplete class definitions, missing timeUnits started with (%s)", 
                        token);
        break;
      }
        
      // should start with units-
      if (strncmp(token, "units-", 6)) {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing units amount, got (%s)", 
                        token);
        break;
      }

      // get the time units
      timeUnit = *(token+6);
      if (timeUnit == 'm') {
        stats_class_data->timeUnits = 'm';
      } else if (timeUnit == 'h') {
        stats_class_data->timeUnits = 'h';
      } else if (timeUnit == 'd') {
        stats_class_data->timeUnits = 'd';
      } else {
        extsql_willbe_active = 0;
        sql_print_error("ExtSQL: deactivated, missing valid time unit amount, got (%c)", 
                        timeUnit);
        break;
      }

      // set remaining vars
      stats_class_data->maxVar = 0;
      stats_class_data->lastTime = 0;
          
      // okay, next token is var list like: (Com_show_tables,Com_show_variables) OR
      //                                    (  Com_....  )
      // first and last token have parens
      // clear out tmp storage
      j = 0;  // keep track of var tokens
      i = 0;
      foundParen = 0;  // haven't seen closing paren
      while (!foundParen && (token = strtok(parse_string, ", ")) ) {

        if (thisDebug) sql_print_information("ExtSQL, variable token is (%s)", token);

        // none of the tokens should be close to MAX_VAR_SIZE
        if (strlen(token) > MAX_VAR_SIZE - 5) {
          extsql_willbe_active = 0;
          sql_print_error("ExtSQL, var name too long in var list (%s)", 
                          token);
          break;
        }

        // if we got a closing paren by itself, we're done
        if (!strcmp(")", token)) {
          break;
        }

        // should have a paren( on first one, move past that
        if (!j) {
          if (token[0] != '(') {
            extsql_willbe_active = 0;
            sql_print_error("ExtSQL, bad format in var list, missing ( in %s", 
                            token);
            break;
          }
            
          // okay, now we count the , from opening to closing paren, need to know
          // how many vars there are so we can allocate space for pointer array to them
          for (i = 0, num = 1, tmpPtr = token; *tmpPtr != ')'; tmpPtr++) {
            if (*tmpPtr == ',') {
              num++;
            }
          } // end for - token string
            
            // we null term pointer array by alloc of one extra
          if (!(stats_class_data->varIndex =  (STAT_VAR **)extsql_malloc( ((num+1) * sizeof(pSTAT_VAR)),
                                                                      MYF(MY_WME | MY_ZEROFILL)) )) {
            extsql_willbe_active = 0;
            sql_print_error("ExtSQL: deactivated, can't allocate class varIndex memory");
            break;
          }
            
          Shared->statsAllocArray[Shared->allocIndex++] = (char *) stats_class_data->varIndex;
          var_ptr = stats_class_data->varIndex; // pointer to array of pointer to each var we find below

          // check to see if got just a ')', in that case we get the next
          if (!strcmp(token, "(")) {
            token = strtok(parse_string, ", ");
          } else {
            token++; // move off paren
          }

          if (thisDebug) sql_print_information("ExtSQL, FIRST variable token is (%s)", token);

        } // end if - starting paren

        if (token[strlen(token) - 1] == ')') { // last one
          token[strlen(token) - 1] = '\0'; // null it out early
          foundParen = 1;
          if (thisDebug) sql_print_information("ExtSQL, LAST variable token is (%s)", token);
        }

        // unless the debug flag is set, check to see if allowed
        if (!STATS_DEBUG(STATS_EXTRA_VARS)) {
          char **varPtr; extsql_willbe_active = 0;
          for (varPtr = allowedVars; *varPtr; varPtr++) {
            if (!strcmp(*varPtr, token)) {
              extsql_willbe_active = 1;
              break;
            } // end if
          } // end for - all allowed
            
          if (!extsql_willbe_active) {
            sql_print_error("ExtSQL, requested tracking var (%s) not on approved list.  Please check spelling and documenation.  This setting can be overridden via debug flag (STATS_EXTRA_VARS)",
                            token);
            break;
          }
        } // end if - check approved vars


        j++;   // token count
          
        // we should now have a token, look for match in status_vars
        found = 0; skip = 0;
        for (status_var_ptr = status_vars;  status_var_ptr->name; status_var_ptr++) {
            
          if (thisDebug) sql_print_information("ExtSQL, status_var is %s(0x%x)", 
                                               status_var_ptr->name, (uint)status_var_ptr->value );

          if (!strcmp(token, status_var_ptr->name)) { // match
              
            if (!status_var_ptr->value && strcmp(token, "Questions")) { // no address assigned, not tracking this 
              // okay if Questions, we will fix below
                                           

#ifdef EXTSQL_50
              // okay if Bytes_received - first item
              if (strcmp(token,"Bytes_received")) { // only okay for Bytes_received
#endif
                sql_print_error("ExtSQL, variable (%s) has no address, can not be tracked",
                                status_var_ptr->name);
                extsql_willbe_active = 0;
                break;
#ifdef EXTSQL_50
              }
#endif
            } // end if - no address
            
            // allocate room for the structure itself, put the address in the pointer array
            if (!(*var_ptr =  (STAT_VAR *)extsql_malloc( (sizeof(STAT_VAR)),
                                                     MYF(MY_WME | MY_ZEROFILL)) )) {
              extsql_willbe_active = 0;
              skip = 1;
              sql_print_error("ExtSQL: deactivated, can't allocate class var memory");
              break;
            }
              
            Shared->statsAllocArray[Shared->allocIndex++] = (char *) *var_ptr;

            if (!((*var_ptr)->name = extsql_strdup(status_var_ptr->name, MYF(MY_WME)))) {
              extsql_willbe_active = 0;
              sql_print_error( "ExtSQL: deactivated, unable to alloc for (%s)",
                               status_var_ptr->name);
              break;
            } // end if - malloc

            Shared->statsAllocArray[Shared->allocIndex++] = (*var_ptr)->name;

#ifdef PGSQL
            (*var_ptr)->addr = (char *)status_var_ptr->value;
#else

            // SPECIAL here, if the value is Connections or Questions, we override with our internal
            // tracking variable address (connects or queries) -- special case for them.
            if (!strcmp(token, "Questions")) {
              (*var_ptr)->addr = (char *)&Shared->queries;
            } else if (!strcmp(token, "Connections")) {
              (*var_ptr)->addr = (char *)&Shared->connects;
            } else { // regular
              (*var_ptr)->addr = (char *)status_var_ptr->value;
            }
#endif
              
            found = 1;
            if (thisDebug) sql_print_information("ExtSQL: tracking varIndex[%d] var(%s), addr(0x%x)\n",
                                                 i, (*var_ptr)->name, (uint)(*var_ptr)->addr);
            (uint)stats_class_data->maxVar++;
            i++; 

            // store the addr in our quick look up array, only if its not already there
            if (extsql_store_var_addr((*var_ptr)->addr)) {
              extsql_willbe_active = 0;
              break;
            }
            
            break ; // look for next one
              
          } // end if - match
            
        } // end for - more status vars
          
        if (skip) { // we missed a variable that had a zero address, that is okay.
          continue;
        }
            
        if (! found ) { // could not complete match on one of their variables
          extsql_willbe_active = 0;
          sql_print_error( "ExtSQL: deactivated, could not complete variable match for (%s)",
                           token);
        }
          
        if (! extsql_willbe_active || ! stats_class_data->maxVar) { // had failure somewhere above
          extsql_willbe_active = 0;
          break;
        }
        var_ptr++;
 
      } // end while -  more tokens in var list
        
      if (! extsql_willbe_active) { // had a failure above
        break;
      }
      
    } // end while - more token in class definitions
      
  } else if (startType == STATS_INIT_RELOAD) {
    
    // make copy of class list in the event of failure
    char* oldClassList = extsql_strdup(extsql_class_list, MYF(MY_WME));

    if (extsql_reload(STATS_READ_RELOAD, reloadFile)) { // reload failed 
      
      sql_print_error("ExtSQL: Reload FAIL--resetting class list OLD(%s), NEW(%s)",
                            oldClassList, extsql_class_list);
      
      // restore old class list
      extsql_class_list = oldClassList;
      
      // reset
      startType = STATS_INIT_RESET;
      extsql_willbe_active = 1; // reload should log error 
      reloadFail = 1; // flag to return error status      
    }
 
  } // end if-else startType (NORMAL, RESET or RELOAD
  
  
  if (extsql_willbe_active) { // calculate data sizes for each class and print what we got
      size = 0;
      
      for (stats_class_data = Shared->statData, classCount = 0; 
           classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
           stats_class_data++, classCount++) {
        
        if STATS_DEBUG(STATS_DUMP_START) sql_print_information("ExtSQL, class %s, max inst %d, max vars %d, max time %d %d", 
                                                               stats_class_data->name, stats_class_data->maxInstance,
                                                               stats_class_data->maxVar, stats_class_data->maxTime,
                                                               stats_class_data->timeUnits);
        
        // loop through all vars
        for (var_ptr = stats_class_data->varIndex, i = 0; i < stats_class_data->maxVar;
             var_ptr++, i++) {
          if STATS_DEBUG(STATS_DUMP_START) sql_print_information("   Var %s at addr 0x%x", (*var_ptr)->name, (uint)(*var_ptr)->addr);
        } // end for all vars defined
        
        size = stats_class_data->maxInstance * stats_class_data->maxVar *
          stats_class_data->maxTime * sizeof(ulong);
        
        // if NOT RELOAD (a NORMAL OR RESET start) allocate data space
        if (startType != STATS_INIT_RELOAD) {
          if (!(stats_class_data->data  = (ulong *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)) )) {
            extsql_willbe_active = 0;
            sql_print_error("ExtSQL: deactivated, failed to alloc %d bytes for class %s data",
                            size, stats_class_data->name);
            break;
          }
        
          Shared->statsAllocArray[Shared->allocIndex++] = (char *)stats_class_data->data;
          stats_class_data->dataEnd = stats_class_data->data + size/sizeof(ulong);
        }
        
        // set end marker
        if STATS_DEBUG(STATS_DUMP_START) sql_print_information("   Data allocated %d bytes, data start at 0x%x, end at 0x%x", 
                                                               size, (uint)stats_class_data->data, (uint)stats_class_data->dataEnd);
        totalSize += size;

#ifdef PGSQL
        if (totalSize + SHMEM_RESERVE > SHMEM_SIZE) {
          extsql_willbe_active = 0;
          sql_print_error("ExtSQL: deactivated, projected mem usage (%d) too close to limit of %d",
                            size, SHMEM_SIZE);
        }
#endif

      } // end for - all classes
  } // end if - stats will be active
  
  if (startType != STATS_INIT_NORMAL) { // RELOAD or RESET, clear any stored thread instance info

#ifndef PGSQL
   
    // loop through ALL threads and zero indexes
    i = 1;
    while (pthread_mutex_trylock(&LOCK_thread_count)) { // didn't get it, do UNLOCK!!!!
      sleep(i++);
      sql_print_information("ExtSQL: Trying to completed RELOAD/RESET, can't get lock, sleeping for (%d)",i);
      if (i > 5) {
        extsql_willbe_active = 0;

        VOID(pthread_mutex_unlock(&LOCK_thread_count));
        break;
      }
    } // end while - get thread lock
    

    if (extsql_willbe_active) { // loop through all threads

        I_List_iterator it(threads);
        THD *tmp;
        int *indexPtr, *statVerify;
      
        while ((tmp=it++)) {
          extsql_thread_init(tmp, STATS_THD_INIT_ALL);
        } // end while - more threads

      } // end if - extsqlwillbeactive

      VOID(pthread_mutex_unlock(&LOCK_thread_count));
#endif
      
#ifdef NEVER__
    }
#endif

  } // end if - RELOAD or RESET
  
  if (extsql_willbe_active) {
    Shared->extsql_active = 1;
    sql_print_information("ExtSQL ACTIVE allocated memory: %d bytes for %d classes", 
                          totalSize, classCount); 
  }  else {
    sql_print_information("ExtSQL NOT ACTIVE");
    Shared->extsql_active = 0;
  }// end if - stats active

  Shared->extsql_reload_in_progress = 0; // in all cases.

  if (Shared->extsql_active && !reloadFail) {
    return(0);
  } else {
    return(1);
  }
  
} // end extsql_init


// extsql_init_proc_vars - is used to abstract the different locations in which both
// MySQL and Postgres store process/status info
// this is always with respect to the current thread -- not all are available for both!
static void extsql_init_proc_vars(int *threadID, int *command, char **user, char **db, char **host, 
                                  char **proc_info, int **indexPtr, int **statVerify) {

#ifdef PGSQL
  THD *thd = MyProc;
#else
  THD *thd = current_thd;
#endif
  
#ifdef PGSQL
  *user = MyProcPort->user_name;
  *statVerify = &thd->statVerify;
  *indexPtr = thd->statIndex;
  *proc_info = "n/a";
  *host = "n/a";
  *command = 1;
  *threadID = getpid();
#else

#ifdef EXTSQL_50
  *user = thd->security_ctx->user;
  *host = thd->security_ctx->host;
  *statVerify = &thd->security_ctx->statVerify;
  *indexPtr = thd->security_ctx->statIndex;
#else
  *user = thd->user;
  *host = thd->host;
  *statVerify = &thd->statVerify;
  *indexPtr = thd->statIndex;
#endif

  // more just MySQL
  *command = thd->command;
  *proc_info = (char *)thd->proc_info;
  *threadID = thd->thread_id;
#endif

#ifdef PGSQL
  *db = MyProcPort->database_name;
#else
  *db = thd->db;
#endif


} // end extsql_init_proc_vars 

// this function prints hex bytes from the startAddr for size
static void extsql_print_bytes(char *startAddr, int size) {

  char *tmpPtr;
  int i;
  char buff[120] = {'\0'};

  for (i = 0, tmpPtr=startAddr; i < size; tmpPtr++, i++) {

    if (!(i%8) && strlen(buff)) {
      sql_print_information("ExtSQL: 0x%x - %s", (uint)startAddr+i, buff);
      buff[0] = '\0';
    }
    sprintf(buff+strlen(buff), "0x%x (0x%x) / ", i, *tmpPtr);

  } // end for - entire string

  if (strlen(buff)) {
    sql_print_information("ExtSQL: 0x%x - %s", (uint)startAddr+i, buff);
  }

} // end extsql_print_bytes
    

// this utility function reds a section header in a
// RELOAD file and stores to address of memPtr, 
// as input if memPtr is zero, this function allocates memory and it must be
// freed by caller.  If memPtr is nonzero, we assume caller has allocated space!
// returns 0 on error,  hdr is the name of the header we should be reading
// num is context for an error.
int extsql_read_section_bytes(char *fileName, File fileDes, char **memPtr,
                                     char *hdr, int num) {

  char buff[80];
  int size;
  

  // read the section hdr
  if (my_read(fileDes, buff, STATS_SECTION_HDR, MYF(MY_WME)) != STATS_SECTION_HDR) {
    sql_print_error("ExtSQL: Section header byte count read failed on file %s, (%s/%d)",
                    fileName, hdr, num);
    return(0);
  }
  
  if (!strstr(buff, hdr)) { // missing
    sql_print_error("ExtSQL: Section header from file %s unexpected, got %s, wanted %s",
                    fileName, buff, hdr);
    return(0);
  }

  size = atoi(buff + STATS_SECTION_BYTES);

  if (size <= 0) {
    sql_print_error("ExtSQL: Section header bad size (%d) on file %s, (%s/%d)",
                    size, fileName, hdr, num);
    return(0);
  }


  if (*memPtr == NULL) { // alloc memory for the data
    if (!(*memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
      sql_print_error("ExtSQL: Could not alloc %d bytes to read data  on file %s, (%s/%d)",
                      size, fileName, hdr, num);
      return(0);
    }
  }

  if (my_read(fileDes, *memPtr, size, MYF(MY_WME)) != size) {
    sql_print_error("ExtSQL: Section header data read failed on file %s for %d bytes, (%s/%d)",
                    fileName, size, hdr, num);
    return(0);
  }
  
  return(size);
} // end extsql_read_section_bytes
  

// extsql_write_section_hdr - writes the header for a section to the RELOAD
// file.  The hdr should curretnly be 6 chars in length.  The size is the
// size of the data to be stored in that section.
// Returns non zero on error, puts msg in error log.
static int extsql_write_section_hdr(char *loadFileName, File reloadFile, char *hdr, int size) {

  char buff[60];

  if (strlen(hdr) != 6) {
    sql_print_error("ExtSQL: Bad header length for %s", hdr);
    return(1);
  }

  sprintf(buff, "\nEXT %s: %09d\n", hdr, size);

  if (strlen(buff) != STATS_SECTION_HDR) { // sanity check
    sql_print_error("ExtSQL: Bad length for %s section hdr,  %d/%d", 
                    hdr, STATS_SECTION_HDR, strlen(buff));
    return(1);
  }

  
  if (my_write(reloadFile, (byte*) buff, STATS_SECTION_HDR, MYF(MY_WME)) != STATS_SECTION_HDR) {
    sql_print_error("ExtSQL: Could not write %s header section to RELOAD file %s (%d/%d)", 
                    hdr, loadFileName, size, reloadFile);
    return(1);
  }
 
  return(0);

} // end - extsql_write_section_hdr


// manage reload data - this function take care of reading/write reload data. 
// WRITE_RELOAD: It creates the file used for the RELOAD command, most of it 
// consists of binary data, but the first few lines are in ASCII so that a 'head' can
// be done on the file to extract general info
// READ_RELOAD: Reads in file, sets globa statData and extsql_class_list to what is found
// in the file.
// IN: action (STATS_WRITE_RELOAD, STATS_READ_RELOAD)
// IN: loadFileName (name of reload file to read/write)
// GLOBALS: will read/write extsql_class_list and statData
int extsql_reload(int action, char *loadFileName) {

  File reloadFile;  // a file descriptor
  char buff[STATS_TMP_BUFF_SIZE];
  char *memPtr; // memPtr holds dynamic alloc, needs to be freed!
  my_time_t now_sec;
  MYSQL_TIME mysql_time;

#ifndef PGSQL
  THD *thd =  _current_thd();
#endif

  char dateBuff[80];
#define CLASS_SIZE 8192
  char classBuff[CLASS_SIZE];
  char *srcPtr, *destPtr, *actionStr, *className, **ptrPtr;

  pSTATS_CLASS_DATA stats_class_data;
  int maxVar, maxTime, maxInstance, var, found, instance, classCount, size, size2, mode, i;
  int numClasses;
  char hostname[FN_REFLEN];  // defined in my_global.h
 
  if (! STATS_DEBUG(STATS_ADMIN)) { // disabled
    sql_print_information(adminDisabled);
    return(0);
  }

  if (gethostname(hostname, FN_REFLEN) < 0) { // failed
    strcpy(hostname, "localhost");
  }

  // Shared->extsql_debug |= STATS_RELOAD;

  if (action == STATS_WRITE_RELOAD) { // set some params
    mode = O_WRONLY|O_CREAT|O_BINARY;
    actionStr = "write/store";
  } else if (action == STATS_READ_RELOAD) {
    mode = O_RDONLY|O_BINARY;
    actionStr = "read/restore";
  } else {
    sql_print_error("ExtSQL: unexpected reload request (%d)", action);
    return(1);
  }

  if (strlen(Shared->extsql_class_list) >= CLASS_SIZE) {
    sql_print_error("ExtSQL: extsql_class_list too long for configured limit, can't save RELOAD data.");
    return(1);
  }

  // open the file
  if ((reloadFile= my_open(loadFileName, mode, MYF(0))) < 0 ) {
    sql_print_error("ExtSQL: Unable to open %s for %s of RELOAD data, aborted (%d).", 
                    loadFileName, actionStr, errno);
    return(1);
  }
  sql_print_information("ExtSQL: (%s) Reload file is %s, Current active extsql_class_list is %s",
                        actionStr, loadFileName, Shared->extsql_class_list);

  // get the current date/time, do any conversions
  now_sec = time(NULL);

#ifdef PGSQL
        // need time convesion ZZ
  mysql_time = now_sec;
#else
  thd->variables.time_zone->gmt_sec_to_TIME(&mysql_time, (my_time_t) now_sec);
#endif

  my_datetime_to_str(&mysql_time, dateBuff);

  // strip ALL blanks from current class_list, count classes
  numClasses = 0;
  for (srcPtr=Shared->extsql_class_list, destPtr=classBuff; *srcPtr; srcPtr++) {
    
    if (*srcPtr != ' ' && *srcPtr != '\t') {
      *destPtr++ = *srcPtr;
    }

    if (*srcPtr == ')') {
      numClasses++;
    }
  } // end for - all chars

  // null term
  *destPtr = '\0';
  
  // write/read the header lines first, following format, designed to allow grep "EXT" to show
  //  useful info:
  //
  // line 0 - "\nEXT RELOAD: 000000025\n   (ALL section headers, 12 char prefix, 9 char size
  // line 1 - "EXT data stored on:  date/time\n"
  // line 2 - "EXT      From host:  bit.bongo.com\n"
  // line 3 - "EXT extsql version:  version\n"
  // line 4 - "EXT\n"
  // line 3 - "EXT ExtSQL extsql_class_list: db,max-130,time-100,units-m,(Binlog_cache_disk_use,Binlog_cache_use...\n"
  // line 5 - "EXT\n";

  if (action == STATS_WRITE_RELOAD) {

    sprintf(buff, "\
EXT data stored: %s\n\
EXT   from host: %s\n\
EXT     version: %s\n\
EXT num classes: %d\n\
EXT\n\
EXT extsql_class_list: %s\n\
EXT\n", dateBuff, hostname, Shared->extsql_version+1, numClasses, classBuff);
    
 
    if (extsql_write_section_hdr(loadFileName, reloadFile, "RELOAD", strlen(buff))) {
      return(1);
    }
  
    if (my_write(reloadFile, (byte*) buff, strlen(buff), MYF(MY_WME)) != strlen(buff)) {
      sql_print_error("ExtSQL: Could not %s header data to RELOAD file %s", actionStr, loadFileName);
      return(1);
    }
    
  } else { // READ

    char *classListStart;
    
    memPtr = NULL; // read_section_bytes will alloc space
    if (!(size = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                           "RELOAD", 0))) {
      return(1);
    } 

    // find the stored class list.
    found = 0;
    for (srcPtr = memPtr, i = 0; i < size; srcPtr++, i++) {
      if (!strncmp(srcPtr, "extsql_class_list:", 18)) { // found it
        found = 1;
        classListStart = srcPtr + strlen("extsql_class_list: ");
        break;
      }
      
    } // end for - all chars in header
    
    if (!found) {
      sql_print_error("ExtSQL: Could not find class info in %s, section size (%d) (%s)", 
                      loadFileName, size, memPtr);
      my_free(memPtr, MYF(0));
      return(1);
    }

    // NULL term the class list
    for (i = 0; *(srcPtr+i) != '\n'; i++) ;      
    *(srcPtr+i) = '\0'; // null term

    // check for new class list
    if (strncmp(classListStart, classBuff, strlen(classBuff)) ||
        strlen(classListStart) != strlen(classBuff)) {

      char *numClassPtr;
      
      if (!(Shared->extsql_class_list = extsql_strdup(srcPtr, MYF(MY_WME)))) {
        sql_print_error("ExtSQL: reload failed, unable to alloc space for new class list");
        return(1);
      }

      // check the number of classes present
      numClasses = 0;
      if (!(numClassPtr = strstr(memPtr, "num classes: "))) {
        sql_print_error("ExtSQL: failed to find class count in reload file");
        return(1);
      }
      numClasses = atoi(numClassPtr + strlen("num classes: "));
      

      sql_print_information("ExtSQL: %d classes found, new class list will be loaded: %s", 
                            numClasses, Shared->extsql_class_list);
 
    } // end if - new class list
    
    my_free(memPtr, MYF(0));

  } // end if - else (first section)

  if STATS_DEBUG(STATS_RELOAD) {
    sql_print_information("ExtSQL: during %s on %s, completed section RELOAD", actionStr, hostname);
  }
    
  // loop and read/write each class data to/from the RELOAD file  
  classCount = 0;
  for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && classCount < numClasses; 
       stats_class_data++, classCount++) {

    STAT_VAR **statPtr;
      
    
    // -- below section repeated for each class
    // -- write the binary data in sections, each unique section preceded by byte count that follows
    // line 6 - "\nEXT CLAS n: 000000000\n"   (class number, 0..n
    //        -  structure STATS_CLASS_DATA

    sprintf(buff, "CLAS %1d", classCount);

    if (action == STATS_WRITE_RELOAD) { 

      if (extsql_write_section_hdr(loadFileName, reloadFile, buff, sizeof(STATS_CLASS_DATA))) {
        return(1);
      }

      if (my_write(reloadFile, (byte*) stats_class_data, sizeof(STATS_CLASS_DATA), 
                   MYF(MY_WME)) != sizeof(STATS_CLASS_DATA)) {
        sql_print_error("ExtSQL: Could not %s class %d header data to RELOAD file %s", actionStr, 
                        classCount, loadFileName);
        return(1);
      }
      
    } else { // READ
      
      memPtr = (char *) stats_class_data;
      if (!extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                    buff, classCount)) {
        return(1);
      }
      
    }
    
    className = stats_class_data->name;
    maxVar = stats_class_data->maxVar; 
    maxTime = stats_class_data->maxTime; 
    maxInstance = stats_class_data->maxInstance;


    // sanity check, make sure the className is on our supported list
    for (i = 0, ptrPtr=extsqlClassesSupported; i < STATS_MAX_CLASSES; i++, ptrPtr++) {

      found = 0;
      if (! strcmp(className, *ptrPtr)) {
        found = 1;
        break;
      }
    } // end for - all supported classes
    
    if (!found) {
      sql_print_error("ExtSQL: Bad class name found (%s)", className);
      return(1);
    }

    // print out summary info on the CLASS
    if STATS_DEBUG(STATS_RELOAD) {
      sql_print_information("ExtSQL: During reload (%s), starting section %s", actionStr, buff);
      sql_print_information("CLASS is %s, maxInstance(%d), maxVar(%d), maxTime(%d), time(%d), \
timeUnits(%c), lastTime(%d)",
                            stats_class_data->name, maxInstance, maxVar, maxTime,
                            stats_class_data->time, stats_class_data->timeUnits,
                            stats_class_data->lastTime);
    }

    //        - "\nEXT INSTNC: 3400\n"
    //        - instanceNames 1..n from char **instanceIndex

    if (action == STATS_WRITE_RELOAD) {

      // count the size of what we have
      size = 0;
      for (instance = 0; instance < maxInstance  && stats_class_data->instanceIndex[instance];
         instance++) {
        size += strlen(stats_class_data->instanceIndex[instance]) + 1; // don't forget the NULL!
      } // end for - all class instances
      
      if (!(memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
        sql_print_error("ExtSQL: Could not alloc %d bytes to write instance data for class %d",
                        size, classCount);
        return(1);
      }

      // write the data to memPtr
      size = 0;
      for (instance = 0; instance < maxInstance  && stats_class_data->instanceIndex[instance];
           instance++) {
        strcpy(memPtr + size, stats_class_data->instanceIndex[instance]);

        if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: INSTNC (%d/%s)", 
                                                             instance, memPtr+size);

        size += strlen(stats_class_data->instanceIndex[instance]);
        size++;  // don't forget the NULL!

      } // end for - all class instances
      
      
      if (extsql_write_section_hdr(loadFileName, reloadFile, "INSTNC", size)) {
        my_free(memPtr, MYF(0));
        return(1);
      }

      if (my_write(reloadFile, (byte*) memPtr, size, 
                   MYF(MY_WME)) != size) {
        sql_print_error("ExtSQL: Could write instance data to RELOAD file %s for %d", 
                        loadFileName, classCount);
        my_free(memPtr, MYF(0));
        return(1);
      }  

      my_free(memPtr, MYF(0));

    } else {  // READ

      memPtr = NULL;
      if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                              "INSTNC", classCount))) {
        return(1);
      }

      // allocate memory to hold the instanceIndex array!
      if (!(stats_class_data->instanceIndex =  (char **)extsql_malloc( ((maxInstance+1) * sizeof(char *)),
                                                                   MYF(MY_WME | MY_ZEROFILL)) )) {
        sql_print_error("ExtSQL: Could not alloc space for instanceIndex, RELOAD file %s for %d",
                        loadFileName, classCount);
        return(1);
      }
       

      // move the data from buffer to memory, it is a long sequence of null
      // terminated instance names.
      // careful, we limit ourselves based not on the max supported, but how many
      // instances we really had.
      size = 0;
      for (instance = 0, srcPtr = memPtr; instance < maxInstance && srcPtr - memPtr < size2; instance++) {

        size = strlen(srcPtr) + 1; // don't forget the NULL
          
        if (!(destPtr = extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
          sql_print_error("ExtSQL: Memory alloc failed on instance(%d), class(%d)",
                          instance, classCount);
          my_free(memPtr, MYF(0));
          return(1);
        }
        
        strcpy(destPtr, srcPtr);
        stats_class_data->instanceIndex[instance] = destPtr;
        srcPtr += size;

        if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: INSTNC (%d/%s)", 
                                                             instance, stats_class_data->instanceIndex[instance]);

        // sql_print_information("ExtSQL: On read, instance(%d) at addr (0x%x), at (%s)", instance, destPtr, destPtr);
      } // end for - all instance names

      my_free(memPtr, MYF(0));

    }

    if STATS_DEBUG(STATS_RELOAD) {
      sql_print_information("ExtSQL: during %s, completed section INSTNC, class (%d)", 
                            actionStr, classCount);
    }

    //        - "\nEXT VAR___:  2343\n"
    //        - structure STAT_VAR from STAT_VAR **varIndex

    if (action == STATS_WRITE_RELOAD) {

      // count the size of what we have, this is an array of pointers to structures, store
      // the addr and the string
      statPtr = stats_class_data->varIndex;
      size = 0;
      for (var = 0; var < maxVar; var++, statPtr++) {
        size += sizeof(char *) + strlen((*statPtr)->name) + 1;
      }
      
      if (!(memPtr = (char *)extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
        sql_print_error("ExtSQL: Could not alloc %d bytes to write varIndex data for class %d",
                        size, classCount);
        return(1);
      }
      
      // write the data to buffPtr (address, followed by null term name)
      statPtr = stats_class_data->varIndex;
      size = 0;
      for (var = 0; var < maxVar; var++, statPtr++) {
        ptrPtr = (char **)(memPtr+size);
        *ptrPtr = (*statPtr)->addr; // ZZ - var addr may not match on restart?
        size += sizeof (char *);
        strcpy(memPtr+size, (*statPtr)->name);
        size += strlen((*statPtr)->name);
        if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: VAR (%d / %s / 0x%x)", 
                                                             var, (*statPtr)->name, (uint)(*statPtr)->addr);
        *(memPtr+size++) = '\0'; // don't forget the NULL
      }

      if (extsql_write_section_hdr(loadFileName, reloadFile, "VAR___", size)) {
        my_free(memPtr, MYF(0));
        return(1);
      }
       
     
      if (my_write(reloadFile, (byte*) memPtr, size, 
                   MYF(MY_WME)) != size) {
        sql_print_error("ExtSQL: Could write varIndexdata to RELOAD file %s for %d", 
                        loadFileName, classCount);
        my_free(memPtr, MYF(0));
        return(1);
      }  

      my_free(memPtr, MYF(0));

    } else { // READ

      memPtr = NULL;
      if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                              "VAR___", classCount))) {
        return(1);
      }

      // alloc memory for the complete var index of pointers
      if (!(stats_class_data->varIndex =  (STAT_VAR **)extsql_malloc( ((maxVar+1) * sizeof(pSTAT_VAR)),
                                                                  MYF(MY_WME | MY_ZEROFILL)) )) {
        sql_print_error("ExtSQL: Could not alloc space for varIndex, RELOAD file %s for %d",
                        loadFileName, classCount);
        return(1);
      }


      // move the data from buff to memory, careful that we don't exceed what
      // we actually read in.
      statPtr = stats_class_data->varIndex;
      size = 0;
      for (var = 0, srcPtr = memPtr; srcPtr - memPtr < size2; var++, statPtr++) {

        // allocate room to the STAT_VAR structure we will point to and that will hold
        // the name and addr
        if (!(*statPtr =  (STAT_VAR *)extsql_malloc( (sizeof(STAT_VAR)),
                                                 MYF(MY_WME | MY_ZEROFILL)) )) {
          sql_print_error("ExtSQL: Could not alloc space for STAT_VAR structure, RELOAD file %s for %d",
                        loadFileName, classCount); 
          return(1);
        }
          
        ptrPtr = (char **)srcPtr;
        (*statPtr)->addr = *ptrPtr;
  
        // move to name
        size = sizeof (char *);
        srcPtr += size;       // start of name
        size = strlen(srcPtr) + 1; // length
        
        if (!(destPtr = extsql_malloc(size, MYF(MY_WME | MY_ZEROFILL)))) {
          sql_print_error("ExtSQL: Memory alloc failed on var(%d), class(%d)",
                          var, classCount);
          my_free(memPtr, MYF(0));
          return(1);
        }
        
        strcpy(destPtr, srcPtr);   // copy name to allocated memory
        (*statPtr)->name = destPtr;  // set pointer

        // need to store the address in our quick lookup array, it would have been cleared
        // prior to the reload
        if (extsql_store_var_addr((*statPtr)->addr )) {
          return(1);
        }
        
        if STATS_DEBUG(STATS_RELOAD) sql_print_information("ExtSQL: VAR (%d / %s / 0x%x)", 
                                                             var, (*statPtr)->name, (uint)(*statPtr)->addr);
        
        srcPtr += size;  // move to next addr/name pair
        
      } // end for - all vars
      
      my_free(memPtr, MYF(0));
      
    } // end if - else

    if STATS_DEBUG(STATS_RELOAD) {
      sql_print_information("ExtSQL: during %s, completed section VAR___, class (%d)", 
                            actionStr, classCount);
    }

    //        - "\nEXT TIME:  2123\n"
    //        - my_time_t array of time entries for each data entry from my_time_t *timeLabels
    

    if (action == STATS_WRITE_RELOAD) {

      // count the size of what we have, this is an array of time labels,
      // just items of my_time_t all in a row
      size = sizeof(my_time_t) * maxTime;

      if (extsql_write_section_hdr(loadFileName, reloadFile, "TIME__", size)) {
        return(1);
      }
    
      if STATS_DEBUG(STATS_RELOAD) {

        strcpy(buff, "ExtSQL: TIME ");
        
        // only the first 10!
        for (i = 0; i < maxTime  && i < 10; i++) {
          sprintf(buff+strlen(buff), "(%d / %d)", i, (int)stats_class_data->timeLabels[i]);
        }

        sql_print_information(buff);
      }

      if (my_write(reloadFile, (byte*) stats_class_data->timeLabels, size, 
                   MYF(MY_WME)) != size) {
        sql_print_error("ExtSQL: Could write timeLabels data to RELOAD file %s for %d", 
                        loadFileName, classCount);
        return(1);
      }  

      // just to satisfy compiler that extsql_print_bytes could be used!
      if (!strcmp(loadFileName, "bongongongog")) {
        extsql_print_bytes((char *)stats_class_data->timeLabels, size);
      }
      
    } else { // READ

      // this one is easy, just read it right in, space has already been allocated!
      memPtr = NULL;
      if (!extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                    "TIME__", classCount)) {
        return(1);
      }

      // DON'T FREE memPtr here
      stats_class_data->timeLabels = (my_time_t *) memPtr;
 
      if STATS_DEBUG(STATS_RELOAD) {
        
        strcpy(buff, "ExtSQL: TIME ");
        
        // only the first 10!
        for (i = 0; i < maxTime  && i < 10; i++) {
          sprintf(buff+strlen(buff), "(%d / %d)", i, (int)stats_class_data->timeLabels[i]);
        }
        
        sql_print_information(buff);
      }
      
    } // end if - else

    if STATS_DEBUG(STATS_RELOAD) {
      sql_print_information("ExtSQL: during %s, completed section TIME__, class (%d)", 
                            actionStr, classCount);
    }

    //        - "\nEXT DATA__: 000342432\n"
    //        - ulong data (actual recorded data) from ulong *data
 
    // get the size of the data section
    size = stats_class_data->maxInstance * stats_class_data->maxVar *
      stats_class_data->maxTime * sizeof(ulong);
    
    if (action == STATS_WRITE_RELOAD) {

      
      // sanity check, size should be same as diff, remember offset in dataEnd
      // is as an index to ulong entries, size is in bytes!
      size2 = (stats_class_data->dataEnd - stats_class_data->data) * sizeof(ulong);
      if (size != size2) {
        sql_print_error("ExtSQL: data size error (%d/%d) for class %d",
                        size, size2, classCount);
        return(1);
      }
      
      if (extsql_write_section_hdr(loadFileName, reloadFile, "DATA__", size)) {
        return(1);
      }

      if (my_write(reloadFile, (byte*) stats_class_data->data, size, 
                   MYF(MY_WME)) != size) {
        sql_print_error("ExtSQL: Could write ext data to RELOAD file %s for %d", 
                        loadFileName, classCount);
        return(1);
      }
      
    } else { // READ

      // this one is easy, just read it right in!
      memPtr = NULL;
      if (!(size2 = extsql_read_section_bytes(loadFileName, reloadFile, &memPtr, 
                                              "DATA__", classCount)) ) {
        return(1);
      }

      // sanity check, same size?
      if (size != size2) {
        sql_print_error("ExtSQL: data size error  (%d/%d) for class %d",
                        size, size2, classCount);
        
        my_free(memPtr, MYF(0));
        return(1);
      }
      
      // DON'T FREE memPtr here
      stats_class_data->data = (ulong *) memPtr;

      // set dataEnd
      stats_class_data->dataEnd = stats_class_data->data + size/sizeof(ulong);

    } // end if -else
    
    
   if STATS_DEBUG(STATS_RELOAD) {
     sql_print_information("ExtSQL: during %s, completed section DATA__, class (%d)", 
                           actionStr, classCount);
   }
    //        - "\nEXT CLASS n: 2\n"   (repeat part for each class)
    
  } // end for - all classes
  
  return(0);

} // end extsql_write_reload

// extsql_store_var_addr - stores just the address of a tracked var in a single array
// this makes increment processing much quicker on the lookup to see if we are
// tracking a variable
static int extsql_store_var_addr(char *varAddr) {

  int k, varAddrFound;

  for (k = 0, varAddrFound = 0; k < Shared->varAddrCount; k++) {
                
    if (Shared->varAddrTrackArray[k] == varAddr) { // it's there
      varAddrFound = 1;
      break;
    }
  } // end for - existing array values
              
  if (!varAddrFound && k < MAX_VARS) { // not there and not at end, add it
    Shared->varAddrTrackArray[k] =  varAddr;
    Shared->varAddrCount++;
    
  } else if (!varAddrFound && k >= MAX_VARS) {
    sql_print_error("ExtSQL: disabled, var tracking limit of %d exceeded",
                    MAX_VARS);    
    return(1);
  }
  
  return(0);

} // end extsql_store_var_addr


// print out all the internal data structures
static void extsql_stats_dump()  {

  pSTATS_CLASS_DATA stats_class_data;
  STAT_VAR **statPtr;
  int i, time, maxVar, maxTime, maxInstance, var, value, instance, classCount;
  char buff[STATS_TMP_BUFF_SIZE];
 

  // DEBUG LOG TO FILE
  // general info
  sql_print_information("ExtSQL: DUMP START (%d)", (int)StartTime);
  sql_print_information("    version (%s)", Shared->extsql_version+17); // don't reprint version
  sql_print_information("    extsql_class_string (%s)", Shared->extsql_class_list);
  sql_print_information("    extsql_users (%s)", extsql_users); 


  // varAddrTrackArray
  sprintf(buff,"   varAddrTrackArray (%d): ", Shared->varAddrCount);

  for (i = 0; i < MAX_VARS && i < 40 && i < Shared->varAddrCount; i++) {
    sprintf(buff + strlen(buff),"(0x%x) ", (uint) Shared->varAddrTrackArray[i]);
  }

  sql_print_information(buff);
    
  
  // loop and print everything to the log file  
  classCount = 0;
  for (stats_class_data = Shared->statData; classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
       stats_class_data++, classCount++) {
    
    maxVar = stats_class_data->maxVar; 
    maxTime = stats_class_data->maxTime; 
    maxInstance = stats_class_data->maxInstance;
      
    
    // print out summary info on the CLASS
    sql_print_information("CLASS is %s, maxInstance(%d), maxVar(%d), maxTime(%d), time(%d), \
timeUnits(%c), lastTime(%d)",
                          stats_class_data->name, maxInstance, maxVar, maxTime,
                          stats_class_data->time, stats_class_data->timeUnits,
                          stats_class_data->lastTime);
    
    sql_print_information("  Base of instanceIndex array at 0x%x", (uint)stats_class_data->instanceIndex);

    for (instance=0; 
         instance < maxInstance && stats_class_data->instanceIndex[instance]; 
         instance++) {
      sql_print_information("  instanceIndex[%d] = (%s), addr(0x%x)", 
                            instance, stats_class_data->instanceIndex[instance],
                            (uint) stats_class_data->instanceIndex[instance]);
    } // end for -- all instances

    for (var=0, statPtr = stats_class_data->varIndex; 
         (var < stats_class_data->maxVar) && *statPtr ; 
         var++, statPtr++) {
      sql_print_information("    varIndex[0x%x] = name(%s), addr(0x%x)",
                            var, (*statPtr)->name, (uint)(*statPtr)->addr);

    } // end for - all vars
    
    for (instance=0; instance < maxInstance; instance++) {
      for (var=0, statPtr = stats_class_data->varIndex; 
           var < stats_class_data->maxVar && *statPtr; 
           var++, statPtr++) {
        for (time = 0; time < maxTime; time++) {
            
          value = stats_class_data->data [ARRAY_INDEX(instance, var, maxVar,
                                                      time, maxTime)];
          if (value) {
            sql_print_information("      statData[%d-%s][%d-%s][%d](%d) = %d",
                                  instance, stats_class_data->instanceIndex[instance], 
                                  var, (*statPtr)->name, 
                                  time, (int)stats_class_data->timeLabels[time],
                                  value);
            
          } // end if - value
        } // end for - all times    
      } // end for - all variables
    } // end for - all instances
    
  } // end for - log file write all classes
  sql_print_information("ExtSQL: DUMP END ");

} // end extsql_stats_dump



// dump/check thread info
// if doDump is set, then go ahead and dump all info on all threads;
// otherwise just create output if there is an error.
static void extsql_thread_dump(THD *thd, int doDump)
{

#ifndef PGSQL
  if (pthread_mutex_trylock(&LOCK_thread_count)) { // forget about it!
    return;
  }; // For unlink from list
#endif

#ifdef PGSQL

  // NEED this ZZ
#else

  I_List_iterator it(threads);
  THD *tmp;
  pthread_t my_id = pthread_self();
  int foundError = 0;
  char *user, *host;
  int *statVerify, *indexPtr;

  if (doDump) {
    sql_print_information("ExtSQL: this thread id is %d", thd->thread_id);
  }

  

  while ((tmp=it++))
    {
#ifdef EXTSQL_50
      user = tmp->security_ctx->user;
      host = tmp->security_ctx->host;
      statVerify = &tmp->security_ctx->statVerify;
      indexPtr = tmp->security_ctx->statIndex;
#else
      user = tmp->user;
      host = tmp->host;
      statVerify = &tmp->statVerify;
      indexPtr = tmp->statIndex;
#endif

      if (doDump) {
        sql_print_information("ExtSQL:   thd(%d/%s/%s) user(%s) host(%s) db(%s) magic(0x%x)",
                              tmp->thread_id, tmp->proc_info, command_name[tmp->command], user, 
                              host, tmp->db, *statVerify);
      }

      for (int i = 0; i < STATS_MAX_CLASSES; i++) {
        if (indexPtr[i]) {

          if ( (*statVerify == STATS_MAGIC_NUM) && 
               ( (indexPtr[i] >= Shared->statData[i].maxInstance) || indexPtr[i] < 0) ) {
            sql_print_warning("ExtSQL: special check failed: thd(%d/%s/%s) user(%s) host(%s) db(%s) magic(0x%x)",
                              tmp->thread_id, tmp->proc_info, command_name[tmp->command], user, 
                              host, tmp->db, *statVerify);
            sql_print_warning("           index(%d), value(0x%x/0x%x)",
                                  i, indexPtr[i], Shared->statData[i].maxInstance);  
          }

          if (doDump) {
            sql_print_information("           index(%d), value(0x%x/0x%x)",
                                  i, indexPtr[i], Shared->statData[i].maxInstance);
          } // end if - dump value

        } // end if - has value
      } // end for - all entries in this thread
    } // end while - still more threads

#endif

#ifndef PGSQL
  VOID(pthread_mutex_unlock(&LOCK_thread_count));
#endif

} // end extsql_thread_dump



// a new thread has been created, init the stat tracking structures
// if (initType == STATS_THD_INIT_DB) is set, the thread exists, but a 'use' command was given to change DB's
// we need to update the db index ONLY!
// if initType == STATS_THD_INIT_ALL -- we reset everybody no matter what!
int extsql_thread_init(THD *thd, int initType)

{
  int i, threadID, command;
  char *tmpName = "", classStart;
  int classCount = 0; 
  int found = 0;
  pSTATS_CLASS_DATA stats_class_data;
  char *user = "", *host = "", *db = "", *proc_info = "", **instPtr;
  int *statVerify = 0, *indexPtr = 0;
  char tmpBuff[STATS_TMP_BUFF_SIZE];


  if (initType != STATS_THD_INIT_ALL) { // normal
    
    if (!Shared->extsql_active || Shared->extsql_disabled) { // shouldn't normally even get here
      return(0);
    }
    
    if (initType == STATS_THD_INIT_DB) { // ZZ -return
      // return(0);
    }  
  }

  // attach to our shared memory!
  if (extsql_init_globals()) {
    return(1);
  }
  
  if STATS_DEBUG(STATS_DUMP_START) {
    extsql_stats_dump();
  }  

  StartTime = time(NULL);

  extsql_init_proc_vars(&threadID, &command, &user, &db, &host, &proc_info, &indexPtr, &statVerify);


  // OKAY, if (initType == STATS_THD_INIT_DB) is set we should already be tracking data on this thread, just
  // need to change the db index - SO, we should have already done an init on this
  // thread and the MAGIC should have been set, if not, get out!
  if (initType == STATS_THD_INIT_DB) {
    if (*statVerify != STATS_MAGIC_NUM) {
      return(1);
    }
  }

  // check that we have a host and db
  if (! host || ! *host ||  *host == '\0') {
    host = "(null)";
  }

  if (! db || ! *db ||  *db == '\0') {
    db = "(null)";
  }

  // we don't do thread zero -- ZZ disabled temp
  if (0 && ! threadID) {
    sql_print_warning("ExtSQL: thread_init called for thread zero in error");
    return(0);
  }
  
  // for root stuff, i.e. phpMyAdmin, the db is (null).
  if STATS_DEBUG(STATS_THD_INIT_PARAMS) {
    sql_print_information("ExtSQL: thread (%d) started for db %s(0x%x), user %s(0x%x), host %s(0x%x)",
                          threadID, db, (uint)db, user, (uint)user, host, (uint)host);
  }

  // for quick indexing on increment, we store the index values used to track the instances
  // used by this thread within the thread itself
  // see if user and db names are there already
      
  // check/add indexes depending on classes that we are collecting for;

  // init the class index array to zero if this is a first time init or for ALL
  if (!(initType == STATS_THD_INIT_DB)) {
    for (i = 0; i< STATS_MAX_CLASSES; i++) {
      indexPtr[i] = 0;
    }
    *statVerify = 0; // will be set below when we find match on anything.
  }

  for (stats_class_data = Shared->statData, classCount = 0; 
        classCount < STATS_MAX_CLASSES && stats_class_data->maxInstance; 
       stats_class_data++, classCount++) {

    classStart = stats_class_data->name[0];

    if ((initType == STATS_THD_INIT_DB) && classStart != 'd' && classStart != 'c') { // we just want the db or condb
      continue;
    }

    switch (classStart) { // LIST should match extsqlClassesSupported[]
          
    case 'c':   // connect          

      if ((initType == STATS_THD_INIT_DB) && stats_class_data->name[3] != 'd') { // not condb
        continue;
      }

      if (stats_class_data->name[3] == 'd') { // condb - db@host
         
        strcpy(tmpBuff, db);

      } else if (stats_class_data->name[3] == 'u') {   // conuser- user@host
        strcpy(tmpBuff, user);

      } else {
        
        sql_print_error("ExtSQL: disabled, unexpected connection class name %s", 
                        stats_class_data->name);
        Shared->extsql_disabled = 1;
        break;
      }

      strcat(tmpBuff, "@");
      strcat(tmpBuff, host);
      tmpName = tmpBuff;
          
      break;
          
    case 'd':   // db          
         
      tmpName = db;          
      break;
          
    case 'h':    // host
          
      tmpName = host;
      break;
          
    case 's':    // server -- cum total of all on this server
          
      tmpName = "server";
      break;
          
    case 'u':   // user
          
      tmpName = user;
      break;
          
    default:
          
      sql_print_error("ExtSQL: disabled, unexpected class name %s", 
                      stats_class_data->name);
      Shared->extsql_disabled = 1;
          
    } // end switch
        
    // make sure we got something in tmpName
    if (!tmpName) {
      // sql_print_warning("ExtSQL: got null tmpName instance value for class %s, forcing to (null)", stats_class_data->name);
      tmpName = "(null)";
    }

    if (Shared->extsql_disabled) {
      break;
    }
 
    // now try to match or insert
    // check for new or existing instance, LOCK statData during the search
    found = 0;

    i = 0;
    for (instPtr = (char **)stats_class_data->instanceIndex; 
         i <  stats_class_data->maxInstance; instPtr++, i++) {
          
      if STATS_DEBUG(STATS_THD_INIT_LOOP) { 
        sql_print_information("   Looking for match of %s with %s", *instPtr, tmpName);
      }
              
      if (!*instPtr) { // at a blank, fill it

        // Grab the lock now, check to see if it is still NULL
        //    if it is, fill it in
        (void) pthread_mutex_lock(&Shared->LOCK_stats_load); // IMPORTANT, don't leave with unlock!
        
        if (!*instPtr) { // at a blank, fill it
          if (!(*instPtr = extsql_strdup(tmpName, MYF(MY_WME)))) {
            Shared->extsql_disabled = 1;
            sql_print_error("ExtSQL: disabled, unable to alloc instance item %s",
                            tmpName);
            // UNLOCK statData
            (void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!
            break;
          } 
          found = 1;

          Shared->statsAllocArray[Shared->allocIndex] = *instPtr;
  
          // UNLOCK statData
          (void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!
          
          indexPtr[classCount] = i;

          if  STATS_DEBUG(STATS_THD_INIT_LOOP) {
            sql_print_information("ExtSQL, NEW statIndex[%d]->%d for %s in class %s stored as (%s)",
                                  classCount, i, tmpName, stats_class_data->name,
                                  Shared->statsAllocArray[Shared->allocIndex]);
          }

          Shared->allocIndex++;
          break;

        } // end if - was emtpy - fill in new values

        //  if we get here it is not null, someone else got it ahead of us. May have filled in another value,
        // or what we want. -- we release lock, we will fall throught to below and check for a match, and keep looping
        // UNLOCK statData
        (void) pthread_mutex_unlock(&Shared->LOCK_stats_load); // IMPORTANT!

      } // end if - was empty - fill in new value
          
      if (!strcmp(*instPtr, tmpName)) { // match
        indexPtr[classCount] = i;
        found = 1;
        break;
      }   

    } // end for - each instance
 

   // check for max instances exceeded
    if (! found) {
      sql_print_warning("ExtSQL: disabled, instances exceed tracking limit for class %s with %s",
                        stats_class_data->name, tmpName);
      Shared->extsql_disabled = 1;
      break;
    }

    // if we get here, something was found.
    *statVerify = STATS_MAGIC_NUM;

    if (Shared->extsql_disabled) {
      break;
    }
        
  } // end for - each class                     
  if STATS_DEBUG(STATS_DUMP_START) {
    extsql_stats_dump();
  }

#ifdef PGSQL
  extsql_inc(&Connections, 1);
#endif

  return(0);
  
} // end extsql_thread_init

#ifdef EXTSQL_50    
// only for information schema, in 5 and above

extern bool schema_table_store_record(THD *thd, TABLE *table);

// We fill the table with our tracking data, depending on the name
int fill_schema_extsql(THD *thd, TABLE_LIST *tables, COND *cond)
{

  const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS;
  TABLE *table= tables->table;
  CHARSET_INFO *scs= system_charset_info;

  pSTATS_CLASS_DATA stats_class_data;
  STAT_VAR **statPtr;
  ST_SCHEMA_TABLE *schema_table= tables->schema_table;
  int found, var, maxVar, maxTime, maxInstance, currentTime, timeLimit, timeCount, indexTime,
    varNameLength, varIndex, value, stats_hourly, fieldIndex=0, rowHasData, doZeroTime=1;
  char listBuff[MAX_BUFF_SIZE], className[STATS_MAX_NAME];  
  char *varListPtr = listBuff, *stats_class = className, *varName, *termChar;  
  MYSQL_TIME now_mysql_time;
  char *user;

  DBUG_ENTER("schema_table_store_record");

  // are they authorized
#ifdef EXTSQL_50
  user = thd->security_ctx->user;
#else
  user = thd->user;
#endif

  // return if not authorized, disabled or inactive
  if (extsql_check(thd, 0)) DBUG_RETURN(1);

  // lets make sure this is one of our tables
  if (!strncmp(schema_table->table_name, "EXTSTATS_", 9)) { // one of ours
    
    int found, var, maxVar;
    
    // lets get the class name off the end
    strcpy(stats_class, schema_table->table_name + 9);
    
    // find the data for the Class we are interested in
    found = 0;
    for (stats_class_data = Shared->statData; stats_class_data->maxInstance; 
         stats_class_data++) {
      if (!strcmp(stats_class_data->name, stats_class)) { // found it
        found = 1;
        break;
      }
    } // end for 
    
    if (!found) {
      net_printf_error(thd, 0, "ExtSQL: can't fill requested SCHEMA TABLE %s(%s).  The \
most likely reason is that no data is being recorded for that class.  Use SHOW STATISTICS to see \
what is being tracked.",

                       schema_table->table_name, stats_class);
      DBUG_RETURN(0);
    } 
 
  } else { // called by mistake
    net_printf_error(thd, 0, "ExtSQL: schema called in error to fill table %s.",
                     schema_table->table_name);
    DBUG_RETURN(0);
  }
 
  // AT THIS POINT stats_class_data points to the requested class 
  maxVar = stats_class_data->maxVar; 
  maxTime = stats_class_data->maxTime; 
  maxInstance = stats_class_data->maxInstance; // needed for array indexing
  
  currentTime = stats_class_data->time; // time we are currently logging to.
  timeLimit = maxTime-1; // they get it all

  // we need to now load the columns in the PROPER order, data is recorded as it
  // occurs, but the Var name order is controlled by the user, we try to use
  // that order.
  strcpy(varListPtr,","); // init the list
  // loop through the instance variables for names
  for (var=0, statPtr = stats_class_data->varIndex; 
       (var < stats_class_data->maxVar) && *statPtr ; 
       var++, statPtr++) {      
                                                            
    strcat(varListPtr,(*statPtr)->name );
    strcat(varListPtr, ",");

    if (strlen(varListPtr) > MAX_BUFF_SIZE - 50) {
      net_printf(thd, 0, "ExtSQL: List of Vars too long");
      DBUG_RETURN(1);
    }
  } // end for - all instances

 
  // Remember, zero index holds the total. The
  // currentTime has our present location and it is circular, i.e. if
  // maxTime for the class is 4, we store current + 3.  If the currentTime
  // is 2, the proper way to display all hours, starting with the most
  // recent is to show 2(current),1(prior),3(more prior) - skip 0
  // we now go through all the instances, we will be loading the table 
  // with instance_name, time, var, var, .....
  for (int instance=0; instance < maxInstance && stats_class_data->instanceIndex[instance] ; 
       instance++) {
    
    // loop through the times,
    // if stats_timely is set we will output a number of hours starting at currentTime, and
    // limited by timeLimit (starts at 1).
    // For INFORMATION_SCHEMA we have a real problem, need to print the totals under
    // time heading of 0000-00-00 00:00 (zero time), but then pick up and print the rest,
    // have to really hack the loop control to make first output special, zero time.
    // If doZeroTime = 1 -- we need to output the zero values(totals)
    doZeroTime = 1;

    for (timeCount = 0, indexTime = currentTime ;
         timeCount < timeLimit  && indexTime >= 0;
         timeCount++, indexTime--  )  {
      
      // we have to adjust our indexTime for wrap
      if (!indexTime && !doZeroTime) { // we have gone back to the total 
        indexTime = maxTime - 1; // pick the last
      }
      
      restore_record(table, s->default_values);
      fieldIndex = 0;
      rowHasData = 0;  // do we have anything to show, only on HISTORY

      // first column - instance name - always
      table->field[fieldIndex++]->store(stats_class_data->instanceIndex[instance],
                              strlen(stats_class_data->instanceIndex[instance]), scs);

      // second column is times - always, we use indexTime
      if (indexTime) {
        thd->variables.time_zone->gmt_sec_to_TIME(&now_mysql_time,
                                                  (my_time_t) stats_class_data->timeLabels[indexTime]);
      } else {
        char *zeroTime = "0000-00-00 00:00:00";
        str_to_time_with_warn(zeroTime, strlen(zeroTime), &now_mysql_time);
      }

      table->field[fieldIndex++]->store_time(&now_mysql_time, MYSQL_TIMESTAMP_DATETIME);
        
      // loop through varListPtr, output the vars in the DEFAULT order
      // varListPtr = ",bytes,selects," varName will move through this
      for ( varName = varListPtr; *varName ; varName = strstr(varName, ",")) {
        
        varName++; // move off the comma
        //sql_print_information("   target varName is (%s)", varName);

        if (! *varName) { // we are done
          break;
        }

        varNameLength = strchr(varName, ',') - varName;
    
        // okay, varName points at a name, lets go through the index looking
        // for a match
        found = 0;
        for (varIndex=0, statPtr = stats_class_data->varIndex; 
             varIndex < maxVar && *statPtr;
             varIndex++, statPtr++) { 
          
          termChar = (*statPtr)->name + varNameLength +1; // point to end of the current var name

          if (!strncmp( (*statPtr)->name, varName, varNameLength)) {
            found = 1;
            value = stats_class_data->data [ARRAY_INDEX(instance, varIndex, maxVar,
                                                        indexTime, maxTime)]; 

            table->field[fieldIndex++]->store((longlong) value, TRUE);
            if (value) { // is it non-zero
              rowHasData = 1;
            }
            break;
          }
          
        } // end for - a matching var in the list
               
        if (!found) {
          table->field[fieldIndex++]->store((longlong) 9999999, TRUE);
        }
        
      } // end for - requested values
            
      if (!indexTime) { // it had hit zero and we have outputed totals
        doZeroTime = 0; // don't do it again for this class
        indexTime = 1; // we want it to zero again for wrap.
        timeCount--;   // this row doesn't count
      }

      if (!rowHasData) {
        continue;
      }

      // record the row
      if (schema_table_store_record(thd, table)) {
        sql_print_error("ExtSQL, protocol write failed");
        DBUG_RETURN(1);
      }
    } // end for - all requested times
    
  } // end for - all instances
  
  return 0;
}

#endif
 

[Home]    [FAQ List]    [About Us]    [Contact Us]   
 

NOTE: MySQL® is a registered trademark of Sun Microsystems. 
ExtSQL® is registered trademark of Software Workshop Inc.
ExtSQL is a separate product and should not be confused with MySQL
or PostgreSQL. It contains independently developed additional features,
released under the GPL,v.2.

©Copyright 1996-2009 Software Workshop Inc. 
1