diff options
author | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
---|---|---|
committer | Marc G. Fournier <scrappy@hub.org> | 1996-07-09 06:22:35 +0000 |
commit | d31084e9d1118b25fd16580d9d8c2924b5740dff (patch) | |
tree | 3179e66307d54df9c7b966543550e601eb55e668 /src/bin/psql | |
download | postgresql-d31084e9d1118b25fd16580d9d8c2924b5740dff.tar.gz postgresql-d31084e9d1118b25fd16580d9d8c2924b5740dff.zip |
Postgres95 1.01 Distribution - Virgin SourcesPG95-1_01
Diffstat (limited to 'src/bin/psql')
-rw-r--r-- | src/bin/psql/Makefile | 65 | ||||
-rw-r--r-- | src/bin/psql/psql.c | 1230 | ||||
-rw-r--r-- | src/bin/psql/psqlHelp.h | 168 | ||||
-rw-r--r-- | src/bin/psql/rlstubs.c | 41 | ||||
-rw-r--r-- | src/bin/psql/stringutils.c | 104 | ||||
-rw-r--r-- | src/bin/psql/stringutils.h | 51 |
6 files changed, 1659 insertions, 0 deletions
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile new file mode 100644 index 00000000000..8e6ab5bb4f1 --- /dev/null +++ b/src/bin/psql/Makefile @@ -0,0 +1,65 @@ +#------------------------------------------------------------------------- +# +# Makefile.inc-- +# Makefile for bin/psql +# +# Copyright (c) 1994, Regents of the University of California +# +# +# IDENTIFICATION +# $Header: /cvsroot/pgsql/src/bin/psql/Makefile,v 1.1.1.1 1996/07/09 06:22:15 scrappy Exp $ +# +#------------------------------------------------------------------------- + +PROG= psql + +MKDIR= ../../mk +include $(MKDIR)/postgres.mk +include ../Makefile.global + +# +#USE_READLINE is set in Makefile.global +# + +ifeq ($(USE_READLINE), true) + CFLAGS += -I$(READLINE_INCDIR) -I$(HISTORY_INCDIR) + +# if you are using an older readline that uses #include "readline.h" instead +# of #include <readline/readline.h>, +# uncomment this +# CFLAGS += -DOLD_READLINE + + LIBCURSES= -lcurses + LD_ADD += -L$(READLINE_LIBDIR) -L$(HISTORY_LIBDIR) -lreadline -lhistory $(LIBCURSES) +# use the following if your readline has no separate history lib +# LD_ADD += -L$(READLINE_LIBDIR) -lreadline $(LIBCURSES) + + ifeq ($(PORTNAME), ultrix4) + LD_ADD += -ltermcap + else + ifeq ($(PORTNAME), sparc) + LD_ADD += -ltermcap + else + ifeq ($(PORTNAME), linux) + LD_ADD += -ltermcap + endif + ifeq ($(PORTNAME), next) + LD_ADD += -ltermcap + endif + endif + endif +else + CFLAGS += -DNOREADLINE +endif + +SRCS= psql.c stringutils.c + +ifneq ($(USE_READLINE), true) +SRCS+= rlstubs.c +endif + +include $(MKDIR)/postgres.prog.mk + + + + 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; +} + diff --git a/src/bin/psql/psqlHelp.h b/src/bin/psql/psqlHelp.h new file mode 100644 index 00000000000..e0d5077bc3b --- /dev/null +++ b/src/bin/psql/psqlHelp.h @@ -0,0 +1,168 @@ +/*------------------------------------------------------------------------- + * + * psqlHelp.h-- + * Help for query language syntax + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: psqlHelp.h,v 1.1.1.1 1996/07/09 06:22:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +struct _helpStruct { + char* cmd; /* the command name */ + char* help; /* the help associated with it */ + char* syntax; /* the syntax associated with it */ +} ; + +static struct _helpStruct QL_HELP[] = { + { "abort", + "abort the current transaction", + "abort [transaction];"}, + { "abort transaction", + "abort the current transaction", + "abort [transaction];"}, + { "alter table", + "add/rename attributes, rename tables", + "alter table <relname> [*] add column <attr> <type>;\n\talter table <relname> [*] rename [column] <attr1> to <attr2>;\n\talter table <relname1> rename to <relname2>"}, + { "begin", + "begin a new transaction", + "begin [transaction|work];"}, + { "begin transaction", + "begin a new transaction", + "begin [transaction|work];"}, + { "begin work", + "begin a new transaction", + "begin [transaction|work];"}, + { "cluster", + "create a clustered index (from an existing index)", + "cluster <index_name> on <relation_name>"}, + { "close", + "close an existing cursor (portal)", + "close <portalname>;"}, + { "commit", + "commit a transaction", + "commit [work]"}, + { "commit work", + "commit a transaction", + "commit [work]"}, + { "copy", + "copy data to and from a table", + "copy [binary] [nonulls] <relname>\n\t{to|from} {<filename>|stdin|stdout} [using delimiters <delim>];"}, + { "create", + "Please more be specific:", + "\tcreate aggregate\n\tcreate database\n\tcreate function\n\tcreate index\n\tcreate operator\n\tcreate rule\n\tcreate table\n\tcreate type\n\tcreate view"}, + { "create aggregate", + "define an aggregate function", + "create aggregate <agg_name> [as] (basetype = <data_type>, \n\t[sfunc1 = <sfunc_1>, stype1 = <sfunc1_return_type>]\n\t[sfunc2 = <sfunc_2>, stype2 = <sfunc2_return_type>]\n\t[,finalfunc = <final-function>]\n\t[,initcond1 = <initial-cond1>][,initcond2 = <initial-cond2>]);"}, + { "create database", + "create a database", + "create database <dbname>"}, + { "create function", + "create a user-defined function", + "create function <function_name> ([<type1>,...<typeN>]) returns <return_type>\n\tas '<object_filename>'|'<sql-queries>'\n\tlanguage 'c'|'sql'|'internal';"}, + { "create index", + "construct an index", + "create index <indexname> on <relname> using <access_method> (<attr1>|<funcname>(<attr1>,...) <type_class1>);"}, + { "create operator", + "create a user-defined operator", + "create operator <operator_name> (\n\t[leftarg = <type1>][,rightarg = <type2>]\n\t,procedure = <func_name>,\n\t[,commutator = <com_op>][,negator = <neg_op>]\n\t[,restrict = <res_proc>][,hashes]\n\t[,join = <join_proc>][,sort = <sort_op1>...<sort_opN>]);"}, + { "create rule", + "define a new rule", + "create rule <rule_name> as on\n\t[select|update|delete|insert]\n\tto <object> [where <qual>]\n\tdo [instead] [<action>|nothing| [<actions>]];"}, + { "create table", + "create a new table", + "create table <relname> ( <attr1> <type1>,... <attrN> <typeN>)\n\t[inherits (<relname1>,...<relnameN>\n\tarchive=<archive_mode>\n\tstore=<smgr_name>\n\tarch_store=<smgr_name>];"}, + { "create type", + "create a new user-defined base data type", + "create type <typename> (\n\tinternallength = (<number> | variable),\n\t[externallength = (<number>|variable),]\n\tinput=<input_function>, output = <output_function>\n\t[,element = <typename>][,delimiter=<character>][,default=\'<string>\']\n\t[,send = <send_function>][,receive = <receive_function>][,passedbyvalue]);"}, + { "create view", + "create a view", + "create view <view_name> as select <expr1>[as <attr1>][,... <exprN>[as <attrN>]] [from <from_list>] [where <qual>];"}, + { "declare", + "set up a cursor (portal)", + "declare <portalname> [binary] cursor for\n\tselect [distinct]\n\t<expr1> [as <attr1>],...<exprN> [as <attrN>]\n\t[from <from_list>] [where <qual>]\n\t[order by <attr1> [using <op1>],... <attrN> [using <opN>]];"}, + { "delete", + "delete tuples", + "delete from <relname> [where <qual>];"}, + { "drop", + "Please more be specific:", + "\tdrop aggregate\n\tdrop database\n\tdrop function\n\tdrop index\n\tdrop operator\n\tdrop rule\n\tdrop table\n\tdrop type\n\tdrop view"}, + { "drop aggregate", + "remove an aggregate function", + "drop aggregate <agg_name>;"}, + { "drop database", + "remove a database", + "drop database <dbname>"}, + { "drop function", + "remove a user-defined function", + "drop function <funcname> ([<type1>,....<typeN>]);"}, + { "drop index", + "remove an existing index", + "drop index <indexname>;"}, + { "drop operator", + "remove a user-defined operator", + "drop operator <operator_name> ([<ltype>|none],[<rtype>|none]);"}, + { "drop rule", + "remove a rule", + "drop rule <rulename>;"}, + { "drop table", + "remove a table", + "drop table <relname>[,...<relnameN];"}, + { "drop type", + "remove a user-defined base type", + "drop type <typename>;"}, + { "drop view", + "remove a view", + "drop view <view_name>"}, + { "end", + "end the current transaction", + "end [transaction];"}, + { "end transaction", + "end the current transaction", + "end [transaction];"}, + { "explain", + "explain the query execution plan", + "explain [with {cost|full_plan}] <query>"}, + { "extend index", + "extend a partial index", + "extend index <indexname> [where <qual>];"}, + { "fetch", + "retrieve tuples from a cursor (portal)", + "fetch [forward|backward] [<number>|all] [in <portalname>];"}, + { "grant", + "grant access control to a user or group", + "grant <privilege[,privilege,...]> on <rel1>[,...<reln>] to \n[public | group <group> | <username>]\n\t privilege is {ALL | SELECT | INSERT | UPDATE | DELETE | RULE}"}, + { "insert", + "insert tuples", + "insert into <relname> [(<attr1>...<attrN>)]\n\t[values (<expr1>...<exprN>); |\n\tselect <expr1>,...<exprN> [from <from_clause>] [where <qual>];"}, + { "listen", + "listen for notification on a relation", + "listen <relname>"}, + { "load", + "dynamically load a module", + "load <filename>;"}, + { "notify", + "signal all frontends and backends listening on a relation", + "notify <relname>"}, + { "purge", + "purge historical data", + "purge <relname> [before <abstime>] [after <reltime>];"}, + { "revoke", + "revoke access control from a user or group", + "revoke <privilege[,privilege,...]> on <rel1>[,...<reln>] from \n[public | group <group> | <username>]\n\t privilege is {ALL | SELECT | INSERT | UPDATE | DELETE | RULE}"}, + { "rollback", + "abort a transaction", + "rollback [work]"}, + { "select", + "retrieve tuples", + "select [distinct on <attr>] <expr1> [as <attr1>], ... <exprN> [as <attrN>]\n\t[into table <relname>] [from <from_list>]\n\t[where <qual>]\n\t[order by <attr1>\n\t\t[using <op1>],..<attrN> [[using <opN>] | ASC | DESC]];" }, + { "update", + "update tuples", + "update <relname> set <attr1>=<expr1>,...<attrN>=<exprN> [from <from_clause>] [where <qual>];"}, + { "vacuum", + "vacuum the database, i.e. cleans out deleted records, updates statistics", + "vacuum;"}, + { NULL, NULL, NULL} /* important to keep a NULL terminator here! */ +}; diff --git a/src/bin/psql/rlstubs.c b/src/bin/psql/rlstubs.c new file mode 100644 index 00000000000..2640d3ce094 --- /dev/null +++ b/src/bin/psql/rlstubs.c @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------- + * + * rlstubs.c-- + * stub routines when compiled without readline and history libraries + * + * Copyright (c) 1994-5, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/bin/psql/Attic/rlstubs.c,v 1.1.1.1 1996/07/09 06:22:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> + +char * +readline(char *prompt) +{ + static char buf[500]; + + printf("%s"); + return fgets(buf, 500, stdin); +} + +int +write_history() +{ + return 0; +} + +int +using_history() +{ + return 0; +} + +int +add_history() +{ + return 0; +} diff --git a/src/bin/psql/stringutils.c b/src/bin/psql/stringutils.c new file mode 100644 index 00000000000..0a9043c6337 --- /dev/null +++ b/src/bin/psql/stringutils.c @@ -0,0 +1,104 @@ +/*------------------------------------------------------------------------- + * + * stringutils.c-- + * simple string manipulation routines + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/bin/psql/stringutils.c,v 1.1.1.1 1996/07/09 06:22:15 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include "stringutils.h" + +/* all routines assume null-terminated strings! */ + +/* removes whitespaces from the left, right and both sides of a string */ +/* MODIFIES the string passed in and returns the head of it */ +char* leftTrim(char* s) +{ + char* s2 = s; + int shift=0; + int j=0; + + while (isspace(*s)) + { s++; shift++;} + if (shift > 0) + { + while ( (s2[j] = s2[j+shift]) !='\0') + j++; + } + + return s2; +} + +char* rightTrim(char* s) +{ + char* sEnd; + sEnd = s+strlen(s)-1; + while (isspace(*sEnd)) + sEnd--; + if (sEnd < s) + s[0]='\0'; + else + s[sEnd-s+1]='\0'; + return s; +} + +char* doubleTrim(char* s) +{ + strcpy(s,leftTrim(rightTrim(s))); + return s; +} + +/* dupstr : copies a string, while allocating space for it. + the CALLER is responsible for freeing the space + returns NULL if the argument is NULL*/ +char* dupstr(char *s) +{ + char* result; + + if (s == NULL) + return NULL; + + result = (char*)malloc(strlen(s)+1); + strcpy(result, s); + return result; +} + + +#ifdef STRINGUTILS_TEST +void testStringUtils() +{ + static char* tests[] = {" goodbye \n", /* space on both ends */ + "hello world", /* no spaces to trim */ + "", /* empty string */ + "a", /* string with one char*/ + " ", /* string with one whitespace*/ + NULL_STR}; + + int i=0; + while (tests[i]!=NULL_STR) + { + char* t; + t = dupstr(tests[i]); + printf("leftTrim(%s) = ",t); + printf("%sEND\n", leftTrim(t)); + t = dupstr(tests[i]); + printf("rightTrim(%s) = ",t); + printf("%sEND\n", rightTrim(t)); + t = dupstr(tests[i]); + printf("doubleTrim(%s) = ",t); + printf("%sEND\n", doubleTrim(t)); + i++; + } + +} + +#endif diff --git a/src/bin/psql/stringutils.h b/src/bin/psql/stringutils.h new file mode 100644 index 00000000000..d8564a02d08 --- /dev/null +++ b/src/bin/psql/stringutils.h @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * stringutils.h-- + * + * + * Copyright (c) 1994, Regents of the University of California + * + * $Id: stringutils.h,v 1.1.1.1 1996/07/09 06:22:16 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef STRINGUTILS_H +#define STRINGUTILS_H + +/* use this for memory checking of alloc and free using Tcl's memory check + package*/ +#ifdef TCL_MEM_DEBUG +#include <tcl.h> +#define malloc(x) ckalloc(x) +#define free(x) ckfree(x) +#define realloc(x,y) ckrealloc(x,y) +#endif + +/* string fiddling utilties */ + +/* all routines assume null-terminated strings! as arguments */ + +/* removes whitespaces from the left, right and both sides of a string */ +/* MODIFIES the string passed in and returns the head of it */ +extern char* leftTrim(char* s); +extern char* rightTrim(char* s); +extern char* doubleTrim(char* s); + +/* dupstr : copies a string, while making room for it */ +/* the CALLER is responsible for freeing the space */ +/* returns NULL if the argument is NULL */ +extern char* dupstr(char *s); + +#ifdef STRINGUTILS_TEST +extern void testStringUtils(); +#endif + +#ifndef NULL_STR +#define NULL_STR (char*)0 +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#endif /* STRINGUTILS_H */ |