head	1.1;
access;
symbols;
locks; strict;
comment	@ * @;


1.1
date	94.10.17.20.52.32;	author jolly;	state Exp;
branches;
next	;


desc
@@


1.1
log
@Initial revision
@
text
@/*
*  psql:  a monitor workalike to test the libtdb library
*
*       
*/

#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include "pqutils.h"
#include "tdb.h"
#include "tgPrint.h"
#include "stringutils.h"

#include "psqlHelp.h"

/* from the GNU readline library */
#include "readline.h"
#include "history.h"

#define MAX_QUERY_BUFFER 20000

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


/* print out usage for command line arguments */
static void  
usage(char* progname)
{
  fprintf(stderr,"Usage: %s [options] [dbname]\n");
  fprintf(stderr,"\t -c single query to run\n");
  fprintf(stderr,"\t -d database name\n");
  fprintf(stderr,"\t -l list available databases\n");
  fprintf(stderr,"\t -h help information\n");
  fprintf(stderr,"\t -H host\n");
  fprintf(stderr,"\t -S single line mode (i.e. query terminated by newline)\n");
  fprintf(stderr,"\t -p port\n");
  fprintf(stderr,"\t -q run quietly (no messages, no prompts)\n");
  exit(1);
}

/* print out usage for the / 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 \\?          -- help\n");
}

/* 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);
      return 0;
    }
}

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

/* the routine to get from readline(), the source is ignored */
char* 
gets_readline(char* prompt, FILE* source)
{
  return (readline(prompt));
}

/* the routine to read from a file, the prompt argument is ignored */
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: send the query string to the backend 
*/
void 
SendQuery(TgDb* db, char* query)
{
  int qres;                     /* query result */
  TgTupleGroup* results;

  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);
    }
}


/*
  Handles all the different commands that start with \ 

  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 status = 0;
  TgDb* db = *db_ptr;
  char* dbname = db->name;
  char* host = db->host;
  char* port = db->port;

  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
	  {
	    printf("closing connection to database:%s\n", dbname);
	    TgCloseDB(db);
	    TG_FREEDB(db);
	    db = TgNewDB(newdbname, host, 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, host, 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;
	      }
	    dbname = dupstr(newdbname);
	    free(*prompt_ptr);
	    *prompt_ptr = malloc(strlen(dbname) + 10);
	    sprintf(*prompt_ptr,"%s=> ", dbname);
	    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);
	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; 
    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)
{
  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);
    using_history();
    GetNextLine = gets_readline;
  }
  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 */

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

      len = strlen(line);

      if (interactive)
	  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);
	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 */
	    printf("%s\n",query);

	  SendQuery(db, 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;
  
  int c;
  int list_databases = 0 ;
  int single_line_mode = 0;
  char* single_query = NULL;
  int quiet = 0;

  while ((c = getopt(argc, argv, "c:d:lh:H:p:qS")) != EOF) {
    switch (c) {
    case 'c':
      single_query = optarg;
      break;
    case 'd':
      dbname = optarg;
      break;
    case 'l':
      list_databases = 1;
      break;
    case 'H':
      host = optarg;
      break;
    case 'S':
      single_line_mode = 1;
    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) {
    printf("Welcome to the PGLITE 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 (single_query)
    SendQuery(db, single_query);
  else
    MainLoop(&db, single_line_mode, stdin, quiet);

  TgCloseDB(db);
  TG_FREEDB(db);
}



@
