aboutsummaryrefslogtreecommitdiff
path: root/src/bin/psql/psql.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/psql.c')
-rw-r--r--src/bin/psql/psql.c1230
1 files changed, 1230 insertions, 0 deletions
diff --git a/src/bin/psql/psql.c b/src/bin/psql/psql.c
new file mode 100644
index 00000000000..d5dbfcea6fd
--- /dev/null
+++ b/src/bin/psql/psql.c
@@ -0,0 +1,1230 @@
+/*-------------------------------------------------------------------------
+ *
+ * psql.c--
+ * an interactive front-end to postgres95
+ *
+ * Copyright (c) 1996, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.1.1.1 1996/07/09 06:22:15 scrappy Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "libpq-fe.h"
+#include "stringutils.h"
+
+#include "psqlHelp.h"
+
+#ifdef NOREADLINE
+extern char *readline(char *); /* in rlstubs.c */
+#else
+/* from the GNU readline library */
+#ifdef OLD_READLINE
+#include "readline.h"
+#include "history.h"
+#else
+#include <readline/readline.h>
+#include <history.h>
+#endif
+#endif
+
+#define MAX_QUERY_BUFFER 20000
+#define MAX_FIELD_SEP_LENGTH 40
+
+#define COPYBUFSIZ 8192
+
+#define DEFAULT_FIELD_SEP " "
+#define DEFAULT_EDITOR "vi"
+#define DEFAULT_SHELL "/bin/sh"
+
+typedef struct _psqlSettings {
+ int echoQuery; /* if 1, echo the query before sending it */
+ int quiet; /* run quietly, no messages, no promt */
+ int singleStep; /* if 1, prompt for each query */
+ int singleLineMode; /* if 1, query terminated by newline */
+ int useReadline; /* use the readline routines or not */
+ int printHeader; /* print output field headers or not */
+ int fillAlign; /* fill align the fields */
+ FILE *queryFout; /* where to send the query results */
+ char fieldSep[MAX_FIELD_SEP_LENGTH]; /* field separator */
+} PsqlSettings;
+
+/* declarations for functions in this file */
+static void usage(char* progname);
+static void slashUsage();
+static void handleCopyOut(PGresult *res, int quiet);
+static void handleCopyIn(PGresult *res, int quiet);
+static int tableList(PGconn* conn, int deep_tablelist);
+static int tableDesc(PGconn* conn, char* table);
+
+char* gets_noreadline(char* prompt, FILE* source);
+char* gets_readline(char* prompt, FILE* source);
+char* gets_fromFile(char* prompt, FILE* source);
+int listAllDbs(PGconn *db, PsqlSettings *settings);
+int SendQuery(PGconn* db, char* query, PsqlSettings *settings);
+int HandleSlashCmds(PGconn** db_ptr,
+ char *line,
+ char** prompt_ptr,
+ char *query,
+ PsqlSettings *settings);
+int MainLoop(PGconn** db_ptr, FILE *source, PsqlSettings *settings);
+FILE* setFout(char *fname);
+
+
+/*
+ * usage
+ * print out usage for command line arguments
+ */
+
+static void
+usage(char* progname)
+{
+ fprintf(stderr,"Usage: %s [options] [dbname]\n",progname);
+ fprintf(stderr,"\t -a authsvc set authentication service\n");
+ fprintf(stderr,"\t -A turn off fill-justification when printing out attributes\n");
+ fprintf(stderr,"\t -c query run single query (slash commands too)\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 -F sep set the field separator (default is " ")\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 -o filename send output to filename\n");
+ fprintf(stderr,"\t -p port set port number\n");
+ fprintf(stderr,"\t -q run quietly (no messages, no prompts)\n");
+ fprintf(stderr,"\t -s single step mode (prompts for each query)\n");
+ fprintf(stderr,"\t -S single line mode (i.e. query terminated by newline)\n");
+ fprintf(stderr,"\t -T turn off printing of attribute names\n");
+ exit(1);
+}
+
+/*
+ * slashUsage
+ * print out usage for the backslash commands
+ */
+
+static void
+slashUsage()
+{
+ fprintf(stderr,"\t \\a -- toggle fill-justification of display of attributes\n");
+ fprintf(stderr,"\t \\d [<table>] -- list tables in database or columns in <table>\n");
+ fprintf(stderr,"\t \\d * -- list tables in database and columns in all tables\n");
+ fprintf(stderr,"\t \\e [<fname>] -- edit the current query buffer or <fname>\n");
+ fprintf(stderr,"\t \\f <sep> -- change field separator\n");
+ fprintf(stderr,"\t \\g -- query to backend\n");
+ fprintf(stderr,"\t \\h <command> -- help on syntax of sql commands\n");
+ fprintf(stderr,"\t \\h * -- complete description of all 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 \\o [<fname>] -- send query results file named <fname> or stdout\n");
+ fprintf(stderr,"\t \\p -- print the current query buffer\n");
+ fprintf(stderr,"\t \\q -- quit\n");
+ fprintf(stderr,"\t \\s [<fname>] -- save or print history\n");
+ fprintf(stderr,"\t \\t -- toggle output field headers (defaults to on)\n");
+ fprintf(stderr,"\t \\! [<cmd>] -- shell escape\n");
+ fprintf(stderr,"\t \\? -- help\n");
+}
+
+/*
+ * listAllDbs
+ *
+ * list all the databases in the system
+ * returns 0 if all went well
+ *
+ *
+ */
+int
+listAllDbs(PGconn *db, PsqlSettings *settings)
+{
+ PGresult *results;
+ char* query = "select * from pg_database;";
+
+ results = PQexec(db, query);
+ if (results == NULL) {
+ fprintf(stderr,"%s", PQerrorMessage(db));
+ return 1;
+ }
+
+ if (PQresultStatus(results) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr,"Unexpected error from executing: %s\n", query);
+ return 2;
+ }
+ else
+ {
+ PQdisplayTuples(results,
+ settings->queryFout,
+ settings->fillAlign,
+ settings->fieldSep,
+ settings->printHeader,
+ settings->quiet);
+ PQclear(results);
+ return 0;
+ }
+}
+
+/*
+ * tableList (PGconn* conn)
+ *
+ * List The Database Tables
+ * returns 0 if all went well
+ *
+ */
+int
+tableList (PGconn* conn, int deep_tablelist)
+{
+ char listbuf[256];
+ int nColumns;
+ int i;
+ char* ru;
+ char* rk;
+ char* rr;
+
+ PGresult* res;
+
+ listbuf[0] = '\0';
+ strcat(listbuf,"SELECT usename, relname, relkind, relhasrules");
+ strcat(listbuf," FROM pg_class, pg_user ");
+ strcat(listbuf,"WHERE ( relkind = 'r' OR relkind = 'i') ");
+ strcat(listbuf," and relname !~ '^pg_'");
+ strcat(listbuf," and relname !~ '^Inv'");
+/* the usesysid = relowner won't work on stock 1.0 dbs, need to
+ add in the int4oideq function */
+ strcat(listbuf," and usesysid = relowner");
+ strcat(listbuf," ORDER BY relname ");
+ res = PQexec(conn,listbuf);
+ if (res == NULL) {
+ fprintf(stderr,"%s", PQerrorMessage(conn));
+ return (-1);
+ }
+
+ if ((PQresultStatus(res) != PGRES_TUPLES_OK) || (PQntuples(res) <= 0)) {
+ fprintf(stderr,"No tables found in database %s.\n", PQdb(conn));
+ PQclear(res);
+ return (-1);
+ }
+
+ /* first, print out the attribute names */
+ nColumns = PQntuples(res);
+ if (nColumns > 0)
+ {
+ if ( deep_tablelist ) {
+ /* describe everything here */
+ char **table;
+ table = (char**)malloc(nColumns * sizeof(char*));
+ if ( table == NULL )
+ perror("malloc");
+
+ /* load table table*/
+ for (i=0; i < nColumns; i++) {
+ table[i] = (char *) malloc(PQgetlength(res,i,1) * sizeof(char) + 1);
+ if ( table[i] == NULL )
+ perror("malloc");
+ strcpy(table[i],PQgetvalue(res,i,1));
+ }
+
+ PQclear(res);
+ for (i=0; i < nColumns; i++) {
+ tableDesc(conn,table[i]);
+ }
+ free(table);
+ }
+ else {
+ /* Display the information */
+
+ printf ("\nDatabase = %s\n", PQdb(conn));
+ printf (" +------------------+----------------------------------+----------+\n");
+ printf (" | Owner | Relation | Type |\n");
+ printf (" +------------------+----------------------------------+----------+\n");
+
+ /* next, print out the instances */
+ for (i=0; i < PQntuples(res); i++) {
+ printf (" | %-16.16s", PQgetvalue(res,i,0));
+ printf (" | %-32.32s | ", PQgetvalue(res,i,1));
+ rk = PQgetvalue(res,i,2);
+ rr = PQgetvalue(res,i,3);
+ if (strcmp(rk, "r") == 0)
+ printf ("%-8.8s |", (rr[0] == 't') ? "view?" : "table" );
+ else
+ printf ("%-8.8s |", "index");
+ printf("\n");
+ }
+ printf (" +------------------+----------------------------------+----------+\n");
+ PQclear(res);
+ }
+ return (0);
+
+ } else {
+ fprintf (stderr, "Couldn't find any tables!\n");
+ return (-1);
+ }
+}
+
+/*
+ * Describe a table (PGconn* conn, char* table)
+ *
+ * Describe the columns in a database table.
+ * returns 0 if all went well
+ *
+ *
+ */
+int
+tableDesc (PGconn* conn, char* table)
+{
+ char descbuf[256];
+ int nColumns;
+ char *rtype;
+ int i;
+ int rsize;
+
+ PGresult* res;
+
+ /* Build the query */
+
+ descbuf[0] = '\0';
+ strcat(descbuf,"SELECT a.attnum, a.attname, t.typname, a.attlen");
+ strcat(descbuf," FROM pg_class c, pg_attribute a, pg_type t ");
+ strcat(descbuf," WHERE c.relname = '");
+ strcat(descbuf,table);
+ strcat(descbuf,"'");
+ strcat(descbuf," and a.attnum > 0 ");
+ strcat(descbuf," and a.attrelid = c.oid ");
+ strcat(descbuf," and a.atttypid = t.oid ");
+ strcat(descbuf," ORDER BY attnum ");
+ res = PQexec(conn,descbuf);
+ if (res == NULL) {
+ fprintf(stderr,"%s", PQerrorMessage(conn));
+ return (-1);
+ }
+ if ((PQresultStatus(res) != PGRES_TUPLES_OK) || (PQntuples(res) <= 0)) {
+ fprintf(stderr,"Couldn't find table %s!\n", table);
+ PQclear(res);
+ return (-1);
+ }
+ /* first, print out the attribute names */
+ nColumns = PQntuples(res);
+ if (nColumns > 0)
+ {
+ /*
+ ** Display the information
+ */
+
+ printf ("\nTable = %s\n", table);
+ printf ("+----------------------------------+----------------------------------+-------+\n");
+ printf ("| Field | Type | Length|\n");
+ printf ("+----------------------------------+----------------------------------+-------+\n");
+
+ /* next, print out the instances */
+ for (i=0; i < PQntuples(res); i++) {
+ printf ("| %-32.32s | ", PQgetvalue(res,i,1));
+ rtype = PQgetvalue(res,i,2);
+ rsize = atoi(PQgetvalue(res,i,3));
+ if (strcmp(rtype, "text") == 0) {
+ printf ("%-32.32s |", rtype);
+ printf (" %-6s |", "var" );
+ }
+ else if (strcmp(rtype, "bpchar") == 0) {
+ printf ("%-32.32s |", "char");
+ printf (" %-6i |", rsize > 0 ? rsize - 4 : 0 );
+ }
+ else if (strcmp(rtype, "varchar") == 0) {
+ printf ("%-32.32s |", rtype);
+ printf (" %-6i |", rsize > 0 ? rsize - 4 : 0 );
+ }
+ else {
+ /* array types start with an underscore */
+ if (rtype[0] != '_')
+ printf ("%-32.32s |", rtype);
+ else {
+ char *newname;
+ newname = malloc(strlen(rtype) + 2);
+ strcpy(newname, rtype+1);
+ strcat(newname, "[]");
+ printf ("%-32.32s |", newname);
+ free(newname);
+ }
+ if (rsize > 0)
+ printf ("%-6i |", rsize);
+ else
+ printf ("%-6s |", "var");
+ }
+ printf("\n");
+ }
+ printf ("+----------------------------------+----------------------------------+-------+\n");
+
+ PQclear(res);
+ return (0);
+
+ } else {
+ fprintf (stderr, "Couldn't find table %s!\n", table);
+ return (-1);
+ }
+}
+
+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(stdout);
+ 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 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
+ *
+ * return 0 if the query executed successfully
+ * returns 1 otherwise
+ */
+int
+SendQuery(PGconn* db, char* query, PsqlSettings *settings)
+{
+ PGresult* results;
+ PGnotify* notify;
+ int status = 0;
+
+ if (settings->singleStep)
+ fprintf(stdout, "\n*******************************************************************************\n");
+
+ if (settings->echoQuery || settings->singleStep) {
+ fprintf(stderr,"QUERY: %s\n",query);
+ fflush(stderr);
+ }
+
+ if (settings->singleStep) {
+ fprintf(stdout, "\n*******************************************************************************\n");
+ fflush(stdout);
+ printf("\npress return to continue ..\n");
+ gets_fromFile("",stdin);
+ }
+
+ results = PQexec(db, query);
+ if (results == NULL) {
+ fprintf(stderr,"%s",PQerrorMessage(db));
+ return 1;
+ }
+
+ switch (PQresultStatus(results)) {
+ case PGRES_TUPLES_OK:
+ PQdisplayTuples(results,
+ settings->queryFout,
+ settings->fillAlign,
+ settings->fieldSep,
+ settings->printHeader,
+ settings->quiet);
+ PQclear(results);
+ break;
+ case PGRES_EMPTY_QUERY:
+ /* do nothing */
+ break;
+ case PGRES_COMMAND_OK:
+ if (!settings->quiet)
+ fprintf(stdout,"%s\n",PQcmdStatus(results));
+ break;
+ case PGRES_COPY_OUT:
+ handleCopyOut(results, settings->quiet);
+ break;
+ case PGRES_COPY_IN:
+ handleCopyIn(results, settings->quiet);
+ break;
+ case PGRES_NONFATAL_ERROR:
+ case PGRES_FATAL_ERROR:
+ case PGRES_BAD_RESPONSE:
+ status = 1;
+ fprintf(stderr,"%s",PQerrorMessage(db));
+ break;
+
+ }
+
+ /* check for asynchronous returns */
+ notify = PQnotifies(db);
+ if (notify) {
+ fprintf(stderr,"ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
+ notify->relname, notify->be_pid);
+ free(notify);
+ }
+
+ return status;
+
+}
+
+/*
+ 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(PGconn** db_ptr,
+ char* line,
+ char** prompt_ptr,
+ char *query,
+ PsqlSettings *settings)
+{
+ int status = 0;
+ PGconn* db = *db_ptr;
+ char* dbname = PQdb(db);
+ char *optarg = NULL;
+ int len;
+
+ len = strlen(line);
+ if (len > 2)
+ optarg = leftTrim(line+2);
+ switch (line[1])
+ {
+ case 'a': /* toggles to fill fields on output */
+ if (settings->fillAlign)
+ settings->fillAlign = 0;
+ else
+ settings->fillAlign = 1;
+ if (!settings->quiet)
+ fprintf(stderr,"turning %s fill-justification\n",
+ (settings->fillAlign) ? "on" : "off" );
+ break;
+ case 'c': /* \c means connect to new database */
+ {
+ if (!optarg) {
+ fprintf(stderr,"\\c must be followed by a database name\n");
+ status = 1;
+ break;
+ }
+ if (strcmp(optarg, dbname) == 0) {
+ fprintf(stderr,"already connected to %s\n", dbname);
+ status = 1;
+ break;
+ }
+ else {
+ PGconn *olddb;
+
+ printf("closing connection to database:%s\n", dbname);
+ olddb = db;
+ db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, optarg);
+ *db_ptr = db;
+ printf("connecting to new database: %s\n", optarg);
+ if (PQstatus(db) == CONNECTION_BAD) {
+ fprintf(stderr,"%s\n", PQerrorMessage(db));
+ printf("reconnecting to %s\n", dbname);
+ db = PQsetdb(PQhost(olddb), PQport(olddb),
+ NULL, NULL, dbname);
+ *db_ptr = db;
+ if (PQstatus(db) == CONNECTION_BAD) {
+ fprintf(stderr,
+ "could not reconnect to %s. exiting\n", dbname);
+ exit(2);
+ }
+ status = 1;
+ break;
+ }
+ PQfinish(olddb);
+ free(*prompt_ptr);
+ *prompt_ptr = malloc(strlen(optarg) + 10);
+ sprintf(*prompt_ptr,"%s=> ", optarg);
+ status = 1;
+ break;
+ }
+ }
+ break;
+ case 'd': /* \d describe tables or columns in a table */
+ {
+ if (!optarg) {
+ tableList(db,0);
+ status = 1;
+ break;
+ }
+ if ( strcmp(optarg,"*") == 0 ) {
+ tableList(db, 0);
+ tableList(db, 1);
+ }
+ else {
+ tableDesc(db,optarg);
+ }
+ status = 1;
+ break;
+ }
+ case 'e':
+ {
+ char s[256];
+ int fd;
+ int ql = strlen(query);
+ int f_arg = 0;
+ int cc;
+ if (optarg)
+ {
+ f_arg = 1;
+ strcpy(s, optarg);
+ }
+ else
+ {
+ sprintf(s, "/tmp/psql.%d.%d", getuid(), getpid());
+ unlink(s);
+ if (ql)
+ {
+ if ((fd=open(s, O_EXCL|O_CREAT|O_WRONLY, 0600))==-1)
+ {
+ perror(s);
+ break;
+ }
+ if (query[ql-1]!='\n')
+ strcat(query, "\n");
+ if (write(fd, query, ql)!=ql)
+ {
+ perror(s);
+ close(fd);
+ unlink(s);
+ break;
+ }
+ close(fd);
+ }
+ }
+ {
+ char sys[256];
+ char *editorName;
+ editorName = getenv("EDITOR");
+ if (editorName == NULL)
+ editorName = DEFAULT_EDITOR;
+ sprintf(sys, "exec %s %s", editorName, s);
+ system(sys);
+ }
+ if ((fd=open(s, O_RDONLY))==-1)
+ {
+ if (!f_arg)
+ unlink(s);
+ break;
+ }
+ if ((cc=read(fd, query, MAX_QUERY_BUFFER))==-1)
+ {
+ perror(s);
+ close(fd);
+ if (!f_arg)
+ unlink(s);
+ break;
+ }
+ query[cc]='\0';
+ close(fd);
+ if (!f_arg)
+ unlink(s);
+ rightTrim(query);
+ if (query[strlen(query)-1]==';')
+ return 0;
+ break;
+ }
+ case 'f':
+ if (optarg)
+ strcpy(settings->fieldSep,optarg);
+ else
+ strcpy(settings->fieldSep,DEFAULT_FIELD_SEP);
+ break;
+ case 'g': /* \g means send query */
+ status = 0;
+ break;
+ case 'i': /* \i is include file */
+ {
+ FILE* fd;
+
+ if (!optarg) {
+ fprintf(stderr,"\\i must be followed by a file name\n");
+ status = 1;
+ break;
+ }
+
+ if ( (fd = fopen(optarg, "r")) == NULL)
+ {
+ fprintf(stderr,"file named %s could not be opened\n",optarg);
+ status = 1;
+ break;
+ }
+ MainLoop(&db, fd, settings);
+ fclose(fd);
+ status = 1;
+ break;
+ }
+ case 'h':
+ {
+ char* cmd;
+ int i, numCmds;
+ int all_help = 0;
+
+ if (!optarg) {
+ 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++;
+ }
+ printf("type \\h * for a complete description of all commands\n");
+ }
+ else
+ {
+ cmd = optarg;
+
+ numCmds = 0;
+ while (QL_HELP[numCmds++].cmd != NULL);
+
+ numCmds = numCmds - 1;
+
+ if ( strcmp(cmd,"*") == 0 ) {
+ all_help=1;
+ }
+
+ for (i=0; i<numCmds;i++) {
+ if (strcmp(QL_HELP[i].cmd, cmd) == 0 || all_help) {
+ printf("Command: %s\n",QL_HELP[i].cmd);
+ printf("Description: %s\n", QL_HELP[i].help);
+ printf("Syntax:\n");
+ printf("%s\n", QL_HELP[i].syntax);
+ if ( all_help ) {
+ printf("\n");
+ }
+ else {
+ break;
+ }
+ }
+ }
+ if (i == numCmds && ! all_help)
+ 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,settings);
+ status = 1;
+ break;
+ case 'o':
+ settings->queryFout = setFout(optarg);
+ break;
+ case 'p':
+ if (query) {
+ fputs(query, stdout);
+ fputc('\n', stdout);
+ }
+ break;
+ case 'q': /* \q is quit */
+ status = 2;
+ break;
+ case 's': /* \s is save history to a file */
+ {
+ char* fname;
+
+ if (!optarg) {
+ fprintf(stderr,"\\s must be followed by a file name\n");
+ status = 1;
+ break;
+ }
+
+ fname = optarg;
+ if (write_history(fname) != 0)
+ {
+ fprintf(stderr,"cannot write history to %s\n",fname);
+ }
+ status = 1;
+ break;
+ }
+ case 't':
+ if ( settings->printHeader )
+ settings->printHeader = 0;
+ else
+ settings->printHeader = 1;
+ if (!settings->quiet)
+ fprintf(stderr,"turning %s printing of field headers\n",
+ (settings->printHeader) ? "on" : "off" );
+ break;
+ case '!':
+ if (!optarg) {
+ char sys[256];
+ char *shellName;
+ shellName = getenv("SHELL");
+ if (shellName == NULL)
+ shellName = DEFAULT_SHELL;
+ sprintf(sys,"exec %s", shellName);
+ system(sys);
+ }
+ else
+ system(optarg);
+ break;
+ default:
+ case '?': /* \? is help */
+ slashUsage();
+ 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
+*/
+int
+MainLoop(PGconn** db_ptr,
+ FILE* source,
+ PsqlSettings *settings)
+{
+ char* prompt; /* readline prompt */
+ char* line; /* line of input*/
+ int len; /* length of the line */
+ char query[MAX_QUERY_BUFFER]; /* multi-line query storage */
+ PGconn* db = *db_ptr;
+ char* dbname = PQdb(db);
+ int exitStatus = 0;
+
+ int slashCmdStatus = 0;
+ /* slashCmdStatus 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 (settings->quiet)
+ prompt[0] = '\0';
+ else
+ sprintf(prompt,"%s=> ", dbname);
+ if (settings->useReadline) {
+ 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)
+ {
+ exitStatus = 0;
+ line = rightTrim(line); /* remove whitespaces on the right, incl. \n's */
+
+ if (line[0] == '\0') {
+ free(line);
+ continue;
+ }
+
+ /* 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] == '-') {
+ if (settings->singleStep) /* in single step mode, show comments */
+ fprintf(stdout,"%s\n",line);
+ free(line);
+ continue;
+ }
+
+ len = strlen(line);
+
+ if (interactive && settings->useReadline)
+ 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 = settings->singleLineMode || (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';
+ }
+ }
+
+ /* slash commands have to be on their own line */
+ if (line[0] == '\\') {
+ slashCmdStatus = HandleSlashCmds(db_ptr,
+ line,
+ &prompt,
+ query,
+ settings);
+ db = *db_ptr; /* in case \c changed the database */
+ if (slashCmdStatus == 1)
+ continue;
+ if (slashCmdStatus == 2)
+ break;
+ if (slashCmdStatus == 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') {
+ strcat(query,"\n");
+ strcat(query,line);
+ }
+ else
+ strcpy(query,line);
+
+ if (send_query && query[0] != '\0')
+ {
+ /* echo the line read from the file,
+ unless we are in single_step mode, because single_step mode
+ will echo anyway */
+ if (!interactive && !settings->singleStep)
+ fprintf(stderr,"%s\n",query);
+
+ exitStatus = SendQuery(db, query, settings);
+ query[0] = '\0';
+ }
+
+ free(line); /* free storage malloc'd by GetNextLine */
+ } /* while */
+ return exitStatus;
+}
+
+int
+main(int argc, char** argv)
+{
+ extern char* optarg;
+ extern int optind, opterr;
+
+ PGconn *db;
+ char* dbname = NULL;
+ char* host = NULL;
+ char* port = NULL;
+ char* qfilename = NULL;
+ char errbuf[ERROR_MSG_LENGTH];
+
+ PsqlSettings settings;
+
+ char* singleQuery = NULL;
+
+ int listDatabases = 0 ;
+ int exitStatus = 0;
+ int singleSlashCmd = 0;
+ int c;
+
+
+#ifdef NOREADLINE
+ settings.useReadline = 0;
+#else
+ settings.useReadline = 1;
+#endif
+
+ settings.quiet = 0;
+ settings.fillAlign = 1;
+ settings.printHeader = 1;
+ settings.echoQuery = 0;
+ settings.singleStep = 0;
+ settings.singleLineMode = 0;
+ settings.queryFout = stdout;
+ strcpy(settings.fieldSep, DEFAULT_FIELD_SEP);
+
+ while ((c = getopt(argc, argv, "Aa:c:d:ef:F:lhH:nso:p:qST")) != EOF) {
+ switch (c) {
+ case 'A':
+ settings.fillAlign = 0;
+ break;
+ case 'a':
+ fe_setauthsvc(optarg, errbuf);
+ break;
+ case 'c':
+ singleQuery = optarg;
+ if ( singleQuery[0] == '\\' ) {
+ singleSlashCmd=1;
+ }
+ break;
+ case 'd':
+ dbname = optarg;
+ break;
+ case 'e':
+ settings.echoQuery = 1;
+ break;
+ case 'f':
+ qfilename = optarg;
+ break;
+ case 'F':
+ strncpy(settings.fieldSep,optarg,MAX_FIELD_SEP_LENGTH);
+ break;
+ case 'l':
+ listDatabases = 1;
+ break;
+ case 'H':
+ host = optarg;
+ break;
+ case 'n':
+ settings.useReadline = 0;
+ break;
+ case 'o':
+ settings.queryFout = setFout(optarg);
+ break;
+ case 'p':
+ port = optarg;
+ break;
+ case 'q':
+ settings.quiet = 1;
+ break;
+ case 's':
+ settings.singleStep = 1;
+ break;
+ case 'S':
+ settings.singleLineMode = 1;
+ break;
+ case 'T':
+ settings.printHeader = 0;
+ 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 (listDatabases)
+ dbname = "template1";
+
+ db = PQsetdb(host, port, NULL, NULL, dbname);
+ dbname = PQdb(db);
+
+ if (PQstatus(db) == CONNECTION_BAD) {
+ fprintf(stderr,"Connection to database '%s' failed.\n", dbname);
+ fprintf(stderr,"%s",PQerrorMessage(db));
+ exit(1);
+ }
+ if (listDatabases) {
+ exit(listAllDbs(db,&settings));
+ }
+
+ if (!settings.quiet && !singleQuery && !qfilename) {
+ printf("Welcome to the POSTGRES95 interactive sql monitor:\n");
+ printf(" Please read the file COPYRIGHT for copyright terms of POSTGRES95\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 || singleSlashCmd) {
+ /* read in a file full of queries instead of reading in queries
+ interactively */
+ char *line;
+ char prompt[100];
+
+ if ( singleSlashCmd ) {
+ /* Not really a query, but "Do what I mean, not what I say." */
+ line = singleQuery;
+ }
+ else {
+ line = malloc(strlen(qfilename) + 5);
+ sprintf(line,"\\i %s", qfilename);
+ }
+ HandleSlashCmds(&db, line, (char**)prompt, "", &settings);
+
+ } else {
+ if (singleQuery) {
+ exitStatus = SendQuery(db, singleQuery, &settings);
+ }
+ else
+ exitStatus = MainLoop(&db, stdin, &settings);
+ }
+
+ PQfinish(db);
+
+ return exitStatus;
+}
+
+
+static void
+handleCopyOut(PGresult *res, int quiet)
+{
+ bool copydone = false;
+ char copybuf[COPYBUFSIZ];
+ int ret;
+
+ if (!quiet)
+ fprintf(stdout, "Copy command returns...\n");
+
+ while (!copydone) {
+ ret = PQgetline(res->conn, copybuf, COPYBUFSIZ);
+
+ if (copybuf[0] == '.' && copybuf[1] =='\0') {
+ copydone = true; /* don't print this... */
+ } else {
+ fputs(copybuf, stdout);
+ switch (ret) {
+ case EOF:
+ copydone = true;
+ /*FALLTHROUGH*/
+ case 0:
+ fputc('\n', stdout);
+ break;
+ case 1:
+ break;
+ }
+ }
+ }
+ fflush(stdout);
+ PQendcopy(res->conn);
+}
+
+
+static void
+handleCopyIn(PGresult *res, int quiet)
+{
+ bool copydone = false;
+ bool firstload;
+ bool linedone;
+ char copybuf[COPYBUFSIZ];
+ char *s;
+ int buflen;
+ int c;
+
+ if (!quiet) {
+ fputs("Enter info followed by a newline\n", stdout);
+ fputs("End with a dot on a line by itself.\n", stdout);
+ }
+
+ /*
+ * eat extra newline still in input buffer
+ *
+ */
+ fflush(stdin);
+ if ((c = getc(stdin)) != '\n' && c != EOF) {
+ (void) ungetc(c, stdin);
+ }
+
+ while (!copydone) { /* for each input line ... */
+ if (!quiet) {
+ fputs(">> ", stdout);
+ fflush(stdout);
+ }
+ firstload = true;
+ linedone = false;
+ while (!linedone) { /* for each buffer ... */
+ s = copybuf;
+ buflen = COPYBUFSIZ;
+ for (; buflen > 1 &&
+ !(linedone = (c = getc(stdin)) == '\n' || c == EOF);
+ --buflen) {
+ *s++ = c;
+ }
+ if (c == EOF) {
+ /* reading from stdin, but from a file */
+ PQputline(res->conn, ".");
+ copydone = true;
+ break;
+ }
+ *s = '\0';
+ PQputline(res->conn, copybuf);
+ if (firstload) {
+ if (!strcmp(copybuf, ".")) {
+ copydone = true;
+ }
+ firstload = false;
+ }
+ }
+ PQputline(res->conn, "\n");
+ }
+ PQendcopy(res->conn);
+}
+
+
+/* try to open fname and return a FILE*,
+ if it fails, use stdout, instead */
+FILE*
+setFout(char *fname)
+{
+ FILE *queryFout;
+
+ if (!fname)
+ queryFout = stdout;
+ else {
+ queryFout = fopen(fname, "w");
+ if (!queryFout) {
+ perror(fname);
+ queryFout = stdout;
+ }
+ }
+
+ return queryFout;
+}
+