/* pgsqlfuncs.c:  PostGreSQL database compatibility layer. */

/* This file is part of <Meta-HTML>(tm), a system for the rapid
   deployment of Internet and Intranet applications via the use
   of the Meta-HTML language.

   Author: Anderson MacKay (mackay@rice.edu) Tue May 12 1998
   Author: Henry Minsky (hqm@ai.mit.edu) Original msql file.
   Author: Brian J. Fox (bfox@ai.mit.edu) Original database manager.

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include <pgsql/libpq-fe.h>
#include <pgsql/config.h>

/****************************************************************/

#define DEFAULT_SQL_ESCAPE_CHARACTER  '\\'
#define DEFAULT_SQL_TRUNCATE_COLUMNS  1
#define DEFAULT_SQL_PREFIX_TABLENAMES 0

static PFunDesc func_table[] =
{
  { "PGSQL::WITH-OPEN-DATABASE",	1, 0, pf_with_open_database },
  { "PGSQL::DATABASE-EXEC-QUERY",	0, 0, pf_database_exec_query },
  { "PGSQL::DATABASE-EXEC-SQL",   	0, 0, pf_database_exec_sql },
  { "PGSQL::DATABASE-NEXT-RECORD",	0, 0, pf_database_next_record },
  { "PGSQL::DATABASE-SAVE-RECORD",	0, 0, pf_database_save_record },
  { "PGSQL::DATABASE-DELETE-RECORD",	0, 0, pf_database_delete_record },
  { "PGSQL::DATABASE-LOAD-RECORD",	0, 0, pf_database_load_record },
  { "PGSQL::DATABASE-SAVE-PACKAGE",	0, 0, pf_database_save_package },
  { "PGSQL::NUMBER-OF-ROWS",   	        0, 0, pf_database_num_rows},
  { "PGSQL::AFFECTED-ROWS",   	        0, 0, pf_database_affected_rows},
  { "PGSQL::SET-ROW-POSITION", 	        0, 0, pf_database_set_pos},
  { "PGSQL::DATABASE-QUERY",		0, 0, pf_database_query },
  { "PGSQL::HOST-DATABASES",	        0, 0, pf_host_databases },
  { "PGSQL::DATABASE-TABLES",     	0, 0, pf_database_tables },
  { "PGSQL::DATABASE-TABLES-INFO",     	0, 0, pf_database_tables_info },
  { "PGSQL::DATABASE-COLUMNS",          0, 0, pf_database_columns },
  { "PGSQL::DATABASE-COLUMN-INFO",      0, 0, pf_database_column_info },
  { "PGSQL::DATABASE-COLUMNS-INFO",     0, 0, pf_database_columns_info },
  { "PGSQL::DATABASE-QUERY-INFO",       0, 0, pf_database_query_info },
  { "PGSQL::DATABASE-SET-OPTIONS",      0, 0, pf_database_set_options },
  { "PGSQL::CURSOR-GET-COLUMN",         0, 0, pf_cursor_get_column },
  { "PGSQL::QUERY-GET-COLUMN",          0, 0, pf_query_get_column },
  { "PGSQL::SQL-TRANSACT",              0, 0, pf_sql_transact },
  { (char *)NULL,			0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_pgsql_functions)
DEFINE_SECTION (PGSQL-DATABASE-EXTENSIONS, database; SQL; PostGres,
"<Meta-HTML> can use the <b>PostGres</b> database engine (residing in the
dynamic module <code>modpgsql.so</code>) when requested to by the
<funref generic-sql-interface sql::set-database-type> command.
The PostGres extensions allow true SQL database interaction at many levels,
providing a clean and flexible abstraction to a PostGres database.",
"At the time of this writing, the PostGres implementation is in <i>beta</i>
release -- the speed of the implementation is not as good as the mSQL, MySQL,
or ODBC implementation.  We expect this to change in the near future")

DEFVAR (pgsql::pgsql-error-message,
"An array of strings containing any error messages generated by the
last call to the PostGres database.  Don't use this variable, use
the function <funref generic-sql-interface sql::sql-error-message> instead.")

DEFVAR (pgsql::recent-query,
"The last query sent to the PostGres database for execution.  Don't use this
variable, use the function <funref generic-sql-interface sql::recent-query>
instead.")

DEFMACROX (pf_pgsql::with_open_database, ,
"See <funref generic-sql-interface sql::with-open-database>.")
DEFUNX (pf_pgsql::database_exec_query, ,
"See <funref generic-sql-interface sql::database-exec-query>.")
DEFUNX (pf_pgsql::database_exec_sql, ,
"See <funref generic-sql-interface sql::database-exec-sql>.")
DEFUNX (pf_pgsql::database_next_record, ,
"See <funref generic-sql-interface sql::database-next-record>.")
DEFUNX (pf_pgsql::database_save_record, ,
"See <funref generic-sql-interface sql::database-save-record>.")
DEFUNX (pf_pgsql::database_delete_record, ,
"See <funref generic-sql-interface sql::database-delete-record>.")
DEFUNX (pf_pgsql::database_load_record, ,
"See <funref generic-sql-interface sql::database-load-record>.")
DEFUNX (pf_pgsql::database_save_package, ,
"See <funref generic-sql-interface sql::database-save-package>.")
DEFUNX (pf_pgsql::database_num_rows, ,
"See <funref generic-sql-interface sql::database-num-rows>.")
DEFUNX (pf_pgsql::database_affected_rows, ,
"See <funref generic-sql-interface sql::database-affected-rows>.")
DEFUNX (pf_pgsql::database_set_pos, ,
"See <funref generic-sql-interface sql::database-set-pos>.")
DEFUNX (pf_pgsql::database_query, ,
"See <funref generic-sql-interface sql::database-query>.")
DEFUNX (pf_pgsql::host_databases, ,
"See <funref generic-sql-interface sql::host-databases>.")
DEFUNX (pf_pgsql::database_tables, ,
"See <funref generic-sql-interface sql::database-tables>.")
DEFUNX (pf_pgsql::database_tables_info, ,
"See <funref generic-sql-interface sql::database-tables_info>.")
DEFUNX (pf_pgsql::database_tables_info, ,
"See <funref generic-sql-interface sql::database-tables-info>.")
DEFUNX (pf_pgsql::database_columns, ,
"See <funref generic-sql-interface sql::database-columns>.")
DEFUNX (pf_pgsql::database_column_info, ,
"See <funref generic-sql-interface sql::database-column-info>.")
DEFUNX (pf_pgsql::database_columns_info, ,
"See <funref generic-sql-interface sql::database-columns-info>.")
DEFUNX (pf_pgsql::database_query_info, ,
"See <funref generic-sql-interface sql::database-query-info>.")
DEFUNX (pf_pgsql::database_set_options, ,
"See <funref generic-sql-interface sql::database-set-options>.")
DEFUNX (pf_pgsql::cursor_get_column, ,
"See <funref generic-sql-interface sql::cursor-get-column>.")
DEFUNX (pf_pgsql::query_get_column, ,
"See <funref generic-sql-interface sql::query-get-column>.")
DEFUNX (pf_pgsq::sql_transact, ,
"See <funref generic-sql-interface sql::sql-transact>.")

/****************************************************************
 * The Database object:
 *
 * Contains a stack of cursors, and information about the open
 * database connection.
 ****************************************************************/

typedef struct
{
  IStack *cursors;
  int connected;		/* 1 if db connection open, 0 otherwise */
  char sql_escape_char;
  int  sql_truncate_columns;
  int  sql_prefix_tablenames;

  /* Postgres-specific data */
  PGconn *sock;
  char   *dbname;
  char   *hostname;
  PGresult *postgres_last_query;

} Database;

typedef struct
{
  char *name;		/* Field name. */
  char *table;		/* The name of the table that this field belongs to. */
  char *value;		/* The value of this field. */
  int  fsize;		/* Size of the field, -1 if of variable length */
  int  ftype;		/* PostGres internal type. */
  char *qualifier;
  char *owner;
  char *type_name;
  int precision;
  int scale;
  int radix;
  int nullable;
} gsql_field;

typedef struct
{
  PGresult *postgres_result;
  gsql_field **fields;
  int current_row;
} gsql_result;


/* PostgreSQL holds the cursor within the gsql_result struct ... */
typedef struct
{
  gsql_result *result;		/* The results of <database-exec-query..> */
  Database *db;			/* Associated database connection. */
  int index;			/* A unique index number for this cursor within
				   the stack of cursors for a database. */
} DBCursor;

static void free_database_cursors (Database *db);

static void
free_database_resources (Database *db)
{
  /* Free cursors, namestrings, hdbc resources, etc */
  if (db != (Database *)NULL)
    {
      free_database_cursors (db);
      xfree (db->dbname);
      xfree (db->hostname);
      db->dbname = (char *)NULL;
      db->hostname = (char *)NULL;
    }
}

/* Index where the next error message should go. */
static int postgres_error_index = 0;

static void
gsql_clear_error_message (void)
{
  postgres_error_index = 0;
  pagefunc_set_variable ("pgsql::pgsql-error-message[]", "");
}

/* Pass NULL to use system's error message. */
static void
gsql_save_error_message (Database *db, char *msg)
{
  char error_variable[128];

  sprintf (error_variable, "pgsql::pgsql-error-message[%d]",
	   postgres_error_index);
  postgres_error_index++;

  if (msg == GSQL_DEFAULT_ERRMSG)
    {
      char *message = (char *)NULL;
      if ((db != (Database *)NULL) && (db->sock != (PGconn *)NULL))
	{
	  if(PQstatus (db->sock) == CONNECTION_OK)
	    message = PQerrorMessage (db->sock);
	  else
	    message = strdup ("PostGreSQL: Connection is bad.\n");
	}
      else
	message = "Not connected to PostGreSQL server";

      pagefunc_set_variable (error_variable, message);
      page_debug ("postgres: %s", message);
    }
  else
    {
      pagefunc_set_variable (error_variable, msg);
      page_debug ("pgsql: %s", msg);
    }
}

static char *
my_pgtty (void)
{
  static char *pgtty = (char *)NULL;
  static int looked_up = 0;

  if (!looked_up)
    pgtty = (char *)getenv ("PGTTY");

  if (pgtty == (char *)NULL)
    return ("/dev/null");
  else
    return (pgtty);
}

static int
gsql_number_of_rows (gsql_result *result)
{
  if (result && result->postgres_result)
    return (PQntuples(result->postgres_result));
  else
    return (0);
}

static int
gsql_affected_rows (DBCursor *cursor)
{
  PGresult *res = cursor->result->postgres_result;
  return (PQntuples (res));
}

static void
gsql_data_seek (gsql_result *result, int position)
{
  result->current_row = position;
}

/* This fetches the current row into the gsql_result->fields
   field pointer array.  It's really a one-dimensional array
   of field pointers, as much as it looks like a two-dimensional
   array of fields.  It holds all the values of the current
   row in the returned database cursor. */
static int
gsql_fetch_row (gsql_result *result)
{
  /* FIXME: we should store the number of fields in the
     gsql_result structure so we don't have to do a
     PQnfields() call every time. */

  int numfields, i;

  if (result->current_row < PQntuples (result->postgres_result))
    {
      numfields = PQnfields (result->postgres_result);
      for (i = 0; i < numfields; i++)
	{
	  gsql_field *currfield = result->fields[i];
	  free (currfield->value);
	  currfield->value = strdup (PQgetvalue (result->postgres_result,
						 result->current_row, i));
	}

      (result->current_row)++;
      return (GSQL_SUCCESS);
    }
  else
    return (GSQL_NO_DATA_FOUND);
}

static gsql_field *
gsql_fetch_field (gsql_result *result, int i)
{
  if (result->fields != (gsql_field **) NULL)
    return (result->fields[i]);
  else
    return ((gsql_field *)NULL);
}

static int
gsql_query (Database *db, char *query, int save_errors_p)
{
  ExecStatusType query_result_status = PGRES_FATAL_ERROR;

  /* Store the results from the query. */
  db->postgres_last_query = PQexec (db->sock, query);

  if (db->postgres_last_query != (PGresult *)NULL)
    query_result_status = PQresultStatus (db->postgres_last_query);

  pagefunc_set_variable ("pgsql::recent-query", query);

  if ((query_result_status == PGRES_BAD_RESPONSE) ||
      (query_result_status == PGRES_NONFATAL_ERROR) ||
      (query_result_status == PGRES_FATAL_ERROR))
    {
      if (save_errors_p)
	gsql_save_error_message (db, GSQL_DEFAULT_ERRMSG);

      return (GSQL_ERROR);
    }
  else
    return (GSQL_SUCCESS);
}

static void
gsql_free_field (gsql_field *f)
{
  if (f != (gsql_field *)NULL)
    {
      xfree (f->value);
      xfree (f->name);
      xfree (f->table);
      xfree (f->qualifier);
      xfree (f->owner);
      xfree (f->type_name);
      free (f);
    }
}

static void
gsql_free_result (gsql_result *result)
{
  register int i;
  PGresult *pr = result->postgres_result;

  if (result->fields != (gsql_field **) NULL)
    {
      for (i = 0; result->fields[i] != (gsql_field *) NULL; i++)
	gsql_free_field (result->fields[i]);

      free (result->fields);
    }

  if (pr != (PGresult *)NULL)
    {
      PQclear (pr);
      pr = (PGresult *)NULL;
      result->postgres_result = (PGresult *)NULL;
    }

  free (result);
}

static void
initialize_database (Database *db)
{
  db->dbname = (char *) NULL;
  db->hostname = (char *) NULL;
  db->sock= (PGconn *) NULL;
  db->postgres_last_query= (PGresult *) NULL;

  /* FIXME: is this correct? (Andy) */
  db->sql_escape_char       = DEFAULT_SQL_ESCAPE_CHARACTER;
  db->sql_truncate_columns  = DEFAULT_SQL_TRUNCATE_COLUMNS;
  db->sql_prefix_tablenames = DEFAULT_SQL_PREFIX_TABLENAMES;
}

static gsql_result *
make_gsql_result (void)
{
  gsql_result *g;
  g = (gsql_result *)xmalloc (sizeof (gsql_result));

  g->postgres_result = (PGresult *) NULL;
  g->fields = (gsql_field **) NULL;
  g->current_row = 0;

  return (g);
}

static void
initialize_gsql_field (gsql_field *gfield)
{
  /* PostGreSQL-specific initialization ... */
  gfield->value = (char *) NULL;
  gfield->fsize = 0;
  gfield->ftype = 0;
  gfield->nullable = 0;;

  /* gsql standard stuff */
  gfield->name = (char *)NULL;
  gfield->table = (char *)NULL;
  gfield->qualifier = (char *) NULL;
  gfield->owner     = (char *) NULL;
  gfield->type_name  = (char *) NULL;
  gfield->precision = 0;
  gfield->scale     = 0;
  gfield->radix     = 0;
}

static gsql_result *
gsql_make_field_array (PGresult *result)
{
  gsql_result *gr = make_gsql_result ();
  gsql_field *gfield;
  int numfields, i;

  gr->postgres_result = result;

  numfields = PQnfields(result);

  if (numfields > 0)
    {
      gr->fields = (gsql_field **)xmalloc
	((numfields + 1) * sizeof (gsql_field *));

      for (i = 0; i < numfields; i++)
	{
	  gfield = (gsql_field *)xmalloc (sizeof (gsql_field));
	  initialize_gsql_field (gfield);

	  /* Pgres says we have to then copy this value out ... */
	  gfield->name = strdup (PQfname (result, i));
	  gfield->fsize = (int)PQfsize (result, i);
	  gfield->ftype = (int)PQftype (result, i);
	  gfield->nullable = (int)PQgetisnull (result, 0, i);
	  gfield->value = PQgetvalue (result, 0, i);
	  if (gfield->value) gfield->value = strdup (gfield->value);
	  gr->fields[i] = gfield;
	}

      /* Signify the end of the list. */
      gr->fields[i] = (gsql_field *) NULL;
    }

  return (gr);
}

/* We need to create an array of gsql_field structs which have
   the column info. We traverse the MYSQL field list, and
   wrap each field into a gsql_field object. */
static gsql_result *
gsql_store_result (Database *db)
{
  gsql_result *gr = (gsql_result *) NULL;

  if (db->postgres_last_query != (PGresult *) NULL)
    {
      gr = gsql_make_field_array (db->postgres_last_query);
    }

  return (gr);
}

/* The options  qualifier,owner,name,type are for ODBC compatibility. */
static gsql_result *
gsql_db_list_tables (Database *db,
		     char *table_qualifier,
		     char *table_owner,
		     char *table_name,
		     char *table_type)
{
  static char query_string[1024] = { '\0' };
  gsql_result *gr = (gsql_result *) NULL;
  ExecStatusType status = PGRES_FATAL_ERROR;
  PGresult *mr;
  int inhibit = empty_string_p (pagefunc_get_variable
				("pgsql::allow-system-tables"));

  query_string[0] = '\0';
  strcat (query_string, "SELECT relname FROM pg_class ");
  strcat (query_string, "WHERE relkind = 'r' ");
  if (inhibit)
    strcat (query_string, "AND relname NOT LIKE 'pg_%' ");
  strcat (query_string, "AND relname NOT LIKE 'Inv%' ");
  strcat (query_string, "ORDER BY relname");

  pagefunc_set_variable ("pgsql::recent-query", query_string);

  mr = PQexec (db->sock, query_string);
  if (mr != (PGresult *)NULL)
    status = PQresultStatus (mr);

  /* Read stuff in if we got anything from our query. */
  if (status == PGRES_TUPLES_OK)
    {
      gr = gsql_make_field_array (mr);
    }
  return (gr);
}

/****************************************************************
 * Field properties
 *
 * name, length, datatype, is_primary_key, not_null
 *
 ****************************************************************/

#define gsql_field_name(f) (f->name)
#define gsql_field_table(f) (f->table)
#define gsql_field_length(f) (f->fsize)
#define gsql_field_is_primary_key(f) (0)
#define gsql_field_is_unique(f) (0)
#define gsql_field_is_not_null(f) (1)

   /* Future ODBC compatibility */
#define gsql_field_qualifier(f) (f->qualifier)
#define gsql_field_owner(f) (f->owner)
#define gsql_field_type_name(f) (f->type_name)
#define gsql_field_precision(f) (f->precision)
#define gsql_field_scale(f) (f->scale)
#define gsql_field_radix(f) (f->radix)

#define FIELD_TYPE_BOOL         16
#define FIELD_TYPE_BYTEA        17
#define FIELD_TYPE_CHAR         18
#define FIELD_TYPE_NAME         19
#define FIELD_TYPE_CHAR16       20
#define FIELD_TYPE_INT2         21
#define FIELD_TYPE_INT28        22
#define FIELD_TYPE_INT4         23
#define FIELD_TYPE_REGPROC      24
#define FIELD_TYPE_TEXT         25
#define FIELD_TYPE_OID          26
#define FIELD_TYPE_TID          27
#define FIELD_TYPE_XID          28
#define FIELD_TYPE_CID          29
#define FIELD_TYPE_OID8         30
#define FIELD_TYPE_SET          32
#define FIELD_TYPE_CHAR2       409
#define FIELD_TYPE_CHAR4       410
#define FIELD_TYPE_CHAR8       411
#define FIELD_TYPE_POINT       600
#define FIELD_TYPE_LSEG        601
#define FIELD_TYPE_PATH        602
#define FIELD_TYPE_BOX         603
#define FIELD_TYPE_POLYGON     604
#define FIELD_TYPE_FILENAME    605
#define FIELD_TYPE_FLOAT4      700
#define FIELD_TYPE_FLOAT8      701
#define FIELD_TYPE_ABSTIME     702
#define FIELD_TYPE_RELTIME     703
#define FIELD_TYPE_TINTERVAL   704
#define FIELD_TYPE_UNKNOWN     705
#define FIELD_TYPE_MONEY       790
#define FIELD_TYPE_OIDINT2     810
#define FIELD_TYPE_OIDINT4     910
#define FIELD_TYPE_OIDNAME     911
#define FIELD_TYPE_BPCHAR     1042
#define FIELD_TYPE_VARCHAR    1043
#define FIELD_TYPE_DATE       1082
#define FIELD_TYPE_TIME       1083

static int
gsql_raw_field_type (gsql_field *field)
{
  return ((int)field->ftype);
}

static int
gsql_field_type (gsql_field *field)
{
  int result = 0;

  /* FIXME: somebody shoot me for this. (andy) */
  /* Oh, don't worry, I will (bfox) */
  /* return GSQL_CHAR; */

  switch (field->ftype)
    {
    case FIELD_TYPE_BYTEA:
    case FIELD_TYPE_BOOL:	result = GSQL_BOOLEAN;	break;

    case FIELD_TYPE_NAME:	result = GSQL_CHAR;	break;
    case FIELD_TYPE_CHAR2:	result = GSQL_CHAR;	break;
    case FIELD_TYPE_CHAR4:	result = GSQL_CHAR;	break;
    case FIELD_TYPE_CHAR8:	result = GSQL_CHAR;	break;
    case FIELD_TYPE_CHAR16:	result = GSQL_CHAR;	break;
    case FIELD_TYPE_CHAR:	result = GSQL_CHAR;	break;

    case FIELD_TYPE_INT2:	result = GSQL_SMALLINT;	break;

    case FIELD_TYPE_INT28:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_INT4:	result = GSQL_INTEGER;	break;


      /* Variable length character fields. */
    case FIELD_TYPE_VARCHAR:	result = GSQL_VARCHAR;	break;
    case FIELD_TYPE_BPCHAR:	result = GSQL_VARCHAR;	break;
    case FIELD_TYPE_FILENAME:	result = GSQL_VARCHAR;	break;
    case FIELD_TYPE_PATH:	result = GSQL_VARCHAR;	break;
    case FIELD_TYPE_TEXT:	result = GSQL_VARCHAR;	break;
    case FIELD_TYPE_OIDNAME:	result = GSQL_VARCHAR;	break;

      /* Object Identitifiers are integers, right? */
    case FIELD_TYPE_OID:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_TID:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_XID:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_CID:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_OID8:	result = GSQL_INTEGER;	break;

    case FIELD_TYPE_OIDINT2:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_OIDINT4:	result = GSQL_INTEGER;	break;


      /* As are types related to seconds since epoch, etc. */
    case FIELD_TYPE_ABSTIME:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_RELTIME:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_TINTERVAL:	result = GSQL_INTEGER;	break;
    case FIELD_TYPE_TIME:	result = GSQL_INTEGER;	break;

    case FIELD_TYPE_MONEY:	result = GSQL_MONEY;	break;
    case FIELD_TYPE_DATE:	result = GSQL_DATE;	break;

    case FIELD_TYPE_FLOAT4:	result = GSQL_REAL;	break;
    case FIELD_TYPE_FLOAT8:	result = GSQL_DOUBLE;	break;

    case FIELD_TYPE_SET:	result = GSQL_ANY;	break;
    case FIELD_TYPE_POINT:	result = GSQL_ANY;	break;
    case FIELD_TYPE_LSEG:	result = GSQL_ANY;	break;
    case FIELD_TYPE_BOX:	result = GSQL_ANY;	break;
    case FIELD_TYPE_POLYGON:	result = GSQL_ANY;	break;
    case FIELD_TYPE_REGPROC:	result = GSQL_ANY;	break;

    case FIELD_TYPE_UNKNOWN:	result = GSQL_ANY;	break;

    default:			result = GSQL_ANY;
    }
  return (result);
}

/* This needs to build the array of gsql_fields. */

/* FIXME: how to do this efficiently (-this- is horrible) in pgres ... ? */
static gsql_result *
gsql_list_fields (Database *db, char *tablename)
{
  static char query_string[256];
  gsql_result *gr = (gsql_result *)NULL;
  PGresult *mr;
  int status = PGRES_FATAL_ERROR;

  sprintf (query_string, "SELECT * FROM %s;\n", tablename);
  pagefunc_set_variable ("pgsql::recent-query", query_string);
  mr = PQexec (db->sock, query_string);

  if (mr != (PGresult *)NULL)
    status = PQresultStatus (mr);

  if (status == PGRES_TUPLES_OK)
    {
      register int i;
      int numfields = PQnfields (mr);

      gr = (gsql_result *)xmalloc (sizeof (gsql_result));
      gr->fields = (gsql_field **)xmalloc
	((numfields + 1) * sizeof (gsql_field *));

      for (i = 0; i < numfields; i++)
	{
	  gsql_field *gfield = (gsql_field *)xmalloc (sizeof (gsql_field));
	  initialize_gsql_field (gfield);
	  gfield->name = strdup (PQfname (mr, i));
	  /* gfield->nullable = PQgetisnull (mr, 0, i); */
	  gfield->table = strdup (tablename);
	  gfield->fsize = (int)PQfsize (mr, i);
	  gfield->ftype = (int)PQftype (mr, i);
	  gr->fields[i] = gfield;
	}
      gr->fields[i] = (gsql_field *)NULL;
    }

  gr->postgres_result = mr;

  return (gr);
}

static int
gsql_num_fields (gsql_result *result)
{
  if (result->postgres_result != (PGresult *) NULL)
    return (PQnfields(result->postgres_result));
  else
    return (0);
}

/* Fetch data from RESULT at column COL.
   A new string is consed.*/
static char *
gsql_get_column (gsql_result *result, int col)
{
  if (result->fields[col]->value)
    return (strdup (result->fields[col]->value));
  else
    return ((char *)NULL);
}


/* From the result set of a gsql_list_table_names,
   return the column which has the table name.  This
   is column 0. */
static char *
gsql_get_column_table_name (gsql_result *gr)
{
  return (gsql_get_column (gr, 0));
}

/* FIXME: this postgres module doesn't do authentication yet.
   No password, UID stuff. */
static void
gsql_connect (char *dsn, Database *db)
{
  PGconn *sock;
  char *dbhost = dsn_lookup ("host", dsn);
  char *dbname = dsn_lookup ("database", dsn);

  db->connected = 0;
  db->dbname = dbname;
  db->hostname = dbhost;

  if ((dbhost != (char *) NULL) && (dbname != (char *)NULL))
    {
      sock = PQsetdb (dbhost, NULL, NULL, "/dev/null", dbname);

      db->sock = sock;
      if (PQstatus (sock) == CONNECTION_BAD)
	{
	  BPRINTF_BUFFER *e = bprintf_create_buffer ();
	  bprintf (e, "Unable to connect to database: `%s' on `%s'",
		   dbname, dbhost);
	  gsql_save_error_message (db, e->buffer);
	  bprintf_free_buffer (e);
	}
      else
	db->connected = 1;
    }
  else
    {
      BPRINTF_BUFFER *e = bprintf_create_buffer ();
      bprintf (e, "Unable to connect to server! ");
      bprintf (e, "(HOST: `%s'; DATABASE: `%s')",
	       dbhost ? dbhost : "[not supplied]",
	       dbname ? dbname : "[not supplied]");
      gsql_save_error_message (db, e->buffer);
      bprintf_free_buffer (e);
    }
}

static void
gsql_close (Database *db)
{
  if (db->connected == 1)
    {
      PQfinish (db->sock);
      db->sock = (PGconn *) NULL;
      db->connected = 0;
    }
}

/* <mysql::host-databases [hostname] [result=varname]>
   Returns an array of the databases available on HOST.
   If VARNAME is supplied, the array is placed into that variable instead. */
static void
pf_host_databases (PFunArgs)
{
  char *host = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *resultvar = mhtml_evaluate_string (get_value (vars, "result"));
  PGconn *sock;

  /* No errors yet! */
  pagefunc_set_variable ("pgsql::pgsql-error-message[]", "");

  if (empty_string_p (host))
    {
      xfree (host);
      host = strdup ("localhost");
    }

  sock = PQsetdb (host, NULL, NULL, my_pgtty(), "template1");

  if (PQstatus (sock) == CONNECTION_OK)
    {
      char *query_string = "SELECT datname FROM pg_database";
      PGresult *result;
      ExecStatusType status;
      int nrows;

      pagefunc_set_variable ("pgsql::recent-query", query_string);
      
      result = PQexec (sock, query_string);
      status = result ? PQresultStatus (result) : PGRES_FATAL_ERROR;
      nrows = result ? PQntuples (result) : 0;

      if (nrows != 0 && ((status == PGRES_TUPLES_OK) ||
			 (status == PGRES_COMMAND_OK)))
	{
	  register int i;
	  char **dbnames = (char **) xmalloc ((nrows + 1) * sizeof (char *));

	  /* Loop over rows returned; the db name will be passed in the first
	     field of each 'row'.  Add names to the result array.  */
	  for (i = 0; i < nrows; i++)
	    dbnames[i] = strdup (PQgetvalue (result, i, 0));

	  dbnames[i] = (char *) NULL;

 	  if (!empty_string_p (resultvar))
	    {
	      symbol_store_array (resultvar, dbnames);
	    }
	  else
	    {
	      for (i = 0; dbnames[i] != (char *)NULL; i++)
		{
		  bprintf_insert (page, start, "%s\n", dbnames[i]);
		  start += 1 + strlen (dbnames[i]);
		  free (dbnames[i]);
		}
	      free (dbnames);
	      *newstart = start;
	    }
	}
      if (result != (PGresult *)NULL)
	{
	  PQclear (result);
	}
      PQfinish (sock);
    }
  else
    {
      gsql_save_error_message
	((Database *)NULL, "HOST-DATABASES: PostGreSQL Connect Failed");
    }

  xfree (host);
  xfree (resultvar);
}

/* FIXME: transactions work just fine in PostGreSQL, but how to implement them?
   Standard SQL BEGIN and COMMIT query statements should work just fine, no
   need for internal transactional support like this ... */

static int
gsql_transact_internal (Database *db, char *action_arg)
{
  return (GSQL_ERROR);
}
