📄 common.c
字号:
/* * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2005, PostgreSQL Global Development Group * * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.110.2.1 2005/11/22 18:23:27 momjian Exp $ */#include "postgres_fe.h"#include "common.h"#include <ctype.h>#ifndef HAVE_STRDUP#include <strdup.h>#endif#include <signal.h>#ifndef WIN32#include <sys/time.h>#include <unistd.h> /* for write() */#include <setjmp.h>#else#include <io.h> /* for _write() */#include <win32.h>#include <sys/timeb.h> /* for _ftime() */#endif#include "libpq-fe.h"#include "pqsignal.h"#include "settings.h"#include "variables.h"#include "command.h"#include "copy.h"#include "prompt.h"#include "print.h"#include "mainloop.h"#include "mb/pg_wchar.h"/* Workarounds for Windows *//* Probably to be moved up the source tree in the future, perhaps to be replaced by * more specific checks like configure-style HAVE_GETTIMEOFDAY macros. */#ifndef WIN32typedef struct timeval TimevalStruct;#define GETTIMEOFDAY(T) gettimeofday(T, NULL)#define DIFF_MSEC(T, U) \ ((((int) ((T)->tv_sec - (U)->tv_sec)) * 1000000.0 + \ ((int) ((T)->tv_usec - (U)->tv_usec))) / 1000.0)#elsetypedef struct _timeb TimevalStruct;#define GETTIMEOFDAY(T) _ftime(T)#define DIFF_MSEC(T, U) \ (((T)->time - (U)->time) * 1000.0 + \ ((T)->millitm - (U)->millitm))#endifextern bool prompt_state;static bool command_no_begin(const char *query);/* * "Safe" wrapper around strdup() */char *pg_strdup(const char *string){ char *tmp; if (!string) { fprintf(stderr, _("%s: pg_strdup: cannot duplicate null pointer (internal error)\n"), pset.progname); exit(EXIT_FAILURE); } tmp = strdup(string); if (!tmp) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } return tmp;}void *pg_malloc(size_t size){ void *tmp; tmp = malloc(size); if (!tmp) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } return tmp;}void *pg_malloc_zero(size_t size){ void *tmp; tmp = pg_malloc(size); memset(tmp, 0, size); return tmp;}void *pg_calloc(size_t nmemb, size_t size){ void *tmp; tmp = calloc(nmemb, size); if (!tmp) { psql_error("out of memory"); exit(EXIT_FAILURE); } return tmp;}/* * setQFout * -- handler for -o command line option and \o command * * Tries to open file fname (or pipe if fname starts with '|') * and stores the file handle in pset) * Upon failure, sets stdout and returns false. */boolsetQFout(const char *fname){ bool status = true; /* Close old file/pipe */ if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr) { if (pset.queryFoutPipe) pclose(pset.queryFout); else fclose(pset.queryFout); } /* If no filename, set stdout */ if (!fname || fname[0] == '\0') { pset.queryFout = stdout; pset.queryFoutPipe = false; } else if (*fname == '|') { pset.queryFout = popen(fname + 1, "w"); pset.queryFoutPipe = true; } else { pset.queryFout = fopen(fname, "w"); pset.queryFoutPipe = false; } if (!(pset.queryFout)) { psql_error("%s: %s\n", fname, strerror(errno)); pset.queryFout = stdout; pset.queryFoutPipe = false; status = false; } /* Direct signals */#ifndef WIN32 pqsignal(SIGPIPE, pset.queryFoutPipe ? SIG_IGN : SIG_DFL);#endif return status;}/* * Error reporting for scripts. Errors should look like * psql:filename:lineno: message * */voidpsql_error(const char *fmt,...){ va_list ap; fflush(stdout); if (pset.queryFout != stdout) fflush(pset.queryFout); if (pset.inputfile) fprintf(stderr, "%s:%s:%u: ", pset.progname, pset.inputfile, pset.lineno); va_start(ap, fmt); vfprintf(stderr, _(fmt), ap); va_end(ap);}/* * for backend Notice messages (INFO, WARNING, etc) */voidNoticeProcessor(void *arg, const char *message){ (void) arg; /* not used */ psql_error("%s", message);}/* * Code to support query cancellation * * Before we start a query, 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 because PQcancel() is written to make it * so. We use write() to print to stderr because it's better to use simple * facilities in a signal handler. * * On win32, the signal cancelling happens on a separate thread, because * that's how SetConsoleCtrlHandler works. The PQcancel function is safe * for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required * to protect the PGcancel structure against being changed while the other * thread is using it. */static PGcancel *cancelConn = NULL;#ifdef WIN32static CRITICAL_SECTION cancelConnLock;#endifvolatile bool cancel_pressed = false;#define write_stderr(str) write(fileno(stderr), str, strlen(str))#ifndef WIN32voidhandle_sigint(SIGNAL_ARGS){ int save_errno = errno; char errbuf[256]; /* Don't muck around if prompting for a password. */ if (prompt_state) return; if (cancelConn == NULL) siglongjmp(main_loop_jmp, 1); cancel_pressed = true; if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) write_stderr("Cancel request sent\n"); else { write_stderr("Could not send cancel request: "); write_stderr(errbuf); } errno = save_errno; /* just in case the write changed it */}#else /* WIN32 */static BOOL WINAPIconsoleHandler(DWORD dwCtrlType){ char errbuf[256]; if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { if (prompt_state) return TRUE; /* Perform query cancel */ EnterCriticalSection(&cancelConnLock); if (cancelConn != NULL) { cancel_pressed = true; if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) write_stderr("Cancel request sent\n"); else { write_stderr("Could not send cancel request: "); write_stderr(errbuf); } } LeaveCriticalSection(&cancelConnLock); return TRUE; } else /* Return FALSE for any signals not being handled */ return FALSE;}voidsetup_win32_locks(void){ InitializeCriticalSection(&cancelConnLock);}voidsetup_cancel_handler(void){ static bool done = false; /* only need one handler per process */ if (!done) { SetConsoleCtrlHandler(consoleHandler, TRUE); done = true; }}#endif /* WIN32 *//* ConnectionUp * * Returns whether our backend connection is still there. */static boolConnectionUp(void){ return PQstatus(pset.db) != CONNECTION_BAD;}/* CheckConnection * * Verify that we still have a good connection to the backend, and if not, * see if it can be restored. * * Returns true if either the connection was still there, or it could be * restored successfully; false otherwise. If, however, there was no * connection and the session is non-interactive, this will exit the program * with a code of EXIT_BADCONN. */static boolCheckConnection(void){ bool OK; OK = ConnectionUp(); if (!OK) { if (!pset.cur_cmd_interactive) { psql_error("connection to server was lost\n"); exit(EXIT_BADCONN); } fputs(_("The connection to the server was lost. Attempting reset: "), stderr); PQreset(pset.db); OK = ConnectionUp(); if (!OK) { fputs(_("Failed.\n"), stderr); PQfinish(pset.db); pset.db = NULL; ResetCancelConn(); UnsyncVariables(); } else fputs(_("Succeeded.\n"), stderr); } return OK;}/* * SetCancelConn * * Set cancelConn to point to the current database connection. */static voidSetCancelConn(void){#ifdef WIN32 EnterCriticalSection(&cancelConnLock);#endif /* Free the old one if we have one */ if (cancelConn != NULL) PQfreeCancel(cancelConn); cancelConn = PQgetCancel(pset.db);#ifdef WIN32 LeaveCriticalSection(&cancelConnLock);#endif}/* * ResetCancelConn * * Free the current cancel connection, if any, and set to NULL. */voidResetCancelConn(void){#ifdef WIN32 EnterCriticalSection(&cancelConnLock);#endif if (cancelConn) PQfreeCancel(cancelConn); cancelConn = NULL;#ifdef WIN32 LeaveCriticalSection(&cancelConnLock);#endif}/* * on errors, print syntax error position if available. * * the query is expected to be in the client encoding. */static voidReportSyntaxErrorPosition(const PGresult *result, const char *query){#define DISPLAY_SIZE 60 /* screen width limit, in screen cols */#define MIN_RIGHT_CUT 10 /* try to keep this far away from EOL */ int loc = 0; const char *sp; int clen, slen, i, *qidx, *scridx, qoffset, scroffset, ibeg, iend, loc_line; char *wquery; bool beg_trunc, end_trunc; PQExpBufferData msg; if (pset.verbosity == PQERRORS_TERSE) return; sp = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION); if (sp == NULL) { sp = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION); if (sp == NULL) return; /* no syntax error */ query = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY); } if (query == NULL) return; /* nothing to reference location to */ if (sscanf(sp, "%d", &loc) != 1) { psql_error("INTERNAL ERROR: unexpected statement position \"%s\"\n", sp); return; } /* Make a writable copy of the query, and a buffer for messages. */ wquery = pg_strdup(query); initPQExpBuffer(&msg); /* * The returned cursor position is measured in logical characters. Each * character might occupy multiple physical bytes in the string, and in * some Far Eastern character sets it might take more than one screen * column as well. We compute the starting byte offset and starting * screen column of each logical character, and store these in qidx[] and * scridx[] respectively. */ /* we need a safe allocation size... */ slen = strlen(query) + 1; qidx = (int *) pg_malloc(slen * sizeof(int)); scridx = (int *) pg_malloc(slen * sizeof(int)); qoffset = 0; scroffset = 0; for (i = 0; query[qoffset] != '\0'; i++) { qidx[i] = qoffset; scridx[i] = scroffset; scroffset += PQdsplen(&query[qoffset], pset.encoding); qoffset += PQmblen(&query[qoffset], pset.encoding); } qidx[i] = qoffset; scridx[i] = scroffset; clen = i; psql_assert(clen < slen); /* convert loc to zero-based offset in qidx/scridx arrays */ loc--; /* do we have something to show? */ if (loc >= 0 && loc <= clen) { /* input line number of our syntax error. */ loc_line = 1; /* first included char of extract. */ ibeg = 0; /* last-plus-1 included char of extract. */ iend = clen; /* * Replace tabs with spaces in the writable copy. (Later we might * want to think about coping with their variable screen width, but * not today.) * * Extract line number and begin and end indexes of line containing * error location. There will not be any newlines or carriage returns * in the selected extract. */ for (i = 0; i < clen; i++) { /* character length must be 1 or it's not ASCII */ if ((qidx[i + 1] - qidx[i]) == 1) { if (wquery[qidx[i]] == '\t') wquery[qidx[i]] = ' '; else if (wquery[qidx[i]] == '\r' || wquery[qidx[i]] == '\n') { if (i < loc) { /* * count lines before loc. Each \r or \n counts as a * line except when \r \n appear together. */ if (wquery[qidx[i]] == '\r' || i == 0 || (qidx[i] - qidx[i - 1]) != 1 || wquery[qidx[i - 1]] != '\r') loc_line++; /* extract beginning = last line start before loc. */ ibeg = i + 1; } else { /* set extract end. */ iend = i; /* done scanning. */ break; } } } } /* If the line extracted is too long, we truncate it. */ beg_trunc = false; end_trunc = false; if (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE) { /* * We first truncate right if it is enough. This code might be * off a space or so on enforcing MIN_RIGHT_CUT if there's a wide * character right there, but that should be okay. */ if (scridx[ibeg] + DISPLAY_SIZE >= scridx[loc] + MIN_RIGHT_CUT) { while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE) iend--; end_trunc = true; } else { /* Truncate right if not too close to loc. */ while (scridx[loc] + MIN_RIGHT_CUT < scridx[iend]) { iend--; end_trunc = true; } /* Truncate left if still too long. */ while (scridx[iend] - scridx[ibeg] > DISPLAY_SIZE) { ibeg++; beg_trunc = true; } } } /* the extract MUST contain the target position! */ psql_assert(ibeg <= loc && loc <= iend); /* truncate working copy at desired endpoint */ wquery[qidx[iend]] = '\0'; /* Begin building the finished message. */ printfPQExpBuffer(&msg, _("LINE %d: "), loc_line); if (beg_trunc) appendPQExpBufferStr(&msg, "..."); /* * While we have the prefix in the msg buffer, compute its screen * width. */ scroffset = 0; for (i = 0; i < msg.len; i += PQmblen(&msg.data[i], pset.encoding)) scroffset += PQdsplen(&msg.data[i], pset.encoding); /* Finish and emit the message. */ appendPQExpBufferStr(&msg, &wquery[qidx[ibeg]]); if (end_trunc) appendPQExpBufferStr(&msg, "..."); psql_error("%s\n", msg.data); /* Now emit the cursor marker line. */ scroffset += scridx[loc] - scridx[ibeg]; resetPQExpBuffer(&msg); for (i = 0; i < scroffset; i++) appendPQExpBufferChar(&msg, ' '); appendPQExpBufferChar(&msg, '^'); psql_error("%s\n", msg.data); } /* Clean up. */ termPQExpBuffer(&msg); free(wquery); free(qidx); free(scridx);}/* * AcceptResult * * Checks whether a result is valid, giving an error message if necessary; * resets cancelConn as needed, and ensures that the connection to the backend * is still up. * * Returns true for valid result, false for error state. */static boolAcceptResult(const PGresult *result, const char *query){ bool OK = true; ResetCancelConn(); if (!result) OK = false; else switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: case PGRES_COPY_IN: /* Fine, do nothing */ break; case PGRES_COPY_OUT: /* keep cancel connection for copy out state */ SetCancelConn(); break; default: OK = false; break; } if (!OK) { const char *error = PQerrorMessage(pset.db); if (strlen(error)) psql_error("%s", error); ReportSyntaxErrorPosition(result, query); CheckConnection(); } return OK;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -