/*-------------------------------------------------------------------------
 *
 * psql.c--
 *    a monitor workalike to test the libtdb library
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/bin/psql/psql.c,v 1.11 1995/05/06 16:30:32 andrew Exp
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include "pqutils.h"
#include "tdb.h"
#include "tgPrint.h"
#include "stringutils.h"

#include "psqlHelp.h"

#ifdef NOREADLINE
extern char *readline(char *);	/* in rlstubs.c */
#else
/* from the GNU readline library */
#include "readline.h"
#include "history.h"
#endif

#define MAX_QUERY_BUFFER 20000

/* declarations for functions in this file */
static void usage(char* progname);
static void slash_usage();
static int listAllDbs(TgDb* db);
char* gets_noreadline(char* prompt, FILE* source);
char* gets_readline(char* prompt, FILE* source);
char* gets_fromFile(char* prompt, FILE* source);
void SendQuery(TgDb* db, char* query, int echo_query);
int HandleSlashCmds(TgDb** db_ptr, char* line, char** prompt_ptr,
		    int echo_query);
void MainLoop(TgDb** db_ptr, int single_line_mode, FILE* source,
	      int quiet, int use_readline, int echo_query);


/*
 * usage 
 *   print out usage for command line arguments 
 */

static void  
usage(char* progname)
{
  fprintf(stderr,"Usage: %s [options] [dbname]\n",progname);
  fprintf(stderr,"\t -c query                run single query\n");
  fprintf(stderr,"\t -d dbName               specify database name\n");
  fprintf(stderr,"\t -e                      echo the query sent to the backend\n");
  fprintf(stderr,"\t -f filename             use file as a source of queries\n");
  fprintf(stderr,"\t -h                      help information\n");
  fprintf(stderr,"\t -H host                 set database server host\n");
  fprintf(stderr,"\t -l                      list available databases\n");
  fprintf(stderr,"\t -n                      don't use readline library\n");
  fprintf(stderr,"\t -S                      single line mode (i.e. query terminated by newline)\n");
  fprintf(stderr,"\t -p port                 set port number\n");
  fprintf(stderr,"\t -q                      run quietly (no messages, no prompts)\n");
  exit(1);
}

/*
 * slash_usage
 *    print out usage for the backslash commands 
 */

static void
slash_usage()
{
  fprintf(stderr,"\t \\c <dbname> -- establish new connection to <dbname>\n");
  fprintf(stderr,"\t \\h <command>-- help on syntax of sql commands\n");
  fprintf(stderr,"\t \\g          -- send query to backend\n");
  fprintf(stderr,"\t \\i <fname>  -- read queries from filename\n");
  fprintf(stderr,"\t \\l          -- list all databases\n");
  fprintf(stderr,"\t \\q          -- quit\n");
  fprintf(stderr,"\t \\s <fname>  -- save history to filename\n");
  fprintf(stderr,"\t \\?          -- help\n");
}

/*
 * listAllDbs   (TgDb* db)
 *
 * list all the databases in the system
 *     returns 0 if all went well
 *    
 *
 */
int 
listAllDbs(TgDb* db)
{
  TgTupleGroup* results;
  char* query = "select * from pg_database;";

  results = TgRetrieve(db,query,0);
  if (results == NULL)
    {
      fprintf(stderr,"Unexpected error from executing: %s\n", query);
      return 2;
    }
  else
    {
      TgPrintTupleGroup(results);
      TG_FREETUPLEGROUP(results);
      return 0;
    }
}

typedef char* (*READ_ROUTINE)(char* prompt, FILE* source);

/* gets_noreadline  prompt source
      gets a line of input without calling readline, the source is ignored
*/
char* 
gets_noreadline(char* prompt, FILE* source)
{
    fputs(prompt, stdout);
    fflush(stdin);
    return(gets_fromFile(prompt,stdin));
}

/*
 * gets_readline  prompt source
 *   the routine to get input from GNU readline(), the source is ignored 
 * the prompt argument is used as the prompting string
 */
char* 
gets_readline(char* prompt, FILE* source)
{
  return (readline(prompt));
}


/*
 * gets_fromFile  prompt source
 *    
 * the routine to read from a file, the prompt argument is ignored
 * the source argument is a FILE* 
 */
char* 
gets_fromFile(char* prompt, FILE* source)
{
  char* line;
  int len;

  line = malloc(MAX_QUERY_BUFFER+1);

  /* read up to MAX_QUERY_BUFFER - 1 characters */
  if (fgets(line, MAX_QUERY_BUFFER, source) == NULL)
    return NULL;

  line[MAX_QUERY_BUFFER-1] = '\0';
  len = strlen(line);
  if (len == MAX_QUERY_BUFFER)
    {
      fprintf(stderr, "line read exceeds maximum length.  Truncating at %d\n", MAX_QUERY_BUFFER);
    }
  
  return line;
}

/*
 * SendQuery:
     SendQuery: send the query string to the backend 
 *
 */
void 
SendQuery(TgDb* db, char* query, int echo_query)
{
  int qres;                     /* query result */
  TgTupleGroup* results;

  if (echo_query) {
      fprintf(stderr,"QUERY: %s\n",query);
      fflush(stderr);
  }
  qres = TgDoQuery(db, query, 0);
  if (qres == 0) {
/*    fprintf(stderr,"ERROR: %s\n", tgDbErrMsg); */
   }
  else if (qres == 2)
    {
      results = TgGetData(db,0);
      if (results) {
	TgPrintTupleGroup(results);
	fflush(stdout);
	TG_FREETUPLEGROUP(results);
       }
    }
}


/*
  HandleSlashCmds:

  Handles all the different commands that start with \ 
     db_ptr is a pointer to the TgDb* structure
     line is the current input line
     prompt_ptr is a pointer to the prompt string,
                  a pointer is used because the prompt can be used with 
		  a connection to a new database
  returns a status:
       0 - send currently constructed query to backend (i.e. we got a \g)
       1 - skip processing of this line, continue building up query
       2 - terminate processing of this query entirely
*/
int
HandleSlashCmds(TgDb** db_ptr, char* line, char** prompt_ptr, int echo_query)
{
  int status = 0;
  TgDb* db = *db_ptr;
  char* dbname = db->name;

  switch (line[1])
    {
    case 'g':  /* \g means send query */
      status = 0;     
      break;
    case 'c':  /* \c means connect to new database */
      {
	char* newdbname;
	
	if (strlen(line) < 3) {
	  fprintf(stderr,"\\c must be followed by a database name\n");
	  status = 1;
	  break;
	}
	/* left and rightTrim take out whitespaces, including \n's
	   from gets_fromFile */
	newdbname = leftTrim(line+2);
	if (strcmp(newdbname, dbname) == 0)
	  {
	    fprintf(stderr,"already connected to %s\n", dbname);
	    status = 1;
	    break;
	  }
	else
	  {
	    TgDb *olddb;

	    printf("closing connection to database:%s\n", dbname);
	    olddb = db;
	    TgCloseDB(olddb);
	    db = TgNewDB(newdbname, olddb->host, olddb->port, NULL, NULL);
	    *db_ptr = db;
	    printf("connecting to new database: %s\n", newdbname);
	    if (TgConnectDB(db) == 0)
	      {
		fprintf(stderr,"%s\n", tgDbErrMsg);
		printf("reconnecting to %s\n", dbname);
		db = TgNewDB(dbname, olddb->host, olddb->port, NULL, NULL);
		*db_ptr = db;
		if (TgConnectDB(db) == 0)
		  {
		    fprintf(stderr, "could not reconnect to %s.  exiting\n", dbname);
		    exit(2);
		  }
		status = 1;
		break;
	      }
	    TG_FREEDB(olddb);
	    free(*prompt_ptr);
	    *prompt_ptr = malloc(strlen(newdbname) + 10);
	    sprintf(*prompt_ptr,"%s=> ", newdbname);
	    status = 1;
	    break;
	  }
      }
      break;
    case 'i':     /* \i is include file */
      {
	char* fname;
	FILE* fd;

	if (strlen(line) < 3) {
	  fprintf(stderr,"\\i must be followed by a file name\n");
	  status = 1;
	  break;
	}

	fname = leftTrim(line+2);
	if ( (fd = fopen(fname, "r")) == NULL)
	  {
	    fprintf(stderr,"file named %s could not be opened\n",fname);
	    status = 1;
	    break;
	  }
	MainLoop(&db, 0, fd, 0, 0, echo_query);
	fclose(fd);
	status = 1;
	break;
      }
    case 'h':
      {
	char* cmd;
	int i, numCmds;

	if (strlen(line) < 3)
	  {
	    printf("type \\h <cmd> where <cmd> is one of the following:\n");
	    i = 0;
	    while (QL_HELP[i].cmd != NULL)
	      {
		printf("\t%s\n", QL_HELP[i].cmd);
		i++;
	      }
	  }
	else
	  {
	  cmd = leftTrim(line+2);

	  numCmds = 0;
	  while (QL_HELP[numCmds++].cmd != NULL);

	  numCmds = numCmds - 1;
	  
	  for (i=0; i<numCmds;i++)  {
	      if (strcmp(QL_HELP[i].cmd, cmd) == 0)    {
		printf("Description: %s\n", QL_HELP[i].help);
		printf("Syntax:\n");
		printf("%s\n", QL_HELP[i].syntax);
		break;
	      }
	    }
	  if (i == numCmds)
	    printf("command not found,  try \\h with no arguments to see available help\n");
	}
	status = 1;
	break;
      }
    case 'l':     /* \l is list database */
      listAllDbs(db);
      status = 1;
      break;
    case 'q': /* \q is quit */
      status = 2;
      break;
    case 's': /* \s is save history to a file */
      {
	char* fname;
	FILE* fd;

	if (strlen(line) < 3) {
	  fprintf(stderr,"\\s must be followed by a file name\n");
	  status = 1;
	  break;
	}

	fname = leftTrim(line+2);
	if (write_history(fname) != 0)
	  {
	    fprintf(stderr,"cannot write history to %s\n",fname);
	  }
	status = 1;
	break;
      }
    default:
    case '?':     /* \? is help */
      slash_usage();
      status = 1;
      break;
    }
  return status;
}

/* 
 MainLoop: main processing loop for reading lines of input
 and sending them to the backend

 this loop is re-entrant.  May be called by \i command
 which reads input from a file

 *db_ptr must be initialized and set
 A pointer to a TgDb* is passed in, because the TgDb* itself
 can be changed by a \c command 
*/
void 
MainLoop(TgDb** db_ptr, int single_line_mode, FILE* source,
	 int quiet, int use_readline, int echo_query)
{
  char* prompt;                 /* readline prompt */
  char* line;                   /* line of input*/
  int len;                      /* length of the line */
  char query[MAX_QUERY_BUFFER]; /* multi-line query storage */
  TgDb* db = *db_ptr;
  char* dbname = db->name;
  int slash_cmd_status = 0;
  /* slash_cmd_status can be:
       0 - send currently constructed query to backend (i.e. we got a \g)
       1 - skip processing of this line, continue building up query
       2 - terminate processing of this query entirely
  */

  int send_query = 0;
  int interactive;
  READ_ROUTINE GetNextLine;

  interactive = (source == stdin);

  if (interactive) {
    prompt = malloc(strlen(dbname) + 10);
    if (quiet)
      prompt[0] = '\0';
    else
      sprintf(prompt,"%s=> ", dbname);
    if (use_readline && isatty(0)) {
	using_history();
	GetNextLine = gets_readline;
    } else
	GetNextLine = gets_noreadline;

  }
  else
    GetNextLine = gets_fromFile;

  query[0] = '\0';
  
  /* main loop for getting queries and executing them */
  while ((line = GetNextLine(prompt, source)) != NULL)
    {
      line = rightTrim(line); /* remove whitespaces on the right, incl. \n's */

      /* filter out comment lines that begin with --,
         this could be incorrect if -- is part of a quoted string.
         But we won't go through the trouble of detecting that.  If you have
	 -- in your quoted string, be careful and don't start a line with it*/
      if (line[0] == '-' && line[1] == '-') {
	  free(line);
	  continue;
      }

      if (line[0] == '\0') {
	  free(line);
	  continue;
      }

      len = strlen(line);

      if (interactive && use_readline)
	  add_history(line);      /* save non-empty lines in history */
      
      /* do the query immediately if we are doing single line queries 
       or if the last character is a semicolon */
      send_query = single_line_mode || (line[len-1] == ';') ;

      /* normally, \ commands have to be start the line,
	 but for backwards compatibility with monitor,
	 check for \g at the end of line */
      if (len > 2 && !send_query) 
	{
	  if (line[len-1]=='g' && line[len-2]=='\\')
	    {
	    send_query = 1;
	    line[len-2]='\0';
	  }
	}
      
      /* / commands have to be on their own line */
      /* slash commands handling here */
      if (line[0] == '\\') {
	slash_cmd_status = HandleSlashCmds(db_ptr, line, &prompt, echo_query);
	db = *db_ptr; /* in case \c changed the database */
	if (slash_cmd_status == 1)
	  continue;
	if (slash_cmd_status == 2)
	  break;
	if (slash_cmd_status == 0)
	  send_query = 1;
      }
      else
	if (strlen(query) + len > MAX_QUERY_BUFFER)
	  {
	    fprintf(stderr,"query buffer max length of %d exceeded\n",MAX_QUERY_BUFFER);
	    fprintf(stderr,"query line ignored\n");
	  }
      else
	if (query[0]!='\0')
	  sprintf(query,"%s\n%s",query,line);
      else
	strcpy(query,line);
      
      if (send_query && query[0] != '\0')
	{
	  if (!interactive) /* echo the line read from the file */
	    fprintf(stderr,"%s\n",query);

	  SendQuery(db, query, echo_query);
	  query[0] = '\0';
	}
      
       free(line); /* free storage malloc'd by GetNextLine */
    } /* while */
} 

int
main(int argc, char** argv)
{
  extern char* optarg;
  extern int optind, opterr;
  
  TgDb *db;
  char* dbname = NULL;
  char* host = NULL;
  char* port = NULL;
  char* qfilename = NULL;
  
  int c;
  int list_databases = 0 ;
  int single_line_mode = 0;
  char* single_query = NULL;
  int quiet = 0;
#ifdef NOREADLINE
  int use_readline = 0;
#else
  int use_readline = 1;
#endif
  int echo_query = 0;
 
  while ((c = getopt(argc, argv, "c:d:eintff:lhH:np:qS")) != EOF) {
    switch (c) {
    case 'c':
      single_query = optarg;
      break;
    case 'd':
      dbname = optarg;
      break;
    case 'e':
      echo_query = 1;
      break;
    case 'f':
      qfilename = optarg;
      break;
    case 'l':
      list_databases = 1;
      break;
    case 'H':
      host = optarg;
      break;
    case 'S':
      single_line_mode = 1;
      break;
    case 'n':
      use_readline = 0;
      break;
    case 'p':
      port = optarg;
      break;
    case 'q':
      quiet = 1;
      break;
    case 'h':
    default:
      usage(argv[0]);
      break;
    }
  }
  /* if we still have an argument, use it as the database name */
  if (argc - optind == 1)
    dbname = argv[optind];

  if (list_databases)
    dbname = "template1";
  
  if (dbname == NULL)
    {
      char *name;
      struct passwd *pw = getpwuid(getuid());
      name = pw->pw_name;
      fprintf(stderr, "No database name supplied, using user name: %s\n", name);
      dbname = name;
    }
  
  db = TgNewDB(dbname, host, port, NULL,NULL);
  if (TgConnectDB(db) == 0)
    {
      fprintf(stderr,"%s\n", tgDbErrMsg);
      exit(2);
    }

  if (list_databases) {
      exit(listAllDbs(db));
    }

  if (!quiet && !single_query && !qfilename) {
    printf("Welcome to the POSTGRES95 interactive sql monitor:\n\n");
    printf("   type \\? for help on slash commands\n");
    printf("   type \\q to quit\n");
    printf("   type \\g or terminate with semicolon to execute query\n");
    printf(" You are currently connected to the database: %s\n\n", dbname);
     }

  if (qfilename) {
      /* read in a file full of queries instead of reading in queries
	 interactively */
      char *line;
      char prompt[100];

      line = malloc(strlen(qfilename) + 5);
      sprintf(line,"\\i %s", qfilename);
      HandleSlashCmds(&db, line, (char**)prompt, echo_query);
  } else
      if (single_query)
	  SendQuery(db, single_query, echo_query);
      else
	  MainLoop(&db, single_line_mode, stdin, quiet, 
		   use_readline, echo_query);

  TgCloseDB(db);
  TG_FREEDB(db);

  return 0;
}



