/*------------------------------------------------------------------------- * * psql.c * an interactive front-end to postgreSQL * * Copyright (c) 1996, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.185 1999/07/19 16:46:53 momjian Exp $ * *------------------------------------------------------------------------- */ #include #include #include #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include #include #else #include /* for MAXPATHLEN */ #include #include #endif #include #include #include #include "postgres.h" #include "libpq-fe.h" #include "pqsignal.h" #include "stringutils.h" #include "psqlHelp.h" #ifndef HAVE_STRDUP #include "strdup.h" #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_LIBREADLINE #ifdef HAVE_READLINE_H #include #define USE_READLINE 1 #if defined(HAVE_HISTORY_H) #include #define USE_HISTORY 1 #endif #else #if defined(HAVE_READLINE_READLINE_H) #include #define USE_READLINE 1 #if defined(HAVE_READLINE_HISTORY_H) #include #define USE_HISTORY 1 #endif #endif #endif #if defined(HAVE_HISTORY) && !defined(USE_HISTORY) #define USE_HISTORY 1 #endif #endif #ifdef WIN32 #define popen(x,y) _popen(x,y) #define pclose(x) _pclose(x) #define open(x,y,z) _open(x,y,z) #define strcasecmp(x,y) stricmp(x,y) #define pqsignal(x,y) #define MAXPATHLEN MAX_PATH #define R_OK 0 /* getopt is not in the standard includes on Win32 */ extern char *optarg; extern int optind, opterr, optopt; int getopt(int, char *const[], const char *); char *__progname = "psql"; #endif #ifdef MULTIBYTE /* flag to indicate if PGCLIENTENCODING has been set by a user */ static char *has_client_encoding = 0; #endif /* This prompt string is assumed to have at least 3 characters by code in MainLoop(). * A character two characters from the end is replaced each time by a mode character. */ #define PROMPT "=> " #define PROMPT_READY '=' #define PROMPT_CONTINUE '-' #define PROMPT_COMMENT '*' #define PROMPT_SINGLEQUOTE '\'' #define PROMPT_DOUBLEQUOTE '"' /* Backslash command handling: * 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 * 3 - new query supplied by edit */ #define CMD_UNKNOWN -1 #define CMD_SEND 0 #define CMD_SKIP_LINE 1 #define CMD_TERMINATE 2 #define CMD_NEWEDIT 3 #define MAX_QUERY_BUFFER MAX_QUERY_SIZE #define COPYBUFSIZ 8192 #define DEFAULT_FIELD_SEP "|" #define DEFAULT_EDITOR "vi" #define DEFAULT_SHELL "/bin/sh" typedef struct _psqlSettings { 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 echoAllQueries; /* echo all queries 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 */ bool getPassword; /* prompt the user for a username and * password */ } PsqlSettings; /* * cur_cmd_source and cur_cmd_interactive are the top of a stack of * source files (one stack level per recursive invocation of MainLoop). * It's kinda grotty to make these global variables, but the alternative * of passing them around through many function parameter lists seems * worse. */ static FILE *cur_cmd_source = NULL; /* current source of command input */ static bool cur_cmd_interactive = false; /* is it an interactive * source? */ #ifdef TIOCGWINSZ struct winsize screen_size; #else struct winsize { int ws_row; int ws_col; } screen_size; #endif /* declarations for functions in this file */ static void usage(char *progname); static void slashUsage(); static bool handleCopyOut(PGconn *conn, FILE *copystream); static bool handleCopyIn(PGconn *conn, const bool mustprompt, FILE *copystream); static int tableList(PsqlSettings *pset, bool deep_tablelist, char info_type, bool system_tables); static int tableDesc(PsqlSettings *pset, char *table, FILE *fout); static int objectDescription(PsqlSettings *pset, char *object); static int rightsList(PsqlSettings *pset); static void emitNtimes(FILE *fout, const char *str, int N); static void prompt_for_password(char *username, char *password); static char *gets_noreadline(char *prompt, FILE *source); static char *gets_readline(char *prompt, FILE *source); static char *gets_fromFile(char *prompt, FILE *source); static int listAllDbs(PsqlSettings *pset); static bool SendQuery(PsqlSettings *pset, const char *query, FILE *copy_in_stream, FILE *copy_out_stream); static int HandleSlashCmds(PsqlSettings *pset, char *line, char *query); static int MainLoop(PsqlSettings *pset, char *query, FILE *source); static FILE *setFout(PsqlSettings *pset, char *fname); static char *selectVersion(PsqlSettings *pset); /* * 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 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 -E echo all queries 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 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 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 headings and row count\n"); fprintf(stderr, "\t -T html set html3.0 table command options (cf. -H)\n"); fprintf(stderr, "\t -u ask for a username and password for authentication\n"); fprintf(stderr, "\t -x turn on expanded output (field names on left)\n"); exit(1); } /* * slashUsage print out usage for the backslash commands */ static char * on(bool f) { return f ? "on" : "off"; } static void slashUsage(PsqlSettings *pset) { int usePipe = 0; char *pagerenv; FILE *fout; #ifdef TIOCGWINSZ if (pset->notty == 0 && (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 || screen_size.ws_row == 0)) { #endif screen_size.ws_row = 24; screen_size.ws_col = 80; #ifdef TIOCGWINSZ } #endif if (pset->notty == 0 && (pagerenv = getenv("PAGER")) && (pagerenv[0] != '\0') && screen_size.ws_row <= 35 && (fout = popen(pagerenv, "w"))) { usePipe = 1; pqsignal(SIGPIPE, SIG_IGN); } else fout = stdout; /* if you add/remove a line here, change the row test above */ fprintf(fout, " \\? -- help\n"); fprintf(fout, " \\a -- toggle field-alignment (currently %s)\n", on(pset->opt.align)); fprintf(fout, " \\C [] -- set html3 caption (currently '%s')\n", pset->opt.caption ? pset->opt.caption : ""); fprintf(fout, " \\connect -- connect to new database (currently '%s')\n", PQdb(pset->db)); fprintf(fout, " \\copy table {from | to} \n"); fprintf(fout, " \\d [] -- list tables and indices, columns in
, or * for all\n"); fprintf(fout, " \\da -- list aggregates\n"); fprintf(fout, " \\dd []- list comment for table, field, type, function, or operator.\n"); fprintf(fout, " \\df -- list functions\n"); fprintf(fout, " \\di -- list only indices\n"); fprintf(fout, " \\do -- list operators\n"); fprintf(fout, " \\ds -- list only sequences\n"); fprintf(fout, " \\dS -- list system tables and indexes\n"); fprintf(fout, " \\dt -- list only tables\n"); fprintf(fout, " \\dT -- list types\n"); fprintf(fout, " \\e [] -- edit the current query buffer or \n"); fprintf(fout, " \\E [] -- edit the current query buffer or , and execute\n"); fprintf(fout, " \\f [] -- change field separater (currently '%s')\n", pset->opt.fieldSep); fprintf(fout, " \\g [] [|] -- send query to backend [and results in or pipe]\n"); fprintf(fout, " \\h [] -- help on syntax of sql commands, * for all commands\n"); fprintf(fout, " \\H -- toggle html3 output (currently %s)\n", on(pset->opt.html3)); fprintf(fout, " \\i -- read and execute queries from filename\n"); fprintf(fout, " \\l -- list all databases\n"); fprintf(fout, " \\m -- toggle monitor-like table display (currently %s)\n", on(pset->opt.standard)); fprintf(fout, " \\o [] [|] -- send all query results to stdout, , or pipe\n"); fprintf(fout, " \\p -- print the current query buffer\n"); fprintf(fout, " \\q -- quit\n"); fprintf(fout, " \\r -- reset(clear) the query buffer\n"); fprintf(fout, " \\s [] -- print history or save it in \n"); fprintf(fout, " \\t -- toggle table headings and row count (currently %s)\n", on(pset->opt.header)); fprintf(fout, " \\T [] -- set html3.0
options (currently '%s')\n", pset->opt.tableOpt ? pset->opt.tableOpt : ""); fprintf(fout, " \\x -- toggle expanded output (currently %s)\n", on(pset->opt.expanded)); fprintf(fout, " \\w -- output current buffer to a file\n"); fprintf(fout, " \\z -- list current grant/revoke permissions\n"); fprintf(fout, " \\! [] -- shell escape or command\n"); if (usePipe) { pclose(fout); pqsignal(SIGPIPE, SIG_DFL); } } static PGresult * PSQLexec(PsqlSettings *pset, char *query) { PGresult *res; if (pset->echoAllQueries) { fprintf(stderr, "QUERY: %s\n", query); fprintf(stderr, "\n"); fflush(stderr); } res = PQexec(pset->db, query); if (!res) fputs(PQerrorMessage(pset->db), stderr); else { if (PQresultStatus(res) == PGRES_COMMAND_OK || PQresultStatus(res) == PGRES_TUPLES_OK) return res; if (!pset->quiet) fputs(PQerrorMessage(pset->db), stderr); PQclear(res); } return NULL; } /* * Code to support command cancellation. * If interactive, we enable a SIGINT signal catcher that sends * a cancel request to the backend. * Note that sending the cancel directly from the signal handler * is safe only because PQrequestCancel is carefully written to * make it so. We have to be very careful what else we do in the * signal handler. * Writing on stderr is potentially dangerous, if the signal interrupted * some stdio operation on stderr. On Unix we can avoid trouble by using * write() instead; on Windows that's probably not workable, but we can * at least avoid trusting printf by using the more primitive fputs. */ static PGconn *cancelConn = NULL; /* connection to try cancel on */ static void safe_write_stderr(const char *s) { #ifdef WIN32 fputs(s, stderr); #else write(fileno(stderr), s, strlen(s)); #endif } static void handle_sigint(SIGNAL_ARGS) { if (cancelConn == NULL) exit(1); /* accept signal if no connection */ /* Try to send cancel request */ if (PQrequestCancel(cancelConn)) safe_write_stderr("\nCANCEL request sent\n"); else { safe_write_stderr("\nCannot send cancel request:\n"); safe_write_stderr(PQerrorMessage(cancelConn)); } } /* * listAllDbs * * list all the databases in the system returns 0 if all went well * * */ static int listAllDbs(PsqlSettings *pset) { PGresult *results; char *query = "select * from pg_database;"; if (!(results = PSQLexec(pset, query))) return 1; else { PQprint(pset->queryFout, results, &pset->opt); PQclear(results); return 0; } } /* * List The Database Tables returns 0 if all went well * */ static int tableList(PsqlSettings *pset, bool deep_tablelist, char info_type, bool system_tables) { char listbuf[512]; int nColumns; int i; char *rk; char *rr; PGresult *res; int usePipe = 0; bool haveIndexes = false; char *pagerenv; FILE *fout; #ifdef TIOCGWINSZ if (pset->notty == 0 && (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 || screen_size.ws_row == 0)) { #endif screen_size.ws_row = 24; screen_size.ws_col = 80; #ifdef TIOCGWINSZ } #endif listbuf[0] = '\0'; strcat(listbuf, "SELECT usename, relname, relkind, relhasrules "); strcat(listbuf, "FROM pg_class, pg_user "); strcat(listbuf, "WHERE usesysid = relowner "); switch (info_type) { case 't': strcat(listbuf, "and ( relkind = 'r') "); break; case 'i': strcat(listbuf, "and ( relkind = 'i') "); haveIndexes = true; break; case 'S': strcat(listbuf, "and ( relkind = 'S') "); break; case 'b': default: strcat(listbuf, "and ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') "); haveIndexes = true; break; } if (!system_tables) strcat(listbuf, "and relname !~ '^pg_' "); else strcat(listbuf, "and relname ~ '^pg_' "); /* * Large-object relations are automatically ignored because they have * relkind 'l'. However, we want to ignore their indexes as well. * The clean way to do that would be to do a join to find out which * table each index is for. The ugly but fast way is to know that * large object indexes have names starting with 'xinx'. */ if (haveIndexes) strcat(listbuf, "and (relkind != 'i' OR relname !~ '^xinx') "); strcat(listbuf, " ORDER BY relname "); if (!(res = PSQLexec(pset, listbuf))) return -1; /* first, print out the attribute names */ nColumns = PQntuples(res); if (nColumns > 0) { if (pset->notty == 0 && (pagerenv = getenv("PAGER")) && pagerenv[0] != '\0' && (deep_tablelist || screen_size.ws_row <= nColumns + 7) && (fout = popen(pagerenv, "w"))) { usePipe = 1; pqsignal(SIGPIPE, SIG_IGN); } else fout = stdout; if (deep_tablelist) { /* describe everything here */ char **table; table = (char **) malloc(nColumns * sizeof(char *)); if (table == NULL) perror("malloc"); /* load table table */ /* * Put double quotes around the table name to allow for * mixed-case and whitespaces in the table name. - BGA * 1998-11-14 */ for (i = 0; i < nColumns; i++) { table[i] = (char *) malloc(PQgetlength(res, i, 1) * sizeof(char) + 3); if (table[i] == NULL) perror("malloc"); strcpy(table[i], "\""); strcat(table[i], PQgetvalue(res, i, 1)); strcat(table[i], "\""); } PQclear(res); for (i = 0; i < nColumns; i++) tableDesc(pset, table[i], fout); free(table); } else { /* Display the information */ fprintf(fout, "Database = %s\n", PQdb(pset->db)); fprintf(fout, " +------------------+----------------------------------+----------+\n"); fprintf(fout, " | Owner | Relation | Type |\n"); fprintf(fout, " +------------------+----------------------------------+----------+\n"); /* next, print out the instances */ for (i = 0; i < PQntuples(res); i++) { fprintf(fout, " | %-16.16s", PQgetvalue(res, i, 0)); fprintf(fout, " | %-32.32s | ", PQgetvalue(res, i, 1)); rk = PQgetvalue(res, i, 2); rr = PQgetvalue(res, i, 3); if (strcmp(rk, "r") == 0) fprintf(fout, "%-8.8s |", (rr[0] == 't') ? "view?" : "table"); else if (strcmp(rk, "i") == 0) fprintf(fout, "%-8.8s |", "index"); else fprintf(fout, "%-8.8s |", "sequence"); fprintf(fout, "\n"); } fprintf(fout, " +------------------+----------------------------------+----------+\n"); fprintf(fout, "\n"); PQclear(res); } if (usePipe) { pclose(fout); pqsignal(SIGPIPE, SIG_DFL); } return 0; } else { PQclear(res); switch (info_type) { case 't': fprintf(stderr, "Couldn't find any tables!\n"); break; case 'i': fprintf(stderr, "Couldn't find any indices!\n"); break; case 'S': fprintf(stderr, "Couldn't find any sequences!\n"); break; case 'b': default: fprintf(stderr, "Couldn't find any tables, sequences or indices!\n"); break; } return -1; } } /* * List Tables Grant/Revoke Permissions returns 0 if all went well * */ static int rightsList(PsqlSettings *pset) { char listbuf[512]; int nColumns; int i; int maxCol1Len; int maxCol2Len; int usePipe = 0; char *pagerenv; FILE *fout; PGresult *res; #ifdef TIOCGWINSZ if (pset->notty == 0 && (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 || screen_size.ws_row == 0)) { #endif screen_size.ws_row = 24; screen_size.ws_col = 80; #ifdef TIOCGWINSZ } #endif listbuf[0] = '\0'; strcat(listbuf, "SELECT relname, relacl "); strcat(listbuf, "FROM pg_class "); /* Currently, we ignore indexes since they have no meaningful rights */ strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'S') "); strcat(listbuf, " and relname !~ '^pg_'"); strcat(listbuf, " ORDER BY relname "); if (!(res = PSQLexec(pset, listbuf))) return -1; /* first, print out the attribute names */ nColumns = PQntuples(res); if (nColumns > 0) { if (pset->notty == 0 && (pagerenv = getenv("PAGER")) && pagerenv[0] != '\0' && screen_size.ws_row <= nColumns + 7 && (fout = popen(pagerenv, "w"))) { usePipe = 1; pqsignal(SIGPIPE, SIG_IGN); } else fout = stdout; /* choose column widths */ maxCol1Len = strlen("Relation"); maxCol2Len = strlen("Grant/Revoke Permissions"); for (i = 0; i < PQntuples(res); i++) { int l = strlen(PQgetvalue(res, i, 0)); if (l > maxCol1Len) maxCol1Len = l; l = strlen(PQgetvalue(res, i, 1)); if (l > maxCol2Len) maxCol2Len = l; } /* Display the information */ fprintf(fout, "Database = %s\n", PQdb(pset->db)); fprintf(fout, " +"); emitNtimes(fout, "-", maxCol1Len + 2); fprintf(fout, "+"); emitNtimes(fout, "-", maxCol2Len + 2); fprintf(fout, "+\n"); fprintf(fout, " | %-*s | %-*s |\n", maxCol1Len, "Relation", maxCol2Len, "Grant/Revoke Permissions"); fprintf(fout, " +"); emitNtimes(fout, "-", maxCol1Len + 2); fprintf(fout, "+"); emitNtimes(fout, "-", maxCol2Len + 2); fprintf(fout, "+\n"); /* next, print out the instances */ for (i = 0; i < PQntuples(res); i++) { fprintf(fout, " | %-*s | %-*s |\n", maxCol1Len, PQgetvalue(res, i, 0), maxCol2Len, PQgetvalue(res, i, 1)); } fprintf(fout, " +"); emitNtimes(fout, "-", maxCol1Len + 2); fprintf(fout, "+"); emitNtimes(fout, "-", maxCol2Len + 2); fprintf(fout, "+\n"); PQclear(res); if (usePipe) { pclose(fout); pqsignal(SIGPIPE, SIG_DFL); } return 0; } else { PQclear(res); fprintf(stderr, "Couldn't find any tables!\n"); return -1; } } static void emitNtimes(FILE *fout, const char *str, int N) { int i; for (i = 0; i < N; i++) fputs(str, fout); } /* * Describe a table * * Describe the columns in a database table. returns 0 if all went well * * */ static int tableDesc(PsqlSettings *pset, char *table, FILE *fout) { char descbuf[512]; int nColumns, nIndices; char *rtype; char *rnotnull; char *rhasdef; int i; int attlen, atttypmod; PGresult *res, *res2; int usePipe = 0; char *pagerenv; #ifdef TIOCGWINSZ if (pset->notty == 0 && (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 || screen_size.ws_row == 0)) { #endif screen_size.ws_row = 24; screen_size.ws_col = 80; #ifdef TIOCGWINSZ } #endif /* Build the query */ /* * if the table name is surrounded by double-quotes, then don't * convert case */ if (*table == '"') { table++; if (*(table + strlen(table) - 1) == '"') *(table + strlen(table) - 1) = '\0'; } else { #ifdef MULTIBYTE for (i = 0; table[i]; i += PQmblen(table + i)) #else for (i = 0; table[i]; i++) #endif if (isascii((unsigned char) table[i]) && isupper(table[i])) table[i] = tolower(table[i]); } descbuf[0] = '\0'; strcat(descbuf, "SELECT a.attnum, a.attname, t.typname, a.attlen, "); strcat(descbuf, "a.atttypmod, a.attnotnull, a.atthasdef "); 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 "); if (!(res = PSQLexec(pset, descbuf))) return -1; /* first, print out the attribute names */ nColumns = PQntuples(res); if (nColumns > 0) { if (fout == NULL) { if (pset->notty == 0 && (pagerenv = getenv("PAGER")) && pagerenv[0] != '\0' && screen_size.ws_row <= nColumns + 7 && (fout = popen(pagerenv, "w"))) { usePipe = 1; pqsignal(SIGPIPE, SIG_IGN); } else fout = stdout; } /* * Extract the veiw name and veiw definition from pg_views. -Ryan * 2/14/99 */ descbuf[0] = '\0'; strcat(descbuf, "SELECT viewname, definition "); strcat(descbuf, "FROM pg_views "); strcat(descbuf, "WHERE viewname like '"); strcat(descbuf, table); strcat(descbuf, "' "); if (!(res2 = PSQLexec(pset, descbuf))) return -1; /* * Display the information */ if (PQntuples(res2)) { /* * display the query. o * -Ryan 2/14/99 */ fprintf(fout, "View = %s\n", table); fprintf(fout, "Query = %s\n", PQgetvalue(res2, 0, 1)); } else fprintf(fout, "Table = %s\n", table); PQclear(res2); fprintf(fout, "+----------------------------------+----------------------------------+-------+\n"); fprintf(fout, "| Field | Type | Length|\n"); fprintf(fout, "+----------------------------------+----------------------------------+-------+\n"); /* next, print out the instances */ for (i = 0; i < PQntuples(res); i++) { char type_str[33]; fprintf(fout, "| %-32.32s | ", PQgetvalue(res, i, 1)); rtype = PQgetvalue(res, i, 2); attlen = atoi(PQgetvalue(res, i, 3)); atttypmod = atoi(PQgetvalue(res, i, 4)); rnotnull = PQgetvalue(res, i, 5); rhasdef = PQgetvalue(res, i, 6); strcpy(type_str, rtype); if (strcmp(rtype, "bpchar") == 0) strcpy(type_str, "char()"); else if (strcmp(rtype, "varchar") == 0) strcpy(type_str, "varchar()"); else if (rtype[0] == '_') { strcpy(type_str, rtype + 1); strncat(type_str, "[]", 32 - strlen(type_str)); type_str[32] = '\0'; } if (rnotnull[0] == 't') { strncat(type_str, " not null", 32 - strlen(type_str)); type_str[32] = '\0'; } if (rhasdef[0] == 't') { descbuf[0] = '\0'; strcat(descbuf, "SELECT d.adsrc "); strcat(descbuf, "FROM pg_attrdef d, pg_class c "); strcat(descbuf, "WHERE c.relname = '"); strcat(descbuf, table); strcat(descbuf, "'"); strcat(descbuf, " and c.oid = d.adrelid "); strcat(descbuf, " and d.adnum = "); strcat(descbuf, PQgetvalue(res, i, 0)); if (!(res2 = PSQLexec(pset, descbuf))) return -1; strcat(type_str, " default "); strncat(type_str, PQgetvalue(res2, 0, 0), 32 - strlen(type_str)); type_str[32] = '\0'; } fprintf(fout, "%-32.32s |", type_str); if (strcmp(rtype, "text") == 0) fprintf(fout, "%6s |", "var"); else if (strcmp(rtype, "bpchar") == 0 || strcmp(rtype, "varchar") == 0) fprintf(fout, "%6i |", atttypmod != -1 ? atttypmod - VARHDRSZ : 0); else if (strcmp(rtype, "numeric") == 0) fprintf(fout, "%3i.%-2i |", ((atttypmod - VARHDRSZ) >> 16) & 0xffff, (atttypmod - VARHDRSZ) & 0xffff); else { if (attlen > 0) fprintf(fout, "%6i |", attlen); else fprintf(fout, "%6s |", "var"); } fprintf(fout, "\n"); } fprintf(fout, "+----------------------------------+----------------------------------+-------+\n"); PQclear(res); /* display defined indexes for this table */ descbuf[0] = '\0'; strcat(descbuf, "SELECT c2.relname "); strcat(descbuf, "FROM pg_class c, pg_class c2, pg_index i "); strcat(descbuf, "WHERE c.relname = '"); strcat(descbuf, table); strcat(descbuf, "'"); strcat(descbuf, " and c.oid = i.indrelid "); strcat(descbuf, " and i.indexrelid = c2.oid "); strcat(descbuf, " ORDER BY c2.relname "); if ((res = PSQLexec(pset, descbuf))) { nIndices = PQntuples(res); if (nIndices > 0) { /* * Display the information */ if (nIndices == 1) fprintf(fout, "Index: "); else fprintf(fout, "Indices: "); /* next, print out the instances */ for (i = 0; i < PQntuples(res); i++) if (i == 0) fprintf(fout, "%s\n", PQgetvalue(res, i, 0)); else fprintf(fout, " %s\n", PQgetvalue(res, i, 0)); fprintf(fout, "\n"); } PQclear(res); } if (usePipe) { pclose(fout); pqsignal(SIGPIPE, SIG_DFL); } return 0; } else { PQclear(res); fprintf(stderr, "Couldn't find table %s!\n", table); return -1; } } /* * Get object comments * * Describe the columns in a database table. returns 0 if all went well * * */ static int objectDescription(PsqlSettings *pset, char *object) { char descbuf[512]; PGresult *res; int i; bool success; /* Build the query */ while (isspace(*object)) object++; /* * if the object name is surrounded by double-quotes, then don't * convert case */ if (*object == '"') { object++; if (*(object + strlen(object) - 1) == '"') *(object + strlen(object) - 1) = '\0'; } else { #ifdef MULTIBYTE for (i = 0; object[i]; i += PQmblen(object + i)) #else for (i = 0; object[i]; i++) #endif if (isupper(object[i])) object[i] = tolower(object[i]); } descbuf[0] = '\0'; if (strchr(object, '.') != NULL) { char table[NAMEDATALEN], column[NAMEDATALEN]; StrNCpy(table, object, ((strchr(object, '.') - object + 1) < NAMEDATALEN) ? (strchr(object, '.') - object + 1) : NAMEDATALEN); StrNCpy(column, strchr(object, '.') + 1, NAMEDATALEN); strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_class, pg_attribute, pg_description "); strcat(descbuf, "WHERE pg_class.relname = '"); strcat(descbuf, table); strcat(descbuf, "' and "); strcat(descbuf, "pg_class.oid = pg_attribute.attrelid and "); strcat(descbuf, "pg_attribute.attname = '"); strcat(descbuf, column); strcat(descbuf, "' and "); strcat(descbuf, " pg_attribute.oid = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; } else { strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_class, pg_description "); strcat(descbuf, "WHERE pg_class.relname ~ '^"); strcat(descbuf, object); strcat(descbuf, "'"); strcat(descbuf, " and pg_class.oid = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; else if (PQntuples(res) <= 0) { PQclear(res); descbuf[0] = '\0'; strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_type, pg_description "); strcat(descbuf, "WHERE pg_type.typname ~ '^"); strcat(descbuf, object); strcat(descbuf, "' and "); strcat(descbuf, " pg_type.oid = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; else if (PQntuples(res) <= 0) { PQclear(res); descbuf[0] = '\0'; strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_proc, pg_description "); strcat(descbuf, "WHERE pg_proc.proname ~ '^"); strcat(descbuf, object); strcat(descbuf, "'"); strcat(descbuf, " and pg_proc.oid = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; else if (PQntuples(res) <= 0) { PQclear(res); descbuf[0] = '\0'; strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_operator, pg_description "); strcat(descbuf, "WHERE pg_operator.oprname ~ '^"); strcat(descbuf, object); strcat(descbuf, "'"); /* operator descriptions are attached to the proc */ strcat(descbuf, " and RegprocToOid(pg_operator.oprcode) = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; else if (PQntuples(res) <= 0) { PQclear(res); descbuf[0] = '\0'; strcat(descbuf, "SELECT DISTINCT description "); strcat(descbuf, "FROM pg_aggregate, pg_description "); strcat(descbuf, "WHERE pg_aggregate.aggname ~ '^"); strcat(descbuf, object); strcat(descbuf, "'"); strcat(descbuf, " and pg_aggregate.oid = pg_description.objoid "); if (!(res = PSQLexec(pset, descbuf))) return -1; else if (PQntuples(res) <= 0) { PQclear(res); descbuf[0] = '\0'; strcat(descbuf, "SELECT 'no description' as description "); if (!(res = PSQLexec(pset, descbuf))) return -1; } } } } } } PQclear(res); success = SendQuery(pset, descbuf, NULL, NULL); return 0; } typedef char *(*READ_ROUTINE) (char *prompt, FILE *source); /* * gets_noreadline prompt source gets a line of input without calling * readline, the source is ignored */ static 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 */ static char * gets_readline(char *prompt, FILE *source) { char *s; #ifdef USE_READLINE s = readline(prompt); #else char buf[500]; printf("%s", prompt); s = fgets(buf, 500, stdin); #endif fputc('\r', stdout); fflush(stdout); return s; } /* * gets_fromFile prompt source * * the routine to read from a file, the prompt argument is ignored the source * argument is a FILE * */ static char * gets_fromFile(char *prompt, FILE *source) { char *line; line = malloc(MAX_QUERY_BUFFER); /* read up to MAX_QUERY_BUFFER characters */ if (fgets(line, MAX_QUERY_BUFFER, source) == NULL) { free(line); return NULL; } line[MAX_QUERY_BUFFER - 1] = '\0'; /* this is unnecessary, I think */ if (strlen(line) == MAX_QUERY_BUFFER - 1) { fprintf(stderr, "line read exceeds maximum length. Truncating at %d\n", MAX_QUERY_BUFFER - 1); } return line; } /* * SendQuery: send the query string to the backend. * * Return true if the query executed successfully, false otherwise. * * If not NULL, copy_in_stream and copy_out_stream are files to redirect * copy in/out data to. */ static bool SendQuery(PsqlSettings *pset, const char *query, FILE *copy_in_stream, FILE *copy_out_stream) { bool success = false; PGresult *results; PGnotify *notify; if (pset->singleStep) fprintf(stdout, "\n**************************************" "*****************************************\n"); if (pset->echoQuery || pset->singleStep) { fprintf(stderr, "QUERY: %s\n", query); fflush(stderr); } if (pset->singleStep) { fprintf(stdout, "\n**************************************" "*****************************************\n"); fflush(stdout); printf("\npress return to continue ..\n"); gets_fromFile("", stdin); } results = PQexec(pset->db, query); if (results == NULL) { fprintf(stderr, "%s", PQerrorMessage(pset->db)); success = false; } else { switch (PQresultStatus(results)) { case PGRES_TUPLES_OK: if (pset->gfname) { PsqlSettings settings_copy = *pset; FILE *fp; settings_copy.queryFout = stdout; fp = setFout(&settings_copy, pset->gfname); if (!fp || fp == stdout) { success = false; break; } PQprint(fp, results, &pset->opt); if (settings_copy.pipe) pclose(fp); else fclose(fp); free(pset->gfname); pset->gfname = NULL; success = true; break; } else { success = true; PQprint(pset->queryFout, results, &(pset->opt)); fflush(pset->queryFout); } break; case PGRES_EMPTY_QUERY: success = true; break; case PGRES_COMMAND_OK: success = true; if (!pset->quiet) printf("%s\n", PQcmdStatus(results)); break; case PGRES_COPY_OUT: if (copy_out_stream) success = handleCopyOut(pset->db, copy_out_stream); else { if (pset->queryFout == stdout && !pset->quiet) printf("Copy command returns...\n"); success = handleCopyOut(pset->db, pset->queryFout); } break; case PGRES_COPY_IN: if (copy_in_stream) success = handleCopyIn(pset->db, false, copy_in_stream); else success = handleCopyIn(pset->db, cur_cmd_interactive && !pset->quiet, cur_cmd_source); break; case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: success = false; fprintf(stderr, "%s", PQerrorMessage(pset->db)); break; } if (PQstatus(pset->db) == CONNECTION_BAD) { fprintf(stderr, "We have lost the connection to the backend, so " "further processing is impossible. " "Terminating.\n"); exit(2); /* we are out'ta here */ } /* check for asynchronous returns */ while ((notify = PQnotifies(pset->db)) != NULL) { fprintf(stderr, "ASYNC NOTIFY of '%s' from backend pid '%d' received\n", notify->relname, notify->be_pid); free(notify); } if (results) PQclear(results); } return success; } static 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); } static bool toggle(PsqlSettings *pset, bool *sw, char *msg) { *sw = !*sw; if (!pset->quiet) printf("turned %s %s\n", on(*sw), msg); return *sw; } static void unescape(char *dest, const char *source) { /*----------------------------------------------------------------------------- Return as the string the value of string with escape sequences turned into the bytes they represent. -----------------------------------------------------------------------------*/ char *p; bool esc; /* Last character we saw was the escape * character (/) */ esc = false; /* Haven't seen escape character yet */ for (p = (char *) source; *p; p++) { char c; /* Our output character */ if (esc) { switch (*p) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'f': c = '\f'; break; case '\\': c = '\\'; break; default: c = *p; } esc = false; } else if (*p == '\\') { esc = true; c = ' '; /* meaningless, but compiler doesn't know * that */ } else { c = *p; esc = false; } if (!esc) *dest++ = c; } *dest = '\0'; /* Terminating null character */ } static void parse_slash_copy(const char *args, char *table, const int table_len, char *file, const int file_len, bool *from_p, bool *error_p) { char work_args[200]; /* * A copy of the \copy command arguments, except that we modify it as * we parse to suit our parsing needs. */ char *table_tok, *fromto_tok; strncpy(work_args, args, sizeof(work_args)); work_args[sizeof(work_args) - 1] = '\0'; *error_p = false; /* initial assumption */ table_tok = strtok(work_args, " "); if (table_tok == NULL) { fprintf(stderr, "\\copy needs arguments.\n"); *error_p = true; } else { strncpy(table, table_tok, table_len); file[table_len - 1] = '\0'; fromto_tok = strtok(NULL, " "); if (fromto_tok == NULL) { fprintf(stderr, "'FROM' or 'TO' must follow table name.\n"); *error_p = true; } else { if (strcasecmp(fromto_tok, "from") == 0) *from_p = true; else if (strcasecmp(fromto_tok, "to") == 0) *from_p = false; else { fprintf(stderr, "Unrecognized token found where " "'FROM' or 'TO' expected: '%s'.\n", fromto_tok); *error_p = true; } if (!*error_p) { char *file_tok; file_tok = strtok(NULL, " "); if (file_tok == NULL) { fprintf(stderr, "A file pathname must follow '%s'.\n", fromto_tok); *error_p = true; } else { strncpy(file, file_tok, file_len); file[file_len - 1] = '\0'; if (strtok(NULL, " ") != NULL) { fprintf(stderr, "You have extra tokens after the filename.\n"); *error_p = true; } } } } } } static void do_copy(const char *args, PsqlSettings *pset) { /*--------------------------------------------------------------------------- Execute a \copy command (frontend copy). We have to open a file, then submit a COPY query to the backend and either feed it data from the file or route its response into the file. We do a text copy with default (tab) column delimiters. Some day, we should do all the things a backend copy can do. ----------------------------------------------------------------------------*/ char query[200]; /* The COPY command we send to the back end */ bool from; /* The direction of the copy is from a file to a table. */ char file[MAXPATHLEN + 1]; /* The pathname of the file from/to which we copy */ char table[NAMEDATALEN]; /* The name of the table from/to which we copy */ bool syntax_error; /* The \c command has invalid syntax */ FILE *copystream; parse_slash_copy(args, table, sizeof(table), file, sizeof(file), &from, &syntax_error); if (!syntax_error) { strcpy(query, "COPY "); strcat(query, table); if (from) strcat(query, " FROM stdin"); else strcat(query, " TO stdout"); if (from) #ifndef __CYGWIN32__ copystream = fopen(file, "r"); #else copystream = fopen(file, "rb"); #endif else #ifndef __CYGWIN32__ copystream = fopen(file, "w"); #else copystream = fopen(file, "wb"); #endif if (copystream == NULL) fprintf(stderr, "Unable to open file %s which to copy, errno = %s (%d).", from ? "from" : "to", strerror(errno), errno); else { bool success;/* The query succeeded at the backend */ success = SendQuery(pset, query, from ? copystream : (FILE *) NULL, !from ? copystream : (FILE *) NULL); fclose(copystream); if (!pset->quiet) { if (success) printf("Successfully copied.\n"); else printf("Copy failed.\n"); } } } } static void do_connect(const char *new_dbname, const char *new_user, PsqlSettings *pset) { if (!new_dbname) fprintf(stderr, "\\connect must be followed by a database name\n"); else if (new_user != NULL && pset->getPassword) fprintf(stderr, "You can't specify a username when using passwords.\n"); else { PGconn *olddb = pset->db; const char *dbparam; const char *userparam; const char *pwparam; if (strcmp(new_dbname, "-") != 0) dbparam = new_dbname; else dbparam = PQdb(olddb); if (new_user != NULL && strcmp(new_user, "-") != 0) userparam = new_user; else userparam = PQuser(olddb); /* FIXME: if changing user, ought to prompt for a new password? */ pwparam = PQpass(olddb); #ifdef MULTIBYTE /* * PGCLIENTENCODING may be set by the previous connection. if a * user does not explicitly set PGCLIENTENCODING, we should * discard PGCLIENTENCODING so that libpq could get the backend * encoding as the default PGCLIENTENCODING value. -- 1998/12/12 * Tatsuo Ishii */ if (!has_client_encoding) { static const char ev[] = "PGCLIENTENCODING="; putenv(ev); } #endif pset->db = PQsetdbLogin(PQhost(olddb), PQport(olddb), NULL, NULL, dbparam, userparam, pwparam); if (!pset->quiet) { if (!new_user) printf("connecting to new database: %s\n", dbparam); else if (dbparam != new_dbname) printf("connecting as new user: %s\n", new_user); else printf("connecting to new database: %s as user: %s\n", dbparam, new_user); } if (PQstatus(pset->db) == CONNECTION_BAD) { fprintf(stderr, "%s\n", PQerrorMessage(pset->db)); fprintf(stderr, "Could not connect to new database. exiting\n"); exit(2); } else { cancelConn = pset->db; /* redirect sigint's loving * attentions */ PQfinish(olddb); free(pset->prompt); pset->prompt = malloc(strlen(PQdb(pset->db)) + 10); sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT); } } } static void do_edit(const char *filename_arg, char *query, int *status_p) { int fd; char tmp[64]; char *fname; int cc; const int ql = strlen(query); bool error; if (filename_arg) { fname = (char *) filename_arg; error = false; } else { #ifndef WIN32 sprintf(tmp, "/tmp/psql.%ld.%ld", (long) geteuid(), (long) getpid()); #else GetTempFileName(".", "psql", 0, tmp); #endif fname = tmp; unlink(tmp); if (ql > 0) { if ((fd = open(tmp, O_EXCL | O_CREAT | O_WRONLY, 0600)) == -1) { perror(tmp); error = true; } else { if (query[ql - 1] != '\n') strcat(query, "\n"); if (write(fd, query, ql) != ql) { perror(tmp); close(fd); unlink(tmp); error = true; } else error = false; close(fd); } } else error = false; } if (error) *status_p = CMD_SKIP_LINE; else { editFile(fname); if ((fd = open(fname, O_RDONLY, 0)) == -1) { perror(fname); if (!filename_arg) unlink(fname); *status_p = CMD_SKIP_LINE; } else { if ((cc = read(fd, query, MAX_QUERY_BUFFER)) == -1) { perror(fname); close(fd); if (!filename_arg) unlink(fname); *status_p = CMD_SKIP_LINE; } else { query[cc] = '\0'; close(fd); if (!filename_arg) unlink(fname); rightTrim(query); *status_p = CMD_NEWEDIT; } } } } static void do_help(PsqlSettings *pset, const char *topic) { if (!topic) { char left_center_right; /* Which column we're displaying */ int i; /* Index into QL_HELP[] */ printf("type \\h where is one of the following:\n"); left_center_right = 'L';/* Start with left column */ i = 0; while (QL_HELP[i].cmd != NULL) { switch (left_center_right) { case 'L': printf(" %-25s", QL_HELP[i].cmd); left_center_right = 'C'; break; case 'C': printf("%-25s", QL_HELP[i].cmd); left_center_right = 'R'; break; case 'R': printf("%-25s\n", QL_HELP[i].cmd); left_center_right = 'L'; break; } i++; } if (left_center_right != 'L') puts("\n"); printf("type \\h * for a complete description of all commands\n"); } else { int i; /* Index into QL_HELP[] */ bool help_found; /* We found the help he asked for */ int usePipe = 0; char *pagerenv; FILE *fout; if (strcmp(topic, "*") == 0 && (pset->notty == 0) && (pagerenv = getenv("PAGER")) && (pagerenv[0] != '\0') && (fout = popen(pagerenv, "w"))) { usePipe = 1; pqsignal(SIGPIPE, SIG_IGN); } else fout = stdout; help_found = false; /* Haven't found it yet */ for (i = 0; QL_HELP[i].cmd; i++) { if (strcasecmp(QL_HELP[i].cmd, topic) == 0 || strcmp(topic, "*") == 0) { help_found = true; fprintf(fout, "Command: %s\n", QL_HELP[i].cmd); fprintf(fout, "Description: %s\n", QL_HELP[i].help); fprintf(fout, "Syntax:\n"); fprintf(fout, "%s\n", QL_HELP[i].syntax); fprintf(fout, "\n"); } } if (usePipe) { pclose(fout); pqsignal(SIGPIPE, SIG_DFL); } if (!help_found) fprintf(stderr, "command not found, " "try \\h with no arguments to see available help\n"); } } static void do_shell(const char *command) { if (!command) { 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(command); } /* * 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 * 3 - new query supplied by edit */ static int HandleSlashCmds(PsqlSettings *pset, char *line, char *query) { int status = CMD_SKIP_LINE; char *optarg; bool success; /* * Pointer inside the string to the argument of the slash * command, assuming it is a one-character slash command. If it's not * a one-character command, this is meaningless. */ char *optarg2; /* * Pointer inside the string to the argument of the slash * command assuming it's not a one-character command. If it's a * one-character command, this is meaningless. */ char *cmd; /* * String: value of the slash command, less the slash and with escape * sequences decoded. */ int blank_loc; /* Offset within of first blank */ cmd = malloc(strlen(line)); /* unescaping better not make string grow. */ unescape(cmd, line + 1); /* sets cmd string */ if (strlen(cmd) >= 1 && cmd[strlen(cmd) - 1] == ';') /* strip trailing ; */ cmd[strlen(cmd) - 1] = '\0'; /* * Originally, there were just single character commands. Now, we * define some longer, friendly commands, but we have to keep the old * single character commands too. \c used to be what \connect is now. * Complicating matters is the fact that with the single-character * commands, you can start the argument right after the single * character, so "\copy" would mean "connect to database named 'opy'". */ if (strlen(cmd) > 1) optarg = cmd + 1 + strspn(cmd + 1, " \t"); else optarg = NULL; blank_loc = strcspn(cmd, " \t"); if (blank_loc == 0 || !cmd[blank_loc]) optarg2 = NULL; else optarg2 = cmd + blank_loc + strspn(cmd + blank_loc, " \t"); switch (cmd[0]) { case 'a': /* toggles to align fields on output */ toggle(pset, &pset->opt.align, "field alignment"); break; case 'C': /* define new caption */ if (pset->opt.caption) { free(pset->opt.caption); pset->opt.caption = NULL; } if (optarg && !(pset->opt.caption = strdup(optarg))) { perror("malloc"); exit(CMD_TERMINATE); } break; case 'c': { if (strncmp(cmd, "copy ", strlen("copy ")) == 0 || strncmp(cmd, "copy ", strlen("copy ")) == 0) do_copy(optarg2, pset); else if (strcmp(cmd, "copy") == 0) { fprintf(stderr, "See \\? for help\n"); break; } else if (strncmp(cmd, "connect ", strlen("connect ")) == 0 || strcmp(cmd, "connect") == 0 /* issue error message */ ) { char *optarg3 = NULL; int blank_loc2; if (optarg2) { blank_loc2 = strcspn(optarg2, " \t"); if (blank_loc2 == 0 || *(optarg2 + blank_loc2) == '\0') optarg3 = NULL; else { optarg3 = optarg2 + blank_loc2 + strspn(optarg2 + blank_loc2, " \t"); *(optarg2 + blank_loc2) = '\0'; } } do_connect(optarg2, optarg3, pset); } else { char *optarg3 = NULL; int blank_loc2; if (optarg) { blank_loc2 = strcspn(optarg, " \t"); if (blank_loc2 == 0 || *(optarg + blank_loc2) == '\0') optarg3 = NULL; else { optarg3 = optarg + blank_loc2 + strspn(optarg + blank_loc2, " \t"); *(optarg + blank_loc2) = '\0'; } } do_connect(optarg, optarg3, pset); } } break; case 'd': /* \d describe database information */ /* * if the optarg2 name is surrounded by double-quotes, then * don't convert case */ if (optarg2) { if (*optarg2 == '"') { optarg2++; if (*(optarg2 + strlen(optarg2) - 1) == '"') *(optarg2 + strlen(optarg2) - 1) = '\0'; } else { int i; #ifdef MULTIBYTE for (i = 0; optarg2[i]; i += PQmblen(optarg2 + i)) #else for (i = 0; optarg2[i]; i++) #endif if (isupper(optarg2[i])) optarg2[i] = tolower(optarg2[i]); } } #ifdef TIOCGWINSZ if (pset->notty == 0 && (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 || screen_size.ws_row == 0)) { #endif screen_size.ws_row = 24; screen_size.ws_col = 80; #ifdef TIOCGWINSZ } #endif if (strncmp(cmd, "da", 2) == 0) { char descbuf[4096]; /* aggregates */ descbuf[0] = '\0'; strcat(descbuf, "SELECT a.aggname AS aggname, "); strcat(descbuf, " t.typname AS type, "); strcat(descbuf, " obj_description(a.oid) as description "); strcat(descbuf, "FROM pg_aggregate a, pg_type t "); strcat(descbuf, "WHERE a.aggbasetype = t.oid "); if (optarg2) { strcat(descbuf, "AND a.aggname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "UNION "); strcat(descbuf, "SELECT a.aggname AS aggname, "); strcat(descbuf, " 'all types' as type, "); strcat(descbuf, " obj_description(a.oid) as description "); strcat(descbuf, "FROM pg_aggregate a "); strcat(descbuf, "WHERE a.aggbasetype = 0 "); if (optarg2) { strcat(descbuf, "AND a.aggname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "ORDER BY aggname, type;"); success = SendQuery(pset, descbuf, NULL, NULL); } else if (strncmp(cmd, "dd", 2) == 0) /* descriptions */ objectDescription(pset, optarg + 1); else if (strncmp(cmd, "df", 2) == 0) { char descbuf[4096]; /* functions/procedures */ /* * we skip in/out funcs by excluding functions that take * some arguments, but have no types defined for those * arguments */ descbuf[0] = '\0'; strcat(descbuf, "SELECT t.typname as result, "); strcat(descbuf, " p.proname as function, "); if (screen_size.ws_col <= 80) strcat(descbuf, " substr(oid8types(p.proargtypes),1,14) as arguments, "); else strcat(descbuf, " oid8types(p.proargtypes) as arguments, "); if (screen_size.ws_col <= 80) strcat(descbuf, " substr(obj_description(p.oid),1,34) as description "); else strcat(descbuf, " obj_description(p.oid) as description "); strcat(descbuf, "FROM pg_proc p, pg_type t "); strcat(descbuf, "WHERE p.prorettype = t.oid and "); strcat(descbuf, "(pronargs = 0 or oid8types(p.proargtypes) != '') "); if (optarg2) { strcat(descbuf, "AND p.proname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "ORDER BY result, function, arguments;"); success = SendQuery(pset, descbuf, NULL, NULL); } else if (strncmp(cmd, "di", 2) == 0) /* only indices */ tableList(pset, false, 'i', false); else if (strncmp(cmd, "do", 2) == 0) { char descbuf[4096]; /* operators */ descbuf[0] = '\0'; strcat(descbuf, "SELECT o.oprname AS op, "); strcat(descbuf, " t1.typname AS left_arg, "); strcat(descbuf, " t2.typname AS right_arg, "); strcat(descbuf, " t0.typname AS result, "); if (screen_size.ws_col <= 80) strcat(descbuf, " substr(obj_description(p.oid),1,41) as description "); else strcat(descbuf, " obj_description(p.oid) as description "); strcat(descbuf, "FROM pg_proc p, pg_type t0, "); strcat(descbuf, " pg_type t1, pg_type t2, "); strcat(descbuf, " pg_operator o "); strcat(descbuf, "WHERE p.prorettype = t0.oid AND "); strcat(descbuf, " RegprocToOid(o.oprcode) = p.oid AND "); strcat(descbuf, " p.pronargs = 2 AND "); strcat(descbuf, " o.oprleft = t1.oid AND "); strcat(descbuf, " o.oprright = t2.oid "); if (optarg2) { strcat(descbuf, "AND o.oprname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "UNION "); strcat(descbuf, "SELECT o.oprname as op, "); strcat(descbuf, " ''::name AS left_arg, "); strcat(descbuf, " t1.typname AS right_arg, "); strcat(descbuf, " t0.typname AS result, "); if (screen_size.ws_col <= 80) strcat(descbuf, " substr(obj_description(p.oid),1,41) as description "); else strcat(descbuf, " obj_description(p.oid) as description "); strcat(descbuf, "FROM pg_operator o, pg_proc p, pg_type t0, pg_type t1 "); strcat(descbuf, "WHERE RegprocToOid(o.oprcode) = p.oid AND "); strcat(descbuf, " o.oprresult = t0.oid AND "); strcat(descbuf, " o.oprkind = 'l' AND "); strcat(descbuf, " o.oprright = t1.oid "); if (optarg2) { strcat(descbuf, "AND o.oprname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "UNION "); strcat(descbuf, "SELECT o.oprname as op, "); strcat(descbuf, " t1.typname AS left_arg, "); strcat(descbuf, " ''::name AS right_arg, "); strcat(descbuf, " t0.typname AS result, "); if (screen_size.ws_col <= 80) strcat(descbuf, " substr(obj_description(p.oid),1,41) as description "); else strcat(descbuf, " obj_description(p.oid) as description "); strcat(descbuf, "FROM pg_operator o, pg_proc p, pg_type t0, pg_type t1 "); strcat(descbuf, "WHERE RegprocToOid(o.oprcode) = p.oid AND "); strcat(descbuf, " o.oprresult = t0.oid AND "); strcat(descbuf, " o.oprkind = 'r' AND "); strcat(descbuf, " o.oprleft = t1.oid "); if (optarg2) { strcat(descbuf, "AND o.oprname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } strcat(descbuf, "ORDER BY op, left_arg, right_arg, result;"); success = SendQuery(pset, descbuf, NULL, NULL); } else if (strncmp(cmd, "ds", 2) == 0) /* only sequences */ tableList(pset, false, 'S', false); else if (strncmp(cmd, "dS", 2) == 0) /* system tables */ tableList(pset, false, 'b', true); else if (strncmp(cmd, "dt", 2) == 0) /* only tables */ tableList(pset, false, 't', false); else if (strncmp(cmd, "dT", 2) == 0) { char descbuf[4096]; /* types */ descbuf[0] = '\0'; strcat(descbuf, "SELECT typname AS type, "); strcat(descbuf, " obj_description(oid) as description "); strcat(descbuf, "FROM pg_type "); strcat(descbuf, "WHERE typrelid = 0 AND "); strcat(descbuf, " typname !~ '^_.*' "); strcat(descbuf, "ORDER BY type;"); if (optarg2) { strcat(descbuf, "AND typname ~ '^"); strcat(descbuf, optarg2); strcat(descbuf, "' "); } success = SendQuery(pset, descbuf, NULL, NULL); } else if (!optarg) /* show tables, sequences and indices */ tableList(pset, false, 'b', false); else if (strcmp(optarg, "*") == 0) { /* show everything */ if (tableList(pset, false, 'b', false) == 0) tableList(pset, true, 'b', false); } else if (strncmp(cmd, "d ", 2) == 0) /* describe the specified table */ tableDesc(pset, optarg, NULL); else slashUsage(pset); break; case 'e': /* edit */ { do_edit(optarg, query, &status); break; } case 'E': { 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(CMD_TERMINATE); } strcpy(lastfile, optarg); } else if (!lastfile) { fprintf(stderr, "\\r must be followed by a file name initially\n"); break; } stat(lastfile, &st); editFile(lastfile); #ifndef __CYGWIN32__ if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL)) #else if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "rb")) == NULL)) #endif { perror(lastfile); break; } if (st2.st_mtime == st.st_mtime) { if (!pset->quiet) fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile); fclose(fd); break; } MainLoop(pset, query, fd); fclose(fd); break; } case 'f': { char *fs = DEFAULT_FIELD_SEP; if (optarg) fs = optarg; /* handle \f \{space} */ if (optarg && !*optarg && strlen(cmd) > 1) { int i; /* line and cmd match until the first blank space */ for (i = 2; isspace(line[i]); i++) ; fs = cmd + i - 1; } if (pset->opt.fieldSep) free(pset->opt.fieldSep); if (!(pset->opt.fieldSep = strdup(fs))) { perror("malloc"); exit(CMD_TERMINATE); } if (!pset->quiet) printf("field separator changed to '%s'\n", pset->opt.fieldSep); break; } case 'g': /* \g means send query */ if (!optarg) pset->gfname = NULL; else if (!(pset->gfname = strdup(optarg))) { perror("malloc"); exit(CMD_TERMINATE); } status = CMD_SEND; break; case 'h': /* help */ { do_help(pset, optarg); break; } case 'i': /* \i is include file */ { FILE *fd; if (!optarg) { fprintf(stderr, "\\i must be followed by a file name\n"); break; } #ifndef __CYGWIN32__ if ((fd = fopen(optarg, "r")) == NULL) #else if ((fd = fopen(optarg, "rb")) == NULL) #endif { fprintf(stderr, "file named %s could not be opened\n", optarg); break; } MainLoop(pset, query, fd); fclose(fd); break; } case 'H': if (toggle(pset, &pset->opt.html3, "HTML3.0 tabular output")) pset->opt.standard = 0; break; case 'l': /* \l is list database */ listAllDbs(pset); break; case 'm': /* monitor like type-setting */ if (toggle(pset, &pset->opt.standard, "standard SQL separaters and padding")) { pset->opt.html3 = pset->opt.expanded = 0; pset->opt.align = pset->opt.header = 1; if (pset->opt.fieldSep) free(pset->opt.fieldSep); pset->opt.fieldSep = strdup("|"); if (!pset->quiet) printf("field separator changed to '%s'\n", pset->opt.fieldSep); } else { if (pset->opt.fieldSep) free(pset->opt.fieldSep); pset->opt.fieldSep = strdup(DEFAULT_FIELD_SEP); if (!pset->quiet) printf("field separator changed to '%s'\n", pset->opt.fieldSep); } break; case 'o': setFout(pset, optarg); break; case 'p': if (query) { fputs(query, stdout); fputc('\n', stdout); } break; case 'q': /* \q is quit */ status = CMD_TERMINATE; break; case 'r': /* reset(clear) the buffer */ query[0] = '\0'; if (!pset->quiet) printf("buffer reset(cleared)\n"); break; case 's': /* \s is save history to a file */ if (!optarg) optarg = "/dev/tty"; #ifdef USE_HISTORY if (write_history(optarg) != 0) fprintf(stderr, "cannot write history to %s\n", optarg); #endif break; case 't': /* toggle headers */ toggle(pset, &pset->opt.header, "output headings and row count"); break; case 'T': /* define html
option */ if (pset->opt.tableOpt) free(pset->opt.tableOpt); if (!optarg) pset->opt.tableOpt = NULL; else if (!(pset->opt.tableOpt = strdup(optarg))) { perror("malloc"); exit(CMD_TERMINATE); } break; case 'w': { FILE *fd; if (!optarg) { fprintf(stderr, "\\w must be followed by a file name\n"); break; } #ifndef __CYGWIN32__ if ((fd = fopen(optarg, "w")) == NULL) #else if ((fd = fopen(optarg, "w")) == NULL) #endif { fprintf(stderr, "file named %s could not be opened\n", optarg); break; } fputs(query, fd); fputs("\n", fd); fclose(fd); break; } case 'x': toggle(pset, &pset->opt.expanded, "expanded table representation"); break; case 'z': /* list table rights (grant/revoke) */ rightsList(pset); break; case '!': do_shell(optarg); break; default: case '?': /* \? is help */ slashUsage(pset); break; } free(cmd); 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. */ static int MainLoop(PsqlSettings *pset, char *query, FILE *source) { char *line; /* line of input */ char *xcomment; /* start of extended comment */ int len; /* length of the line */ int successResult = 1; int slashCmdStatus = CMD_SEND; /*-------------------------------------------------------------- * slashCmdStatus can be: * CMD_UNKNOWN - send currently constructed query to backend * (i.e. we got a \g) * CMD_SEND - send currently constructed query to backend * (i.e. we got a \g) * CMD_SKIP_LINE - skip processing of this line, continue building * up query * CMD_TERMINATE - terminate processing of this query entirely * CMD_NEWEDIT - new query supplied by edit *--------------------------------------------------------------- */ bool querySent = false; READ_ROUTINE GetNextLine; bool eof = false; /* end of our command input? */ bool success; char in_quote; /* == 0 for no in_quote */ bool was_bslash; /* backslash */ int paren_level; char *query_start; /* Stack the prior command source */ FILE *prev_cmd_source = cur_cmd_source; bool prev_cmd_interactive = cur_cmd_interactive; /* Establish new source */ cur_cmd_source = source; cur_cmd_interactive = ((source == stdin) && !pset->notty); if ((query = malloc(MAX_QUERY_BUFFER)) == NULL) perror("Memory Allocation Failed"); if (cur_cmd_interactive) { if (pset->prompt) free(pset->prompt); pset->prompt = malloc(strlen(PQdb(pset->db)) + strlen(PROMPT) + 1); if (pset->quiet) pset->prompt[0] = '\0'; else sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT); if (pset->useReadline) { #ifdef USE_HISTORY using_history(); #endif GetNextLine = gets_readline; } else GetNextLine = gets_noreadline; } else GetNextLine = gets_fromFile; query[0] = '\0'; xcomment = NULL; in_quote = false; paren_level = 0; slashCmdStatus = CMD_UNKNOWN; /* set default */ /* main loop to get queries and execute them */ while (!eof) { /* * just returned from editing the line? then just copy to the * input buffer */ if (slashCmdStatus == CMD_NEWEDIT) { paren_level = 0; line = strdup(query); query[0] = '\0'; /* * otherwise, get another line and set interactive prompt if * necessary */ } else { if (cur_cmd_interactive && !pset->quiet) { if (in_quote && in_quote == PROMPT_SINGLEQUOTE) pset->prompt[strlen(pset->prompt) - 3] = PROMPT_SINGLEQUOTE; else if (in_quote && in_quote == PROMPT_DOUBLEQUOTE) pset->prompt[strlen(pset->prompt) - 3] = PROMPT_DOUBLEQUOTE; else if (xcomment != NULL) pset->prompt[strlen(pset->prompt) - 3] = PROMPT_COMMENT; else if (query[0] != '\0' && !querySent) pset->prompt[strlen(pset->prompt) - 3] = PROMPT_CONTINUE; else pset->prompt[strlen(pset->prompt) - 3] = PROMPT_READY; } line = GetNextLine(pset->prompt, source); #ifdef USE_HISTORY if (cur_cmd_interactive && pset->useReadline && line != NULL) add_history(line); /* save non-empty lines in history */ #endif } /* * query - pointer to current command query_start - placeholder * for next command */ if (line == NULL || (!cur_cmd_interactive && *line == '\0')) { /* No more input. Time to quit, or \i * done */ if (!pset->quiet) printf("EOF\n");/* Goes on prompt line */ eof = true; continue; } /* not currently inside an extended comment? */ if (xcomment == NULL) { query_start = line; /* otherwise, continue the extended comment... */ } else { query_start = line; xcomment = line; } /* remove whitespaces on the right, incl. \n's */ line = rightTrim(line); /* echo back if input is from file */ if (!cur_cmd_interactive && !pset->singleStep && !pset->quiet) fprintf(stderr, "%s\n", line); slashCmdStatus = CMD_UNKNOWN; /* nothing on line after trimming? then ignore */ if (line[0] == '\0') { free(line); continue; } len = strlen(line); if (pset->singleLineMode) { success = SendQuery(pset, line, NULL, NULL); successResult &= success; querySent = true; } else { int i; /* * The current character is at line[i], the prior character at * line[i - prevlen], the next character at line[i + thislen]. */ #ifdef MULTIBYTE int prevlen = 0; int thislen = (len > 0) ? PQmblen(line) : 0; #define ADVANCE_I (prevlen = thislen, i += thislen, thislen = PQmblen(line+i)) #else #define prevlen 1 #define thislen 1 #define ADVANCE_I (i++) #endif was_bslash = false; for (i = 0; i < len; ADVANCE_I) { if (line[i] == '\\' && !in_quote) { char hold_char = line[i]; line[i] = '\0'; if (query_start[0] != '\0') { if (query[0] != '\0') { strcat(query, "\n"); strcat(query, query_start); } else strcpy(query, query_start); } line[i] = hold_char; query_start = line + i; break; /* handle command */ } if (querySent && isascii((unsigned char) (line[i])) && !isspace(line[i])) { query[0] = '\0'; querySent = false; } if (was_bslash) was_bslash = false; else if (i > 0 && line[i - prevlen] == '\\') was_bslash = true; /* inside a quote? */ if (in_quote && (line[i] != in_quote || was_bslash)) /* do nothing */ ; /* inside an extended comment? */ else if (xcomment != NULL) { if (line[i] == '*' && line[i + thislen] == '/') { xcomment = NULL; ADVANCE_I; } } /* start of extended comment? */ else if (line[i] == '/' && line[i + thislen] == '*') { xcomment = line + i; ADVANCE_I; } /* single-line comment? truncate line */ else if ((line[i] == '-' && line[i + thislen] == '-') || (line[i] == '/' && line[i + thislen] == '/')) { /* print comment at top of query */ if (pset->singleStep) fprintf(stdout, "%s\n", line + i); line[i] = '\0'; /* remove comment */ break; } else if (in_quote && line[i] == in_quote) in_quote = false; else if (!in_quote && (line[i] == '\'' || line[i] == '"')) in_quote = line[i]; /* semi-colon? then send query now */ else if (!paren_level && line[i] == ';') { char hold_char = line[i + thislen]; line[i + thislen] = '\0'; if (query_start[0] != '\0') { if (query[0] != '\0') { strcat(query, "\n"); strcat(query, query_start); } else strcpy(query, query_start); } success = SendQuery(pset, query, NULL, NULL); successResult &= success; line[i + thislen] = hold_char; query_start = line + i + thislen; /* sometimes, people do ';\g', don't execute twice */ if (*query_start == '\\' && *(query_start + 1) == 'g') query_start += 2; querySent = true; } else if (line[i] == '(') { paren_level++; } else if (paren_level && line[i] == ')') paren_level--; } } /* nothing on line after trimming? then ignore */ if (line[0] == '\0') { free(line); continue; } if (!in_quote && query_start[0] == '\\') { /* handle \p\g and other backslash combinations */ while (query_start[0] != '\0') { char hold_char; #ifndef WIN32 /* I believe \w \dos\system\x would cause a problem */ /* do we have '\p\g' or '\p \g' ? */ if (strlen(query_start) > 2 && query_start[2 + strspn(query_start + 2, " \t")] == '\\') { hold_char = query_start[2 + strspn(query_start + 2, " \t")]; query_start[2 + strspn(query_start + 2, " \t")] = '\0'; } else /* spread over #endif */ #endif hold_char = '\0'; slashCmdStatus = HandleSlashCmds(pset, query_start, query); if (slashCmdStatus == CMD_SKIP_LINE && !hold_char) { if (query[0] == '\0') paren_level = 0; break; } if (slashCmdStatus == CMD_TERMINATE) break; query_start += strlen(query_start); if (hold_char) query_start[0] = hold_char; } free(line); /* They did \q, leave the loop */ if (slashCmdStatus == CMD_TERMINATE) break; } else if (strlen(query) + strlen(query_start) > MAX_QUERY_BUFFER) { fprintf(stderr, "query buffer max length of %d exceeded\n", MAX_QUERY_BUFFER); fprintf(stderr, "query line ignored\n"); free(line); } else { if (query_start[0] != '\0') { querySent = false; if (query[0] != '\0') { strcat(query, "\n"); strcat(query, query_start); } else strcpy(query, query_start); } free(line); } /* had a backslash-g? force the query to be sent */ if (slashCmdStatus == CMD_SEND) { success = SendQuery(pset, query, NULL, NULL); successResult &= success; xcomment = NULL; in_quote = false; paren_level = 0; querySent = true; } } /* while */ if (query) free(query); cur_cmd_source = prev_cmd_source; cur_cmd_interactive = prev_cmd_interactive; return successResult; } /* MainLoop() */ int main(int argc, char **argv) { extern char *optarg; extern int optind; char *dbname = NULL; char *host = NULL; char *port = NULL; char *qfilename = NULL; PsqlSettings settings; char *singleQuery = NULL; bool listDatabases = 0; int successResult = 1; bool singleSlashCmd = 0; int c; char *home = NULL; /* Used to store $HOME */ char *version = NULL; /* PostgreSQL version */ /* * initialize cur_cmd_source in case we do not use MainLoop ... some * systems fail if we try to use a static initializer for this :-( */ cur_cmd_source = stdin; cur_cmd_interactive = false; MemSet(&settings, 0, sizeof settings); settings.opt.align = 1; settings.opt.header = 1; settings.queryFout = stdout; settings.opt.fieldSep = strdup(DEFAULT_FIELD_SEP); settings.opt.pager = 1; if (!isatty(0) || !isatty(1)) { /* Noninteractive defaults */ settings.notty = 1; } else { /* Interactive defaults */ pqsignal(SIGINT, handle_sigint); /* control-C => cancel */ #ifdef USE_READLINE settings.useReadline = 1; { /* * Set the application name, used for parsing .inputrc -- dz */ char *progname = strrchr(argv[0], SEP_CHAR); rl_readline_name = (progname ? progname+1 : argv[0]); } #endif } #ifdef PSQL_ALWAYS_GET_PASSWORDS settings.getPassword = 1; #else settings.getPassword = 0; #endif #ifdef MULTIBYTE has_client_encoding = getenv("PGCLIENTENCODING"); #endif while ((c = getopt(argc, argv, "Aa:c:d:eEf:F:lh:Hnso:p:qStT:ux")) != EOF) { switch (c) { case 'A': settings.opt.align = 0; break; case 'a': #ifdef NOT_USED /* this no longer does anything */ fe_setauthsvc(optarg, errbuf); #endif break; case 'c': singleQuery = strdup(optarg); if (singleQuery[0] == '\\') singleSlashCmd = 1; break; case 'd': dbname = optarg; break; case 'e': settings.echoQuery = 1; break; case 'E': settings.echoAllQueries = 1; settings.echoQuery = 1; break; case 'f': qfilename = optarg; break; case 'F': settings.opt.fieldSep = strdup(optarg); break; case 'l': listDatabases = 1; break; case 'h': host = optarg; break; case 'H': settings.opt.html3 = 1; break; case 'n': settings.useReadline = 0; break; case 'o': setFout(&settings, 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.opt.header = 0; break; case 'T': settings.opt.tableOpt = strdup(optarg); break; case 'u': settings.getPassword = 1; break; case 'x': settings.opt.expanded = 1; break; 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"; if (settings.getPassword) { char username[100]; char password[100]; prompt_for_password(username, password); settings.db = PQsetdbLogin(host, port, NULL, NULL, dbname, username, password); } else settings.db = PQsetdb(host, port, NULL, NULL, dbname); dbname = PQdb(settings.db); if (PQstatus(settings.db) == CONNECTION_BAD) { fprintf(stderr, "Connection to database '%s' failed.\n", dbname); fprintf(stderr, "%s\n", PQerrorMessage(settings.db)); PQfinish(settings.db); exit(1); } cancelConn = settings.db; /* enable SIGINT to send cancel */ if (listDatabases) exit(listAllDbs(&settings)); if (!settings.quiet && !settings.notty && !singleQuery && !qfilename) { printf("Welcome to the POSTGRESQL interactive sql monitor:\n"); printf(" Please read the file COPYRIGHT for copyright terms " "of POSTGRESQL\n"); if ((version = selectVersion(&settings)) != NULL) printf("[%s]\n", version); printf("\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); } /* * 20.06.97 ACRM See if we've got a /etc/psqlrc or .psqlrc file */ if (!access("/etc/psqlrc", R_OK)) HandleSlashCmds(&settings, "\\i /etc/psqlrc", ""); if ((home = getenv("HOME")) != NULL) { char *psqlrc = NULL, *line = NULL; if ((psqlrc = (char *) malloc(strlen(home) + 10)) != NULL) { sprintf(psqlrc, "%s/.psqlrc", home); if (!access(psqlrc, R_OK)) { if ((line = (char *) malloc(strlen(psqlrc) + 5)) != NULL) { sprintf(line, "\\i %s", psqlrc); HandleSlashCmds(&settings, line, ""); free(line); } } free(psqlrc); } } /* End of check for psqlrc files */ if (qfilename || singleSlashCmd) { /* * read in a file full of queries instead of reading in queries * interactively */ char *line; 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(&settings, line, ""); free(line); } else { if (singleQuery) successResult = SendQuery(&settings, singleQuery, NULL, NULL); else successResult = MainLoop(&settings, NULL, stdin); } PQfinish(settings.db); free(settings.opt.fieldSep); if (settings.prompt) free(settings.prompt); return !successResult; } #define COPYBUFSIZ 8192 static bool handleCopyOut(PGconn *conn, FILE *copystream) { bool copydone; char copybuf[COPYBUFSIZ]; int ret; copydone = false; /* Can't be done; haven't started. */ while (!copydone) { ret = PQgetline(conn, copybuf, COPYBUFSIZ); if (copybuf[0] == '\\' && copybuf[1] == '.' && copybuf[2] == '\0') { copydone = true; /* don't print this... */ } else { fputs(copybuf, copystream); switch (ret) { case EOF: copydone = true; /* FALLTHROUGH */ case 0: fputc('\n', copystream); break; case 1: break; } } } fflush(copystream); return !PQendcopy(conn); } static bool handleCopyIn(PGconn *conn, const bool mustprompt, FILE *copystream) { bool copydone = false; bool firstload; bool linedone; char copybuf[COPYBUFSIZ]; char *s; int buflen; int c = 0; if (mustprompt) { fputs("Enter info followed by a newline\n", stdout); fputs("End with a backslash and a " "period on a line by itself.\n", stdout); } while (!copydone) { /* for each input line ... */ if (mustprompt) { fputs(">> ", stdout); fflush(stdout); } firstload = true; linedone = false; while (!linedone) { /* for each buffer ... */ s = copybuf; for (buflen = COPYBUFSIZ; buflen > 1; buflen--) { c = getc(copystream); if (c == '\n' || c == EOF) { linedone = true; break; } *s++ = c; } *s = '\0'; if (c == EOF) { PQputline(conn, "\\."); copydone = true; break; } PQputline(conn, copybuf); if (firstload) { if (!strcmp(copybuf, "\\.")) copydone = true; firstload = false; } } PQputline(conn, "\n"); } return !PQendcopy(conn); } /* * try to open fname and return a FILE *, if it fails, use stdout, instead */ static FILE * setFout(PsqlSettings *pset, char *fname) { if (pset->queryFout && pset->queryFout != stdout) { if (pset->pipe) pclose(pset->queryFout); else fclose(pset->queryFout); } if (!fname) { pset->queryFout = stdout; pqsignal(SIGPIPE, SIG_DFL); } else { if (*fname == '|') { pqsignal(SIGPIPE, SIG_IGN); #ifndef __CYGWIN32__ pset->queryFout = popen(fname + 1, "w"); #else pset->queryFout = popen(fname + 1, "wb"); #endif pset->pipe = 1; } else { pset->queryFout = fopen(fname, "w"); pqsignal(SIGPIPE, SIG_DFL); pset->pipe = 0; } if (!pset->queryFout) { perror(fname); pset->queryFout = stdout; } } return pset->queryFout; } static void prompt_for_password(char *username, char *password) { char buf[512]; int length; #ifdef HAVE_TERMIOS_H struct termios t_orig, t; #endif printf("Username: "); fgets(username, 100, stdin); length = strlen(username); /* skip rest of the line */ if (length > 0 && username[length - 1] != '\n') { do { fgets(buf, 512, stdin); } while (buf[strlen(buf) - 1] != '\n'); } if (length > 0 && username[length - 1] == '\n') username[length - 1] = '\0'; printf("Password: "); #ifdef HAVE_TERMIOS_H tcgetattr(0, &t); t_orig = t; t.c_lflag &= ~ECHO; tcsetattr(0, TCSADRAIN, &t); #endif fgets(password, 100, stdin); #ifdef HAVE_TERMIOS_H tcsetattr(0, TCSADRAIN, &t_orig); #endif length = strlen(password); /* skip rest of the line */ if (length > 0 && password[length - 1] != '\n') { do { fgets(buf, 512, stdin); } while (buf[strlen(buf) - 1] != '\n'); } if (length > 0 && password[length - 1] == '\n') password[length - 1] = '\0'; printf("\n\n"); } static char * selectVersion(PsqlSettings *pset) { #define PGVERSIONBUFSZ 128 static char version[PGVERSIONBUFSZ + 1]; PGresult *res; char *query = "select version();"; if (!(res = PQexec(pset->db, query))) return (NULL); if (PQresultStatus(res) == PGRES_COMMAND_OK || PQresultStatus(res) == PGRES_TUPLES_OK) { strncpy(version, PQgetvalue(res, 0, 0), PGVERSIONBUFSZ); version[PGVERSIONBUFSZ] = '\0'; PQclear(res); return (version); } else { PQclear(res); return (NULL); } }