/* See the copyright notice (COPYRIGHT) in this directory. */

/*
 * NAME
 *      pg_submit()	- submit a POSTGRES query for execution
 *
 * SYNOPSIS
 *      #include "libgendb.h"
 *	#include "gdi_postgres.h"
 *	#include "proto.h"
 *
 *	int
 *	pg_submit(pgconn, query, maxrecs, constr, ret_obj)
 *	dbConn    *dbconn;	(i) connection returned by gdi_open
 *	char      *query;	(i) syntactically complete database query
 *	int       maxrecs;	(i) upper limit of rows to fetch
 *	dbConstr  *constr;	(i) tuple constructor
 *	dbObj     **ret_obj;	(i/o) dbObj to hold data returned in tuples
 *
 * DESCRIPTION
 *	Do any query.
 *
 *	If there are results, data are returned in the user-specified tuple 
 *	container of the dbObj.
 *
 *	POSTGRES has hooks for returning multiple query results. Although I
 *	haven't been able to produce this behavior yet, the code assumes it.
 *	The logical flow is...
 *
 *		1) Analyze the query (pg_parse & pg_query)
 *		2) Execute the query
 *		3) Create a dbObj to store query execution status
 *		4) If the query returns data, 
 *		   -for each instance group (PQngroups) 
 *		   create & populate a tuple container 
 *
 *	It's a little tangled because a Portalbuffer is only returned if there
 *	are data.
 *
 *	The user is responsible for freeing up memory with gdi_obj_destroy().
 *
 *	Note about portals and transactions.....
 *
 *		This routine is responsible for all portal management 
 *		(allocation and deallocation) and transaction management.
 *		If a transaction is not already alive, it will start one
 *		if it is a retrieve.
 *
 * DIAGNOSTICS
 *	GDI_FAILURE	An an error occurred.	
 *	GDI_SUCCESS	No problema.
 *
 * CALLED BY
 *	gdi_submit
 *
 * SEE ALSO
 *	Generic Database Interface Design Document
 *	Generic Database Interface Requirements Document
 *	pg_submit.3
 *
 * BUGS
 *	Many.
 *
 * AUTHOR
 *	J. T. Anderson, May 1993, Postgres implementation
 */

#ifndef lint
static char SccsId[] = "@(#)pg_submit.c	16.2 8/7/93 Copyright (c) 1992-1993 Science Applications International Corporation";
#endif

#include "gdi_postgres.h"

Proto   (static dbStatus,   pg_execute,
        (
        dbConn  *d,		/* (i) database connector   */
	int	c,		/* (i) channel		*/
        char    *q,		/* (i) query              */
	char	*p,		/* (i) postgres portal  */
        int     recs,		/* (i) maximum records      */
        dbConstr *constr,       /* (i) database constructor */
        dbObj   **obj	        /* (o) results              */
));

dbStatus
pg_submit(dbconn, query, maxrecs, constr, ret_obj)
dbConn		*dbconn;
char		*query;
int		maxrecs;	/* gets passed through to pg_execute */
dbConstr	*constr;	/* gets passed through to pg_execute */
dbObj		**ret_obj;	/* gets passed through to pg_execute */
{
	char	*r = "pg_submit";
	int	status;				/* query execution status */
	int	channo = GDI_DEFAULT_CHAN;	/* query channel */

	char	qtype[PG_SHORT_COMMAND_SIZE+1];	/* query name: pg_parse */
	int	qcode;				/* query code: pg_parse() */

	char	portal[PG_PORTAL_NAME_SIZE+1];	/* portal name: pg_query() */
	char	*final_query = NULL;		/* final query: pg_query() */
	int	final_size;


	/* ====================
	 * QA & Initialization
	 * ====================
	 */
	if( ( query == NULL ) || ( strlen(query) == 0) ) {
		gdi_error_app(dbconn, GDI_BADDATA,
			"pg_submit: 'query' is a required input parameter.");
		return (GDI_FAILURE);
	}

	if( (ret_obj == NULL) || (constr == NULL) || (dbconn == NULL) ) {
		gdi_error_app(dbconn, GDI_BADDATA,
			"pg_submit: NULL input parameter.");
		return (GDI_FAILURE);
	}

	if (pg_channel_is_open (dbconn, channo ) != TRUE ) {
		gdi_error_app (dbconn, GDI_NOCONNECT,
			"pg_submit: the default GDI channel is not open");
		return (GDI_FAILURE );
	}

	if (maxrecs < -1 )
	{
		gdi_error_app(dbconn, GDI_BADDATA,
			"pg_submit: invalid maxrecs parameter: ");
		return(GDI_FAILURE);
	}

	if ( ret_obj )
		*ret_obj = NULL;

	/* ==========================================================
	 *  Analyze Query:
	 *	What is it?
	 *	If it is a retrieve, create a final query and portal
	 * ==========================================================
	 */
	if ( (qcode = pg_parse(dbconn, query, qtype, sizeof(qtype))) 
		== PG_ALLOC_FAIL)
			return (GDI_FAILURE);

	PG_QUERY_CODE(dbconn,channo) = qcode;
	(void) pg_strncpy(PG_QUERY_NAME(dbconn, channo), qtype, 
		PG_SHORT_COMMAND_SIZE, strlen(qtype));

	if (dbconn->debug == GDI_DEBUG_VERBOSE) {
		fprintf(stderr,"%16s: Query code is '%d', query type is '%s'\n",
				r, qcode, qtype);
		if (qcode == PG_UNKNOWN_QUERY)
			fprintf(stderr,
				"%16s: Warning! Unrecognized command!\n", r);
	}
	
		/* Produce a final query and portal */
	if(qcode == PG_RETRIEVE) 
	{
		final_size = (strlen(query)+32);
		if (( final_query = UCALLOC(char, final_size+1)) == NULL) 
			return ( gdi_error_unix (dbconn, r) );

		if( (pg_query(dbconn, channo, query, final_query, final_size, portal)) 
			== PG_ALLOC_FAIL)
		{
			UFREE(final_query);
			return (GDI_FAILURE);
		}
	}
	else
	{
		if (( final_query = UCALLOC(char, strlen(query)+1)) == NULL) 
			return ( gdi_error_unix (dbconn, r) );
		strcpy(final_query, query);
	}

	if (dbconn->debug == GDI_DEBUG_VERBOSE)
	{
		fprintf(stderr, "%16s: Query = '%s'", r, final_query);
		if(qcode == PG_RETRIEVE)
			fprintf(stderr,
                             "\n                  executed on portal '%s'\n",
                             portal);
	}

	/* ================
	 *  EXECUTE QUERY
	 * ================
	 */
	status = pg_execute(dbconn, channo, final_query, 
				portal, maxrecs, constr, ret_obj);

	UFREE(final_query);
	return(status);
}


/* ==========================  pg_execute() ================================
 * PORTAL NOTE
 *	So far the postgres portal looks like the sybase dbproc; except that a
 *	that a portal exists only when data are returned.
 *
 *	Like sybase, it looks like postgres can handle multiple queries
 *	or at least can return multiple query sets.  Ideally we want to 
 *	create one dbObj per query so we can store information about each 
 *	query (return status, the number of rows affected, etc.). Since we 
 *	don't have that degree of control and information for postgres, 
 *	we'll create one "master" dbobj up front.  If we have multiple 
 *	datasets coming back, we can add more dbobj's.
 *
 * TM NOTE
 *	For retrieves, we force the use of a portal (we may decide this is
 *	bad).  We want to stay out of the user's way as much as possible where
 *	tm is concerned, but named portals require a transaction. So we check 
 *	transaction state and start a transaction one needs to be started and
 *	end it at the end if we started it. We may decide this is really risky.
 */

static
dbStatus
pg_execute(d, c, q, p, recs, constr, pgobj)
dbConn		*d;		/* dbConn */
int		c;		/* query channel */
char		*q;		/* final query */
char		*p;		/* final portal */
int		recs;		/* max records the user wants */
dbConstr	*constr;	/* Constructor */
dbObj		**pgobj;	/* dbObj */
{
	char	*r = "pg_execute";
	char	*res;
	int	status, 
		tm_set = FALSE;	/* tracks if we began a transaction */
	dbObj	*obj = NULL;
	char	tmp_q[80];

		/* error handling variables for restoring error after rollback*/
	int             err_code;
	dbStatus        tmp_stat;
	dbErrLev        severity;
	char            err_string [GDI_ERROR_SIZE + 1];

	/* ========================
	 * Create the Master dbObj 
	 * ========================
	 */
	if ((obj = gdi_obj_create (constr)) == NULL)
	{
		(void) gdi_error_unix (d, 
			"pg_execute: error allocating dbObj: ");
		return (GDI_FAILURE);
	}

	GDI_OBJ_NUM_TUPLES(obj) = 0;
	GDI_OBJ_NUM_COLUMNS(obj) = 0;
	GDI_OBJ_ROWS_AFFECTED(obj) = 0;
	GDI_OBJ_CMD_NUM(obj) = 0;		/* we won't know this for pg */
	GDI_OBJ_MORE_ROWS(obj) = FALSE;
	GDI_OBJ_STATUS(obj) = GDI_SUCCESS;

	if ((GDI_OBJ_QUERY(obj) = UCALLOC(char, strlen(q) + 1)) == NULL )
	{
		(void) gdi_error_unix (d, 
			"pg_execute: error allocating query: ");
		obj = gdi_obj_destroy(obj);
		return (GDI_FAILURE);
	}
	else
		strcpy(GDI_OBJ_QUERY(obj), q);

	/* ===============
	 *  EXECUTE QUERY
	 * ===============
	 */
	switch ( PG_QUERY_CODE(d,c) )
	{
	   case PG_ABORT:
		status = pg_rollback (d,c);
		break;
	   case PG_BEGIN:
		status = pg_begin_tran (d,c,"");
		break;
	   case PG_END:
		status = pg_commit (d,c);
		break;
	   case PG_RETRIEVE:

		/* ================================================
		 * 'begin' a transaction if one is not already live
		 * ================================================
		 */
		if ( PG_TRAN(d) == FALSE )
		{
			(void) pg_begin_tran (d,c,"");
			tm_set = TRUE;
		}
		/* Fall through */
	   default:
		res=PQexec(q);
		status = pg_error(d, c, res, r);
		break;
	}

	if(status == GDI_FAILURE)
	{
		if ( tm_set == TRUE )
                {
                        /* Save the error that caused the failure. */
                        gdi_error_get (d, &err_code, err_string,
                                sizeof (err_string), &tmp_stat, &severity);
                        (void) pg_rollback (d,c);
                        (void) gdi_error_app (d,err_code,err_string);
                }
		obj = gdi_obj_destroy(obj);
		return(GDI_FAILURE);
	}

	/* ===================
	 *  Fetch any results
	 * ===================
	 */
	if ( PG_QUERY_CODE(d,c) == PG_RETRIEVE )
	{
		if(pg_fetch(d, c, obj, recs, p) )
		{
			obj = gdi_obj_destroy(obj);
			return(GDI_FAILURE);
		}
	}

	/* ===================
	 *  Cleanup
	 * ===================
	 */

	if (PG_QUERY_CODE(d,c) == PG_APPEND)
		GDI_OBJ_ROWS_AFFECTED(obj) = 1;

	if ( PG_QUERY_CODE(d,c) == PG_RETRIEVE )
	{

		sprintf(tmp_q, "close %s", p);
		res = PQexec (tmp_q);
                if ( (pg_error(d, c, res, r)) == GDI_FAILURE)
			return(GDI_FAILURE);

		PQclear(p); 

		/* If we started a transaction, we better end it */

		if ( tm_set == TRUE )
			(void) pg_commit (d,c);
	}

	*pgobj = obj;
	return(GDI_SUCCESS);
}
