diff options
Diffstat (limited to 'src/bin/psql/command.c')
-rw-r--r-- | src/bin/psql/command.c | 451 |
1 files changed, 361 insertions, 90 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 38253fa0988..2728216022e 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -49,6 +49,14 @@ #include "settings.h" #include "variables.h" +/* + * Editable database object types. + */ +typedef enum EditableObjectType +{ + EditableFunction, + EditableView +} EditableObjectType; /* functions for use in this file */ static backslashResult exec_command(const char *cmd, @@ -59,9 +67,14 @@ static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, static bool do_connect(char *dbname, char *user, char *host, char *port); static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, long sleep); -static bool lookup_function_oid(const char *desc, Oid *foid); -static bool get_create_function_cmd(Oid oid, PQExpBuffer buf); -static int strip_lineno_from_funcdesc(char *func); +static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, + Oid *obj_oid); +static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid, + PQExpBuffer buf); +static int strip_lineno_from_objdesc(char *obj); +static int count_lines_in_buf(PQExpBuffer buf); +static void print_with_linenumbers(FILE *output, char *lines, + const char *header_keyword); static void minimal_error_message(PGresult *res); static void printSSLInfo(void); @@ -612,7 +625,7 @@ exec_command(const char *cmd, func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); - lineno = strip_lineno_from_funcdesc(func); + lineno = strip_lineno_from_objdesc(func); if (lineno == 0) { /* error already reported */ @@ -629,12 +642,12 @@ exec_command(const char *cmd, "AS $function$\n" "\n$function$\n"); } - else if (!lookup_function_oid(func, &foid)) + else if (!lookup_object_oid(EditableFunction, func, &foid)) { /* error already reported */ status = PSQL_CMD_ERROR; } - else if (!get_create_function_cmd(foid, query_buf)) + else if (!get_create_object_cmd(EditableFunction, foid, query_buf)) { /* error already reported */ status = PSQL_CMD_ERROR; @@ -682,6 +695,74 @@ exec_command(const char *cmd, } } + /* + * \ev -- edit the named view, or present a blank CREATE VIEW template if + * no argument is given + */ + else if (strcmp(cmd, "ev") == 0) + { + int lineno = -1; + + if (pset.sversion < 70400) + { + psql_error("The server (version %d.%d) does not support editing view definitions.\n", + pset.sversion / 10000, (pset.sversion / 100) % 100); + status = PSQL_CMD_ERROR; + } + else if (!query_buf) + { + psql_error("no query buffer\n"); + status = PSQL_CMD_ERROR; + } + else + { + char *view; + Oid view_oid = InvalidOid; + + view = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); + lineno = strip_lineno_from_objdesc(view); + if (lineno == 0) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!view) + { + /* set up an empty command to fill in */ + printfPQExpBuffer(query_buf, + "CREATE VIEW AS\n" + " SELECT \n" + " -- something...\n"); + } + else if (!lookup_object_oid(EditableView, view, &view_oid)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!get_create_object_cmd(EditableView, view_oid, query_buf)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + + if (view) + free(view); + } + + if (status != PSQL_CMD_ERROR) + { + bool edited = false; + + if (!do_edit(NULL, query_buf, lineno, &edited)) + status = PSQL_CMD_ERROR; + else if (!edited) + puts(_("No changes")); + else + status = PSQL_CMD_NEWEDIT; + } + } + /* \echo and \qecho */ else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) { @@ -1235,12 +1316,12 @@ exec_command(const char *cmd, psql_error("function name is required\n"); status = PSQL_CMD_ERROR; } - else if (!lookup_function_oid(func, &foid)) + else if (!lookup_object_oid(EditableFunction, func, &foid)) { /* error already reported */ status = PSQL_CMD_ERROR; } - else if (!get_create_function_cmd(foid, func_buf)) + else if (!get_create_object_cmd(EditableFunction, foid, func_buf)) { /* error already reported */ status = PSQL_CMD_ERROR; @@ -1254,18 +1335,7 @@ exec_command(const char *cmd, if (pset.queryFout == stdout) { /* count lines in function to see if pager is needed */ - int lineno = 0; - const char *lines = func_buf->data; - - while (*lines != '\0') - { - lineno++; - /* find start of next line */ - lines = strchr(lines, '\n'); - if (!lines) - break; - lines++; - } + int lineno = count_lines_in_buf(func_buf); output = PageOutput(lineno, &(pset.popt.topt)); is_pager = true; @@ -1279,45 +1349,14 @@ exec_command(const char *cmd, if (show_linenumbers) { - bool in_header = true; - int lineno = 0; - char *lines = func_buf->data; - /* * lineno "1" should correspond to the first line of the * function body. We expect that pg_get_functiondef() will * emit that on a line beginning with "AS ", and that there * can be no such line before the real start of the function * body. - * - * Note that this loop scribbles on func_buf. */ - while (*lines != '\0') - { - char *eol; - - if (in_header && strncmp(lines, "AS ", 3) == 0) - in_header = false; - /* increment lineno only for body's lines */ - if (!in_header) - lineno++; - - /* find and mark end of current line */ - eol = strchr(lines, '\n'); - if (eol != NULL) - *eol = '\0'; - - /* show current line as appropriate */ - if (in_header) - fprintf(output, " %s\n", lines); - else - fprintf(output, "%-7d %s\n", lineno, lines); - - /* advance to next line, if any */ - if (eol == NULL) - break; - lines = ++eol; - } + print_with_linenumbers(output, func_buf->data, "AS "); } else { @@ -1334,6 +1373,79 @@ exec_command(const char *cmd, destroyPQExpBuffer(func_buf); } + /* \sv -- show a view's source code */ + else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) + { + bool show_linenumbers = (strcmp(cmd, "sv+") == 0); + PQExpBuffer view_buf; + char *view; + Oid view_oid = InvalidOid; + + view_buf = createPQExpBuffer(); + view = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); + if (pset.sversion < 70400) + { + psql_error("The server (version %d.%d) does not support showing view definitions.\n", + pset.sversion / 10000, (pset.sversion / 100) % 100); + status = PSQL_CMD_ERROR; + } + else if (!view) + { + psql_error("view name is required\n"); + status = PSQL_CMD_ERROR; + } + else if (!lookup_object_oid(EditableView, view, &view_oid)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!get_create_object_cmd(EditableView, view_oid, view_buf)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else + { + FILE *output; + bool is_pager; + + /* Select output stream: stdout, pager, or file */ + if (pset.queryFout == stdout) + { + /* count lines in view to see if pager is needed */ + int lineno = count_lines_in_buf(view_buf); + + output = PageOutput(lineno, &(pset.popt.topt)); + is_pager = true; + } + else + { + /* use previously set output file, without pager */ + output = pset.queryFout; + is_pager = false; + } + + if (show_linenumbers) + { + /* add line numbers, numbering all lines */ + print_with_linenumbers(output, view_buf->data, NULL); + } + else + { + /* just send the view definition to output */ + fputs(view_buf->data, output); + } + + if (is_pager) + ClosePager(output); + } + + if (view) + free(view); + destroyPQExpBuffer(view_buf); + } + /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) { @@ -3002,7 +3114,7 @@ do_watch(PQExpBuffer query_buf, long sleep) * returns true unless we have ECHO_HIDDEN_NOEXEC. */ static bool -lookup_function_echo_hidden(char *query) +echo_hidden_command(const char *query) { if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF) { @@ -3026,34 +3138,57 @@ lookup_function_echo_hidden(char *query) } /* - * This function takes a function description, e.g. "x" or "x(int)", and - * issues a query on the given connection to retrieve the function's OID - * using a cast to regproc or regprocedure (as appropriate). The result, - * if there is one, is returned at *foid. Note that we'll fail if the - * function doesn't exist OR if there are multiple matching candidates - * OR if there's something syntactically wrong with the function description; - * unfortunately it can be hard to tell the difference. + * Look up the object identified by obj_type and desc. If successful, + * store its OID in *obj_oid and return TRUE, else return FALSE. + * + * Note that we'll fail if the object doesn't exist OR if there are multiple + * matching candidates OR if there's something syntactically wrong with the + * object description; unfortunately it can be hard to tell the difference. */ static bool -lookup_function_oid(const char *desc, Oid *foid) +lookup_object_oid(EditableObjectType obj_type, const char *desc, + Oid *obj_oid) { bool result = true; - PQExpBuffer query; + PQExpBuffer query = createPQExpBuffer(); PGresult *res; - query = createPQExpBuffer(); - appendPQExpBufferStr(query, "SELECT "); - appendStringLiteralConn(query, desc, pset.db); - appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid", - strchr(desc, '(') ? "regprocedure" : "regproc"); - if (!lookup_function_echo_hidden(query->data)) + switch (obj_type) + { + case EditableFunction: + + /* + * We have a function description, e.g. "x" or "x(int)". Issue a + * query to retrieve the function's OID using a cast to regproc or + * regprocedure (as appropriate). + */ + appendPQExpBufferStr(query, "SELECT "); + appendStringLiteralConn(query, desc, pset.db); + appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid", + strchr(desc, '(') ? "regprocedure" : "regproc"); + break; + + case EditableView: + + /* + * Convert view name (possibly schema-qualified) to OID. Note: + * this code doesn't check if the relation is actually a view. + * We'll detect that in get_create_object_cmd(). + */ + appendPQExpBufferStr(query, "SELECT "); + appendStringLiteralConn(query, desc, pset.db); + appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid"); + break; + } + + if (!echo_hidden_command(query->data)) { destroyPQExpBuffer(query); return false; } res = PQexec(pset.db, query->data); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) - *foid = atooid(PQgetvalue(res, 0, 0)); + *obj_oid = atooid(PQgetvalue(res, 0, 0)); else { minimal_error_message(res); @@ -3067,20 +3202,42 @@ lookup_function_oid(const char *desc, Oid *foid) } /* - * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the - * function with the given OID. If successful, the result is stored in buf. + * Construct a "CREATE OR REPLACE ..." command that describes the specified + * database object. If successful, the result is stored in buf. */ static bool -get_create_function_cmd(Oid oid, PQExpBuffer buf) +get_create_object_cmd(EditableObjectType obj_type, Oid oid, + PQExpBuffer buf) { bool result = true; - PQExpBuffer query; + PQExpBuffer query = createPQExpBuffer(); PGresult *res; - query = createPQExpBuffer(); - printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid); + switch (obj_type) + { + case EditableFunction: + printfPQExpBuffer(query, + "SELECT pg_catalog.pg_get_functiondef(%u)", + oid); + break; + + case EditableView: - if (!lookup_function_echo_hidden(query->data)) + /* + * pg_get_viewdef() just prints the query, so we must prepend + * CREATE for ourselves. We must fully qualify the view name to + * ensure the right view gets replaced. Also, check relation kind + * to be sure it's a view. + */ + printfPQExpBuffer(query, + "SELECT nspname, relname, relkind, pg_catalog.pg_get_viewdef(c.oid, true) FROM " + "pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n " + "ON c.relnamespace = n.oid WHERE c.oid = %u", + oid); + break; + } + + if (!echo_hidden_command(query->data)) { destroyPQExpBuffer(query); return false; @@ -3089,7 +3246,53 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf) if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) { resetPQExpBuffer(buf); - appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0)); + switch (obj_type) + { + case EditableFunction: + appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0)); + break; + + case EditableView: + { + char *nspname = PQgetvalue(res, 0, 0); + char *relname = PQgetvalue(res, 0, 1); + char *relkind = PQgetvalue(res, 0, 2); + char *viewdef = PQgetvalue(res, 0, 3); + + /* + * If the backend ever supports CREATE OR REPLACE + * MATERIALIZED VIEW, allow that here; but as of today it + * does not, so editing a matview definition in this way + * is impossible. + */ + switch (relkind[0]) + { +#ifdef NOT_USED + case 'm': + appendPQExpBufferStr(buf, "CREATE OR REPLACE MATERIALIZED VIEW "); + break; +#endif + case 'v': + appendPQExpBufferStr(buf, "CREATE OR REPLACE VIEW "); + break; + default: + psql_error("%s.%s is not a view\n", + nspname, relname); + result = false; + break; + } + appendPQExpBuffer(buf, "%s.", fmtId(nspname)); + appendPQExpBuffer(buf, "%s AS\n", fmtId(relname)); + appendPQExpBufferStr(buf, viewdef); + /* Get rid of the semicolon that pg_get_viewdef appends */ + if (buf->len > 0 && buf->data[buf->len - 1] == ';') + buf->data[--(buf->len)] = '\0'; + } + break; + } + /* Make sure result ends with a newline */ + if (buf->len > 0 && buf->data[buf->len - 1] != '\n') + appendPQExpBufferChar(buf, '\n'); } else { @@ -3104,24 +3307,24 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf) } /* - * If the given argument of \ef ends with a line number, delete the line + * If the given argument of \ef or \ev ends with a line number, delete the line * number from the argument string and return it as an integer. (We need - * this kluge because we're too lazy to parse \ef's function name argument - * carefully --- we just slop it up in OT_WHOLE_LINE mode.) + * this kluge because we're too lazy to parse \ef's function or \ev's view + * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.) * * Returns -1 if no line number is present, 0 on error, or a positive value * on success. */ static int -strip_lineno_from_funcdesc(char *func) +strip_lineno_from_objdesc(char *obj) { char *c; int lineno; - if (!func || func[0] == '\0') + if (!obj || obj[0] == '\0') return -1; - c = func + strlen(func) - 1; + c = obj + strlen(obj) - 1; /* * This business of parsing backwards is dangerous as can be in a @@ -3134,20 +3337,20 @@ strip_lineno_from_funcdesc(char *func) */ /* skip trailing whitespace */ - while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c)) + while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c)) c--; /* must have a digit as last non-space char */ - if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c)) + if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c)) return -1; /* find start of digit string */ - while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c)) + while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c)) c--; - /* digits must be separated from func name by space or closing paren */ - /* notice also that we are not allowing an empty func name ... */ - if (c == func || !isascii((unsigned char) *c) || + /* digits must be separated from object name by space or closing paren */ + /* notice also that we are not allowing an empty object name ... */ + if (c == obj || !isascii((unsigned char) *c) || !(isspace((unsigned char) *c) || *c == ')')) return -1; @@ -3160,13 +3363,81 @@ strip_lineno_from_funcdesc(char *func) return 0; } - /* strip digit string from func */ + /* strip digit string from object name */ *c = '\0'; return lineno; } /* + * Count number of lines in the buffer. + * This is used to test if pager is needed or not. + */ +static int +count_lines_in_buf(PQExpBuffer buf) +{ + int lineno = 0; + const char *lines = buf->data; + + while (*lines != '\0') + { + lineno++; + /* find start of next line */ + lines = strchr(lines, '\n'); + if (!lines) + break; + lines++; + } + + return lineno; +} + +/* + * Write text at *lines to output with line numbers. + * + * If header_keyword isn't NULL, then line 1 should be the first line beginning + * with header_keyword; lines before that are unnumbered. + * + * Caution: this scribbles on *lines. + */ +static void +print_with_linenumbers(FILE *output, char *lines, + const char *header_keyword) +{ + bool in_header = (header_keyword != NULL); + size_t header_sz = in_header ? strlen(header_keyword) : 0; + int lineno = 0; + + while (*lines != '\0') + { + char *eol; + + if (in_header && strncmp(lines, header_keyword, header_sz) == 0) + in_header = false; + + /* increment lineno only for body's lines */ + if (!in_header) + lineno++; + + /* find and mark end of current line */ + eol = strchr(lines, '\n'); + if (eol != NULL) + *eol = '\0'; + + /* show current line as appropriate */ + if (in_header) + fprintf(output, " %s\n", lines); + else + fprintf(output, "%-7d %s\n", lineno, lines); + + /* advance to next line, if any */ + if (eol == NULL) + break; + lines = ++eol; + } +} + +/* * Report just the primary error; this is to avoid cluttering the output * with, for instance, a redisplay of the internally generated query */ |