/* -------------------------------------------------------------------------
 *
 * parse_param.cpp
 *	  handle parameters in parser
 *
 * This code covers two cases that are used within the core backend:
 *		* a fixed list of parameters with known types
 *		* an expandable list of parameters whose types can optionally
 *		  be determined from context
 * In both cases, only explicit $n references (ParamRef nodes) are supported.
 *
 * Note that other approaches to parameters are possible using the parser
 * hooks defined in ParseState.
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/backend/parser/parse_param.cpp
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"
#include "knl/knl_variable.h"

#include <limits.h>

#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_param.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"

typedef struct FixedParamState {
    Oid* paramTypes; /* array of parameter type OIDs */
    int numParams;   /* number of array entries */
} FixedParamState;

/*
 * In the varparams case, the caller-supplied OID array (if any) can be
 * re-palloc'd larger at need.  A zero array entry means that parameter number
 * hasn't been seen, while UNKNOWNOID means the parameter has been used but
 * its type is not yet known.
 */
typedef struct VarParamState {
    Oid** paramTypes; /* array of parameter type OIDs */
    int* numParams;   /* number of array entries */
#ifndef ENABLE_MULTIPLE_NODES	
    char **paramTypeNames;
#endif	
} VarParamState;

static Node* fixed_paramref_hook(ParseState* pstate, ParamRef* pref);
static Node* variable_paramref_hook(ParseState* pstate, ParamRef* pref);
static Node* variable_coerce_param_hook(
    ParseState* pstate, Param* param, Oid targetTypeId, int32 targetTypeMod, int location);
static bool check_parameter_resolution_walker(Node* node, ParseState* pstate);


/*
 * Set up to process a query containing references to fixed parameters.
 */
void parse_fixed_parameters(ParseState* pstate, Oid* paramTypes, int numParams)
{
    FixedParamState* parstate = (FixedParamState*)palloc(sizeof(FixedParamState));

    parstate->paramTypes = paramTypes;
    parstate->numParams = numParams;
    pstate->p_ref_hook_state = (void*)parstate;
    pstate->p_paramref_hook = fixed_paramref_hook;
    /* no need to use p_coerce_param_hook */
}

/*
 * Set up to process a query containing references to variable parameters.
 */
void parse_variable_parameters(ParseState* pstate, Oid** paramTypes, int* numParams)
{
    VarParamState* parstate = (VarParamState*)palloc(sizeof(VarParamState));

    parstate->paramTypes = paramTypes;
    parstate->numParams = numParams;
    pstate->p_ref_hook_state = (void*)parstate;
    pstate->p_paramref_hook = variable_paramref_hook;
    pstate->p_coerce_param_hook = variable_coerce_param_hook;
}

/*
 * Transform a ParamRef using fixed parameter types.
 */
static Node* fixed_paramref_hook(ParseState* pstate, ParamRef* pref)
{
    FixedParamState* parstate = (FixedParamState*)pstate->p_ref_hook_state;
    int paramno = pref->number;
    Param* param = NULL;

    /* Check parameter number is valid */
    if (paramno <= 0 || paramno > parstate->numParams || !OidIsValid(parstate->paramTypes[paramno - 1])) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_PARAMETER),
                errmsg("there is no parameter $%d", paramno),
                parser_errposition(pstate, pref->location)));
    }
    param = makeNode(Param);
    param->paramkind = PARAM_EXTERN;
    param->paramid = paramno;
    param->paramtype = parstate->paramTypes[paramno - 1];
    param->paramtypmod = -1;
    if (OidIsValid(GetCollationConnection()) && IsSupportCharsetType(param->paramtype)) {
        param->paramcollid = GetCollationConnection();
    } else {
        param->paramcollid = get_typcollation(param->paramtype);
    }
    param->location = pref->location;
    param->tableOfIndexTypeList = NULL;
    param->is_bind_param = true;

    return (Node*)param;
}

/*
 * Transform a ParamRef using variable parameter types.
 *
 * The only difference here is we must enlarge the parameter type array
 * as needed.
 */
static Node* variable_paramref_hook(ParseState* pstate, ParamRef* pref)
{
    VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;
    uint32 paramno = pref->number;
    Oid* pptype = NULL;
    Param* param = NULL;

    /* Check parameter number is in range */
    if (paramno <= 0 || paramno > INT_MAX / sizeof(Oid)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_PARAMETER),
                errmsg("there is no parameter $%u", paramno),
                parser_errposition(pstate, pref->location)));
    }
    if (paramno > (uint32)*parstate->numParams) {
        errno_t rc = EOK;
        /* Need to enlarge param array */
        if (*parstate->paramTypes) {
            *parstate->paramTypes = (Oid*)repalloc(*parstate->paramTypes, paramno * sizeof(Oid));
        } else {
            *parstate->paramTypes = (Oid*)palloc(paramno * sizeof(Oid));
        }
        /* Zero out the previously-unreferenced slots */
        rc = memset_s(*parstate->paramTypes + *parstate->numParams,
            (paramno - *parstate->numParams) * sizeof(Oid),
            0,
            (paramno - *parstate->numParams) * sizeof(Oid));
        securec_check(rc, "\0", "\0");
        *parstate->numParams = paramno;
    }

    /* Locate param's slot in array */
    pptype = &(*parstate->paramTypes)[paramno - 1];

    /* If not seen before, initialize to UNKNOWN type */
    if (*pptype == InvalidOid) {
        *pptype = UNKNOWNOID;
    }
    param = makeNode(Param);
    param->paramkind = PARAM_EXTERN;
    param->paramid = paramno;
    param->paramtype = *pptype;
    param->paramtypmod = -1;
    if (OidIsValid(GetCollationConnection()) && IsSupportCharsetType(param->paramtype)) {
        param->paramcollid = GetCollationConnection();
    } else {
        param->paramcollid = get_typcollation(param->paramtype);
    }
    param->location = pref->location;
    param->tableOfIndexTypeList = NULL;
    param->is_bind_param = true;

    return (Node*)param;
}

/*
 * Coerce a Param to a query-requested datatype, in the varparams case.
 */
static Node* variable_coerce_param_hook(
    ParseState* pstate, Param* param, Oid targetTypeId, int32 targetTypeMod, int location)
{
    if (param->paramkind == PARAM_EXTERN && param->paramtype == UNKNOWNOID) {
        /*
         * Input is a Param of previously undetermined type, and we want to
         * update our knowledge of the Param's type.
         */
        VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;
        Oid* paramTypes = *parstate->paramTypes;
        int paramno = param->paramid;

        if (paramno <= 0 || /* shouldn't happen, but... */
            paramno > *parstate->numParams) {
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_PARAMETER),
                    errmsg("there is no parameter $%d", paramno),
                    parser_errposition(pstate, param->location)));
        }
        if (paramTypes[paramno - 1] == UNKNOWNOID) {
            /* We've successfully resolved the type */
            paramTypes[paramno - 1] = targetTypeId;
        } else if (paramTypes[paramno - 1] == targetTypeId) {
            /* We previously resolved the type, and it matches */
        } else {
            /* Ooops */
            ereport(ERROR,
                (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
                    errmsg("inconsistent types deduced for parameter $%d", paramno),
                    errdetail("%s versus %s", format_type_be(paramTypes[paramno - 1]), format_type_be(targetTypeId)),
                    parser_errposition(pstate, param->location)));
        }

        param->paramtype = targetTypeId;

        /*
         * Note: it is tempting here to set the Param's paramtypmod to
         * targetTypeMod, but that is probably unwise because we have no
         * infrastructure that enforces that the value delivered for a Param
         * will match any particular typmod.  Leaving it -1 ensures that a
         * run-time length check/coercion will occur if needed.
         */
        param->paramtypmod = -1;

        /*
         * This module always sets a Param's collation to be the default for
         * its datatype.  If that's not what you want, you should be using the
         * more general parser substitution hooks.
         */
        param->paramcollid = get_typcollation(param->paramtype);

        /* Use the leftmost of the param's and coercion's locations */
        if (location >= 0 && (param->location < 0 || location < param->location)) {
            param->location = location;
        }
        return (Node*)param;
    }

    /* Else signal to proceed with normal coercion */
    return NULL;
}

/*
 * Check for consistent assignment of variable parameters after completion
 * of parsing with parse_variable_parameters.
 *
 * Note: this code intentionally does not check that all parameter positions
 * were used, nor that all got non-UNKNOWN types assigned.	Caller of parser
 * should enforce that if it's important.
 */
void check_variable_parameters(ParseState* pstate, Query* query)
{
    VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;

    /* If numParams is zero then no Params were generated, so no work */
    if (*parstate->numParams > 0) {
        (void)query_tree_walker(query, (bool (*)())check_parameter_resolution_walker, (void*)pstate, 0);
    }
}

/*
 * Traverse a fully-analyzed tree to verify that parameter symbols
 * match their types.  We need this because some Params might still
 * be UNKNOWN, if there wasn't anything to force their coercion,
 * and yet other instances seen later might have gotten coerced.
 */
static bool check_parameter_resolution_walker(Node* node, ParseState* pstate)
{
    if (node == NULL) {
        return false;
    }
    if (IsA(node, Param)) {
        Param* param = (Param*)node;

        if (param->paramkind == PARAM_EXTERN) {
            VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;
            int paramno = param->paramid;

            if (paramno <= 0 || /* shouldn't happen, but... */
                paramno > *parstate->numParams) {
                ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_PARAMETER),
                        errmsg("there is no parameter $%d", paramno),
                        parser_errposition(pstate, param->location)));
            }
            if (param->paramtype != (*parstate->paramTypes)[paramno - 1]) {
                ereport(ERROR,
                    (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
                        errmsg("could not determine data type of parameter $%d", paramno),
                        parser_errposition(pstate, param->location)));
            }
        }
        return false;
    }
    if (IsA(node, Query)) {
        /* Recurse into RTE subquery or not-yet-planned sublink subquery */
        return query_tree_walker((Query*)node, (bool (*)())check_parameter_resolution_walker, (void*)pstate, 0);
    }
    return expression_tree_walker(node, (bool (*)())check_parameter_resolution_walker, (void*)pstate);
}