]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/command.c
Add missing newlines at end of error messages
[postgresql] / src / bin / psql / command.c
index affdffce1d9476d00905037cb1b9bd2118ca273c..3608b725963e56fa9a228abd5500b9c0177e5b11 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2010, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2011, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.222 2010/07/20 03:54:19 rhaas 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,20 +295,26 @@ exec_command(const char *cmd,
                        free(opt);
        }
 
-       /* \conninfo -- display information about the current connection        */
+       /* \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)
-                       printf("You are not connected.\n");
-               else if (host)
-                       printf("You are connected to database \"%s\" on host \"%s\" at port \"%s\" as user \"%s\".\n",
-                                  db, host, PQport(pset.db), PQuser(pset.db));
+               if (db == NULL)
+                       printf(_("You are currently not connected to a database.\n"));
                else
-                       printf("You are connected to database \"%s\" via local socket as user \"%s\".\n",
-                                  db, PQuser(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 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 */
@@ -362,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);
@@ -409,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;
@@ -425,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':
@@ -476,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;
                }
@@ -491,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)
        {
@@ -504,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);
                }
        }
 
@@ -524,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");
@@ -536,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,
@@ -557,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);
                }
@@ -565,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"));
@@ -693,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);
@@ -706,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);
                }
        }
@@ -1008,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)
        {
@@ -1018,7 +1229,6 @@ exec_command(const char *cmd,
                free(opt);
        }
 
-
        /* \T -- define html <table ...> attributes */
        else if (strcmp(cmd, "T") == 0)
        {
@@ -1282,7 +1492,7 @@ do_connect(char *dbname, char *user, char *host, char *port)
 
        while (true)
        {
-#define PARAMS_ARRAY_SIZE      7
+#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));
 
@@ -1298,8 +1508,10 @@ do_connect(char *dbname, char *user, char *host, char *port)
                values[4] = dbname;
                keywords[5] = "fallback_application_name";
                values[5] = pset.progname;
-               keywords[6] = NULL;
-               values[6] = NULL;
+               keywords[6] = "client_encoding";
+               values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
+               keywords[7] = NULL;
+               values[7] = NULL;
 
                n_conn = PQconnectdbParams(keywords, values, true);
 
@@ -1363,18 +1575,24 @@ do_connect(char *dbname, char *user, char *host, char *port)
        /* 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)
@@ -1445,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
 
@@ -1531,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;
 
@@ -1550,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
@@ -1557,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)
@@ -1576,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;
@@ -1604,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;
                }
@@ -1668,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)
        {
@@ -1726,15 +1977,19 @@ 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)
@@ -1743,6 +1998,24 @@ process_file(char *filename, bool single_txn)
        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
@@ -1762,7 +2035,10 @@ process_file(char *filename, bool single_txn)
                if ((res = PSQLexec("BEGIN", false)) == NULL)
                {
                        if (pset.on_error_stop)
-                               return EXIT_USER;
+                       {
+                               result = EXIT_USER;
+                               goto error;
+                       }
                }
                else
                        PQclear(res);
@@ -1775,13 +2051,19 @@ process_file(char *filename, bool single_txn)
                if ((res = PSQLexec("COMMIT", false)) == NULL)
                {
                        if (pset.on_error_stop)
-                               return EXIT_USER;
+                       {
+                               result = EXIT_USER;
+                               goto error;
+                       }
                }
                else
                        PQclear(res);
        }
 
-       fclose(fd);
+error:
+       if (fd != stdin)
+               fclose(fd);
+
        pset.inputfile = oldfilename;
        return result;
 }
@@ -2196,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 <ctype.h> 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