/* Copyright (C) agentzh */

#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"

#include "ngx_http_drizzle_module.h"
#include "ngx_http_drizzle_upstream.h"
#include "ngx_http_drizzle_keepalive.h"
#include "ngx_http_drizzle_processor.h"
#include "ngx_http_drizzle_util.h"

enum {
    ngx_http_drizzle_default_port = 3306
};

static void ngx_http_upstream_drizzle_cleanup(void *data);

static ngx_int_t ngx_http_upstream_drizzle_init(ngx_conf_t *cf,
        ngx_http_upstream_srv_conf_t *uscf);

static ngx_int_t ngx_http_upstream_drizzle_init_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *uscf);

static ngx_int_t ngx_http_upstream_drizzle_get_peer(ngx_peer_connection_t *pc,
        void *data);

static void ngx_http_upstream_drizzle_free_peer(ngx_peer_connection_t *pc,
        void *data, ngx_uint_t state);

/* just a work-around to override the default u->output_filter */
static ngx_int_t ngx_http_drizzle_output_filter(void *data, ngx_chain_t *in);


void *
ngx_http_upstream_drizzle_create_srv_conf(ngx_conf_t *cf)
{
    ngx_pool_cleanup_t                      *cln;
    ngx_http_upstream_drizzle_srv_conf_t    *conf;

    dd("drizzle create srv conf");

    conf = ngx_pcalloc(cf->pool,
                       sizeof(ngx_http_upstream_drizzle_srv_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    /* set by ngx_pcalloc:
     *      conf->peers   = NULL
     *      conf->current = 0
     *      conf->servers = NULL
     *      conf->single = 0
     *      conf->max_cached = 0
     *      conf->overflow = 0 (drizzle_keepalive_overflow_ignore)
     */

    conf->pool = cf->pool;

    cln = ngx_pool_cleanup_add(cf->pool, 0);
    if (cln == NULL) {
        return NULL;
    }

    (void) drizzle_create(&conf->drizzle);

    cln->handler = ngx_http_upstream_drizzle_cleanup;
    cln->data = &conf->drizzle;

    drizzle_add_options(&conf->drizzle, DRIZZLE_NON_BLOCKING);

    /* we use 0 timeout for the underlying poll event model
     * used by libdrizzle itself. */
    drizzle_set_timeout(&conf->drizzle, 0);

    return conf;
}


/* mostly based on ngx_http_upstream_server in
 * ngx_http_upstream.c of nginx 0.8.30.
 * Copyright (C) Igor Sysoev */
char *
ngx_http_upstream_drizzle_server(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf)
{
    ngx_http_upstream_drizzle_srv_conf_t        *dscf = conf;
    ngx_http_upstream_drizzle_server_t          *ds;
    ngx_str_t                                   *value;
    ngx_url_t                                    u;
    ngx_uint_t                                   i, j;
    ngx_http_upstream_srv_conf_t                *uscf;
    ngx_str_t                                    protocol;
    ngx_str_t                                    charset;
    u_char                                      *p;
    size_t                                       len;

    dd("entered drizzle_server directive handler...");

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    if (dscf->servers == NULL) {
        dscf->servers = ngx_array_create(cf->pool, 4,
                                 sizeof(ngx_http_upstream_drizzle_server_t));

        if (dscf->servers == NULL) {
            return NGX_CONF_ERROR;
        }

        uscf->servers = dscf->servers;
    }

    ds = ngx_array_push(dscf->servers);
    if (ds == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_memzero(ds, sizeof(ngx_http_upstream_drizzle_server_t));

    value = cf->args->elts;

    /* parse the first name:port argument */

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.url = value[1];
    u.default_port = ngx_http_drizzle_default_port;

    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
        if (u.err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "drizzle: %s in upstream \"%V\"", u.err, &u.url);
        }

        return NGX_CONF_ERROR;
    }

    ds->addrs  = u.addrs;
    ds->naddrs = u.naddrs;
    ds->port   = u.port;
    ds->protocol = ngx_http_drizzle_protocol;

    /* parse various options */

    for (i = 2; i < cf->args->nelts; i++) {

        if (ngx_strncmp(value[i].data, "dbname=", sizeof("dbname=") - 1)
            == 0)
        {
            ds->dbname.len = value[i].len - (sizeof("dbname=") - 1);

            if (ds->dbname.len >= DRIZZLE_MAX_DB_SIZE) {
                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                       "drizzle: \"dbname\" value too large in upstream \"%V\""
                       " (at most %d bytes)",
                       dscf->peers->name,
                       (int) DRIZZLE_MAX_DB_SIZE);

                return NGX_CONF_ERROR;
            }

            ds->dbname.data = &value[i].data[sizeof("dbname=") - 1];

            continue;
        }

        if (ngx_strncmp(value[i].data, "user=", sizeof("user=") - 1)
            == 0)
        {
            ds->user.len = value[i].len - (sizeof("user=") - 1);

            if (ds->user.len >= DRIZZLE_MAX_USER_SIZE) {
                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                       "drizzle: \"user\" value too large in upstream \"%V\""
                       " (at most %d bytes)",
                       dscf->peers->name,
                       (int) DRIZZLE_MAX_USER_SIZE);

                return NGX_CONF_ERROR;
            }

            ds->user.data = &value[i].data[sizeof("user=") - 1];

            continue;
        }

        if (ngx_strncmp(value[i].data, "password=", sizeof("password=") - 1)
            == 0)
        {
            ds->password.len = value[i].len - (sizeof("password=") - 1);

            if (ds->password.len >= DRIZZLE_MAX_PASSWORD_SIZE) {
                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                       "drizzle: \"password\" value too large in upstream "
                       "\"%V\" (at most %d bytes)",
                       dscf->peers->name,
                       (int) DRIZZLE_MAX_PASSWORD_SIZE);

                return NGX_CONF_ERROR;
            }

            ds->password.data = &value[i].data[sizeof("password=") - 1];

            continue;
        }

        if (ngx_strncmp(value[i].data, "protocol=", sizeof("protocol=") - 1)
            == 0)
        {
            protocol.len = value[i].len - (sizeof("protocol=") - 1);
            protocol.data = &value[i].data[sizeof("protocol=") - 1];

            switch (protocol.len) {

            case 5:
                if (ngx_http_drizzle_strcmp_const(protocol.data, "mysql") == 0)
                {
                    ds->protocol = ngx_http_mysql_protocol;
                } else {
                    continue;
                }

                break;

            case 7:
                if (ngx_http_drizzle_strcmp_const(protocol.data,
                            "drizzle") != 0)
                {
                    continue;
                }

                break;

            default:
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "drizzle: invalid protocol \"%V\""
                                   " in drizzle_server", &protocol);

                return NGX_CONF_ERROR;
            }

            continue;
        }

        if (ngx_strncmp(value[i].data, "charset=", sizeof("charset=") - 1)
            == 0)
        {
            charset.len = value[i].len - (sizeof("charset=") - 1);
            charset.data = &value[i].data[sizeof("charset=") - 1];

            dd("charset: %.*s", (int) charset.len, charset.data);

            if (charset.len == 0) {
                continue;
            }

            for (j = 0; j < charset.len; j++) {
                if (charset.data[j] == '\'') {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "bad charste value \"%V\" in"
                                       " drizzle_server", &charset);

                    return NGX_CONF_ERROR;
                }
            }

            len = sizeof("set names ''") - 1 + charset.len;

            p = ngx_palloc(cf->pool, len);
            if (p == NULL) {
                return NGX_CONF_ERROR;
            }

            ds->set_names_query.data = p;
            ds->set_names_query.len = len;

            dd("charset query len: %d", (int) len);

            p = ngx_copy(p, "set names '", sizeof("set names '") - 1);
            p = ngx_copy(p, charset.data, charset.len);
            *p = '\'';

            continue;
        }

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid parameter \"%V\" in"
                           " drizzle_server", &value[i]);

        return NGX_CONF_ERROR;
    }

    dd("reset init_upstream...");

    uscf->peer.init_upstream = ngx_http_upstream_drizzle_init;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_upstream_drizzle_init(ngx_conf_t *cf,
    ngx_http_upstream_srv_conf_t *uscf)
{
    ngx_uint_t                               i, j, n;
    ngx_http_upstream_drizzle_srv_conf_t    *dscf;
    ngx_http_upstream_drizzle_server_t      *server;
    ngx_http_upstream_drizzle_peers_t       *peers;
    size_t                                   len;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
                   "drizzle upstream init");

    uscf->peer.init = ngx_http_upstream_drizzle_init_peer;

    dscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_drizzle_module);

    if (dscf->servers == NULL || dscf->servers->nelts == 0) {
        /* XXX an upstream implicitly defined by drizzle_pass, etc.,
         * is not allowed for now */

        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                      "drizzle: no drizzle_server defined in upstream \"%V\""
                      " in %s:%ui",
                      &uscf->host, uscf->file_name, uscf->line);

        return NGX_ERROR;
    }

    /* dscf->servers != NULL */

    server = uscf->servers->elts;

    n = 0;

    for (i = 0; i < uscf->servers->nelts; i++) {
        n += server[i].naddrs;
    }

    peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_drizzle_peers_t)
                        + sizeof(ngx_http_upstream_drizzle_peer_t) * (n - 1));

    if (peers == NULL) {
        return NGX_ERROR;
    }

    peers->single = (n == 1);
    peers->number = n;
    peers->name = &uscf->host;

    n = 0;

    for (i = 0; i < uscf->servers->nelts; i++) {
        for (j = 0; j < server[i].naddrs; j++) {
            peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
            peers->peer[n].socklen = server[i].addrs[j].socklen;
            peers->peer[n].name = server[i].addrs[j].name;
            peers->peer[n].port = server[i].port;
            peers->peer[n].user = server[i].user;
            peers->peer[n].password = server[i].password;
            peers->peer[n].dbname = server[i].dbname;
            peers->peer[n].protocol = server[i].protocol;
            peers->peer[n].set_names_query = &server[i].set_names_query;

            len = NGX_SOCKADDR_STRLEN + 1 /* for '\0' */;

            peers->peer[n].host = ngx_palloc(cf->pool, len);

            if (peers->peer[n].host == NULL) {
                return NGX_ERROR;
            }

            len = ngx_sock_ntop(peers->peer[n].sockaddr,
#if defined(nginx_version) && (nginx_version >= 1005003)
                                peers->peer[n].socklen,
#endif
                                peers->peer[n].host, len - 1, 0 /* no port */);

            peers->peer[n].host[len] = '\0';

            n++;
        }
    }

    dscf->peers = peers;

    dscf->active_conns = 0;

    if (dscf->max_cached) {
        return ngx_http_drizzle_keepalive_init(cf->pool, dscf);
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_upstream_drizzle_init_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *uscf)
{
    ngx_http_upstream_drizzle_peer_data_t   *dp;
    ngx_http_upstream_drizzle_srv_conf_t    *dscf;
    ngx_http_upstream_t                     *u;
    ngx_http_core_loc_conf_t                *clcf;
    ngx_http_drizzle_loc_conf_t             *dlcf;
    ngx_http_drizzle_ctx_t                  *dctx;
    ngx_drizzle_mixed_t                     *query;
    ngx_str_t                                dbname, sql;
    ngx_uint_t                               i;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "drizzle init peer");

    dp = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_drizzle_peer_data_t));
    if (dp == NULL) {
        goto failed;
    }

    u = r->upstream;

    dp->upstream = u;
    dp->request  = r;

    dp->last_out = &u->out_bufs;

    dscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_drizzle_module);

    dp->srv_conf = dscf;

    dlcf = ngx_http_get_module_loc_conf(r, ngx_http_drizzle_module);

    dp->loc_conf = dlcf;

    dp->query.len  = 0;
    dp->dbname.len = 0;

    /* to force ngx_output_chain not to use ngx_chain_writer */

    u->output.output_filter = ngx_http_drizzle_output_filter;
    u->output.filter_ctx = r;
    u->output.in   = NULL;
    u->output.busy = NULL;

    u->peer.data = dp;
    u->peer.get = ngx_http_upstream_drizzle_get_peer;
    u->peer.free = ngx_http_upstream_drizzle_free_peer;

    /* Stash dp in the module ctx so handlers can recover it even if
     * another module (e.g. the standard upstream keepalive, which is
     * implicit in nginx 1.29.7+) wraps u->peer.data afterwards. */
    dctx = ngx_http_get_module_ctx(r, ngx_http_drizzle_module);
    if (dctx != NULL) {
        dctx->peer_data = dp;
    }

    /* prepare dbname */

    dp->dbname.len = 0;

    if (dlcf->dbname) {
        /* check if dbname requires overriding at request time */
        if (ngx_http_complex_value(r, dlcf->dbname, &dbname) != NGX_OK) {
            goto failed;
        }

        if (dbname.len) {
            if (dbname.len >= DRIZZLE_MAX_DB_SIZE) {
                ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0,
                       "drizzle: \"dbname\" value too large in upstream \"%V\"",
                       dscf->peers->name);

                goto failed;
            }

            dp->dbname = dbname;
        }
    }

    /* prepare SQL query */

    if (dlcf->methods_set & r->method) {
        /* method-specific query */
        dd("using method-specific query");

        query = dlcf->queries->elts;
        for (i = 0; i < dlcf->queries->nelts; i++) {
            if (query[i].key & r->method) {
                query = &query[i];
                break;
            }
        }

        if (i == dlcf->queries->nelts) {
            goto failed;
        }

    } else {
        /* default query */
        dd("using default query");

        query = dlcf->default_query;
    }

    if (query->cv) {
        /* complex value */
        dd("using complex value");

        if (ngx_http_complex_value(r, query->cv, &sql) != NGX_OK) {
            goto failed;
        }

        if (sql.len == 0) {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "drizzle: empty \"drizzle_query\" (was: \"%V\")"
                          " in location \"%V\"", &query->cv->value,
                          &clcf->name);

            goto failed;
        }

        dp->query = sql;

        return NGX_OK;
    }

    /* simple value */
    dd("using simple value");

    dp->query = query->sv;

    return NGX_OK;

failed:

#if defined(nginx_version) && (nginx_version >= 8017)
    return NGX_ERROR;
#else
    r->upstream->peer.data = NULL;

    return NGX_OK;
#endif
}


static ngx_int_t
ngx_http_upstream_drizzle_get_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_http_upstream_drizzle_peer_data_t   *dp = data;
    ngx_http_upstream_drizzle_srv_conf_t    *dscf;
    ngx_http_upstream_drizzle_peers_t       *peers;
    ngx_http_upstream_drizzle_peer_t        *peer;
#if defined(nginx_version) && (nginx_version < 8017)
    ngx_http_drizzle_ctx_t                  *dctx;
#endif
    ngx_connection_t                        *c = NULL;
    drizzle_con_st                          *dc = NULL;
    ngx_str_t                                dbname;
    drizzle_return_t                         ret;
    ngx_socket_t                             fd;
    ngx_event_t                             *rev, *wev;
    ngx_int_t                                rc;
    ngx_int_t                                event;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, dp->request->connection->log, 0,
                   "drizzle get peer");

#if defined(nginx_version) && (nginx_version < 8017)
    if (data == NULL) {
        goto failed;
    }

    dctx = ngx_http_get_module_ctx(dp->request, ngx_http_drizzle_module);
#endif

    dscf = dp->srv_conf;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, dp->request->connection->log, 0,
                   "active drizzle connections %ui", dscf->active_conns);

    dp->failed = 0;

    /* try to get an idle connection from our single-mode
     * keep-alive pool */

    if (dscf->max_cached && dscf->single) {
        rc = ngx_http_drizzle_keepalive_get_peer_single(pc, dp, dscf);
        if (rc != NGX_DECLINED) {
            return rc;
        }
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, dp->request->connection->log, 0,
                   "drizzle get peer using simple round robin");

    peers = dscf->peers;

    if (dscf->current > peers->number - 1) {
        dscf->current = 0;
    }

    peer = &peers->peer[dscf->current++];

    dp->name.data = peer->name.data;
    dp->name.len = peer->name.len;

    dp->sockaddr = *peer->sockaddr;

    dp->enable_charset = (peer->set_names_query->len > 0);
    dp->set_names_query = peer->set_names_query;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "drizzle set connection charset query \"%V\"",
                   dp->set_names_query);

    pc->name = &dp->name;
    pc->sockaddr = &dp->sockaddr;
    pc->socklen = peer->socklen;
    pc->cached = 0;

    if (dscf->max_cached && ! dscf->single) {
        rc = ngx_http_drizzle_keepalive_get_peer_multi(pc, dp, dscf);
        if (rc != NGX_DECLINED) {
            return rc;
        }
    }

    if (dscf->overflow == drizzle_keepalive_overflow_reject
        && dscf->active_conns >= dscf->max_cached)
    {
        ngx_log_error(NGX_LOG_INFO, pc->log, 0,
                      "drizzle: connection pool full, rejecting request "
                      "to upstream \"%V\"",
                      &peer->name);

        /* a bit hack-ish way to return error response (setup part) */
        pc->connection = ngx_get_connection(0, pc->log);

#if defined(nginx_version) && (nginx_version < 8017)
        dctx->status = NGX_HTTP_SERVICE_UNAVAILABLE;
#endif

        return NGX_AGAIN;
    }

    /* set up the peer's drizzle connection */

    dc = ngx_pcalloc(dscf->pool, sizeof(drizzle_con_st));

    if (dc == NULL) {
#if defined(nginx_version) && (nginx_version >= 8017)
        return NGX_ERROR;
#else
        goto failed;
#endif
    }

    dp->drizzle_con = dc;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
        "drizzle creating connection");

    (void) drizzle_con_create(&dscf->drizzle, dc);

    /* set protocol for the drizzle connection */

    if (peer->protocol == ngx_http_mysql_protocol) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "drizzle using mysql protocol");

        drizzle_con_add_options(dc, DRIZZLE_CON_MYSQL);

    }

    /* set dbname for the drizzle connection */

    if (dp->dbname.len) {
        dbname = dp->dbname;

    } else {
        dbname = peer->dbname;
    }

    ngx_memcpy(dc->db, dbname.data, dbname.len);
    dc->db[dbname.len] = '\0';

    /* set user for the drizzle connection */

    ngx_memcpy(dc->user, peer->user.data, peer->user.len);
    dc->user[peer->user.len] = '\0';

    /* set password for the drizzle connection */

    ngx_memcpy(dc->password, peer->password.data, peer->password.len);
    dc->password[peer->password.len] = '\0';

    dd("user %s, password %s", dc->user, dc->password);

    /* TODO add support for uds (unix domain socket) */

    /* set host and port for the drizzle connection */

    drizzle_con_set_tcp(dc, (char *) peer->host, peer->port);

    /* ask drizzle to connect to the remote */

    ngx_log_debug7(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "drizzle connecting: host %s, port %d, dbname \"%V\", "
                   "user \"%V\", pass \"%V\", dc pass \"%s\", "
                   "protocol %d", peer->host, (int) peer->port, &dbname,
                   &peer->user, &peer->password, dc->password,
                   (int) peer->protocol);

    ret = drizzle_con_connect(dc);

    if (ret != DRIZZLE_RETURN_OK && ret != DRIZZLE_RETURN_IO_WAIT) {
        ngx_log_error(NGX_LOG_EMERG, pc->log, 0,
                      "drizzle: failed to connect: %d: %s in upstream \"%V\"",
                      (int) ret,
                      drizzle_error(&dscf->drizzle),
                      &peer->name);

        drizzle_con_free(dc);
        ngx_pfree(dscf->pool, dc);

#if defined(nginx_version) && (nginx_version >= 8017)
        return NGX_DECLINED;
#else
        dctx->status = NGX_HTTP_BAD_GATEWAY;
        goto failed;
#endif
    }

    dscf->active_conns++;

    /* add the file descriptor (fd) into an nginx connection structure */

    fd = drizzle_con_fd(dc);

    if (fd == -1) {
        ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                      "drizzle: failed to get the drizzle connection fd");

        goto invalid;
    }

    c = ngx_get_connection(fd, pc->log);
    if (c == NULL) {
        ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                      "drizzle: failed to get a free nginx connection");

        goto invalid;
    }

    c->log = pc->log;
    c->log_error = pc->log_error;
    c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);

    rev = c->read;
    wev = c->write;

    rev->log = pc->log;
    wev->log = pc->log;

    pc->connection = c;

    /* register the connection with the drizzle fd into the
     * nginx event model */

#if 0
    if (ngx_nonblocking(fd) == -1) {
        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
                      ngx_nonblocking_n " failed");

        goto invalid;
    }
#endif

    if (ret == DRIZZLE_RETURN_IO_WAIT) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "drizzle get peer: still connecting to remote");

        dp->state = state_db_connect;

        c->log->action = "connecting to drizzle upstream";

    } else {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "drizzle get peer: already connected to remote");

        /* to ensure send_query sets corresponding timers */
        dp->state = state_db_idle;
    }

    if (ngx_add_conn) {
        dd("Found ngx_add_conn");

        if (ngx_add_conn(c) == NGX_ERROR) {
            goto invalid;
        }

        if (ret == DRIZZLE_RETURN_IO_WAIT) {
            dd("returned NGX_AGAIN!!!");

            return NGX_AGAIN;
        }

        /* ret == DRIZZLE_RETURN_OK */

        wev->ready = 1;

        return NGX_DONE;
    }

    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {

        /* kqueue */

        event = NGX_CLEAR_EVENT;

    } else {

        /* select, poll, /dev/poll */

        event = NGX_LEVEL_EVENT;
    }

    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                "drizzle: failed to add connection into nginx event model");

        goto invalid;
    }

    if (ret == DRIZZLE_RETURN_IO_WAIT) {
        if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
            goto invalid;
        }

        return NGX_AGAIN;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "drizzle connected");

    wev->ready = 1;

    /* ret == DRIZZLE_RETURN_OK */

    return NGX_DONE;

invalid:

    ngx_http_upstream_drizzle_free_connection(pc->log, pc->connection,
                                              dc, dscf);

#if defined(nginx_version) && (nginx_version >= 8017)
    return NGX_ERROR;
#else

failed:

    /* a bit hack-ish way to return error response (setup part) */
    pc->connection = ngx_get_connection(0, pc->log);

    return NGX_AGAIN;
#endif
}


static void
ngx_http_upstream_drizzle_free_peer(ngx_peer_connection_t *pc,
    void *data, ngx_uint_t state)
{
    ngx_http_upstream_drizzle_peer_data_t   *dp = data;
    ngx_http_upstream_drizzle_srv_conf_t    *dscf;

#if defined(nginx_version) && (nginx_version < 8017)
    if (data == NULL) {
        return;
    }
#endif

    if (pc && pc->log) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "drizzle free peer");
    }

    dscf = dp->srv_conf;

    if (dp->drizzle_con && dp->drizzle_res.con) {
        dd("before drizzle result free");

        dd("%p vs. %p", dp->drizzle_res.con, dp->drizzle_con);

        drizzle_result_free(&dp->drizzle_res);

        dd("after drizzle result free");
    }

    if (pc != NULL) {
        if (dscf->max_cached) {
            ngx_http_drizzle_keepalive_free_peer(pc, dp, dscf, state);
        }

        if (pc->connection) {
            dd("actually free the drizzle connection");

            ngx_http_upstream_drizzle_free_connection(pc->log, pc->connection,
                                                      dp->drizzle_con, dscf);

            dp->drizzle_con = NULL;
            pc->connection = NULL;
        }
    }
}


static ngx_int_t
ngx_http_drizzle_output_filter(void *data, ngx_chain_t *in)
{
    ngx_http_request_t              *r = data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
        "drizzle output filter");

    /* just to ensure u->reinit_request always gets called for
     * upstream_next */
    r->upstream->request_sent = 1;

    (void) ngx_http_drizzle_process_events(r);

    /* discard the ret val from process events because
     * we can only return NGX_AGAIN here to prevent
     * ngx_http_upstream_process_header from being called
     * and avoid u->write_event_handler to be set to
     * ngx_http_upstream_dummy. */

    return NGX_AGAIN;
}


ngx_flag_t
ngx_http_upstream_drizzle_is_my_peer(const ngx_peer_connection_t    *peer)
{
    return (peer->get == ngx_http_upstream_drizzle_get_peer);
}


void
ngx_http_upstream_drizzle_free_connection(ngx_log_t *log,
    ngx_connection_t *c, drizzle_con_st *dc,
    ngx_http_upstream_drizzle_srv_conf_t *dscf)
{
    ngx_event_t  *rev, *wev;

    dd("drizzle free peer connection");

    dscf->active_conns--;

    if (dc) {
        dd("before con free");
        drizzle_con_free(dc);
        dd("after con free");
        ngx_pfree(dscf->pool, dc);
    }

    if (c) {
        /* dd("c pool: %p", c->pool); */
        rev = c->read;
        wev = c->write;

        if (rev->timer_set) {
            ngx_del_timer(rev);
        }

        if (wev->timer_set) {
            ngx_del_timer(wev);
        }

        if (ngx_del_conn) {
            ngx_del_conn(c, NGX_CLOSE_EVENT);

        } else {
            if (rev->active || rev->disabled) {
                ngx_del_event(rev, NGX_READ_EVENT, NGX_CLOSE_EVENT);
            }

            if (wev->active || wev->disabled) {
                ngx_del_event(wev, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
            }
        }

#if defined(nginx_version) && nginx_version >= 1007005
        if (rev->posted) {
#else
        if (rev->prev) {
#endif
            ngx_delete_posted_event(rev);
        }

#if defined(nginx_version) && nginx_version >= 1007005
        if (wev->posted) {
#else
        if (wev->prev) {
#endif
            ngx_delete_posted_event(wev);
        }

        rev->closed = 1;
        wev->closed = 1;

        ngx_free_connection(c);

        c->fd = (ngx_socket_t) -1;
    }
}


ngx_http_upstream_srv_conf_t *
ngx_http_upstream_drizzle_add(ngx_http_request_t *r, ngx_url_t *url)
{
    ngx_http_upstream_main_conf_t  *umcf;
    ngx_http_upstream_srv_conf_t  **uscfp;
    ngx_uint_t                      i;

    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);

    uscfp = umcf->upstreams.elts;

    for (i = 0; i < umcf->upstreams.nelts; i++) {

        if (uscfp[i]->host.len != url->host.len
            || ngx_strncasecmp(uscfp[i]->host.data, url->host.data,
                               url->host.len) != 0)
        {
            dd("upstream_add: host not match");
            continue;
        }

        if (uscfp[i]->port != url->port) {
            dd("upstream_add: port not match: %d != %d",
               (int) uscfp[i]->port, (int) url->port);
            continue;
        }

#if !defined(nginx_version) || nginx_version < 1011006
        if (uscfp[i]->default_port && url->default_port
            && uscfp[i]->default_port != url->default_port)
        {
            dd("upstream_add: default_port not match");
            continue;
        }
#endif

        return uscfp[i];
    }

    dd("No upstream found: %.*s", (int) url->host.len, url->host.data);

    return NULL;
}


static void
ngx_http_upstream_drizzle_cleanup(void *data)
{
    drizzle_st  *drizzle = data;

    drizzle_free(drizzle);
}

