hphp/runtime/ext/async_mysql/ext_async_mysql.cpp (1,997 lines of code) (raw):
/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com)         |
   +----------------------------------------------------------------------+
   | 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 "ext_async_mysql.h"
#include <algorithm>
#include <memory>
#include <vector>
#include <folly/ssl/OpenSSLCertUtils.h>
#include <squangle/mysql_client/AsyncHelpers.h>
#include <squangle/mysql_client/ClientPool.h>
#include "squangle/mysql_client/Connection.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/container-functions.h"
#include "hphp/runtime/ext/collections/ext_collections-map.h"
#include "hphp/runtime/ext/collections/ext_collections-vector.h"
#include "hphp/runtime/ext/mysql/ext_mysql.h"
#include "hphp/runtime/ext/mysql/mysql_common.h"
#include "hphp/runtime/vm/native-data.h"
#include "hphp/system/systemlib.h"
#include "hphp/util/logger.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
#define IMPLEMENT_GET_CLASS(cls)                                               \
  Class* cls::getClass() {                                                     \
    if (s_class == nullptr) {                                                  \
      s_class = Class::lookup(s_className.get());                          \
      assertx(s_class);                                                        \
    }                                                                          \
    return s_class;                                                            \
  }                                                                            \
typedef am::ClientPool<am::AsyncMysqlClient, am::AsyncMysqlClientFactory>
    AsyncMysqlClientPool;
namespace {
int HdfAsyncMysqlClientPoolSize = -1;
static Class* s_queryClass = nullptr;
const StaticString s_queryClassName("HH\\Lib\\SQL\\Query");
const Slot s_query_format_idx { 0 };
const Slot s_query_args_idx { 1 };
folly::Singleton<AsyncMysqlClientPool> clientPool([]() {
  if (HdfAsyncMysqlClientPoolSize == -1) {
    Logger::Error("AsyncMysql Config should have been initialized");
    HdfAsyncMysqlClientPoolSize = 2;
  }
  return new AsyncMysqlClientPool(
      std::make_unique<am::AsyncMysqlClientFactory>(),
      HdfAsyncMysqlClientPoolSize);
});
am::Query amquery_from_queryf(const StringData* pattern, const ArrayData* args);
am::QueryArgument queryarg_from_variant(const Variant& arg) {
  if (arg.isInteger()) {
    return arg.toInt64();
  }
  if (arg.isDouble()) {
    return arg.toDouble();
  }
  if (arg.isString()) {
    return static_cast<std::string>(arg.toString());
  }
  if (arg.isNull()) {
    return nullptr;
  }
  if (arg.isVec()) {
    const Array& vec = arg.asCArrRef();
    std::vector<am::QueryArgument> elems;
    elems.reserve(vec.size());
    for (ArrayIter listIter(vec); listIter; ++listIter) {
      const Variant& item = listIter.second();
      elems.push_back(queryarg_from_variant(item));
    }
    return elems;
  }
  if (arg.isObject()) {
    const Object& obj = arg.asCObjRef();
    if (obj->getVMClass() == s_queryClass) {
      const auto format =
        val(obj->propRvalAtOffset(s_query_format_idx).tv()).pstr;
      const auto args = val(obj->propRvalAtOffset(s_query_args_idx).tv()).parr;
      return amquery_from_queryf(format, args);
    }
    if (obj->isCollection() && isVectorCollection(obj->collectionType())) {
      std::vector<am::QueryArgument> elems;
      elems.reserve(collections::getSize(obj.get()));
      for (ArrayIter listIter(arg); listIter; ++listIter) {
        const Variant& item = listIter.second();
        elems.push_back(queryarg_from_variant(item));
      }
      return elems;
    }
  }
  SystemLib::throwInvalidArgumentExceptionObject(
    folly::sformat("Unable to serialize type '{}' for SQL",
                   getDataTypeString(arg.getType()))
  );
}
am::Query amquery_from_queryf(const StringData* pattern,
                              const ArrayData* args) {
  // Not directly calling argsv.toFollyDynamic() as that creates a folly
  // dynamic object, not list
  std::vector<am::QueryArgument> query_args;
  query_args.reserve(args->size());
  for (ArrayIter iter(args); iter; ++iter) {
    const Variant& arg = iter.second();
    query_args.push_back(queryarg_from_variant(arg));
  }
  return am::Query(pattern->toCppString(), query_args);
}
AsyncMysqlConnectionOptions* getConnectionOptions(const Object& opts) {
  if (!opts.instanceof(AsyncMysqlConnectionOptions::s_className)) {
    SystemLib::throwInvalidArgumentExceptionObject(
      folly::sformat(
        "Invalid argument. Expected {}, received {}",
        AsyncMysqlConnectionOptions::s_className,
        opts->getClassName().c_str()
      )
    );
  }
  return Native::data<AsyncMysqlConnectionOptions>(opts);
}
MySSLContextProvider* getSSLContextProvider(const Object& ctx) {
  if (!ctx.instanceof(MySSLContextProvider::s_className)) {
    SystemLib::throwInvalidArgumentExceptionObject(
      folly::sformat(
        "Invalid argument. Expected {}, received {}",
        MySSLContextProvider::s_className,
        ctx->getClassName().c_str()
      )
    );
  }
  return Native::data<MySSLContextProvider>(ctx);
}
}
static std::shared_ptr<am::AsyncMysqlClient> getClient() {
  return folly::Singleton<AsyncMysqlClientPool>::try_get()->getClient();
}
static std::vector<std::string> certLoggingImpl(
    X509* cert,
    const std::vector<std::string>& extNames,
    am::ConnectOperation& op,
    bool validated) {
  // Capture the certificare Common Name
  std::string cn =
    folly::ssl::OpenSSLCertUtils::getCommonName(*cert).value_or("none");
  // Capture cert extension values for the extensions requested by the
  // callback installer
  std::vector<std::string> extValues;
  std::vector<std::pair<std::string, std::string>> allExtensions =
      folly::ssl::OpenSSLCertUtils::getAllExtensions(*cert);
  for (const auto& extName: extNames) {
    const auto& i =
      std::find_if(allExtensions.begin(), allExtensions.end(),
                   [&extName]
                   (const std::pair<std::string, std::string>& element) {
                     return element.first == extName;
                   });
    if (i != allExtensions.end() && !i->second.empty()) {
      extValues.push_back(i->second);
    }
  }
  // Capture the cert ASN values
  std::vector<std::string> subjectAltNames =
      folly::ssl::OpenSSLCertUtils::getSubjectAltNames(*cert);
  // Update the operation with the cert parameters
  op.reportServerCertContent(
      cn,
      subjectAltNames,
      extValues,
      validated);
  return extValues;
}
static bool certValidationImpl(
    const std::vector<std::string>& expectedValues,
    const std::vector<std::string>& certValues) {
  // Search for the expected extension values
  for (const auto& value: expectedValues) {
    if (std::find(certValues.begin(), certValues.end(), value) != certValues.end()) {
      return true;
    }
  }
  // No expected extension values found
  return false;
}
static bool serverCertLoggingCallback(
    X509* server_cert,
    const void* context,
    folly::StringPiece& /*errMsg*/,
    const std::vector<std::string>& extNames) {
  am::ConnectOperation* op = reinterpret_cast<am::ConnectOperation*>(
          const_cast<void*>(context));
  CHECK(op);
  // Log the server cert content
  certLoggingImpl(server_cert, extNames, *op, false);
  return true;
}
static bool serverCertValidationCallback(
    X509* server_cert,
    const void* context,
    folly::StringPiece& /* errMsg */,
    const std::vector<std::string>& extNames,
    const std::vector<std::string>& extValues) {
  facebook::common::mysql_client::ConnectOperation* op =
      reinterpret_cast<facebook::common::mysql_client::ConnectOperation*>(
          const_cast<void*>(context));
  CHECK(op);
  // Log the server cert content
  auto certValues = certLoggingImpl(server_cert, extNames, *op, true);
  return certValidationImpl(extValues, certValues);
}
static facebook::common::mysql_client::CertValidatorCallback
generateCertValidationCallback(
    const std::string& serverCertExtNames,
    const std::string& extensionValues) {
  std::vector<std::string> extNames;
  folly::split(",", serverCertExtNames, extNames);
  if (extensionValues.empty()) {
    return [extNames = std::move(extNames)] (
          X509* server_cert, const void* context, folly::StringPiece& errMsg) {
      return serverCertLoggingCallback(server_cert, context, errMsg, extNames);
    };
  } else {
    std::vector<std::string> extValues;
    folly::split(",", extensionValues, extValues);
    return [extNames = std::move(extNames), extValues = std::move(extValues)] (
          X509* server_cert, const void* context, folly::StringPiece& errMsg) {
        return serverCertValidationCallback(
            server_cert, context, errMsg, extNames, extValues);
    };
  }
}
///////////////////////////////////////////////////////////////////////////
// AsyncMysqlClientStats
struct AsyncMysqlClientStats {
  AsyncMysqlClientStats& operator=(const AsyncMysqlClientStats& other) {
    m_values = other.m_values;
    return *this;
  }
  static Class* getClass();
  static Object newInstance(db::ClientPerfStats values) {
    Object obj{getClass()};
    auto* data = Native::data<AsyncMysqlClientStats>(obj);
    data->setPerfValues(std::move(values));
    return obj;
  }
  void setPerfValues(db::ClientPerfStats values) {
    m_values = std::move(values);
  }
  static Class* s_class;
  static const StaticString s_className;
  db::ClientPerfStats m_values;
};
Class* AsyncMysqlClientStats::s_class = nullptr;
const StaticString AsyncMysqlClientStats::s_className("AsyncMysqlClientStats");
IMPLEMENT_GET_CLASS(AsyncMysqlClientStats)
static double HHVM_METHOD(AsyncMysqlClientStats, ioEventLoopMicrosAvg) {
  auto* data = Native::data<AsyncMysqlClientStats>(this_);
  return data->m_values.ioEventLoopMicrosAvg;
}
static double HHVM_METHOD(AsyncMysqlClientStats, callbackDelayMicrosAvg) {
  auto* data = Native::data<AsyncMysqlClientStats>(this_);
  return data->m_values.callbackDelayMicrosAvg;
}
static double HHVM_METHOD(AsyncMysqlClientStats, ioThreadBusyMicrosAvg) {
  auto* data = Native::data<AsyncMysqlClientStats>(this_);
  return data->m_values.ioThreadBusyTime;
}
static double HHVM_METHOD(AsyncMysqlClientStats, ioThreadIdleMicrosAvg) {
  auto* data = Native::data<AsyncMysqlClientStats>(this_);
  return data->m_values.ioThreadIdleTime;
}
static int64_t HHVM_METHOD(AsyncMysqlClientStats, notificationQueueSize) {
  auto* data = Native::data<AsyncMysqlClientStats>(this_);
  return data->m_values.notificationQueueSize;
}
static String HHLibSQLQuery__toString__FOR_DEBUGGING_ONLY(
  ObjectData* this_,
  const Object& conn) {
  const auto format =
    val(this_->propRvalAtOffset(s_query_format_idx).tv()).pstr;
  const auto args = val(this_->propRvalAtOffset(s_query_args_idx).tv()).parr;
  const auto query = amquery_from_queryf(format, args);
  auto mysql = Native::data<AsyncMysqlConnection>(conn)
    ->m_conn
    ->mysql_for_testing_only()
    ->mysql();
  const auto str = query.render(mysql);
  return String(str.data(), str.length(), CopyString);
}
static String HHLibSQLQuery__toUnescapedString__FOR_DEBUGGING_ONLY__UNSAFE(
  ObjectData* this_) {
  const auto format =
    val(this_->propRvalAtOffset(s_query_format_idx).tv()).pstr;
  const auto args = val(this_->propRvalAtOffset(s_query_args_idx).tv()).parr;
  const auto query = amquery_from_queryf(format, args);
  const auto str = query.renderInsecure();
  return String(str.data(), str.length(), CopyString);
}
//////////////////////////////////////////////////////////////////////////////
// MySSLContextProvider
MySSLContextProvider::MySSLContextProvider(
    std::shared_ptr<am::SSLOptionsProviderBase> provider)
    : m_provider(provider) {}
Object MySSLContextProvider::newInstance(
    std::shared_ptr<am::SSLOptionsProviderBase> ssl_provider) {
  Object obj{getClass()};
  Native::data<MySSLContextProvider>(obj)->setSSLProvider(
      std::move(ssl_provider));
  return obj;
}
std::shared_ptr<am::SSLOptionsProviderBase>
MySSLContextProvider::getSSLProvider() {
  return m_provider;
}
void MySSLContextProvider::setSSLProvider(
    std::shared_ptr<am::SSLOptionsProviderBase> ssl_provider) {
  m_provider = std::move(ssl_provider);
}
static bool HHVM_METHOD(MySSLContextProvider, isValid) {
  auto* data = Native::data<MySSLContextProvider>(this_);
  return data->m_provider != nullptr;
}
static void HHVM_METHOD(MySSLContextProvider, allowSessionResumption, bool allow) {
  auto* data = Native::data<MySSLContextProvider>(this_);
  if (data->m_provider) {
    data->m_provider->allowSessionResumption(allow);
  }
}
Class* MySSLContextProvider::s_class = nullptr;
const StaticString MySSLContextProvider::s_className("MySSLContextProvider");
IMPLEMENT_GET_CLASS(MySSLContextProvider)
//////////////////////////////////////////////////////////////////////////////
// AsyncMysqlConnectionOptions
AsyncMysqlConnectionOptions::AsyncMysqlConnectionOptions() {
  // set default timeout
  auto default_timeout = mysqlExtension::ConnectTimeout * 1000;
  m_conn_opts.setTotalTimeout(am::Duration(default_timeout));
}
const am::ConnectionOptions&
AsyncMysqlConnectionOptions::getConnectionOptions() {
  return m_conn_opts;
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setConnectTimeout, int64_t timeout) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setTimeout(am::Duration(timeout));
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setConnectTcpTimeout, int64_t timeout) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setConnectTcpTimeout(am::Duration(timeout));
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setConnectAttempts, int32_t attempts) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setConnectAttempts(attempts);
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setTotalTimeout, int64_t timeout) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setTotalTimeout(am::Duration(timeout));
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setQueryTimeout, int64_t timeout) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setQueryTimeout(am::Duration(timeout));
}
static void HHVM_METHOD(
    AsyncMysqlConnectionOptions,
    setConnectionAttributes,
    const Array& attrs) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  IterateKV(attrs.get(), [&](TypedValue k, TypedValue v) {
    data->m_conn_opts.setAttribute(
      tvCastToString(k).toCppString(),
      tvCastToString(v).toCppString()
    );
  });
}
static void HHVM_METHOD(
    AsyncMysqlConnectionOptions,
    setSSLOptionsProvider,
    const Variant& sslContextProvider /* = null */) {
  if (sslContextProvider.isNull()) {
    return;
  }
  if (!sslContextProvider.isObject() ||
          !sslContextProvider.toObject()->instanceof
      (MySSLContextProvider::getClass())) {
    SystemLib::throwInvalidArgumentExceptionObject(
        "Wrong type: expected MySSLContextProvider argument");
    return;
  }
  auto* sslProvider =
      Native::data<MySSLContextProvider>(sslContextProvider.toObject());
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setSSLOptionsProvider(sslProvider->getSSLProvider());
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, setSniServerName, const String& sniServername) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.setSniServerName(static_cast<std::string>(sniServername));
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, enableResetConnBeforeClose) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.enableResetConnBeforeClose();
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, enableDelayedResetConn) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.enableDelayedResetConn();
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions, enableChangeUser) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  data->m_conn_opts.enableChangeUser();
}
static void
HHVM_METHOD(AsyncMysqlConnectionOptions,
            setServerCertValidation,
            const String& serverCertExtNames /* = "" */,
            const String& extensionValues /* = "" */) {
  auto* data = Native::data<AsyncMysqlConnectionOptions>(this_);
  // #ifdef FACEBOOK until Open Source squangle pin is updated - needed as of
  // Squangle 2020-10-21
  #ifdef FACEBOOK
  data->m_conn_opts.setCertValidationCallback(
      generateCertValidationCallback(
          std::string(serverCertExtNames), std::string(extensionValues)),
      nullptr,
      true);
  #endif
}
static int64_t getQueryTimeout(int64_t timeout_micros) {
  if (timeout_micros < 0) {
    return mysqlExtension::ReadTimeout * 1000;
  } else {
    return timeout_micros;
  }
}
static std::vector<am::Query> transformQueries(const Array& queries) {
  std::vector<am::Query> queries_vec;
  queries_vec.reserve(queries.size());
  for (ArrayIter iter(queries); iter; ++iter) {
    queries_vec.emplace_back(am::Query::unsafe(
        static_cast<std::string>(iter.second().toString().data())));
  }
  return queries_vec;
}
Class* AsyncMysqlConnectionOptions::s_class = nullptr;
const StaticString AsyncMysqlConnectionOptions::s_className(
    "AsyncMysqlConnectionOptions");
IMPLEMENT_GET_CLASS(AsyncMysqlConnectionOptions)
//////////////////////////////////////////////////////////////////////////////
// AsyncMysqlClient
void HHVM_STATIC_METHOD(
    AsyncMysqlClient,
    setPoolsConnectionLimit,
    int64_t limit) {
  getClient()->setPoolsConnectionLimit(limit);
}
static AsyncMysqlConnection::AttributeMap transformAttributes(
    const Array& attributes) {
  AsyncMysqlConnection::AttributeMap cppAttributes;
  IterateKV(attributes.get(), [&](TypedValue k, TypedValue v) {
    cppAttributes[tvCastToString(k).toCppString()] =
        tvCastToString(v).toCppString();
  });
  return cppAttributes;
}
static Object newAsyncMysqlConnectEvent(
    std::shared_ptr<am::ConnectOperation> op,
    std::shared_ptr<am::AsyncMysqlClient> clientPtr) {
  // Set connection context to store the cert parameters
  if (op->getCertValidationCallback() &&
      op->getConnectionContext() == nullptr) {
    auto context = std::make_shared<db::ConnectionContextBase>();
    op->setConnectionContext(std::move(context));
  }
  auto event = new AsyncMysqlConnectEvent(op);
  try {
    op->setCallback([event, clientPtr](am::ConnectOperation& /* op */) {
      // Get current stats
      event->setClientStats(clientPtr->collectPerfStats());
      event->opFinished();
    });
    op->run();
    return Object{event->getWaitHandle()};
  } catch (...) {
    assertx(false);
    event->abandon();
    return Object{};
  }
}
static Object newAsyncMysqlConnectAndQueryEvent(
    std::shared_ptr<am::ConnectOperation> connectOp,
    std::shared_ptr<am::AsyncMysqlClient> clientPtr,
    const Variant& queries,
    const Array& queryAttributes,
    bool connReusable) {
  if (connectOp->getCertValidationCallback() &&
      connectOp->getConnectionContext() == nullptr) {
    auto context = std::make_shared<db::ConnectionContextBase>();
    connectOp->setConnectionContext(std::move(context));
  }
  auto event = new AsyncMysqlConnectAndMultiQueryEvent(connectOp);
  auto queries_as_array = queries.isArray()
    ? queries.asCArrRef()
    // In this codepath, queries must be a Hack collection
    : collections::toArray(queries.getObjectData());
  auto transformedQueries = transformQueries(queries_as_array);
  auto transformedAttributes = transformAttributes(queryAttributes);
  try {
    connectOp->setCallback(
            [clientPtr,
            connReusable,
            event,
            transformedQueries,
            transformedAttributes]
        (am::ConnectOperation& op) mutable {
        if (!op.ok()) {
          // early exit must collect stats
          event->setClientStats(clientPtr->collectPerfStats());
          event->opFinished();
          return;
        }
        auto query_op = am::Connection::beginMultiQuery(
          op.releaseConnection(), std::move(transformedQueries));
        query_op->setAttributes(transformedAttributes);
        event->setQueryOp(query_op);
        try {
          am::MultiQueryAppenderCallback appender_callback = [event, clientPtr, connReusable](
            am::MultiQueryOperation& op,
            std::vector<am::QueryResult> query_results,
            am::QueryCallbackReason reason) {
            DCHECK(reason != am::QueryCallbackReason::RowsFetched);
            DCHECK(reason != am::QueryCallbackReason::QueryBoundary);
            if (!op.done()) {
              Logger::Error("Invalid state! Callback called as finished "
                            "but operation didn't finish");
            }
            op.setQueryResults(std::move(query_results));
            event->setClientStats(clientPtr->collectPerfStats());
            if (connReusable) {
              auto conn = op.releaseConnection();
              conn->setReusable(true);
            }
            event->opFinished();
          };
          query_op->setCallback(am::resultAppender(appender_callback));
          query_op->run();
        } catch (...) {
          Logger::Error("Unexpected exception while executing Query");
          // If this assertion doesn't always hold, then we can add an m_error
          // flag to AsyncMysqlConnectAndMultiQueryEvent and set it here. But
          // my guess is that if query_op throws during its execution, then it
          // will have a non-ok result code.
          always_assert(!query_op->ok());
          event->opFinished();
        }
    });
    connectOp->run();
    return Object{event->getWaitHandle()};
  } catch (...) {
    Logger::Error("Unexpected exception while creating Connection");
    event->abandon();
    return Object{};
  }
}
Object HHVM_STATIC_METHOD(
    AsyncMysqlClient,
    connect,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    int64_t timeout_micros /* = -1 */,
    const Variant& sslContextProvider /* = null */,
    int64_t tcp_timeout_micros /* = 0 */,
    const String& sni_server_name /* = "" */,
    const String& serverCertExtNames /* = "" */,
    const String& serverCertExtValues /* = "" */) {
  am::ConnectionKey key(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password));
  auto op = getClient()->beginConnection(key);
  if (!sslContextProvider.isNull()) {
    auto* mysslContextProvider = getSSLContextProvider(
            sslContextProvider.toObject());
    op->setSSLOptionsProvider(mysslContextProvider->getSSLProvider());
  }
  if (timeout_micros < 0) {
    timeout_micros = mysqlExtension::ConnectTimeout * 1000;
  }
  if (timeout_micros > 0) {
    op->setTimeout(am::Duration(timeout_micros));
  }
  // If tcp_timeout_micros is <= 0, skip setting the timeout
  if (tcp_timeout_micros > 0) {
    op->setTcpTimeout(am::Duration(tcp_timeout_micros));
  }
  // If ssl sni name is not empty, set it
  if (!sni_server_name.empty()) {
    op->setSniServerName(static_cast<std::string>(sni_server_name));
  }
  if (!serverCertExtNames.empty()) {
    op->setCertValidationCallback(
        generateCertValidationCallback(
            std::string(serverCertExtNames), std::string(serverCertExtValues)),
        nullptr,
        true);
  }
  return newAsyncMysqlConnectEvent(std::move(op), getClient());
}
Object HHVM_STATIC_METHOD(
    AsyncMysqlClient,
    connectWithOpts,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    const Object& asyncMysqlConnOpts) {
  am::ConnectionKey key(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password));
  auto op = getClient()->beginConnection(key);
  auto* obj = getConnectionOptions(asyncMysqlConnOpts);
  const auto& connOpts = obj->getConnectionOptions();
  op->setConnectionOptions(connOpts);
  return newAsyncMysqlConnectEvent(std::move(op), getClient());
}
Object HHVM_STATIC_METHOD(
    AsyncMysqlClient,
    connectAndQuery,
    const Variant& queries,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    const Object& asyncMysqlConnOpts,
    const Array& queryAttributes) {
  if (UNLIKELY(!isContainer(queries))) {
    raise_warning("AsyncMysqlClient::connectAndQuery() expects parameter 1 to "
                  "be array, %s given",
                  getDataTypeString(queries.getType()).c_str());
    return Object{};
  }
  am::ConnectionKey key(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password));
  auto clientPtr = getClient();
  auto connectOp = clientPtr->beginConnection(key);
  auto* obj = getConnectionOptions(asyncMysqlConnOpts);
  const auto& connOpts = obj->getConnectionOptions();
  connectOp->setConnectionOptions(connOpts);
  return newAsyncMysqlConnectAndQueryEvent(
    std::move(connectOp),
    clientPtr,
    queries,
    queryAttributes,
    false /* connReusable*/);
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlConnectionPool
const StaticString AsyncMysqlConnectionPool::s_className(
  "AsyncMysqlConnectionPool");
// `connection_limit` - Defines the limit of opened connections for each set of
// User, Database, Host, etc
// `total_connection_limit` - Defines the total limit of opened connection as a
// whole
// `idle_timeout_micros` - Sets the maximum idle time in microseconds a
// connection can be left in the pool without being killed by the pool
// `age_timeout_micros` - Sets the maximum age (means the time since started) of
// a connection, the pool will then kill this connection when reaches that limit
// `expiration_policy` - We offer 2 policies for the expiration of a
// connection: `IdleTime` and `Age`, in the Idle policy a connection will only
// die after some time being idle; in Age policy we extend the idle one to kill
// also by age
//
const StaticString s_per_key_connection_limit("per_key_connection_limit"),
    s_pool_connection_limit("pool_connection_limit"),
    s_idle_timeout_micros("idle_timeout_micros"),
    s_age_timeout_micros("age_timeout_micros"),
    s_expiration_policy("expiration_policy");
static void
HHVM_METHOD(AsyncMysqlConnectionPool, __construct, const Array& options) {
  auto* data = Native::data<AsyncMysqlConnectionPool>(this_);
  am::PoolOptions pool_options;
  if (options.exists(s_per_key_connection_limit)) {
    pool_options.setPerKeyLimit(options[s_per_key_connection_limit].toInt32());
  }
  if (options.exists(s_pool_connection_limit)) {
    pool_options.setPoolLimit(options[s_pool_connection_limit].toInt32());
  }
  if (options.exists(s_idle_timeout_micros)) {
    pool_options.setIdleTimeout(
        am::Duration(options[s_idle_timeout_micros].toInt64()));
  }
  if (options.exists(s_age_timeout_micros)) {
    pool_options.setAgeTimeout(
        am::Duration(options[s_age_timeout_micros].toInt64()));
  }
  if (options.exists(s_expiration_policy)) {
    static StaticString s_IdleTime{"IdleTime"};
    pool_options.setExpPolicy(options[s_expiration_policy].toString() ==
                                      s_IdleTime
                                  ? am::ExpirationPolicy::IdleTime
                                  : am::ExpirationPolicy::Age);
  }
  data->m_async_pool =
      am::AsyncConnectionPool::makePool(getClient(), pool_options);
}
void AsyncMysqlConnectionPool::sweep() {
  if (m_async_pool) {
    m_async_pool->shutdown();
    m_async_pool.reset();
  }
}
// `created_pool_connections` - Number of connections created by the pool
// `destroyed_pool_connections` - Number of connections destroyed by the pool,
//  be careful with this number, it will only be equal to the above when all
//  created connections have been close. This may not be true by the end of
//  a request.
// `connections_requested` - This number helps with comparing how many
//  connection would have been made if the there were no pooling.
// `pool_hits` - Counts the number of times a request for connection went to
//  the pool and it had a connection ready in cache
// `pool_misses` - Counts the number of times a we needed a connection and
//  none was ready to return
const StaticString s_created_pool_connections("created_pool_connections"),
    s_destroyed_pool_connections("destroyed_pool_connections"),
    s_connections_requested("connections_requested"), s_pool_hits("pool_hits"),
    s_pool_misses("pool_misses");
static Array HHVM_METHOD(AsyncMysqlConnectionPool, getPoolStats) {
  auto* data = Native::data<AsyncMysqlConnectionPool>(this_);
  auto* pool_stats = data->m_async_pool->stats();
  Array ret = make_dict_array(
      s_created_pool_connections,
      pool_stats->numCreatedPoolConnections(),
      s_destroyed_pool_connections,
      pool_stats->numDestroyedPoolConnections(),
      s_connections_requested,
      pool_stats->numConnectionsRequested(),
      s_pool_hits,
      pool_stats->numPoolHits(),
      s_pool_misses,
      pool_stats->numPoolMisses());
  return ret;
}
static Object HHVM_METHOD(
    AsyncMysqlConnectionPool,
    connect,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    int64_t timeout_micros,
    const String& extra_key,
    const Variant& sslContextProvider,
    int64_t tcp_timeout_micros,
    const String& sni_server_name,
    const String& serverCertExtNames,
    const String& serverCertExtValues) {
  auto* data = Native::data<AsyncMysqlConnectionPool>(this_);
  auto op = data->m_async_pool->beginConnection(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password),
      static_cast<std::string>(extra_key));
  if (!sslContextProvider.isNull()) {
    auto* mysslContextProvider = getSSLContextProvider(
            sslContextProvider.toObject());
    op->setSSLOptionsProvider(mysslContextProvider->getSSLProvider());
  }
  if (timeout_micros < 0) {
    timeout_micros = mysqlExtension::ConnectTimeout * 1000;
  }
  if (timeout_micros > 0) {
    op->setTimeout(am::Duration(timeout_micros));
  }
  // If tcp_timeout_micros is <= 0, skip setting the timeout
  if (tcp_timeout_micros > 0) {
    op->setTcpTimeout(am::Duration(tcp_timeout_micros));
  }
  // If ssl sni name is not empty, set it
  if (!sni_server_name.empty()) {
    op->setSniServerName(static_cast<std::string>(sni_server_name));
  }
  if (!serverCertExtNames.empty()) {
    op->setCertValidationCallback(
        generateCertValidationCallback(
            std::string(serverCertExtNames), std::string(serverCertExtValues)),
        nullptr,
        true);
  }
  return newAsyncMysqlConnectEvent(
      std::move(op), data->m_async_pool->getMysqlClient());
}
static Object HHVM_METHOD(
    AsyncMysqlConnectionPool,
    connectWithOpts,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    const Object& asyncMysqlConnOpts,
    const String& extra_key) {
  auto* data = Native::data<AsyncMysqlConnectionPool>(this_);
  auto op = data->m_async_pool->beginConnection(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password),
      static_cast<std::string>(extra_key));
  auto* obj = getConnectionOptions(asyncMysqlConnOpts);
  const auto& connOpts = obj->getConnectionOptions();
  op->setConnectionOptions(connOpts);
  return newAsyncMysqlConnectEvent(
      std::move(op), data->m_async_pool->getMysqlClient());
}
static Object HHVM_METHOD(
    AsyncMysqlConnectionPool,
    connectAndQuery,
    const Variant& queries,
    const String& host,
    int port,
    const String& dbname,
    const String& user,
    const String& password,
    const Object& asyncMysqlConnOpts,
    const String& extra_key,
    const Array& queryAttributes) {
  if (UNLIKELY(!isContainer(queries))) {
    raise_warning("AsyncMysqlConnectionPool::connectAndQuery() expects parameter 1 to "
                  "be array, %s given",
                  getDataTypeString(queries.getType()).c_str());
    return Object{};
  }
  auto* data = Native::data<AsyncMysqlConnectionPool>(this_);
  auto connectOp = data->m_async_pool->beginConnection(
      static_cast<std::string>(host),
      port,
      static_cast<std::string>(dbname),
      static_cast<std::string>(user),
      static_cast<std::string>(password),
      static_cast<std::string>(extra_key));
  auto* obj = getConnectionOptions(asyncMysqlConnOpts);
  const auto& connOpts = obj->getConnectionOptions();
  connectOp->setConnectionOptions(connOpts);
  return newAsyncMysqlConnectAndQueryEvent(
    std::move(connectOp),
    data->m_async_pool->getMysqlClient(),
    queries,
    queryAttributes,
    true /* connReusable*/);
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlConnection
Class* AsyncMysqlConnection::s_class = nullptr;
const StaticString AsyncMysqlConnection::s_className("AsyncMysqlConnection");
IMPLEMENT_GET_CLASS(AsyncMysqlConnection)
Object AsyncMysqlConnection::newInstance(
    std::unique_ptr<am::Connection> conn,
    std::shared_ptr<am::ConnectOperation> conn_op,
    db::ClientPerfStats clientStats) {
  Object ret{getClass()};
  auto* retPtr = Native::data<AsyncMysqlConnection>(ret);
  retPtr->setConnection(std::move(conn));
  retPtr->setConnectOperation(std::move(conn_op));
  retPtr->setClientStats(std::move(clientStats));
  return ret;
}
AsyncMysqlConnection::AsyncMysqlConnection() : m_port(0), m_closed(false) {}
void AsyncMysqlConnection::sweep() {
  m_conn.reset();
}
void AsyncMysqlConnection::setConnection(std::unique_ptr<am::Connection> conn) {
  m_conn = std::move(conn);
  m_host = String(m_conn->host(), CopyString);
  m_port = m_conn->port();
}
void AsyncMysqlConnection::setConnectOperation(
    std::shared_ptr<am::ConnectOperation> op) {
  m_op = std::move(op);
}
void AsyncMysqlConnection::setClientStats(db::ClientPerfStats clientStats) {
  m_clientStats = std::move(clientStats);
}
void AsyncMysqlConnection::verifyValidConnection() {
  if (UNLIKELY(!m_conn || !m_conn->ok())) {
    if (m_closed) {
      SystemLib::throwInvalidArgumentExceptionObject(
        "attempt to invoke method on a closed connection");
    } else if (m_conn && !m_conn->ok()) {
      SystemLib::throwInvalidArgumentExceptionObject(
        "attempt to invoke method on an invalid connection");
    } else {
      SystemLib::throwInvalidArgumentExceptionObject(
        "attempt to invoke method on a busy connection");
    }
  }
}
bool AsyncMysqlConnection::isValidConnection() {
  // When a query timeout happens, the connection is invalid and SQuangLe
  // layer closes it for precaution.
  return m_conn && m_conn->ok() && !m_closed;
}
Object AsyncMysqlConnection::query(
    ObjectData* this_,
    am::Query query,
    int64_t timeout_micros /* = -1 */,
    const AttributeMap& queryAttributes /*  = AttributeMap() */) {
  verifyValidConnection();
  auto* clientPtr = static_cast<am::AsyncMysqlClient*>(m_conn->client());
  auto op = am::Connection::beginQuery(std::move(m_conn), query);
  op->setAttributes(queryAttributes);
  op->setTimeout(am::Duration(getQueryTimeout(timeout_micros)));
  auto event = new AsyncMysqlQueryEvent(this_, op);
  try {
    am::QueryAppenderCallback appender_callback = [event, clientPtr](
        am::QueryOperation& op,
        am::QueryResult query_result,
        am::QueryCallbackReason reason) {
      DCHECK(reason != am::QueryCallbackReason::RowsFetched);
      if (!op.done()) {
        Logger::Error("Invalid state! Callback called as finished "
                      "but operation didn't finish");
      }
      op.setQueryResult(std::move(query_result));
      event->setClientStats(clientPtr->collectPerfStats());
      event->opFinished();
    };
    op->setCallback(am::resultAppender(appender_callback));
    op->run();
    return Object{event->getWaitHandle()};
  }
  catch (...) {
    Logger::Error("Unexpected exception while beginning ConnectOperation");
    assertx(false);
    event->abandon();
    return Object{};
  }
}
static Object HHVM_METHOD(
    AsyncMysqlConnection,
    query,
    const String& query,
    int64_t timeout_micros /* = -1 */,
    const Array& queryAttributes) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  return data->query(
      this_,
      am::Query::unsafe(static_cast<std::string>(query)),
      timeout_micros,
      transformAttributes(queryAttributes));
}
static Object HHVM_METHOD(
    AsyncMysqlConnection,
    queryf,
    const String& pattern,
    const Array& args) {
  const auto query = amquery_from_queryf(pattern.get(), args.get());
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  return data->query(this_, query);
}
static Object HHVM_METHOD(
    AsyncMysqlConnection,
    queryAsync,
    const Object& query) {
  const auto format =
    val(query->propRvalAtOffset(s_query_format_idx).tv()).pstr;
  const auto args = val(query->propRvalAtOffset(s_query_args_idx).tv()).parr;
  return Native::data<AsyncMysqlConnection>(this_)->query(
    this_, amquery_from_queryf(format, args));
}
static Object HHVM_METHOD(
    AsyncMysqlConnection,
    multiQuery,
    const Variant& queries,
    int64_t timeout_micros /* = -1 */,
    const Array& queryAttributes) {
  if (UNLIKELY(!isContainer(queries))) {
    raise_warning("AsyncMysqlConnection::multiQuery() expects parameter 1 to "
                  "be array, %s given",
                  getDataTypeString(queries.getType()).c_str());
    return Object{};
  }
  auto queries_as_array = queries.isArray()
    ? queries.asCArrRef()
    // In this codepath, queries must be a Hack collection
    : collections::toArray(queries.getObjectData());
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  data->verifyValidConnection();
  auto* clientPtr = static_cast<am::AsyncMysqlClient*>(data->m_conn->client());
  auto op = am::Connection::beginMultiQuery(std::move(data->m_conn),
                                            transformQueries(queries_as_array));
  op->setAttributes(transformAttributes(queryAttributes));
  op->setTimeout(am::Duration(getQueryTimeout(timeout_micros)));
  auto event = new AsyncMysqlMultiQueryEvent(this_, op);
  try {
    am::MultiQueryAppenderCallback appender_callback = [event, clientPtr](
        am::MultiQueryOperation& op,
        std::vector<am::QueryResult> query_results,
        am::QueryCallbackReason reason) {
      DCHECK(reason != am::QueryCallbackReason::RowsFetched);
      DCHECK(reason != am::QueryCallbackReason::QueryBoundary);
      if (!op.done()) {
        Logger::Error("Invalid state! Callback called as finished "
                      "but operation didn't finish");
      }
      op.setQueryResults(std::move(query_results));
      event->setClientStats(clientPtr->collectPerfStats());
      event->opFinished();
    };
    op->setCallback(am::resultAppender(appender_callback));
    op->run();
    return Object{event->getWaitHandle()};
  }
  catch (...) {
    assertx(false);
    event->abandon();
    return Object{};
  }
}
static bool HHVM_METHOD(AsyncMysqlConnection, isValid) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  return data->isValidConnection();
}
static String
HHVM_METHOD(AsyncMysqlConnection, escapeString, const String& input) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  data->verifyValidConnection();
  String ret = data->m_conn->escapeString(input.data());
  return ret;
}
static String HHVM_METHOD(AsyncMysqlConnection, serverInfo) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  String ret = "";
  if (data->isValidConnection()) {
    ret = data->m_conn->serverInfo();
  } else {
    Logger::Error("Accessing closed connection");
  }
  return ret;
}
static bool HHVM_METHOD(AsyncMysqlConnection, sslSessionReused) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  // Will throw PHP catchable Exception in case the connection isn't valid.
  data->verifyValidConnection();
  return data->m_conn->sslSessionReused();
}
static bool HHVM_METHOD(AsyncMysqlConnection, isSSL) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  // Will throw PHP catchable Exception in case the connection isn't valid.
  data->verifyValidConnection();
  return data->m_conn->isSSL();
}
static int HHVM_METHOD(AsyncMysqlConnection, warningCount) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  int count = 0;
  if (data->isValidConnection()) {
    count = data->m_conn->warningCount();
  } else {
    Logger::Error("Accessing closed connection");
  }
  return count;
}
static String HHVM_METHOD(AsyncMysqlConnection, host) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  return data->m_host;
}
static int HHVM_METHOD(AsyncMysqlConnection, port) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  return data->m_port;
}
static void HHVM_METHOD(AsyncMysqlConnection, setReusable, bool reusable) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  if (data->m_conn) {
    data->m_conn->setReusable(reusable);
  } else {
    Logger::Error("Accessing closed connection");
  }
}
static bool HHVM_METHOD(AsyncMysqlConnection, isReusable) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  if (data->m_conn) {
    return data->m_conn->isReusable();
  } else {
    Logger::Error("Accessing closed connection");
  }
  return false;
}
static Variant HHVM_METHOD(AsyncMysqlConnection, connectResult) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  if (data->m_op) {
    return AsyncMysqlConnectResult::newInstance(data->m_op,
                                                data->m_clientStats);
  } else {
    Logger::Error("ConnectResult only available when Connection created by "
                  "AsyncMysqlClient");
  }
  return false;
}
static double HHVM_METHOD(AsyncMysqlConnection, lastActivityTime) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  if (data->m_conn) {
    auto d = std::chrono::duration_cast<std::chrono::microseconds>(
        data->m_op->startTime().time_since_epoch());
    return d.count() / 1000.0 / 1000.0;
  } else {
    Logger::Error("Accessing closed connection");
  }
  return false;
}
static void HHVM_METHOD(AsyncMysqlConnection, close) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  data->m_conn.reset();
  data->m_closed = true;
}
static Variant HHVM_METHOD(AsyncMysqlConnection, releaseConnection) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  data->verifyValidConnection();
  auto raw_connection = data->m_conn->stealMysql();
  auto host = data->m_conn->host();
  auto port = data->m_conn->port();
  auto username = data->m_conn->user();
  auto database = data->m_conn->database();
  data->m_conn.reset();
  data->m_closed = true;
  return Variant(
    req::make<MySQLResource>(
      std::make_shared<MySQL>(host.c_str(),
                              port,
                              username.c_str(),
                              "",
                              database.c_str(),
                              raw_connection)));
}
static String HHVM_METHOD(AsyncMysqlConnection, getSslCertCn) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  const auto* context = data->m_conn->getConnectionContext();
  if (context && context->sslCertCn.hasValue()) {
    return context->sslCertCn.value();
  } else {
    return String();
  }
}
static Object HHVM_METHOD(AsyncMysqlConnection, getSslCertSan) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  auto ret = req::make<c_Vector>();
  const auto* context = data->m_conn->getConnectionContext();
  if (context && context->sslCertSan.hasValue()) {
    for (const auto& san: context->sslCertSan.value()) {
      ret->add(Variant(san));
    }
  }
  return Object(std::move(ret));
}
static Object HHVM_METHOD(AsyncMysqlConnection, getSslCertExtensions) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  auto ret = req::make<c_Vector>();
  const auto* context = data->m_conn->getConnectionContext();
  if (context && context->sslCertIdentities.hasValue()) {
    for (const auto& id: context->sslCertIdentities.value()) {
      ret->add(Variant(id));
    }
  }
  return Object(std::move(ret));
}
static bool HHVM_METHOD(AsyncMysqlConnection, isSslCertValidationEnforced) {
  auto* data = Native::data<AsyncMysqlConnection>(this_);
  const auto* context = data->m_conn->getConnectionContext();
  return context && context->isServerCertValidated;
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlResult
int64_t AsyncMysqlResult::elapsedMicros() {
  return op()->elapsed().count();
}
double AsyncMysqlResult::startTime() {
  am::Duration d = std::chrono::duration_cast<std::chrono::microseconds>(
      op()->startTime().time_since_epoch());
  return d.count() / 1000.0 / 1000.0;
}
double AsyncMysqlResult::endTime() {
  am::Duration d = std::chrono::duration_cast<std::chrono::microseconds>(
      op()->endTime().time_since_epoch());
  return d.count() / 1000.0 / 1000.0;
}
am::Operation* AsyncMysqlResult::op() {
  if (m_op.get() == nullptr) {
    // m_op is null if this object is directly created. It is possible if
    // a derived class is defined that does not call this class' constructor.
    SystemLib::throwInvalidOperationExceptionObject(
        "AsyncMysqlErrorResult object is not properly initialized.");
  }
  return m_op.get();
}
Object AsyncMysqlResult::clientStats() {
  return AsyncMysqlClientStats::newInstance(m_clientStats);
}
//
// Depending on the scenario finding the link to connection context
// associated with the operation can be tricky. If the operation
// completed successfully and there is a valid connection associated
// with the operation, then we should search for the connection context
// linked to the connection associated with the operation. In case of
// failed conneciton (failed ConnectOperation, failed ConnectPoolOperation)
// there is no connection associated with the operation at the end because
// we failed to connect. In this case checking for the connection context
// directly linked to the operation is our best bet.
//
static const db::ConnectionContextBase*
connectionContextFromOperation(const am::Operation* operation) {
  const db::ConnectionContextBase* context = nullptr;
  if (auto* connection = operation->connection()) {
    context = connection->getConnectionContext();
  }
  if (!context) {
    auto* connectOp = dynamic_cast<const am::ConnectOperation*>(operation);
    if (connectOp) {
      context = connectOp->getConnectionContext();
    }
  }
  return context;
}
static String HHVM_METHOD(AsyncMysqlResult, getSslCertCn) {
  auto* data = Native::data<AsyncMysqlResult>(this_);
  if (auto* op = data->m_op.get()) {
    const auto* context = connectionContextFromOperation(op);
    if (context && context->sslCertCn.hasValue()) {
      return context->sslCertCn.value();
    }
  }
  return String();
}
static Object HHVM_METHOD(AsyncMysqlResult, getSslCertSan) {
  auto* data = Native::data<AsyncMysqlResult>(this_);
  auto ret = req::make<c_Vector>();
  if (auto* op = data->m_op.get() ) {
    const auto* context = connectionContextFromOperation(op);
    if (context && context->sslCertSan.hasValue()) {
      for (const auto& san: context->sslCertSan.value()) {
        ret->add(Variant(san));
      }
    }
  }
  return Object(std::move(ret));
}
static Object HHVM_METHOD(AsyncMysqlResult, getSslCertExtensions) {
  auto* data = Native::data<AsyncMysqlResult>(this_);
  auto ret = req::make<c_Vector>();
  if (auto* op = data->m_op.get()) {
    const auto* context = connectionContextFromOperation(op);
    if (context && context->sslCertIdentities.hasValue()) {
      for (const auto& id: context->sslCertIdentities.value()) {
        ret->add(Variant(id));
      }
    }
  }
  return Object(std::move(ret));
}
static bool HHVM_METHOD(AsyncMysqlResult, isSslCertValidationEnforced) {
  auto* data = Native::data<AsyncMysqlResult>(this_);
  if (auto* op = data->m_op.get()) {
    const auto* context = connectionContextFromOperation(op);
    return context && context->isServerCertValidated;
  }
  return false;
}
#define DEFINE_PROXY_METHOD(cls, method, type) \
  type HHVM_METHOD(cls, method) { return Native::data<cls>(this_)->method(); }
#define EXTENDS_ASYNC_MYSQL_RESULT(cls)            \
  DEFINE_PROXY_METHOD(cls, elapsedMicros, int64_t) \
  DEFINE_PROXY_METHOD(cls, startTime, double)      \
  DEFINE_PROXY_METHOD(cls, endTime, double)        \
  DEFINE_PROXY_METHOD(cls, clientStats, Object)
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlConnectResult
Class* AsyncMysqlConnectResult::s_class = nullptr;
const StaticString AsyncMysqlConnectResult::s_className(
    "AsyncMysqlConnectResult");
IMPLEMENT_GET_CLASS(AsyncMysqlConnectResult)
Object AsyncMysqlConnectResult::newInstance(std::shared_ptr<am::Operation> op,
                                            db::ClientPerfStats values) {
  Object ret{getClass()};
  Native::data<AsyncMysqlConnectResult>(ret)
      ->create(std::move(op), std::move(values));
  return ret;
}
EXTENDS_ASYNC_MYSQL_RESULT(AsyncMysqlConnectResult)
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlErrorResult
Class* AsyncMysqlErrorResult::s_class = nullptr;
const StaticString AsyncMysqlErrorResult::s_className("AsyncMysqlErrorResult");
IMPLEMENT_GET_CLASS(AsyncMysqlErrorResult)
Object AsyncMysqlErrorResult::newInstance(std::shared_ptr<am::Operation> op,
                                          db::ClientPerfStats values) {
  Object ret{getClass()};
  Native::data<AsyncMysqlErrorResult>(ret)
      ->create(std::move(op), std::move(values));
  return ret;
}
EXTENDS_ASYNC_MYSQL_RESULT(AsyncMysqlErrorResult)
static int HHVM_METHOD(AsyncMysqlErrorResult, mysql_errno) {
  auto* data = Native::data<AsyncMysqlErrorResult>(this_);
  return data->m_op->mysql_errno();
}
static String HHVM_METHOD(AsyncMysqlErrorResult, mysql_error) {
  auto* data = Native::data<AsyncMysqlErrorResult>(this_);
  return data->m_op->mysql_error();
}
static String HHVM_METHOD(AsyncMysqlErrorResult, mysql_normalize_error) {
  auto* data = Native::data<AsyncMysqlErrorResult>(this_);
  return data->m_op->mysql_normalize_error();
}
static String HHVM_METHOD(AsyncMysqlErrorResult, failureType) {
  auto* data = Native::data<AsyncMysqlErrorResult>(this_);
  return data->m_op->resultString().toString();
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlQueryErrorResult
Class* AsyncMysqlQueryErrorResult::s_class = nullptr;
const StaticString AsyncMysqlQueryErrorResult::s_className(
  "AsyncMysqlQueryErrorResult");
IMPLEMENT_GET_CLASS(AsyncMysqlQueryErrorResult)
Object AsyncMysqlQueryErrorResult::newInstance(
    std::shared_ptr<am::Operation> op,
    db::ClientPerfStats values,
    req::ptr<c_Vector> results) {
  Object ret{getClass()};
  Native::data<AsyncMysqlQueryErrorResult>(ret)
      ->create(std::move(op), std::move(values), results);
  return ret;
}
AsyncMysqlQueryErrorResult::AsyncMysqlQueryErrorResult() {
  static_assert(offsetof(AsyncMysqlQueryErrorResult, m_parent) +
    sizeof(AsyncMysqlErrorResult) == sizeof(AsyncMysqlQueryErrorResult),
    "m_parent must be the last member of AsyncMysqlQueryErrorResult");
}
void AsyncMysqlQueryErrorResult::sweep() {
  m_parent.sweep();
}
void AsyncMysqlQueryErrorResult::create(std::shared_ptr<am::Operation> op,
                                        db::ClientPerfStats stats,
                                        req::ptr<c_Vector> results) {
  m_parent.create(std::move(op), std::move(stats));
  m_query_results = results;
}
static int HHVM_METHOD(AsyncMysqlQueryErrorResult, numSuccessfulQueries) {
  auto* data = Native::data<AsyncMysqlQueryErrorResult>(this_);
  return std::dynamic_pointer_cast<am::FetchOperation>(data->m_parent.m_op)
      ->numQueriesExecuted();
}
static Object HHVM_METHOD(AsyncMysqlQueryErrorResult, getSuccessfulResults) {
  auto* data = Native::data<AsyncMysqlQueryErrorResult>(this_);
  return Object(data->m_query_results);
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlQueryResult
Class* AsyncMysqlQueryResult::s_class = nullptr;
const StaticString AsyncMysqlQueryResult::s_className("AsyncMysqlQueryResult");
IMPLEMENT_GET_CLASS(AsyncMysqlQueryResult)
void AsyncMysqlQueryResult::sweep() {
  m_op.reset();
  m_query_result.reset();
}
Object AsyncMysqlQueryResult::newInstance(std::shared_ptr<am::Operation> op,
                                          db::ClientPerfStats stats,
                                          am::QueryResult query_result,
                                          bool noIndexUsed) {
  Object ret{ getClass() };
  Native::data<AsyncMysqlQueryResult>(ret)->create(
    std::move(op), std::move(stats), std::move(query_result), noIndexUsed);
  return ret;
}
void AsyncMysqlQueryResult::create(std::shared_ptr<am::Operation> op,
                                   db::ClientPerfStats stats,
                                   am::QueryResult query_result,
                                   bool noIndexUsed) {
  AsyncMysqlResult::create(std::move(op), std::move(stats));
  m_query_result = std::make_unique<am::QueryResult>(std::move(query_result));
  m_no_index_used = noIndexUsed;
  m_field_index = req::make_shared<FieldIndex>(m_query_result->getRowFields());
}
EXTENDS_ASYNC_MYSQL_RESULT(AsyncMysqlQueryResult)
static int64_t HHVM_METHOD(AsyncMysqlQueryResult, lastInsertId) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->m_query_result->lastInsertId();
}
static int64_t HHVM_METHOD(AsyncMysqlQueryResult, numRowsAffected) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->m_query_result->numRowsAffected();
}
static int64_t HHVM_METHOD(AsyncMysqlQueryResult, numRows) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->m_query_result->numRows();
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, vectorRows) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->buildRows(false /* as_maps */, false /* typed */);
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, mapRows) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->buildRows(true /* as_maps */, false /* typed */);
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, vectorRowsTyped) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->buildRows(false /* as_maps */, true /* typed */);
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, mapRowsTyped) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->buildRows(true /* as_maps */, true /* typed */);
}
static Array HHVM_METHOD(AsyncMysqlQueryResult, dictRowsTyped) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->buildTypedVecMaps();
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, rowBlocks) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  auto ret = req::make<c_Vector>();
  auto row_blocks = data->m_query_result->stealRows();
  ret->reserve(row_blocks.size());
  for (auto& row_block : row_blocks) {
    ret->add(AsyncMysqlRowBlock::newInstance(&row_block,
                                             data->m_field_index));
  }
  return Object{std::move(ret)};
}
static bool HHVM_METHOD(AsyncMysqlQueryResult, noIndexUsed) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return data->m_no_index_used;
}
static String HHVM_METHOD(AsyncMysqlQueryResult, recvGtid) {
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  return String(data->m_query_result->recvGtid(), CopyString);
}
static Object HHVM_METHOD(AsyncMysqlQueryResult, responseAttributes) {
  auto ret = req::make<c_Map>();
  auto* data = Native::data<AsyncMysqlQueryResult>(this_);
  const auto& responseAttributes = data->m_query_result->responseAttributes();
  for (const auto& [key, value] : responseAttributes) {
    ret->set(key, value);
  }
  return Object{std::move(ret)};
}
namespace {
Variant buildTypedValue(const am::RowFields* row_fields,
                        const am::Row& row,
                        int field_num,
                        bool typed_values) {
  if (row.isNull(field_num)) {
    return init_null();
  }
  // The underlying library may return zero length null ptr's to
  // indicate an empty string (since the isNull check above would tell
  // if it were actually NULL).
  String string_value =
      (row[field_num].data() == nullptr && row[field_num].size() == 0)
          ? empty_string()
          : String(row[field_num].data(), row[field_num].size(), CopyString);
  if (!typed_values) {
    return string_value;
  }
  return mysql_makevalue(string_value, row_fields->getFieldType(field_num));
}
}
Object AsyncMysqlQueryResult::buildRows(bool as_maps, bool typed_values) {
  auto ret = req::make<c_Vector>();
  ret->reserve(m_query_result->numRows());
  for (const auto& row : *m_query_result) {
    if (as_maps) {
      auto row_map = req::make<c_Map>();
      for (int i = 0; i < row.size(); ++i) {
        row_map->set(
            m_field_index->getFieldString(i),
            buildTypedValue(
                m_query_result->getRowFields(), row, i, typed_values));
      }
      ret->add(Variant(std::move(row_map)));
    } else {
      auto row_vector = req::make<c_Vector>();
      row_vector->reserve(row.size());
      for (int i = 0; i < row.size(); ++i) {
        row_vector->add(buildTypedValue(
            m_query_result->getRowFields(), row, i, typed_values));
      }
      ret->add(Variant(std::move(row_vector)));
    }
  }
  return Object(std::move(ret));
}
Array AsyncMysqlQueryResult::buildTypedVecMaps() {
  VecInit ret{m_query_result->numRows()};
  for (const auto& row : *m_query_result) {
    DictInit row_map{row.size()};
    for (int i = 0; i < row.size(); ++i) {
      row_map.set(
          m_field_index->getFieldString(i),
          buildTypedValue(
              m_query_result->getRowFields(), row, i, true));
    }
    ret.append(row_map.toArray());
  }
  return ret.toArray();
}
FieldIndex::FieldIndex(const am::RowFields* row_fields) {
  if (row_fields == nullptr)
    return;
  auto n = row_fields->numFields();
  field_names_.reserve(n);
  field_name_map_.reserve(n);
  for (int i = 0; i < n; ++i) {
    auto name = String(row_fields->fieldName(i).str());
    field_names_.push_back(name);
    field_name_map_[name] = i; // last duplicate field name wins
  }
}
size_t FieldIndex::getFieldIndex(String field_name) const {
  auto it = field_name_map_.find(field_name);
  if (it == field_name_map_.end()) {
    SystemLib::throwInvalidArgumentExceptionObject(
      "Given field name doesn't exist");
  }
  return it->second;
}
String FieldIndex::getFieldString(size_t field_index) const {
  // Leaving out of bounds to be thrown by the vector in case needed
  return field_names_.at(field_index);
}
namespace {
req::ptr<c_Vector> transformQueryResults(
  std::shared_ptr<am::MultiQueryOperation> op,
  db::ClientPerfStats stats) {
  auto results = req::make<c_Vector>();
  auto query_results = op->stealQueryResults();
  results->reserve(query_results.size());
  for (int i = 0; i < query_results.size(); ++i) {
    auto ret = AsyncMysqlQueryResult::newInstance(
        op, stats, std::move(query_results[i]), op->noIndexUsed());
    results->add(std::move(ret));
  }
  query_results.clear();
  return results;
}
void throwAsyncMysqlException(const char* exception_type,
                              std::shared_ptr<am::Operation> op,
                              db::ClientPerfStats clientStats) {
  auto error =
      AsyncMysqlErrorResult::newInstance(op, std::move(clientStats));
  assertx(op->result() == am::OperationResult::Failed ||
         op->result() == am::OperationResult::TimedOut ||
         op->result() == am::OperationResult::Cancelled);
  throw_object(
    exception_type,
    make_vec_array(std::move(error)),
    true /* init */);
}
void throwAsyncMysqlQueryException(const char* exception_type,
                                   std::shared_ptr<am::Operation> op,
                                   db::ClientPerfStats clientStats,
                                   req::ptr<c_Vector> res) {
  auto error = AsyncMysqlQueryErrorResult::newInstance(
      op, std::move(clientStats), res);
  assertx(op->result() == am::OperationResult::Failed ||
         op->result() == am::OperationResult::TimedOut ||
         op->result() == am::OperationResult::Cancelled);
  throw_object(
    exception_type,
    make_vec_array(std::move(error)),
    true /* init */);
}
}
void AsyncMysqlConnectEvent::unserialize(TypedValue& result) {
  if (m_op->ok()) {
    auto ret = AsyncMysqlConnection::newInstance(
        m_op->releaseConnection(), m_op, std::move(m_clientStats));
    tvCopy(make_tv<KindOfObject>(ret.detach()), result);
  } else {
    throwAsyncMysqlException("AsyncMysqlConnectException", m_op,
                             std::move(m_clientStats));
  }
}
void AsyncMysqlQueryEvent::unserialize(TypedValue& result) {
  // Retrieve the original conn and return the underlying connection
  // to it.
  assertx(getPrivData()->instanceof(AsyncMysqlConnection::getClass()));
  auto* conn = Native::data<AsyncMysqlConnection>(getPrivData());
  conn->setConnection(m_query_op->releaseConnection());
  if (m_query_op->ok()) {
    auto query_result = m_query_op->stealQueryResult();
    auto ret = AsyncMysqlQueryResult::newInstance(
      m_query_op, std::move(m_clientStats), std::move(query_result),
      m_query_op->noIndexUsed());
    tvCopy(make_tv<KindOfObject>(ret.detach()), result);
  } else {
    throwAsyncMysqlQueryException("AsyncMysqlQueryException",
                                  m_query_op,
                                  std::move(m_clientStats),
                                  req::make<c_Vector>());
  }
}
void AsyncMysqlMultiQueryEvent::unserialize(TypedValue& result) {
  // Same as unserialize from AsyncMysqlQueryEvent but the result is a
  // vector of query results
  assertx(getPrivData()->instanceof(AsyncMysqlConnection::getClass()));
  auto* conn = Native::data<AsyncMysqlConnection>(getPrivData());
  conn->setConnection(m_multi_op->releaseConnection());
  // Retrieving the results for all executed queries
  auto results = transformQueryResults(m_multi_op, m_clientStats);
  if (m_multi_op->ok()) {
    tvDup(make_tv<KindOfObject>(results.get()), result);
  } else {
    throwAsyncMysqlQueryException("AsyncMysqlQueryException", m_multi_op,
                                  std::move(m_clientStats), results);
  }
}
void AsyncMysqlConnectAndMultiQueryEvent::unserialize(TypedValue& result) {
  if (!m_connect_op->ok()) {
    throwAsyncMysqlException("AsyncMysqlConnectException", m_connect_op,
                             std::move(m_clientStats));
  }
  // Retrieving the results for all executed queries
  auto queryResults = transformQueryResults(m_multi_query_op, m_clientStats);
  auto connResult = AsyncMysqlConnectResult::newInstance(
      m_connect_op, m_clientStats);
  if (m_multi_query_op->ok()) {
    auto resTuple = make_vec_array(connResult, queryResults);
    tvCopy(make_array_like_tv(resTuple.detach()), result);
  } else {
    throwAsyncMysqlQueryException("AsyncMysqlQueryException", m_multi_query_op,
                                  std::move(m_clientStats), queryResults);
  }
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlRowBlock
Class* AsyncMysqlRowBlock::s_class = nullptr;
const StaticString AsyncMysqlRowBlock::s_className("AsyncMysqlRowBlock");
IMPLEMENT_GET_CLASS(AsyncMysqlRowBlock)
Object AsyncMysqlRowBlock::newInstance(am::RowBlock* row_block,
    req::shared_ptr<FieldIndex> indexer) {
  Object ret{AsyncMysqlRowBlock::getClass()};
  auto* data = Native::data<AsyncMysqlRowBlock>(ret);
  data->m_row_block.reset(new am::RowBlock(std::move(*row_block)));
  data->m_field_index = indexer;
  return ret;
}
void AsyncMysqlRowBlock::sweep() {
  m_row_block.reset();
}
size_t AsyncMysqlRowBlock::getIndexFromVariant(const Variant& field) {
  if (field.isInteger()) {
    return field.toInt64();
  } else if (field.isString()) {
    return m_field_index->getFieldIndex(field.toString());
  }
  SystemLib::throwInvalidArgumentExceptionObject(
    "Only integer or string field names may be used with RowBlock");
}
// The String conversion allows `NULL` to ""
template <>
folly::StringPiece AsyncMysqlRowBlock::getFieldAs(int64_t row,
                                                  const Variant& field) {
  auto index = getIndexFromVariant(field);
  try {
    // Note that for String before you return to PHP you need to copy it into
    // HPHP::String.
    return m_row_block->getField<folly::StringPiece>(row, index);
  }
  catch (std::range_error& excep) {
    SystemLib::throwBadMethodCallExceptionObject(
      std::string("Error during conversion: ") + excep.what());
  }
}
template <typename FieldType>
FieldType AsyncMysqlRowBlock::getFieldAs(int64_t row, const Variant& field) {
  auto index = getIndexFromVariant(field);
  if (m_row_block->isNull(row, index)) {
    SystemLib::throwBadMethodCallExceptionObject(
        "Field value needs to be non-null.");
  }
  try {
    return m_row_block->getField<FieldType>(row, index);
  }
  catch (std::range_error& excep) {
    SystemLib::throwBadMethodCallExceptionObject(
      std::string("Error during conversion: ") + excep.what());
  }
}
static Variant
HHVM_METHOD(AsyncMysqlRowBlock, at, int64_t row, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  auto col_index = data->getIndexFromVariant(field);
  return buildTypedValue(
      data->m_row_block->getRowFields(),
      data->m_row_block->getRow(row),
      col_index,
      true);
}
static int64_t HHVM_METHOD(
    AsyncMysqlRowBlock,
    getFieldAsInt,
    int64_t row,
    const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->getFieldAs<int64_t>(row, field);
}
static double HHVM_METHOD(
    AsyncMysqlRowBlock,
    getFieldAsDouble,
    int64_t row,
    const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->getFieldAs<double>(row, field);
}
static String HHVM_METHOD(
    AsyncMysqlRowBlock,
    getFieldAsString,
    int64_t row,
    const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  auto val = data->getFieldAs<folly::StringPiece>(row, field);
  if (val.empty()) {
    return empty_string();
  }
  return String{val};
}
static bool
HHVM_METHOD(AsyncMysqlRowBlock, isNull, int64_t row, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->isNull(row, data->getIndexFromVariant(field));
}
static int64_t
HHVM_METHOD(AsyncMysqlRowBlock, fieldType, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->getFieldType(data->getIndexFromVariant(field));
}
static int64_t
HHVM_METHOD(AsyncMysqlRowBlock, fieldFlags, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->getFieldFlags(data->getIndexFromVariant(field));
}
static String HHVM_METHOD(AsyncMysqlRowBlock, fieldName, int64_t field_id) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_field_index->getFieldString(field_id);
}
static bool HHVM_METHOD(AsyncMysqlRowBlock, isEmpty) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->empty();
}
static int64_t HHVM_METHOD(AsyncMysqlRowBlock, fieldsCount) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->numFields();
}
static int64_t HHVM_METHOD(AsyncMysqlRowBlock, count) {
  auto* data = Native::data<AsyncMysqlRowBlock>(this_);
  return data->m_row_block->numRows();
}
static Object HHVM_METHOD(AsyncMysqlRowBlock, getRow, int64_t row_no) {
  return AsyncMysqlRow::newInstance(Object{this_}, row_no);
}
static Object HHVM_METHOD(AsyncMysqlRowBlock, getIterator) {
  return AsyncMysqlRowBlockIterator::newInstance(Object{this_}, 0);
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlRowBlockIterator
Class* AsyncMysqlRowBlockIterator::s_class = nullptr;
const StaticString AsyncMysqlRowBlockIterator::s_className(
  "AsyncMysqlRowBlockIterator");
IMPLEMENT_GET_CLASS(AsyncMysqlRowBlockIterator)
Object AsyncMysqlRowBlockIterator::newInstance(Object row_block,
                                               size_t row_number) {
  Object ret{getClass()};
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(ret);
  data->m_row_block = row_block;
  data->m_row_number = row_number;
  return ret;
}
static void HHVM_METHOD(AsyncMysqlRowBlockIterator, next) {
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(this_);
  data->m_row_number++;
}
static bool HHVM_METHOD(AsyncMysqlRowBlockIterator, valid) {
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(this_);
  static_assert(
      std::is_unsigned<decltype(data->m_row_number)>::value,
      "m_row_number should be unsigned");
  int64_t count = HHVM_MN(AsyncMysqlRowBlock, count)(data->m_row_block.get());
  return data->m_row_number < count;
}
static Object HHVM_METHOD(AsyncMysqlRowBlockIterator, current) {
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(this_);
  if (!HHVM_MN(AsyncMysqlRowBlockIterator, valid)(this_)) {
    throw_iterator_not_valid();
  }
  return HHVM_MN(AsyncMysqlRowBlock, getRow)(
      data->m_row_block.get(), data->m_row_number);
}
static int64_t HHVM_METHOD(AsyncMysqlRowBlockIterator, key) {
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(this_);
  return data->m_row_number;
}
static void HHVM_METHOD(AsyncMysqlRowBlockIterator, rewind) {
  auto* data = Native::data<AsyncMysqlRowBlockIterator>(this_);
  data->m_row_number = 0;
}
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlRow
Class* AsyncMysqlRow::s_class = nullptr;
const StaticString AsyncMysqlRow::s_className("AsyncMysqlRow");
IMPLEMENT_GET_CLASS(AsyncMysqlRow)
Object AsyncMysqlRow::newInstance(Object row_block, size_t row_number) {
  Object ret{getClass()};
  auto* data = Native::data<AsyncMysqlRow>(ret);
  data->m_row_block = row_block;
  data->m_row_number = row_number;
  return ret;
}
#define ROW_BLOCK(method, ...) \
  HHVM_MN(AsyncMysqlRowBlock, method)(data->m_row_block.get(), __VA_ARGS__)
static Variant HHVM_METHOD(AsyncMysqlRow, at, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(at, data->m_row_number, field);
}
static int64_t HHVM_METHOD(AsyncMysqlRow, getFieldAsInt, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(getFieldAsInt, data->m_row_number, field);
}
static double
HHVM_METHOD(AsyncMysqlRow, getFieldAsDouble, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(getFieldAsDouble, data->m_row_number, field);
}
static String
HHVM_METHOD(AsyncMysqlRow, getFieldAsString, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(getFieldAsString, data->m_row_number, field);
}
static bool HHVM_METHOD(AsyncMysqlRow, isNull, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(isNull, data->m_row_number, field);
}
static int64_t HHVM_METHOD(AsyncMysqlRow, fieldType, const Variant& field) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return ROW_BLOCK(fieldType, field);
}
static int64_t HHVM_METHOD(AsyncMysqlRow, count) {
  auto* data = Native::data<AsyncMysqlRow>(this_);
  return HHVM_MN(AsyncMysqlRowBlock, fieldsCount)(data->m_row_block.get());
}
static Object HHVM_METHOD(AsyncMysqlRow, getIterator) {
  return AsyncMysqlRowIterator::newInstance(Object{this_}, 0);
}
#undef ROW_BLOCK
///////////////////////////////////////////////////////////////////////////////
// class AsyncMysqlRowIterator
Class* AsyncMysqlRowIterator::s_class = nullptr;
const StaticString AsyncMysqlRowIterator::s_className("AsyncMysqlRowIterator");
IMPLEMENT_GET_CLASS(AsyncMysqlRowIterator)
Object AsyncMysqlRowIterator::newInstance(Object row,
                                          size_t field_number) {
  Object ret{getClass()};
  auto* data = Native::data<AsyncMysqlRowIterator>(ret);
  data->m_row = row;
  data->m_field_number = field_number;
  return ret;
}
static void HHVM_METHOD(AsyncMysqlRowIterator, next) {
  auto* data = Native::data<AsyncMysqlRowIterator>(this_);
  data->m_field_number++;
}
static bool HHVM_METHOD(AsyncMysqlRowIterator, valid) {
  auto* data = Native::data<AsyncMysqlRowIterator>(this_);
  static_assert(
      std::is_unsigned<decltype(data->m_field_number)>::value,
      "m_field_number should be unsigned");
  int64_t count = HHVM_MN(AsyncMysqlRow, count)(data->m_row.get());
  return data->m_field_number < count;
}
/*?? return as string? */
static String HHVM_METHOD(AsyncMysqlRowIterator, current) {
  auto* data = Native::data<AsyncMysqlRowIterator>(this_);
  return HHVM_MN(AsyncMysqlRow, getFieldAsString)(
      data->m_row.get(), (uint64_t)data->m_field_number);
}
static int64_t HHVM_METHOD(AsyncMysqlRowIterator, key) {
  auto* data = Native::data<AsyncMysqlRowIterator>(this_);
  return data->m_field_number;
}
static void HHVM_METHOD(AsyncMysqlRowIterator, rewind) {
  auto* data = Native::data<AsyncMysqlRowIterator>(this_);
  data->m_field_number = 0;
}
///////////////////////////////////////////////////////////////////////////////
static const int64_t DISABLE_COPY_AND_SWEEP = Native::NDIFlags::NO_COPY |
  Native::NDIFlags::NO_SWEEP;
static struct AsyncMysqlExtension final : Extension {
  // Since hhvm (and thereby the extension) is on a one-week release cycle
  // whereas www release is continuous, for forward compatibility, any future
  // modification of the extension that requires client (www) changes should
  // bump the version number and use a version guard in www:
  //   $ext = new ReflectionExtension("async_mysql");
  //   $version = (float) $ext->getVersion();
  AsyncMysqlExtension() : Extension("async_mysql", "6.0") {}
  void moduleInit() override {
    // expose the mysql flags
    HHVM_RC_INT_SAME(NOT_NULL_FLAG);
    HHVM_RC_INT_SAME(PRI_KEY_FLAG);
    HHVM_RC_INT_SAME(UNIQUE_KEY_FLAG);
    HHVM_RC_INT_SAME(MULTIPLE_KEY_FLAG);
    HHVM_RC_INT_SAME(UNSIGNED_FLAG);
    HHVM_RC_INT_SAME(ZEROFILL_FLAG);
    HHVM_RC_INT_SAME(BINARY_FLAG);
    HHVM_RC_INT_SAME(AUTO_INCREMENT_FLAG);
    HHVM_RC_INT_SAME(ENUM_FLAG);
    HHVM_RC_INT_SAME(SET_FLAG);
    HHVM_RC_INT_SAME(BLOB_FLAG);
    HHVM_RC_INT_SAME(TIMESTAMP_FLAG);
    HHVM_RC_INT_SAME(NUM_FLAG);
    HHVM_RC_INT_SAME(NO_DEFAULT_VALUE_FLAG);
    // expose the mysql field types
    HHVM_RC_INT_SAME(MYSQL_TYPE_TINY);
    HHVM_RC_INT_SAME(MYSQL_TYPE_SHORT);
    HHVM_RC_INT_SAME(MYSQL_TYPE_LONG);
    HHVM_RC_INT_SAME(MYSQL_TYPE_INT24);
    HHVM_RC_INT_SAME(MYSQL_TYPE_LONGLONG);
    HHVM_RC_INT_SAME(MYSQL_TYPE_DECIMAL);
    HHVM_RC_INT_SAME(MYSQL_TYPE_NEWDECIMAL);
    HHVM_RC_INT_SAME(MYSQL_TYPE_FLOAT);
    HHVM_RC_INT_SAME(MYSQL_TYPE_DOUBLE);
    HHVM_RC_INT_SAME(MYSQL_TYPE_BIT);
    HHVM_RC_INT_SAME(MYSQL_TYPE_TIMESTAMP);
    HHVM_RC_INT_SAME(MYSQL_TYPE_DATE);
    HHVM_RC_INT_SAME(MYSQL_TYPE_TIME);
    HHVM_RC_INT_SAME(MYSQL_TYPE_DATETIME);
    HHVM_RC_INT_SAME(MYSQL_TYPE_YEAR);
    HHVM_RC_INT_SAME(MYSQL_TYPE_STRING);
    HHVM_RC_INT_SAME(MYSQL_TYPE_VAR_STRING);
    HHVM_RC_INT_SAME(MYSQL_TYPE_BLOB);
    HHVM_RC_INT_SAME(MYSQL_TYPE_SET);
    HHVM_RC_INT_SAME(MYSQL_TYPE_ENUM);
    HHVM_RC_INT_SAME(MYSQL_TYPE_GEOMETRY);
    HHVM_RC_INT_SAME(MYSQL_TYPE_NULL);
    HHVM_STATIC_ME(AsyncMysqlClient, setPoolsConnectionLimit);
    HHVM_STATIC_ME(AsyncMysqlClient, connect);
    HHVM_STATIC_ME(AsyncMysqlClient, connectWithOpts);
    HHVM_STATIC_ME(AsyncMysqlClient, connectAndQuery);
    HHVM_ME(AsyncMysqlConnectionPool, __construct);
    HHVM_ME(AsyncMysqlConnectionPool, getPoolStats);
    HHVM_ME(AsyncMysqlConnectionPool, connect);
    HHVM_ME(AsyncMysqlConnectionPool, connectWithOpts);
    HHVM_ME(AsyncMysqlConnectionPool, connectAndQuery);
    Native::registerNativeDataInfo<AsyncMysqlConnectionPool>(
        AsyncMysqlConnectionPool::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlClientStats, ioEventLoopMicrosAvg);
    HHVM_ME(AsyncMysqlClientStats, callbackDelayMicrosAvg);
    HHVM_ME(AsyncMysqlClientStats, ioThreadBusyMicrosAvg);
    HHVM_ME(AsyncMysqlClientStats, ioThreadIdleMicrosAvg);
    HHVM_ME(AsyncMysqlClientStats, notificationQueueSize);
    Native::registerNativeDataInfo<AsyncMysqlClientStats>(
        AsyncMysqlClientStats::s_className.get());
    Native::registerNativeDataInfo<MySSLContextProvider>(
        MySSLContextProvider::s_className.get());
    HHVM_ME(MySSLContextProvider, isValid);
    HHVM_ME(MySSLContextProvider, allowSessionResumption);
    HHVM_ME(AsyncMysqlConnection, query);
    HHVM_ME(AsyncMysqlConnection, queryf);
    HHVM_ME(AsyncMysqlConnection, queryAsync);
    HHVM_ME(AsyncMysqlConnection, multiQuery);
    HHVM_ME(AsyncMysqlConnection, escapeString);
    HHVM_ME(AsyncMysqlConnection, close);
    HHVM_ME(AsyncMysqlConnection, releaseConnection);
    HHVM_ME(AsyncMysqlConnection, isValid);
    HHVM_ME(AsyncMysqlConnection, serverInfo);
    HHVM_ME(AsyncMysqlConnection, sslSessionReused);
    HHVM_ME(AsyncMysqlConnection, isSSL);
    HHVM_ME(AsyncMysqlConnection, warningCount);
    HHVM_ME(AsyncMysqlConnection, host);
    HHVM_ME(AsyncMysqlConnection, port);
    HHVM_ME(AsyncMysqlConnection, setReusable);
    HHVM_ME(AsyncMysqlConnection, isReusable);
    HHVM_ME(AsyncMysqlConnection, connectResult);
    HHVM_ME(AsyncMysqlConnection, lastActivityTime);
    HHVM_ME(AsyncMysqlConnection, getSslCertCn);
    HHVM_ME(AsyncMysqlConnection, getSslCertSan);
    HHVM_ME(AsyncMysqlConnection, getSslCertExtensions);
    HHVM_ME(AsyncMysqlConnection, isSslCertValidationEnforced);
    Native::registerNativeDataInfo<AsyncMysqlConnection>(
        AsyncMysqlConnection::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlResult, getSslCertCn);
    HHVM_ME(AsyncMysqlResult, getSslCertSan);
    HHVM_ME(AsyncMysqlResult, getSslCertExtensions);
    HHVM_ME(AsyncMysqlResult, isSslCertValidationEnforced);
    HHVM_ME(AsyncMysqlConnectResult, elapsedMicros);
    HHVM_ME(AsyncMysqlConnectResult, startTime);
    HHVM_ME(AsyncMysqlConnectResult, endTime);
    HHVM_ME(AsyncMysqlConnectResult, clientStats);
    Native::registerNativeDataInfo<AsyncMysqlConnectResult>(
        AsyncMysqlConnectResult::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlErrorResult, elapsedMicros);
    HHVM_ME(AsyncMysqlErrorResult, startTime);
    HHVM_ME(AsyncMysqlErrorResult, endTime);
    HHVM_ME(AsyncMysqlErrorResult, clientStats);
    HHVM_ME(AsyncMysqlErrorResult, mysql_errno);
    HHVM_ME(AsyncMysqlErrorResult, mysql_error);
    HHVM_ME(AsyncMysqlErrorResult, mysql_normalize_error);
    HHVM_ME(AsyncMysqlErrorResult, failureType);
    Native::registerNativeDataInfo<AsyncMysqlErrorResult>(
      AsyncMysqlErrorResult::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlQueryErrorResult, numSuccessfulQueries);
    HHVM_ME(AsyncMysqlQueryErrorResult, getSuccessfulResults);
    Native::registerNativeDataInfo<AsyncMysqlQueryErrorResult>(
      AsyncMysqlQueryErrorResult::s_className.get(),
      Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlQueryResult, elapsedMicros);
    HHVM_ME(AsyncMysqlQueryResult, startTime);
    HHVM_ME(AsyncMysqlQueryResult, endTime);
    HHVM_ME(AsyncMysqlQueryResult, clientStats);
    HHVM_ME(AsyncMysqlQueryResult, numRowsAffected);
    HHVM_ME(AsyncMysqlQueryResult, lastInsertId);
    HHVM_ME(AsyncMysqlQueryResult, numRows);
    HHVM_ME(AsyncMysqlQueryResult, mapRows);
    HHVM_ME(AsyncMysqlQueryResult, vectorRows);
    HHVM_ME(AsyncMysqlQueryResult, mapRowsTyped);
    HHVM_ME(AsyncMysqlQueryResult, dictRowsTyped);
    HHVM_ME(AsyncMysqlQueryResult, vectorRowsTyped);
    HHVM_ME(AsyncMysqlQueryResult, rowBlocks);
    HHVM_ME(AsyncMysqlQueryResult, noIndexUsed);
    HHVM_ME(AsyncMysqlQueryResult, recvGtid);
    HHVM_ME(AsyncMysqlQueryResult, responseAttributes);
    Native::registerNativeDataInfo<AsyncMysqlQueryResult>(
      AsyncMysqlQueryResult::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlRowBlock, at);
    HHVM_ME(AsyncMysqlRowBlock, getFieldAsInt);
    HHVM_ME(AsyncMysqlRowBlock, getFieldAsDouble);
    HHVM_ME(AsyncMysqlRowBlock, getFieldAsString);
    HHVM_ME(AsyncMysqlRowBlock, isNull);
    HHVM_ME(AsyncMysqlRowBlock, fieldType);
    HHVM_ME(AsyncMysqlRowBlock, fieldFlags);
    HHVM_ME(AsyncMysqlRowBlock, fieldName);
    HHVM_ME(AsyncMysqlRowBlock, isEmpty);
    HHVM_ME(AsyncMysqlRowBlock, fieldsCount);
    HHVM_ME(AsyncMysqlRowBlock, count);
    HHVM_ME(AsyncMysqlRowBlock, getIterator);
    HHVM_ME(AsyncMysqlRowBlock, getRow);
    Native::registerNativeDataInfo<AsyncMysqlRowBlock>(
      AsyncMysqlRowBlock::s_className.get(), Native::NDIFlags::NO_COPY);
    HHVM_ME(AsyncMysqlRowBlockIterator, valid);
    HHVM_ME(AsyncMysqlRowBlockIterator, next);
    HHVM_ME(AsyncMysqlRowBlockIterator, current);
    HHVM_ME(AsyncMysqlRowBlockIterator, key);
    HHVM_ME(AsyncMysqlRowBlockIterator, rewind);
    Native::registerNativeDataInfo<AsyncMysqlRowBlockIterator>(
      AsyncMysqlRowBlockIterator::s_className.get(),
      DISABLE_COPY_AND_SWEEP);
    HHVM_ME(AsyncMysqlRow, at);
    HHVM_ME(AsyncMysqlRow, getFieldAsInt);
    HHVM_ME(AsyncMysqlRow, getFieldAsDouble);
    HHVM_ME(AsyncMysqlRow, getFieldAsString);
    HHVM_ME(AsyncMysqlRow, isNull);
    HHVM_ME(AsyncMysqlRow, fieldType);
    HHVM_ME(AsyncMysqlRow, count);
    HHVM_ME(AsyncMysqlRow, getIterator);
    Native::registerNativeDataInfo<AsyncMysqlRow>(
      AsyncMysqlRow::s_className.get(), DISABLE_COPY_AND_SWEEP);
    HHVM_ME(AsyncMysqlRowIterator, valid);
    HHVM_ME(AsyncMysqlRowIterator, next);
    HHVM_ME(AsyncMysqlRowIterator, current);
    HHVM_ME(AsyncMysqlRowIterator, key);
    HHVM_ME(AsyncMysqlRowIterator, rewind);
    Native::registerNativeDataInfo<AsyncMysqlRowIterator>(
        AsyncMysqlRowIterator::s_className.get(), DISABLE_COPY_AND_SWEEP);
    HHVM_ME(AsyncMysqlConnectionOptions, setConnectTimeout);
    HHVM_ME(AsyncMysqlConnectionOptions, setConnectTcpTimeout);
    HHVM_ME(AsyncMysqlConnectionOptions, setConnectAttempts);
    HHVM_ME(AsyncMysqlConnectionOptions, setTotalTimeout);
    HHVM_ME(AsyncMysqlConnectionOptions, setQueryTimeout);
    HHVM_ME(AsyncMysqlConnectionOptions, setConnectionAttributes);
    HHVM_ME(AsyncMysqlConnectionOptions, setSSLOptionsProvider);
    HHVM_ME(AsyncMysqlConnectionOptions, setSniServerName);
    HHVM_ME(AsyncMysqlConnectionOptions, enableResetConnBeforeClose);
    HHVM_ME(AsyncMysqlConnectionOptions, enableDelayedResetConn);
    HHVM_ME(AsyncMysqlConnectionOptions, enableChangeUser);
    HHVM_ME(AsyncMysqlConnectionOptions, setServerCertValidation);
    Native::registerNativeDataInfo<AsyncMysqlConnectionOptions>(
        AsyncMysqlConnectionOptions::s_className.get());
    HHVM_NAMED_ME(HH\\Lib\\SQL\\Query,
        toString__FOR_DEBUGGING_ONLY,
        HHLibSQLQuery__toString__FOR_DEBUGGING_ONLY);
    HHVM_NAMED_ME(HH\\Lib\\SQL\\Query,
        toUnescapedString__FOR_DEBUGGING_ONLY__UNSAFE,
        HHLibSQLQuery__toUnescapedString__FOR_DEBUGGING_ONLY__UNSAFE);
    loadSystemlib("mysqlrow");
    loadSystemlib("async_mysql_exceptions");
    loadSystemlib();
    s_queryClass = Class::lookup(s_queryClassName.get());
  }
  void moduleLoad(const IniSetting::Map& ini, Hdf config) override {
    Config::Bind(
        HdfAsyncMysqlClientPoolSize,
        ini,
        config,
        "AsyncMysql.ClientPoolSize",
        2);
  }
} s_async_mysql_extension;
///////////////////////////////////////////////////////////////////////////////
}