/**********************************************************************
 * pl_funcs.c		- Misc functins for the PL/pgSQL
 *			  procedural language
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.1 1998/08/24 19:14:48 momjian Exp $
 *
 *    This software is copyrighted by Jan Wieck - Hamburg.
 *
 *    The author hereby grants permission  to  use,  copy,  modify,
 *    distribute,  and  license this software and its documentation
 *    for any purpose, provided that existing copyright notices are
 *    retained  in  all  copies  and  that  this notice is included
 *    verbatim in any distributions. No written agreement, license,
 *    or  royalty  fee  is required for any of the authorized uses.
 *    Modifications to this software may be  copyrighted  by  their
 *    author  and  need  not  follow  the licensing terms described
 *    here, provided that the new terms are  clearly  indicated  on
 *    the first page of each file where they apply.
 *
 *    IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
 *    PARTY  FOR  DIRECT,   INDIRECT,   SPECIAL,   INCIDENTAL,   OR
 *    CONSEQUENTIAL   DAMAGES  ARISING  OUT  OF  THE  USE  OF  THIS
 *    SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
 *    IF  THE  AUTHOR  HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
 *    DAMAGE.
 *
 *    THE  AUTHOR  AND  DISTRIBUTORS  SPECIFICALLY   DISCLAIM   ANY
 *    WARRANTIES,  INCLUDING,  BUT  NOT  LIMITED  TO,  THE  IMPLIED
 *    WARRANTIES  OF  MERCHANTABILITY,  FITNESS  FOR  A  PARTICULAR
 *    PURPOSE,  AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON
 *    AN "AS IS" BASIS, AND THE AUTHOR  AND  DISTRIBUTORS  HAVE  NO
 *    OBLIGATION   TO   PROVIDE   MAINTENANCE,   SUPPORT,  UPDATES,
 *    ENHANCEMENTS, OR MODIFICATIONS.
 *
 **********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>

#include "plpgsql.h"
#include "pl.tab.h"


/* ----------
 * Local variables for the namestack handling
 * ----------
 */
static PLpgSQL_ns		*ns_current = NULL;
static bool			ns_localmode = false;


/* ----------
 * plpgsql_dstring_init			Dynamic string initialization
 * ----------
 */
void plpgsql_dstring_init(PLpgSQL_dstring *ds)
{
    ds->value = palloc(ds->alloc = 512);
    ds->used = 0;
}


/* ----------
 * plpgsql_dstring_free			Dynamic string destruction
 * ----------
 */
void plpgsql_dstring_free(PLpgSQL_dstring *ds)
{
    pfree(ds->value);
}


/* ----------
 * plpgsql_dstring_append		Dynamic string extending
 * ----------
 */
void plpgsql_dstring_append(PLpgSQL_dstring *ds, char *str)
{
    int len = strlen(str);

    if (ds->used + len + 1 > ds->alloc) {
        ds->alloc *= 2;
	ds->value = repalloc(ds->value, ds->alloc);
    }

    strcpy(&(ds->value[ds->used]), str);
    ds->used += len;
}


/* ----------
 * plpgsql_dstring_get			Dynamic string get value
 * ----------
 */
char *plpgsql_dstring_get(PLpgSQL_dstring *ds)
{
    return ds->value;
}


/* ----------
 * plpgsql_ns_init			Initialize the namestack
 * ----------
 */
void plpgsql_ns_init(void)
{
    ns_current   = NULL;
    ns_localmode = false;
}


/* ----------
 * plpgsql_ns_setlocal			Tell plpgsql_ns_lookup to or to
 *					not look into the current level
 *					only.
 * ----------
 */
bool plpgsql_ns_setlocal(bool flag)
{
    bool oldstate;

    oldstate = ns_localmode;
    ns_localmode = flag;
    return oldstate;
}


/* ----------
 * plpgsql_ns_push			Enter a new namestack level
 * ----------
 */
void plpgsql_ns_push(char *label)
{
    PLpgSQL_ns	*new;

    new = palloc(sizeof(PLpgSQL_ns));
    memset(new, 0, sizeof(PLpgSQL_ns));
    new->upper = ns_current;
    ns_current = new;

    plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, 0, label);
}


/* ----------
 * plpgsql_ns_pop			Return to the previous level
 * ----------
 */
void plpgsql_ns_pop()
{
    int		i;
    PLpgSQL_ns	*old;

    old = ns_current;
    ns_current = old->upper;

    for (i = 0; i < old->items_used; i++) {
        pfree(old->items[i]);
    }
    pfree(old->items);
    pfree(old);
}


/* ----------
 * plpgsql_ns_additem			Add an item to the current
 *					namestack level
 * ----------
 */
void plpgsql_ns_additem(int itemtype, int itemno, char *name)
{
    PLpgSQL_ns		*ns = ns_current;
    PLpgSQL_nsitem	*nse;

    if (name == NULL)
	    name = "";

    if (ns->items_used == ns->items_alloc) {
	if (ns->items_alloc == 0) {
	    ns->items_alloc = 32;
	    ns->items = palloc(sizeof(PLpgSQL_nsitem *) * ns->items_alloc);
	} else {
	    ns->items_alloc *= 2;
	    ns->items = repalloc(ns->items, 
	    		sizeof(PLpgSQL_nsitem *) * ns->items_alloc);
	}
    }

    nse = palloc(sizeof(PLpgSQL_nsitem) + strlen(name));
    nse->itemtype = itemtype;
    nse->itemno   = itemno;
    strcpy(nse->name, name);
    ns->items[ns->items_used++] = nse;
}


/* ----------
 * plpgsql_ns_lookup			Lookup for a word in the namestack
 * ----------
 */
PLpgSQL_nsitem *plpgsql_ns_lookup(char *name, char *label)
{
    PLpgSQL_ns	*ns;
    int		i;

    /* ----------
     * If a label is specified, lookup only in that
     * ----------
     */
    if (label != NULL) {
        for (ns = ns_current; ns != NULL; ns = ns->upper) {
	    if (!strcmp(ns->items[0]->name, label)) {
	        for (i = 1; i < ns->items_used; i++) {
		    if (!strcmp(ns->items[i]->name, name)) {
		        return ns->items[i];
		    }
		}
		return NULL;	/* name not found in specified label */
	    }
	}
	return NULL;	/* label not found */
    }

    /* ----------
     * No label given, lookup for visible labels ignoring localmode
     * ----------
     */
    for (ns = ns_current; ns != NULL; ns = ns->upper) {
	if (!strcmp(ns->items[0]->name, name)) {
	    return ns->items[0];
        }
    }

    /* ----------
     * Finally lookup name in the namestack
     * ----------
     */
    for (ns = ns_current; ns != NULL; ns = ns->upper) {
	for (i = 1; i < ns->items_used; i++) {
	    if (!strcmp(ns->items[i]->name, name))
		    return ns->items[i];
	}
	if (ns_localmode) {
	    return NULL;	/* name not found in current namespace */
	}
    }

    return NULL;
}


/* ----------
 * plpgsql_ns_rename			Rename a namespace entry
 * ----------
 */
void plpgsql_ns_rename(char *oldname, char *newname)
{
    PLpgSQL_ns		*ns;
    PLpgSQL_nsitem	*newitem;
    int			i;

    /* ----------
     * Lookup in the current namespace only
     * ----------
     */
    /* ----------
     * Lookup name in the namestack
     * ----------
     */
    for (ns = ns_current; ns != NULL; ns = ns->upper) {
	for (i = 1; i < ns->items_used; i++) {
	    if (!strcmp(ns->items[i]->name, oldname)) {
		newitem = palloc(sizeof(PLpgSQL_nsitem) + strlen(newname));
		newitem->itemtype = ns->items[i]->itemtype;
		newitem->itemno   = ns->items[i]->itemno;
		strcpy(newitem->name, newname);

		pfree(oldname);
		pfree(newname);

		pfree(ns->items[i]);
		ns->items[i] = newitem;
		return;
	    }
	}
    }

    elog(ERROR, "there is no variable '%s' in the current block", oldname);
}


/* ----------
 * plpgsql_tolower			Translate a string in place to
 *					lower case
 * ----------
 */
char *plpgsql_tolower(char *s)
{
    char *cp;

    for (cp = s; *cp; cp++) {
        if (isupper(*cp)) *cp = tolower(*cp);
    }

    return s;
}





/**********************************************************************
 * Debug functions for analyzing the compiled code
 **********************************************************************/
static int	dump_indent;

static void dump_ind();
static void dump_stmt(PLpgSQL_stmt *stmt);
static void dump_block(PLpgSQL_stmt_block *block);
static void dump_assign(PLpgSQL_stmt_assign *stmt);
static void dump_if(PLpgSQL_stmt_if *stmt);
static void dump_loop(PLpgSQL_stmt_loop *stmt);
static void dump_while(PLpgSQL_stmt_while *stmt);
static void dump_fori(PLpgSQL_stmt_fori *stmt);
static void dump_fors(PLpgSQL_stmt_fors *stmt);
static void dump_select(PLpgSQL_stmt_select *stmt);
static void dump_exit(PLpgSQL_stmt_exit *stmt);
static void dump_return(PLpgSQL_stmt_return *stmt);
static void dump_raise(PLpgSQL_stmt_raise *stmt);
static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
static void dump_expr(PLpgSQL_expr *expr);


static void dump_ind()
{
    int i;
    for (i = 0; i < dump_indent; i++) {
        printf(" ");
    }
}

static void dump_stmt(PLpgSQL_stmt *stmt)
{
    printf("%3d:", stmt->lineno);
    switch (stmt->cmd_type) {
        case PLPGSQL_STMT_BLOCK:
		dump_block((PLpgSQL_stmt_block *)stmt);
		break;
        case PLPGSQL_STMT_ASSIGN:
		dump_assign((PLpgSQL_stmt_assign *)stmt);
		break;
        case PLPGSQL_STMT_IF:
		dump_if((PLpgSQL_stmt_if *)stmt);
		break;
        case PLPGSQL_STMT_LOOP:
		dump_loop((PLpgSQL_stmt_loop *)stmt);
		break;
        case PLPGSQL_STMT_WHILE:
		dump_while((PLpgSQL_stmt_while *)stmt);
		break;
        case PLPGSQL_STMT_FORI:
		dump_fori((PLpgSQL_stmt_fori *)stmt);
		break;
        case PLPGSQL_STMT_FORS:
		dump_fors((PLpgSQL_stmt_fors *)stmt);
		break;
        case PLPGSQL_STMT_SELECT:
		dump_select((PLpgSQL_stmt_select *)stmt);
		break;
        case PLPGSQL_STMT_EXIT:
		dump_exit((PLpgSQL_stmt_exit *)stmt);
		break;
        case PLPGSQL_STMT_RETURN:
		dump_return((PLpgSQL_stmt_return *)stmt);
		break;
        case PLPGSQL_STMT_RAISE:
		dump_raise((PLpgSQL_stmt_raise *)stmt);
		break;
        case PLPGSQL_STMT_EXECSQL:
		dump_execsql((PLpgSQL_stmt_execsql *)stmt);
		break;
        default:
		elog(ERROR, "plpgsql_dump: unknown cmd_type %d\n", stmt->cmd_type);
		break;
    }
}

static void dump_block(PLpgSQL_stmt_block *block)
{
    int i;
    char *name;

    if (block->label == NULL) {
        name = "*unnamed*";
    } else {
        name = block->label;
    }

    dump_ind();
    printf("BLOCK <<%s>>\n", name);

    dump_indent += 2;
    for (i = 0; i < block->body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(block->body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    END -- %s\n", name);
}

static void dump_assign(PLpgSQL_stmt_assign *stmt)
{
    dump_ind();
    printf("ASSIGN var %d := ", stmt->varno);
    dump_expr(stmt->expr);
    printf("\n");
}

static void dump_if(PLpgSQL_stmt_if *stmt)
{
    int i;

    dump_ind();
    printf("IF ");
    dump_expr(stmt->cond);
    printf(" THEN\n");

    dump_indent += 2;
    for (i = 0; i < stmt->true_body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->true_body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ELSE\n");

    dump_indent += 2;
    for (i = 0; i < stmt->false_body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->false_body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ENDIF\n");
}

static void dump_loop(PLpgSQL_stmt_loop *stmt)
{
    int i;

    dump_ind();
    printf("LOOP\n");

    dump_indent += 2;
    for (i = 0; i < stmt->body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ENDLOOP\n");
}

static void dump_while(PLpgSQL_stmt_while *stmt)
{
    int i;

    dump_ind();
    printf("WHILE ");
    dump_expr(stmt->cond);
    printf("\n");

    dump_indent += 2;
    for (i = 0; i < stmt->body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ENDWHILE\n");
}

static void dump_fori(PLpgSQL_stmt_fori *stmt)
{
    int i;

    dump_ind();
    printf("FORI %s %s\n", stmt->var->refname, (stmt->reverse) ? "REVERSE" : "NORMAL");

    dump_indent += 2;
    dump_ind();
    printf("    lower = ");
    dump_expr(stmt->lower);
    printf("\n");
    dump_ind();
    printf("    upper = ");
    dump_expr(stmt->upper);
    printf("\n");

    for (i = 0; i < stmt->body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ENDFORI\n");
}

static void dump_fors(PLpgSQL_stmt_fors *stmt)
{
    int i;

    dump_ind();
    printf("FORS %s ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname);
    dump_expr(stmt->query);
    printf("\n");

    dump_indent += 2;
    for (i = 0; i < stmt->body->stmts_used; i++) {
        dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
    }
    dump_indent -= 2;

    dump_ind();
    printf("    ENDFORS\n");
}

static void dump_select(PLpgSQL_stmt_select *stmt)
{
    dump_ind();
    printf("SELECT ");
    dump_expr(stmt->query);
    printf("\n");

    dump_indent += 2;
    if (stmt->rec != NULL) {
	dump_ind();
	printf("    target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
    }
    if (stmt->row != NULL) {
	dump_ind();
	printf("    target = %d %s\n", stmt->row->rowno, stmt->row->refname);
    }
    dump_indent -= 2;

}

static void dump_exit(PLpgSQL_stmt_exit *stmt)
{
    dump_ind();
    printf("EXIT lbl='%s'", stmt->label);
    if (stmt->cond != NULL) {
        printf(" WHEN ");
	dump_expr(stmt->cond);
    }
    printf("\n");
}

static void dump_return(PLpgSQL_stmt_return *stmt)
{
    dump_ind();
    printf("RETURN ");
    if (stmt->retrecno >= 0) {
        printf("record %d", stmt->retrecno);
    } else {
	if (stmt->expr == NULL) {
	    printf("NULL");
	} else {
	    dump_expr(stmt->expr);
	}
    }
    printf("\n");
}

static void dump_raise(PLpgSQL_stmt_raise *stmt)
{
    int i;

    dump_ind();
    printf("RAISE '%s'", stmt->message);
    for (i = 0; i < stmt->nparams; i++) {
        printf(" %d", stmt->params[i]);
    }
    printf("\n");
}

static void dump_execsql(PLpgSQL_stmt_execsql *stmt)
{
    dump_ind();
    printf("EXECSQL ");
    dump_expr(stmt->sqlstmt);
    printf("\n");
}

static void dump_expr(PLpgSQL_expr *expr)
{
    int i;
    printf("'%s", expr->query);
    if (expr->nparams > 0) {
	printf(" {");
	for(i = 0; i < expr->nparams; i++) {
	    if (i > 0) printf(", ");
	    printf("$%d=%d", i+1, expr->params[i]);
	}
	printf("}");
    }
    printf("'");
}

void plpgsql_dumptree(PLpgSQL_function *func)
{
    int i;
    PLpgSQL_datum *d;

    printf("\nExecution tree of successfully compiled PL/pgSQL function %s:\n",
    		func->fn_name);

    printf("\nFunctions data area:\n");
    for (i = 0; i < func->ndatums; i++) {
    	d = func->datums[i];

	printf("    entry %d: ", i);
	switch (d->dtype) {
	    case PLPGSQL_DTYPE_VAR:
		{
		    PLpgSQL_var *var = (PLpgSQL_var *)d;
		    printf("VAR %-16s type %s (typoid %d) atttypmod %d\n",
		    		var->refname, var->datatype->typname,
				var->datatype->typoid,
				var->datatype->atttypmod);
		}
	    	break;
	    case PLPGSQL_DTYPE_ROW:
	        {
		    PLpgSQL_row *row = (PLpgSQL_row *)d;
		    int i;
		    printf("ROW %-16s fields", row->refname);
		    for (i = 0; i < row->nfields; i++) {
		        printf(" %s=var %d", row->fieldnames[i],
					     row->varnos[i]);
		    }
		    printf("\n");
		}
		break;
	    case PLPGSQL_DTYPE_REC:
	    	printf("REC %s\n", ((PLpgSQL_rec *)d)->refname);
	    	break;
	    case PLPGSQL_DTYPE_RECFIELD:
	    	printf("RECFIELD %-16s of REC %d\n", ((PLpgSQL_recfield *)d)->fieldname, ((PLpgSQL_recfield *)d)->recno);
	    	break;
	    case PLPGSQL_DTYPE_TRIGARG:
	    	printf("TRIGARG ");
		dump_expr(((PLpgSQL_trigarg *)d)->argnum);
		printf("\n");
		break;
	    default:
	    	printf("??? unknown data type %d\n", d->dtype);
	}
    }
    printf("\nFunctions statements:\n");
    
    dump_indent = 0;
    printf("%3d:", func->action->lineno);
    dump_block(func->action);
    printf("\nEnd of execution tree of function %s\n\n", func->fn_name);
}


