/*
 * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
 * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
 * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef DDEBUG
#define DDEBUG 0
#endif

#include "ngx_postgres_ddebug.h"
#include "ngx_postgres_escape.h"
#include "ngx_postgres_handler.h"
#include "ngx_postgres_keepalive.h"
#include "ngx_postgres_module.h"
#include "ngx_postgres_output.h"
#include "ngx_postgres_upstream.h"
#include "ngx_postgres_util.h"
#include "ngx_postgres_variable.h"
#include "ngx_postgres_rewrite.h"


#define NGX_CONF_TAKE34  (NGX_CONF_TAKE3|NGX_CONF_TAKE4)


static ngx_command_t ngx_postgres_module_commands[] = {

    { ngx_string("postgres_server"),
      NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
      ngx_postgres_conf_server,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_keepalive"),
      NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
      ngx_postgres_conf_keepalive,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_pass"),
      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
      ngx_postgres_conf_pass,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_query"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
          NGX_HTTP_LIF_CONF|NGX_CONF_1MORE,
      ngx_postgres_conf_query,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_rewrite"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
          NGX_HTTP_LIF_CONF|NGX_CONF_2MORE,
      ngx_postgres_conf_rewrite,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_output"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
          NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
      ngx_postgres_conf_output,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_set"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE34,
      ngx_postgres_conf_set,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_escape"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
      ngx_postgres_conf_escape,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("postgres_connect_timeout"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_postgres_loc_conf_t, upstream.connect_timeout),
      NULL },

    { ngx_string("postgres_result_timeout"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_postgres_loc_conf_t, upstream.read_timeout),
      NULL },

      ngx_null_command
};

static ngx_http_variable_t ngx_postgres_module_variables[] = {

    { ngx_string("postgres_columns"), NULL,
      ngx_postgres_variable_columns, 0,
      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

    { ngx_string("postgres_rows"), NULL,
      ngx_postgres_variable_rows, 0,
      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

    { ngx_string("postgres_affected"), NULL,
      ngx_postgres_variable_affected, 0,
      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

    { ngx_string("postgres_query"), NULL,
      ngx_postgres_variable_query, 0,
      NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

    { ngx_null_string, NULL, NULL, 0, 0, 0 }
};

static ngx_http_module_t ngx_postgres_module_ctx = {
    ngx_postgres_add_variables,             /* preconfiguration */
    NULL,                                   /* postconfiguration */

    NULL,                                   /* create main configuration */
    NULL,                                   /* init main configuration */

    ngx_postgres_create_upstream_srv_conf,  /* create server configuration */
    NULL,                                   /* merge server configuration */

    ngx_postgres_create_loc_conf,           /* create location configuration */
    ngx_postgres_merge_loc_conf             /* merge location configuration */
};

ngx_module_t ngx_postgres_module = {
    NGX_MODULE_V1,
    &ngx_postgres_module_ctx,      /* module context */
    ngx_postgres_module_commands,  /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};

ngx_conf_bitmask_t ngx_postgres_http_methods[] = {
   { ngx_string("GET"),       NGX_HTTP_GET },
   { ngx_string("HEAD"),      NGX_HTTP_HEAD },
   { ngx_string("POST"),      NGX_HTTP_POST },
   { ngx_string("PUT"),       NGX_HTTP_PUT },
   { ngx_string("DELETE"),    NGX_HTTP_DELETE },
   { ngx_string("MKCOL"),     NGX_HTTP_MKCOL },
   { ngx_string("COPY"),      NGX_HTTP_COPY },
   { ngx_string("MOVE"),      NGX_HTTP_MOVE },
   { ngx_string("OPTIONS"),   NGX_HTTP_OPTIONS },
   { ngx_string("PROPFIND"),  NGX_HTTP_PROPFIND },
   { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH },
   { ngx_string("LOCK"),      NGX_HTTP_LOCK },
   { ngx_string("UNLOCK"),    NGX_HTTP_UNLOCK },
#if defined(nginx_version) && (nginx_version >= 8041)
   { ngx_string("PATCH"),     NGX_HTTP_PATCH },
#endif
    { ngx_null_string, 0 }
};

ngx_conf_enum_t ngx_postgres_upstream_mode_options[] = {
    { ngx_string("multi"),  0 },
    { ngx_string("single"), 1 },
    { ngx_null_string, 0 }
};

ngx_conf_enum_t ngx_postgres_upstream_overflow_options[] = {
    { ngx_string("ignore"), 0 },
    { ngx_string("reject"), 1 },
    { ngx_null_string, 0 }
};

ngx_conf_enum_t ngx_postgres_requirement_options[] = {
    { ngx_string("optional"), 0 },
    { ngx_string("required"), 1 },
    { ngx_null_string, 0 }
};

ngx_postgres_rewrite_enum_t ngx_postgres_rewrite_handlers[] = {
    { ngx_string("no_changes"), 0, ngx_postgres_rewrite_changes },
    { ngx_string("changes"),    1, ngx_postgres_rewrite_changes },
    { ngx_string("no_rows"),    2, ngx_postgres_rewrite_rows },
    { ngx_string("rows"),       3, ngx_postgres_rewrite_rows },
    { ngx_null_string, 0, NULL }
};

ngx_postgres_output_enum_t ngx_postgres_output_handlers[] = {
    { ngx_string("none"),         0, NULL },
    { ngx_string("rds"),          0, ngx_postgres_output_rds },
    { ngx_string("text") ,        0, ngx_postgres_output_text },
    { ngx_string("value"),        0, ngx_postgres_output_value },
    { ngx_string("binary_value"), 1, ngx_postgres_output_value },
    { ngx_null_string, 0, NULL }
};


ngx_int_t
ngx_postgres_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    dd("entering");

    for (v = ngx_postgres_module_variables; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            dd("returning NGX_ERROR");
            return NGX_ERROR;
        }

        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    dd("returning NGX_OK");
    return NGX_OK;
}

void *
ngx_postgres_create_upstream_srv_conf(ngx_conf_t *cf)
{
    ngx_postgres_upstream_srv_conf_t  *conf;
    ngx_pool_cleanup_t                *cln;

    dd("entering");

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_postgres_upstream_srv_conf_t));
    if (conf == NULL) {
        dd("returning NULL");
        return NULL;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     conf->peers = NULL
     *     conf->current = 0
     *     conf->servers = NULL
     *     conf->free = { NULL, NULL }
     *     conf->cache = { NULL, NULL }
     *     conf->active_conns = 0
     *     conf->reject = 0
     */

    conf->pool = cf->pool;

    /* enable keepalive (single) by default */
    conf->max_cached = 10;
    conf->single = 1;

    cln = ngx_pool_cleanup_add(cf->pool, 0);
    cln->handler = ngx_postgres_keepalive_cleanup;
    cln->data = conf;

    dd("returning");
    return conf;
}

void *
ngx_postgres_create_loc_conf(ngx_conf_t *cf)
{
    ngx_postgres_loc_conf_t  *conf;

    dd("entering");

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_postgres_loc_conf_t));
    if (conf == NULL) {
        dd("returning NULL");
        return NULL;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     conf->upstream.* = 0 / NULL
     *     conf->upstream_cv = NULL
     *     conf->query.methods_set = 0
     *     conf->query.methods = NULL
     *     conf->query.def = NULL
     *     conf->output_binary = 0
     */

    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;

    conf->rewrites = NGX_CONF_UNSET_PTR;
    conf->output_handler = NGX_CONF_UNSET_PTR;
    conf->variables = NGX_CONF_UNSET_PTR;

    /* the hardcoded values */
    conf->upstream.cyclic_temp_file = 0;
    conf->upstream.buffering = 1;
    conf->upstream.ignore_client_abort = 1;
    conf->upstream.send_lowat = 0;
    conf->upstream.bufs.num = 0;
    conf->upstream.busy_buffers_size = 0;
    conf->upstream.max_temp_file_size = 0;
    conf->upstream.temp_file_write_size = 0;
    conf->upstream.intercept_errors = 1;
    conf->upstream.intercept_404 = 1;
    conf->upstream.pass_request_headers = 0;
    conf->upstream.pass_request_body = 0;

    dd("returning");
    return conf;
}

char *
ngx_postgres_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_postgres_loc_conf_t  *prev = parent;
    ngx_postgres_loc_conf_t  *conf = child;

    dd("entering");

    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
                              prev->upstream.connect_timeout, 10000);

    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
                              prev->upstream.read_timeout, 30000);

    if ((conf->upstream.upstream == NULL) && (conf->upstream_cv == NULL)) {
        conf->upstream.upstream = prev->upstream.upstream;
        conf->upstream_cv = prev->upstream_cv;
    }

    if ((conf->query.def == NULL) && (conf->query.methods == NULL)) {
        conf->query.methods_set = prev->query.methods_set;
        conf->query.methods = prev->query.methods;
        conf->query.def = prev->query.def;
    }

    ngx_conf_merge_ptr_value(conf->rewrites, prev->rewrites, NULL);

    if (conf->output_handler == NGX_CONF_UNSET_PTR) {
        if (prev->output_handler == NGX_CONF_UNSET_PTR) {
            /* default */
            conf->output_handler = ngx_postgres_output_rds;
            conf->output_binary = 0;
        } else {
            /* merge */
            conf->output_handler = prev->output_handler;
            conf->output_binary = prev->output_binary;
        }
    }

    ngx_conf_merge_ptr_value(conf->variables, prev->variables, NULL);

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

/*
 * Based on: ngx_http_upstream.c/ngx_http_upstream_server
 * Copyright (C) Igor Sysoev
 */
char *
ngx_postgres_conf_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                         *value = cf->args->elts;
    ngx_postgres_upstream_srv_conf_t  *pgscf = conf;
    ngx_postgres_upstream_server_t    *pgs;
    ngx_http_upstream_srv_conf_t      *uscf;
    ngx_url_t                          u;
    ngx_uint_t                         i;

    dd("entering");

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    if (pgscf->servers == NULL) {
        pgscf->servers = ngx_array_create(cf->pool, 4,
                             sizeof(ngx_postgres_upstream_server_t));
        if (pgscf->servers == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        uscf->servers = pgscf->servers;
    }

    pgs = ngx_array_push(pgscf->servers);
    if (pgs == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    ngx_memzero(pgs, sizeof(ngx_postgres_upstream_server_t));

    /* parse the first name:port argument */

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.url = value[1];
    u.default_port = 5432; /* PostgreSQL default */

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

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pgs->addrs = u.addrs;
    pgs->naddrs = u.naddrs;
    pgs->port = u.port;

    /* parse various options */
    for (i = 2; i < cf->args->nelts; i++) {

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

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

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

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

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    uscf->peer.init_upstream = ngx_postgres_upstream_init;

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

char *
ngx_postgres_conf_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                         *value = cf->args->elts;
    ngx_postgres_upstream_srv_conf_t  *pgscf = conf;
    ngx_conf_enum_t                   *e;
    ngx_uint_t                         i, j;
    ngx_int_t                          n;

    dd("entering");

    if (pgscf->max_cached != 10 /* default */) {
        dd("returning");
        return "is duplicate";
    }

    if ((cf->args->nelts == 2) && (ngx_strcmp(value[1].data, "off") == 0)) {
        pgscf->max_cached = 0;

        dd("returning NGX_CONF_OK");
        return NGX_CONF_OK;
    }

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

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

            n = ngx_atoi(value[i].data, value[i].len);
            if (n == NGX_ERROR) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "postgres: invalid \"max\" value \"%V\""
                                   " in \"%V\" directive",
                                   &value[i], &cmd->name);

                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }

            pgscf->max_cached = (ngx_uint_t) n;

            continue;
        }

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

            e = ngx_postgres_upstream_mode_options;
            for (j = 0; e[j].name.len; j++) {
                if ((e[j].name.len == value[i].len)
                    && (ngx_strcasecmp(e[j].name.data, value[i].data) == 0))
                {
                    pgscf->single = e[j].value;
                    break;
                }
            }

            if (e[j].name.len == 0) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "postgres: invalid \"mode\" value \"%V\""
                                   " in \"%V\" directive",
                                   &value[i], &cmd->name);

                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }

            continue;
        }

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

            e = ngx_postgres_upstream_overflow_options;
            for (j = 0; e[j].name.len; j++) {
                if ((e[j].name.len == value[i].len)
                    && (ngx_strcasecmp(e[j].name.data, value[i].data) == 0))
                {
                    pgscf->reject = e[j].value;
                    break;
                }
            }

            if (e[j].name.len == 0) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "postgres: invalid \"overflow\" value \"%V\""
                                   " in \"%V\" directive",
                                   &value[i], &cmd->name);

                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }

            continue;
        }

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid parameter \"%V\" in"
                           " \"%V\" directive",
                           &value[i], &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

char *
ngx_postgres_conf_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                         *value = cf->args->elts;
    ngx_postgres_loc_conf_t           *pglcf = conf;
    ngx_http_core_loc_conf_t          *clcf;
    ngx_http_compile_complex_value_t   ccv;
    ngx_url_t                          url;

    dd("entering");

    if ((pglcf->upstream.upstream != NULL) || (pglcf->upstream_cv != NULL)) {
        dd("returning");
        return "is duplicate";
    }

    if (value[1].len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty upstream in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    clcf->handler = ngx_postgres_handler;

    if (clcf->name.data[clcf->name.len - 1] == '/') {
        clcf->auto_redirect = 1;
    }

    if (ngx_http_script_variables_count(&value[1])) {
        /* complex value */
        dd("complex value");

        pglcf->upstream_cv = ngx_palloc(cf->pool,
                                        sizeof(ngx_http_complex_value_t));
        if (pglcf->upstream_cv == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

        ccv.cf = cf;
        ccv.value = &value[1];
        ccv.complex_value = pglcf->upstream_cv;

        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        dd("returning NGX_CONF_OK");
        return NGX_CONF_OK;
    } else {
        /* simple value */
        dd("simple value");

        ngx_memzero(&url, sizeof(ngx_url_t));

        url.url = value[1];
        url.no_resolve = 1;

        pglcf->upstream.upstream = ngx_http_upstream_add(cf, &url, 0);
        if (pglcf->upstream.upstream == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        dd("returning NGX_CONF_OK");
        return NGX_CONF_OK;
    }
}

char *
ngx_postgres_conf_query(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                         *value = cf->args->elts;
    ngx_str_t                          sql = value[cf->args->nelts - 1];
    ngx_postgres_loc_conf_t           *pglcf = conf;
    ngx_http_compile_complex_value_t   ccv;
    ngx_postgres_mixed_t              *query;
    ngx_conf_bitmask_t                *b;
    ngx_uint_t                         methods, i, j;

    dd("entering");

    if (sql.len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty query in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (cf->args->nelts == 2) {
        /* default query */
        dd("default query");

        if (pglcf->query.def != NULL) {
            dd("returning");
            return "is duplicate";
        }

        pglcf->query.def = ngx_palloc(cf->pool, sizeof(ngx_postgres_mixed_t));
        if (pglcf->query.def == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        methods = 0xFFFF;
        query = pglcf->query.def;
    } else {
        /* method-specific query */
        dd("method-specific query");

        methods = 0;

        for (i = 1; i < cf->args->nelts - 1; i++) {
            b = ngx_postgres_http_methods;
            for (j = 0; b[j].name.len; j++) {
                if ((b[j].name.len == value[i].len)
                    && (ngx_strcasecmp(b[j].name.data, value[i].data) == 0))
                {
                    if (pglcf->query.methods_set & b[j].mask) {
                        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                           "postgres: method \"%V\" is"
                                           " duplicate in \"%V\" directive",
                                           &value[i], &cmd->name);

                        dd("returning NGX_CONF_ERROR");
                        return NGX_CONF_ERROR;
                    }

                    methods |= b[j].mask;
                    break;
                }
            }

            if (b[j].name.len == 0) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "postgres: invalid method \"%V\""
                                   " in \"%V\" directive",
                                   &value[i], &cmd->name);

                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }
        }

        if (pglcf->query.methods == NULL) {
            pglcf->query.methods = ngx_array_create(cf->pool, 4,
                                       sizeof(ngx_postgres_mixed_t));
            if (pglcf->query.methods == NULL) {
                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }
        }

        query = ngx_array_push(pglcf->query.methods);
        if (query == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        pglcf->query.methods_set |= methods;
    }

    if (ngx_http_script_variables_count(&sql)) {
        /* complex value */
        dd("complex value");

        query->key = methods;

        query->cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
        if (query->cv == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

        ccv.cf = cf;
        ccv.value = &sql;
        ccv.complex_value = query->cv;

        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }
    } else {
        /* simple value */
        dd("simple value");

        query->key = methods;
        query->sv = sql;
        query->cv = NULL;
    }

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

char *
ngx_postgres_conf_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                    *value = cf->args->elts;
    ngx_str_t                     what = value[cf->args->nelts - 2];
    ngx_str_t                     to = value[cf->args->nelts - 1];
    ngx_postgres_loc_conf_t      *pglcf = conf;
    ngx_postgres_rewrite_conf_t  *pgrcf;
    ngx_postgres_rewrite_t       *rewrite;
    ngx_postgres_rewrite_enum_t  *e;
    ngx_conf_bitmask_t           *b;
    ngx_uint_t                    methods, keep_body, i, j;

    dd("entering");

    e = ngx_postgres_rewrite_handlers;
    for (i = 0; e[i].name.len; i++) {
        if ((e[i].name.len == what.len)
            && (ngx_strcasecmp(e[i].name.data, what.data) == 0))
        {
            break;
        }
    }

    if (e[i].name.len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid condition \"%V\""
                           " in \"%V\" directive", &what, &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (pglcf->rewrites == NGX_CONF_UNSET_PTR) {
        pglcf->rewrites = ngx_array_create(cf->pool, 2,
                                           sizeof(ngx_postgres_rewrite_conf_t));
        if (pglcf->rewrites == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }
    } else {
        pgrcf = pglcf->rewrites->elts;
        for (j = 0; j < pglcf->rewrites->nelts; j++) {
            if (pgrcf[j].key == e[i].key) {
                pgrcf = &pgrcf[j];
                goto found;
            }
        }
    }

    pgrcf = ngx_array_push(pglcf->rewrites);
    if (pgrcf == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    ngx_memzero(pgrcf, sizeof(ngx_postgres_rewrite_conf_t));

    pgrcf->key = e[i].key;
    pgrcf->handler = e[i].handler;

found:
    if (cf->args->nelts == 3) {
        /* default rewrite */
        dd("default rewrite");

        if (pgrcf->def != NULL) {
            dd("returning");
            return "is duplicate";
        }

        pgrcf->def = ngx_palloc(cf->pool, sizeof(ngx_postgres_rewrite_t));
        if (pgrcf->def == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        methods = 0xFFFF;
        rewrite = pgrcf->def;
    } else {
        /* method-specific rewrite */
        dd("method-specific rewrite");

        methods = 0;

        for (i = 1; i < cf->args->nelts - 2; i++) {
            b = ngx_postgres_http_methods;
            for (j = 0; b[j].name.len; j++) {
                if ((b[j].name.len == value[i].len)
                    && (ngx_strcasecmp(b[j].name.data, value[i].data) == 0))
                {
                    if (pgrcf->methods_set & b[j].mask) {
                        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                           "postgres: method \"%V\" for"
                                           " condition \"%V\" is duplicate"
                                           " in \"%V\" directive",
                                           &value[i], &what, &cmd->name);

                        dd("returning NGX_CONF_ERROR");
                        return NGX_CONF_ERROR;
                    }

                    methods |= b[j].mask;
                    break;
                }
            }

            if (b[j].name.len == 0) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "postgres: invalid method \"%V\" for"
                                   " condition \"%V\" in \"%V\" directive",
                                   &value[i], &what, &cmd->name);

                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }
        }

        if (pgrcf->methods == NULL) {
            pgrcf->methods = ngx_array_create(cf->pool, 4,
                                              sizeof(ngx_postgres_rewrite_t));
            if (pgrcf->methods == NULL) {
                dd("returning NGX_CONF_ERROR");
                return NGX_CONF_ERROR;
            }
        }

        rewrite = ngx_array_push(pgrcf->methods);
        if (rewrite == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        pgrcf->methods_set |= methods;
    }

    if (to.data[0] == '=') {
        keep_body = 1;
        to.len--;
        to.data++;
    } else {
        keep_body = 0;
    }

    rewrite->key = methods;
    rewrite->status = ngx_atoi(to.data, to.len);
    if ((rewrite->status == NGX_ERROR)
        || (rewrite->status < NGX_HTTP_OK)
        || (rewrite->status > NGX_HTTP_INSUFFICIENT_STORAGE)
        || ((rewrite->status >= NGX_HTTP_SPECIAL_RESPONSE)
            && (rewrite->status < NGX_HTTP_BAD_REQUEST)))
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid status value \"%V\" for"
                           " condition \"%V\" in \"%V\" directive",
                           &to, &what, &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (keep_body) {
        rewrite->status = -rewrite->status;
    }

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

char *
ngx_postgres_conf_output(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                   *value = cf->args->elts;
    ngx_postgres_loc_conf_t     *pglcf = conf;
    ngx_postgres_output_enum_t  *e;
    ngx_uint_t                   i;

    dd("entering");

    if (pglcf->output_handler != NGX_CONF_UNSET_PTR) {
        dd("returning");
        return "is duplicate";
    }

    e = ngx_postgres_output_handlers;
    for (i = 0; e[i].name.len; i++) {
        if ((e[i].name.len == value[1].len)
            && (ngx_strcasecmp(e[i].name.data, value[1].data) == 0))
        {
            pglcf->output_handler = e[i].handler;
            break;
        }
    }

    if (e[i].name.len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid output format \"%V\""
                           " in \"%V\" directive", &value[1], &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pglcf->output_binary = e[i].binary;

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

char *
ngx_postgres_conf_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                *value = cf->args->elts;
    ngx_postgres_loc_conf_t  *pglcf = conf;
    ngx_postgres_variable_t  *pgvar;
    ngx_conf_enum_t          *e;
    ngx_int_t                 idx;
    ngx_uint_t                i;

    dd("entering");

    if (value[1].len < 2) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty variable name in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (value[1].data[0] != '$') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid variable name \"%V\""
                           " in \"%V\" directive", &value[1], &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    value[1].len--;
    value[1].data++;

    if (value[3].len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty column in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (pglcf->variables == NGX_CONF_UNSET_PTR) {
        pglcf->variables = ngx_array_create(cf->pool, 4,
                                            sizeof(ngx_postgres_variable_t));
        if (pglcf->variables == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }
    }

    pgvar = ngx_array_push(pglcf->variables);
    if (pgvar == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pgvar->idx = pglcf->variables->nelts - 1;

    pgvar->var = ngx_http_add_variable(cf, &value[1], 0);
    if (pgvar->var == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    idx = ngx_http_get_variable_index(cf, &value[1]);
    if (idx == NGX_ERROR) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    /*
     * Check if "$variable" was previously defined,
     * back-off even if it was marked as "CHANGEABLE".
     */
    if (pgvar->var->get_handler != NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: variable \"$%V\" is duplicate"
                           " in \"%V\" directive", &value[1], &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pgvar->var->get_handler = ngx_postgres_variable_get_custom;
    pgvar->var->data = (uintptr_t) pgvar;

    pgvar->value.row = ngx_atoi(value[2].data, value[2].len);
    if (pgvar->value.row == NGX_ERROR) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid row number \"%V\""
                           " in \"%V\" directive", &value[2], &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pgvar->value.column = ngx_atoi(value[3].data, value[3].len);
    if (pgvar->value.column == NGX_ERROR) {
        /* get column by name */
        pgvar->value.col_name = ngx_pnalloc(cf->pool, value[3].len + 1);
        if (pgvar->value.col_name == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        (void) ngx_cpystrn(pgvar->value.col_name,
                           value[3].data, value[3].len + 1);
    }

    if (cf->args->nelts == 4) {
        /* default value */
        pgvar->value.required = 0;
    } else {
        /* user-specified value */
        e = ngx_postgres_requirement_options;
        for (i = 0; e[i].name.len; i++) {
            if ((e[i].name.len == value[4].len)
                && (ngx_strcasecmp(e[i].name.data, value[4].data) == 0))
            {
                pgvar->value.required = e[i].value;
                break;
            }
        }

        if (e[i].name.len == 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "postgres: invalid requirement option \"%V\""
                               " in \"%V\" directive", &value[4], &cmd->name);

            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }
    }

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

/*
 * Based on: ngx_http_rewrite_module.c/ngx_http_rewrite_set
 * Copyright (C) Igor Sysoev
 */
char *
ngx_postgres_conf_escape(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                           *value = cf->args->elts;
    ngx_str_t                            src = value[cf->args->nelts - 1];
    ngx_int_t                            index;
    ngx_http_variable_t                 *v;
    ngx_http_script_var_code_t          *vcode;
    ngx_http_script_var_handler_code_t  *vhcode;
    ngx_postgres_rewrite_loc_conf_t     *rlcf;
    ngx_postgres_escape_t               *pge;
    ngx_str_t                            dst;
    ngx_uint_t                           empty;

    dd("entering");

    if ((src.len != 0) && (src.data[0] == '=')) {
        empty = 1;
        src.len--;
        src.data++;
    } else {
        empty = 0;
    }

    if (src.len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty value in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (cf->args->nelts == 2) {
        dst = src;
    } else {
        dst = value[1];
    }

    if (dst.len < 2) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: empty variable name in \"%V\" directive",
                           &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (dst.data[0] != '$') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "postgres: invalid variable name \"%V\""
                           " in \"%V\" directive", &dst, &cmd->name);

        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    dst.len--;
    dst.data++;

    v = ngx_http_add_variable(cf, &dst, NGX_HTTP_VAR_CHANGEABLE);
    if (v == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    index = ngx_http_get_variable_index(cf, &dst);
    if (index == NGX_ERROR) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    if (v->get_handler == NULL
        && ngx_strncasecmp(dst.data, (u_char *) "http_", 5) != 0
        && ngx_strncasecmp(dst.data, (u_char *) "sent_http_", 10) != 0
        && ngx_strncasecmp(dst.data, (u_char *) "upstream_http_", 14) != 0)
    {
        v->get_handler = ngx_postgres_rewrite_var;
        v->data = index;
    }

    rlcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_rewrite_module);

    if (ngx_postgres_rewrite_value(cf, rlcf, &src) != NGX_CONF_OK) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pge = ngx_http_script_start_code(cf->pool, &rlcf->codes,
                                     sizeof(ngx_postgres_escape_t));
    if (pge == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    pge->code = ngx_postgres_escape_string;
    pge->empty = empty;

    if (v->set_handler) {
        vhcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
                                   sizeof(ngx_http_script_var_handler_code_t));
        if (vhcode == NULL) {
            dd("returning NGX_CONF_ERROR");
            return NGX_CONF_ERROR;
        }

        vhcode->code = ngx_http_script_var_set_handler_code;
        vhcode->handler = v->set_handler;
        vhcode->data = v->data;

        dd("returning NGX_CONF_OK");
        return NGX_CONF_OK;
    }

    vcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
                                       sizeof(ngx_http_script_var_code_t));
    if (vcode == NULL) {
        dd("returning NGX_CONF_ERROR");
        return NGX_CONF_ERROR;
    }

    vcode->code = ngx_http_script_set_var_code;
    vcode->index = (uintptr_t) index;

    dd("returning NGX_CONF_OK");
    return NGX_CONF_OK;
}

ngx_http_upstream_srv_conf_t *
ngx_postgres_find_upstream(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;

    dd("entering");

    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("host doesn't match");
            continue;
        }

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

        if (uscfp[i]->default_port && url->default_port
            && (uscfp[i]->default_port != url->default_port))
        {
            dd("default_port doesn't match");
            continue;
        }

        dd("returning");
        return uscfp[i];
    }

    dd("returning NULL");
    return NULL;
}
