/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com)     |
   | Copyright (c) 1997-2010 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#include <vector>

#include "hphp/runtime/base/actrec-args.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/ext/extension.h"
#include "hphp/runtime/ext/mysql/mysql_common.h"
#include "hphp/runtime/ext/mysql/ext_mysql.h"
#include "hphp/util/logger.h"
#include "hphp/runtime/vm/native-prop-handler.h"

#include "mysql.h"

namespace HPHP {

const StaticString
  s_mysqli("mysqli"),
  s_connection_errno("__connection_errno"),
  s_connection_error("__connection_error"),
  s_connection("__connection"),
  s_link("__link"),
  s_result("__result"),
  s_resulttype("__resulttype"),
  s_reconnect("__reconnect"),
  s_report_mode("__report_mode"),
  s_done("__done"),
  s_stmt("__stmt"),
  s_charset("charset"),
  s_collation("collation"),
  s_dir("dir"),
  s_min_length("min_length"),
  s_max_length("max_length"),
  s_number("number"),
  s_state("state"),
  s_comment("comment"),
  s_mysqli_driver("mysqli_driver"),
  s_mysqli_result("mysqli_result"),
  s_mysqli_sql_exception("mysqli_sql_exception"),
  s_mysqli_stmt("mysqli_stmt"),
  s_mysqli_warning("mysqli_warning"),
  s_persistent_prefix("p:"),
  s_def("def"),
  s_free("free");

// ini settings settable anywhere -- PHP_INI_ALL
struct MysqliIniSetting {
  int m_default_port;
  std::string m_default_socket;
  std::string m_default_host;
  std::string m_default_user;
  std::string m_default_pw;
};

IMPLEMENT_THREAD_LOCAL(MysqliIniSetting, s_ini_setting);

// System-wide ini settings -- PHP_INI_SYSTEM
static bool allow_local_infile = true;
static bool allow_persistent = true;
static int max_persistent = -1;
static int max_links = -1;
static bool reconnect = false;

//////////////////////////////////////////////////////////////////////////////
// helper

static Resource get_connection_resource(const Object& obj) {
  auto res = obj->o_realProp(
    s_connection,
    ObjectData::RealPropUnchecked,
    s_mysqli
  );
  if (!res || !res->isResource()) {
    return Resource();
  }

  return res->toResource();
}

static std::shared_ptr<MySQL> get_connection(const Object& obj) {
  auto res = get_connection_resource(obj);
  return isa<MySQLResource>(res) ? unsafe_cast<MySQLResource>(res)->mysql()
                                 : nullptr;
}

static req::ptr<MySQLStmt> getStmt(const Object& obj) {
  auto res = obj->o_realProp(
    s_stmt,
    ObjectData::RealPropUnchecked,
    s_mysqli_stmt
  );
  assert(res->isResource());
  return cast<MySQLStmt>(*res);
}

static req::ptr<MySQLResult> getResult(const Object& obj) {
  auto res = obj->o_realProp(
    s_result,
    ObjectData::RealPropUnchecked,
    s_mysqli_result
  );
  if (!res || !res->isResource()) {
    return nullptr;
  }

  return cast_or_null<MySQLResult>(*res);
}

Variant mysqli_stmt_param_count_get(const Object& this_);
Variant mysqli_stmt_field_count_get(const Object& this_);

static TypedValue* bind_param_helper(const Object& obj, ActRec* ar,
                                     int start_index) {
  String types(getArg<KindOfString>(ar, start_index));

  int type_size = types.size();

  if (type_size < 1) {
    raise_warning("Invalid type or no types specified");
    return arReturn(ar, false);
  }
  if (type_size != ar->numArgs() - 1 - start_index) {
    raise_warning("Number of elements in type definition string doesn't match "
                  "number of bind variables");
    return arReturn(ar, false);
  }
  if (type_size != mysqli_stmt_param_count_get(obj).toInt64()) {
    raise_warning("Number of variables doesn't match number of parameters in "
                  "prepared statement");
    return arReturn(ar, false);
  }

  PackedArrayInit vars(type_size);
  for (int i = 0; i < type_size; i++) {
    char t = types[i];
    if (t != 'i' && t != 'd' && t != 's' && t != 'b') {
      raise_warning("Undefined fieldtype %c (parameter %d)", types[i],
                    i + 2 + start_index);
      return arReturn(ar, false);
    }
    auto rparam = getArg<KindOfRef>(ar, i + 1 + start_index);

    if (!rparam) {
      return arReturn(ar, false);
    }
    vars.appendRef(*rparam);
  }

  return arReturn(ar, getStmt(obj)->bind_param(types, vars.toArray()));
}

static TypedValue* bind_result_helper(const Object& obj, ActRec* ar,
                                      int start_index) {
  int64_t fields = mysqli_stmt_field_count_get(obj).toInt64();
  if (ar->numArgs() - start_index != fields) {
    raise_warning("Number of bind variables doesn't match number of fields in "
                  "prepared statement");
    return arReturn(ar, false);
  }

  PackedArrayInit vars(ar->numArgs());
  for (int i = start_index; i < ar->numArgs(); i++) {
    auto rparam = getArg<KindOfRef>(ar, i);

    if (!rparam) {
      return arReturn(ar, false);
    }
    vars.appendRef(*rparam);
  }

  return arReturn(ar, getStmt(obj)->bind_result(vars.toArray()));
}

//////////////////////////////////////////////////////////////////////////////
// class mysqli

#define VALIDATE_CONN(conn, state)                                \
  if (!conn ||                                                    \
      (static_cast<MySQLState>(state) != MySQLState::CLOSED &&    \
       conn->getState() < static_cast<MySQLState>(state))) {      \
    raise_warning("invalid object or resource mysqli");           \
    return init_null();                                           \
  }

#define VALIDATE_CONN_CONNECTED(conn) VALIDATE_CONN(conn, MySQLState::CONNECTED)

// since we allow null (e.g. nullOkay is true in the call into type-resource),
// we have to check if the resource data is null before we try to get a
// connection.
#define VALIDATE_RESOURCE(res, state)                                     \
  auto rdata = cast_or_null<MySQLResource>(res);                          \
  std::shared_ptr<MySQL> conn = nullptr;                                  \
  if (rdata) {                                                            \
    conn = rdata->mysql();                                                \
  }                                                                       \
  VALIDATE_CONN(conn, state)

static Variant HHVM_METHOD(mysqli, autocommit, bool mode) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return !mysql_autocommit(conn->get(), (my_bool)mode);
}

static Variant HHVM_METHOD(mysqli, change_user, const String& user,
                           const String& password, const String& database) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return !mysql_change_user(conn->get(), user.c_str(), password.c_str(),
                            database.c_str());
}

static Variant HHVM_METHOD(mysqli, character_set_name) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return String(mysql_character_set_name(conn->get()), CopyString);
}

static Variant HHVM_METHOD(mysqli, dump_debug_info) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return !mysql_dump_debug_info(conn->get());
}

static Variant HHVM_METHOD(mysqli, get_charset) {
  MY_CHARSET_INFO cs;
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  mysql_get_character_set_info(conn->get(), &cs);

  auto ret = SystemLib::AllocStdClassObject();
  ret.o_set(s_charset, String(cs.csname, CopyString));
  ret.o_set(s_collation, String(cs.name, CopyString));
  ret.o_set(s_dir, String(cs.dir, CopyString));
  ret.o_set(s_min_length, (int64_t)cs.mbminlen);
  ret.o_set(s_max_length, (int64_t)cs.mbmaxlen);
  ret.o_set(s_number, (int64_t)cs.number);
  ret.o_set(s_state, (int64_t)cs.state);
  ret.o_set(s_comment, String(cs.comment, CopyString));
  return ret;
}

//static Variant HHVM_METHOD(mysqli, get_connection_stats) {
//  throw NotImplementedException(__FUNCTION__);
//}

static Variant HHVM_METHOD(mysqli, hh_get_connection, int64_t state) {
  auto res = get_connection_resource(Object{this_});
  VALIDATE_RESOURCE(res, state)
  return res;
}

static Variant HHVM_METHOD(mysqli, hh_get_result, bool use_store) {
  auto res = get_connection_resource(Object{this_});
  VALIDATE_RESOURCE(res, MySQLState::CONNECTED)
  return php_mysql_get_result(res, use_store);
}

static void HHVM_METHOD(mysqli, hh_init) {
  auto data = std::make_shared<MySQL>(nullptr, 0, nullptr, nullptr, nullptr);
  auto rsrc = req::make<MySQLResource>(std::move(data));
  this_->o_set(s_connection, Variant(std::move(rsrc)), s_mysqli);
}

static bool HHVM_METHOD(mysqli, hh_real_connect, const Variant& server,
                        const Variant& username, const Variant& password,
                        const Variant& dbname, const Variant& client_flags) {
  bool persistent = false;
  String s = server.toString();
  if (s.substr(0, 2).equal(s_persistent_prefix)) {
    persistent = true;
    s = s.substr(2);
  }
  auto conn = get_connection(Object{this_});
  assert(conn);
  Variant ret = php_mysql_do_connect_on_link(
                  conn, s, username.toString(), password.toString(),
                  dbname.toString(), client_flags.toInt64(), persistent, false,
                  -1, -1);
  if (ret.toBoolean()) {
    // replace the connection incase we get a different one back (persistent)
    this_->o_set(s_connection, ret, s_mysqli);
    return true;
  } else {
    return false;
  }
}

static Variant HHVM_METHOD(mysqli, hh_real_query, const String& query) {
  auto res = get_connection_resource(Object{this_});
  VALIDATE_RESOURCE(res, MySQLState::CONNECTED)
  return php_mysql_do_query(query, res, false);
}

static void HHVM_METHOD(mysqli, hh_update_last_error, const Object& stmt_obj) {
  auto conn = get_connection(Object{this_});
  assert(conn);

  auto stmt = getStmt(stmt_obj);
  assert(stmt);

  auto s = stmt->get();
  auto mysql = conn->get();

  auto last_errno = mysql_stmt_errno(s);
  char last_error[MYSQL_ERRMSG_SIZE];
  char sqlstate[SQLSTATE_LENGTH + 1];
  memcpy(last_error, mysql_stmt_error(s), MYSQL_ERRMSG_SIZE);
  memcpy(sqlstate, mysql->net.sqlstate, SQLSTATE_LENGTH + 1);

  // This will clear the error both on the stmt and connection so we make sure
  // it closed now. Otherwise it will happen when the object is swept later
  stmt->close();

  // The MySQL C API documentation say that you shouldn't touch the internals of
  // the MySQL's datastructurs. But this is how Zend does it and there is no
  // other good way
  mysql->net.last_errno = last_errno;
  memcpy(mysql->net.last_error, last_error, MYSQL_ERRMSG_SIZE);
  memcpy(mysql->net.sqlstate, sqlstate, SQLSTATE_LENGTH + 1);
}

static Variant HHVM_METHOD(mysqli, kill, int64_t processid) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return !mysql_kill(conn->get(), processid);
}

static MaybeDataType get_option_value_type(int64_t option) {
  switch (option) {
    case MYSQL_INIT_COMMAND:
    case MYSQL_READ_DEFAULT_FILE:
    case MYSQL_READ_DEFAULT_GROUP:
    case MYSQL_SET_CHARSET_DIR:
    case MYSQL_SET_CHARSET_NAME:
#if MYSQL_VERSION_ID >= 50606
    case MYSQL_SERVER_PUBLIC_KEY:
#endif
#if MYSQL_VERSION_ID >= 40101
    case MYSQL_SET_CLIENT_IP:
#endif
#if MYSQL_VERSION_ID >= 40100
    case MYSQL_SHARED_MEMORY_BASE_NAME:
#endif
      return KindOfString;
    case MYSQL_OPT_CONNECT_TIMEOUT:
    case MYSQL_OPT_LOCAL_INFILE:
#if MYSQL_VERSION_ID >= 40101
    case MYSQL_OPT_READ_TIMEOUT:
    case MYSQL_OPT_WRITE_TIMEOUT:
#endif
#if MYSQL_VERSION_ID >= 40100
    case MYSQL_OPT_PROTOCOL:
#endif
      return KindOfInt64;
#if MYSQL_VERSION_ID >= 50610
    case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
#endif
#if MYSQL_VERSION_ID >= 50023
    case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
#endif
#if MYSQL_VERSION_ID >= 50013
    case MYSQL_OPT_RECONNECT:
#endif
#if MYSQL_VERSION_ID >= 50003
    case MYSQL_REPORT_DATA_TRUNCATION:
#endif
#if MYSQL_VERSION_ID >= 40101
    case MYSQL_SECURE_AUTH:
#endif
      return KindOfBoolean;
    case MYSQL_OPT_COMPRESS:
    case MYSQL_OPT_NAMED_PIPE:
#if MYSQL_VERSION_ID >= 40101
    case MYSQL_OPT_GUESS_CONNECTION:
    case MYSQL_OPT_USE_EMBEDDED_CONNECTION:
    case MYSQL_OPT_USE_REMOTE_CONNECTION:
#endif
      return KindOfNull;
    default:
      return folly::none;
  }

  not_reached();
}

static Variant HHVM_METHOD(mysqli, options, int64_t option,
                           const Variant& value) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN(conn, MySQLState::INITED)

  MaybeDataType dt = get_option_value_type(option);
  if (!dt) return false;

  // Just holders for the value
  my_bool bool_value;
  Variant other_value;

  const void *value_ptr = nullptr;
  if (!value.isNull()) {
    [&] {
      switch (*dt) {
        case KindOfNull:
          return;
        case KindOfBoolean:
          bool_value = value.toBoolean();
          value_ptr = &bool_value;
          return;
        case KindOfInt64:
          other_value = value.toInt64();
          value_ptr = other_value.getInt64Data();
          return;
        case KindOfString:
          other_value = value.toString();
          value_ptr = other_value.getStringData()->data();
          return;
        case KindOfUninit:
        case KindOfDouble:
        case KindOfStaticString:
        case KindOfPersistentArray:
        case KindOfArray:
        case KindOfObject:
        case KindOfResource:
        case KindOfRef:
        case KindOfClass:
          // Impossible.
          break;
      }
      not_reached();
    }();
  }

  return !mysql_options(conn->get(), (mysql_option)option,
                        static_cast<const char*>(value_ptr));
}

//static int64_t HHVM_STATIC_METHOD(mysqli, poll, VRefParam read,
//                                  VRefParam error, VRefParam reject,
//                                  int64_t sec, int64_t usec) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static Object HHVM_METHOD(mysqli, reap_async_query) {
//  throw NotImplementedException(__FUNCTION__);
//}

static Variant HHVM_METHOD(mysqli, refresh, int64_t options) {
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN_CONNECTED(conn);
  return !mysql_refresh(conn->get(), options);
}

//static int64_t HHVM_METHOD(mysqli, rpl_query_type, const String& query) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static bool HHVM_METHOD(mysqli, send_query, const String& query) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static bool HHVM_METHOD(mysqli, set_local_infile_handler,
//                        const Object& read_func) {
//  throw NotImplementedException(__FUNCTION__);
//}

static Variant HHVM_METHOD(mysqli, ssl_set, const Variant& key,
                           const Variant& cert, const Variant& ca,
                           const Variant& capath, const Variant& cipher) {
  auto get_str_ptr = [](const Variant& v) -> const char* {
    return v.isNull() ? nullptr : v.toString().c_str();
  };
  auto conn = get_connection(Object{this_});
  VALIDATE_CONN(conn, MySQLState::INITED);
  mysql_ssl_set(conn->get(), get_str_ptr(key), get_str_ptr(cert),
                get_str_ptr(ca), get_str_ptr(capath), get_str_ptr(cipher));
  return true;
}

static Variant* getRawProp(const Object& obj,
                          const String& propName,
                          const String& className) {
  return obj->o_realProp(
    propName,
    ObjectData::RealPropUnchecked,
    className
  );
}

static Variant getStaticProp(const Object& obj, const StaticString& prop) {
  auto cls = obj->getVMClass();
  return tvAsVariant(cls->getSPropData(cls->lookupSProp(prop.get())));
}

static String HHVM_FUNCTION(mysqli_get_client_info) {
  return HHVM_FN(mysql_get_client_info)();
}

static int64_t HHVM_FUNCTION(mysqli_get_client_version) {
  return mysql_get_client_version();
}

// Native accessor properties of mysqli.

static Variant mysqli_client_info_get(const Object& this_) {
  return HHVM_FN(mysqli_get_client_info)();
}

static Variant mysqli_client_version_get(const Object& this_) {
  return HHVM_FN(mysqli_get_client_version)();
}

static Variant mysqli_connect_errno_get(const Object& this_) {
  return getStaticProp(this_, s_connection_errno);
}

static Variant mysqli_connect_error_get(const Object& this_) {
  return getStaticProp(this_, s_connection_error);
}

static Variant mysqli_affected_rows_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_affected_rows)(res);
}

static Variant mysqli_error_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_error)(res);
}

static Variant mysqli_errno_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_errno)(res);
}

static Variant mysqli_field_count_get(const Object& this_) {
  auto conn = get_connection(this_);
  VALIDATE_CONN_CONNECTED(conn);
  return (int64_t)mysql_field_count(conn->get());
}

static Variant mysqli_host_info_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_get_host_info)(res);
}

static Variant mysqli_info_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_info)(res);
}

static Variant mysqli_insert_id_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_insert_id)(res);
}

static Variant mysqli_protocol_version_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_get_proto_info)(res);
}

static Variant mysqli_server_info_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_get_server_info)(res);
}

static Variant mysqli_server_version_get(const Object& this_) {
  auto conn = get_connection(this_);
  VALIDATE_CONN_CONNECTED(conn);
  return (int64_t)mysql_get_server_version(conn->get());
}

static Variant mysqli_sqlstate_get(const Object& this_) {
  auto conn = get_connection(this_);
  VALIDATE_CONN_CONNECTED(conn);
  return String(mysql_sqlstate(conn->get()), CopyString);
}

static Variant mysqli_thread_id_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_thread_id)(res);
}

static Variant mysqli_warning_count_get(const Object& this_) {
  auto res = get_connection_resource(this_);
  VALIDATE_RESOURCE(res, MySQLState::INITED)
  return HHVM_FN(mysql_warning_count)(res);
}

static Variant mysqli_error_list_get(const Object& this_) {
  Array ret = Array::Create();
  Variant errorNumber = mysqli_errno_get(this_);

  if (errorNumber.asInt64Val()) {
    Array errorList = Array::Create();
    errorList.set(String("errno"), errorNumber);
    errorList.set(String("sqlstate"), mysqli_sqlstate_get(this_));
    errorList.set(String("error"), mysqli_error_get(this_));
    ret.set(0, errorList);
  }

  return ret;
}

static Native::PropAccessor mysqli_Accessors[] = {
  {"client_info",       mysqli_client_info_get,
                        nullptr, nullptr, nullptr},
  {"client_version",    mysqli_client_version_get,
                        nullptr, nullptr, nullptr},
  {"connect_errno",     mysqli_connect_errno_get,
                        nullptr, nullptr, nullptr},
  {"connect_error",     mysqli_connect_error_get,
                        nullptr, nullptr, nullptr},
  {"affected_rows",     mysqli_affected_rows_get,
                        nullptr, nullptr, nullptr},
  {"error",             mysqli_error_get,
                        nullptr, nullptr, nullptr},
  {"errno",             mysqli_errno_get,
                        nullptr, nullptr, nullptr},
  {"field_count",       mysqli_field_count_get,
                        nullptr, nullptr, nullptr},
  {"host_info",         mysqli_host_info_get,
                        nullptr, nullptr, nullptr},
  {"info",              mysqli_info_get,
                        nullptr, nullptr, nullptr},
  {"insert_id",         mysqli_insert_id_get,
                        nullptr, nullptr, nullptr},
  {"protocol_version",  mysqli_protocol_version_get,
                        nullptr, nullptr, nullptr},
  {"server_info",       mysqli_server_info_get,
                        nullptr, nullptr, nullptr},
  {"server_version",    mysqli_server_version_get,
                        nullptr, nullptr, nullptr},
  {"sqlstate",          mysqli_sqlstate_get,
                        nullptr, nullptr, nullptr},
  {"thread_id",         mysqli_thread_id_get,
                        nullptr, nullptr, nullptr},
  {"warning_count",     mysqli_warning_count_get,
                        nullptr, nullptr, nullptr},
  {"error_list",        mysqli_error_list_get,
                        nullptr, nullptr, nullptr},

  {nullptr, nullptr, nullptr, nullptr, nullptr}
};

static Native::PropAccessorMap mysqli_accessorsMap
((Native::PropAccessor*)mysqli_Accessors);

// Prop handler for mysqli.
struct mysqli_PropHandler : Native::MapPropHandler<mysqli_PropHandler> {
  static constexpr Native::PropAccessorMap& map = mysqli_accessorsMap;
};


//////////////////////////////////////////////////////////////////////////////
// class mysqli_driver

//static void HHVM_METHOD(mysqli_driver, embedded_server_end) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static bool HHVM_METHOD(mysqli_driver, embedded_server_start, bool start,
//                        const Array& arguments, const Array& groups) {
//  throw NotImplementedException(__FUNCTION__);
//}

static Variant mysqli_driver_client_info_get(const Object& this_) {
  return HHVM_FN(mysqli_get_client_info)();
}

static Variant mysqli_driver_client_version_get(const Object& this_) {
  return HHVM_FN(mysqli_get_client_version)();
}

static Variant mysqli_driver_driver_version_get(const Object& this_) {
  // Lets pretend we are the same version as PHP. Taken from here
  // http://git.io/wY2WPw
  return 101009;
}

static Variant mysqli_driver_embedded_get(const Object& this_) {
  return false;
}

static Variant mysqli_driver_reconnect_get(const Object& this_) {
  return *getRawProp(this_, s_reconnect, s_mysqli_driver);
}

void mysqli_driver_reconnect_set(const Object& this_, Variant& value) {
  this_->o_set(s_reconnect, value, s_mysqli_driver);
}

static Variant mysqli_driver_report_mode_get(const Object& this_) {
  return *getRawProp(this_, s_report_mode, s_mysqli_driver);
}

void mysqli_driver_report_mode_set(const Object& this_, Variant& value) {
  this_->o_set(s_report_mode, value, s_mysqli_driver);
}

static Native::PropAccessor mysqli_driver_Accessors[] = {
  {"client_info",    mysqli_driver_client_info_get,
                     nullptr, nullptr, nullptr},

  {"client_version", mysqli_driver_client_version_get,
                     nullptr, nullptr, nullptr},

  {"driver_version", mysqli_driver_driver_version_get,
                     nullptr, nullptr, nullptr},

  {"embedded",       mysqli_driver_embedded_get,
                     nullptr, nullptr, nullptr},

  {"reconnect",      mysqli_driver_reconnect_get,
                     mysqli_driver_reconnect_set,
                     nullptr, nullptr},

  {"report_mode",    mysqli_driver_report_mode_get,
                     mysqli_driver_report_mode_set,
                     nullptr, nullptr},

  {nullptr, nullptr, nullptr, nullptr, nullptr}
};

static Native::PropAccessorMap mysqli_driver_accessorsMap
((Native::PropAccessor*)mysqli_driver_Accessors);

// Prop handler for mysqli_result.
struct mysqli_driver_PropHandler :
  Native::MapPropHandler<mysqli_driver_PropHandler> {

  static constexpr Native::PropAccessorMap& map = mysqli_driver_accessorsMap;
};


//////////////////////////////////////////////////////////////////////////////
// class mysqli_result

#define VALIDATE_RESULT(res)                                                   \
  if (!res || !res->get()) {                                                   \
    raise_warning("invalid object or resource mysqli_result");                 \
    return init_null();                                                        \
  }

static Variant HHVM_METHOD(mysqli_result, get_mysqli_conn_resource,
                           const Object& connection) {
  auto res = get_connection_resource(connection);
  VALIDATE_RESOURCE(res, MySQLState::CONNECTED);
  return res;
}

#undef VALIDATE_CONN
#undef VALIDATE_CONN_CONNECTED

static Variant HHVM_METHOD(mysqli_result, fetch_field) {
  auto res = getResult(Object{this_});
  VALIDATE_RESULT(res)

  auto info = res->fetchFieldInfo();
  if (!info) {
    return false;
  }

  auto obj = SystemLib::AllocStdClassObject();
  obj->o_set("name",       info->name);
  obj->o_set("orgname",    info->org_name);
  obj->o_set("table",      info->table);
  obj->o_set("orgtable",   info->org_table);
  obj->o_set("def",        info->def);
  obj->o_set("db",         info->db);
  obj->o_set("catalog",    s_def);
  obj->o_set("max_length", info->max_length);
  obj->o_set("length",     info->length);
  obj->o_set("charsetnr",  (int64_t)info->charsetnr);
  obj->o_set("flags",      (int64_t)info->flags);
  obj->o_set("type",       info->type);
  obj->o_set("decimals",   (int64_t)info->decimals);

  return obj;
}

// Consts to be used on PHP and C++ side.
const int64_t _MYSQLI_STORE_RESULT = 0;
const int64_t _MYSQLI_USE_RESULT = 1;

// Native accessor properties of mysqli_result.

Variant mysqli_result_current_field_get(const Object& this_) {
  auto res = getResult(this_);
  VALIDATE_RESULT(res)
  return res->tellField();
}

Variant mysqli_result_field_count_get(const Object& this_) {
  auto res = getResult(this_);
  VALIDATE_RESULT(res)
  return HHVM_FN(mysql_num_fields)(Resource(std::move(res)));
}

Variant mysqli_result_lengths_get(const Object& this_) {
  auto res = getResult(this_);
  VALIDATE_RESULT(res)
  auto lengths = HHVM_FN(mysql_fetch_lengths)(Resource(std::move(res)));
  if (!lengths.toBoolean()) {
    return init_null_variant;
  }
  return lengths;
}

Variant mysqli_result_type_get(const Object& this_) {
  return *getRawProp(this_, s_resulttype, s_mysqli_result);
}

Variant mysqli_result_num_rows_get(const Object& this_) {
  auto res = getResult(this_);
  VALIDATE_RESULT(res)

  auto resultType = getRawProp(this_, s_resulttype, s_mysqli_result);
  auto done = getRawProp(this_, s_done, s_mysqli_result)->toBoolean();

  if (resultType->toInt64() == _MYSQLI_USE_RESULT && !done) {
    raise_warning("Function can not be used with MYSQL_USE_RESULT");
    return VarNR(0);
  }

  return HHVM_FN(mysql_num_rows)(Resource(std::move(res)));
}

static Native::PropAccessor mysqli_result_Accessors[] = {
  {"current_field", mysqli_result_current_field_get,
                    nullptr, nullptr, nullptr},

  {"field_count",   mysqli_result_field_count_get,
                    nullptr, nullptr, nullptr},

  {"lengths",       mysqli_result_lengths_get,
                    nullptr, nullptr, nullptr},

  {"type",          mysqli_result_type_get,
                    nullptr, nullptr, nullptr},

  {"num_rows",      mysqli_result_num_rows_get,
                    nullptr, nullptr, nullptr},

  {nullptr, nullptr, nullptr, nullptr, nullptr}
};

static Native::PropAccessorMap mysqli_result_accessorsMap
((Native::PropAccessor*)mysqli_result_Accessors);

// Prop handler for mysqli_result.
struct mysqli_result_PropHandler :
  Native::MapPropHandler<mysqli_result_PropHandler> {

  static constexpr Native::PropAccessorMap& map = mysqli_result_accessorsMap;
};

#undef VALIDATE_RESULT

//////////////////////////////////////////////////////////////////////////////
// class mysqli_stmt

static Variant HHVM_METHOD(mysqli_stmt, attr_get, int64_t attr) {
  return getStmt(Object{this_})->attr_get(attr);
}

static Variant HHVM_METHOD(mysqli_stmt, attr_set, int64_t attr, int64_t mode) {
  return getStmt(Object{this_})->attr_set(attr, mode);
}

static TypedValue* HHVM_MN(mysqli_stmt, bind_param)(ActRec* ar) {
  return bind_param_helper(Object{ar->getThis()}, ar, 0);
}

static TypedValue* HHVM_MN(mysqli_stmt, bind_result)(ActRec* ar) {
  return bind_result_helper(Object{ar->getThis()}, ar, 0);
}

static Variant HHVM_METHOD(mysqli_stmt, close) {
  return getStmt(Object{this_})->close();
}

static void HHVM_METHOD(mysqli_stmt, data_seek, int64_t offset) {
  getStmt(Object{this_})->data_seek(offset);
}

static Variant HHVM_METHOD(mysqli_stmt, execute) {
  return getStmt(Object{this_})->execute();
}

static Variant HHVM_METHOD(mysqli_stmt, fetch) {
  return getStmt(Object{this_})->fetch();
}

static void HHVM_METHOD(mysqli_stmt, free_result) {
  getStmt(Object{this_})->free_result();
}

//static Object HHVM_METHOD(mysqli_stmt, get_result) {
//  throw NotImplementedException("mysqli_stmt::get_result");
//}

static void HHVM_METHOD(mysqli_stmt, hh_init, const Object& connection) {
  auto conn = get_connection(connection);
  if (!conn) {
    raise_warning("invalid object or resource mysqli");
    return;
  }
  auto data = req::make<MySQLStmt>(conn->get());
  this_->o_set(s_stmt, Variant(std::move(data)), s_mysqli_stmt);
}

static Variant HHVM_METHOD(mysqli_stmt, hh_store_result) {
  return getStmt(Object{this_})->store_result();
}

static Variant HHVM_METHOD(mysqli_stmt, prepare, const String& query) {
  return getStmt(Object{this_})->prepare(query);
}

static Variant HHVM_METHOD(mysqli_stmt, reset) {
  return getStmt(Object{this_})->reset();
}

static Variant HHVM_METHOD(mysqli_stmt, result_metadata) {
  return getStmt(Object{this_})->result_metadata();
}

static Variant HHVM_METHOD(mysqli_stmt, send_long_data, int64_t param_nr,
                           const String& data) {
  return getStmt(Object{this_})->send_long_data(param_nr, data);
}

Variant mysqli_stmt_affected_rows_get(const Object& this_) {
  return getStmt(this_)->affected_rows();
}

Variant mysqli_stmt_errno_get(const Object& this_) {
  return getStmt(this_)->get_errno();
}

Variant mysqli_stmt_error_get(const Object& this_) {
  return getStmt(this_)->get_error();
}

Variant mysqli_stmt_field_count_get(const Object& this_) {
  return getStmt(this_)->field_count();
}

Variant mysqli_stmt_insert_id_get(const Object& this_) {
  return getStmt(this_)->insert_id();
}

Variant mysqli_stmt_num_rows_get(const Object& this_) {
  return getStmt(this_)->num_rows();
}

Variant mysqli_stmt_param_count_get(const Object& this_) {
  return getStmt(this_)->param_count();
}

Variant mysqli_stmt_sqlstate_get(const Object& this_) {
  return getStmt(this_)->sqlstate();
}

Variant mysqli_stmt_error_list_get(const Object& this_) {
  Array ret = Array::Create();
  Variant errorNumber = mysqli_stmt_errno_get(this_);

  if (errorNumber.asInt64Val()) {
    Array errorList = Array::Create();
    errorList.set(String("errno"), errorNumber);
    errorList.set(String("sqlstate"), mysqli_stmt_sqlstate_get(this_));
    errorList.set(String("error"), mysqli_stmt_error_get(this_));
    ret.set(0, errorList);
  }

  return ret;
}

static Native::PropAccessor mysqli_stmt_Accessors[] = {
  {"affected_rows", mysqli_stmt_affected_rows_get,
                    nullptr, nullptr, nullptr},

  {"errno",         mysqli_stmt_errno_get,
                    nullptr, nullptr, nullptr},

  {"error_list",    mysqli_stmt_error_list_get,
                    nullptr, nullptr, nullptr},

  {"error",         mysqli_stmt_error_get,
                    nullptr, nullptr, nullptr},

  {"field_count",   mysqli_stmt_field_count_get,
                    nullptr, nullptr, nullptr},

  {"insert_id",     mysqli_stmt_insert_id_get,
                    nullptr, nullptr, nullptr},

  {"num_rows",      mysqli_stmt_num_rows_get,
                    nullptr, nullptr, nullptr},

  {"param_count",   mysqli_stmt_param_count_get,
                    nullptr, nullptr, nullptr},

  {"sqlstate",      mysqli_stmt_sqlstate_get,
                    nullptr, nullptr, nullptr},

  {nullptr, nullptr, nullptr, nullptr, nullptr}
};

static Native::PropAccessorMap mysqli_stmt_accessorsMap
((Native::PropAccessor*)mysqli_stmt_Accessors);

struct mysqli_stmt_PropHandler :
  Native::MapPropHandler<mysqli_stmt_PropHandler> {

  static constexpr Native::PropAccessorMap& map = mysqli_stmt_accessorsMap;
};

//////////////////////////////////////////////////////////////////////////////
// class mysqli_warning

//static void HHVM_METHOD(mysqli_warning, __construct) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static void HHVM_METHOD(mysqli_warning, next) {
//  throw NotImplementedException(__FUNCTION__);
//}

//////////////////////////////////////////////////////////////////////////////
// functions

void HHVM_FUNCTION(mysqli_free_result, const Variant& result) {
  if (!UNLIKELY(result.isObject()
      && result.toObject().instanceof(s_mysqli_result))) {
    raise_warning(
        "mysqli_free_result() expects parameter 1 to be mysqli_result");
  } else {
    result.toObject()->o_invoke_few_args(s_free, 0);
  }
}

//static Array HHVM_FUNCTION(mysqli_get_client_stats) {
//  throw NotImplementedException(__FUNCTION__);
//}
//
//static void HHVM_FUNCTION(mysqli_set_local_infile_default,
//                          const Object& link) {
//  throw NotImplementedException(__FUNCTION__);
//}

static TypedValue* HHVM_FN(mysqli_stmt_bind_param)(ActRec* ar) {
  return bind_param_helper(Object{getArg<KindOfObject>(ar, 0)}, ar, 1);
}

static TypedValue* HHVM_FN(mysqli_stmt_bind_result)(ActRec* ar) {
  return bind_result_helper(Object{getArg<KindOfObject>(ar, 0)}, ar, 1);
}

static bool HHVM_FUNCTION(mysqli_thread_safe) {
  return mysql_thread_safe();
}

//////////////////////////////////////////////////////////////////////////////

class mysqliExtension final : public Extension {
public:
  mysqliExtension() : Extension("mysqli") {}
  // Use moduleLoad() for settings that are system-wide and cannot
  // change per request (e.g. PHP_INI_SYSTEM)
  void moduleLoad(const IniSetting::Map& ini, Hdf config) override {
    // Not supporting local_infile yet. But this is the skeleton for
    // when we do.
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.allow_local_infile",
                     IniSetting::SetAndGet<bool>(
                       [](const bool value) { return false; },
                       []() { return false; }
                     ),
                     &allow_local_infile);
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.allow_persistent", "true",
                     &allow_persistent);
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.max_persistent", "-1",
                     &max_persistent);
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.max_links", "-1",
                     &max_links);
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.reconnect", "false",
                     &reconnect);
    // Requires mysqlnd. This setting is actually an int, but setting
    // to a bool that returns false for now. If and when we support
    // this, we set to an int and define and bind to &cache_size
    IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
                     "mysqli.cache_size",
                     IniSetting::SetAndGet<bool>(
                       [](const bool value) { return false; },
                       []() { return false; }
                     ));

    MySQL::SetAllowReconnect(reconnect);
    MySQL::SetAllowPersistent(allow_persistent);
    if (allow_persistent) {
      // unlimited (0) is the default, if max_persistent is -1
      if (max_persistent <= -1) {
        MySQL::SetMaxNumPersistent(0);
      } else {
        MySQL::SetMaxNumPersistent(max_persistent);
      }
    }
  }

  // Use threadInit() for settings that can change per user request
  // (e.g., PHP_INI_ALL, PHP_INI_USER)
  void threadInit() override {
    IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
                     "mysqli.default_port", "3306",
                     &s_ini_setting->m_default_port);
    IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
                     "mysqli.default_socket", nullptr,
                     &s_ini_setting->m_default_socket);
    IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
                     "mysqli.default_host", nullptr,
                     &s_ini_setting->m_default_host);
    IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
                     "mysqli.default_user", nullptr,
                     &s_ini_setting->m_default_user);
    IniSetting::Bind(this, IniSetting::PHP_INI_ALL,
                     "mysqli.default_pw", nullptr,
                     &s_ini_setting->m_default_pw);
  }

  void moduleInit() override {
    // mysqli
    HHVM_ME(mysqli, autocommit);
    HHVM_ME(mysqli, change_user);
    HHVM_ME(mysqli, character_set_name);
    HHVM_ME(mysqli, dump_debug_info);
    HHVM_ME(mysqli, get_charset);
    //HHVM_ME(mysqli, get_connection_stats); // MYSQLND
    HHVM_ME(mysqli, hh_get_connection);
    HHVM_ME(mysqli, hh_get_result);
    HHVM_ME(mysqli, hh_init);
    HHVM_ME(mysqli, hh_real_connect);
    HHVM_ME(mysqli, hh_real_query);
    HHVM_ME(mysqli, hh_update_last_error);
    HHVM_ME(mysqli, kill);
    HHVM_ME(mysqli, options);
    //HHVM_STATIC_ME(mysqli, poll); // MYSQLND
    //HHVM_ME(mysqli, reap_async_query); // MYSQLND
    HHVM_ME(mysqli, refresh);
    //HHVM_ME(mysqli, set_local_infile_handler);
    HHVM_ME(mysqli, ssl_set);

    Native::registerNativePropHandler
      <mysqli_PropHandler>(s_mysqli);

    // mysqli_driver
    Native::registerNativePropHandler
      <mysqli_driver_PropHandler>(s_mysqli_driver);

    // mysqli_result
    HHVM_ME(mysqli_result, get_mysqli_conn_resource);
    HHVM_ME(mysqli_result, fetch_field);

    Native::registerNativePropHandler
      <mysqli_result_PropHandler>(s_mysqli_result);

    // mysqli_stmt
    HHVM_ME(mysqli_stmt, attr_get);
    HHVM_ME(mysqli_stmt, attr_set);
    HHVM_ME(mysqli_stmt, bind_param);
    HHVM_ME(mysqli_stmt, bind_result);
    HHVM_ME(mysqli_stmt, close);
    HHVM_ME(mysqli_stmt, data_seek);
    HHVM_ME(mysqli_stmt, execute);
    HHVM_ME(mysqli_stmt, fetch);
    HHVM_ME(mysqli_stmt, free_result);
    //HHVM_ME(mysqli_stmt, get_result); // MYSQLND
    HHVM_ME(mysqli_stmt, hh_init);
    HHVM_ME(mysqli_stmt, hh_store_result);
    HHVM_ME(mysqli_stmt, prepare);
    HHVM_ME(mysqli_stmt, reset);
    HHVM_ME(mysqli_stmt, result_metadata);
    HHVM_ME(mysqli_stmt, send_long_data);

    Native::registerNativePropHandler
      <mysqli_stmt_PropHandler>(s_mysqli_stmt);

    HHVM_FE(mysqli_get_client_info);
    HHVM_FE(mysqli_get_client_version);
    //HHVM_FE(mysqli_get_client_stats);
    //HHVM_FE(mysqli_set_local_infile_default);
    HHVM_FE(mysqli_stmt_bind_param);
    HHVM_FE(mysqli_stmt_bind_result);
    HHVM_FE(mysqli_thread_safe);
    HHVM_FE(mysqli_free_result);

#define REGISTER_CONST_VALUE(option, value)                                    \
  Native::registerConstant<KindOfInt64>(makeStaticString("MYSQLI_" #option),   \
                                        (value));
#define REGISTER_CONST(option)                                                 \
  Native::registerConstant<KindOfInt64>(makeStaticString("MYSQLI_" #option),   \
                                        (option));
#define REGISTER_CONST_MIRROR(option)                                          \
  Native::registerConstant<KindOfInt64>(makeStaticString("MYSQLI_" #option),   \
                                        (MYSQL_##option));

    // Fetch type
    REGISTER_CONST_VALUE(ASSOC, 1)
    REGISTER_CONST_VALUE(NUM, 2)
    REGISTER_CONST_VALUE(BOTH, 3)

    // Fetch return
    REGISTER_CONST_MIRROR(NO_DATA)
    REGISTER_CONST_MIRROR(DATA_TRUNCATED)

    // Result type
    REGISTER_CONST_VALUE(STORE_RESULT, _MYSQLI_STORE_RESULT)
    REGISTER_CONST_VALUE(USE_RESULT, _MYSQLI_USE_RESULT)

    // Report options
    REGISTER_CONST_VALUE(REPORT_OFF, 0)
    REGISTER_CONST_VALUE(REPORT_ERROR, 1)
    REGISTER_CONST_VALUE(REPORT_STRICT, 2)
    REGISTER_CONST_VALUE(REPORT_INDEX, 4)
    REGISTER_CONST_VALUE(REPORT_CLOSE, 8)
    REGISTER_CONST_VALUE(REPORT_ALL, 255)

    // Stmt Attr types
    REGISTER_CONST(STMT_ATTR_UPDATE_MAX_LENGTH)
    REGISTER_CONST(STMT_ATTR_CURSOR_TYPE)
    REGISTER_CONST(STMT_ATTR_PREFETCH_ROWS)

    // Stmt Attr Cursor type
    REGISTER_CONST(CURSOR_TYPE_NO_CURSOR)
    REGISTER_CONST(CURSOR_TYPE_READ_ONLY)
    REGISTER_CONST(CURSOR_TYPE_FOR_UPDATE)
    REGISTER_CONST(CURSOR_TYPE_SCROLLABLE)

    // Field flags
    REGISTER_CONST(NOT_NULL_FLAG)
    REGISTER_CONST(PRI_KEY_FLAG)
    REGISTER_CONST(UNIQUE_KEY_FLAG)
    REGISTER_CONST(MULTIPLE_KEY_FLAG)
    REGISTER_CONST(UNSIGNED_FLAG)
    REGISTER_CONST(ZEROFILL_FLAG)
    REGISTER_CONST(BINARY_FLAG)
    REGISTER_CONST(AUTO_INCREMENT_FLAG)
    REGISTER_CONST(ENUM_FLAG)
    REGISTER_CONST(SET_FLAG)
    REGISTER_CONST(BLOB_FLAG)
    REGISTER_CONST(TIMESTAMP_FLAG)
    REGISTER_CONST(NUM_FLAG)
    REGISTER_CONST(PART_KEY_FLAG)
    REGISTER_CONST(GROUP_FLAG)
#if MYSQL_VERSION_ID > 50001
    REGISTER_CONST(NO_DEFAULT_VALUE_FLAG)
#endif

    // Refresh options
    REGISTER_CONST(REFRESH_GRANT)
    REGISTER_CONST(REFRESH_LOG)
    REGISTER_CONST(REFRESH_TABLES)
    REGISTER_CONST(REFRESH_HOSTS)
    REGISTER_CONST(REFRESH_STATUS)
    REGISTER_CONST(REFRESH_THREADS)
    REGISTER_CONST(REFRESH_SLAVE)
    REGISTER_CONST(REFRESH_MASTER)

    // Transaction flags
    REGISTER_CONST_VALUE(TRANS_START_WITH_CONSISTENT_SNAPSHOT, 1)
    REGISTER_CONST_VALUE(TRANS_START_READ_WRITE, 2)
    REGISTER_CONST_VALUE(TRANS_START_READ_ONLY, 4)
    REGISTER_CONST_VALUE(TRANS_COR_AND_CHAIN, 1)
    REGISTER_CONST_VALUE(TRANS_COR_AND_NO_CHAIN, 2)
    REGISTER_CONST_VALUE(TRANS_COR_RELEASE, 4)
    REGISTER_CONST_VALUE(TRANS_COR_NO_RELEASE, 8)

    // Connection flags
    REGISTER_CONST(CLIENT_SSL)
    REGISTER_CONST(CLIENT_COMPRESS)
    REGISTER_CONST(CLIENT_INTERACTIVE)
    REGISTER_CONST(CLIENT_IGNORE_SPACE)
    REGISTER_CONST(CLIENT_NO_SCHEMA)
    REGISTER_CONST(CLIENT_FOUND_ROWS)

    // Field types
    REGISTER_CONST_MIRROR(TYPE_TINY)
    REGISTER_CONST_MIRROR(TYPE_SHORT)
    REGISTER_CONST_MIRROR(TYPE_INT24)
    REGISTER_CONST_MIRROR(TYPE_LONG)
    REGISTER_CONST_MIRROR(TYPE_LONGLONG)
    REGISTER_CONST_MIRROR(TYPE_DECIMAL)
    REGISTER_CONST_MIRROR(TYPE_FLOAT)
    REGISTER_CONST_MIRROR(TYPE_DOUBLE)
    REGISTER_CONST_MIRROR(TYPE_TIMESTAMP)
    REGISTER_CONST_MIRROR(TYPE_DATE)
    REGISTER_CONST_MIRROR(TYPE_NEWDATE)
    REGISTER_CONST_MIRROR(TYPE_TIME)
    REGISTER_CONST_MIRROR(TYPE_DATETIME)
    REGISTER_CONST_MIRROR(TYPE_YEAR)
    REGISTER_CONST_MIRROR(TYPE_ENUM)
    REGISTER_CONST_MIRROR(TYPE_SET)
    REGISTER_CONST_MIRROR(TYPE_TINY_BLOB)
    REGISTER_CONST_MIRROR(TYPE_MEDIUM_BLOB)
    REGISTER_CONST_MIRROR(TYPE_LONG_BLOB)
    REGISTER_CONST_MIRROR(TYPE_BLOB)
    REGISTER_CONST_MIRROR(TYPE_VARCHAR)
    REGISTER_CONST_MIRROR(TYPE_VAR_STRING)
    REGISTER_CONST_MIRROR(TYPE_STRING)
    REGISTER_CONST_MIRROR(TYPE_GEOMETRY)
    REGISTER_CONST_MIRROR(TYPE_NULL)
#if MYSQL_VERSION_ID > 50002
    REGISTER_CONST_MIRROR(TYPE_NEWDECIMAL)
    REGISTER_CONST_MIRROR(TYPE_BIT)
#endif

    // Zend have some extra that is just maps to some other type
    REGISTER_CONST_VALUE(TYPE_INTERVAL, MYSQL_TYPE_ENUM)
    REGISTER_CONST_VALUE(TYPE_CHAR, MYSQL_TYPE_TINY)

    // Options
    REGISTER_CONST_MIRROR(INIT_COMMAND)
    REGISTER_CONST_MIRROR(OPT_COMPRESS)
    REGISTER_CONST_MIRROR(OPT_CONNECT_TIMEOUT)
    REGISTER_CONST_MIRROR(OPT_LOCAL_INFILE)
    REGISTER_CONST_MIRROR(OPT_NAMED_PIPE)
    REGISTER_CONST_MIRROR(READ_DEFAULT_FILE)
    REGISTER_CONST_MIRROR(READ_DEFAULT_GROUP)
    REGISTER_CONST_MIRROR(SET_CHARSET_DIR)
    REGISTER_CONST_MIRROR(SET_CHARSET_NAME)
#if MYSQL_VERSION_ID >= 50610
    REGISTER_CONST_MIRROR(OPT_CAN_HANDLE_EXPIRED_PASSWORDS)
#endif
#if MYSQL_VERSION_ID >= 50606
    REGISTER_CONST_MIRROR(SERVER_PUBLIC_KEY)
#endif
#if MYSQL_VERSION_ID >= 50023
    REGISTER_CONST_MIRROR(OPT_SSL_VERIFY_SERVER_CERT)
#endif
#if MYSQL_VERSION_ID >= 50013
    REGISTER_CONST_MIRROR(OPT_RECONNECT)
#endif
#if MYSQL_VERSION_ID >= 50003
    REGISTER_CONST_MIRROR(REPORT_DATA_TRUNCATION)
#endif
#if MYSQL_VERSION_ID >= 40101
    REGISTER_CONST_MIRROR(OPT_GUESS_CONNECTION)
    REGISTER_CONST_MIRROR(OPT_READ_TIMEOUT)
    REGISTER_CONST_MIRROR(OPT_USE_EMBEDDED_CONNECTION)
    REGISTER_CONST_MIRROR(OPT_USE_REMOTE_CONNECTION)
    REGISTER_CONST_MIRROR(OPT_WRITE_TIMEOUT)
    REGISTER_CONST_MIRROR(SECURE_AUTH)
    REGISTER_CONST_MIRROR(SET_CLIENT_IP)
#endif
#if MYSQL_VERSION_ID >= 40100
    REGISTER_CONST_MIRROR(OPT_PROTOCOL)
    REGISTER_CONST_MIRROR(SHARED_MEMORY_BASE_NAME)
#endif

    // Misc
    REGISTER_CONST_VALUE(DEBUG_TRACE_ENABLED, 0)
    REGISTER_CONST_VALUE(SERVER_QUERY_NO_GOOD_INDEX_USED, 16)
    REGISTER_CONST_VALUE(SERVER_QUERY_NO_INDEX_USED, 32)
    REGISTER_CONST_VALUE(SERVER_QUERY_WAS_SLOW, 2048)

#undef REGISTER_CONST_VALUE
#undef REGISTER_CONST
#undef REGISTER_CONST_MIRROR

    loadSystemlib();
  }
} s_mysqli_extension;

// Uncomment for non-bundled module
//HHVM_GET_MODULE(mysqli);

//////////////////////////////////////////////////////////////////////////////
} // namespace HPHP
