diff --git a/src/bin/psql/psql.c b/src/bin/psql/psql.c index 2201c06426..4bb783f564 100644 --- a/src/bin/psql/psql.c +++ b/src/bin/psql/psql.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.6 1996/07/23 03:03:43 scrappy Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.7 1996/07/25 06:46:35 julian Exp $ * *------------------------------------------------------------------------- */ @@ -16,8 +16,8 @@ #include #include #include +#include #include - #include "libpq-fe.h" #include "stringutils.h" @@ -32,52 +32,57 @@ extern char *readline(char *); /* in rlstubs.c */ #include "history.h" #else #include -#include +#include #endif #endif #define MAX_QUERY_BUFFER 20000 -#define MAX_FIELD_SEP_LENGTH 40 #define COPYBUFSIZ 8192 -#define DEFAULT_FIELD_SEP " " +#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 */ + PGconn *db; /* connection to backend */ + FILE *queryFout; /* where to send the query results */ + PQprintOpt opt; /* options to be passed to PQprint */ + char *prompt; /* prompt to display */ + char *gfname; /* one-shot file output argument for \g */ + bool notty; /* input or output is not a tty */ + bool pipe; /* queryFout is from a popen() */ + bool echoQuery; /* echo the query before sending it */ + bool quiet; /* run quietly, no messages, no promt */ + bool singleStep; /* prompt before for each query */ + bool singleLineMode; /* query terminated by newline */ + bool useReadline; /* use libreadline routines */ } 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); +static void handleCopyOut(PGresult *res, bool quiet); +static void handleCopyIn(PGresult *res, bool quiet); +static int tableList(PsqlSettings *ps, bool deep_tablelist); +static int tableDesc(PsqlSettings *ps, 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, +int listAllDbs(PsqlSettings *settings); +int SendQuery(PsqlSettings *settings, char* query); +int HandleSlashCmds(PsqlSettings *settings, char *line, - char** prompt_ptr, - char *query, - PsqlSettings *settings); -int MainLoop(PGconn** db_ptr, FILE *source, PsqlSettings *settings); -FILE* setFout(char *fname); + char *query); +int MainLoop(PsqlSettings *settings, FILE *source); +/* probably should move this into libpq */ +void PQprint(FILE *fp, + PGresult *res, + PQprintOpt *po + ); +FILE* setFout(PsqlSettings *ps, char *fname); /* * usage @@ -89,22 +94,24 @@ 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 -A turn off alignment 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 -h host set database server host\n"); + fprintf(stderr,"\t -H turn on html3.0 table output\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 -o filename send output to filename or (|pipe)\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"); + fprintf(stderr,"\t -t turn off printing of attribute headers\n"); + fprintf(stderr,"\t -T html set html3.0 table command options (cf. -H)\n"); + fprintf(stderr,"\t -x turn on expanded output (field names on left)"); exit(1); } @@ -113,29 +120,60 @@ usage(char* progname) * print out usage for the backslash commands */ -static void -slashUsage() +char *on(bool f) { - fprintf(stderr,"\t \\a -- toggle fill-justification of display of attributes\n"); + return f? "on": "off"; +} + +static void +slashUsage(PsqlSettings *ps) +{ + fprintf(stderr,"\t \\a -- toggle field-alignment (currenty %s)\n", on(ps->opt.align)); + fprintf(stderr,"\t \\C [] -- set html3 caption (currently '%s')\n", ps->opt.caption? ps->opt.caption: ""); + fprintf(stderr,"\t \\c -- connect to new database (currently '%s')\n", PQdb(ps->db)); fprintf(stderr,"\t \\d [] -- list tables in database or columns in
\n"); fprintf(stderr,"\t \\d * -- list tables in database and columns in all tables\n"); fprintf(stderr,"\t \\e [] -- edit the current query buffer or \n"); - fprintf(stderr,"\t \\f -- change field separator\n"); - fprintf(stderr,"\t \\g -- query to backend\n"); - fprintf(stderr,"\t \\h -- help on syntax of sql commands\n"); + fprintf(stderr,"\t \\f [] -- change field separater (currently '%s')\n", ps->opt.fieldSep); + fprintf(stderr,"\t \\g [] -- send query to backend [and place results in ]\n"); + fprintf(stderr,"\t \\g | -- send query to backend and pipe results into \n"); + fprintf(stderr,"\t \\h [] -- 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 -- read queries from filename\n"); + fprintf(stderr,"\t \\H -- toggle html3 output (currently %s)\n", on(ps->opt.html3)); + fprintf(stderr,"\t \\i -- read and execute queries from filename\n"); fprintf(stderr,"\t \\l -- list all databases\n"); - fprintf(stderr,"\t \\o [] -- send query results file named or stdout\n"); + fprintf(stderr,"\t \\m -- toggle monitor-like type-setting (currently %s)\n", on(ps->opt.standard)); + fprintf(stderr,"\t \\o [] -- send all query results to or stdout\n"); + fprintf(stderr,"\t \\o | -- pipe all query results through \n"); fprintf(stderr,"\t \\p -- print the current query buffer\n"); fprintf(stderr,"\t \\q -- quit\n"); - fprintf(stderr,"\t \\s [] -- save or print history\n"); - fprintf(stderr,"\t \\t -- toggle output field headers (defaults to on)\n"); - fprintf(stderr,"\t \\! [] -- shell escape\n"); + fprintf(stderr,"\t \\r [] -- edit then execute on save\n"); + fprintf(stderr,"\t \\s [] -- print history or save it in \n"); + fprintf(stderr,"\t \\t -- toggle table output header (currently %s)\n", on(ps->opt.header)); + fprintf(stderr,"\t \\T [] -- set html3.0
options (currently '%s')\n", ps->opt.tableOpt? ps->opt.tableOpt: ""); + fprintf(stderr,"\t \\x -- toggle expanded output (currently %s)\n", on(ps->opt.expanded)); + fprintf(stderr,"\t \\z -- zorch current query buffer (i.e clear it)\n"); + fprintf(stderr,"\t \\! [] -- shell escape or command\n"); fprintf(stderr,"\t \\? -- help\n"); } +PGresult * +PSQLexec(PsqlSettings *ps, char *query) +{ + PGresult *res = PQexec(ps->db, query); + if (!res) + fputs(PQerrorMessage(ps->db), stderr); + else + { + if (PQresultStatus(res)==PGRES_COMMAND_OK || + PQresultStatus(res)==PGRES_TUPLES_OK) + return res; + if (!ps->quiet) + fputs(PQerrorMessage(ps->db), stderr); + PQclear(res); + } + return NULL; +} /* * listAllDbs * @@ -144,50 +182,37 @@ slashUsage() * * */ -int -listAllDbs(PGconn *db, PsqlSettings *settings) + +int +listAllDbs(PsqlSettings *ps) { PGresult *results; char* query = "select * from pg_database;"; - results = PQexec(db, query); - if (results == NULL) { - fprintf(stderr,"%s", PQerrorMessage(db)); + if (!(results=PSQLexec(ps, query))) 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); + PQprint(ps->queryFout, + results, + &ps->opt); PQclear(results); return 0; } } /* - * tableList (PGconn* conn) * * List The Database Tables * returns 0 if all went well * */ int -tableList (PGconn* conn, int deep_tablelist) +tableList (PsqlSettings *ps, bool deep_tablelist) { char listbuf[256]; int nColumns; int i; - char* ru; char* rk; char* rr; @@ -203,17 +228,8 @@ tableList (PGconn* conn, int deep_tablelist) 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); - } + if (!(res=PSQLexec(ps, listbuf))) + return -1; /* first, print out the attribute names */ nColumns = PQntuples(res); @@ -236,14 +252,14 @@ tableList (PGconn* conn, int deep_tablelist) PQclear(res); for (i=0; i < nColumns; i++) { - tableDesc(conn,table[i]); + tableDesc(ps, table[i]); } free(table); } else { /* Display the information */ - printf ("\nDatabase = %s\n", PQdb(conn)); + printf ("\nDatabase = %s\n", PQdb(ps->db)); printf (" +------------------+----------------------------------+----------+\n"); printf (" | Owner | Relation | Type |\n"); printf (" +------------------+----------------------------------+----------+\n"); @@ -272,7 +288,7 @@ tableList (PGconn* conn, int deep_tablelist) } /* - * Describe a table (PGconn* conn, char* table) + * Describe a table * * Describe the columns in a database table. * returns 0 if all went well @@ -280,7 +296,7 @@ tableList (PGconn* conn, int deep_tablelist) * */ int -tableDesc (PGconn* conn, char* table) +tableDesc (PsqlSettings *ps, char *table) { char descbuf[256]; int nColumns; @@ -302,16 +318,8 @@ tableDesc (PGconn* conn, char* table) 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); - } + if (!(res = PSQLexec(ps, descbuf))) + return -1; /* first, print out the attribute names */ nColumns = PQntuples(res); if (nColumns > 0) @@ -332,15 +340,15 @@ tableDesc (PGconn* conn, char* table) rsize = atoi(PQgetvalue(res,i,3)); if (strcmp(rtype, "text") == 0) { printf ("%-32.32s |", rtype); - printf ("%-6s |", "var" ); + printf ("%6s |", "var" ); } else if (strcmp(rtype, "bpchar") == 0) { printf ("%-32.32s |", "char"); - printf ("%-6i |", rsize > 0 ? rsize - 4 : 0 ); + printf ("%6i |", rsize > 0 ? rsize - 4 : 0 ); } else if (strcmp(rtype, "varchar") == 0) { printf ("%-32.32s |", rtype); - printf ("%-6i |", rsize > 0 ? rsize - 4 : 0 ); + printf ("%6i |", rsize > 0 ? rsize - 4 : 0 ); } else { /* array types start with an underscore */ @@ -355,9 +363,9 @@ tableDesc (PGconn* conn, char* table) free(newname); } if (rsize > 0) - printf ("%-6i |", rsize); + printf ("%6i |", rsize); else - printf ("%-6s |", "var"); + printf ("%6s |", "var"); } printf("\n"); } @@ -396,7 +404,6 @@ gets_readline(char* prompt, FILE* source) return (readline(prompt)); } - /* * gets_fromFile prompt source * @@ -426,14 +433,12 @@ gets_fromFile(char* prompt, FILE* source) } /* - * SendQuery: - SendQuery: send the query string to the backend - * + * 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) +SendQuery(PsqlSettings *settings, char *query) { PGresult* results; PGnotify* notify; @@ -454,20 +459,41 @@ SendQuery(PGconn* db, char* query, PsqlSettings *settings) gets_fromFile("",stdin); } - results = PQexec(db, query); + results = PQexec(settings->db, query); if (results == NULL) { - fprintf(stderr,"%s",PQerrorMessage(db)); + fprintf(stderr,"%s",PQerrorMessage(settings->db)); return 1; } switch (PQresultStatus(results)) { case PGRES_TUPLES_OK: - PQdisplayTuples(results, - settings->queryFout, - settings->fillAlign, - settings->fieldSep, - settings->printHeader, - settings->quiet); + if (settings->gfname) + { + PsqlSettings ps=*settings; + FILE *fp; + ps.queryFout=stdout; + fp=setFout(&ps, settings->gfname); + if (!fp || fp==stdout) + { + status = 1; + break; + } + PQprint(fp, + results, + &(settings->opt)); + if (ps.pipe) + pclose(fp); + else + fclose(fp); + settings->gfname=NULL; + break; + } else + { + PQprint(settings->queryFout, + results, + &(settings->opt)); + fflush(settings->queryFout); + } PQclear(results); break; case PGRES_EMPTY_QUERY: @@ -475,7 +501,7 @@ SendQuery(PGconn* db, char* query, PsqlSettings *settings) break; case PGRES_COMMAND_OK: if (!settings->quiet) - fprintf(stdout,"%s\n",PQcmdStatus(results)); + fprintf(stdout,"%s\n", PQcmdStatus(results)); break; case PGRES_COPY_OUT: handleCopyOut(results, settings->quiet); @@ -487,13 +513,12 @@ SendQuery(PGconn* db, char* query, PsqlSettings *settings) case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: status = 1; - fprintf(stderr,"%s",PQerrorMessage(db)); + fprintf(stderr,"%s",PQerrorMessage(settings->db)); break; - } /* check for asynchronous returns */ - notify = PQnotifies(db); + notify = PQnotifies(settings->db); if (notify) { fprintf(stderr,"ASYNC NOTIFY of '%s' from backend pid '%d' received\n", notify->relname, notify->be_pid); @@ -504,6 +529,71 @@ SendQuery(PGconn* db, char* query, PsqlSettings *settings) } +void +editFile(char *fname) +{ + char *editorName; + char *sys; + editorName = getenv("EDITOR"); + if (!editorName) + editorName = DEFAULT_EDITOR; + sys=malloc(strlen(editorName)+strlen(fname)+32+1); + if (!sys) + { + perror("malloc"); + exit(1); + } + sprintf(sys, "exec '%s' '%s'", editorName, fname); + system(sys); + free(sys); +} + +bool +toggle(PsqlSettings *settings, bool *sw, char *msg) +{ + *sw= !*sw; + if (!settings->quiet) + fprintf(stderr, "turned %s %s\n", on(*sw), msg); + return *sw; +} + +char * +decode(char *s) +{ + char *p, *d; + bool esc=0; + for (d=p=s; *p; p++) + { + char c=*p; + if (esc) + { + switch(*p) + { + case 'n': + c='\n'; + break; + case 'r': + c='\r'; + break; + case 't': + c='\t'; + break; + case 'f': + c='\f'; + break; + } + esc=0; + } else + if (c=='\\') + { + esc=1; + continue; + } + *d++=c; + } + *d='\0'; +} + /* HandleSlashCmds: @@ -519,189 +609,161 @@ SendQuery(PGconn* db, char* query, PsqlSettings *settings) 2 - terminate processing of this query entirely */ int -HandleSlashCmds(PGconn** db_ptr, +HandleSlashCmds(PsqlSettings *settings, char* line, - char** prompt_ptr, - char *query, - PsqlSettings *settings) + char *query) { - int status = 0; - PGconn* db = *db_ptr; - char* dbname = PQdb(db); + int status = 1; char *optarg = NULL; int len; len = strlen(line); if (len > 2) + { optarg = leftTrim(line+2); + decode(optarg); + } 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" ); + case 'a': /* toggles to align fields on output */ + toggle(settings, &settings->opt.align, "field alignment"); + break; + case 'C': /* define new caption */ + if (settings->opt.caption) + free(settings->opt.caption); + if (!optarg) + settings->opt.caption=NULL; + else + if (!(settings->opt.caption=dupstr(optarg))) + { + perror("malloc"); + exit(1); + } break; case 'c': /* \c means connect to new database */ { + char *dbname=PQdb(settings->db); 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; + { + PGconn *olddb=settings->db; - printf("closing connection to database:%s\n", dbname); - olddb = db; - db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, optarg); - *db_ptr = db; + printf("closing connection to database: %s\n", dbname); + settings->db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, optarg); printf("connecting to new database: %s\n", optarg); - if (PQstatus(db) == CONNECTION_BAD) { - fprintf(stderr,"%s\n", PQerrorMessage(db)); + if (PQstatus(settings->db) == CONNECTION_BAD) { + fprintf(stderr,"%s\n", PQerrorMessage(settings->db)); printf("reconnecting to %s\n", dbname); - db = PQsetdb(PQhost(olddb), PQport(olddb), + settings->db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, dbname); - *db_ptr = db; - if (PQstatus(db) == CONNECTION_BAD) { + if (PQstatus(settings->db) == CONNECTION_BAD) { fprintf(stderr, - "could not reconnect to %s. exiting\n", dbname); + "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; + free(settings->prompt); + settings->prompt = malloc(strlen(PQdb(settings->db)) + 10); + sprintf(settings->prompt,"%s=> ", PQdb(settings->db)); break; } } break; case 'd': /* \d describe tables or columns in a table */ - { if (!optarg) { - tableList(db,0); - status = 1; + tableList(settings, 0); break; - } - if ( strcmp(optarg,"*") == 0 ) { - tableList(db, 0); - tableList(db, 1); + } + if (strcmp(optarg, "*") == 0 ) { + tableList(settings, 0); + tableList(settings, 1); } else { - tableDesc(db,optarg); + tableDesc(settings, optarg); } - status = 1; break; - } case 'e': { - char s[256]; int fd; - int ql = strlen(query); - int f_arg = 0; + char tmp[64]; + char *fname; int cc; + int ql = strlen(query); if (optarg) - { - f_arg = 1; - strcpy(s, optarg); - } + fname=optarg; else - { - sprintf(s, "/tmp/psql.%d.%d", geteuid(), getpid()); - unlink(s); + { + sprintf(tmp, "/tmp/psql.%d.%d", geteuid(), getpid()); + fname=tmp; + unlink(tmp); if (ql) { - if ((fd=open(s, O_EXCL|O_CREAT|O_WRONLY, 0600))==-1) + if ((fd=open(tmp, O_EXCL|O_CREAT|O_WRONLY, 0600))==-1) { - perror(s); + perror(tmp); break; } if (query[ql-1]!='\n') strcat(query, "\n"); if (write(fd, query, ql)!=ql) { - perror(s); + perror(tmp); close(fd); - unlink(s); + unlink(tmp); break; } close(fd); } } + editFile(fname); + if ((fd=open(fname, O_RDONLY))==-1) { - 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); + perror(fname); + if (!optarg) + unlink(fname); break; } if ((cc=read(fd, query, MAX_QUERY_BUFFER))==-1) { - perror(s); + perror(fname); close(fd); - if (!f_arg) - unlink(s); + if (!optarg) + unlink(fname); break; } query[cc]='\0'; close(fd); - if (!f_arg) - unlink(s); + if (!optarg) + unlink(fname); 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; + case 'f': + { + char *fs=DEFAULT_FIELD_SEP; + if (optarg) + fs=optarg; + if (settings->opt.fieldSep); + free(settings->opt.fieldSep); + if (!(settings->opt.fieldSep=dupstr(fs))) + { + perror("malloc"); + exit(1); } - - 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; + if (!settings->quiet) + fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep); break; - } + } + case 'g': /* \g means send query */ + settings->gfname = optarg; + status = 0; + break; case 'h': { char* cmd; @@ -727,7 +789,7 @@ HandleSlashCmds(PGconn** db_ptr, numCmds = numCmds - 1; - if ( strcmp(cmd,"*") == 0 ) { + if (strcmp(cmd, "*") == 0 ) { all_help=1; } @@ -748,74 +810,154 @@ HandleSlashCmds(PGconn** db_ptr, if (i == numCmds && ! all_help) printf("command not found, try \\h with no arguments to see available help\n"); } - status = 1; + break; + } + case 'i': /* \i is include file */ + { + FILE* fd; + + if (!optarg) { + fprintf(stderr,"\\i must be followed by a file name\n"); + break; + } + + if ((fd = fopen(optarg, "r")) == NULL) + { + fprintf(stderr,"file named %s could not be opened\n",optarg); + break; + } + MainLoop(settings, fd); + fclose(fd); break; } case 'l': /* \l is list database */ - listAllDbs(db,settings); - status = 1; + listAllDbs(settings); + break; + case 'H': + if (toggle(settings, &settings->opt.html3, "HTML3.0 tablular output")) + settings->opt.standard = 0; break; case 'o': - settings->queryFout = setFout(optarg); + setFout(settings, optarg); break; case 'p': - if (query) { - fputs(query, stdout); - fputc('\n', stdout); - status = 1; - } - break; + if (query) + { + fputs(query, stdout); + fputc('\n', stdout); + } + break; case 'q': /* \q is quit */ status = 2; break; case 'r': - query[0] = '\0'; - status = 1; - 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) + { + FILE* fd; + static char *lastfile; + struct stat st, st2; + if (optarg) + { + if (lastfile) + free(lastfile); + lastfile=malloc(strlen(optarg+1)); + if (!lastfile) + { + perror("malloc"); + exit(1); + } + strcpy(lastfile, optarg); + } else if (!lastfile) + { + fprintf(stderr,"\\r must be followed by a file name initially\n"); + break; + } + stat(lastfile, &st); + editFile(lastfile); + if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL)) { - fprintf(stderr,"cannot write history to %s\n",fname); + perror(lastfile); + break; } - status = 1; + if (st2.st_mtime==st.st_mtime) + { + if (!settings->quiet) + fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile); + fclose(fd); + break; + } + MainLoop(settings, fd); + fclose(fd); 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" ); + case 's': /* \s is save history to a file */ + if (!optarg) + optarg="/dev/tty"; + if (write_history(optarg) != 0) + fprintf(stderr,"cannot write history to %s\n",optarg); break; + case 'm': /* monitor like type-setting */ + if (toggle(settings, &settings->opt.standard, "standard SQL separaters and padding")) + { + settings->opt.html3 = settings->opt.expanded = 0; + settings->opt.align = settings->opt.header = 1; + free(settings->opt.fieldSep); + settings->opt.fieldSep=dupstr("|"); + if (!settings->quiet) + fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep); + } else + { + free(settings->opt.fieldSep); + settings->opt.fieldSep=dupstr(DEFAULT_FIELD_SEP); + if (!settings->quiet) + fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep); + } + break; + case 't': /* toggle headers */ + toggle(settings, &settings->opt.header, "output headers"); + break; + case 'T': /* define html
option */ + if (settings->opt.tableOpt) + free(settings->opt.tableOpt); + if (!optarg) + settings->opt.tableOpt=NULL; + else + if (!(settings->opt.tableOpt=dupstr(optarg))) + { + perror("malloc"); + exit(1); + } + break; + case 'x': + toggle(settings, &settings->opt.expanded, "expanded table representation"); + break; + case 'z': /* zorch buffer */ + query[0]='\0'; + if (!settings->quiet) + fprintf(stderr, "zorched current query buffer\n"); + break; case '!': if (!optarg) { - char sys[256]; + char *sys; char *shellName; shellName = getenv("SHELL"); if (shellName == NULL) shellName = DEFAULT_SHELL; + sys = malloc(strlen(shellName)+16); + if (!sys) + { + perror("malloc"); + exit(1); + } sprintf(sys,"exec %s", shellName); system(sys); + free(sys); } else system(optarg); break; default: case '?': /* \? is help */ - slashUsage(); - status = 1; + slashUsage(settings); break; } return status; @@ -830,19 +972,14 @@ HandleSlashCmds(PGconn** db_ptr, *db_ptr must be initialized and set */ + int -MainLoop(PGconn** db_ptr, - FILE* source, - PsqlSettings *settings) +MainLoop(PsqlSettings *settings, FILE* source) { - 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) @@ -850,18 +987,21 @@ MainLoop(PGconn** db_ptr, 2 - terminate processing of this query entirely */ - int send_query = 0; - int interactive; + bool sendQuery = 0; + bool querySent = 0; + bool interactive; READ_ROUTINE GetNextLine; - interactive = (source == stdin); - + interactive = ((source == stdin) && !settings->notty); +#define PROMPT "=> " if (interactive) { - prompt = malloc(strlen(dbname) + 10); + if (settings->prompt) + free(settings->prompt); + settings->prompt = malloc(strlen(PQdb(settings->db)) + strlen(PROMPT) + 1); if (settings->quiet) - prompt[0] = '\0'; + settings->prompt[0] = '\0'; else - sprintf(prompt,"%s=> ", dbname); + sprintf(settings->prompt,"%s%s", PQdb(settings->db), PROMPT); if (settings->useReadline) { using_history(); GetNextLine = gets_readline; @@ -875,7 +1015,7 @@ MainLoop(PGconn** db_ptr, query[0] = '\0'; /* main loop for getting queries and executing them */ - while ((line = GetNextLine(prompt, source)) != NULL) + while ((line = GetNextLine(settings->prompt, source)) != NULL) { exitStatus = 0; line = rightTrim(line); /* remove whitespaces on the right, incl. \n's */ @@ -895,6 +1035,11 @@ MainLoop(PGconn** db_ptr, free(line); continue; } + if (line[0] != '\\' && querySent) + { + query[0]='\0'; + querySent = 0; + } len = strlen(line); @@ -903,34 +1048,31 @@ MainLoop(PGconn** db_ptr, /* 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] == ';') ; + sendQuery = 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 (len > 2 && !sendQuery) { if (line[len-1]=='g' && line[len-2]=='\\') { - send_query = 1; + sendQuery = 1; line[len-2]='\0'; } } /* slash commands have to be on their own line */ if (line[0] == '\\') { - slashCmdStatus = HandleSlashCmds(db_ptr, + slashCmdStatus = HandleSlashCmds(settings, line, - &prompt, - query, - settings); - db = *db_ptr; /* in case \c changed the database */ + query); if (slashCmdStatus == 1) continue; if (slashCmdStatus == 2) break; if (slashCmdStatus == 0) - send_query = 1; + sendQuery = 1; } else if (strlen(query) + len > MAX_QUERY_BUFFER) @@ -946,30 +1088,29 @@ MainLoop(PGconn** db_ptr, else strcpy(query,line); - if (send_query && query[0] != '\0') + if (sendQuery && 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); + if (!interactive && !settings->singleStep && !settings->quiet) + fprintf(stderr,"%s\n", query); - exitStatus = SendQuery(db, query, settings); - query[0] = '\0'; + exitStatus = SendQuery(settings, query); + querySent = 1; } free(line); /* free storage malloc'd by GetNextLine */ } /* while */ - return exitStatus; + return exitStatus; } int main(int argc, char** argv) { extern char* optarg; - extern int optind, opterr; + extern int optind; - PGconn *db; char* dbname = NULL; char* host = NULL; char* port = NULL; @@ -980,31 +1121,27 @@ main(int argc, char** argv) char* singleQuery = NULL; - int listDatabases = 0 ; + bool listDatabases = 0 ; int exitStatus = 0; - int singleSlashCmd = 0; + bool singleSlashCmd = 0; int c; - -#ifdef NOREADLINE - settings.useReadline = 0; -#else - settings.useReadline = 1; + memset(&settings, 0, sizeof settings); + settings.opt.align = 1; + settings.opt.header = 1; + settings.queryFout = stdout; + settings.opt.fieldSep=dupstr(DEFAULT_FIELD_SEP); + if (!isatty(0) || !isatty(1)) + settings.quiet = settings.notty = 1; + else +#ifndef NOREADLINE + 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) { + while ((c = getopt(argc, argv, "Aa:c:d:ef:F:lh:Hnso:p:qStT:x")) != EOF) { switch (c) { case 'A': - settings.fillAlign = 0; + settings.opt.align = 0; break; case 'a': fe_setauthsvc(optarg, errbuf); @@ -1025,20 +1162,23 @@ main(int argc, char** argv) qfilename = optarg; break; case 'F': - strncpy(settings.fieldSep,optarg,MAX_FIELD_SEP_LENGTH); - break; + settings.opt.fieldSep=dupstr(optarg); + break; case 'l': listDatabases = 1; break; - case 'H': + case 'h': host = optarg; break; + case 'H': + settings.opt.html3 = 1; + break; case 'n': - settings.useReadline = 0; - break; + settings.useReadline = 0; + break; case 'o': - settings.queryFout = setFout(optarg); - break; + setFout(&settings, optarg); + break; case 'p': port = optarg; break; @@ -1051,10 +1191,15 @@ main(int argc, char** argv) case 'S': settings.singleLineMode = 1; break; + case 't': + settings.opt.header = 0; + break; case 'T': - settings.printHeader = 0; - break; - case 'h': + settings.opt.tableOpt = dupstr(optarg); + break; + case 'x': + settings.opt.expanded = 0; + break; default: usage(argv[0]); break; @@ -1067,16 +1212,16 @@ main(int argc, char** argv) if (listDatabases) dbname = "template1"; - db = PQsetdb(host, port, NULL, NULL, dbname); - dbname = PQdb(db); + settings.db = PQsetdb(host, port, NULL, NULL, dbname); + dbname = PQdb(settings.db); - if (PQstatus(db) == CONNECTION_BAD) { + if (PQstatus(settings.db) == CONNECTION_BAD) { fprintf(stderr,"Connection to database '%s' failed.\n", dbname); - fprintf(stderr,"%s",PQerrorMessage(db)); + fprintf(stderr,"%s",PQerrorMessage(settings.db)); exit(1); } if (listDatabases) { - exit(listAllDbs(db,&settings)); + exit(listAllDbs(&settings)); } if (!settings.quiet && !singleQuery && !qfilename) { @@ -1092,7 +1237,6 @@ main(int argc, char** argv) /* 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." */ @@ -1102,24 +1246,25 @@ main(int argc, char** argv) line = malloc(strlen(qfilename) + 5); sprintf(line,"\\i %s", qfilename); } - HandleSlashCmds(&db, line, (char**)prompt, "", &settings); + HandleSlashCmds(&settings, line, ""); } else { if (singleQuery) { - exitStatus = SendQuery(db, singleQuery, &settings); + exitStatus = SendQuery(&settings, singleQuery); } else - exitStatus = MainLoop(&db, stdin, &settings); + exitStatus = MainLoop(&settings, stdin); } - PQfinish(db); + PQfinish(settings.db); return exitStatus; } +#define COPYBUFSIZ 8192 static void -handleCopyOut(PGresult *res, int quiet) +handleCopyOut(PGresult *res, bool quiet) { bool copydone = false; char copybuf[COPYBUFSIZ]; @@ -1153,7 +1298,7 @@ handleCopyOut(PGresult *res, int quiet) static void -handleCopyIn(PGresult *res, int quiet) +handleCopyIn(PGresult *res, bool quiet) { bool copydone = false; bool firstload; @@ -1212,24 +1357,38 @@ handleCopyIn(PGresult *res, int quiet) PQendcopy(res->conn); } - /* try to open fname and return a FILE*, if it fails, use stdout, instead */ + FILE* -setFout(char *fname) +setFout(PsqlSettings *ps, char *fname) { - FILE *queryFout; - - if (!fname) - queryFout = stdout; - else { - queryFout = fopen(fname, "w"); - if (!queryFout) { - perror(fname); - queryFout = stdout; + if (ps->queryFout && ps->queryFout != stdout) + { + if (ps->pipe) + pclose(ps->queryFout); + else + fclose(ps->queryFout); } - } - - return queryFout; + if (!fname) + ps->queryFout = stdout; + else + { + if (*fname == '|') + { + signal(SIGPIPE, SIG_IGN); + ps->queryFout = popen(fname+1, "w"); + ps->pipe = 1; + } + else + { + ps->queryFout = fopen(fname, "w"); + ps->pipe = 0; + } + if (!ps->queryFout) { + perror(fname); + ps->queryFout = stdout; + } + } + return ps->queryFout; } -