/************************************************************************************
   Copyright (C) 2000, 2012 MySQL AB & MySQL Finland AB & TCX DataKonsult AB,
                 Monty Program AB
   Copyright (c) 2021 OceanBase.
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not see <http://www.gnu.org/licenses>
   or write to the Free Software Foundation, Inc.,
   51 Franklin St., Fifth Floor, Boston, MA 02110, USA

*************************************************************************************/

#include <ma_global.h>

#include <ma_sys.h>
#include <ma_string.h>
#include <mariadb_ctype.h>
#include <ma_common.h>
#include "ma_priv.h"
#include "ma_context.h"
#include "mysql.h"
#include "mariadb_version.h"
#include "ma_server_error.h"
#include <mariadb/ma_io.h>
#include "errmsg.h"
#include <sys/stat.h>
#include <signal.h>
#include <time.h>
#include <mariadb_dyncol.h>

#ifndef __has_feature
# define __has_feature(x) 0
#endif

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#if !defined(_WIN32)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef HAVE_SELECT_H
#  include <select.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif
#ifdef HAVE_SYS_UN_H
#  include <sys/un.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif
#include <ma_sha1.h>
#ifndef _WIN32
#include <poll.h>
#endif
#include <ma_pvio.h>
#ifdef HAVE_TLS
#include <ma_tls.h>
#endif
#include <mysql/client_plugin.h>
#ifdef _WIN32
#include "shlwapi.h"
#include <ws2tcpip.h>
#define strncasecmp _strnicmp
#endif
#include <ob_protocol20.h>
#include <ob_full_link_trace.h>

#define ASYNC_CONTEXT_DEFAULT_STACK_SIZE (4096*15)
#define MA_RPL_VERSION_HACK "5.5.5-"

#define CHARSET_NAME_LEN 64

#undef max_allowed_packet
#undef net_buffer_length
extern ulong max_allowed_packet; /* net.c */
extern ulong net_buffer_length;  /* net.c */

static MYSQL_PARAMETERS mariadb_internal_parameters= {&max_allowed_packet, &net_buffer_length, 0};
static my_bool mysql_client_init=0;
static void mysql_close_options(MYSQL *mysql);
static void ma_clear_session_state(MYSQL *mysql);
extern void release_configuration_dirs();
extern char **get_default_configuration_dirs();
extern my_bool  ma_init_done;
extern my_bool  mysql_ps_subsystem_initialized;
extern my_bool mysql_handle_local_infile(MYSQL *mysql, const char *filename, my_bool can_local_infile);
extern const MARIADB_CHARSET_INFO * mysql_find_charset_nr(uint charsetnr);
extern const MARIADB_CHARSET_INFO * mysql_find_charset_name(const char * const name);
extern my_bool set_default_charset_by_name(const char *cs_name, myf flags __attribute__((unused)));
extern int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
                           const char *data_plugin, const char *db);
extern int net_add_multi_command(NET *net, uchar command, const uchar *packet,
                                 size_t length);

extern LIST *pvio_callback;

/* prepare statement methods from my_stmt.c */
extern my_bool mthd_supported_buffer_type(enum enum_field_types type);
extern my_bool mthd_stmt_read_prepare_response(MYSQL_STMT *stmt);
extern my_bool mthd_stmt_get_param_metadata(MYSQL_STMT *stmt);
extern my_bool mthd_stmt_get_result_metadata(MYSQL_STMT *stmt);
extern int mthd_stmt_fetch_row(MYSQL_STMT *stmt, unsigned char **row);
extern int mthd_stmt_fetch_to_bind(MYSQL_STMT *stmt, unsigned char *row);
extern int mthd_stmt_read_all_rows(MYSQL_STMT *stmt);
extern void mthd_stmt_flush_unbuffered(MYSQL_STMT *stmt);
extern my_bool _mariadb_read_options(MYSQL *mysql, const char *dir, const char *config_file, char *group, unsigned int recursion);
extern unsigned char *mysql_net_store_length(unsigned char *packet, size_t length);

extern void
my_context_install_suspend_resume_hook(struct mysql_async_context *b,
                                       void (*hook)(my_bool, void *),
                                       void *user_data);

uint mysql_port=0;
my_string mysql_unix_port=0;

#define CONNECT_TIMEOUT 0

struct st_mariadb_methods MARIADB_DEFAULT_METHODS;

#if defined(_WIN32)
// socket_errno is defined in ma_global.h for all platforms
#define perror(A)
#else
#include <errno.h>
#define SOCKET_ERROR -1
#endif /* _WIN32 */

#include <mysql/client_plugin.h>

#define IS_CONNHDLR_ACTIVE(mysql)\
  ((mysql)->extension && (mysql)->extension->conn_hdlr)

static my_bool
ma_set_connect_attrs_extend(MYSQL *mysql);

my_bool get_local_ip_port(my_socket fd, char *ip, int iplen, int *port)
{
  struct sockaddr local_addr;
  socklen_t len = sizeof(struct sockaddr);
  if (getsockname(fd, &local_addr, &len) == 0) {
    struct sockaddr_in *sin = (struct sockaddr_in *)(&local_addr);
    void *tmp = &(sin->sin_addr);
    //just IPV4
#if defined(_WIN32) /* _WIN32 */
    char *ip_str = inet_ntoa(sin->sin_addr); //winsock2.h
    if (NULL != ip_str) {
      memcpy(ip, ip_str, strlen(ip_str));
    } else {
      return FALSE;
    }
#else
    if (inet_ntop(AF_INET, tmp, ip, iplen) == NULL){
      return FALSE;
    }
#endif /* defined(_WIN32) */
    *port = ntohs(sin->sin_port);
    return TRUE;
  } else {
    return FALSE;
  }
}

// static void end_server(MYSQL *mysql);
static void mysql_close_memory(MYSQL *mysql);
void read_user_name(char *name);
my_bool STDCALL mariadb_reconnect(MYSQL *mysql);
// static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length);

extern int mysql_client_plugin_init();
extern void mysql_client_plugin_deinit();

#include "ob_oralce_format_models.h"


#define EXT_NLS_DATE_FORMAT(M) \
  (NULL != (M) ? (MYSQL_EXTENSION_PTR(M)->nls_date_format) : NULL)
#define EXT_NLS_TIMESTAMP_FORMAT(M) \
  (NULL != (M) ? (MYSQL_EXTENSION_PTR(M)->nls_timestamp_format) : NULL)
#define EXT_NLS_TIMESTAMP_TZ_FORMAT(M) \
  (NULL != (M) ? (MYSQL_EXTENSION_PTR(M)->nls_timestamp_tz_format) : NULL)

/*
  MYSQL::extension handling (see sql_common.h for declaration
  of st_mysql_extension structure).
*/

MYSQL_EXTENSION* mysql_extension_init(MYSQL *mysql  __attribute__((unused)))
{
  MYSQL_EXTENSION *ext;
  const char *nls_date_format = "YYYY-MM-DD HH24:MI:SS";
  const char *nls_timestamp_format = "YYYY-MM-DD HH24:MI:SS.FF";
  const char *nls_timestamp_tz_format = "YYYY-MM-DD HH24:MI:SS.FF TZR TZD";

  ext= calloc(1, sizeof(MYSQL_EXTENSION)); //need free 
  memcpy(ext->nls_date_format, nls_date_format, strlen(nls_date_format));
  memcpy(ext->nls_timestamp_format, nls_timestamp_format, strlen(nls_timestamp_format));
  memcpy(ext->nls_timestamp_tz_format, nls_timestamp_tz_format, strlen(nls_timestamp_tz_format));
  return ext;
}


void mysql_extension_free(struct st_mysql_extension* ext)
{
  if (!ext)
    return;

  if (ext->mysql) {
    mysql_close(ext->mysql);
  }

  // if (ext->complex_type_hash) {
  //   my_hash_free(ext->complex_type_hash);
  //   my_free(ext->complex_type_hash);
  // }

  // if (ext->trace_data)
  //   my_free(ext->trace_data);

  // // free state change related resources.
  // free_state_change_info(ext);

  // free use result
  if (ext->res_extension.pkt_buffer) {
    free(ext->res_extension.pkt_buffer);
    ext->res_extension.pkt_buffer = NULL;
  }

  free(ext);
}


/* net_get_error */
void net_get_error(char *buf, size_t buf_len,
       char *error, size_t error_len,
       unsigned int *error_no,
       char *sqlstate)
{
  char *p= buf;
  size_t error_msg_len= 0;

  if (buf_len > 2)
  {
    *error_no= uint2korr(p);
    p+= 2;

    /* since 4.1 sqlstate is following */
    if (*p == '#')
    {
      memcpy(sqlstate, ++p, SQLSTATE_LENGTH);
      p+= SQLSTATE_LENGTH;
    }
    error_msg_len= buf_len - (p - buf);
    error_msg_len= MIN(error_msg_len, error_len - 1);
    memcpy(error, p, error_msg_len);
  }
  else
  {
    *error_no= CR_UNKNOWN_ERROR;
    memcpy(sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH);
  }
}

/*****************************************************************************
** read a packet from server. Give error message if socket was down
** or packet is an error message
*****************************************************************************/

ulong
ma_net_safe_read(MYSQL *mysql)
{
  NET *net= &mysql->net;
  ulong len=0;

// restart:
  if (net->pvio != 0)
    len=ma_net_read(net);

  if (len == packet_error || len == 0)
  {
    end_server(mysql);
    my_set_error(mysql, net->last_errno == ER_NET_PACKET_TOO_LARGE ?
		     CR_NET_PACKET_TOO_LARGE:
		     CR_SERVER_LOST,
         SQLSTATE_UNKNOWN, 0, errno);
    return(packet_error);
  }
  if (net->read_pos[0] == 255)
  {
    if (len > 3)
    {
      char *pos=(char*) net->read_pos+1;
      uint last_errno=uint2korr(pos);
      pos+=2;
      len-=2;

      // if (last_errno== 65535 &&
      //     ((mariadb_connection(mysql) && (mysql->server_capabilities & CLIENT_PROGRESS)) ||
      //      (!(mysql->extension->mariadb_server_capabilities & MARIADB_CLIENT_PROGRESS << 32))))
      // {
      //   if (cli_report_progress(mysql, (uchar *)pos, (uint) (len-1)))
      //   {
      //     /* Wrong packet */
      //     my_set_error(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
      //     return (packet_error);
      //   }
      //   goto restart;
      // }
      net->last_errno= last_errno;
      if (pos[0]== '#')
      {
        ma_strmake(net->sqlstate, pos+1, SQLSTATE_LENGTH);
        pos+= SQLSTATE_LENGTH + 1;
      }
      else
      {
        strncpy(net->sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH);
      }
      ma_strmake(net->last_error,(char*) pos,
              min(len,sizeof(net->last_error)-1));
    }
    else
    {
      my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
    }

    mysql->server_status&= ~SERVER_MORE_RESULTS_EXIST;

    return(packet_error);
  }
  return len;
}

/*
  Report progress to the client

  RETURN VALUES
    0  ok
    1  error
*/
// static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length)
// {
//   uint stage, max_stage, proc_length;
//   double progress;
//   uchar *start= packet;

//   if (length < 5)
//     return 1;                         /* Wrong packet */

//   if (!(mysql->options.extension && mysql->options.extension->report_progress))
//     return 0;                         /* No callback, ignore packet */

//   packet++;                           /* Ignore number of strings */
//   stage= (uint) *packet++;
//   max_stage= (uint) *packet++;
//   progress= uint3korr(packet)/1000.0;
//   packet+= 3;
//   proc_length= net_field_length(&packet);
//   if (packet + proc_length > start + length)
//     return 1;                         /* Wrong packet */
//   (*mysql->options.extension->report_progress)(mysql, stage, max_stage,
//                                                progress, (char*) packet,
//                                                proc_length);
//   return 0;
// }

/* Get the length of next field. Change parameter to point at fieldstart */
ulong
net_field_length(uchar **packet)
{
  reg1 uchar *pos= *packet;
  if (*pos < 251)
  {
    (*packet)++;
    return (ulong) *pos;
  }
  if (*pos == 251)
  {
    (*packet)++;
    return NULL_LENGTH;
  }
  if (*pos == 252)
  {
    (*packet)+=3;
    return (ulong) uint2korr(pos+1);
  }
  if (*pos == 253)
  {
    (*packet)+=4;
    return (ulong) uint3korr(pos+1);
  }
  (*packet)+=9;					/* Must be 254 when here */
  return (ulong) uint4korr(pos+1);
}

/* Same as above, but returns ulonglong values */

static unsigned long long
net_field_length_ll(uchar **packet)
{
  reg1 uchar *pos= *packet;
  if (*pos < 251)
  {
    (*packet)++;
    return (unsigned long long) *pos;
  }
  if (*pos == 251)
  {
    (*packet)++;
    return (unsigned long long) NULL_LENGTH;
  }
  if (*pos == 252)
  {
    (*packet)+=3;
    return (unsigned long long) uint2korr(pos+1);
  }
  if (*pos == 253)
  {
    (*packet)+=4;
    return (unsigned long long) uint3korr(pos+1);
  }
  (*packet)+=9;					/* Must be 254 when here */
#ifdef NO_CLIENT_LONGLONG
  return (unsigned long long) uint4korr(pos+1);
#else
  return (unsigned long long) uint8korr(pos+1);
#endif
}


void free_rows(MYSQL_DATA *cur)
{
  if (cur)
  {
    ma_free_root(&cur->alloc,MYF(0));
    free(cur);
  }
}


int
mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg,
	       size_t length, my_bool skipp_check, void *opt_arg)
{
  NET *net= &mysql->net;
  int result= -1;
  if (mysql->net.pvio == 0)
  {
    /* Do reconnect if possible */
    if (mariadb_reconnect(mysql))
      return(1);
  }
  if (mysql->status != MYSQL_STATUS_READY ||
      mysql->server_status & SERVER_MORE_RESULTS_EXIST)
  {
    //SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0);
    SET_OB_CLIENT_ERROR(mysql, CR_STATUS_ERROR_NOT_READY, SQLSTATE_UNKNOWN, 0);
    goto end;
  }

  if (IS_CONNHDLR_ACTIVE(mysql))
  {
    result= mysql->extension->conn_hdlr->plugin->set_connection(mysql, command, arg, length, skipp_check, opt_arg);
    if (result== -1)
      return(result);
  }

  CLEAR_CLIENT_ERROR(mysql);

  mysql->info=0;
  mysql->affected_rows= ~(unsigned long long) 0;
  ma_net_clear(net);			/* Clear receive buffer */
  if (!arg)
    arg="";

  if (net->extension->multi_status== COM_MULTI_ENABLED)
  {
    return net_add_multi_command(net, command, (const uchar *)arg, length);
  }

  if (ma_net_write_command(net,(uchar) command,arg,
			length ? length : (ulong) strlen(arg), 0))
  {
    if (net->last_errno == ER_NET_PACKET_TOO_LARGE)
    {
      my_set_error(mysql, CR_NET_PACKET_TOO_LARGE, SQLSTATE_UNKNOWN, 0);
      goto end;
    }
    end_server(mysql);
    if (mariadb_reconnect(mysql))
      goto end;
    if (ma_net_write_command(net,(uchar) command,arg,
			  length ? length : (ulong) strlen(arg), 0))
    {
      my_set_error(mysql, CR_SERVER_GONE_ERROR, SQLSTATE_UNKNOWN, 0);
      goto end;
    }
  }
  result=0;

  if (net->extension->multi_status > COM_MULTI_OFF)
    skipp_check= 1;

  if (!skipp_check)
  {
    result= ((mysql->packet_length=ma_net_safe_read(mysql)) == packet_error ?
	     1 : 0);
  }
 end:
  return(result);
}

int
ma_simple_command(MYSQL *mysql,enum enum_server_command command, const char *arg,
	       size_t length, my_bool skipp_check, void *opt_arg)
{
  if ((mysql->options.client_flag & CLIENT_LOCAL_FILES) &&
       mysql->options.extension && mysql->extension->auto_local_infile == WAIT_FOR_QUERY &&
       arg && (*arg == 'l' || *arg == 'L') &&
       command == COM_QUERY)
  {
    if (strncasecmp(arg, "load", 4) == 0)
      mysql->extension->auto_local_infile= ACCEPT_FILE_REQUEST;
  }
  return mysql->methods->db_command(mysql, command, arg, length, skipp_check, opt_arg);
}

int ma_multi_command(MYSQL *mysql, enum enum_multi_status status)
{
  NET *net= &mysql->net;

  switch (status) {
  case COM_MULTI_OFF:
    ma_net_clear(net);
    net->extension->multi_status= status;
    return 0;
  case COM_MULTI_ENABLED:
    if (net->extension->multi_status > COM_MULTI_DISABLED)
      return 1;
    ma_net_clear(net);
    net->extension->multi_status= status;
    return 0;
  case COM_MULTI_DISABLED:
    /* Opposite to COM_MULTI_OFF we don't clear net buffer,
       next command or com_nulti_end will flush entire buffer */
    net->extension->multi_status= status;
    return 0;
  case COM_MULTI_END:
  {
    size_t len= net->write_pos - net->buff - NET_HEADER_SIZE;

    if (len < NET_HEADER_SIZE) /* don't send empty request */
    {
      ma_net_clear(net);
      return 1;
    }
    net->extension->multi_status= COM_MULTI_OFF;
    return ma_net_flush(net);
  }
  case COM_MULTI_CANCEL:
    ma_net_clear(net);
    net->extension->multi_status= COM_MULTI_OFF;
    return 0;
  default:
    return 1;
  }
}

void free_old_query(MYSQL *mysql)
{
  if (mysql->fields)
    ma_free_root(&mysql->field_alloc,MYF(0));
  ma_init_alloc_root(&mysql->field_alloc,8192,0);	/* Assume rowlength < 8192 */
  mysql->fields=0;
  mysql->field_count=0;				/* For API */
  mysql->info= 0;
  return;
}

#if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL)
struct passwd *getpwuid(uid_t);
char* getlogin(void);
#endif

#if !defined(_WIN32)
void read_user_name(char *name)
{
  if (geteuid() == 0)
    strcpy(name,"root");		/* allow use of surun */
  else
  {
#ifdef HAVE_GETPWUID
    struct passwd *skr;
    const char *str;
    if ((skr=getpwuid(geteuid())) != NULL)
    {
      str=skr->pw_name;
    } else if ((str=getlogin()) == NULL)
    {
      if (!(str=getenv("USER")) && !(str=getenv("LOGNAME")) &&
               !(str=getenv("LOGIN")))
        str="UNKNOWN_USER";
    }
    ma_strmake(name,str,USERNAME_LENGTH);
#elif defined(HAVE_CUSERID)
    (void) cuserid(name);
#else
    ma_strmake(name,"UNKNOWN_USER", USERNAME_LENGTH);
#endif
  }
  return;
}

#else /* WIN32 */

void read_user_name(char *name)
{
  char *str=getenv("USERNAME");		/* ODBC will send user variable */
  ma_strmake(name,str ? str : "ODBC", USERNAME_LENGTH);
}

#endif


/**************************************************************************
** Shut down connection
**************************************************************************/
void
end_server(MYSQL *mysql)
{
  /* if net->error 2 and reconnect is activated, we need to inforn
     connection handler */
  if (mysql->net.pvio != 0)
  {
    ma_pvio_close(mysql->net.pvio);
    mysql->net.pvio= 0;    /* Marker */
  }
  ma_net_end(&mysql->net);
  free_old_query(mysql);
  return;
}

void mthd_my_skip_result(MYSQL *mysql)
{
  ulong pkt_len;

  do {
    pkt_len= ma_net_safe_read(mysql);
    if (pkt_len == packet_error)
      break;
  } while (pkt_len > 8 || mysql->net.read_pos[0] != 254);
  return;
}

void STDCALL
mysql_free_result(MYSQL_RES *result)
{
  if (result)
  {
    if (result->handle && result->handle->status == MYSQL_STATUS_USE_RESULT)
    {
      result->handle->methods->db_skip_result(result->handle);
      result->handle->status=MYSQL_STATUS_READY;
    }
    free_rows(result->data);
    if (result->fields)
      ma_free_root(&result->field_alloc,MYF(0));
    if (result->row)
      free(result->row);
    free(result);
  }
  return;
}


/****************************************************************************
** Get options from my.cnf
****************************************************************************/
enum enum_option_type {
  MARIADB_OPTION_NONE,
  MARIADB_OPTION_BOOL,
  MARIADB_OPTION_INT,
  MARIADB_OPTION_SIZET,
  MARIADB_OPTION_STR,
};

struct st_default_options {
  enum mysql_option option;
  enum enum_option_type type;
  const char *conf_key;
};

struct st_default_options mariadb_defaults[] =
{
  {MARIADB_OPT_PORT, MARIADB_OPTION_INT,"port"},
  {MARIADB_OPT_UNIXSOCKET, MARIADB_OPTION_STR, "socket"},
  {MYSQL_OPT_COMPRESS, MARIADB_OPTION_BOOL, "compress"},
  {MARIADB_OPT_PASSWORD, MARIADB_OPTION_STR, "password"},
  {MYSQL_OPT_NAMED_PIPE, MARIADB_OPTION_BOOL, "pipe"},
  {MYSQL_OPT_CONNECT_TIMEOUT, MARIADB_OPTION_INT, "timeout"},
  {MARIADB_OPT_USER, MARIADB_OPTION_STR, "user"},
  {MYSQL_INIT_COMMAND, MARIADB_OPTION_STR, "init-command"},
  {MARIADB_OPT_HOST, MARIADB_OPTION_STR, "host"},
  {MARIADB_OPT_SCHEMA, MARIADB_OPTION_STR, "database"},
  {MARIADB_OPT_DEBUG, MARIADB_OPTION_STR, "debug"},
  {MARIADB_OPT_FOUND_ROWS, MARIADB_OPTION_NONE, "return-found-rows"},
  {MYSQL_OPT_SSL_KEY, MARIADB_OPTION_STR, "ssl-key"},
  {MYSQL_OPT_SSL_CERT, MARIADB_OPTION_STR,"ssl-cert"},
  {MYSQL_OPT_SSL_CA, MARIADB_OPTION_STR,"ssl-ca"},
  {MYSQL_OPT_SSL_CAPATH, MARIADB_OPTION_STR,"ssl-capath"},
  {MYSQL_OPT_SSL_CRL, MARIADB_OPTION_STR,"ssl-crl"},
  {MYSQL_OPT_SSL_CRLPATH, MARIADB_OPTION_STR,"ssl-crlpath"},
  {MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MARIADB_OPTION_BOOL,"ssl-verify-server-cert"},
  {MYSQL_SET_CHARSET_DIR, MARIADB_OPTION_STR, "character-sets-dir"},
  {MYSQL_SET_CHARSET_NAME, MARIADB_OPTION_STR, "default-character-set"},
  {MARIADB_OPT_INTERACTIVE, MARIADB_OPTION_NONE, "interactive-timeout"},
  {MYSQL_OPT_CONNECT_TIMEOUT, MARIADB_OPTION_INT, "connect-timeout"},
  {MYSQL_OPT_LOCAL_INFILE, MARIADB_OPTION_BOOL, "local-infile"},
  {0, 0 ,"disable-local-infile",},
  {MYSQL_OPT_SSL_CIPHER, MARIADB_OPTION_STR, "ssl-cipher"},
  {MYSQL_OPT_MAX_ALLOWED_PACKET, MARIADB_OPTION_SIZET, "max-allowed-packet"},
  {MYSQL_OPT_NET_BUFFER_LENGTH, MARIADB_OPTION_SIZET, "net-buffer-length"},
  {MYSQL_OPT_PROTOCOL, MARIADB_OPTION_INT, "protocol"},
  {MYSQL_SHARED_MEMORY_BASE_NAME, MARIADB_OPTION_STR,"shared-memory-base-name"},
  {MARIADB_OPT_MULTI_RESULTS, MARIADB_OPTION_NONE, "multi-results"},
  {MARIADB_OPT_MULTI_STATEMENTS, MARIADB_OPTION_STR, "multi-statements"},
  {MARIADB_OPT_MULTI_STATEMENTS, MARIADB_OPTION_STR, "multi-queries"},
  {MYSQL_SECURE_AUTH, MARIADB_OPTION_BOOL, "secure-auth"},
  {MYSQL_REPORT_DATA_TRUNCATION, MARIADB_OPTION_BOOL, "report-data-truncation"},
  {MYSQL_OPT_RECONNECT, MARIADB_OPTION_BOOL, "reconnect"},
  {MYSQL_PLUGIN_DIR, MARIADB_OPTION_STR, "plugin-dir"},
  {MYSQL_DEFAULT_AUTH, MARIADB_OPTION_STR, "default-auth"},
  {MARIADB_OPT_SSL_FP, MARIADB_OPTION_STR, "ssl-fp"},
  {MARIADB_OPT_SSL_FP_LIST, MARIADB_OPTION_STR, "ssl-fp-list"},
  {MARIADB_OPT_SSL_FP_LIST, MARIADB_OPTION_STR, "ssl-fplist"},
  {MARIADB_OPT_TLS_PASSPHRASE, MARIADB_OPTION_STR, "ssl-passphrase"},
  {MARIADB_OPT_TLS_VERSION, MARIADB_OPTION_STR, "tls-version"},
  {MYSQL_SERVER_PUBLIC_KEY, MARIADB_OPTION_STR, "server-public-key"},
  {MYSQL_OPT_BIND, MARIADB_OPTION_STR, "bind-address"},
  {MYSQL_OPT_SSL_ENFORCE, MARIADB_OPTION_BOOL, "ssl-enforce"},
  {OB_OPT_PROXY_USER, MARIADB_OPTION_STR, "proxy-user"},
  {0, 0, NULL}
};

#define CHECK_OPT_EXTENSION_SET(OPTS)\
    if (!(OPTS)->extension)                                     \
      (OPTS)->extension= (struct st_mysql_options_extension *)  \
        calloc(1, sizeof(struct st_mysql_options_extension));

#define OPT_SET_EXTENDED_VALUE_STR(OPTS, KEY, VAL)               \
    CHECK_OPT_EXTENSION_SET(OPTS)                                \
    free((gptr)(OPTS)->extension->KEY);                          \
    if((VAL))                                                   \
      (OPTS)->extension->KEY= strdup((char *)(VAL));             \
    else                                                         \
      (OPTS)->extension->KEY= NULL

#define OPT_SET_EXTENDED_VALUE(OPTS, KEY, VAL)                \
    CHECK_OPT_EXTENSION_SET(OPTS)                                 \
    (OPTS)->extension->KEY= (VAL)

#define OPT_SET_EXTENDED_VALUE_INT(A,B,C) OPT_SET_EXTENDED_VALUE(A,B,C)

#define OPT_SET_VALUE_STR(OPTS, KEY, VAL)                        \
    free((OPTS)->KEY);                                           \
    if((VAL))                                                   \
      (OPTS)->KEY= strdup((char *)(VAL));                        \
    else                                                         \
      (OPTS)->KEY= NULL

#define OPT_SET_VALUE_INT(OPTS, KEY, VAL)                         \
    (OPTS)->KEY= (VAL)

static void options_add_initcommand(struct st_mysql_options *options,
                                     const char *init_cmd)
{
  char *insert= strdup(init_cmd);
  if (!options->init_command)
  {
    options->init_command= (DYNAMIC_ARRAY*)malloc(sizeof(DYNAMIC_ARRAY));
    ma_init_dynamic_array(options->init_command, sizeof(char*), 5, 5);
  }

  if (ma_insert_dynamic(options->init_command, (gptr)&insert))
    free(insert);
}
my_bool _mariadb_set_conf_option(MYSQL *mysql, const char *config_option, const char *config_value)
{
  if (config_option)
  {
    int i;
    char *c;
    
    /* CONC-395: replace underscore "_" by dash "-" */
    while ((c= strchr(config_option, '_')))
      *c= '-';

    for (i=0; mariadb_defaults[i].conf_key; i++)
    {
      if (!strcmp(mariadb_defaults[i].conf_key, config_option))
      {
        my_bool val_bool;
        int     val_int;
        size_t  val_sizet;
        int rc;
        void *option_val= NULL;
        switch (mariadb_defaults[i].type) {
        case MARIADB_OPTION_BOOL:
          val_bool= 0;
          if (config_value)
            val_bool= atoi(config_value);
          option_val= &val_bool;
          break;
        case MARIADB_OPTION_INT:
          val_int= 0;
          if (config_value)
            val_int= atoi(config_value);
          option_val= &val_int;
          break;
        case MARIADB_OPTION_SIZET:
          val_sizet= 0;
          if (config_value)
            val_sizet= strtol(config_value, NULL, 10);
          option_val= &val_sizet;
          break;
        case MARIADB_OPTION_STR:
          option_val= (void*)config_value;
          break;
        case MARIADB_OPTION_NONE:
          break;
        }
        rc= mysql_optionsv(mysql, mariadb_defaults[i].option, option_val);
        return(test(rc));
      }
    }
  }
  /* unknown key */
  return 1;
}


static MARIADB_CONST_STRING null_const_string= {0,0};

/***************************************************************************
** Allocate a string copy on memroot
***************************************************************************/
static MARIADB_CONST_STRING ma_const_string_copy_root(MA_MEM_ROOT *memroot,
                                                      const char *str,
                                                      size_t length)
{
  MARIADB_CONST_STRING res;
  if (!str || !(res.str= ma_memdup_root(memroot, str, length)))
    return null_const_string;
  res.length= length;
  return res;
}


/***************************************************************************
** Allocate and initialize MA_FIELD_EXTENSION
***************************************************************************/
MA_FIELD_EXTENSION *new_ma_field_extension(MA_MEM_ROOT *memroot)
{
  MA_FIELD_EXTENSION *ext= ma_alloc_root(memroot, sizeof(MA_FIELD_EXTENSION));
  if (ext)
    memset((void *) ext, 0, sizeof(*ext));
  return ext;
}


/***************************************************************************
** Populate field extension from a type info packet
***************************************************************************/

static void ma_field_extension_init_type_info(MA_MEM_ROOT *memroot,
                                              MA_FIELD_EXTENSION *ext,
                                              const char *ptr, size_t length)
{
  const char *end= ptr + length;
  for ( ;  ptr < end; )
  {
    uint itype= (uchar) *ptr++;
    uint len= (uchar) *ptr++;
    if (ptr + len > end  || len > 127)
      break; /*Badly encoded data*/
    if (itype <= 127 && itype <= MARIADB_FIELD_ATTR_LAST)
      ext->metadata[itype]= ma_const_string_copy_root(memroot, ptr, len);
    ptr+= len;
  }
}


/***************************************************************************
** Allocate a field extension deep copy
***************************************************************************/

MA_FIELD_EXTENSION *ma_field_extension_deep_dup(MA_MEM_ROOT *memroot,
                                                const MA_FIELD_EXTENSION *from)
{
  MA_FIELD_EXTENSION *ext= new_ma_field_extension(memroot);
  uint i;
  if (!ext)
    return NULL;
  for (i= 0; i < MARIADB_FIELD_ATTR_LAST; i++)
  {
    if (from->metadata[i].str)
      ext->metadata[i]= ma_const_string_copy_root(memroot,
                                                  from->metadata[i].str,
                                                  from->metadata[i].length);
  }
  return ext;
}

/***************************************************************************
** Change field rows to field structs
***************************************************************************/

static size_t rset_field_offsets[]= {
  OFFSET(MYSQL_FIELD, catalog),
  OFFSET(MYSQL_FIELD, catalog_length),
  OFFSET(MYSQL_FIELD, db),
  OFFSET(MYSQL_FIELD, db_length),
  OFFSET(MYSQL_FIELD, table),
  OFFSET(MYSQL_FIELD, table_length),
  OFFSET(MYSQL_FIELD, org_table),
  OFFSET(MYSQL_FIELD, org_table_length),
  OFFSET(MYSQL_FIELD, name),
  OFFSET(MYSQL_FIELD, name_length),
  OFFSET(MYSQL_FIELD, org_name),
  OFFSET(MYSQL_FIELD, org_name_length)
};

MYSQL_FIELD *
unpack_fields(const MYSQL *mysql,
              MYSQL_DATA *data, MA_MEM_ROOT *alloc, uint fields, my_bool default_value)
{
#define CHECK_PKT_LENGTH(len1, len2) {if ((len1) > (len2)) goto error;}
#define CHECK_PKT_LENGTH_END(len1, len2) {if ((len1) >= (len2)) goto error;}

  MYSQL_ROWS	*row;
  MYSQL_FIELD	*field,*result;
  uchar    *p; /* use unsigned char for data to avoid convert error for char to uint. */
  unsigned int i, field_count= sizeof(rset_field_offsets)/sizeof(size_t)/2;
  uchar *complex_type = NULL;
  ulong len = 0;
  ulong pkt_len = 0;

  field=result=(MYSQL_FIELD*) ma_alloc_root(alloc,sizeof(MYSQL_FIELD)*fields);
  if (!result)
    return(0);

  memset(field, 0, sizeof(MYSQL_FIELD)*fields);
  for (row=data->data; row ; row = row->next,field++)
  {
    if (field >= result + fields)
      goto error;

    for (i=0; i < field_count; i++)
    {
      uint length= (uint)(row->data[i+1] - row->data[i] - 1);
      if (!row->data[i] && row->data[i][length])
        goto error;

      *(char **)(((char *)field) + rset_field_offsets[i*2])=
        ma_strdup_root(alloc, (char *)row->data[i]);
      *(unsigned int *)(((char *)field) + rset_field_offsets[i*2+1])= length;
    }

    field->extension= NULL;
    if (ma_has_extended_type_info(mysql))
    {
      if (row->data[i+1] - row->data[i] > 1)
      {
        size_t len= row->data[i+1] - row->data[i] - 1;
        MA_FIELD_EXTENSION *ext= new_ma_field_extension(alloc);
        if ((field->extension= ext))
          ma_field_extension_init_type_info(alloc, ext, row->data[i], len);
      }
      i++;
    }

    p= (uchar *)row->data[i];
    /* filler */
    field->charsetnr= uint2korr(p);
    p+= 2;
    field->length= (uint) uint4korr(p);
    p+= 4;
    field->type=   (enum enum_field_types)uint1korr(p);
    p++;
    field->flags= uint2korr(p);
    p+= 2;
    field->decimals= (uint) p[0];
    p++;
    //use the filler to support oracle precision
    field->precision=  (uint) p[0];
    p++;
    field->ob_routine_param_inout= (enum ObClientRoutineParamInOut) (p[0] & 0x3);
    field->is_implicit_rowid= (p[0] & 0x4) > 0 ? TRUE : FALSE;
    p++;

    if (INTERNAL_NUM_FIELD(field))
      field->flags|= NUM_FLAG;

    i++;

    if (MYSQL_TYPE_OBJECT == field->type) {
      complex_type = (uchar*)row->data[i];
      pkt_len = row->data_length[i];

      len=(ulong) net_field_length(&complex_type);
      field->owner_name_length = len;
      if (0 == len) {
        field->owner_name = NULL;
      } else {
        CHECK_PKT_LENGTH(len, (pkt_len - ((char*)complex_type - row->data[i])));
        field->owner_name = (unsigned char *)ma_memdup_root(alloc, (char*)complex_type, len + 1);
        field->owner_name[len] = 0;
        complex_type += len;
      }

      CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], (long)pkt_len);
      len=(ulong) net_field_length(&complex_type);
      field->type_name_length = len;
      if (0 == len) {
        field->type_name = NULL;
      } else {
        CHECK_PKT_LENGTH(len, (pkt_len - ((char*)complex_type - row->data[i])));
        field->type_name = (unsigned char *)ma_memdup_root(alloc, (char*)complex_type, len+1);
        field->type_name[len] = 0;
        complex_type += len;
      }

      CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], (long)pkt_len);
      field->version = (ulong) net_field_length(&complex_type);

      if (0 == len) {
        field->elem_type = (enum enum_field_types)uint1korr(complex_type);
        complex_type++; 

        if (MYSQL_TYPE_OBJECT == field->elem_type) {
          CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], (long)pkt_len);
          len=(ulong) net_field_length(&complex_type);
          field->elem_owner_name_length = len;
          if (0 == len) {
            field->elem_owner_name = NULL;
          }
          else
          {
            CHECK_PKT_LENGTH(len, (pkt_len - ((char*)complex_type - row->data[i])));
            field->elem_owner_name = (unsigned char *)ma_memdup_root(alloc, (char *)complex_type, len + 1);
            field->elem_owner_name[len] = 0;
            complex_type += len;
          }

          CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], (long)pkt_len);
          len=(ulong) net_field_length(&complex_type);
          field->elem_type_name_length = len;
          if (0 == len) {
            field->elem_type_name = NULL;
          } else {
            CHECK_PKT_LENGTH(len, (pkt_len - ((char*)complex_type - row->data[i])));
            field->elem_type_name = (unsigned char *)ma_memdup_root(alloc, (char*)complex_type, len+1);
            field->elem_type_name[len] = 0;
            complex_type += len;
          }
          CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], (long)pkt_len);
          field->elem_version = (ulong) net_field_length(&complex_type);     
        }
      }

      //object 检查下长度，这里后面应该是没有数据了，防止core
      if ((char*)complex_type - row->data[i] > (long)pkt_len) 
      {
        //CHECK_PKT_LENGTH_END((char*)complex_type - row->data[i], pkt_len);
        len = (ulong)net_field_length(&complex_type);
        if (default_value && len > 0 && len != NULL_LENGTH) {
          field->def = ma_memdup_root(alloc, (char*)complex_type, len);
        } else {
          field->def = 0;
        }
      }
    } else {
      /*
      mthd_my_read_query_result ma_result_set_rows 9:8->8:7, 保证最后的row->data[i]里面含有length 
      为了兼容PS协议/二合一协议object处理,最后row->data[i]里面包含length，所以这里要先解析length 
      */
      if (default_value && row->data[i] && row->data_length[i] > 0) {
        len = (ulong)net_field_length((uchar**) &(row->data[i]));
        if (default_value && len > 0 && len != NULL_LENGTH) {
          field->def = ma_strdup_root(alloc, (char*)row->data[i]);
        } else {
          field->def = 0;
        }
      } else {
        field->def = 0;
      }
    }
    field->def_length= 0;
    field->max_length= 0;
  }
  if (field < result + fields)
    goto error;
  free_rows(data);				/* Free old data */
  return(result);
error:
  free_rows(data);
  ma_free_root(alloc, MYF(0));
  return(0);
}
/* Read all rows (data) from server */
static const char digits[]= "0123456789abcdef";

char *my_safe_ultoa(int base, ulong val, char *buf, uint length, my_bool need_zero)
{
  char *start = buf;

  if (length <= 0) {
    return NULL;
  }

  buf = start + length - 1;
  do {
    *buf--= digits[val % base];
  } while ((val /= base) != 0 && buf >= start);

  while (need_zero && buf >= start) {
    *buf--= '0';
  }

  return buf + 1;
}

char *my_safe_ltoa(int base, long val, char *buf, uint length, my_bool need_zero)
{
  const my_bool is_neg= (val < 0);

  if (length <= 1) {
    return NULL;
  }

  if (is_neg) {
    val= -val;
  }

  buf = my_safe_ultoa(base, (ulong)val, buf + 1, length - 1, need_zero);
  buf--;

  if (is_neg) {
    *buf--= '-';
  } else {
    *buf--= '+';
  }

  return buf + 1;
}

uchar* get_nls_format(MYSQL *mysql, enum_field_types type)
{
  uchar *mysql_nls = NULL;

  if (type == MYSQL_TYPE_DATETIME) {
    mysql_nls = EXT_NLS_DATE_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_NANO || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_TZ_FORMAT(mysql);
  }

  return mysql_nls;
}


//2019-01-01 12:12:12
void oracle_date_to_ob_time(struct ObTime *ob_time, uchar *cp, ulong len)
{
  char *start = (char *)cp;
  char *end = (char *)cp + len;
  char *pos = NULL;

  ob_time->time_zone_id_ = -1;

  if (len > 0) {
    pos = end;
    ob_time->parts_[DT_YEAR] = strtol(start, &pos, 10);
    if (*pos == '-' && end - pos > 1) {
      start = pos + 1;
      pos = end;
      ob_time->parts_[DT_MON] = strtol(start, &pos, 10);
      if (*pos == '-' && end - pos > 1) {
        start = pos + 1;
        pos = end;
        ob_time->parts_[DT_MDAY] = strtol(start, &pos, 10);
        if (*pos == ' ' && end - pos > 1) {
          start = pos + 1;
          pos = end;
          ob_time->parts_[DT_HOUR] = strtol(start, &pos, 10);
          if (*pos == ':' && end - pos > 1) {
            start = pos + 1;
            pos = end;
            ob_time->parts_[DT_MIN] = strtol(start, &pos, 10);
            if (*pos == ':' && end - pos > 1) {
              int error;
              start = pos + 1;
              pos = end;
              ob_time->parts_[DT_SEC] = strtoll10(start, &pos, &error);
            }
          }
        }
      }
    }
  }
}

void oracle_timestamp_to_ob_time(struct ObTime *ob_time, uchar *cp, enum_field_types type)
{
  uint century_offset = 0;
  uint year_offset = 1;
  uint mon_offset = 2;

  uint nano_max_len = 9; //nano only need 9 digit, example: 000000001
  uint nano_offset = 7;
  uint nano_scale_offset = 11;

  uint hour_offset = 12;
  uint min_offset = 13;

  uint tz_name_offset = 14;
  uint tz_m = 0;
  uint tz_n = 0;

  int8_t sign = 1;
  uint i = 0;

  if (*(char*)(cp + century_offset) < 0 || *(char*)(cp + year_offset) < 0) {
    sign = -1;
  }
  ob_time->parts_[DT_YEAR] = sign * (abs((*(char *)(cp + century_offset))) * 100 + abs(*(char *)(cp + year_offset)));

  for (; i <= 5; i++) {
    ob_time->parts_[DT_MON + i] = *(char *)(cp + mon_offset + i);
  }

  ob_time->parts_[DT_USEC] = uint4korr(cp + nano_offset);
  ob_time->parts_[DT_DATE] = ob_time_to_date(ob_time);

  //uint trans to string and fill zero, example: 000110000
  ob_time->nano_scale_ = *(cp + nano_scale_offset);

  //calculate space for nano, include zero before nano
  if (ob_time->nano_scale_ > nano_max_len) {
    ob_time->nano_scale_ = nano_max_len;
  }

  ob_time->time_zone_id_ = -1;
  if (type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    sign = 1;
    if (*(char*)(cp + hour_offset) < 0 || *(char*)(cp + min_offset) < 0) {
      sign = -1;
    }
    ob_time->parts_[DT_OFFSET_MIN] = sign * (abs((*(int8_t *)(cp + hour_offset))) * 60 + abs(*(int8_t *)(cp + min_offset)));

    tz_m = *(cp + tz_name_offset);
    tz_n = *(cp + tz_name_offset + 1 + tz_m);
    if (tz_m > 0) {
      ob_time->time_zone_id_ = 0;
      memcpy(ob_time->tz_name_, cp + tz_name_offset + 1, tz_m);
      if (tz_n > 0) {
        memcpy(ob_time->tzd_abbr_, cp + tz_name_offset + 1 + tz_m + 1, tz_n);
      }
    }
  }
}

ulong calculate_new_time_length_with_nls(MYSQL *mysql, uchar *cp, ulong len, enum_field_types type)
{
  struct ObTime ob_time;
  uchar *mysql_nls = NULL;
  int64_t length = 0;

  memset(&ob_time, 0, sizeof(struct ObTime));
  ob_time.mode_ |= DT_TYPE_ORACLE;

  if (type == MYSQL_TYPE_OB_TIMESTAMP_NANO
      || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
      || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    oracle_timestamp_to_ob_time(&ob_time, cp, type);
  } else {
    oracle_date_to_ob_time(&ob_time, cp, len);
  }

  if (type == MYSQL_TYPE_DATETIME) {
    mysql_nls = EXT_NLS_DATE_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_NANO || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_TZ_FORMAT(mysql);
  }

  if (mysql_nls) {
    calculate_str_oracle_dfm_length(&ob_time, (char *)mysql_nls, strlen((char *)mysql_nls), ob_time.nano_scale_, &length);
  }

  return length;
}

//The search starts with a short gallop favoring small numbers,
//after which it goes into a hand-woven binary search.
inline uint fast_digits10(ulong v)
{
  static const ulong MAX_INTEGER[13] = {
    0,
    10,
    100,
    1000,
    10000,
    100000,
    1000000,
    10000000,
    100000000,
    1000000000,
    10000000000,
    100000000000,
    1000000000000,
  };
  uint ret_int = 0;
  if (v < MAX_INTEGER[1]) {
    ret_int = 1;
  } else if (v < MAX_INTEGER[2]) {
    ret_int = 2;
  } else if (v < MAX_INTEGER[3]) {
    ret_int = 3;
  } else if (v < MAX_INTEGER[12]) {
    if (v < MAX_INTEGER[8]) {
      if (v < MAX_INTEGER[6]) {
        if (v < MAX_INTEGER[4]) {
          ret_int = 4;
        } else {
          ret_int = 5 + (v >= MAX_INTEGER[5]);
        }
      } else {
        ret_int = 7 + (v >= MAX_INTEGER[7]);
      }
    } else if (v < MAX_INTEGER[10]) {
      ret_int = 9 + (v >= MAX_INTEGER[9]);
    } else {
      ret_int = 11 + (v >= MAX_INTEGER[11]);
    }
  } else {
    ret_int = 12 + fast_digits10(v / MAX_INTEGER[12]);
  }
  return ret_int;
}


ulong calculate_interval_length(uchar *cp, enum_field_types type)
{
  ulong convert_len = 0;

  const ulong MIN_YM_LEN = 7;
  const ulong MIN_DS_LEN = 14;
  const ulong MIN_YS_FORMAT_LEN = 6 - 2;//%c%0?d-%02d
  const ulong MIN_DS_FORMAT_LEN_PART1 = 12 - 2;//%c%0?d %02d:%02d:%02d
  const ulong MIN_DS_FORMAT_LEN_PART2 = 3 - 2;//.%0?d

  if (type == MYSQL_TYPE_OB_INTERVAL_YM) {
    const uint32 year_digit_cnt = fast_digits10(uint4korr(cp + 1));
    const uint8_t year_scale = cp[6];
    if (year_digit_cnt > year_scale) {
      convert_len = MIN_YS_FORMAT_LEN + year_digit_cnt;
    } else {
      convert_len = MIN_YS_FORMAT_LEN + year_scale;
    }
    if (convert_len < MIN_YM_LEN) {
      convert_len = MIN_YM_LEN;
    }
  } else {
    const uint32 day_digit_cnt = fast_digits10(uint4korr(cp + 1));
    const uint32 fs_digit_cnt = fast_digits10(uint4korr(cp + 8));
    const uint8_t day_scale = cp[12];
    const int8_t fs_scale = cp[13];
    if (day_digit_cnt > (uint32)day_scale) {
      convert_len = MIN_DS_FORMAT_LEN_PART1 + day_digit_cnt;
    } else {
      convert_len = MIN_DS_FORMAT_LEN_PART1 + day_scale;
    }
    if (fs_scale > 0) {
      convert_len += MIN_DS_FORMAT_LEN_PART2;
      if (fs_digit_cnt > (uint32)fs_scale) {
        convert_len += fs_digit_cnt;
      } else {
        convert_len += fs_scale;
      }
    }
    if (convert_len < MIN_DS_LEN) {
      convert_len = MIN_DS_LEN;
    }
  }
  return convert_len;
}

ulong rewrite_new_time_with_nls(MYSQL *mysql, uchar *cp, ulong len, char *to, int64_t buf_len, enum_field_types type)
{
  struct ObTime ob_time;
  uchar *mysql_nls = NULL;
  size_t mysql_nls_len = 0;
  int64_t pos = 0;

  memset(&ob_time, 0, sizeof(struct ObTime));
  ob_time.mode_ |= DT_TYPE_ORACLE;

  if (type == MYSQL_TYPE_OB_TIMESTAMP_NANO
      || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
      || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    oracle_timestamp_to_ob_time(&ob_time, cp, type);
  } else {
    oracle_date_to_ob_time(&ob_time, cp, len);
  }

  if (type == MYSQL_TYPE_DATETIME) {
    mysql_nls = EXT_NLS_DATE_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_NANO || type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_FORMAT(mysql);
  } else if (type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    mysql_nls = EXT_NLS_TIMESTAMP_TZ_FORMAT(mysql);
  }

  if (mysql_nls) {
    mysql_nls_len = strlen((char *)mysql_nls);
  }
  ob_time_to_str_oracle_dfm(&ob_time, (char *)mysql_nls, mysql_nls_len, ob_time.nano_scale_, to, buf_len, &pos);

  return pos;
}
static const char separator[]= "-- ::.";

ulong rewrite_new_time(uchar *cp, char *to, enum_field_types type)
{
  ulong convert_len = 20; //2012-12-02 12:34:56.

  char nano_buffer[10] = "\0";
  uint nano_max_len = 9; //nano only need 9 digit, example: 000000001
  uint nano_offset = 7;
  uint nano_scale;
  uint nano_scale_offset = 11;

  uint tz_name_offset = 14;
  uint tz_m = 0;
  uint tz_n = 0;

  uint hour_offset = 12;
  uint min_offset = 13;

  uint i;

  //rewrite the fix data, example: 2012-12-02 12:34:56.
  my_safe_ultoa(10, *cp, to, 2, TRUE);
  to += 2;

  for (i = 0; i <= 5; i++) {
    my_safe_ultoa(10, *(cp + 1 + i), to, 2, TRUE);
    to += 2;
    memcpy(to++, separator + i, 1);
  }

  //rewrite nano
  nano_scale = *(cp + nano_scale_offset);
  if (0 != nano_scale) {
    if (nano_scale > nano_max_len) {
      nano_scale = nano_max_len;
    }

    my_safe_ultoa(10, uint4korr(cp + nano_offset), nano_buffer, nano_max_len, TRUE);
    memcpy(to, nano_buffer, nano_scale);
    convert_len += nano_scale;
    to += nano_scale;
  }

  if (type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE) {
    memcpy(to++, " ", 1);
    convert_len += 1;

    tz_m = *(cp + tz_name_offset);
    tz_n = *(cp + tz_name_offset + 1 + tz_m);
    if (tz_m > 0) {
      memcpy(to, cp + tz_name_offset + 1, tz_m);
      convert_len += tz_m;
      to += tz_m;
      if (tz_n > 0) {
        memcpy(to++, " ", 1);
        convert_len += 1;

        memcpy(to, cp + tz_name_offset + 1 + tz_m + 1, tz_n);
        convert_len += tz_n;
        to += tz_n;
      }
    } else {
      // +/-08:00
      if (*(char*)(cp + hour_offset) < 0 || *(char*)(cp + min_offset) < 0) {
        memcpy(to++, "-", 1);
      } else {
        memcpy(to++, "+", 1);
      }
      convert_len += 1;

      my_safe_ultoa(10, abs(*(char*)(cp + hour_offset)), to, 2, TRUE);
      convert_len += 2;
      to += 2;

      memcpy(to++, ":", 1);
      convert_len += 1;

      my_safe_ultoa(10, abs(*(char*)(cp + min_offset)), to, 2, TRUE);
      convert_len += 2;
      to += 2;
    }
  }

  return convert_len;
}


int rewrite_interval(uchar *cp, char *to, const uint to_size, ulong *convert_len, enum_field_types type)
{
  int wlen = 0;
  *convert_len = 0;

  if (type == MYSQL_TYPE_OB_INTERVAL_YM) {
    char format_str[] = "%02u";
    const int scale_idx = 2;

    to[0] = (cp[0] > 0 ? '-' : '+');
    *convert_len = 1;

    format_str[scale_idx] = (char)('0' + cp[6]);
    wlen = snprintf(to + *convert_len, to_size, format_str,
                    uint4korr(cp + 1));
    if (wlen <= 0) {
      return 0;
    } else {
      (*convert_len) += wlen;
      to[*convert_len] = '-';
      my_safe_ultoa(10, cp[5], to + *convert_len + 1, 2, TRUE);
      (*convert_len) += 3;
    }
  } else {
    char format_str_part1[] = "%02u";
    const int day_scale_idx = 2;
    int8 fs_scale = 0;

    to[0] = (cp[0] > 0 ? '-' : '+');
    *convert_len = 1;

    format_str_part1[day_scale_idx] = (char)('0' + cp[12]);
    wlen = snprintf(to + *convert_len, to_size, format_str_part1,
                       uint4korr(cp + 1));
    if (wlen <= 0) {
      return 0;
    } else {
      (*convert_len) += wlen;
      to[*convert_len] = ' ';
      my_safe_ultoa(10, cp[5], to + *convert_len + 1, 2, TRUE);
      to[*convert_len + 3] = ':';
      my_safe_ultoa(10, cp[6], to + *convert_len + 4, 2, TRUE);
      to[*convert_len + 6] = ':';
      my_safe_ultoa(10, cp[7], to + *convert_len + 7, 2, TRUE);
      (*convert_len) += 9;
    }

    fs_scale = cp[13];
    if (fs_scale > 0) {
      char nano_buffer[10] = {0};
      const int8 nano_max_len = 9; //nano only need 9 digit, example: 000000001

      to[*convert_len] = '.';
      (*convert_len) += 1;

      if (fs_scale > nano_max_len) {
        fs_scale = nano_max_len;
      }

      my_safe_ultoa(10, uint4korr(cp + 8), nano_buffer, nano_max_len, TRUE);
      memcpy(to + *convert_len, nano_buffer, fs_scale);
      (*convert_len) += fs_scale;
    }
  }
  return 1;
}
/* Read all rows (fields or data) from server */

MYSQL_DATA *mthd_my_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields,
			     uint fields)
{
  uint	field;
  ulong pkt_len;
  ulong len;
  uchar *cp, *end_cp;
  char	*to, *end_to;
  MYSQL_DATA *result;
  MYSQL_ROWS **prev_ptr,*cur;
  NET *net = &mysql->net;

  ulong convert_len;

  if ((pkt_len= ma_net_safe_read(mysql)) == packet_error)
    return(0);
  if (!(result=(MYSQL_DATA*) calloc(1, sizeof(MYSQL_DATA))))
  {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    return(0);
  }
  ma_init_alloc_root(&result->alloc,8192,0);	/* Assume rowlength < 8192 */
  result->alloc.min_malloc=sizeof(MYSQL_ROWS);
  prev_ptr= &result->data;
  result->rows=0;
  result->fields=fields;

  while (*(cp=net->read_pos) != 254 || pkt_len >= 8)
  {
    end_cp = cp + pkt_len;
    if (mysql_fields) {
      for (field=0 ; field < fields ; field++) {
        if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH) {
          continue;
        }

        //calculate the space for new type
        if (mysql->oracle_mode
            && (mysql_fields[field].type == MYSQL_TYPE_DATETIME
                || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
                || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
                || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE)) {
          convert_len = calculate_new_time_length_with_nls(mysql, cp, len, mysql_fields[field].type);
          //already include '/0' in the end
          pkt_len = pkt_len - len + convert_len;
        } else if (mysql_fields[field].type == MYSQL_TYPE_OB_RAW) {
          convert_len = len * 2;
          //already include '/0' in the end
          pkt_len = pkt_len - len + convert_len;
        } else if (mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_YM
                   || mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_DS) {
          convert_len = calculate_interval_length(cp, mysql_fields[field].type);
          //already include '/0' in the end
          pkt_len = pkt_len - len + convert_len;
        }
        cp+=len;
      }
    }
    cp = net->read_pos;
    end_cp = cp + pkt_len;
    result->rows++;
    if (!(cur= (MYSQL_ROWS*) ma_alloc_root(&result->alloc, sizeof(MYSQL_ROWS))) ||
	      !(cur->data= ((MYSQL_ROW)
		      ma_alloc_root(&result->alloc, (fields+1)*sizeof(char *)+fields+pkt_len))) ||
        !(cur->data_length= (unsigned long*)
          ma_alloc_root(&result->alloc, (fields + 1) * sizeof(unsigned long)))
    )
    {
      free_rows(result);
      SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
      return(0);
    }
    memset(cur->data, 0, (fields + 1) * sizeof(char *) + fields + pkt_len);
    memset(cur->data_length, 0, (fields + 1) * sizeof(unsigned long));
    *prev_ptr=cur;
    prev_ptr= &cur->next;
    to= (char*) (cur->data+fields+1);
    end_to=to+fields+pkt_len-1;
    for (field=0 ; field < fields ; field++)
    {
      if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH)
      {						/* null field */
        cur->data[field] = 0;
        cur->data_length[field] = 0;
      }
      else
      {
        int is_done = 0;

        cur->data[field] = to;
        if ((len > (ulong)(end_to - to) || to > end_to) && !(mysql->oracle_mode && mysql_fields
                   && (mysql_fields[field].type == MYSQL_TYPE_DATETIME    /* FOR oracle mode with mysql_fields==true
                    (parse the result row not fields' info).The datas of these types need to extend space to convert
                    them to the format we need. So it could be skip if len > end_to - to, which pkt_len maybe become
                    less than it compare with before convert. */
                       || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
                       || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
                       || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE)))
        {
          free_rows(result);
          SET_CLIENT_ERROR(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
          return(0);
        }
        if (mysql_fields) {
          if (mysql->oracle_mode
              && (mysql_fields[field].type == MYSQL_TYPE_DATETIME
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE)) {
            convert_len = rewrite_new_time_with_nls(mysql, cp, len, to, end_to - to + 1, mysql_fields[field].type);
            to[convert_len]=0;
            to+=convert_len+1;
            cp+=len;
            len = convert_len;
            cur->data_length[field] = len;
            is_done = 1;
          } else if (mysql_fields[field].type == MYSQL_TYPE_OB_RAW) {
            uchar *end = cp + len;
            for(; cp < end; cp++, to+=2) {
              sprintf(to, "%02X", *((uchar*)cp));
            }

            (*to++)=0;
            len *= 2;
            cur->data_length[field] = len;
            is_done = 1;
          } else if (mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_YM
                     || mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_DS) {
            if (!rewrite_interval(cp, to, (uint)(end_to - to), &convert_len, mysql_fields[field].type)) {
              free_rows(result);
              return (0);
            }
            to[convert_len]=0;
            to+=convert_len+1;
            cp+=len;
            len = convert_len;
            cur->data_length[field] = len;
            is_done = 1;
          }
        }
        if (!is_done) {
          memcpy(to,(char*) cp,len); to[len]=0;
          to+=len+1;
          cp+=len;
          cur->data_length[field] = len;
          is_done = 1;
        }
       
        if (mysql_fields)
        {
          if (mysql_fields[field].max_length < len)
            mysql_fields[field].max_length=len;
        }
      }
    }
    convert_len = (ulong)(end_cp - cp >= 0 ? (end_cp - cp) : 0);
    memcpy(to, (char *)cp, convert_len);
    //convert_len = (ulong)(end_to - to >= 0 ? (end_to - to) : 0);
    //memcpy(to, (char *)cp, (ulong)(end_to - to));
    cur->data[field]=to;			/* End of last field */
    cur->data_length[field] = convert_len; //convert_len
    if ((pkt_len=ma_net_safe_read(mysql)) == packet_error)
    {
      free_rows(result);
      return(0);
    }
  }
  *prev_ptr=0;					/* last pointer is null */
  /* save status */
  if (pkt_len > 1)
  {
    cp++;
    mysql->warning_count= uint2korr(cp);
    cp+= 2;
    mysql->server_status= uint2korr(cp);
  }
  return(result);
}

static int
read_one_row_ob_extension_buffer_check(OB_RES_EXT *ob_res, ulong pkt) {
  int ret = 0;
  if (NULL == ob_res || 0 == pkt) {
    ret = 1;
  } else if (ob_res->pkt_len < pkt) {
    ob_res->pkt_buffer = (char *) realloc(ob_res->pkt_buffer, pkt);
    if (NULL == ob_res->pkt_buffer) {
      ret = 1;
    }
  }
  return ret;
}

/*
** Read one row. Uses packet buffer as storage for fields.
** When next packet is read, the previous field values are destroyed
*/

int mthd_my_read_one_row_backup(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths)
{
  uint field;
  ulong pkt_len,len;
  uchar *pos,*prev_pos, *end_pos;

  if ((pkt_len=(uint) ma_net_safe_read(mysql)) == packet_error)
    return -1;

  if (pkt_len <= 8 && mysql->net.read_pos[0] == 254)
  {
    mysql->warning_count= uint2korr(mysql->net.read_pos + 1);
    mysql->server_status= uint2korr(mysql->net.read_pos + 3);
    return 1;				/* End of data */
  }
  prev_pos= 0;				/* allowed to write at packet[-1] */
  pos=mysql->net.read_pos;
  end_pos=pos+pkt_len;
  for (field=0 ; field < fields ; field++)
  {
    if ((len=(ulong) net_field_length(&pos)) == NULL_LENGTH)
    {						/* null field */
      row[field] = 0;
      *lengths++=0;
    }
    else
    {
      if (len > (ulong) (end_pos - pos) || pos > end_pos)
      {
        mysql->net.last_errno=CR_UNKNOWN_ERROR;
        strncpy(mysql->net.last_error,ER(mysql->net.last_errno),
                MYSQL_ERRMSG_SIZE - 1);
        return -1;
      }
      row[field] = (char*) pos;
      pos+=len;
      *lengths++=len;
    }
    if (prev_pos)
      *prev_pos=0;				/* Terminate prev field */
    prev_pos=pos;
  }
  row[field]=(char*) prev_pos+1;		/* End of last field */
  *prev_pos=0;					/* Terminate last field */
  return 0;
}


int mthd_my_read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths)
{
  uint field;
  ulong pkt_len,len;
  uchar *pos,*prev_pos, *end_pos;

  if ((pkt_len=(uint) ma_net_safe_read(mysql)) == packet_error)
    return -1;

  if (pkt_len <= 8 && mysql->net.read_pos[0] == 254)
  {
    mysql->warning_count= uint2korr(mysql->net.read_pos + 1);
    mysql->server_status= uint2korr(mysql->net.read_pos + 3);
    return 1;				/* End of data */
  }
  prev_pos= 0;				/* allowed to write at packet[-1] */
  pos=mysql->net.read_pos;
  end_pos=pos+pkt_len;

  if (mysql->oracle_mode) {
    ulong new_pkt_len = pkt_len;
    ulong convert_len;
    OB_RES_EXT *res_ext = &(((MYSQL_EXTENSION *)(mysql->ob_extension))->res_extension);
    MYSQL_FIELD *mysql_fields = res_ext->mysql_fields;
    char *to, *end_to;
    uchar *cp = pos;

    if (NULL == mysql_fields) {
      return 1;
    }
    
    for (field=0 ; field < fields ; field++) {
      if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH) {
        continue;
      }

      //calculate the space for new type
      if ((mysql_fields[field].type == MYSQL_TYPE_DATETIME
              || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
              || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
              || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE)) {
        convert_len = calculate_new_time_length_with_nls(mysql, cp, len, mysql_fields[field].type);
        //already include '/0' in the end
        new_pkt_len = new_pkt_len - len + convert_len;
      } else if (mysql_fields[field].type == MYSQL_TYPE_OB_RAW) {
        convert_len = len * 2;
        //already include '/0' in the end
        new_pkt_len = new_pkt_len - len + convert_len;
      } else if (mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_YM
                  || mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_DS) {
        convert_len = calculate_interval_length(cp, mysql_fields[field].type);
        //already include '/0' in the end
        new_pkt_len = new_pkt_len - len + convert_len;
      }
      cp+=len;
    }

    new_pkt_len += fields;   // for every field，add '\0' length

    if (read_one_row_ob_extension_buffer_check(res_ext, new_pkt_len)) {
      // error
      return 1;
    }

    cp = pos;   //reset cpoy pos
    to = res_ext->pkt_buffer;
    end_to = to + new_pkt_len - 1;

    for (field=0 ; field < fields ; field++)
    {
      if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH)
      {						/* null field */
        row[field] = 0;
        *lengths++=0;
      }
      else
      {
        int is_done = 0;

        row[field] = to;
        if ((len > (ulong) (end_to - to))
          && !(mysql->oracle_mode
                && (mysql_fields[field].type == MYSQL_TYPE_DATETIME
                    || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
                    || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
                    || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE))
          && mysql_fields[field].type != MYSQL_TYPE_OB_RAW
          && mysql_fields[field].type != MYSQL_TYPE_OB_INTERVAL_YM
          && mysql_fields[field].type != MYSQL_TYPE_OB_INTERVAL_DS)
        {
          mysql->net.last_errno=CR_UNKNOWN_ERROR;
          strncpy(mysql->net.last_error,ER(mysql->net.last_errno),
                  MYSQL_ERRMSG_SIZE - 1);
          return -1;
        }

        if (mysql_fields) {
          if ((mysql_fields[field].type == MYSQL_TYPE_DATETIME
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_NANO
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE
                  || mysql_fields[field].type == MYSQL_TYPE_OB_TIMESTAMP_WITH_TIME_ZONE)) {
            convert_len = rewrite_new_time_with_nls(mysql, cp, len, to, end_to - to + 1, mysql_fields[field].type);
            to[convert_len]=0;
            to+=convert_len+1;
            cp+=len;
            len = convert_len;
            *lengths++=len;
            is_done = 1;
          } else if (mysql_fields[field].type == MYSQL_TYPE_OB_RAW) {
            uchar *end = cp + len;
            for(; cp < end; cp++, to+=2) {
              sprintf(to, "%02X", *((uchar*)cp));
            }

            (*to++)=0;
            len *= 2;
            *lengths++=len;
            is_done = 1;
          } else if (mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_YM
                    || mysql_fields[field].type == MYSQL_TYPE_OB_INTERVAL_DS) {
            if (!rewrite_interval(cp, to, (uint)(end_to - to), &convert_len, mysql_fields[field].type)) {
              return 0;
            }
            to[convert_len]=0;
            to+=convert_len+1;
            cp+=len;
            len = convert_len;
            *lengths++=len;
            is_done = 1;
          }
        }

        if (!is_done) {
          memcpy(to,(char*) cp,len); to[len]=0;
          to+=len+1;
          cp+=len;
          *lengths++=len;
          is_done = 1;
        }

        if (mysql_fields[field].max_length < len)
          mysql_fields[field].max_length=len;
      }
    }  
  } else {
    for (field=0 ; field < fields ; field++)
    {
      if ((len=(ulong) net_field_length(&pos)) == NULL_LENGTH)
      {						/* null field */
        row[field] = 0;
        *lengths++=0;
      }
      else
      {
        if (len > (ulong) (end_pos - pos) || pos > end_pos)
        {
          mysql->net.last_errno=CR_UNKNOWN_ERROR;
          strncpy(mysql->net.last_error,ER(mysql->net.last_errno),
                  MYSQL_ERRMSG_SIZE - 1);
          return -1;
        }
        row[field] = (char*) pos;
        pos+=len;
        *lengths++=len;
      }
      if (prev_pos)
        *prev_pos=0;				/* Terminate prev field */
      prev_pos=pos;
    }
    row[field]=(char*) prev_pos+1;		/* End of last field */
    *prev_pos=0;					/* Terminate last field */
  }
  return 0;
}

/****************************************************************************
** Init MySQL structure or allocate one
****************************************************************************/

MYSQL * STDCALL
mysql_init(MYSQL *mysql)
{
  if (mysql_server_init(0, NULL, NULL))
    return NULL;
  if (!mysql)
  {
    if (!(mysql=(MYSQL*) calloc(1, sizeof(MYSQL))))
      return 0;
    mysql->free_me=1;
    mysql->net.pvio= 0;
    mysql->net.extension= 0;
  }
  else
  {
    memset((char*) (mysql), 0, sizeof(*(mysql)));
    mysql->net.pvio= 0;
    mysql->free_me= 0;
    mysql->net.extension= 0;
  }

   /* Initialize extensions. */
  if (!(mysql->ob_extension= mysql_extension_init(mysql)))
  {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    goto error;
    return 0;
  }

  if (!(mysql->net.extension= (struct st_mariadb_net_extension *)
                               calloc(1, sizeof(struct st_mariadb_net_extension))) ||
      !(mysql->extension= (struct st_mariadb_extension *)
                          calloc(1, sizeof(struct st_mariadb_extension))))
    goto error;

  mysql->options.report_data_truncation= 1;
  mysql->options.connect_timeout=CONNECT_TIMEOUT;
  mysql->charset= mysql_find_charset_name(MARIADB_DEFAULT_CHARSET);
  mysql->methods= &MARIADB_DEFAULT_METHODS;
  strcpy(mysql->net.sqlstate, "00000");
  mysql->net.last_error[0]= mysql->net.last_errno= mysql->net.extension->extended_errno= 0;

  if (ENABLED_LOCAL_INFILE != LOCAL_INFILE_MODE_OFF)
    mysql->options.client_flag|= CLIENT_LOCAL_FILES;
  mysql->extension->auto_local_infile= ENABLED_LOCAL_INFILE == LOCAL_INFILE_MODE_AUTO
                                       ? WAIT_FOR_QUERY : ALWAYS_ACCEPT;
  mysql->options.reconnect= 0;
  return mysql;
error:
  if (mysql->free_me)
    free(mysql);
  return 0;
}

int STDCALL
mysql_ssl_set(MYSQL *mysql __attribute__((unused)),
              const char *key __attribute__((unused)),
              const char *cert __attribute__((unused)),
              const char *ca __attribute__((unused)),
              const char *capath __attribute__((unused)),
              const char *cipher __attribute__((unused)))
{
#ifdef HAVE_TLS
  char enable= 1;
  return (mysql_optionsv(mysql, MYSQL_OPT_SSL_ENFORCE, &enable) |
          mysql_optionsv(mysql, MYSQL_OPT_SSL_KEY, key) |
          mysql_optionsv(mysql, MYSQL_OPT_SSL_CERT, cert) |
          mysql_optionsv(mysql, MYSQL_OPT_SSL_CA, ca) |
          mysql_optionsv(mysql, MYSQL_OPT_SSL_CAPATH, capath) |
          mysql_optionsv(mysql, MYSQL_OPT_SSL_CIPHER, cipher)) ? 1 : 0;
#else
  return 0;
#endif
}

/**************************************************************************
**************************************************************************/

const char * STDCALL
mysql_get_ssl_cipher(MYSQL *mysql __attribute__((unused)))
{
#ifdef HAVE_TLS
  if (mysql->net.pvio && mysql->net.pvio->ctls)
  {
    return ma_pvio_tls_cipher(mysql->net.pvio->ctls);
  }
#endif
  return(NULL);
}

/**************************************************************************
** Free strings in the SSL structure and clear 'use_ssl' flag.
** NB! Errors are not reported until you do mysql_real_connect.
**************************************************************************/

char *ma_send_connect_attr(MYSQL *mysql, unsigned char *buffer)
{
  if (mysql->server_capabilities & CLIENT_CONNECT_ATTRS)
  {
    /* Init Client IP and port before send */
    ma_set_connect_attrs_extend(mysql);

    buffer= (unsigned char *)mysql_net_store_length((unsigned char *)buffer, (mysql->options.extension) ?
                             mysql->options.extension->connect_attrs_len : 0);
    if (mysql->options.extension &&
        hash_inited(&mysql->options.extension->connect_attrs))
    {
      uint i;
      for (i=0; i < mysql->options.extension->connect_attrs.records; i++)
      {
        size_t len;
        uchar *p= hash_element(&mysql->options.extension->connect_attrs, i);
        len= strlen((char *)p);
        buffer= mysql_net_store_length(buffer, len);
        memcpy(buffer, p, len);
        buffer+= (len);
        p+= (len + 1);
        len= strlen((char *)p);
        buffer= mysql_net_store_length(buffer, len);
        memcpy(buffer, p, len);
        buffer+= len;
      }
    }
  }
  return (char *)buffer;
}


/** set some ob default attributes */
static my_bool
ma_set_ob_connect_attrs(MYSQL *mysql)
{
#define OB_MAX_UINT64_BUF_LEN 128
  int rc = 0;
  uint64_t cap = OBPROXY_DEFAULT_CAPABILITY_FLAG;
  char cap_buf[OB_MAX_UINT64_BUF_LEN] = {0};
  uint64_t caplob = 0;
  char caplob_buf[OB_MAX_UINT64_BUF_LEN] = {0};

  if (mysql->can_use_protocol_ob20) {
    cap |= OBCLIENT_CAP_OB_PROTOCOL_V2;
    cap |= OBCLIENT_CAP_PROXY_NEW_EXTRA_INFO;
    if (mysql->can_use_full_link_trace) {
      cap |= OBCLIENT_CAP_FULL_LINK_TRACE;
      if (mysql->can_use_flt_show_trace) {
        cap |= OBCLIENT_CAP_PROXY_FULL_LINK_TRACE_SHOW_TRACE;
      } else {
        cap &= ~OBCLIENT_CAP_PROXY_FULL_LINK_TRACE_SHOW_TRACE;
      }
    } else {
      cap &= ~OBCLIENT_CAP_FULL_LINK_TRACE;
    }
  } else {
    cap &= ~OBCLIENT_CAP_OB_PROTOCOL_V2;
    cap &= ~OBCLIENT_CAP_FULL_LINK_TRACE;
    cap &= ~OBCLIENT_CAP_PROXY_NEW_EXTRA_INFO;
    cap &= ~OBCLIENT_CAP_PROXY_FULL_LINK_TRACE_SHOW_TRACE;
  }
  if (mysql->can_use_load_infiles) {
    cap |= OBCLIENT_CAP_PROXY_LOCAL_INFILES;
  }
  
  snprintf(cap_buf, OB_MAX_UINT64_BUF_LEN, "%llu", cap);

  rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_CAPABILITY_FLAG, cap_buf);
  rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_CLIENT_MODE, "__ob_libobclient");
  rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_CLIENT_NAME_KEY, OB_MYSQL_CLIENT_NAME_VALUE);
  rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_CLIENT_VERSION_KEY, LIBOBCLIENT_VERSION);

  if (mysql->can_use_ob_client_lob_locatorv2) {
    caplob |= OBCLIENT_CAP_OB_LOB_LOCATOR_V2;
    snprintf(caplob_buf, OB_MAX_UINT64_BUF_LEN, "%llu", caplob);
    rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_LOB_LOCATOR_V2, caplob_buf);
  }
  if (mysql->proxy_user && mysql->proxy_user[0]) {
    rc += mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, OB_MYSQL_PROXY_USER_NAME, mysql->proxy_user);
  }
  return rc;
}

/** set some default attributes */
static my_bool
ma_set_connect_attrs(MYSQL *mysql, const char *host)
{
  char buffer[255];
  int rc= 0;

  rc= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_name") +
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_version") +
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_os") +
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_server_host") +
#ifdef _WIN32
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_thread") +
#endif
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_pid") +
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_platform");

  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_name", "libmariadb")
       + mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_version", MARIADB_PACKAGE_VERSION)
       + mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_os", MARIADB_SYSTEM_TYPE);

  if (host && *host)
    rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_server_host", host);

#ifdef _WIN32
  snprintf(buffer, 255, "%lu", (ulong) GetCurrentThreadId());
  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_thread", buffer);
  snprintf(buffer, 255, "%lu", (ulong) GetCurrentProcessId());
#else
  snprintf(buffer, 255, "%lu", (ulong) getpid());
#endif
  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_pid", buffer);

  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_platform", MARIADB_MACHINE_TYPE);
  return(test(rc>0));
}

/** set some default attributes for extend, 
 * it will be called when after the connection has been built */
static my_bool
ma_set_connect_attrs_extend(MYSQL *mysql)
{
  char ip_buffer[100] = {0};
  char port_buffer[10]= {0};
  int port = 0;
  int rc= 0;

  if (!mysql) {
    return rc;
  }

  rc= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "__client_ip") +
      mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "__client_port");
     
  if (get_local_ip_port(mysql->net.fd, ip_buffer, 100, &port)) {
    snprintf(port_buffer, 10, "%d", port);
  } else {
    snprintf(ip_buffer, 100, "%s", "invalid");
    snprintf(port_buffer, 10, "%s", "invalid");
  }
  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "__client_ip", ip_buffer);
  rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "__client_port", port_buffer);

  rc += ma_set_ob_connect_attrs(mysql);

  return(test(rc>0));
}



/*
** Note that the mysql argument must be initialized with mysql_init()
** before calling mysql_real_connect !
*/

MYSQL * STDCALL
mysql_real_connect(MYSQL *mysql, const char *host, const char *user,
		   const char *passwd, const char *db,
		   uint port, const char *unix_socket,unsigned long client_flag)
{
  char *end= NULL;
  char *connection_handler= (mysql->options.extension) ?
                            mysql->options.extension->connection_handler : 0;

  if (!mysql->methods)
    mysql->methods= &MARIADB_DEFAULT_METHODS;

  if (connection_handler ||
      (host && (end= strstr(host, "://"))))
  {
    MARIADB_CONNECTION_PLUGIN *plugin;
    char plugin_name[64];

    if (!connection_handler || !connection_handler[0])
    {
      memset(plugin_name, 0, 64);
      ma_strmake(plugin_name, host, MIN(end - host, 63));
      end+= 3;
    }
    else
      ma_strmake(plugin_name, connection_handler, MIN(63, strlen(connection_handler)));

    if (!(plugin= (MARIADB_CONNECTION_PLUGIN *)mysql_client_find_plugin(mysql, plugin_name, MARIADB_CLIENT_CONNECTION_PLUGIN)))
      return NULL;

    if (!(mysql->extension->conn_hdlr= (MA_CONNECTION_HANDLER *)calloc(1, sizeof(MA_CONNECTION_HANDLER))))
    {
      SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
      return NULL;
    }

    /* save URL for reconnect */
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, url, host);

    mysql->extension->conn_hdlr->plugin= plugin;

    if (plugin && plugin->connect)
    {
      MYSQL *my= plugin->connect(mysql, end, user, passwd, db, port, unix_socket, client_flag);
      if (!my)
      {
        free(mysql->extension->conn_hdlr);
        mysql->extension->conn_hdlr= NULL;
      }
      return my;
    }
  }
#ifndef HAVE_SCHANNEL
  return mysql->methods->db_connect(mysql, host, user, passwd,
                                    db, port, unix_socket, client_flag);
#else
/* 
   With older windows versions (prior Win 10) TLS connections periodically
   fail with SEC_E_INVALID_TOKEN, SEC_E_BUFFER_TOO_SMALL or SEC_E_MESSAGE_ALTERED
   error (see MDEV-13492). If the connect attempt returns on of these error codes
   in mysql->net.extended_errno we will try to connect again (max. 3 times)
*/
#define MAX_SCHANNEL_CONNECT_ATTEMPTS 3
  {
    int ssl_retry= (mysql->options.use_ssl) ? MAX_SCHANNEL_CONNECT_ATTEMPTS : 1;
	MYSQL *my= NULL;
    while (ssl_retry)
    {
      if ((my= mysql->methods->db_connect(mysql, host, user, passwd,
                                    db, port, unix_socket, client_flag | CLIENT_REMEMBER_OPTIONS)))
        return my;

      switch (mysql->net.extension->extended_errno) {
        case SEC_E_INVALID_TOKEN:
        case SEC_E_BUFFER_TOO_SMALL:
        case SEC_E_MESSAGE_ALTERED:
          ssl_retry--;
          break;
        default:
          ssl_retry= 0;
          break;
      }
    }
    if (!my && !(client_flag & CLIENT_REMEMBER_OPTIONS))
      mysql_close_options(mysql);
    return my;
  }
#endif
}

static unsigned long mysql_get_version_tool(const char* version_str)
{
  unsigned long major= 0, minor= 0, version= 0;

  if (version_str)
  {
    char *pos= (char*)version_str, *end_pos;
    major=   strtoul(pos, &end_pos, 10);  pos=end_pos+1;
    minor=   strtoul(pos, &end_pos, 10);  pos=end_pos+1;
    version= strtoul(pos, &end_pos, 10);
  }
  else
  {
    // set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate);
  }
  return major*10000 + minor*100 + version;
}
static int get_ob_server_version(MYSQL *con)
{
  if (con->ob_server_version != PROXY_MODE) {
    /* Only one thread calls this, so no synchronization is needed */
    MYSQL_RES *result;
    /* "limit 1" is protection against SQL_SELECT_LIMIT=0 */
    char const *sql = con->oracle_mode ? "select @@version_comment, @@version from dual where rownum <= 1" : "select @@version_comment, @@version limit 1";
    if (!mysql_query(con, sql) &&
        (result = mysql_use_result(con)))
    {
      MYSQL_ROW cur = mysql_fetch_row(result);
      if (cur && cur[0] && cur[1])
      {
        // only get ob server version
        if (strlen(cur[0]) > 9 && strncasecmp(cur[0], "OceanBase", 9) == 0)
        {
          con->ob_server_version = mysql_get_version_tool(cur[1]);
        }
      }
      mysql_free_result(result);
    } else {
      // error
      return 1;
    }
  }
  return 0;
}
static my_bool set_nls_format(MYSQL *mysql)
{
  my_bool bret = TRUE;
  if (mysql->oracle_mode) {
    char *nls_date_format = getenv("NLS_DATE_FORMAT");
    char *nls_timestamp_format = getenv("NLS_TIMESTAMP_FORMAT");
    char *nls_timestamp_tz_format = getenv("NLS_TIMESTAMP_TZ_FORMAT");

    if (NULL != nls_date_format) {
      char change_date_format_sql[100];
      snprintf(change_date_format_sql, 100, "ALTER SESSION SET NLS_DATE_FORMAT='%s';", nls_date_format);
      if (mysql_query(mysql, change_date_format_sql)) {
        bret = FALSE;
      }
    }
    if (bret && NULL != nls_timestamp_format) {
      char change_timestamp_format_sql[100];
      snprintf(change_timestamp_format_sql, 100, "ALTER SESSION SET NLS_TIMESTAMP_FORMAT='%s';", nls_timestamp_format);
      if (mysql_query(mysql, change_timestamp_format_sql)) {
        bret = FALSE;
      }
    }
    if (bret && NULL != nls_timestamp_tz_format) {
      char change_timestamp_tz_format_sql[100];
      snprintf(change_timestamp_tz_format_sql, 100, "ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT='%s';", nls_timestamp_tz_format);
      if (mysql_query(mysql, change_timestamp_tz_format_sql)) {
        bret = FALSE;
      }
    }
  }
  return bret;
}

MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user,
		   const char *passwd, const char *db,
		   uint port, const char *unix_socket, unsigned long client_flag)
{
  char		buff[NAME_LEN+USERNAME_LENGTH+100];
  char		*end, *end_pkt, *host_info;
  MA_PVIO_CINFO  cinfo= {NULL, NULL, 0, -1, NULL};
  MARIADB_PVIO   *pvio= NULL;
  char    *scramble_data;
  my_bool is_maria= 0;
  const char *scramble_plugin;
  uint pkt_length, scramble_len, pkt_scramble_len= 0;
  NET	*net= &mysql->net;

  if (!mysql->methods)
    mysql->methods= &MARIADB_DEFAULT_METHODS;

  if (net->pvio)  /* check if we are already connected */
  {
    SET_CLIENT_ERROR(mysql, CR_ALREADY_CONNECTED, SQLSTATE_UNKNOWN, 0);
    return(NULL);
  }

  /* use default options */
  if (mysql->options.my_cnf_file || mysql->options.my_cnf_group)
  {
    _mariadb_read_options(mysql, NULL,
			  (mysql->options.my_cnf_file ?
			   mysql->options.my_cnf_file : NULL),
			   mysql->options.my_cnf_group, 0);
    free(mysql->options.my_cnf_file);
    free(mysql->options.my_cnf_group);
    mysql->options.my_cnf_file=mysql->options.my_cnf_group=0;
  }

  if (!host || !host[0])
    host = mysql->options.host;

  ma_set_connect_attrs(mysql, host);

#ifndef WIN32
  if (mysql->options.protocol > MYSQL_PROTOCOL_SOCKET)
  {
    SET_CLIENT_ERROR(mysql, CR_CONN_UNKNOWN_PROTOCOL, SQLSTATE_UNKNOWN, 0);
    return(NULL);
  }
#endif

  /* Some empty-string-tests are done because of ODBC */
  if (!user || !user[0])
    user=mysql->options.user;
  if (!passwd)
  {
    passwd=mysql->options.password;
#ifndef DONT_USE_MYSQL_PWD
    if (!passwd)
      passwd=getenv("MYSQL_PWD");  /* get it from environment (haneke) */
    if (!passwd)
      passwd= "";
#endif
  }
  if (!db || !db[0])
    db=mysql->options.db;
  if (!port)
    port=mysql->options.port;
  if (!unix_socket)
    unix_socket=mysql->options.unix_socket;

  mysql->server_status=SERVER_STATUS_AUTOCOMMIT;

  /* try to connect via pvio_init */
  cinfo.host= host;
  cinfo.unix_socket= unix_socket;
  cinfo.port= port;
  cinfo.mysql= mysql;

  /*
  ** Grab a socket and connect it to the server
  */
#ifndef _WIN32
#if defined(HAVE_SYS_UN_H)
  if ((!host ||  strcmp(host,LOCAL_HOST) == 0) &&
      mysql->options.protocol != MYSQL_PROTOCOL_TCP &&
      (unix_socket || mysql_unix_port))
  {
    cinfo.host= LOCAL_HOST;
    cinfo.unix_socket= (unix_socket) ? unix_socket : mysql_unix_port;
    cinfo.type= PVIO_TYPE_UNIXSOCKET;
    sprintf(host_info=buff,ER(CR_LOCALHOST_CONNECTION),cinfo.host);
  }
  else
#endif
#else
  if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY)
  {
    cinfo.host= mysql->options.shared_memory_base_name;
    cinfo.type= PVIO_TYPE_SHAREDMEM;
    sprintf(host_info=buff,ER(CR_SHARED_MEMORY_CONNECTION), cinfo.host ? cinfo.host : SHM_DEFAULT_NAME);
  }
   /* named pipe */
  else if (mysql->options.protocol == MYSQL_PROTOCOL_PIPE ||
	  (host && strcmp(host,LOCAL_HOST_NAMEDPIPE) == 0))
  {
    cinfo.type= PVIO_TYPE_NAMEDPIPE;
    sprintf(host_info=buff,ER(CR_NAMEDPIPE_CONNECTION),cinfo.host);
  }
  else
#endif
  {
    cinfo.unix_socket=0;				/* This is not used */
    if (!port)
      port=mysql_port;
    if (!host)
      host=LOCAL_HOST;
    cinfo.host= host;
    cinfo.port= port;
    cinfo.type= PVIO_TYPE_SOCKET;
    if (mysql->is_socket5) {
      cinfo.host = mysql->socket5_host;
      cinfo.port = mysql->socket5_port;
    }
    sprintf(host_info=buff,ER(CR_TCP_CONNECTION), cinfo.host);
  }
  /* Initialize and load pvio plugin */
  if (!(pvio= ma_pvio_init(&cinfo)))
    goto error;

  /* try to connect */
  if (ma_pvio_connect(pvio, &cinfo) != 0)
  {
    ma_pvio_close(pvio);
    goto error;
  }

  //socket5
  if (mysql->is_socket5)
  {
    int ret = 0;
    if (0 != (ret = ma_pvio_socket5_auth(pvio, mysql->socket5_user, mysql->socket5_pwd, (char*)host, port))) {
      my_set_error(mysql, CR_SOCKET_CREATE_ERROR, SQLSTATE_UNKNOWN, "socket5 proxy communication fail(%d).", ret);
      goto error;
    }
  }

  if (mysql->options.extension && mysql->options.extension->proxy_header)
  {
    char *hdr = mysql->options.extension->proxy_header;
    size_t len = mysql->options.extension->proxy_header_len;
    if (ma_pvio_write(pvio, (unsigned char *)hdr, len) <= 0)
    {
      ma_pvio_close(pvio);
      goto error;
    }
  }

  if (ma_net_init(net, pvio))
    goto error;

  if (mysql->options.max_allowed_packet)
    net->max_packet_size= mysql->options.max_allowed_packet;

  ma_pvio_keepalive(net->pvio);
  strcpy(mysql->net.sqlstate, "00000");

  /* Get version info */
  mysql->protocol_version= PROTOCOL_VERSION;	/* Assume this */
/*
  if (ma_pvio_wait_io_or_timeout(net->pvio, FALSE, 0) < 1)
  {
    my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
                 ER(CR_SERVER_LOST_EXTENDED),
                 "handshake: waiting for initial communication packet",
                 errno);
    goto error;
  }
 */
  if ((pkt_length=ma_net_safe_read(mysql)) == packet_error)
  {
    if (mysql->net.last_errno == CR_SERVER_LOST)
      my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
                 ER(CR_SERVER_LOST_EXTENDED),
                 "handshake: reading initial communication packet",
                 errno);

    goto error;
  }
  end= (char *)net->read_pos;
  end_pkt= (char *)net->read_pos + pkt_length;

  /* Check if version of protocol matches current one */

  mysql->protocol_version= end[0];
  end++;

  /* Check if server sends an error */
  if (mysql->protocol_version == 0XFF)
  {
    net_get_error(end, pkt_length - 1, net->last_error, sizeof(net->last_error),
      &net->last_errno, net->sqlstate);
    /* fix for bug #26426 */
    if (net->last_errno == 1040)
      memcpy(net->sqlstate, "08004", SQLSTATE_LENGTH);
    goto error;
  }

  if (mysql->protocol_version <  PROTOCOL_VERSION)
  {
    net->last_errno= CR_VERSION_ERROR;
    sprintf(net->last_error, ER(CR_VERSION_ERROR), mysql->protocol_version,
	    PROTOCOL_VERSION);
    goto error;
  }
  /* Save connection information */
  if (!user) user="";

  if (mysql->options.proxy_user && mysql->options.proxy_user[0]) {
    mysql->proxy_user = strdup(mysql->options.proxy_user);
  }

  if (!(mysql->host_info= strdup(host_info)) ||
      !(mysql->host= strdup(cinfo.host ? cinfo.host : "")) ||
      !(mysql->user=strdup(user ? user:"")) ||
      !(mysql->passwd=strdup(passwd)))
  {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    goto error;
  }
  if (cinfo.unix_socket)
    mysql->unix_socket= strdup(cinfo.unix_socket);
  else
    mysql->unix_socket=0;
  mysql->port=port;
  client_flag|=mysql->options.client_flag;

  if (strncmp(end, MA_RPL_VERSION_HACK, sizeof(MA_RPL_VERSION_HACK) - 1) == 0)
  {
    mysql->server_version= strdup(end + sizeof(MA_RPL_VERSION_HACK) - 1);
    is_maria= 1;
  }
  else
  {
    if (!(mysql->server_version= strdup(end)))
    {
      SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
      goto error;
    }
  }
  end+= strlen(end) + 1;

  mysql->thread_id=uint4korr(end);
  end+=4;

  /* This is the first part of scramble packet. In 4.1 and later
     a second package will follow later */
  scramble_data= end;
  scramble_len= SCRAMBLE_LENGTH_323 + 1;
  scramble_plugin= old_password_plugin_name;
  end+= SCRAMBLE_LENGTH_323;

  /* 1st pad */
  end++;

  if (end + 1<= end_pkt)
  {
    mysql->server_capabilities=uint2korr(end);
  }

  /* mysql 5.5 protocol */
  if (end + 18 <= end_pkt)
  {
    mysql->server_language= uint1korr(end + 2);
    mysql->server_status= uint2korr(end + 3);
    mysql->server_capabilities|= (unsigned int)(uint2korr(end + 5)) << 16;
    pkt_scramble_len= uint1korr(end + 7);

    /* check if MariaD2B specific capabilities are available */
    if (is_maria && !(mysql->server_capabilities & CLIENT_MYSQL))
    {
      mysql->extension->mariadb_server_capabilities= (ulonglong) uint4korr(end + 14);
    }
  }

  /* pad 2 */
  end+= 18;

  /* second scramble package */
  if (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 <= end_pkt)
  {
    memcpy(end - SCRAMBLE_LENGTH_323, scramble_data, SCRAMBLE_LENGTH_323);
    scramble_data= end - SCRAMBLE_LENGTH_323;
    if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
    {
      scramble_len= pkt_scramble_len;
      scramble_plugin= scramble_data + scramble_len;
      if (scramble_data + scramble_len > end_pkt)
      {
        SET_CLIENT_ERROR(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
        goto error;
      }
    } else
    {
      scramble_len= (uint)(end_pkt - scramble_data);
      scramble_plugin= native_password_plugin_name;
    }
  } else
  {
    mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION;
    if (mysql->options.secure_auth)
    {
      SET_CLIENT_ERROR(mysql, CR_SECURE_AUTH, SQLSTATE_UNKNOWN, 0);
      goto error;
    }
  }

  /* Set character set */
  if (mysql->options.charset_name)
    mysql->charset= mysql_find_charset_name(mysql->options.charset_name);
  else
    mysql->charset=mysql_find_charset_name(MARIADB_DEFAULT_CHARSET);

  if (!mysql->charset)
  {
    net->last_errno=CR_CANT_READ_CHARSET;
    sprintf(net->last_error,ER(net->last_errno),
      mysql->options.charset_name ? mysql->options.charset_name : 
                                    MARIADB_DEFAULT_CHARSET,
      "compiled_in");
    goto error;
  }

  mysql->client_flag= client_flag;
  mysql->client_flag|= CLIENT_SUPPORT_ORACLE_MODE;
  mysql->client_flag|= mysql->options.client_flag;
  mysql->client_flag|= CLIENT_CAPABILITIES;

  // check env variable to determine if use ob20
  determine_protocol_ob20(mysql);
  determine_full_link_trace(mysql);
  determine_ob_client_lob_locatorv2(mysql);
  determine_flt_show_trace(mysql);
  determine_load_infiles(mysql);

  if (run_plugin_auth(mysql, scramble_data, scramble_len,
                             scramble_plugin, db))
    goto error;

  if (mysql->client_flag & CLIENT_COMPRESS)
    net->compress= 1;

  // printf("determine end , use ob20 is %d, use full link trace is %d\n", get_use_protocol_ob20(mysql), get_use_full_link_trace(mysql));
  if (get_use_protocol_ob20(mysql)) {
    if (!(net->ob20protocol=(Ob20Protocol *) malloc(sizeof(Ob20Protocol))))
      goto error;
    net->compress = 1;
    net->use_ob20protocol = TRUE;
    if (ob20_init(net->ob20protocol, mysql->thread_id, get_use_full_link_trace(mysql))) {
      goto error;
    }
  } else {
    // printf("connect ob with normal protocol\n");
  }

  /* last part: select default db */
  if (!(mysql->server_capabilities & CLIENT_CONNECT_WITH_DB) &&
      (db && !mysql->db))
  {
    if (mysql_select_db(mysql, db))
    {
      my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
                          ER(CR_SERVER_LOST_EXTENDED),
                          "Setting intital database",
                          errno);
      goto error;
    }
  }
  if (get_ob_server_version(mysql)) {
    // select version errro
    // goto error;
  }

  if (!set_nls_format(mysql)) {
    // goto error;
    // do nothing
  }

  determine_use_prepare_execute(mysql);
  determine_send_plarray_maxrarr_len(mysql);
  determine_plarray_bindbyname(mysql);
  if (mysql->options.init_command)
  {
    char **begin= (char **)mysql->options.init_command->buffer;
    char **end= begin + mysql->options.init_command->elements;

    /* Avoid reconnect in mysql_real_connect */
    my_bool save_reconnect= mysql->options.reconnect;
    mysql->options.reconnect= 0;

    for (;begin < end; begin++)
    {
      if (mysql_real_query(mysql, *begin, (unsigned long)strlen(*begin)))
        goto error;

    /* check if query produced a result set */
      do {
        MYSQL_RES *res;
        if ((res= mysql_use_result(mysql)))
          mysql_free_result(res);
      } while (!mysql_next_result(mysql));
    }
    mysql->options.reconnect= save_reconnect;
  }

  strcpy(mysql->net.sqlstate, "00000");

  /* connection established, apply timeouts */
  ma_pvio_set_timeout(mysql->net.pvio, PVIO_READ_TIMEOUT, mysql->options.read_timeout);
  ma_pvio_set_timeout(mysql->net.pvio, PVIO_WRITE_TIMEOUT, mysql->options.write_timeout);
  return(mysql);

error:
  /* Free allocated memory */
  end_server(mysql);
  /* only free the allocated memory, user needs to call mysql_close */
  mysql_close_memory(mysql);
  if (!(client_flag & CLIENT_REMEMBER_OPTIONS) &&
      !(IS_MYSQL_ASYNC(mysql)))
    mysql_close_options(mysql);
  return(0);
}

struct my_hook_data {
  MYSQL *orig_mysql;
  MYSQL *new_mysql;
  /* This is always NULL currently, but restoring does not hurt just in case. */
  MARIADB_PVIO *orig_pvio;
};
/*
  Callback hook to make the new VIO accessible via the old MYSQL to calling
  application when suspending a non-blocking call during automatic reconnect.
*/
static void
my_suspend_hook(my_bool suspend, void *data)
{
  struct my_hook_data *hook_data= (struct my_hook_data *)data;
  if (suspend)
  {
    hook_data->orig_pvio= hook_data->orig_mysql->net.pvio;
    hook_data->orig_mysql->net.pvio= hook_data->new_mysql->net.pvio;
  }
  else
    hook_data->orig_mysql->net.pvio= hook_data->orig_pvio;
}

my_bool STDCALL mariadb_reconnect(MYSQL *mysql)
{
  MYSQL tmp_mysql;
  struct my_hook_data hook_data;
  struct mysql_async_context *ctxt= NULL;
  LIST *li_stmt= mysql->stmts;

  /* check if connection handler is active */
  if (IS_CONNHDLR_ACTIVE(mysql))
  {
    if (mysql->extension->conn_hdlr->plugin && mysql->extension->conn_hdlr->plugin->reconnect)
      return(mysql->extension->conn_hdlr->plugin->reconnect(mysql));
  }

  if (!mysql->options.reconnect ||
      (mysql->server_status & SERVER_STATUS_IN_TRANS) || !mysql->host_info)
  {
   /* Allow reconnect next time */
    mysql->server_status&= ~SERVER_STATUS_IN_TRANS;
    my_set_error(mysql, CR_SERVER_GONE_ERROR, SQLSTATE_UNKNOWN, 0);
    return(1);
  }

  mysql_init(&tmp_mysql);
  tmp_mysql.free_me= 0;
  tmp_mysql.options=mysql->options;
  if (mysql->extension->conn_hdlr)
  {
    tmp_mysql.extension->conn_hdlr= mysql->extension->conn_hdlr;
    mysql->extension->conn_hdlr= 0;
  }

  /* don't reread options from configuration files */
  tmp_mysql.options.my_cnf_group= tmp_mysql.options.my_cnf_file= NULL;
  if (IS_MYSQL_ASYNC_ACTIVE(mysql))
  {
    ctxt= mysql->options.extension->async_context;
    hook_data.orig_mysql= mysql;
    hook_data.new_mysql= &tmp_mysql;
    hook_data.orig_pvio= mysql->net.pvio;
    my_context_install_suspend_resume_hook(ctxt, my_suspend_hook, &hook_data);
  }

  if (!mysql_real_connect(&tmp_mysql,mysql->host,mysql->user,mysql->passwd,
			  mysql->db, mysql->port, mysql->unix_socket,
			  mysql->client_flag | CLIENT_REMEMBER_OPTIONS) ||
      mysql_set_character_set(&tmp_mysql, mysql->charset->csname))
  {
    if (ctxt)
      my_context_install_suspend_resume_hook(ctxt, NULL, NULL);
    /* don't free options (CONC-118) */
    memset(&tmp_mysql.options, 0, sizeof(struct st_mysql_options));
    my_set_error(mysql, tmp_mysql.net.last_errno,
                        tmp_mysql.net.sqlstate,
                        tmp_mysql.net.last_error);
    mysql_close(&tmp_mysql);
    return(1);
  }

  for (;li_stmt;li_stmt= li_stmt->next)
  {
    MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data;

    if (stmt->state != MYSQL_STMT_INITTED)
    {
      stmt->state= MYSQL_STMT_INITTED;
      SET_CLIENT_STMT_ERROR(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0);
    }
  }

  tmp_mysql.free_me= mysql->free_me;
  tmp_mysql.stmts= mysql->stmts;
  mysql->stmts= NULL;

  if (ctxt)
    my_context_install_suspend_resume_hook(ctxt, NULL, NULL);
  /* Don't free options, we moved them to tmp_mysql */
  memset(&mysql->options, 0, sizeof(mysql->options));
  mysql->free_me=0;
  mysql_close(mysql);
  *mysql=tmp_mysql;
  mysql->net.pvio->mysql= mysql;
  ma_net_clear(&mysql->net);
  mysql->affected_rows= ~(unsigned long long) 0;
  mysql->info= 0;
  return(0);
}

void ma_invalidate_stmts(MYSQL *mysql, const char *function_name)
{
  if (mysql->stmts)
  {
    LIST *li_stmt= mysql->stmts;

    for (; li_stmt; li_stmt= li_stmt->next)
    {
      MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data;
      stmt->mysql= NULL;
      SET_CLIENT_STMT_ERROR(stmt, CR_STMT_CLOSED, SQLSTATE_UNKNOWN, function_name);
    }
    mysql->stmts= NULL;
  }
}

/*
  Legacy support of the MariaDB 5.5 version, where timeouts where only in
  seconds resolution. Applications that use this will be asked to set a timeout
  at the nearest higher whole-seconds value.
*/
unsigned int STDCALL
mysql_get_timeout_value(const MYSQL *mysql)
{
  unsigned int timeout= 0;

  if (mysql->options.extension && mysql->options.extension->async_context)
    timeout= mysql->options.extension->async_context->timeout_value;
  /* Avoid overflow. */
  if (timeout > UINT_MAX - 999)
    return (timeout - 1)/1000 + 1;
  else
    return (timeout+999)/1000;
}


unsigned int STDCALL
mysql_get_timeout_value_ms(const MYSQL *mysql)
{
  if (mysql->options.extension && mysql->options.extension->async_context)
    return mysql->options.extension->async_context->timeout_value;
  return 0;
}

/**************************************************************************
** Change user and database
**************************************************************************/

my_bool	STDCALL mysql_change_user(MYSQL *mysql, const char *user,
				  const char *passwd, const char *db)
{
  const MARIADB_CHARSET_INFO *s_cs= mysql->charset;
  char *s_user= mysql->user,
       *s_passwd= mysql->passwd,
       *s_db= mysql->db;
  int rc;

  if (mysql->options.charset_name)
    mysql->charset= mysql_find_charset_name(mysql->options.charset_name);
  else
    mysql->charset=mysql_find_charset_name(MARIADB_DEFAULT_CHARSET);

  mysql->user= strdup(user ? user : "");
  mysql->passwd= strdup(passwd ? passwd : "");

  /* db will be set in run_plugin_auth */
  mysql->db= 0;
  rc= run_plugin_auth(mysql, 0, 0, 0, db);

  /* COM_CHANGE_USER always releases prepared statements, so we need to invalidate them */
  ma_invalidate_stmts(mysql, "mysql_change_user()");

  if (rc==0)
  {
    free(s_user);
    free(s_passwd);
    free(s_db);

    if (!mysql->db && db && !(mysql->db= strdup(db)))
    {
      SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
      rc= 1;
    }
  } else
  {
    free(mysql->user);
    free(mysql->passwd);
    free(mysql->db);

    mysql->user= s_user;
    mysql->passwd= s_passwd;
    mysql->db= s_db;
    mysql->charset= s_cs;
  }
  return(rc);
}


/**************************************************************************
** Set current database
**************************************************************************/

int STDCALL
mysql_select_db(MYSQL *mysql, const char *db)
{
  int error;

  if (!db)
    return 1;

  if ((error=ma_simple_command(mysql, COM_INIT_DB, db,
                               (uint) strlen(db),0,0)))
    return(error);
  free(mysql->db);
  mysql->db=strdup(db);
  return(0);
}


/*************************************************************************
** Send a QUIT to the server and close the connection
** If handle is allocated by mysql connect free it.
*************************************************************************/

static void mysql_close_options(MYSQL *mysql)
{
  if (mysql->options.init_command)
  {
    char **begin= (char **)mysql->options.init_command->buffer;
    char **end= begin + mysql->options.init_command->elements;

    for (;begin < end; begin++)
      free(*begin);
    ma_delete_dynamic(mysql->options.init_command);
    free(mysql->options.init_command);
  }
  free(mysql->options.user);
  free(mysql->options.host);
  free(mysql->options.password);
  free(mysql->options.unix_socket);
  free(mysql->options.db);
  free(mysql->options.my_cnf_file);
  free(mysql->options.my_cnf_group);
  free(mysql->options.charset_dir);
  free(mysql->options.charset_name);
  free(mysql->options.bind_address);
  free(mysql->options.ssl_key);
  free(mysql->options.ssl_cert);
  free(mysql->options.ssl_ca);
  free(mysql->options.ssl_capath);
  free(mysql->options.ssl_cipher);

  if (mysql->options.proxy_user) {
    free(mysql->options.proxy_user);
  }
  if (mysql->options.extension)
  {
    struct mysql_async_context *ctxt;
    if ((ctxt = mysql->options.extension->async_context))
    {
      my_context_destroy(&ctxt->async_context);
      free(ctxt);
      mysql->options.extension->async_context= 0;
    }
    free(mysql->options.extension->plugin_dir);
    free(mysql->options.extension->default_auth);
    free(mysql->options.extension->db_driver);
    free(mysql->options.extension->ssl_crl);
    free(mysql->options.extension->ssl_crlpath);
    free(mysql->options.extension->tls_fp);
    free(mysql->options.extension->tls_fp_list);
    free(mysql->options.extension->tls_pw);
    free(mysql->options.extension->tls_version);
    free(mysql->options.extension->url);
    free(mysql->options.extension->connection_handler);
    if(hash_inited(&mysql->options.extension->connect_attrs))
      hash_free(&mysql->options.extension->connect_attrs);
    if (hash_inited(&mysql->options.extension->userdata))
      hash_free(&mysql->options.extension->userdata);

  }
  free(mysql->options.extension);
  /* clear all pointer */
  memset(&mysql->options, 0, sizeof(mysql->options));
}

static void mysql_close_memory(MYSQL *mysql)
{
  ma_clear_session_state(mysql);
  free(mysql->host_info);
  free(mysql->host);
  free(mysql->user);
  free(mysql->passwd);
  free(mysql->db);
  free(mysql->unix_socket);
  free(mysql->server_version);
  if (mysql->proxy_user) {
    free(mysql->proxy_user);
    mysql->proxy_user = 0;
  }
  mysql->host_info= mysql->host= mysql->unix_socket=
                    mysql->server_version=mysql->user=mysql->passwd=mysql->db=0;
}

void my_set_error(MYSQL *mysql,
                  unsigned int error_nr,
                  const char *sqlstate,
                  const char *format,
                  ...)
{
  va_list ap;

  const char *errmsg;

  if (!format)
  {
    if (error_nr >= CR_MIN_ERROR && error_nr <= CR_MYSQL_LAST_ERROR)
      errmsg = ER(error_nr);
    else if (error_nr >= CER_MIN_ERROR && error_nr <= CR_MARIADB_LAST_ERROR)
      errmsg = CER(error_nr);
    else if (error_nr >= OB_MIN_ERROR && error_nr <= CR_OB_LAST_ERROR)
      errmsg = OBER(error_nr);
    else
      errmsg= ER(CR_UNKNOWN_ERROR);
  }

  mysql->net.last_errno= error_nr;
  ma_strmake(mysql->net.sqlstate, sqlstate, SQLSTATE_LENGTH);
  va_start(ap, format);
  vsnprintf(mysql->net.last_error, MYSQL_ERRMSG_SIZE - 1,
            format ? format : errmsg, ap);
  va_end(ap);
  return;
}

void mysql_close_slow_part(MYSQL *mysql)
{
  if (mysql->net.pvio)
  {
    free_old_query(mysql);
    mysql->status=MYSQL_STATUS_READY; /* Force command */
    mysql->options.reconnect=0;
    if (mysql->net.pvio && mysql->net.buff)
      ma_simple_command(mysql, COM_QUIT,NullS,0,1,0);
    end_server(mysql);
  }
}

static void ma_clear_session_state(MYSQL *mysql)
{
  uint i;

  if (!mysql || !mysql->extension)
    return;

  for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++)
  {
    list_free(mysql->extension->session_state[i].list, 0);
  }
  memset(mysql->extension->session_state, 0, sizeof(struct st_mariadb_session_state) * SESSION_TRACK_TYPES);
}

void STDCALL
mysql_close(MYSQL *mysql)
{
  if (mysql)					/* Some simple safety */
  {
    if (mysql->extension && mysql->extension->conn_hdlr)
    {
      MA_CONNECTION_HANDLER *p= mysql->extension->conn_hdlr;
      if (p->plugin->close)
        p->plugin->close(mysql);
      free(p);
      /* Fix for CONC-294: Since we already called plugin->close function
         we need to prevent that mysql_close_slow_part (which sends COM_QUIT
         to the server) will be handled by plugin again. */
      mysql->extension->conn_hdlr= NULL;
    }

    if (mysql->methods)
      mysql->methods->db_close(mysql);

    /* reset the connection in all active statements */
    ma_invalidate_stmts(mysql, "mysql_close()");

    mysql_close_memory(mysql);
    mysql_close_options(mysql);
    ma_clear_session_state(mysql);

    if (mysql->net.extension) {
      free(mysql->net.extension);
      mysql->net.extension = NULL;
    }

    mysql->host_info=mysql->user=mysql->passwd=mysql->db=0;
    mysql->proxy_user = 0;

    /* Clear pointers for better safety */
    memset((char*) &mysql->options, 0, sizeof(mysql->options));

    if (mysql->extension) {
      free(mysql->extension);
      mysql->extension = NULL;
    }

    if (mysql->ob_extension) {
      mysql_extension_free((struct st_mysql_extension *)mysql->ob_extension);
      mysql->ob_extension = NULL;
    }

    mysql->is_socket5 = 0;
    if (mysql->socket5_host) {
      free(mysql->socket5_host);
      mysql->socket5_host = NULL;
    }
    if (mysql->socket5_user) {
      free(mysql->socket5_user);
      mysql->socket5_user = NULL;
    }
    if (mysql->socket5_pwd) {
      free(mysql->socket5_pwd);
      mysql->socket5_pwd = NULL;
    }

    /* Clear pointers for better safety */
    mysql->net.extension = NULL;
    mysql->extension = NULL;
    mysql->ob_extension = NULL;

    mysql->net.pvio= 0;
    if (mysql->free_me)
      free(mysql);
  }
  return;
}


/**************************************************************************
** Do a query. If query returned rows, free old rows.
** Read data by mysql_store_result or by repeating calls to mysql_fetch_row
**************************************************************************/

int STDCALL
mysql_query(MYSQL *mysql, const char *query)
{
  return mysql_real_query(mysql,query, (unsigned long) strlen(query));
}

/*
  Send the query and return so we can do something else.
  Needs to be followed by mysql_read_query_result() when we want to
  finish processing it.
*/

int STDCALL
mysql_send_query(MYSQL* mysql, const char* query, unsigned long length)
{
  return ma_simple_command(mysql, COM_QUERY, query, length, 1,0);
}

int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length)
{
  uchar *end= mysql->net.read_pos+length;
  size_t item_len;
  enum enum_nls_time_type nls_time_type;
  uchar *mysql_nls = NULL;
  mysql->affected_rows= net_field_length_ll(&pos);
  mysql->insert_id=	  net_field_length_ll(&pos);
  mysql->server_status=uint2korr(pos);
  if (mysql->server_status & SERVER_ORACLE_MODE) {
    mysql->oracle_mode = TRUE;
  }
  pos+=2;
  mysql->warning_count=uint2korr(pos);
  pos+=2;
  if (pos > end)
    goto corrupted;
  if (pos < end)
  {
    if ((item_len= net_field_length(&pos)))
      mysql->info=(char*) pos;
    if (pos + item_len > end)
      goto corrupted;

    /* check if server supports session tracking */
    if (mysql->server_capabilities & CLIENT_SESSION_TRACKING)
    {
      ma_clear_session_state(mysql);
      pos+= item_len;

      if (mysql->server_status & SERVER_SESSION_STATE_CHANGED)
      {
        int i;
        if (pos < end)
        {
          LIST *session_item;
          MYSQL_LEX_STRING *str= NULL;
          enum enum_session_state_type si_type;
          uchar *old_pos= pos;

          item_len= net_field_length(&pos);  /* length for all items */
          if (pos + item_len > end)
            goto corrupted;
          end= pos + item_len;

          /* length was already set, so make sure that info will be zero terminated */
          if (mysql->info)
            *old_pos= 0;

          while (pos < end)
          {
            size_t plen;
            char *data;
            si_type= (enum enum_session_state_type)net_field_length(&pos);

            switch(si_type) {
            case SESSION_TRACK_SCHEMA:
            case SESSION_TRACK_STATE_CHANGE:
            case SESSION_TRACK_TRANSACTION_CHARACTERISTICS:
            case SESSION_TRACK_SYSTEM_VARIABLES:
            case SESSION_TRACK_TRANSACTION_STATE:
            case SESSION_TRACK_GTIDS:
              if (si_type != SESSION_TRACK_STATE_CHANGE)
              {
                net_field_length(&pos); /* ignore total length, item length will follow next */
              }
              if (si_type == SESSION_TRACK_GTIDS)
              {
                /* skip encoding */
                net_field_length(&pos);
              }
              plen= net_field_length(&pos);
              if (pos + plen > end)
                goto corrupted;
              if (!(session_item= ma_multi_malloc(0,
                                  &session_item, sizeof(LIST),
                                  &str, sizeof(MYSQL_LEX_STRING),
                                  &data, plen,
                                  NULL)))
                  goto oom;
              str->length= plen;
              str->str= data;
              memcpy(str->str, (char *)pos, plen);
              pos+= plen;
              session_item->data= str;
              mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item);

              /* in case schema has changed, we have to update mysql->db */
              if (si_type == SESSION_TRACK_SCHEMA)
              {
                free(mysql->db);
                mysql->db= malloc(plen + 1);
                memcpy(mysql->db, str->str, plen);
                mysql->db[plen]= 0;
              }
              else if (si_type == SESSION_TRACK_SYSTEM_VARIABLES)
              {
                my_bool set_charset= 0;
                my_bool get_capability_flag = 0;
                
                // char buffer[100] = {0};
                // memcpy(buffer, str->str, str->length);
                // printf("system variables length is %u is %.*s\n", str->length, str->length, buffer);
                /* make sure that we update charset in case it has changed */
                if (!strncmp(str->str, "character_set_client", str->length))
                  set_charset= 1;
                if (!strncmp(str->str, "ob_capability_flag", str->length)) {
                  get_capability_flag = 1;
                }
                if (!strncmp(str->str, "nls_date_format", str->length)) {
                  nls_time_type = NLS_DATE_FORMAT;
                } else if(!strncmp(str->str, "nls_timestamp_format", str->length)) {
                  nls_time_type = NLS_TIMESTAMP_FORMAT;
                } else if (!strncmp(str->str, "nls_timestamp_tz_format", str->length)) {
                  nls_time_type = NLS_TIMESTAMP_TZ_FORMAT;
                } else {
                  nls_time_type = NLS_TIME_MAX;
                }
                plen= net_field_length(&pos);
                if (pos + plen > end)
                  goto corrupted;
                if (!(session_item= ma_multi_malloc(0,
                                    &session_item, sizeof(LIST),
                                    &str, sizeof(MYSQL_LEX_STRING),
                                    &data, plen,
                                    NULL)))
                  goto oom;
                str->length= plen;
                str->str= data;
                memcpy(str->str, (char *)pos, plen);
                pos+= plen;
                session_item->data= str;
                mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item);
                if (set_charset && str->length < CHARSET_NAME_LEN &&
                    strncmp(mysql->charset->csname, str->str, str->length) != 0)
                {
                  char cs_name[CHARSET_NAME_LEN];
                  const MARIADB_CHARSET_INFO *cs_info;
                  memcpy(cs_name, str->str, str->length);
                  cs_name[str->length]= 0;
                  if ((cs_info = mysql_find_charset_name(cs_name)))
                    mysql->charset= cs_info;
                }
                if (1 == get_capability_flag) {
                  #define MAX_INT64_STORE_LEN 31
                  char int_buf[MAX_INT64_STORE_LEN + 1] = {0};
                  char *end_ptr = NULL;
                  if (MAX_INT64_STORE_LEN < plen) {
                    // error
                    goto corrupted;
                  }
                  memcpy(int_buf, str->str, plen);
                  int_buf[plen + 1] = '\0';
                  mysql->capability = strtoull(int_buf, &end_ptr, 10);
                  if (('\0' != *int_buf ) && ('\0' == *end_ptr)) {
                    // succ, do nothing
                  } else {
                    // error
                    goto corrupted;
                  }
                }
                if (nls_time_type >= NLS_DATE_FORMAT && nls_time_type < NLS_TIME_MAX) {
                  if (nls_time_type == NLS_DATE_FORMAT) {
                    mysql_nls = EXT_NLS_DATE_FORMAT(mysql);
                  } else if (nls_time_type == NLS_TIMESTAMP_FORMAT) {
                    mysql_nls = EXT_NLS_TIMESTAMP_FORMAT(mysql);
                  } else if (nls_time_type == NLS_TIMESTAMP_TZ_FORMAT) {
                    mysql_nls = EXT_NLS_TIMESTAMP_TZ_FORMAT(mysql);
                  }
                  memcpy(mysql_nls, str->str, str->length);
                  mysql_nls[str->length] = '\0';
                }
              }
              break;
            default:
              /* not supported yet */
              plen= net_field_length(&pos);
              if (pos + plen > end)
                goto corrupted;
              pos+= plen;
              break;
            }
          }
        }
        for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++)
        {
          mysql->extension->session_state[i].list= list_reverse(mysql->extension->session_state[i].list);
          mysql->extension->session_state[i].current= mysql->extension->session_state[i].list;
        }
      }
    }
  }
  /* CONC-351: clear session state information */
  else if (mysql->server_capabilities & CLIENT_SESSION_TRACKING)
    ma_clear_session_state(mysql);
  return(0);

oom:
  ma_clear_session_state(mysql);
  SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
  return -1;

corrupted:
  ma_clear_session_state(mysql);
  SET_CLIENT_ERROR(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
  return -1;
}

int mthd_my_read_query_result(MYSQL *mysql)
{
  uchar *pos;
  ulong field_count;
  MYSQL_DATA *fields;
  ulong length;
  my_bool can_local_infile= (mysql->options.extension) && (mysql->extension->auto_local_infile != WAIT_FOR_QUERY);

  if (mysql->options.extension && mysql->extension->auto_local_infile == ACCEPT_FILE_REQUEST)
    mysql->extension->auto_local_infile= WAIT_FOR_QUERY;

  if ((length = ma_net_safe_read(mysql)) == packet_error)
  {
    return(1);
  }
  free_old_query(mysql);			/* Free old result */
get_info:
  pos=(uchar*) mysql->net.read_pos;
  if ((field_count= net_field_length(&pos)) == 0)
    return ma_read_ok_packet(mysql, pos, length);
  if (field_count == NULL_LENGTH)		/* LOAD DATA LOCAL INFILE */
  {
    int error=mysql_handle_local_infile(mysql, (char *)pos, can_local_infile);

    if ((length=ma_net_safe_read(mysql)) == packet_error || error)
      return(-1);
    goto get_info;				/* Get info packet */
  }
  if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT))
    mysql->server_status|= SERVER_STATUS_IN_TRANS;

  mysql->extra_info= net_field_length_ll(&pos); /* Maybe number of rec */
  if (!(fields=mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,
                                            ma_result_set_rows(mysql))))
    return(-1);
  if (!(mysql->fields=unpack_fields(mysql, fields, &mysql->field_alloc,
				    (uint) field_count, 1)))
    return(-1);
  mysql->status=MYSQL_STATUS_GET_RESULT;
  mysql->field_count=field_count;
  return(0);
}

int STDCALL mysql_session_track_get_next(MYSQL *mysql, enum enum_session_state_type type,
                                         const char **data, size_t *length)
{
  MYSQL_LEX_STRING *str;
  if (!mysql->extension->session_state[type].current)
    return 1;

  str= (MYSQL_LEX_STRING *)mysql->extension->session_state[type].current->data;
  mysql->extension->session_state[type].current= mysql->extension->session_state[type].current->next;

  *data= str->str ? str->str : NULL;
  *length= str->str ? str->length : 0;
  return 0;
}

int STDCALL mysql_session_track_get_first(MYSQL *mysql, enum enum_session_state_type type,
                                          const char **data, size_t *length)
{
  mysql->extension->session_state[type].current= mysql->extension->session_state[type].list;
  return mysql_session_track_get_next(mysql, type, data, length);
}

my_bool STDCALL
mysql_read_query_result(MYSQL *mysql)
{
  return test(mysql->methods->db_read_query_result(mysql)) ? 1 : 0;
}

int STDCALL
mysql_real_query(MYSQL *mysql, const char *query, unsigned long length)
{
  my_bool skip_result= OPT_EXT_VAL(mysql, multi_command);
  int ret = 0;
  FLT_DECLARE;

  // flt begin span
  FLT_BEFORE_COMMAND(0, FLT_TAG_COMMAND_NAME, "\"mysql_real_query\"");

  if (length == (unsigned long)-1)
    length= (unsigned long)strlen(query);

  free_old_query(mysql);

  if (ma_simple_command(mysql, COM_QUERY,query,length,1,0)) {
    ret = -1;
  } else if (!skip_result) {
    ret = (mysql->methods->db_read_query_result(mysql));
  }

  // flt end span
  FLT_AFTER_COMMAND;

  return ret;
}

/**************************************************************************
** Alloc result struct for buffered results. All rows are read to buffer.
** mysql_data_seek may be used.
**************************************************************************/

MYSQL_RES * STDCALL
mysql_store_result(MYSQL *mysql)
{
  MYSQL_RES *result;

  if (!mysql->fields)
    return(0);
  if (mysql->status != MYSQL_STATUS_GET_RESULT)
  {
    //SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0);
    SET_CLIENT_ERROR(mysql, CR_STATUS_ERROR_NOT_GET_RESULT, SQLSTATE_UNKNOWN, 0);
    return(0);
  }
  mysql->status=MYSQL_STATUS_READY;		/* server is ready */
  if (!(result=(MYSQL_RES*) calloc(1, sizeof(MYSQL_RES)+
				       sizeof(ulong)*mysql->field_count)))
  {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    return(0);
  }
  result->eof=1;				/* Marker for buffered */
  result->lengths=(ulong*) (result+1);
  if (!(result->data=mysql->methods->db_read_rows(mysql,mysql->fields,mysql->field_count)))
  {
    free(result);
    return(0);
  }
  mysql->affected_rows= result->row_count= result->data->rows;
  result->data_cursor=	result->data->data;
  result->fields=	mysql->fields;
  result->field_alloc=	mysql->field_alloc;
  result->field_count=	mysql->field_count;
  result->current_field=0;
  result->current_row=0;			/* Must do a fetch first */
  mysql->fields=0;				/* fields is now in result */
  return(result);				/* Data fetched */
}


/**************************************************************************
** Alloc struct for use with unbuffered reads. Data is fetched by domand
** when calling to mysql_fetch_row.
** mysql_data_seek is a noop.
**
** No other queries may be specified with the same MYSQL handle.
** There shouldn't be much processing per row because mysql server shouldn't
** have to wait for the client (and will not wait more than 30 sec/packet).
**************************************************************************/

MYSQL_RES * STDCALL
mysql_use_result(MYSQL *mysql)
{
  MYSQL_RES *result;
  OB_RES_EXT *res_ext;

  if (!mysql->fields)
    return(0);
  if (mysql->status != MYSQL_STATUS_GET_RESULT)
  {
    //SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0);
    SET_CLIENT_ERROR(mysql, CR_STATUS_ERROR_NOT_GET_RESULT, SQLSTATE_UNKNOWN, 0);
    return(0);
  }
  if (!(result=(MYSQL_RES*) calloc(1, sizeof(*result)+
				      sizeof(ulong)*mysql->field_count)))
    return(0);
  result->lengths=(ulong*) (result+1);
  if (!(result->row=(MYSQL_ROW)
	malloc(sizeof(result->row[0])*(mysql->field_count+1))))
  {					/* Ptrs: to one row */
    free(result);
    return(0);
  }
  result->fields=	mysql->fields;
  result->field_alloc=	mysql->field_alloc;
  result->field_count=	mysql->field_count;
  result->current_field=0;
  result->handle=	mysql;
  result->current_row=	0;
  if (mysql->oracle_mode && NULL == mysql->ob_extension) {
    // error
    return(0);
  } else {
    res_ext = &(((MYSQL_EXTENSION *)(mysql->ob_extension))->res_extension);
    res_ext->mysql_fields = mysql->fields;
  }
  mysql->fields=0;			/* fields is now in result */
  mysql->status=MYSQL_STATUS_USE_RESULT;
  return(result);			/* Data is read to be fetched */
}

/**************************************************************************
** Return next field of the query results
**************************************************************************/
MYSQL_FIELD * STDCALL
mysql_fetch_field(MYSQL_RES *result)
{
  if (result->current_field >= result->field_count)
    return(NULL);
  return &result->fields[result->current_field++];
}


/**************************************************************************
** Return mysql field metadata
**************************************************************************/
int STDCALL
mariadb_field_attr(MARIADB_CONST_STRING *attr,
                   const MYSQL_FIELD *field,
                   enum mariadb_field_attr_t type)
{
  MA_FIELD_EXTENSION *ext= (MA_FIELD_EXTENSION*) field->extension;
  if (!ext || type > MARIADB_FIELD_ATTR_LAST)
  {
    *attr= null_const_string;
    return 1;
  }
  *attr= ext->metadata[type];
  return 0;
}


/**************************************************************************
**  Return next row of the query results
**************************************************************************/
MYSQL_ROW STDCALL
mysql_fetch_row(MYSQL_RES *res)
{
  if (!res)
    return 0;
  if (res->handle)
  {
    if (res->handle->status != MYSQL_STATUS_USE_RESULT &&
        res->handle->status != MYSQL_STATUS_GET_RESULT)
      return 0;
  }
  if (!res->data)
  {						/* Unbufferred fetch */
    if (!res->eof && res->handle)
    {
      if (!(res->handle->methods->db_read_one_row(res->handle,res->field_count,res->row, res->lengths)))
      {
        res->row_count++;
        return(res->current_row=res->row);
      }
      res->eof=1;
      res->handle->status=MYSQL_STATUS_READY;
       /* Don't clear handle in mysql_free_results */
      res->handle=0;
    }
    return((MYSQL_ROW) NULL);
  }
  {
    MYSQL_ROW tmp;
    if (!res->data_cursor)
    {
      return(res->current_row=(MYSQL_ROW) NULL);
    }
    tmp = res->data_cursor->data;
    res->data_cursor = res->data_cursor->next;
    return(res->current_row=tmp);
  }
}

/**************************************************************************
** Get column lengths of the current row
** If one uses mysql_use_result, res->lengths contains the length information,
** else the lengths are calculated from the offset between pointers.
**************************************************************************/

ulong * STDCALL
mysql_fetch_lengths(MYSQL_RES *res)
{
  ulong *lengths,*prev_length;
  char *start;
  MYSQL_ROW column,end;

  if (!(column=res->current_row))
    return 0;					/* Something is wrong */
  if (res->data)
  {
    start=0;
    prev_length=0;				/* Keep gcc happy */
    lengths=res->lengths;
    for (end=column+res->field_count+1 ; column != end ; column++,lengths++)
    {
      if (!*column)
      {
	*lengths=0;				/* Null */
	continue;
      }
      if (start)				/* Found end of prev string */
	*prev_length= (uint) (*column-start-1);
      start= *column;
      prev_length=lengths;
    }
  }
  return res->lengths;
}

/**************************************************************************
** Move to a specific row and column
**************************************************************************/

void STDCALL
mysql_data_seek(MYSQL_RES *result, unsigned long long row)
{
  MYSQL_ROWS	*tmp=0;
  if (result->data)
    for (tmp=result->data->data; row-- && tmp ; tmp = tmp->next) ;
  result->current_row=0;
  result->data_cursor = tmp;
}

/*************************************************************************
** put the row or field cursor one a position one got from mysql_row_tell()
** This doesn't restore any data. The next mysql_fetch_row or
** mysql_fetch_field will return the next row or field after the last used
*************************************************************************/

MYSQL_ROW_OFFSET STDCALL
mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET row)
{
  MYSQL_ROW_OFFSET return_value=result->data_cursor;
  result->current_row= 0;
  result->data_cursor= row;
  return return_value;
}


MYSQL_FIELD_OFFSET STDCALL
mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET field_offset)
{
  MYSQL_FIELD_OFFSET return_value=result->current_field;
  result->current_field=field_offset;
  return return_value;
}

/********************************************************
 Warning: mysql_list_dbs is deprecated and will be
          removed. Use SQL statement "SHOW DATABASES"
          instead
 ********************************************************/

/*****************************************************************************
** List all databases
*****************************************************************************/

MYSQL_RES * STDCALL
mysql_list_dbs(MYSQL *mysql, const char *wild)
{
  char buff[255];
  snprintf(buff, 255, "SHOW DATABASES LIKE '%s'", wild ? wild : "%");
  if (mysql_query(mysql,buff))
    return(0);
  return (mysql_store_result(mysql));
}


/********************************************************
 Warning: mysql_list_tables is deprecated and will be
          removed. Use SQL statement "SHOW TABLES"
          instead
 ********************************************************/
/*****************************************************************************
** List all tables in a database
** If wild is given then only the tables matching wild are returned
*****************************************************************************/

MYSQL_RES * STDCALL
mysql_list_tables(MYSQL *mysql, const char *wild)
{
  char buff[255];

  snprintf(buff, 255, "SHOW TABLES LIKE '%s'", wild ? wild : "%");
  if (mysql_query(mysql,buff))
    return(0);
  return (mysql_store_result(mysql));
}


/**************************************************************************
** List all fields in a table
** If wild is given then only the fields matching wild are returned
** Instead of this use query:
** show fields in 'table' like "wild"
**************************************************************************/

MYSQL_RES * STDCALL
mysql_list_fields(MYSQL *mysql, const char *table, const char *wild)
{
  MYSQL_RES *result;
  MYSQL_DATA *query;
  char buff[255];
  int length= 0;

  LINT_INIT(query);

  length= snprintf(buff, 128, "%s%c%s", table, '\0', wild ? wild : "");

  if (ma_simple_command(mysql, COM_FIELD_LIST,buff,length,1,0) ||
      !(query = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,
                                             ma_result_set_rows(mysql))))
    return(NULL);

  free_old_query(mysql);
  if (!(result = (MYSQL_RES *) calloc(1, sizeof(MYSQL_RES))))
  {
    free_rows(query);
    return(NULL);
  }
  result->field_alloc=mysql->field_alloc;
  mysql->fields=0;
  result->eof=1;
  result->field_count = (uint) query->rows;
  result->fields= unpack_fields(mysql, query, &result->field_alloc,
				result->field_count, 1);
  if (result->fields)
    return(result);

  free(result);
  return(NULL);
}

/********************************************************
 Warning: mysql_list_processes is deprecated and will be
          removed. Use SQL statement "SHOW PROCESSLIST"
          instead
 ********************************************************/

/* List all running processes (threads) in server */

MYSQL_RES * STDCALL
mysql_list_processes(MYSQL *mysql)
{
  MYSQL_DATA *fields;
  uint field_count;
  uchar *pos;

  LINT_INIT(fields);
  if (ma_simple_command(mysql, COM_PROCESS_INFO,0,0,0,0))
    return(NULL);
  free_old_query(mysql);
  pos=(uchar*) mysql->net.read_pos;
  field_count=(uint) net_field_length(&pos);
  if (!(fields = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,7)))
    return(NULL);
  if (!(mysql->fields= unpack_fields(mysql, fields, &mysql->field_alloc,
                                     field_count, 0)))
    return(NULL);
  mysql->status=MYSQL_STATUS_GET_RESULT;
  mysql->field_count=field_count;
  return(mysql_store_result(mysql));
}

/* In 5.0 this version became an additional parameter shutdown_level */
int STDCALL
mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level)
{
  uchar s_level[2];
  s_level[0]= (uchar)shutdown_level;
  return(ma_simple_command(mysql, COM_SHUTDOWN, (char *)s_level, 1, 0, 0));
}

int STDCALL
mysql_refresh(MYSQL *mysql,uint options)
{
  uchar bits[1];
  bits[0]= (uchar) options;
  return(ma_simple_command(mysql, COM_REFRESH,(char*) bits,1,0,0));
}

int STDCALL
mysql_kill(MYSQL *mysql,ulong pid)
{
  char buff[12];
  int4store(buff,pid);
  /* if we kill our own thread, reading the response packet will fail */
  return(ma_simple_command(mysql, COM_PROCESS_KILL,buff,4,0,0));
}


int STDCALL
mysql_dump_debug_info(MYSQL *mysql)
{
  return(ma_simple_command(mysql, COM_DEBUG,0,0,0,0));
}

char * STDCALL
mysql_stat(MYSQL *mysql)
{
  if (ma_simple_command(mysql, COM_STATISTICS,0,0,0,0))
    return mysql->net.last_error;
  mysql->net.read_pos[mysql->packet_length]=0;	/* End of stat string */
  if (!mysql->net.read_pos[0])
  {
    SET_CLIENT_ERROR(mysql, CR_WRONG_HOST_INFO , SQLSTATE_UNKNOWN, 0);
    return mysql->net.last_error;
  }
  return((char*) mysql->net.read_pos);
}

int STDCALL
mysql_ping(MYSQL *mysql)
{
  int rc;
  rc= ma_simple_command(mysql, COM_PING, 0, 0, 0, 0);
  if (rc && mysql->options.reconnect)
    rc= ma_simple_command(mysql, COM_PING, 0, 0, 0, 0);
  return rc;
}

char * STDCALL
mysql_get_server_info(MYSQL *mysql)
{
  return((char*) mysql->server_version);
}

static size_t mariadb_server_version_id(MYSQL *mysql)
{
  size_t major, minor, patch;
  char *p;

  if (!(p = mysql->server_version)) {
    return 0;
  }

  major = strtol(p, &p, 10);
  p += 1; /* consume the dot */
  minor = strtol(p, &p, 10);
  p += 1; /* consume the dot */
  patch = strtol(p, &p, 10);

  return (major * 10000L + (unsigned long)(minor * 100L + patch));
}

unsigned long STDCALL mysql_get_server_version(MYSQL *mysql)
{
  return (unsigned long)mariadb_server_version_id(mysql);
}

char * STDCALL
mysql_get_host_info(MYSQL *mysql)
{
  return(mysql->host_info);
}

uint STDCALL
mysql_get_proto_info(MYSQL *mysql)
{
  return (mysql->protocol_version);
}

const char * STDCALL
mysql_get_client_info(void)
{
  return (char*) MARIADB_CLIENT_VERSION_STR;
}

static size_t get_store_length(size_t length)
{
  #define MAX_STORE_SIZE 9
  unsigned char buffer[MAX_STORE_SIZE], *p;

  /* We just store the length and subtract offset of our buffer
     to determine the length */
  p= mysql_net_store_length(buffer, length);
  return p - buffer;
}

uchar *ma_get_hash_keyval(const uchar *hash_entry,
                       unsigned int *length,
                       my_bool not_used __attribute__((unused)))
{
  /* Hash entry has the following format:
     Offset: 0               key (\0 terminated)
             key_length + 1  value (\0 terminated)
  */
  uchar *p= (uchar *)hash_entry;
  size_t len= strlen((char *)p);
  *length= (unsigned int)len;
  return p;
}

void ma_int_hash_free(void *p)
{
  free(p);
}

int
mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...)
{
  va_list ap;
  void *arg1;
  size_t stacksize;
  struct mysql_async_context *ctxt;

  va_start(ap, option);

  arg1= va_arg(ap, void *);

  switch (option) {
  case MYSQL_OPT_CONNECT_TIMEOUT:
    mysql->options.connect_timeout= *(uint*) arg1;
    break;
  case MYSQL_OPT_COMPRESS:
    mysql->options.compress= 1;			/* Remember for connect */
    mysql->options.client_flag|= CLIENT_COMPRESS;
    break;
  case MYSQL_OPT_NAMED_PIPE:
    mysql->options.named_pipe=1;		/* Force named pipe */
    break;
  case MYSQL_OPT_LOCAL_INFILE:			/* Allow LOAD DATA LOCAL ?*/
    if (!arg1 || test(*(unsigned int*) arg1))
      mysql->options.client_flag|= CLIENT_LOCAL_FILES;
    else
      mysql->options.client_flag&= ~CLIENT_LOCAL_FILES;
    if (arg1) {
      CHECK_OPT_EXTENSION_SET(&mysql->options);
      mysql->extension->auto_local_infile= *(uint*)arg1 == LOCAL_INFILE_MODE_AUTO
                                           ? WAIT_FOR_QUERY : ALWAYS_ACCEPT;
    }
    break;
  case MYSQL_INIT_COMMAND:
    options_add_initcommand(&mysql->options, (char *)arg1);
    break;
  case MYSQL_READ_DEFAULT_FILE:
    OPT_SET_VALUE_STR(&mysql->options, my_cnf_file, (char *)arg1);
    break;
  case MYSQL_READ_DEFAULT_GROUP:
    OPT_SET_VALUE_STR(&mysql->options, my_cnf_group, arg1 ? (char *)arg1 : "");
    break;
  case MYSQL_SET_CHARSET_DIR:
    OPT_SET_VALUE_STR(&mysql->options, charset_dir, arg1);
    break;
  case MYSQL_SET_CHARSET_NAME:
    OPT_SET_VALUE_STR(&mysql->options, charset_name, arg1);
    break;
  case MYSQL_OPT_RECONNECT:
    mysql->options.reconnect= *(my_bool *)arg1;
    break;
  case MYSQL_OPT_PROTOCOL:
    mysql->options.protocol= *((uint *)arg1);
    break;
#ifdef _WIN32
  case MYSQL_SHARED_MEMORY_BASE_NAME:
    OPT_SET_VALUE_STR(&mysql->options, shared_memory_base_name, arg1);
    break;
#endif
  case MYSQL_OPT_READ_TIMEOUT:
    mysql->options.read_timeout= *(uint *)arg1;
    break;
  case MYSQL_OPT_WRITE_TIMEOUT:
    mysql->options.write_timeout= *(uint *)arg1;
    break;
  case MYSQL_REPORT_DATA_TRUNCATION:
    mysql->options.report_data_truncation= *(my_bool *)arg1;
    break;
  case MYSQL_PROGRESS_CALLBACK:
    CHECK_OPT_EXTENSION_SET(&mysql->options);
    if (mysql->options.extension)
      mysql->options.extension->report_progress=
        (void (*)(const MYSQL *, uint, uint, double, const char *, uint)) arg1;
    break;
  case MYSQL_SERVER_PUBLIC_KEY:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, server_public_key, (char *)arg1);
    break;
  case MYSQL_PLUGIN_DIR:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, plugin_dir, (char *)arg1);
    break;
  case MYSQL_DEFAULT_AUTH:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, default_auth, (char *)arg1);
    break;
  case MYSQL_OPT_NONBLOCK:
    if (mysql->options.extension &&
        (ctxt = mysql->options.extension->async_context) != 0)
    {
      /*
        We must not allow changing the stack size while a non-blocking call is
        suspended (as the stack is then in use).
      */
      if (ctxt->suspended)
        goto end;
      my_context_destroy(&ctxt->async_context);
      free(ctxt);
    }
    if (!(ctxt= (struct mysql_async_context *)
          calloc(1, sizeof(*ctxt))))
    {
      SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
      goto end;
    }
    stacksize= 0;
    if (arg1)
      stacksize= *(const size_t *)arg1;
    if (!stacksize)
      stacksize= ASYNC_CONTEXT_DEFAULT_STACK_SIZE;
    if (my_context_init(&ctxt->async_context, stacksize))
    {
      free(ctxt);
      goto end;
    }
    if (!mysql->options.extension)
      if(!(mysql->options.extension= (struct st_mysql_options_extension *)
        calloc(1, sizeof(struct st_mysql_options_extension))))
      {
        free(ctxt);
        SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
        goto end;
      }
    mysql->options.extension->async_context= ctxt;
    break;
  case MYSQL_OPT_MAX_ALLOWED_PACKET:
    if (mysql)
      mysql->options.max_allowed_packet= (unsigned long)(*(size_t *)arg1);
    else
      max_allowed_packet= (unsigned long)(*(size_t *)arg1);
    break;
  case MYSQL_OPT_NET_BUFFER_LENGTH:
    net_buffer_length= (unsigned long)(*(size_t *)arg1);
    break;
  case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
    if (*(my_bool *)arg1)
      mysql->options.client_flag |= CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
    else
      mysql->options.client_flag &= ~CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
    break;
  case MYSQL_OPT_SSL_ENFORCE:
    mysql->options.use_ssl= (*(my_bool *)arg1);
    break;
  case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
    if (*(my_bool *)arg1)
      mysql->options.client_flag |= CLIENT_SSL_VERIFY_SERVER_CERT;
    else
      mysql->options.client_flag &= ~CLIENT_SSL_VERIFY_SERVER_CERT;
    break;
  case MYSQL_OPT_SSL_KEY:
    OPT_SET_VALUE_STR(&mysql->options, ssl_key, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CERT:
    OPT_SET_VALUE_STR(&mysql->options, ssl_cert, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CA:
    OPT_SET_VALUE_STR(&mysql->options, ssl_ca, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CAPATH:
    OPT_SET_VALUE_STR(&mysql->options, ssl_capath, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CIPHER:
    OPT_SET_VALUE_STR(&mysql->options, ssl_cipher, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CRL:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crl, (char *)arg1);
    break;
  case MYSQL_OPT_SSL_CRLPATH:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crlpath, (char *)arg1);
    break;
  case MYSQL_OPT_CONNECT_ATTR_DELETE:
    {
      uchar *h;
      CHECK_OPT_EXTENSION_SET(&mysql->options);
      if (hash_inited(&mysql->options.extension->connect_attrs) &&
          (h= (uchar *)hash_search(&mysql->options.extension->connect_attrs, (uchar *)arg1,
                      arg1 ? (uint)strlen((char *)arg1) : 0)))
      {
        uchar *p= h;
        size_t key_len= strlen((char *)p);
        mysql->options.extension->connect_attrs_len-= key_len + get_store_length(key_len);
        p+= key_len + 1;
        key_len= strlen((char *)p);
        mysql->options.extension->connect_attrs_len-= key_len + get_store_length(key_len);
        hash_delete(&mysql->options.extension->connect_attrs, h);
      }

    }
    break;
  case MYSQL_OPT_CONNECT_ATTR_RESET:
    CHECK_OPT_EXTENSION_SET(&mysql->options);
    if (hash_inited(&mysql->options.extension->connect_attrs))
    {
      hash_free(&mysql->options.extension->connect_attrs);
      mysql->options.extension->connect_attrs_len= 0;
    }
    break;
  case MARIADB_OPT_CONNECTION_HANDLER:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, connection_handler, (char *)arg1);
    break;
  case MARIADB_OPT_PORT:
    OPT_SET_VALUE_INT(&mysql->options, port, *((uint *)arg1));
    break;
  case MARIADB_OPT_UNIXSOCKET:
    OPT_SET_VALUE_STR(&mysql->options, unix_socket, arg1);
    break;
  case MARIADB_OPT_USER:
    OPT_SET_VALUE_STR(&mysql->options, user, arg1);
    break;
  case OB_OPT_PROXY_USER:
    OPT_SET_VALUE_STR(&mysql->options, proxy_user, arg1);
  case MARIADB_OPT_HOST:
    OPT_SET_VALUE_STR(&mysql->options, host, arg1);
    break;
  case MARIADB_OPT_SCHEMA:
    OPT_SET_VALUE_STR(&mysql->options, db, arg1);
    break;
  case MARIADB_OPT_DEBUG:
    break;
  case MARIADB_OPT_FOUND_ROWS:
    mysql->options.client_flag|= CLIENT_FOUND_ROWS;
    break;
  case MARIADB_OPT_INTERACTIVE:
    mysql->options.client_flag|= CLIENT_INTERACTIVE;
    break;
  case MARIADB_OPT_MULTI_RESULTS:
    mysql->options.client_flag|= CLIENT_MULTI_RESULTS;
    break;
  case MARIADB_OPT_MULTI_STATEMENTS:
    mysql->options.client_flag|= CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS;
    break;
  case MARIADB_OPT_PASSWORD:
    OPT_SET_VALUE_STR(&mysql->options, password, arg1);
    break;
  case MARIADB_OPT_USERDATA:
    {
      void *data= va_arg(ap, void *);
      uchar *buffer, *p;
      char *key= (char *)arg1;

      if (!key || !data)
      {
        SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0);
        goto end;
      }

      CHECK_OPT_EXTENSION_SET(&mysql->options);
      if (!hash_inited(&mysql->options.extension->userdata))
      {
        if (_hash_init(&mysql->options.extension->userdata,
                       0, 0, 0, ma_get_hash_keyval, ma_int_hash_free, 0))
        {
          SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
          goto end;
        }
      }
      /* check if key is already in buffer */
      p= (uchar *)hash_search(&mysql->options.extension->userdata, 
                              (uchar *)key,
                              (uint)strlen(key));
      if (p)
      {
        p+= strlen(key) + 1;
        memcpy(p, &data, sizeof(void *));
        break;
      }

      if (!(buffer= (uchar *)malloc(strlen(key) + 1 + sizeof(void *))))
      {
        SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
        goto end;
      }

      p= buffer;
      strcpy((char *)p, key);
      p+= strlen(key) + 1;
      memcpy(p, &data, sizeof(void *));

      if (hash_insert(&mysql->options.extension->userdata, buffer))
      {
        free(buffer);
        SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0);
        goto end;
      }
    }
    break;
  case MYSQL_OPT_CONNECT_ATTR_ADD:
    {
      uchar *buffer;
      void *arg2= va_arg(ap, void *);
      size_t storage_len, key_len= arg1 ? strlen((char *)arg1) : 0,
             value_len= arg2 ? strlen((char *)arg2) : 0;
      if (!key_len || !value_len)
      {
        SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0);
        goto end;
      }
      storage_len= key_len + value_len +
                   get_store_length(key_len) +
                   get_store_length(value_len);

      /* since we store terminating zero character in hash, we need
       * to increase lengths */
      key_len++;
      value_len++;

      CHECK_OPT_EXTENSION_SET(&mysql->options);
      if (!key_len ||
          storage_len + mysql->options.extension->connect_attrs_len > 0xFFFF)
      {
        SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0);
        goto end;
      }

      if (!hash_inited(&mysql->options.extension->connect_attrs))
      {
        if (_hash_init(&mysql->options.extension->connect_attrs,
                       0, 0, 0, ma_get_hash_keyval, ma_int_hash_free, 0))
        {
          SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
          goto end;
        }
      }
      if ((buffer= (uchar *)malloc(key_len + value_len)))
      {
        uchar *p= buffer;
        strcpy((char *)p, arg1);
        p+= (strlen(arg1) + 1);
        if (arg2)
          strcpy((char *)p, arg2);

        if (hash_insert(&mysql->options.extension->connect_attrs, buffer))
        {
          free(buffer);
          SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0);
          goto end;
        }
        mysql->options.extension->connect_attrs_len+= storage_len;
      }
      else
      {
        SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
        goto end;
      }
    }
    break;
  case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
    break;
  case MYSQL_SECURE_AUTH:
    mysql->options.secure_auth= *(my_bool *)arg1;
    break;
  case MYSQL_OPT_BIND:
    OPT_SET_VALUE_STR(&mysql->options, bind_address, arg1);
    break;
  case MARIADB_OPT_TLS_CIPHER_STRENGTH:
    OPT_SET_EXTENDED_VALUE_INT(&mysql->options, tls_cipher_strength, *((unsigned int *)arg1));
    break;
  case MARIADB_OPT_SSL_FP:
  case MARIADB_OPT_TLS_PEER_FP:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_fp, (char *)arg1);
    mysql->options.use_ssl= 1;
    break;
  case MARIADB_OPT_SSL_FP_LIST:
  case MARIADB_OPT_TLS_PEER_FP_LIST:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_fp_list, (char *)arg1);
    mysql->options.use_ssl= 1;
    break;
  case MARIADB_OPT_TLS_PASSPHRASE:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_pw, (char *)arg1);
    break;
  case MARIADB_OPT_CONNECTION_READ_ONLY:
    OPT_SET_EXTENDED_VALUE_INT(&mysql->options, read_only, *(my_bool *)arg1);
    break;
  case MARIADB_OPT_PROXY_HEADER:
    {
    size_t arg2 = va_arg(ap, size_t);
    OPT_SET_EXTENDED_VALUE(&mysql->options, proxy_header, (char *)arg1);
    OPT_SET_EXTENDED_VALUE(&mysql->options, proxy_header_len, arg2);
    }
    break;
  case MARIADB_OPT_TLS_VERSION:
  case MYSQL_OPT_TLS_VERSION:
    OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_version, (char *)arg1);
    break;
  case MARIADB_OPT_IO_WAIT:
    CHECK_OPT_EXTENSION_SET(&mysql->options);
    mysql->options.extension->io_wait = (int(*)(my_socket, my_bool, int))arg1;
    break;
  default:
    va_end(ap);
    SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0);
    return(1);
  }
  va_end(ap);
  return(0);
end:
  va_end(ap);
  return(1);
}

int
mysql_get_optionv(MYSQL *mysql, enum mysql_option option, void *arg, ...)
{
  va_list ap;

  va_start(ap, arg);

  switch(option) {
  case MYSQL_OPT_CONNECT_TIMEOUT:
    *((uint *)arg)= mysql->options.connect_timeout;
    break;
  case MYSQL_OPT_COMPRESS:
    *((my_bool *)arg)= mysql->options.compress;
    break;
  case MYSQL_OPT_NAMED_PIPE:
    *((my_bool *)arg)= mysql->options.named_pipe;
    break;
  case MYSQL_OPT_LOCAL_INFILE:			/* Allow LOAD DATA LOCAL ?*/
    *((uint *)arg)= test(mysql->options.client_flag & CLIENT_LOCAL_FILES);
    break;
  case MYSQL_INIT_COMMAND:
    /* mysql_get_optionsv(mysql, MYSQL_INIT_COMMAND, commands, elements) */
    {
      unsigned int *elements;
      if (arg)
        *((char **)arg)= mysql->options.init_command ? mysql->options.init_command->buffer : NULL;
      if ((elements= va_arg(ap, unsigned int *)))
        *elements= mysql->options.init_command ? mysql->options.init_command->elements : 0;
    }
    break;
  case MYSQL_READ_DEFAULT_FILE:
    *((char **)arg)= mysql->options.my_cnf_file;
    break;
  case MYSQL_READ_DEFAULT_GROUP:
    *((char **)arg)= mysql->options.my_cnf_group;
    break;
  case MYSQL_SET_CHARSET_DIR:
    /* not supported in this version. Since all character sets
       are internally available, we don't throw an error */
    *((char **)arg)= NULL;
    break;
  case MYSQL_SET_CHARSET_NAME:
    if (mysql->charset)
      *((const char **)arg)= mysql->charset->csname;
    else
      *((char **)arg)= mysql->options.charset_name;
    break;
  case MYSQL_OPT_RECONNECT:
    *((my_bool *)arg)= mysql->options.reconnect;
    break;
  case MYSQL_OPT_PROTOCOL:
    *((uint *)arg)= mysql->options.protocol;
    break;
  case MYSQL_OPT_READ_TIMEOUT:
    *((uint *)arg)= mysql->options.read_timeout;
    break;
  case MYSQL_OPT_WRITE_TIMEOUT:
    *((uint *)arg)= mysql->options.write_timeout;
    break;
  case MYSQL_REPORT_DATA_TRUNCATION:
    *((my_bool *)arg)= mysql->options.report_data_truncation;
    break;
  case MYSQL_PROGRESS_CALLBACK:
    *((void (**)(const MYSQL *, uint, uint, double, const char *, uint))arg)=
       mysql->options.extension ?  mysql->options.extension->report_progress : NULL;
    break;
  case MYSQL_SERVER_PUBLIC_KEY:
    *((char **)arg)= mysql->options.extension ?
      mysql->options.extension->server_public_key : NULL;
    break;
  case MYSQL_PLUGIN_DIR:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->plugin_dir : NULL;
    break;
  case MYSQL_DEFAULT_AUTH:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->default_auth : NULL;
    break;
  case MYSQL_OPT_NONBLOCK:
    *((my_bool *)arg)= test(mysql->options.extension && mysql->options.extension->async_context);
    break;
  case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
    *((my_bool *)arg)= test(mysql->options.client_flag & CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS);
    break;
  case MYSQL_OPT_SSL_ENFORCE:
    *((my_bool *)arg)= mysql->options.use_ssl;
    break;
  case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
    *((my_bool *)arg)= test(mysql->options.client_flag & CLIENT_SSL_VERIFY_SERVER_CERT);
    break;
  case MYSQL_OPT_SSL_KEY:
    *((char **)arg)= mysql->options.ssl_key;
    break;
  case MYSQL_OPT_SSL_CERT:
    *((char **)arg)= mysql->options.ssl_cert;
    break;
  case MYSQL_OPT_SSL_CA:
    *((char **)arg)= mysql->options.ssl_ca;
    break;
  case MYSQL_OPT_SSL_CAPATH:
    *((char **)arg)= mysql->options.ssl_capath;
    break;
  case MYSQL_OPT_SSL_CIPHER:
    *((char **)arg)= mysql->options.ssl_cipher;
    break;
  case MYSQL_OPT_SSL_CRL:
    *((char **)arg)= mysql->options.extension ? mysql->options.ssl_cipher : NULL;
    break;
  case MYSQL_OPT_SSL_CRLPATH:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->ssl_crlpath : NULL;
    break;
  case MYSQL_OPT_CONNECT_ATTRS:
    /* mysql_get_optionsv(mysql, MYSQL_OPT_CONNECT_ATTRS, keys, vals, elements) */
    {
      unsigned int i, *elements;
      char **key= NULL;
      void *arg1;
      char **val= NULL;

      if (arg)
        key= *(char ***)arg;

      arg1= va_arg(ap, char **);
      if (arg1)
        val= *(char ***)arg1;

      if (!(elements= va_arg(ap, unsigned int *)))
        goto error;

      *elements= 0;

      if (!mysql->options.extension ||
          !hash_inited(&mysql->options.extension->connect_attrs))
        break;

      *elements= mysql->options.extension->connect_attrs.records;

      if (val || key)
      {
        for (i=0; i < *elements; i++)
        {
          uchar *p= hash_element(&mysql->options.extension->connect_attrs, i);
          if (key)
            key[i]= (char *)p;
          p+= strlen((char *)p) + 1;
          if (val)
            val[i]= (char *)p;
        }
      }
    }
    break;
  case MYSQL_OPT_MAX_ALLOWED_PACKET:
    *((unsigned long *)arg)= (mysql) ? mysql->options.max_allowed_packet :
                                       max_allowed_packet;
    break;
  case MYSQL_OPT_NET_BUFFER_LENGTH:
    *((unsigned long *)arg)= net_buffer_length;
    break;
  case MYSQL_SECURE_AUTH:
    *((my_bool *)arg)= mysql->options.secure_auth;
    break;
  case MYSQL_OPT_BIND:
    *((char **)arg)= mysql->options.bind_address;
    break;
  case MARIADB_OPT_TLS_CIPHER_STRENGTH:
    *((unsigned int *)arg) = mysql->options.extension ? mysql->options.extension->tls_cipher_strength : 0;
    break;
  case MARIADB_OPT_SSL_FP:
  case MARIADB_OPT_TLS_PEER_FP:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_fp : NULL;
    break;
  case MARIADB_OPT_SSL_FP_LIST:
  case MARIADB_OPT_TLS_PEER_FP_LIST:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_fp_list : NULL;
    break;
  case MARIADB_OPT_TLS_PASSPHRASE:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_pw : NULL;
    break;
  case MARIADB_OPT_CONNECTION_READ_ONLY:
    *((my_bool *)arg)= mysql->options.extension ? mysql->options.extension->read_only : 0;
    break;
  case MARIADB_OPT_USERDATA:
    /* nysql_get_optionv(mysql, MARIADB_OPT_USERDATA, key, value) */
    {
      uchar *p;
      void *data= va_arg(ap, void *);
      char *key= (char *)arg;
      if (key && data && mysql->options.extension && hash_inited(&mysql->options.extension->userdata) &&
          (p= (uchar *)hash_search(&mysql->options.extension->userdata, (uchar *)key,
                      (uint)strlen((char *)key))))
      {
        p+= strlen(key) + 1;
        *((void **)data)= *((void **)p);
        break;
      }
      if (data)
        *((void **)data)= NULL;
    }
    break;
  case MARIADB_OPT_CONNECTION_HANDLER:
    *((char **)arg)= mysql->options.extension ? mysql->options.extension->connection_handler : NULL;
    break;
  case MARIADB_OPT_IO_WAIT:
    *((int(**)(my_socket, my_bool, int))arg) = mysql->options.extension ? mysql->options.extension->io_wait : NULL;
    break;
  default:
    va_end(ap);
    SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0);
    return(1);
  }
  va_end(ap);
  return(0);
error:
  va_end(ap);
  return(1);
}

int STDCALL mysql_get_option(MYSQL *mysql, enum mysql_option option, void *arg)
{
  return mysql_get_optionv(mysql, option, arg);
}

int STDCALL
mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg)
{
  return mysql_optionsv(mysql, option, arg);
}

int STDCALL
mysql_options4(MYSQL *mysql,enum mysql_option option, const void *arg1, const void *arg2)
{
  return mysql_optionsv(mysql, option, arg1, arg2);
}
/****************************************************************************
** Functions to get information from the MySQL structure
** These are functions to make shared libraries more usable.
****************************************************************************/

/* MYSQL_RES */
my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res)
{
  return res->row_count;
}

unsigned int STDCALL mysql_num_fields(MYSQL_RES *res)
{
  return res->field_count;
}

/* deprecated */
my_bool STDCALL mysql_eof(MYSQL_RES *res)
{
  return res->eof;
}

MYSQL_FIELD * STDCALL mysql_fetch_field_direct(MYSQL_RES *res,uint fieldnr)
{
  return &(res)->fields[fieldnr];
}

MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res)
{
  return (res)->fields;
}

MYSQL_FIELD * STDCALL mysql_fetch_params(MYSQL_RES *res)
{
  return (res)->param_fields;
}

MYSQL_ROWS * STDCALL mysql_row_tell(MYSQL_RES *res)
{
  return res->data_cursor;
}

uint STDCALL mysql_field_tell(MYSQL_RES *res)
{
  return (res)->current_field;
}

/* MYSQL */

unsigned int STDCALL mysql_field_count(MYSQL *mysql)
{
  return mysql->field_count;
}

my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql)
{
  return (mysql)->affected_rows;
}

my_bool STDCALL mysql_autocommit(MYSQL *mysql, my_bool mode)
{
  return((my_bool) mysql_real_query(mysql, (mode) ? "SET autocommit=1" :
                                         "SET autocommit=0", 16));
}

my_bool STDCALL mysql_commit(MYSQL *mysql)
{
  return((my_bool)mysql_real_query(mysql, "COMMIT", (unsigned long)strlen("COMMIT")));
}

my_bool STDCALL mysql_rollback(MYSQL *mysql)
{
  return((my_bool)mysql_real_query(mysql, "ROLLBACK", (unsigned long)strlen("ROLLBACK")));
}

my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql)
{
  return (mysql)->insert_id;
}

uint STDCALL mysql_errno(MYSQL *mysql)
{
  return mysql ? mysql->net.last_errno : 0;
}

const char * STDCALL mysql_error(MYSQL *mysql)
{
  return mysql ? (mysql)->net.last_error : (char *)"";
}

const char *STDCALL mysql_info(MYSQL *mysql)
{
  return (mysql)->info;
}

my_bool STDCALL mysql_more_results(MYSQL *mysql)
{
  return(test(mysql->server_status & SERVER_MORE_RESULTS_EXIST));
}

int STDCALL mysql_next_result(MYSQL *mysql)
{

  /* make sure communication is not blocking */
  if (mysql->status != MYSQL_STATUS_READY)
  {
    //SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0);
    SET_CLIENT_ERROR(mysql, CR_STATES_ERROR_NEXT_RESULT, SQLSTATE_UNKNOWN, 0);
    return(1);
  }

  /* clear error, and mysql status variables */
  CLEAR_CLIENT_ERROR(mysql);
  mysql->affected_rows = (ulonglong) ~0;

  if (mysql->server_status & SERVER_MORE_RESULTS_EXIST)
  {
     return(mysql->methods->db_read_query_result(mysql));
  }

  return(-1);
}

ulong STDCALL mysql_thread_id(MYSQL *mysql)
{
  return (mysql)->thread_id;
}

const char * STDCALL mysql_character_set_name(MYSQL *mysql)
{
  return mysql->charset->csname;
}


uint STDCALL mysql_thread_safe(void)
{
#ifdef THREAD
  return 1;
#else
  return 0;
#endif
}

/****************************************************************************
** Some support functions
****************************************************************************/

/*
** Add escape characters to a string (blob?) to make it suitable for a insert
** to should at least have place for length*2+1 chars
** Returns the length of the to string
*/

ulong STDCALL
mysql_escape_string(char *to,const char *from, ulong length)
{
    return (ulong)mysql_cset_escape_slashes(ma_default_charset_info, to, from, length);
}

ulong STDCALL
mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
			 ulong length)
{
  if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
    return (ulong)mysql_cset_escape_quotes(mysql->charset, to, from, length);
  else
    return (ulong)mysql_cset_escape_slashes(mysql->charset, to, from, length);
}

static void mariadb_get_charset_info(MYSQL *mysql, MY_CHARSET_INFO *cs)
{
  if (!cs)
    return;

  cs->number= mysql->charset->nr;
  cs->csname=  mysql->charset->csname;
  cs->name= mysql->charset->name;
  cs->state= 0;
  cs->comment= NULL;
  cs->dir= NULL;
  cs->mbminlen= mysql->charset->char_minlen;
  cs->mbmaxlen= mysql->charset->char_maxlen;

  return;
}

void STDCALL mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *cs)
{
  mariadb_get_charset_info(mysql, cs);
}

int STDCALL mysql_set_character_set(MYSQL *mysql, const char *csname)
{
  const MARIADB_CHARSET_INFO *cs;

  if (!csname)
    goto error;

  if ((cs= mysql_find_charset_name(csname)))
  {
    char buff[64];

    snprintf(buff, 63, "SET NAMES %s", cs->csname);
    if (!mysql_real_query(mysql, buff, (unsigned long)strlen(buff)))
    {
      mysql->charset= cs;
      return(0);
    }
    return(mysql->net.last_errno);
  }

error:
  my_set_error(mysql, CR_CANT_READ_CHARSET, SQLSTATE_UNKNOWN,
               0, csname, "compiled_in");
  return(mysql->net.last_errno);
}

unsigned int STDCALL mysql_warning_count(MYSQL *mysql)
{
  return mysql->warning_count;
}

const char * STDCALL mysql_sqlstate(MYSQL *mysql)
{
  return mysql->net.sqlstate;
}

#ifndef _WIN32
#include <signal.h>
static void ignore_sigpipe()
{
  signal(SIGPIPE, SIG_IGN);
}
#else
#define ignore_sigpipe()
#endif

#ifdef _WIN32
static int mysql_once_init()
#else
static void mysql_once_init()
#endif
{
  ma_init();					/* Will init threads */
  init_client_errs();
  get_default_configuration_dirs();
  set_default_charset_by_name(MARIADB_DEFAULT_CHARSET, 0);
  if (mysql_client_plugin_init())
  {
#ifdef _WIN32
    return 1;
#else
    return;
#endif
  }
  if (!mysql_port)
  {
#if !__has_feature(memory_sanitizer) /* work around MSAN deficiency */
    struct servent *serv_ptr;
#endif
    char *env;

    mysql_port = MARIADB_PORT;
#if !__has_feature(memory_sanitizer) /* work around MSAN deficiency */
    if ((serv_ptr = getservbyname("mysql", "tcp")))
      mysql_port = (uint)ntohs((ushort)serv_ptr->s_port);
#endif
    if ((env = getenv("MYSQL_TCP_PORT")))
      mysql_port =(uint)atoi(env);
  }
  if (!mysql_unix_port)
  {
    char *env;
#ifdef _WIN32
    mysql_unix_port = (char*)MARIADB_NAMEDPIPE;
#else
    mysql_unix_port = (char*)MARIADB_UNIX_ADDR;
#endif
    if ((env = getenv("MYSQL_UNIX_PORT")) ||
      (env = getenv("MARIADB_UNIX_PORT")))
      mysql_unix_port = env;
  }
  if (!mysql_ps_subsystem_initialized)
    mysql_init_ps_subsystem();
#ifdef HAVE_TLS
  ma_tls_start(0, 0);
#endif
  ignore_sigpipe();
  mysql_client_init = 1;
#ifdef _WIN32
  return 0;
#endif
}

#ifdef _WIN32
static INIT_ONCE init_once= INIT_ONCE_STATIC_INIT;
BOOL CALLBACK win_init_once(
  PINIT_ONCE InitOnce,
  PVOID Parameter,
  PVOID *lpContext)
{
  return !mysql_once_init();
  return TRUE;
}
#else
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
#endif

int STDCALL mysql_server_init(int argc __attribute__((unused)),
  char **argv __attribute__((unused)),
  char **groups __attribute__((unused)))
{
#ifdef _WIN32
  BOOL ret = InitOnceExecuteOnce(&init_once, win_init_once, NULL, NULL);
  return ret? 0: 1;
#else
  return pthread_once(&init_once, mysql_once_init);
#endif
}

void STDCALL mysql_server_end(void)
{
  if (!mysql_client_init)
    return;

  release_configuration_dirs();
  mysql_client_plugin_deinit();

  list_free(pvio_callback, 0);
  if (ma_init_done)
    ma_end(0);
#ifdef HAVE_TLS
  ma_pvio_tls_end();
#endif
  mysql_client_init= 0;
  ma_init_done= 0;
#ifdef WIN32
  init_once = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
#else
  init_once = (pthread_once_t)PTHREAD_ONCE_INIT;
#endif
}

my_bool STDCALL mysql_thread_init(void)
{
  return 0;
}

void STDCALL mysql_thread_end(void)
{
}

int STDCALL mysql_set_server_option(MYSQL *mysql,
                                    enum enum_mysql_set_option option)
{
  char buffer[2];
  int2store(buffer, (uint)option);
  return(ma_simple_command(mysql, COM_SET_OPTION, buffer, sizeof(buffer), 0, 0));
}

ulong STDCALL mysql_get_client_version(void)
{
  return MARIADB_VERSION_ID;
}

ulong STDCALL mysql_hex_string(char *to, const char *from, unsigned long len)
{
  char *start= to;
  char hexdigits[]= "0123456789ABCDEF";

  while (len--)
  {
    *to++= hexdigits[((unsigned char)*from) >> 4];
    *to++= hexdigits[((unsigned char)*from) & 0x0F];
    from++;
  }
  *to= 0;
  return (ulong)(to - start);
}

my_bool STDCALL mariadb_connection(MYSQL *mysql)
{
  return (strstr(mysql->server_version, "MariaDB") ||
          strstr(mysql->server_version, "-maria-"));
}

const char * STDCALL
mysql_get_server_name(MYSQL *mysql)
{
  if (mysql->options.extension &&
      mysql->options.extension->db_driver != NULL)
    return mysql->options.extension->db_driver->name;
  return mariadb_connection(mysql) ? "MariaDB" : "MySQL";
}

static my_socket mariadb_get_socket(MYSQL *mysql)
{
  my_socket sock= INVALID_SOCKET;
  if (mysql->net.pvio)
  {
    ma_pvio_get_handle(mysql->net.pvio, &sock);

  }
  /* if an asynchronous connect is in progress, we need to obtain
     pvio handle from async_context until the connection was
     successfully established.
  */
  else if (mysql->options.extension && mysql->options.extension->async_context &&
           mysql->options.extension->async_context->pvio)
  {
    ma_pvio_get_handle(mysql->options.extension->async_context->pvio, &sock);
  }
  return sock;
}

my_socket STDCALL
mysql_get_socket(MYSQL *mysql)
{
  return mariadb_get_socket(mysql);
}

MARIADB_CHARSET_INFO * STDCALL mariadb_get_charset_by_name(const char *csname)
{
  return (MARIADB_CHARSET_INFO *)mysql_find_charset_name(csname);
}

MARIADB_CHARSET_INFO * STDCALL mariadb_get_charset_by_nr(unsigned int csnr)
{
  return (MARIADB_CHARSET_INFO *)mysql_find_charset_nr(csnr);
}

my_bool mariadb_get_infov(MYSQL *mysql, enum mariadb_value value, void *arg, ...)
{
  va_list ap;

  va_start(ap, arg);

  switch(value) {
  case MARIADB_MAX_ALLOWED_PACKET:
    *((size_t *)arg)= (size_t)max_allowed_packet;
    break;
  case MARIADB_NET_BUFFER_LENGTH:
    *((size_t *)arg)= (size_t)net_buffer_length;
    break;
  case MARIADB_CONNECTION_ERROR_ID:
    if (!mysql)
      goto error;
    *((unsigned int *)arg)= mysql->net.last_errno;
    break;
  case MARIADB_CONNECTION_ERROR:
    if (!mysql)
      goto error;
    *((char **)arg)= mysql->net.last_error;
    break;
  case MARIADB_CONNECTION_SQLSTATE:
    if (!mysql)
      goto error;
    *((char **)arg)= mysql->net.sqlstate;
    break;
  case MARIADB_CONNECTION_TLS_VERSION:
    #ifdef HAVE_TLS
    if (mysql && mysql->net.pvio && mysql->net.pvio->ctls)
      *((char **)arg)= (char *)ma_pvio_tls_get_protocol_version(mysql->net.pvio->ctls);
    else
    #endif
      goto error;
    break;
  case MARIADB_CONNECTION_TLS_VERSION_ID:
    #ifdef HAVE_TLS
    if (mysql && mysql->net.pvio && mysql->net.pvio->ctls)
      *((unsigned int *)arg)= ma_pvio_tls_get_protocol_version_id(mysql->net.pvio->ctls);
    else
    #endif
      goto error;
    break;
  case MARIADB_TLS_LIBRARY:
#ifdef HAVE_TLS
    *((const char **)arg)= tls_library_version;
#else
    *((const char **)arg)= "Off";
#endif
    break;
  case MARIADB_CLIENT_VERSION:
    *((const char **)arg)= MARIADB_CLIENT_VERSION_STR;
    break;
  case MARIADB_CLIENT_VERSION_ID:
    *((size_t *)arg)= MARIADB_VERSION_ID;
    break;
  case MARIADB_CONNECTION_SERVER_VERSION:
    if (mysql)
      *((char **)arg)= mysql->server_version;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SERVER_TYPE:
    if (mysql)
      *((const char **)arg)= mariadb_connection(mysql) ? "MariaDB" : "MySQL";
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SERVER_VERSION_ID:
    if (mysql)
      *((size_t *)arg)= mariadb_server_version_id(mysql);
    else
      goto error;
    break;
  case MARIADB_CONNECTION_PROTOCOL_VERSION_ID:
    if (mysql)
      *((unsigned int *)arg)= mysql->protocol_version;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_MARIADB_CHARSET_INFO:
    if (mysql)
      mariadb_get_charset_info(mysql, (MY_CHARSET_INFO *)arg);
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SOCKET:
    if (mysql)
      *((my_socket *)arg)= mariadb_get_socket(mysql);
    else
      goto error;
    break;
  case MARIADB_CONNECTION_TYPE:
    if (mysql  && mysql->net.pvio)
      *((int *)arg)= (int)mysql->net.pvio->type;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_ASYNC_TIMEOUT_MS:
    if (mysql && mysql->options.extension && mysql->options.extension->async_context)
      *((unsigned int *)arg)= mysql->options.extension->async_context->timeout_value;
    break;
  case MARIADB_CONNECTION_ASYNC_TIMEOUT:
    if (mysql && mysql->options.extension && mysql->options.extension->async_context)
    {
      unsigned int timeout= mysql->options.extension->async_context->timeout_value;
      if (timeout > UINT_MAX - 999)
        *((unsigned int *)arg)= (timeout - 1)/1000 + 1;
      else
        *((unsigned int *)arg)= (timeout+999)/1000;
    }
    break;
  case MARIADB_CHARSET_NAME:
    {
      char *name;
      name= va_arg(ap, char *);
      if (name)
        *((MARIADB_CHARSET_INFO **)arg)= (MARIADB_CHARSET_INFO *)mysql_find_charset_name(name);
      else
        goto error;
    }
    break;
  case MARIADB_CHARSET_ID:
    {
      unsigned int nr;
      nr= va_arg(ap, unsigned int);
      *((MARIADB_CHARSET_INFO **)arg)= (MARIADB_CHARSET_INFO *)mysql_find_charset_nr(nr);
    }
    break;
  case MARIADB_CONNECTION_SSL_CIPHER:
    #ifdef HAVE_TLS
    if (mysql && mysql->net.pvio && mysql->net.pvio->ctls)
      *((char **)arg)= (char *)ma_pvio_tls_cipher(mysql->net.pvio->ctls);
    else
    #endif
      goto error;
    break;
  case MARIADB_CLIENT_ERRORS:
    *((char ***)arg)= (char **)client_errors;
    break;
  case MARIADB_CONNECTION_INFO:
    if (mysql)
      *((char **)arg)= (char *)mysql->info;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_PVIO_TYPE:
    if (mysql && mysql->net.pvio)
      *((unsigned int *)arg)= (unsigned int)mysql->net.pvio->type;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SCHEMA:
    if (mysql)
      *((char **)arg)= mysql->db;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_USER:
    if (mysql)
      *((char **)arg)= mysql->user;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_PORT:
    if (mysql)
      *((unsigned int *)arg)= mysql->port;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_UNIX_SOCKET:
    if (mysql)
      *((char **)arg)= mysql->unix_socket;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_HOST:
    if (mysql)
      *((char **)arg)= mysql->host;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SERVER_STATUS:
    if (mysql)
      *((unsigned int *)arg)= mysql->server_status;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_SERVER_CAPABILITIES:
    if (mysql)
      *((unsigned long *)arg)= mysql->server_capabilities;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES:
    if (mysql)
      *((unsigned long *)arg)= mysql->extension->mariadb_server_capabilities;
    else
      goto error;
    break;
  case MARIADB_CONNECTION_CLIENT_CAPABILITIES:
    if (mysql)
      *((unsigned long *)arg)= mysql->client_flag;
    else
      goto error;
    break;
  default:
    va_end(ap);
    return(-1);
  }
  va_end(ap);
  return(0);
error:
  va_end(ap);
  return(-1);
}

my_bool STDCALL mariadb_get_info(MYSQL *mysql, enum mariadb_value value, void *arg)
{
  return mariadb_get_infov(mysql, value, arg);
}

/*
  Immediately aborts connection, making all subsequent read/write operations fail.
  Does not invalidate memory used for mysql structure, nor closes any communication
  channels - mysql_close is still needed.
  Useful to break long query, in situation  sending KILL is not possible.
*/
int STDCALL mariadb_cancel(MYSQL *mysql)
{
  if (!mysql || !mysql->net.pvio || !mysql->net.pvio->methods || !mysql->net.pvio->methods->shutdown)
  {
    return 1;
  }
  else
  {
    MARIADB_PVIO *pvio = mysql->net.pvio;
    return pvio->methods->shutdown(pvio);
  }
}

/* compatibility functions for MariaDB */
void STDCALL
mysql_debug(const char *debug __attribute__((unused)))
{
  return;
}

/********************************************************************
  mysql_net_ functions - low-level API to MySQL protocol
*********************************************************************/
ulong STDCALL mysql_net_read_packet(MYSQL *mysql)
{
  return ma_net_safe_read(mysql);
}

ulong STDCALL mysql_net_field_length(uchar **packet)
{
  return net_field_length(packet);
}

my_bool STDCALL mysql_embedded(void)
{
#ifdef EMBEDDED_LIBRARY
  return 1;
#else
  return 0;
#endif
}

MYSQL_PARAMETERS *STDCALL
mysql_get_parameters(void)
{
  return &mariadb_internal_parameters;
}

int STDCALL mysql_reset_connection(MYSQL *mysql)
{
  int rc;

  /* check if connection handler is active */
  if (IS_CONNHDLR_ACTIVE(mysql))
  {
    if (mysql->extension->conn_hdlr->plugin && mysql->extension->conn_hdlr->plugin->reset)
      return(mysql->extension->conn_hdlr->plugin->reset(mysql));
  }

  /* skip result sets */
  if (mysql->status == MYSQL_STATUS_USE_RESULT ||
      mysql->status == MYSQL_STATUS_GET_RESULT ||
      mysql->status & SERVER_MORE_RESULTS_EXIST)
  {
    mthd_my_skip_result(mysql);
    mysql->status= MYSQL_STATUS_READY;
  }

  rc= ma_simple_command(mysql, COM_RESET_CONNECTION, 0, 0, 0, 0);
  if (rc && mysql->options.reconnect)
  {
    /* There is no big sense in resetting but we need reconnect */
    rc= ma_simple_command(mysql, COM_RESET_CONNECTION,0,0,0,0);
  }
  if (rc)
    return 1;

  /* reset the connection in all active statements */
  ma_invalidate_stmts(mysql, "mysql_reset_connection()");
  free_old_query(mysql);
  mysql->status= MYSQL_STATUS_READY;
  mysql->affected_rows= ~(my_ulonglong)0;
  mysql->insert_id= 0;
  return 0;
}

#undef STDCALL
/* API functions for usage in dynamic plugins */
struct st_mariadb_api MARIADB_API=
{
  mysql_num_rows,
  mysql_num_fields,
  mysql_eof,
  mysql_fetch_field_direct,
  mysql_fetch_fields,
  mysql_row_tell,
  mysql_field_tell,
  mysql_field_count,
  mysql_more_results,
  mysql_next_result,
  mysql_affected_rows,
  mysql_autocommit,
  mysql_commit,
  mysql_rollback,
  mysql_insert_id,
  mysql_errno,
  mysql_error,
  mysql_info,
  mysql_thread_id,
  mysql_character_set_name,
  mysql_get_character_set_info,
  mysql_set_character_set,
  mariadb_get_infov,
  mariadb_get_info,
  mysql_init,
  mysql_ssl_set,
  mysql_get_ssl_cipher,
  mysql_change_user,
  mysql_real_connect,
  mysql_close,
  mysql_select_db,
  mysql_query,
  mysql_send_query,
  mysql_read_query_result,
  mysql_real_query,
  mysql_shutdown,
  mysql_dump_debug_info,
  mysql_refresh,
  mysql_kill,
  mysql_ping,
  mysql_stat,
  mysql_get_server_info,
  mysql_get_server_version,
  mysql_get_host_info,
  mysql_get_proto_info,
  mysql_list_dbs,
  mysql_list_tables,
  mysql_list_fields,
  mysql_list_processes,
  mysql_store_result,
  mysql_use_result,
  mysql_options,
  mysql_free_result,
  mysql_data_seek,
  mysql_row_seek,
  mysql_field_seek,
  mysql_fetch_row,
  mysql_fetch_lengths,
  mysql_fetch_field,
  mysql_escape_string,
  mysql_real_escape_string,
  mysql_thread_safe,
  mysql_warning_count,
  mysql_sqlstate,
  mysql_server_init,
  mysql_server_end,
  mysql_thread_end,
  mysql_thread_init,
  mysql_set_server_option,
  mysql_get_client_info,
  mysql_get_client_version,
  mariadb_connection,
  mysql_get_server_name,
  mariadb_get_charset_by_name,
  mariadb_get_charset_by_nr,
  mariadb_convert_string,
  mysql_optionsv,
  mysql_get_optionv,
  mysql_get_option,
  mysql_hex_string,
  mysql_get_socket,
  mysql_get_timeout_value,
  mysql_get_timeout_value_ms,
  mariadb_reconnect,
  mysql_stmt_init,
  mysql_stmt_prepare,
  mysql_stmt_execute,
  mysql_stmt_fetch,
  mysql_stmt_fetch_column,
  mysql_stmt_store_result,
  mysql_stmt_param_count,
  mysql_stmt_attr_set,
  mysql_stmt_attr_get,
  mysql_stmt_bind_param,
  mysql_stmt_bind_result,
  mysql_stmt_close,
  mysql_stmt_reset,
  mysql_stmt_free_result,
  mysql_stmt_send_long_data,
  mysql_stmt_result_metadata,
  mysql_stmt_param_metadata,
  mysql_stmt_errno,
  mysql_stmt_error,
  mysql_stmt_sqlstate,
  mysql_stmt_row_seek,
  mysql_stmt_row_tell,
  mysql_stmt_data_seek,
  mysql_stmt_num_rows,
  mysql_stmt_affected_rows,
  mysql_stmt_insert_id,
  mysql_stmt_field_count,
  mysql_stmt_next_result,
  mysql_stmt_more_results,
  mariadb_stmt_execute_direct,
  mysql_reset_connection
};

/*
 * Default methods for a connection. These methods are
 * stored in mysql->methods and can be overwritten by
 * a plugin, e.g. for using another database
 */
struct st_mariadb_methods MARIADB_DEFAULT_METHODS = {
  /* open a connection */
  mthd_my_real_connect,
  /* close connection */
  mysql_close_slow_part,
  /* send command to server */
  mthd_my_send_cmd,
  /* skip result set */
  mthd_my_skip_result,
  /* read response packet */
  mthd_my_read_query_result,
  /* read all rows from a result set */
  mthd_my_read_rows,
  /* read one/next row */
  mthd_my_read_one_row,
  /* check if datatype is supported */
  mthd_supported_buffer_type,
  /* read response packet from prepare */
  mthd_stmt_read_prepare_response,
  /* read response from stmt execute */
  mthd_my_read_query_result,
  /* get result set metadata for a prepared statement */
  mthd_stmt_get_result_metadata,
  /* get param metadata for a prepared statement */
  mthd_stmt_get_param_metadata,
  /* read all rows (buffered) */
  mthd_stmt_read_all_rows,
  /* fetch one row (unbuffered) */
  mthd_stmt_fetch_row,
  /* store values in bind buffer */
  mthd_stmt_fetch_to_bind,
  /* skip unbuffered stmt result */
  mthd_stmt_flush_unbuffered,
  /* set error */
  my_set_error,
  /* invalidate statements */
  ma_invalidate_stmts,
  /* API functions */
  &MARIADB_API
};

void ob_get_libobclient_version(int *major, int *minor, int *patch, char* version, int len)
{
  if (major)
    *major = LIBOBCLIENT_VERSION_MAJOR;
  if (minor)
    *minor = LIBOBCLIENT_VERSION_MINOR;
  if (patch)
    *patch = LIBOBCLIENT_VERSION_PATCH;
  if (version)
    memcpy(version, LIBOBCLIENT_VERSION, min(len, strlen(LIBOBCLIENT_VERSION)));
}

void ob_set_socket5_proxy(MYSQL *mysql, char socket5_authtype, char *socket5_host, int socket5_port, char *socket5_user, char *socket5_pwd)
{
  mysql->is_socket5 = 1;
  mysql->socket5_authtype = socket5_authtype;
  mysql->socket5_port = socket5_port;
  if (mysql->socket5_host) {
    free(mysql->socket5_host);
    mysql->socket5_host = NULL;
  }
  if (mysql->socket5_user) {
    free(mysql->socket5_user);
    mysql->socket5_user = NULL;
  }
  if (mysql->socket5_pwd) {
    free(mysql->socket5_pwd);
    mysql->socket5_pwd = NULL;
  }
  if (socket5_host)
    mysql->socket5_host = strdup(socket5_host);
  if (socket5_user)
    mysql->socket5_user = strdup(socket5_user);
  if (socket5_pwd)
    mysql->socket5_pwd = strdup(socket5_pwd);
}
