/*
   Copyright (c) 2000, 2018, Oracle and/or its affiliates.
   Copyright (c) 2009, 2019, MariaDB Corporation.
   Copyright (c) 2021 OceanBase Technology Co.,Ltd.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
#include "ma_common.h"
#include "errmsg.h"
#include "ob_complex.h"
#include "ma_string.h"

static OB_HASH *global_hash = NULL;
static ob_rw_lock_t global_rwlock = OB_RW_INITIALIZER;
static void global_hash_free(void *record);
// #define UNUSED(x) ((void)x)
#define COMPLEX_TYPE_MAX_SQL_LENGTH 32 * 1024
#define COMPLEX_TYPE_MYSQL(M)   (MYSQL_EXTENSION_PTR(M)->mysql)
#define COMPLEX_TYPE_HASH(M)   (MYSQL_EXTENSION_PTR(M)->complex_type_hash)
#define COMPLEX_TYPE_USER_TYPES_SQL \
"SELECT "\
"  0 DEPTH, "\
"  NULL PARENT_OWNER, "\
"  NULL PARENT_TYPE, "\
"  TYPE_NAME CHILD_TYPE, "\
"  0 ATTR_NO, "\
"  SYS_CONTEXT('USERENV', 'CURRENT_USER') CHILD_TYPE_OWNER, "\
"  A.TYPECODE ATTR_TYPE_CODE, "\
"  NULL LENGTH, "\
"  NULL NUMBER_PRECISION, "\
"  NULL SCALE, "\
"  NULL CHARACTER_SET_NAME "\
"FROM "\
"  USER_TYPES A WHERE TYPE_NAME = '%s' "\
"UNION "\
"( "\
"  WITH  "\
"  CTE_RESULT(PARENT_OWNER, PARENT_TYPE, CHILD_TYPE, ATTR_NO, CHILD_TYPE_OWNER, ATTR_TYPE_CODE, LENGTH, NUMBER_PRECISION, SCALE, CHARACTER_SET_NAME)  "\
"  AS ( "\
"    SELECT "\
"      SYS_CONTEXT('USERENV','CURRENT_USER') PARENT_OWNER, "\
"      B.TYPE_NAME PARENT_TYPE, "\
"      B.ELEM_TYPE_NAME CHILD_TYPE, "\
"      0 ATTR_NO, "\
"      B.ELEM_TYPE_OWNER CHILD_TYPE_OWNER, "\
"      NVL(A.TYPECODE, B.ELEM_TYPE_NAME) AS ATTR_TYPE_CODE, "\
"      B.LENGTH LENGTH, "\
"      B.NUMBER_PRECISION NUMBER_PRECISION, "\
"      B.SCALE SCALE, "\
"      B.CHARACTER_SET_NAME CHARACTER_SET_NAME "\
"    FROM "\
"      USER_COLL_TYPES B LEFT JOIN USER_TYPES A ON A.TYPE_NAME = B.ELEM_TYPE_NAME "\
"    UNION "\
"    SELECT "\
"      SYS_CONTEXT('USERENV','CURRENT_USER') PARENT_OWNER, "\
"      B.TYPE_NAME PARENT_TYPE, "\
"      B.ATTR_TYPE_NAME CHILD_TYPE, "\
"      B.ATTR_NO ATTR_NO, "\
"      B.ATTR_TYPE_OWNER CHILD_TYPE_OWNER, "\
"      NVL(A.TYPECODE, B.ATTR_TYPE_NAME) AS ATTR_TYPE_CODE, "\
"      B.LENGTH LENGTH, "\
"      B.NUMBER_PRECISION NUMBER_PRECISION, "\
"      B.SCALE SCALE, "\
"      B.CHARACTER_SET_NAME CHARACTER_SET_NAME "\
"    FROM USER_TYPE_ATTRS B LEFT JOIN USER_TYPES A ON B.ATTR_TYPE_NAME = A.TYPE_NAME ORDER BY ATTR_NO "\
"  ) , "\
"  CTE(DEPTH, PARENT_OWNER, PARENT_TYPE, CHILD_TYPE, ATTR_NO, CHILD_TYPE_OWNER, ATTR_TYPE_CODE, LENGTH, NUMBER_PRECISION, SCALE, CHARACTER_SET_NAME) "\
"  AS ( "\
"    SELECT "\
"      1 DEPTH, "\
"      PARENT_OWNER, "\
"      PARENT_TYPE, "\
"      CHILD_TYPE, "\
"      ATTR_NO, "\
"      CHILD_TYPE_OWNER, "\
"      ATTR_TYPE_CODE, "\
"      LENGTH, "\
"      NUMBER_PRECISION, "\
"      SCALE, CHARACTER_SET_NAME "\
"    FROM CTE_RESULT WHERE PARENT_TYPE = '%s' "\
"    UNION ALL "\
"    SELECT "\
"      DEPTH + 1 DEPTH, "\
"      CTE_RESULT.PARENT_OWNER, "\
"      CTE_RESULT.PARENT_TYPE, "\
"      CTE_RESULT.CHILD_TYPE, "\
"      CTE_RESULT.ATTR_NO, "\
"      CTE_RESULT.CHILD_TYPE_OWNER, "\
"      CTE_RESULT.ATTR_TYPE_CODE, "\
"      CTE_RESULT.LENGTH, "\
"      CTE_RESULT.NUMBER_PRECISION, "\
"      CTE_RESULT.SCALE, "\
"      CTE_RESULT.CHARACTER_SET_NAME "\
"    FROM CTE_RESULT INNER JOIN CTE ON CTE_RESULT.PARENT_TYPE = CTE.CHILD_TYPE "\
"  ) "\
"  SELECT * FROM CTE "\
"); "\

#define COMPLEX_TYPE_ALL_TYPES_SQL \
"SELECT "\
"    0 DEPTH, "\
"    NULL PRAENT_OWNER, "\
"    NULL PARENT_TYPE, "\
"    to_char(TYPE_NAME) CHILD_TYPE, "\
"    0 ATTR_NO, "\
"    OWNER CHILD_TYPE_OWNER, "\
"    A.TYPECODE ATTR_TYPE_CODE, "\
"    NULL LENGTH, "\
"    NULL NUMBER_PRECISION, "\
"    NULL SCALE, "\
"    NULL CHARACTER_SET_NAME "\
"  FROM "\
"    ALL_TYPES A WHERE TYPE_NAME = '%s' AND OWNER = '%s' "\
"  UNION "\
"  ( "\
"  WITH "\
"  CTE_RESULT(PARENT_OWNER, PARENT_TYPE, CHILD_TYPE, ATTR_NO, CHILD_TYPE_OWNER, ATTR_TYPE_CODE, LENGTH, NUMBER_PRECISION, SCALE, CHARACTER_SET_NAME) "\
"  AS ( "\
"      SELECT "\
"        B.OWNER PARENT_OWNER, "\
"        B.TYPE_NAME PARENT_TYPE, "\
"        B.ELEM_TYPE_NAME CHILD_TYPE, "\
"        0 ATTR_NO, "\
"        B.ELEM_TYPE_OWNER CHILD_TYPE_OWNER, "\
"        NVL(A.TYPECODE, B.ELEM_TYPE_NAME) AS ATTR_TYPE_CODE, "\
"        B.LENGTH LENGTH, "\
"        B.NUMBER_PRECISION NUMBER_PRECISION, "\
"        B.SCALE SCALE, "\
"        B.CHARACTER_SET_NAME CHARACTER_SET_NAME "\
"      FROM "\
"        ALL_COLL_TYPES B LEFT JOIN ALL_TYPES A ON A.TYPE_NAME = B.ELEM_TYPE_NAME AND A.OWNER = B.ELEM_TYPE_OWNER "\
"      UNION "\
"      SELECT "\
"        B.OWNER PARENT_OWNER, "\
"        B.TYPE_NAME PARENT_TYPE, "\
"        B.ATTR_TYPE_NAME CHILD_TYPE, "\
"        B.ATTR_NO ATTR_NO, "\
"        B.ATTR_TYPE_OWNER CHILD_TYPE_OWNER, "\
"        NVL(A.TYPECODE, B.ATTR_TYPE_NAME) AS ATTR_TYPE_CODE, "\
"        B.LENGTH LENGTH, "\
"        B.NUMBER_PRECISION NUMBER_PRECISION, "\
"        B.SCALE SCALE, "\
"        B.CHARACTER_SET_NAME CHARACTER_SET_NAME "\
"      FROM ALL_TYPE_ATTRS B LEFT JOIN ALL_TYPES A ON A.TYPE_NAME = B.ATTR_TYPE_NAME AND A.OWNER = B.ATTR_TYPE_OWNER ORDER BY ATTR_NO "\
"  ) , "\
"  CTE(DEPTH, PARENT_OWNER, PARENT_TYPE, CHILD_TYPE, ATTR_NO, CHILD_TYPE_OWNER, ATTR_TYPE_CODE, LENGTH, NUMBER_PRECISION, SCALE, CHARACTER_SET_NAME) "\
"  AS ( "\
"    SELECT "\
"      1 DEPTH, "\
"      PARENT_OWNER, "\
"      PARENT_TYPE, "\
"      CHILD_TYPE, "\
"      ATTR_NO, "\
"      CHILD_TYPE_OWNER, "\
"      ATTR_TYPE_CODE, "\
"      LENGTH, "\
"      NUMBER_PRECISION, "\
"      SCALE, CHARACTER_SET_NAME "\
"    FROM CTE_RESULT WHERE PARENT_TYPE = '%s' AND PARENT_OWNER = '%s' "\
"    UNION ALL "\
"    SELECT "\
"      DEPTH + 1 DEPTH, "\
"      CTE_RESULT.PARENT_OWNER, "\
"      CTE_RESULT.PARENT_TYPE, "\
"      CTE_RESULT.CHILD_TYPE, "\
"      CTE_RESULT.ATTR_NO, "\
"      CTE_RESULT.CHILD_TYPE_OWNER, "\
"      CTE_RESULT.ATTR_TYPE_CODE, "\
"      CTE_RESULT.LENGTH, "\
"      CTE_RESULT.NUMBER_PRECISION, "\
"      CTE_RESULT.SCALE, "\
"      CTE_RESULT.CHARACTER_SET_NAME "\
"    FROM CTE_RESULT INNER JOIN CTE ON CTE_RESULT.PARENT_TYPE = CTE.CHILD_TYPE AND CTE_RESULT.PARENT_OWNER = CTE.CHILD_TYPE_OWNER "\
"  ) "\
"  SELECT * FROM CTE "\
"); "\

#define DEPTH_INDEX 0
#define PARENT_TYPE_INDEX 2
#define CHILD_TYPE_INDEX 3
#define ATTR_NO_INDEX 4
#define CHILD_OWNER_INDEX 5
#define ATTR_TYPE_INDEX 6

static enum_types convert_type(const char *type) {
  if (!strcmp(type, "COLLECTION")) {
    return TYPE_COLLECTION;
  } else if (!strcmp(type, "OBJECT")) {
    return TYPE_OBJECT;
  } else if (!strcmp(type, "NUMBER")) {
    return TYPE_NUMBER;
  } else if (!strcmp(type, "VARCHAR2")) {
    return TYPE_VARCHAR2;
  } else if (!strcmp(type, "CHAR")) {
    return TYPE_CHAR;
  } else if (!strcmp(type, "DATE")) {
    return TYPE_DATE;
  } else if (!strcmp(type, "RAW")) {
    return TYPE_RAW;
  } else if (!strcmp(type, "NVARCHAR2")) {
    return TYPE_NVARCHAR2;
  } else if (!strcmp(type, "FLOAT")) {
    return TYPE_OB_NUMBER_FLOAT;
  } else if (!strncmp(type, "INTERVAL YEAR", 13)) {
    return TYPE_OB_INTERVAL_YM;
  } else if (!strncmp(type, "INTERVAL DAY", 12)) {
    return TYPE_OB_INTERVAL_DS;
  } else if (!strncmp(type, "TIMESTAMP", 9)) {
    if (strstr(type, "WITH TIME ZONE")) {
      return TYPE_OB_TIMESTAMP_WITH_TIME_ZONE;
    } else if (strstr(type, "WITH LOCAL TIME ZONE")) {
      return TYPE_OB_TIMESTAMP_WITH_LOCAL_TIME_ZONE;
    } else {
      return TYPE_OB_TIMESTAMP_NANO;
    }
  } else if (!strcmp(type, "BINARY_FLOAT")) {
    return TYPE_FLOAT;
  } else if (!strcmp(type, "BINARY_DOUBLE")) {
    return TYPE_DOUBLE;
  } else {
    return TYPE_MAX;
  }
}

static int STDCALL
parser_complex(MYSQL_RES *result, OB_HASH *hash)
{
  MYSQL_ROW cur; 

  COMPLEX_TYPE *complex_type;
  unsigned char *parent_name;
  COMPLEX_TYPE *parent;
  while ((cur = mysql_fetch_row(result))) {
    unsigned int type_size;
    enum_types type = convert_type(cur[ATTR_TYPE_INDEX]);

    if (TYPE_OBJECT == type || TYPE_COLLECTION == type) {
      complex_type = (COMPLEX_TYPE *) hash_search(hash, (unsigned char*)cur[CHILD_TYPE_INDEX], strlen(cur[CHILD_TYPE_INDEX]));

      if (!complex_type) {
        type_size = TYPE_COLLECTION == type ? sizeof(COMPLEX_TYPE_COLLECTION) : sizeof(COMPLEX_TYPE_OBJECT);
        complex_type = (COMPLEX_TYPE *) calloc(1, type_size);

        if (!complex_type) {
          return (1);
        }

        complex_type->is_valid = 0;
        complex_type->type = type;
        if (TYPE_OBJECT == complex_type->type) {
          ((COMPLEX_TYPE_OBJECT *)complex_type)->attr_no = 0;
        }
        /* name 被初始化成了 0, 所以不需要拷贝最后的 NULL 结束符 */
        memcpy(complex_type->type_name, cur[CHILD_TYPE_INDEX], strlen(cur[CHILD_TYPE_INDEX]));
        memcpy(complex_type->owner_name, cur[CHILD_OWNER_INDEX], strlen(cur[CHILD_OWNER_INDEX]));

        if (hash_insert(hash, (unsigned char *)complex_type)) {
          return (1);
        }
      } else {
        //ReInit node's info in the query
        complex_type->is_valid = 0;
        if (TYPE_OBJECT == complex_type->type) {
          ((COMPLEX_TYPE_OBJECT *)complex_type)->attr_no = 0;
        }
      }
    }

    if (atoi(cur[DEPTH_INDEX]) != 0) {
      parent_name = (unsigned char *)cur[PARENT_TYPE_INDEX];
      parent = (COMPLEX_TYPE *) hash_search(hash, parent_name, strlen((char *)parent_name));

      if (parent == NULL) {
        return (1);
      }

      if (TYPE_OBJECT == parent->type &&
             ((COMPLEX_TYPE_OBJECT *)parent)->attr_no < strtoul(cur[ATTR_NO_INDEX], NULL, 10)) {
        ((COMPLEX_TYPE_OBJECT *)parent)->attr_no = strtoul(cur[ATTR_NO_INDEX], NULL, 10);
      }
    }
  }

  result->data_cursor = result->data->data;

  while ((cur = mysql_fetch_row(result))) {
    enum_types child_type;
    COMPLEX_TYPE_OBJECT *parent_object;
    COMPLEX_TYPE_COLLECTION *parent_collection;

    if (atoi(cur[DEPTH_INDEX]) != 0) {
      parent_name = (unsigned char *)cur[PARENT_TYPE_INDEX];
      parent = (COMPLEX_TYPE *) hash_search(hash, parent_name, strlen((char *)parent_name));

      if (parent == NULL) {
        return (1);
      }

      child_type = convert_type(cur[ATTR_TYPE_INDEX]);

      if (TYPE_OBJECT == parent->type) {
        parent_object = (COMPLEX_TYPE_OBJECT *)parent;

        if (parent_object->child == NULL) {
          parent_object->child = (CHILD_TYPE *) calloc(1, parent_object->attr_no * sizeof(CHILD_TYPE));

          if (parent_object->child == NULL) {
            return (1);
          }
        }

        parent_object->child[atoi(cur[ATTR_NO_INDEX]) - 1].type = child_type;

        if (TYPE_COLLECTION == child_type || TYPE_OBJECT == child_type) {
          complex_type = (COMPLEX_TYPE *)hash_search(hash,
                  (unsigned char *)cur[CHILD_TYPE_INDEX], strlen(cur[CHILD_TYPE_INDEX]));

          if (complex_type == NULL) {
            return (1);
          }

          parent_object->child[atoi(cur[ATTR_NO_INDEX]) - 1].object = complex_type;
        }

        parent_object->init_attr_no++;

        if (parent_object->init_attr_no == parent_object->attr_no) {
          parent->is_valid = 1;
        }

      } else if (TYPE_COLLECTION == parent->type) {
        parent_collection = (COMPLEX_TYPE_COLLECTION *)parent;

        parent_collection->child.type = child_type;

        if (TYPE_COLLECTION == child_type || TYPE_OBJECT == child_type) {
          complex_type = (COMPLEX_TYPE *)hash_search(hash,
                  (unsigned char *)cur[CHILD_TYPE_INDEX], strlen(cur[CHILD_TYPE_INDEX]));

          if (complex_type == NULL) {
            return (1);
          }

          parent_collection->child.object = complex_type;
        }

        parent->is_valid = 1;
      }
    }
  }
  return (0);
}

static int STDCALL
get_type_from_server(MYSQL *mysql, unsigned char *owner_name, unsigned char *type_name, COMPLEX_HASH *complex_hash)
{
  int retval;
  MYSQL_RES *result;
  char sql[COMPLEX_TYPE_MAX_SQL_LENGTH] = {'\0'};

  if (NULL != owner_name) {
    snprintf(sql, COMPLEX_TYPE_MAX_SQL_LENGTH, COMPLEX_TYPE_ALL_TYPES_SQL, type_name, owner_name, type_name, owner_name);
  } else {
    /* 如果 owner 为空, 先从 user_types 视图中获取 */
    snprintf(sql, COMPLEX_TYPE_MAX_SQL_LENGTH, COMPLEX_TYPE_USER_TYPES_SQL, type_name, type_name);
  }

  if (mysql_real_query(mysql, sql, strlen(sql))) {
    return (1);
  }

  if (!(result = mysql_store_result(mysql))) {
    return (1);
  /* 如果 user_types 视图中没有该 type, 在再从 all_types 视图中获取 */
  } else if (0 == mysql_num_rows(result) && NULL == owner_name) {
    mysql_free_result(result);
    snprintf(sql, COMPLEX_TYPE_MAX_SQL_LENGTH, COMPLEX_TYPE_ALL_TYPES_SQL, type_name, "SYS", type_name, "SYS");

    if (mysql_real_query(mysql, sql, strlen(sql))) {
      return (1);
    }

    if (!(result = mysql_store_result(mysql))) {
      return (1);
    }
  }

  ob_rw_wrlock(&(complex_hash->rwlock));
  retval = parser_complex(result, complex_hash->hash);
  ob_rw_unlock(&(complex_hash->rwlock));

  mysql_free_result(result);

  return (retval);
}

static int STDCALL
complex_mysql_init(MYSQL *mysql, MYSQL **complex_mysql)
{
  int ret = 0;

  *complex_mysql = NULL;

  if (!(*complex_mysql = mysql_init(*complex_mysql))) {
    ret = 1;
  } else if (!mysql_real_connect(*complex_mysql, mysql->host, mysql->user, mysql->passwd,
              mysql->db, mysql->port, mysql->unix_socket, 0))
  {
    ret = 1;
  }

  if (ret != 0 && *complex_mysql != NULL) {
    mysql_close(*complex_mysql);
    *complex_mysql = NULL;
  }

  return (ret);
}

static void complex_hash_free(void *record)
{
  COMPLEX_TYPE *header = (COMPLEX_TYPE *)record;
  if (header->type == TYPE_OBJECT) {
    free(((COMPLEX_TYPE_OBJECT *)header)->child);
  }

  free(header);
}

static unsigned char *complex_get_hash_key(const unsigned char *record, uint *length,
             my_bool not_used)
{
  COMPLEX_TYPE *type = (COMPLEX_TYPE *)record;
  *length= strlen((char *)type->type_name);
  UNUSED(not_used);
  return type->type_name;
}

static int STDCALL
complex_hash_init(MYSQL *mysql, unsigned char *hash_key, COMPLEX_HASH **complex_hash)
{
  int ret = 0;

  *complex_hash = NULL;

  if (!(*complex_hash = calloc(1, sizeof(COMPLEX_HASH)))) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    ret = 1;
  } else if(ob_rw_init(&((*complex_hash)->rwlock))) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    ret = 1;
  } else if (!((*complex_hash)->hash = calloc(1, sizeof(OB_HASH)))) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    ret = 1;
  } else if (hash_init((*complex_hash)->hash, 0, 0, 0, (hash_get_key) complex_get_hash_key,
                          complex_hash_free, HASH_CASE_INSENSITIVE)) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    ret = 1;
  } else if (!((*complex_hash)->hash_key = calloc(1, strlen((char *)hash_key) + 1))) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    ret = 1;
  } else {
    //memset((*complex_hash)->hash_key, 0, strlen((char *)hash_key) + 1);
    memcpy((*complex_hash)->hash_key, hash_key, strlen((char *)hash_key));
    (*complex_hash)->hash_key[strlen((char *)hash_key)] = '\0';
    if (hash_insert(global_hash, (unsigned char *)(*complex_hash))) {
      SET_CLIENT_ERROR(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
      ret = 1;
    }
  }

  if (ret && *complex_hash != NULL) {
    global_hash_free((void *)(*complex_hash));
    *complex_hash = NULL;
  }

  return (ret);
}

static void global_hash_free(void *record)
{
  COMPLEX_HASH *complex_hash = (COMPLEX_HASH *)record;
  if (NULL != complex_hash->hash_key) {
    free(complex_hash->hash_key);
  }

  if (NULL != complex_hash->hash) {
    hash_free(complex_hash->hash);
    free(complex_hash->hash);
  }

  ob_rw_destroy(&(complex_hash->rwlock));
  free(complex_hash);
}

static unsigned char *global_get_hash_key(const unsigned char *record, uint *length,
             my_bool not_used)
{
  COMPLEX_HASH *complex_hash = (COMPLEX_HASH *)record;
  *length= strlen((char *)complex_hash->hash_key);
  UNUSED(not_used);
  return complex_hash->hash_key;
}

static int STDCALL
global_hash_init(MYSQL *mysql)
{
  if (!(global_hash = calloc(1, sizeof(OB_HASH)))) {
    SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
    return 1;
  } else if (hash_init(global_hash, 0, 0, 0, (hash_get_key) global_get_hash_key,
             global_hash_free, HASH_CASE_INSENSITIVE)) {
    hash_free(global_hash);
    free(global_hash);
    SET_CLIENT_ERROR(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
    return 1;
  }

  return 0;
}

static const char digits[]= "0123456789abcdef";

char *ma_safe_utoa(int base, ulonglong val, char *buf)
{
  *buf--= 0;
  do {
    *buf--= digits[val % base];
  } while ((val /= base) != 0);
  return buf + 1;
}
static int STDCALL
get_complex_hash(MYSQL *mysql, unsigned char *hash_key, int buffer_len, COMPLEX_HASH **complex_hash)
{
  int ret = 0;

  *complex_hash = NULL;

  if (NULL == global_hash) {
    ob_rw_wrlock(&global_rwlock);
    if (NULL == global_hash && global_hash_init(mysql)) {
      ret = 1;
    }
    ob_rw_unlock(&global_rwlock);
  }
  if (ret == 0) {
    unsigned int hash_length = 0;
    char port[10] = {'\0'};
    char *port_ptr = NULL;
    port_ptr = ma_safe_utoa(10, mysql->port, &port[sizeof(port)-1]);
    if (strlen(mysql->host) + strlen(port_ptr) + strlen(mysql->user) + 1 >= (size_t)buffer_len) {
      ret = 1;
    } else if ((NULL != mysql->db) && (strlen(mysql->host) + strlen(port_ptr) + strlen(mysql->user) +
      strlen(mysql->db) + 1 >= (size_t)buffer_len)) {
      ret = 1;
    } else {
      memcpy(hash_key + hash_length, mysql->host, strlen(mysql->host));
      hash_length += strlen(mysql->host);

      memcpy(hash_key + hash_length, port_ptr, strlen(port_ptr));
      hash_length += strlen(port_ptr);

      memcpy(hash_key + hash_length, mysql->user, strlen(mysql->user));
      hash_length += strlen(mysql->user);
      if (NULL != mysql->db) {
        memcpy(hash_key + hash_length, mysql->db, strlen(mysql->db));
        hash_length += strlen(mysql->db);
      }
      hash_key[hash_length] = '\0';
      ob_rw_rdlock(&global_rwlock);
      *complex_hash = (COMPLEX_HASH *)hash_search(global_hash, hash_key, strlen((char *)hash_key));
      ob_rw_unlock(&global_rwlock);

      if (NULL == *complex_hash) {
        ob_rw_wrlock(&global_rwlock);
        if (NULL == *complex_hash) {
          if (complex_hash_init(mysql, hash_key, complex_hash)) {
            ret = 1;
          }
        }
        ob_rw_unlock(&global_rwlock);
      }
    }
  }

  return (ret);
}

COMPLEX_TYPE* STDCALL
get_complex_type(MYSQL *mysql, unsigned char *owner_name, unsigned char *type_name)
{
  COMPLEX_TYPE *type = NULL;
  COMPLEX_HASH *complex_hash = NULL;
  MYSQL *complex_mysql = NULL;
  unsigned char hash_key[200] = {'\0'};

  if (get_complex_hash(mysql, hash_key, 200, &complex_hash)) {
  } else {
    ob_rw_rdlock(&(complex_hash->rwlock));
    type = (COMPLEX_TYPE *)hash_search(complex_hash->hash, type_name, strlen((char *)type_name));
    ob_rw_unlock(&(complex_hash->rwlock));

    if (NULL == type || type->is_valid != 1) {
      if (complex_mysql_init(mysql, &complex_mysql)) {
      } else if (get_type_from_server(complex_mysql, owner_name, type_name, complex_hash)) {
      } else {
        ob_rw_rdlock(&(complex_hash->rwlock));
        type = (COMPLEX_TYPE *)hash_search(complex_hash->hash, type_name, strlen((char *)type_name));
        ob_rw_unlock(&(complex_hash->rwlock));
      }
    }
  }

  if (complex_mysql != NULL) {
    mysql_close(complex_mysql);
    complex_mysql = NULL;
  }

  if (type && type->is_valid != 1) {
    return (NULL);
  }

  return (type);
}

COMPLEX_TYPE* STDCALL
get_complex_type_with_local(MYSQL *mysql, unsigned char *type_name)
{
  COMPLEX_TYPE *type = NULL;
  COMPLEX_HASH *complex_hash = NULL;
  unsigned char hash_key[200] = {'\0'};
  memset(hash_key, 0, 200);

  if (get_complex_hash(mysql, hash_key, 200, &complex_hash)) {
  } else {
    ob_rw_rdlock(&(complex_hash->rwlock));
    type = (COMPLEX_TYPE *)hash_search(complex_hash->hash, type_name, strlen((char *)type_name));
    ob_rw_unlock(&(complex_hash->rwlock));
  }

  if (type && type->is_valid != 1) {
    return (NULL);
  }

  return (type);
}
