X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbin%2Fpsql%2Fcommand.c;h=3608b725963e56fa9a228abd5500b9c0177e5b11;hb=e67efb01e886d69d40d1cd87fba4507e8bb1035e;hp=cea3942f013be4637cc5d8c946da4e15f6829233;hpb=2eda8dfb52ed9962920282d8384da8bb4c22514d;p=postgresql diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index cea3942f01..3608b72596 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright (c) 2000-2009, PostgreSQL Global Development Group + * Copyright (c) 2000-2011, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.209 2009/10/07 22:14:24 alvherre Exp $ + * src/bin/psql/command.c */ #include "postgres_fe.h" #include "command.h" @@ -57,11 +57,12 @@ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, PQExpBuffer query_buf); static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, - bool *edited); + int lineno, bool *edited); static bool do_connect(char *dbname, char *user, char *host, char *port); static bool do_shell(const char *command); static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid); static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf); +static int strip_lineno_from_funcdesc(char *func); static void minimal_error_message(PGresult *res); static void printSSLInfo(void); @@ -294,6 +295,28 @@ exec_command(const char *cmd, free(opt); } + /* \conninfo -- display information about the current connection */ + else if (strcmp(cmd, "conninfo") == 0) + { + char *db = PQdb(pset.db); + char *host = PQhost(pset.db); + + if (db == NULL) + printf(_("You are currently not connected to a database.\n")); + else + { + if (host == NULL) + host = DEFAULT_PGSOCKET_DIR; + /* If the host is an absolute path, the connection is via socket */ + if (is_absolute_path(host)) + printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"), + db, PQuser(pset.db), host, PQport(pset.db)); + else + printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"), + db, PQuser(pset.db), host, PQport(pset.db)); + } + } + /* \copy */ else if (pg_strcasecmp(cmd, "copy") == 0) { @@ -346,7 +369,7 @@ exec_command(const char *cmd, success = describeTableDetails(pattern, show_verbose, show_system); else /* standard listing of interesting things */ - success = listTables("tvs", NULL, show_verbose, show_system); + success = listTables("tvsE", NULL, show_verbose, show_system); break; case 'a': success = describeAggregates(pattern, show_verbose, show_system); @@ -361,7 +384,7 @@ exec_command(const char *cmd, success = listCasts(pattern); break; case 'd': - if (strcmp(cmd, "ddp") == 0) + if (strncmp(cmd, "ddp", 3) == 0) success = listDefaultACLs(pattern); else success = objectDescription(pattern, show_system); @@ -393,12 +416,18 @@ exec_command(const char *cmd, case 'l': success = do_lo_list(); break; + case 'L': + success = listLanguages(pattern, show_verbose, show_system); + break; case 'n': - success = listSchemas(pattern, show_verbose); + success = listSchemas(pattern, show_verbose, show_system); break; case 'o': success = describeOperators(pattern, show_system); break; + case 'O': + success = listCollations(pattern, show_verbose, show_system); + break; case 'p': success = permissionsList(pattern); break; @@ -409,6 +438,7 @@ exec_command(const char *cmd, case 'v': case 'i': case 's': + case 'E': success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'r': @@ -418,7 +448,7 @@ exec_command(const char *cmd, if (pattern) pattern2 = psql_scan_slash_option(scan_state, - OT_NORMAL, NULL, true); + OT_NORMAL, NULL, true); success = listDbRoleSettings(pattern, pattern2); } else @@ -460,11 +490,20 @@ exec_command(const char *cmd, case 'w': success = listForeignDataWrappers(pattern, show_verbose); break; + case 't': + success = listForeignTables(pattern, show_verbose); + break; default: status = PSQL_CMD_UNKNOWN; break; } break; + case 'x': /* Extensions */ + if (show_verbose) + success = listExtensionContents(pattern); + else + success = listExtensions(pattern); + break; default: status = PSQL_CMD_UNKNOWN; } @@ -475,8 +514,8 @@ exec_command(const char *cmd, /* - * \e or \edit -- edit the current query buffer (or a file and make it the - * query buffer + * \e or \edit -- edit the current query buffer, or edit a file and make + * it the query buffer */ else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) { @@ -488,17 +527,51 @@ exec_command(const char *cmd, else { char *fname; + char *ln = NULL; + int lineno = -1; fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); - expand_tilde(&fname); if (fname) - canonicalize_path(fname); - if (do_edit(fname, query_buf, NULL)) - status = PSQL_CMD_NEWEDIT; - else - status = PSQL_CMD_ERROR; - free(fname); + { + /* try to get separate lineno arg */ + ln = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); + if (ln == NULL) + { + /* only one arg; maybe it is lineno not fname */ + if (fname[0] && + strspn(fname, "0123456789") == strlen(fname)) + { + /* all digits, so assume it is lineno */ + ln = fname; + fname = NULL; + } + } + } + if (ln) + { + lineno = atoi(ln); + if (lineno < 1) + { + psql_error("invalid line number: %s\n", ln); + status = PSQL_CMD_ERROR; + } + } + if (status != PSQL_CMD_ERROR) + { + expand_tilde(&fname); + if (fname) + canonicalize_path(fname); + if (do_edit(fname, query_buf, lineno, NULL)) + status = PSQL_CMD_NEWEDIT; + else + status = PSQL_CMD_ERROR; + } + if (fname) + free(fname); + if (ln) + free(ln); } } @@ -508,6 +581,8 @@ exec_command(const char *cmd, */ else if (strcmp(cmd, "ef") == 0) { + int lineno = -1; + if (!query_buf) { psql_error("no query buffer\n"); @@ -520,7 +595,13 @@ exec_command(const char *cmd, func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); - if (!func) + lineno = strip_lineno_from_funcdesc(func); + if (lineno == 0) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!func) { /* set up an empty command to fill in */ printfPQExpBuffer(query_buf, @@ -541,6 +622,32 @@ exec_command(const char *cmd, /* error already reported */ status = PSQL_CMD_ERROR; } + else if (lineno > 0) + { + /* + * 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. Increment lineno by the number of lines before that + * line, so that it becomes relative to the first line of the + * function definition. + */ + const char *lines = query_buf->data; + + while (*lines != '\0') + { + if (strncmp(lines, "AS ", 3) == 0) + break; + lineno++; + /* find start of next line */ + lines = strchr(lines, '\n'); + if (!lines) + break; + lines++; + } + } + if (func) free(func); } @@ -549,7 +656,7 @@ exec_command(const char *cmd, { bool edited = false; - if (!do_edit(0, query_buf, &edited)) + if (!do_edit(NULL, query_buf, lineno, &edited)) status = PSQL_CMD_ERROR; else if (!edited) puts(_("No changes")); @@ -651,6 +758,17 @@ exec_command(const char *cmd, { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); + size_t len; + + /* strip any trailing spaces and semicolons */ + if (opt) + { + len = strlen(opt); + while (len > 0 && + (isspace((unsigned char) opt[len - 1]) + || opt[len - 1] == ';')) + opt[--len] = '\0'; + } helpSQL(opt, pset.popt.topt.pager); free(opt); @@ -666,8 +784,9 @@ exec_command(const char *cmd, } - /* \i is include file */ - else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0) + /* \i and \ir include files */ + else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 + || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); @@ -679,8 +798,12 @@ exec_command(const char *cmd, } else { + bool include_relative; + + include_relative = (strcmp(cmd, "ir") == 0 + || strcmp(cmd, "include_relative") == 0); expand_tilde(&fname); - success = (process_file(fname, false) == EXIT_SUCCESS); + success = (process_file(fname, false, include_relative) == EXIT_SUCCESS); free(fname); } } @@ -981,6 +1104,121 @@ exec_command(const char *cmd, free(opt0); } + /* \sf -- show a function's source code */ + else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) + { + bool show_linenumbers = (strcmp(cmd, "sf+") == 0); + PQExpBuffer func_buf; + char *func; + Oid foid = InvalidOid; + + func_buf = createPQExpBuffer(); + func = psql_scan_slash_option(scan_state, + OT_WHOLE_LINE, NULL, true); + if (!func) + { + psql_error("function name is required\n"); + status = PSQL_CMD_ERROR; + } + else if (!lookup_function_oid(pset.db, func, &foid)) + { + /* error already reported */ + status = PSQL_CMD_ERROR; + } + else if (!get_create_function_cmd(pset.db, foid, func_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 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++; + } + + output = PageOutput(lineno, pset.popt.topt.pager); + is_pager = true; + } + else + { + /* use previously set output file, without pager */ + output = pset.queryFout; + is_pager = false; + } + + 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; + } + } + else + { + /* just send the function definition to output */ + fputs(func_buf->data, output); + } + + if (is_pager) + ClosePager(output); + } + + if (func) + free(func); + destroyPQExpBuffer(func_buf); + } + /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) { @@ -991,7 +1229,6 @@ exec_command(const char *cmd, free(opt); } - /* \T -- define html attributes */ else if (strcmp(cmd, "T") == 0) { @@ -1213,7 +1450,7 @@ param_is_newly_set(const char *old_val, const char *new_val) * Connects to a database with given parameters. If there exists an * established connection, NULL values will be replaced with the ones * in the current connection. Otherwise NULL will be passed for that - * parameter to PQsetdbLogin(), so the libpq defaults will be used. + * parameter to PQconnectdbParams(), so the libpq defaults will be used. * * In interactive mode, if connection fails with the given parameters, * the old connection will be kept. @@ -1255,8 +1492,31 @@ do_connect(char *dbname, char *user, char *host, char *port) while (true) { - n_conn = PQsetdbLogin(host, port, NULL, NULL, - dbname, user, password); +#define PARAMS_ARRAY_SIZE 8 + const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); + const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); + + keywords[0] = "host"; + values[0] = host; + keywords[1] = "port"; + values[1] = port; + keywords[2] = "user"; + values[2] = user; + keywords[3] = "password"; + values[3] = password; + keywords[4] = "dbname"; + values[4] = dbname; + keywords[5] = "fallback_application_name"; + values[5] = pset.progname; + keywords[6] = "client_encoding"; + values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; + keywords[7] = NULL; + values[7] = NULL; + + n_conn = PQconnectdbParams(keywords, values, true); + + free(keywords); + free(values); /* We can immediately discard the password -- no longer needed */ if (password) @@ -1310,23 +1570,29 @@ do_connect(char *dbname, char *user, char *host, char *port) PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL); pset.db = n_conn; SyncVariables(); - connection_warnings(); /* Must be after SyncVariables */ + connection_warnings(false); /* Must be after SyncVariables */ /* Tell the user about the new connection */ if (!pset.quiet) { - printf(_("You are now connected to database \"%s\""), PQdb(pset.db)); - - if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db))) - printf(_(" on host \"%s\""), PQhost(pset.db)); - - if (param_is_newly_set(PQport(o_conn), PQport(pset.db))) - printf(_(" at port \"%s\""), PQport(pset.db)); - - if (param_is_newly_set(PQuser(o_conn), PQuser(pset.db))) - printf(_(" as user \"%s\""), PQuser(pset.db)); - - printf(".\n"); + if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) || + param_is_newly_set(PQport(o_conn), PQport(pset.db))) + { + char *host = PQhost(pset.db); + + if (host == NULL) + host = DEFAULT_PGSOCKET_DIR; + /* If the host is an absolute path, the connection is via socket */ + if (is_absolute_path(host)) + printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"), + PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db)); + else + printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"), + PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db)); + } + else + printf(_("You are now connected to database \"%s\" as user \"%s\".\n"), + PQdb(pset.db), PQuser(pset.db)); } if (o_conn) @@ -1336,7 +1602,7 @@ do_connect(char *dbname, char *user, char *host, char *port) void -connection_warnings(void) +connection_warnings(bool in_startup) { if (!pset.quiet && !pset.notty) { @@ -1362,7 +1628,8 @@ connection_warnings(void) printf(_("%s (%s, server %s)\n"), pset.progname, PG_VERSION, server_version); } - else + /* For version match, only print psql banner on startup. */ + else if (in_startup) printf("%s (%s)\n", pset.progname, PG_VERSION); if (pset.sversion / 100 != client_ver / 100) @@ -1396,7 +1663,7 @@ printSSLInfo(void) return; /* no SSL */ SSL_get_cipher_bits(ssl, &sslbits); - printf(_("SSL connection (cipher: %s, bits: %i)\n"), + printf(_("SSL connection (cipher: %s, bits: %d)\n"), SSL_get_cipher(ssl), sslbits); #else @@ -1482,11 +1749,11 @@ UnsyncVariables(void) * If you do not specify a filename, the current query buffer will be copied * into a temporary one. */ - static bool -editFile(const char *fname) +editFile(const char *fname, int lineno) { const char *editorName; + const char *editor_lineno_arg = NULL; char *sys; int result; @@ -1501,6 +1768,29 @@ editFile(const char *fname) if (!editorName) editorName = DEFAULT_EDITOR; + /* Get line number argument, if we need it. */ + if (lineno > 0) + { + editor_lineno_arg = getenv("PSQL_EDITOR_LINENUMBER_ARG"); +#ifdef DEFAULT_EDITOR_LINENUMBER_ARG + if (!editor_lineno_arg) + editor_lineno_arg = DEFAULT_EDITOR_LINENUMBER_ARG; +#endif + if (!editor_lineno_arg) + { + psql_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number\n"); + return false; + } + } + + /* Allocate sufficient memory for command line. */ + if (lineno > 0) + sys = pg_malloc(strlen(editorName) + + strlen(editor_lineno_arg) + 10 /* for integer */ + + 1 + strlen(fname) + 10 + 1); + else + sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1); + /* * On Unix the EDITOR value should *not* be quoted, since it might include * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it @@ -1508,11 +1798,20 @@ editFile(const char *fname) * severe brain damage in their command shell plus the fact that standard * program paths include spaces. */ - sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1); #ifndef WIN32 - sprintf(sys, "exec %s '%s'", editorName, fname); + if (lineno > 0) + sprintf(sys, "exec %s %s%d '%s'", + editorName, editor_lineno_arg, lineno, fname); + else + sprintf(sys, "exec %s '%s'", + editorName, fname); #else - sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname); + if (lineno > 0) + sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, + editorName, editor_lineno_arg, lineno, fname); + else + sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, + editorName, fname); #endif result = system(sys); if (result == -1) @@ -1527,7 +1826,8 @@ editFile(const char *fname) /* call this one */ static bool -do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited) +do_edit(const char *filename_arg, PQExpBuffer query_buf, + int lineno, bool *edited) { char fnametmp[MAXPGPATH]; FILE *stream = NULL; @@ -1555,7 +1855,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited) ret = GetTempPath(MAXPGPATH, tmpdir); if (ret == 0 || ret > MAXPGPATH) { - psql_error("cannot locate temporary directory: %s", + psql_error("could not locate temporary directory: %s\n", !ret ? strerror(errno) : ""); return false; } @@ -1619,7 +1919,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited) /* call editor */ if (!error) - error = !editFile(fname); + error = !editFile(fname, lineno); if (!error && stat(fname, &after) != 0) { @@ -1677,22 +1977,49 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited) * process_file * * Read commands from filename and then them to the main processing loop - * Handler for \i, but can be used for other things as well. Returns + * Handler for \i and \ir, but can be used for other things as well. Returns * MainLoop() error code. + * + * If use_relative_path is true and filename is not an absolute path, then open + * the file from where the currently processed file (if any) is located. */ int -process_file(char *filename, bool single_txn) +process_file(char *filename, bool single_txn, bool use_relative_path) { FILE *fd; int result; char *oldfilename; + char relpath[MAXPGPATH]; PGresult *res; if (!filename) return EXIT_FAILURE; - canonicalize_path(filename); - fd = fopen(filename, PG_BINARY_R); + if (strcmp(filename, "-") != 0) + { + canonicalize_path(filename); + + /* + * If we were asked to resolve the pathname relative to the location + * of the currently executing script, and there is one, and this is + * a relative pathname, then prepend all but the last pathname + * component of the current script to this pathname. + */ + if (use_relative_path && pset.inputfile && !is_absolute_path(filename) + && !has_drive_prefix(filename)) + { + snprintf(relpath, MAXPGPATH, "%s", pset.inputfile); + get_parent_directory(relpath); + join_path_components(relpath, relpath, filename); + canonicalize_path(relpath); + + filename = relpath; + } + + fd = fopen(filename, PG_BINARY_R); + } + else + fd = stdin; if (!fd) { @@ -1704,12 +2031,39 @@ process_file(char *filename, bool single_txn) pset.inputfile = filename; if (single_txn) - res = PSQLexec("BEGIN", false); + { + if ((res = PSQLexec("BEGIN", false)) == NULL) + { + if (pset.on_error_stop) + { + result = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } + result = MainLoop(fd); + if (single_txn) - res = PSQLexec("COMMIT", false); + { + if ((res = PSQLexec("COMMIT", false)) == NULL) + { + if (pset.on_error_stop) + { + result = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } + +error: + if (fd != stdin) + fclose(fd); - fclose(fd); pset.inputfile = oldfilename; return result; } @@ -1788,6 +2142,28 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) printf(_("Output format is %s.\n"), _align2string(popt->topt.format)); } + /* set table line style */ + else if (strcmp(param, "linestyle") == 0) + { + if (!value) + ; + else if (pg_strncasecmp("ascii", value, vallen) == 0) + popt->topt.line_style = &pg_asciiformat; + else if (pg_strncasecmp("old-ascii", value, vallen) == 0) + popt->topt.line_style = &pg_asciiformat_old; + else if (pg_strncasecmp("unicode", value, vallen) == 0) + popt->topt.line_style = &pg_utf8format; + else + { + psql_error("\\pset: allowed line styles are ascii, old-ascii, unicode\n"); + return false; + } + + if (!quiet) + printf(_("Line style is %s.\n"), + get_line_style(&popt->topt)->name); + } + /* set border style/width */ else if (strcmp(param, "border") == 0) { @@ -2102,6 +2478,69 @@ get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf) return result; } +/* + * If the given argument of \ef 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.) + * + * 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) +{ + char *c; + int lineno; + + if (!func || func[0] == '\0') + return -1; + + c = func + strlen(func) - 1; + + /* + * This business of parsing backwards is dangerous as can be in a + * multibyte environment: there is no reason to believe that we are + * looking at the first byte of a character, nor are we necessarily + * working in a "safe" encoding. Fortunately the bitpatterns we are + * looking for are unlikely to occur as non-first bytes, but beware of + * trying to expand the set of cases that can be recognized. We must + * guard the macros by using isascii() first, too. + */ + + /* skip trailing whitespace */ + while (c > func && 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)) + return -1; + + /* find start of digit string */ + while (c > func && 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) || + !(isspace((unsigned char) *c) || *c == ')')) + return -1; + + /* parse digit string */ + c++; + lineno = atoi(c); + if (lineno < 1) + { + psql_error("invalid line number: %s\n", c); + return 0; + } + + /* strip digit string from func */ + *c = '\0'; + + return lineno; +} + /* * Report just the primary error; this is to avoid cluttering the output * with, for instance, a redisplay of the internally generated query