#.TH SqlPostgres Onyx
#.SH NAME
#	SqlPostgres.ppg \- Grammar for SQL to Postgres

include "parser.h"
include "pg.h"

class SqlPostgres : SqlParser {
	HashList *sql_table_hash;
	HashList *sql_field_hash;
	HashList *sql_field_type_hash;

	Text *sql_tables;
	Text *sql_fields;

	char *pq_cmd;
	int like_flag;

	pgdb *pg_database;

	SqlPostgres();
	virtual ~SqlPostgres();
	void GetInfo();

	virtual int sql_open();
	virtual int sql_close();

	virtual int sql();
	virtual int statement();

	virtual int colid();
	virtual int value();
	virtual int compop();
	virtual int boolop();
	virtual int numop();
	virtual int expression();
	virtual int condition();

	virtual int where();
	virtual int del();
	virtual int update();
	virtual int insert();
	virtual int select();
	virtual int type();
	virtual int create();
	virtual int drop();
	virtual int load();
	virtual int unload();
	virtual int commit();
	virtual int rollback();
	virtual int grant();
	virtual int revoke();
	virtual void to_din(char *str);
	virtual void from_din(char *str);
	}

#.SH DESCRIPTION
#	SqlPostgres is Postgres v4.2 as a SQL-Engine. It makes the necessary
#	translations from SQL to PQ, and calls libpq directly.
#
#	SqlPostgres has some disadvantages related to this stupid parser.
#
#	SqlPostgres doesn't have aggegate functions now, I think I'll
#	implement it in one of next versions. If you need it ask me
#	for my support and hack it.
#
#	SqlPostgres mostly works like SqlGawk and SqlIngres.

sql : {	sql_purge(); }

	statement ";"

	| true {
		message("error:illegal statement");
		}
	;
#	
#       A Statement has to end with ";" or a message "error:illegal statement"
#       is raised.

statement :
	  database
	| del
	| update
	| insert
	| select
	| create
	| drop
	| load
	| unload
	| commit
	| rollback
	| grant
	| revoke
	| matchexp
	;
#
#       Only a small subset of SQL is valid here.

colid : {	char *p,*q;
		}

	id {	if ((p=strchr(p_val,'_')))
			q=strdup(p+1);
		else	q=strdup(p_val);
		}
	  [ "." id {
	  	q=strrepcat(q,".");
		if ((p=strchr(p_val,'_')))
	  		q=strrepcat(q,p+1);
	  	else	q=strrepcat(q,p_val);
	  	}
	  ] {	free(p_val); p_val=q;
	  	}
	;
#
#	This matches a colum identifier.

value : {	char *q,*p;
		}
	"\"" match "\"" {
		pq_cmd = strrepcat(pq_cmd,"\"");
		/*
		if (like_flag) 
			pq_cmd = strrepcat(pq_cmd,"*");
		*/
		to_din(p_matched->lines[0]);
		pq_cmd = strrepcat(pq_cmd,p_matched->lines[0]);
		/*
		if (like_flag)
			pq_cmd = strrepcat(pq_cmd,"*");
		*/
		pq_cmd = strrepcat(pq_cmd,"\"");
		pq_cmd = strrepcat(pq_cmd," ");
		}
	| num {
		pq_cmd = strrepcat(pq_cmd,p_val);
		pq_cmd = strrepcat(pq_cmd," ");
		}
	| "not" {
		pq_cmd = strrepcat(pq_cmd,"!(");
		}
	  expression {
		pq_cmd = strrepcat(pq_cmd,")");
		}
	| "(" {
		pq_cmd = strrepcat(pq_cmd,"(");
		}
	  expression 
	  ")" {	
	  	pq_cmd = strrepcat(pq_cmd,")");
	  	}
	| colid {
		if (strchr(p_val,'.'))
			p=sql_field_hash->get_buck(p_val);
		else 	p=sql_field_hash->get_buck(sql_tables->lines[0],p_val);

	  	if (!p) {
			message("field %s %s",p_val,"does not exist");
			return(0);
			}

		pq_cmd = strrepcat(pq_cmd,p);
		pq_cmd = strrepcat(pq_cmd," ");
		}
	;
#
#	A value can be a string, a number, a not expression, a expression
#	in braces or, a identifier. Identifiers are translated here between
#	SQL and PQ.

compop :
	  "=" 
	| "!=" | "<>" { free(p_val); p_val=strdup("!="); }
	| "<=" | ">=" | "<" | ">" 
	| "like" { free(p_val); p_val=strdup("~"); like_flag=1; }
	;
#
#       Here the different compare opperators are translated.
#
#	A like operator is translated to "~" and a flag is raised
#	for later be used in string values.
#
#	So its posible to write 'colid like "string"' but not
#	'colid like colid'

boolop :
	"and" 
	| "or"
	;
#
#       Here the boolean operators are translated.

numop :	  "+"
	| "-"
	| "*"
	| "/"
	;
#
#       Here the numeric operators are translated.

expression : {	if (!pq_cmd) pq_cmd = strdup("");
		}

	value
	( compop {
		pq_cmd = strrepcat(pq_cmd," ");
		pq_cmd = strrepcat(pq_cmd,p_val);
		pq_cmd = strrepcat(pq_cmd," ");
		}
	  value {
		like_flag=0; }
	| boolop {
		pq_cmd = strrepcat(pq_cmd," ");
		pq_cmd = strrepcat(pq_cmd,p_val); 
		pq_cmd = strrepcat(pq_cmd," "); 
		}
	  value
	| numop {
		pq_cmd = strrepcat(pq_cmd," ");
		pq_cmd = strrepcat(pq_cmd,p_val);
		pq_cmd = strrepcat(pq_cmd," ");
		}
	  value
	)
	;
#
#       A expression starts with a value and perhaps continues with
#       operator value pairs.

condition :
	expression
	;
#
#       A condition is nothing else than a expression. This is done
#       because PPG is only LL(1) and I don't have any better solution
#       in mind.
#
#       Caution no type checking is done, perhaps in a later version.

where : [ "where" { pq_cmd = strrepcat(pq_cmd,"where "); }
	  condition ]
	;
#
#       The "where" statement only has a condition.

del : {		char *p;
		}

	"delete" {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("delete ");

		sql_fields->purge();
		sql_tables->purge();
		}
	"from" id {
		if (!(p=sql_table_hash->get_buck(p_val))) {
			message("table %s %s",p_val,"does not exist");
			return(0);
			}
		sql_tables->add_line(p_val);

		pq_cmd=strrepcat(pq_cmd,p);
		pq_cmd=strrepcat(pq_cmd," ");
		}
	where {	message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		}
	;
#
#	This is doing the job of translating a delete statment to
#	pq-delete statement and calling the backend to do the
#	job.

update : {	char *p,*q;
		}

	"update" {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("replace ");

		sql_fields->purge();
		sql_tables->purge();
		}

	id {	if (!(p=sql_table_hash->get_buck(p_val))) {
			message("table %s %s",p_val,"does not exist");
			return(0);
			}

		sql_tables->add_line(p_val);

		pq_cmd=strrepcat(pq_cmd,p);
		pq_cmd=strrepcat(pq_cmd," (");
		}
	"set" 

	( id {	if (strchr(p_val,'.'))
			p=sql_field_hash->get_buck(p_val);
		else 	p=sql_field_hash->get_buck(sql_tables->lines[0],p_val);

		if (!p) {
			message("field %s %s",p_val,"does not exist");
			return(0);
			}

		q=strchr(p,'.')+1; if (!q) q=p;

		pq_cmd=strrepcat(pq_cmd,q);
		}
	  "=" { pq_cmd=strrepcat(pq_cmd," = ");
	  	}

	  expression

	  ! "," {
		pq_cmd=strrepcat(pq_cmd,", ");
		}
	) {	pq_cmd=strrepcat(pq_cmd,") ");
		}

	where {	message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		}
	;
#
#	This is doing the job of translating an update statement to
#	a pq-replace statement and calling the backend for doing the
#	job.

insert : {	char *p,*q;

		char **fields;
		int i;
		}

	"insert" {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("append ");

		sql_fields->purge();
		sql_tables->purge();
		}

	"into" id {
		if (!(p=sql_table_hash->get_buck(p_val))) {
			message("table %s %s",p_val,"does not exist");
			return(0);
			}

		sql_tables->add_line(p_val);

		pq_cmd=strrepcat(pq_cmd,p_val);
		pq_cmd=strrepcat(pq_cmd," (");
		}
	[ "(" 
	  ( colid {
		if (strchr(p_val,'.'))
			p=sql_field_hash->get_buck(p_val);
		else 	p=sql_field_hash->get_buck(sql_tables->lines[0],p_val);

	  	if (!p) {
			message("field %s %s",p_val,"does not exist");
			return(0);
			}
		q=strchr(p,'.'); if (!q) q=p; else ++q;

	  	sql_fields->add_line(q);
	  	}
	  ! "," 
	  )
	  ")"
	| true {
		for (i=0; i<sql_field_hash->nlines; i++) {
			p=sql_field_hash->get_val(i);
				if(!strncmp(sql_tables->lines[0],p,strlen(sql_tables->lines[0])))
					sql_fields->add_line(strchr(p,'.')+1);
			}
		}
	]

	"values" "(" {
		fields = sql_fields->lines;
		}

	( num {
		pq_cmd=strrepcat(pq_cmd,*fields);
		pq_cmd=strrepcat(pq_cmd," = ");
		pq_cmd=strrepcat(pq_cmd,p_val);
		}
	| "\""
	  match {
		pq_cmd=strrepcat(pq_cmd,*fields);
		pq_cmd=strrepcat(pq_cmd," = \"");
		to_din(p_matched->lines[0]);
		pq_cmd=strrepcat(pq_cmd,p_matched->lines[0]);
		pq_cmd=strrepcat(pq_cmd,"\"");
		}
	  "\""
	! "," {
		fields++;
		if (*fields)
			pq_cmd=strrepcat(pq_cmd,", ");
		else {
			pq_cmd=strrepcat(pq_cmd,")");

			message(pq_cmd);
			*pg_database << pq_cmd;
			free(pq_cmd); pq_cmd = 0;

			q = strchr(pq_cmd,'(');
			q[1]=0;

			fields = sql_fields->lines;
			}
		}
	)

	")" {	q=pq_cmd+strlen(pq_cmd)-1;
		if (*q != '(') {
			pq_cmd=strrepcat(pq_cmd,")");

			message(pq_cmd);
			*pg_database << pq_cmd;
			free(pq_cmd); pq_cmd = 0;
			}
		}
	;
#
#	This is doing the job of translating a insert statment to
#	a pq-append statement and calling the backend for doing the
#	job.

select : {	char *p,*q;

		char **fields;
		char **tables;

		char line[1024];
		}

	"select" {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("retrieve ");

		sql_fields->purge();
		sql_tables->purge();
		}
	[ "distinct" {
		pq_cmd = strrepcat(pq_cmd, "unique ");
	}
	]

	( colid {
		sql_fields->add_line(p_val);
	  	}

	| "*" {	sql_fields->add_line("*");
		}
	! ","
	)

	"from"

	( id {	if (!(p=sql_table_hash->get_buck(p_val))) {
			message("table %s %s",p_val,"does not exist");
			return(0);
			}
		sql_tables->add_line(p_val);
		}
	! ","
	) {	pq_cmd = strrepcat(pq_cmd, "(");
		fields=sql_fields->lines;
		tables=sql_tables->lines;

		if (!strcmp(*fields,"*")) {
			p=sql_table_hash->get_buck(*tables);
			pq_cmd=strrepcat(pq_cmd,p);
			pq_cmd=strrepcat(pq_cmd,".all");
			}
		else
		while (*fields) {
			if ((p=strchr(*fields,'.'))) {
				q=sql_field_hash->get_buck(*fields);
			} else {
				char *fld = strdup(*tables);
				fld = strrepcat(fld,".");
				fld = strrepcat(fld,*fields);
				q=sql_field_hash->get_buck(fld);
			}

			if (!q) {
				message("field %s does not exist",*fields);
				return(0);
				}

			pq_cmd=strrepcat(pq_cmd,q);

			fields++;

			if (*fields)
				pq_cmd=strrepcat(pq_cmd,",");
			}


		pq_cmd=strrepcat(pq_cmd,") ");
		}

	where 

	{	message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		int ntuples = pg_database->ntuples(0);
		int nattrib = pg_database->nfields(0);
		for (int tupno = 0; tupno < ntuples; tupno++) {
			char *line = strdup(pg_database->fielddata(tupno,pg_database->fieldname(0)));
			for(int attno = 1; attno < nattrib; attno++) {
				line = strrepcat(line,"\t");
				line = strrepcat(line,pg_database->fielddata(tupno,pg_database->fieldname(attno)));
			}
			from_din(line);
			sql_row_text->add_line(line);
		}
	}
	;
#
#	This is doing the job of translating a select statement to
#	pq-retrieve statement and calling the backend for doing the
#	job.
#
type : {	char *p;
		p=0;
		}

	  "integer" { pq_cmd = strrepcat(pq_cmd,"int4"); }
	| "smallint" { pq_cmd = strrepcat(pq_cmd,"int2"); }
	| "serial" { pq_cmd = strrepcat(pq_cmd,"int4"); }
	| "smallfloat" { pq_cmd = strrepcat(pq_cmd,"float4"); }
	| "real" { pq_cmd = strrepcat(pq_cmd,"float8"); }
	| "float" { pq_cmd = strrepcat(pq_cmd,"float8"); }
	| "date" { pq_cmd = strrepcat(pq_cmd,"text"); }
	| "double" "precision" { pq_cmd = strrepcat(pq_cmd,"float8"); }
	| "char" [ "(" num { p=strdup(p_val); } ")" ] {
		pq_cmd = strrepcat(pq_cmd,"text");
		}
	| "varchar" "(" num { p=strdup(p_val); } ")" {
		pq_cmd = strrepcat(pq_cmd,"text");
		}
	| "decimal" [ "(" num [ "," num ] ")" ] {
		pq_cmd = strrepcat(pq_cmd,"float8");
		}
	| "dec" [ "(" num [ "," num ] ")" ] {
		pq_cmd = strrepcat(pq_cmd,"float8");
		}
	| "numeric" [ "(" num [ "," num ] ")" ] {
		pq_cmd = strrepcat(pq_cmd,"float8");
		}
	| "money" [ "(" num [ "," num ] ")" ] {
		pq_cmd = strrepcat(pq_cmd,"float8");
		}
	;

create : {	int first;
		char *key;
		char *rel;
		char *ind;
		char *typename;
		char *p,*q;
		}
	"create" {
		first = 1;
		key = 0;
		if (pq_cmd) free(pq_cmd);

		sql_fields->purge();
		sql_tables->purge();
		}
	(
	"table" id {
		rel = strdup(p_val);
		pq_cmd = strdup("create ");
		pq_cmd = strrepcat(pq_cmd,p_val);
		pq_cmd = strrepcat(pq_cmd," ( ");
		} "("
	   ( "constraint" id "check" "(" match ")"
	   | "primary" "key" "(" match {
		key = strdup(p_matched->lines[0]);
		}
		")"
	   | "foreign" "key" "(" match ")" "references" id
		[ "on"
		  [ "delete" | "update" ]
		  [ "cascade" | "restricted" | "set" id ]
		]
	   | colid {
		if (!first)
			pq_cmd = strrepcat(pq_cmd,", ");
		first=0;
		pq_cmd = strrepcat(pq_cmd,p_val);
		pq_cmd = strrepcat(pq_cmd," = ");
		}
		type
	   ! ","
	   )
	   ")" matchexp {
		pq_cmd = strrepcat(pq_cmd," ) ");
		message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;

		GetInfo();
		if(key) free(key);
		free(rel);
		}
	| "index" id {
		ind = strdup(p_val);
		}
	  "on" id {
		if(!(p=sql_table_hash->get_buck(p_val))) {
			free(ind);
			message("Table %s %s", p_val, "does not exist");
			return(0);
		} else 
			rel = strdup(p_val);
		}
	  "(" id {
		key = strdup(rel);
		key = strrepcat(key,".");
		key = strrepcat(key,p_val);
		if(!(p=sql_field_hash->get_buck(key))) {
			free(ind);
			free(rel);
			message("Field %s %s", key, "does not exist");
			free(key);
			return(0);
		}
		if(!(p=sql_field_type_hash->get_buck(key))) {
			free(ind);
			free(rel);
			message("Type of field %s %s", key, "unknown");
			free(key);
			return(0);
		} else 
			typename = p;
		free(key);
		key = strdup(p_val);
		}
	  ")" {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("define index ");
		pq_cmd = strrepcat(pq_cmd,ind);
		free(ind);
		pq_cmd = strrepcat(pq_cmd," on ");
		pq_cmd = strrepcat(pq_cmd,rel);
		free(rel);
		pq_cmd = strrepcat(pq_cmd," using btree (");
		pq_cmd = strrepcat(pq_cmd,key);
		free(key);
		pq_cmd = strrepcat(pq_cmd," ");
		pq_cmd = strrepcat(pq_cmd,typename);
		pq_cmd = strrepcat(pq_cmd,"_ops)");
		message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		}
	)
	;

drop : {
		char *p;
		}
	  "drop"
	  (
	  "table" id {
		if (pq_cmd) free(pq_cmd);
		if (!(p=sql_table_hash->get_buck(p_val))) {
			message("Table %s %s", p_val, "does not exist");
			return(0);
		}
		pq_cmd = strdup("destroy ");
		pq_cmd = strrepcat(pq_cmd, p_val);
		message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		GetInfo();
		}
	| "index" id {
		if (pq_cmd) free(pq_cmd);
		pq_cmd = strdup("remove index ");
		pq_cmd = strrepcat(pq_cmd,p_val);
		message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		}
	)
	;

load : {
	char *fn, *tn, *p;
       }
	"load" "from" "\"" match {
		fn = strdup(p_val);
	}
	"\""
	"insert" "into" id {
		if(!(p=sql_table_hash->get_buck(p_val))) {
			message("Table %s %s", p_val, "does not exist");
			free(fn);
			return(0);
		}
		tn = strdup(p_val);
		}
	[ "commit" num ] {
		if(pq_cmd) free(pq_cmd);
		pq_cmd = strdup("copy ");
		pq_cmd = strrepcat(pq_cmd,tn);
		free(tn);
		pq_cmd = strrepcat(pq_cmd," from ");
		pq_cmd = strrepcat(pq_cmd,"\"");
		pq_cmd = strrepcat(pq_cmd,fn);
		free(fn);
		pq_cmd = strrepcat(pq_cmd,"\"");
		message(pq_cmd);
		*pg_database << pq_cmd;
		free(pq_cmd); pq_cmd = 0;
		}
		
	;

unload : {
		FILE *f;
		int i;
	}
	"unload" "to" "\"" match {
		f = fopen(p_val,"w");
	}
	"\"" select {
		for(i=0;i<sql_row_text->nlines;i++)
			fprintf(f,"%s\n",sql_row_text->lines[i]);
		fclose(f);
		sql_row_text->purge();
		message("# Unload");
	}
	;

commit : "commit" matchexp ;

rollback : "rollback" ;

grant : "grant" "all" "on" id "from" id
	;

revoke : "revoke" "all" "on" id "from" id
	.

#.SH AUTHOR
#	William Wanders <wwanders@sci.kun.nl>
#	Based on SqlIngres.ppg from:
#       Michael Koehne <kraehe@bakunin.north.de>

{ /* ----------------------------------------------------------------------- */

SqlPostgres::SqlPostgres() : SqlParser()
{	debug("SqlPostgres::new()");

	sql_table_hash = new HashList(16);
	sql_field_hash = new HashList(16);
	sql_field_type_hash = new HashList(16);

	sql_tables = new Text(16);
	sql_fields = new Text(16);

	pq_cmd = 0;

	sql_row_type = 2;
	like_flag=0;
	}

SqlPostgres::~SqlPostgres()
{	debug("SqlPostgres::delete()");

	if (sql_database) sql_close();

	delete sql_table_hash;
	delete sql_field_hash;
	delete sql_field_type_hash;
	delete sql_tables;
	delete sql_fields;

	if (pq_cmd) free(pq_cmd);
	}

void SqlPostgres::GetInfo()
{	debug("SqlPostgres::getinfo()");

	if (pq_cmd) free(pq_cmd);
        pq_cmd = strdup("retrieve (pg_class.relname,pg_class.oid) where (pg_class.relname !~\"^pg_\") and (pg_class.relname !~\"^pgb_\")");

	*pg_database << pq_cmd;

	int rels = pg_database->ntuples(0);
	char *relnames[rels];
	char *relids[rels];

	for (int relno = 0; relno < rels; ++relno) {
		relnames[relno]=strdup(pg_database->fielddata(relno, "relname"));
		relids[relno]=strdup(pg_database->fielddata(relno, "oid"));
		sql_table_hash->add_buck(strdup(relnames[relno]),strdup(relnames[relno]));
	}

	for (relno = 0; relno < rels; ++relno) {
		if (pq_cmd) free(pq_cmd);
                pq_cmd = strdup("retrieve (pg_attribute.attname,pg_attribute.attnum,pg_type.typname) where pg_attribute.atttypid = pg_type.oid and pg_attribute.attrelid = \"");
		pq_cmd = strrepcat(pq_cmd,relids[relno]);
		pq_cmd = strrepcat(pq_cmd,"\"::oid and pg_attribute.attnum > 0 sort by attnum");

		*pg_database << pq_cmd;

		int fields = pg_database->ntuples(0);
		for (int fldno = 0; fldno < fields; ++fldno) {
			char *fieldname = strdup(relnames[relno]);
			fieldname = strrepcat(fieldname,".");
			fieldname = strrepcat(fieldname,pg_database->fielddata(fldno, "attname"));
			char *fieldtype = strdup(pg_database->fielddata(fldno, "typname"));
			sql_field_hash->add_buck(strdup(fieldname),strdup(fieldname));
			sql_field_type_hash->add_buck(strdup(fieldname),strdup(fieldtype));
		}
	free(pq_cmd); pq_cmd = 0;
	}
	debug("SqlPostgres::getinfo()OK");
}

int SqlPostgres::sql_open()
{	char *p;

	debug("SqlPostgres::open()");

	if (sql_database) {
		if ((p=strchr(sql_database,'%'))) *p=0;
		pg_database = new pgdb(sql_database,this);
		if (p) *p='%';
		GetInfo();
		message("Connected to %s",sql_database);
		return(1);
		}
	return(0);
	}

SqlPostgres::sql_close()
{	debug("SqlPostgres::close()");

	if (sql_database) {
		delete pg_database;
		message("Connection to Postgres closed");
		free(sql_database);
		sql_database=0;
		return(1);
		}
	return(0);
	}

static char trans1[] = "\366\326\344\304\374\334\337";
static char trans2[] = "\174\044\173\133\175\135\176";

void
SqlPostgres::to_din(char *str)
{	char *p, *q;

	for (p=str; *p; p++) {
		if (q=strchr(trans1,*p)) 
			*p=trans2[q-trans1];
		}
	}

void
SqlPostgres::from_din(char *str)
{	char *p, *q;

	for (p=str; *p; p++) {
		if (q=strchr(trans2,*p))
			*p=trans1[q-trans2];
		}
	}

void pgdb::operator<<(char* query)
{
    char* tmpres = PQexec(query);	// pointer to static buffer
    (void) strncpy(res, tmpres, PortalNameLength+1);
    portalbuf = (PortalBuffer*) NULL;
    if (*res == 'E') {
	sql_actor->message("error while executing %s",query);
	libpq_raise(&ProtocolError, "pgdb::operator: backend died.\n");
    }
    if (*res == 'P') {
	portalbuf = PQparray(res + 1);
    }
}

int pgdb::ngroups()
{
    int outp = 0;
    /* Tuples in the portal appear in GROUPS */
    if (portalbuf) {
	outp = PQngroups(portalbuf);
    } else {
	sql_actor->message("Attempted ngroups with no retrieve");
    }
    return(outp);
}

int pgdb::nfields(int groupno)
{
    int outp = 0;
    if (portalbuf) {
	outp = PQnfieldsGroup(portalbuf,groupno);
    } else {
	sql_actor->message("Attempted nfields with no retrieve");
    }
    return(outp);
}

char* pgdb::fieldname(int groupno, int n)
{
    char* outp = (char*) NULL;
    if (portalbuf) {
	outp = PQfnameGroup(portalbuf,groupno,n);
    } else {
	sql_actor->message("Attempted fieldname with no retrieve");
    }
    return(outp);
}

char* pgdb::fieldname(int n)
{
    return(fieldname(0,n));
}

char* pgdb::fielddata(int tupleno, char* fieldname)
{
    int fnumber, groupno;
    char* outp = (char*) NULL;
    if (portalbuf) {
	groupno = PQgetgroup(portalbuf, tupleno);
	fnumber = PQfnumberGroup(portalbuf, groupno, fieldname);
	outp = PQgetvalue(portalbuf, tupleno, fnumber);
    } else {
	sql_actor->message("Attempted fielddata with no retrieve");
    }
    return(outp);
}

int pgdb::fieldlength(int tupleno, char* fieldname)
{
    int fnumber, groupno;
    int outp = 0;
    if (portalbuf) {
	groupno = PQgetgroup(portalbuf, tupleno);
	fnumber = PQfnumberGroup(portalbuf, groupno, fieldname);
	outp = PQgetlength(portalbuf, tupleno, fnumber);
    } else {
	sql_actor->message("Attempted fieldlength with no retrieve");
    }
    return(outp);
}

int pgdb::ntuples(int groupno)
{
    int outp = 0;
    if (portalbuf) {
	outp = PQntuplesGroup(portalbuf, groupno);
    } else {
	sql_actor->message("Attempted ntuples with no retrieve");
    }
    return(outp);
}

int pgdb::ntuples()
{
    int outp = 0;
    if (portalbuf) {
	outp = PQntuples(portalbuf);
    } else {
	sql_actor->message("Attempted ntuples with no retrieve");
    }
    return(outp);
}

/* ----------------------------------------------------------------------- */ }

