On Sun, 2006-03-12 at 19:59 +0200, Volkan YAZICI wrote: > I've written do_connect() and \c handling part from scratch in the > attached patch.
Attached is a revised version of this patch. I rewrote most of the code, because the existing stuff was in pretty bad style IMHO. I haven't updated the documentation yet, but I'll do that if no one objects to this version of the patch. One question about behavior: in the attached patch, omitting an argument to \connect or specifying "-" are treated equivalently -- the value for that parameter from the previous connection is used, otherwise NULL (for the libpq default). Is this what people want? (One possible complaint is that once you specify a parameter, you can't get back to the libpq default without specifying it explicitly.) -Neil
============================================================ *** src/bin/psql/command.c b1305764d53ec9138de3c55ec3475a4d8a3190d9 --- src/bin/psql/command.c e570b6eb0b6ade25461b88aca5bbef2e75d27c00 *************** *** 55,61 **** PsqlScanState scan_state, PQExpBuffer query_buf); static bool do_edit(const char *filename_arg, PQExpBuffer query_buf); ! static bool do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_port); static bool do_shell(const char *command); --- 55,61 ---- PsqlScanState scan_state, PQExpBuffer query_buf); 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); *************** *** 154,159 **** --- 154,192 ---- } /* + * Read and interpret an argument to the \connect slash command. + */ + static char * + read_connect_arg(PsqlScanState scan_state) + { + char *result; + char quote; + + /* + * Ideally we should treat the arguments as SQL identifiers. But + * for backwards compatibility with 7.2 and older pg_dump files, + * we have to take unquoted arguments verbatim (don't downcase + * them). For now, double-quoted arguments may be stripped of + * double quotes (as if SQL identifiers). By 7.4 or so, pg_dump + * files can be expected to double-quote all mixed-case \connect + * arguments, and then we can get rid of OT_SQLIDHACK. + */ + result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true); + + if (!result) + return NULL; + + if (quote) + return result; + + if (*result == '\0' || strcmp(result, "-") == 0) + return NULL; + + return result; + } + + + /* * Subroutine to actually try to execute a backslash command. */ static backslashResult *************** *** 188,204 **** free(opt); } ! /*---------- ! * \c or \connect -- connect to new database or as different user, ! * and/or new host and/or port * ! * \c foo bar [-] [-] connect to db "foo" as user "bar" on current host and port ! * \c foo [-] [-] [-] connect to db "foo" as current user on current host and port ! * \c - bar [-] [-] connect to current db as user "bar" on current host and port ! * \c - - host.domain.tld [-] connect to default db as default user on host.domain.tld on default port ! * \c - - - 5555 connect to default db as default user on default host at port 5555 ! * \c connect to default db as default user ! *---------- */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { --- 221,242 ---- free(opt); } ! /* ! * \c or \connect -- connect to database using the specified parameters. * ! * \c dbname user host port ! * ! * If any of these parameters are omitted or specified as '-', the ! * current value of the parameter will be used instead. If the ! * parameter has no current value, the default value for that ! * parameter will be used. Some examples: ! * ! * \c - - hst Connect to current database on current port of ! * host "hst" as current user. ! * \c - usr - prt Connect to current database on "prt" port of current ! * host as user "usr". ! * \c dbs Connect to "dbs" database on current port of current ! * host as current user. */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { *************** *** 206,233 **** *opt2, *opt3, *opt4; - char opt1q, - opt2q, - opt3q, - opt4q; ! /* ! * Ideally we should treat the arguments as SQL identifiers. But for ! * backwards compatibility with 7.2 and older pg_dump files, we have ! * to take unquoted arguments verbatim (don't downcase them). For now, ! * double-quoted arguments may be stripped of double quotes (as if SQL ! * identifiers). By 7.4 or so, pg_dump files can be expected to ! * double-quote all mixed-case \connect arguments, and then we can get ! * rid of OT_SQLIDHACK. ! */ ! opt1 = psql_scan_slash_option(scan_state, ! OT_SQLIDHACK, &opt1q, true); ! opt2 = psql_scan_slash_option(scan_state, ! OT_SQLIDHACK, &opt2q, true); ! opt3 = psql_scan_slash_option(scan_state, ! OT_SQLIDHACK, &opt3q, true); ! opt4 = psql_scan_slash_option(scan_state, ! OT_SQLIDHACK, &opt4q, true); if (opt4) /* gave port */ --- 244,254 ---- *opt2, *opt3, *opt4; ! opt1 = read_connect_arg(scan_state); ! opt2 = read_connect_arg(scan_state); ! opt3 = read_connect_arg(scan_state); ! opt4 = read_connect_arg(scan_state); success = do_connect(opt1, opt2, opt3, opt4); *************** *** 229,271 **** opt4 = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &opt4q, true); ! if (opt4) ! /* gave port */ ! success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || ! strcmp(opt1, "") == 0) ? "" : opt1, ! !opt2q && (strcmp(opt2, "-") == 0 || ! strcmp(opt2, "") == 0) ? "" : opt2, ! !opt3q && (strcmp(opt3, "-") == 0 || ! strcmp(opt3, "") == 0) ? "" : opt3, ! !opt3q && (strcmp(opt3, "-") == 0 || ! strcmp(opt3, "") == 0) ? "" : opt3); ! if (opt3) ! /* gave host */ ! success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || ! strcmp(opt1, "") == 0) ? "" : opt1, ! !opt2q && (strcmp(opt2, "-") == 0 || ! strcmp(opt2, "") == 0) ? "" : opt2, ! !opt3q && (strcmp(opt3, "-") == 0 || ! strcmp(opt3, "") == 0) ? "" : opt3, ! NULL); ! if (opt2) ! /* gave username */ ! success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || ! strcmp(opt1, "") == 0) ? "" : opt1, ! !opt2q && (strcmp(opt2, "-") == 0 || ! strcmp(opt2, "") == 0) ? "" : opt2, ! NULL, ! NULL); ! else if (opt1) ! /* gave database name */ ! success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || ! strcmp(opt1, "") == 0) ? "" : opt1, ! "", ! NULL, ! NULL); ! else ! /* connect to default db as default user */ ! success = do_connect(NULL, NULL, NULL, NULL); free(opt1); free(opt2); --- 250,256 ---- opt4 = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &opt4q, true); ! success = do_connect(opt1, opt2, opt3, opt4); free(opt1); free(opt2); *************** *** 985,1001 **** return status; } ! ! ! /* do_connect ! * -- handler for \connect ! * ! * Connects to a database (new_dbname) as a certain user (new_user). ! * The new user can be NULL. A db name of "-" is the same as the old one. ! * (That is, the one currently in pset. But pset.db can also be NULL. A NULL ! * dbname is handled by libpq.) ! * Returns true if all ok, false if the new connection couldn't be established. ! * The old connection will be kept if the session is interactive. */ static bool do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_port) --- 970,979 ---- return status; } ! /* ! * Ask the user for a password; 'username' is the username the ! * password is for, if one has been explicitly specified. Returns a ! * malloc'd string. */ static char * prompt_for_password(const char *username) *************** *** 997,1015 **** * Returns true if all ok, false if the new connection couldn't be established. * The old connection will be kept if the session is interactive. */ ! static bool ! do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_port) { ! PGconn *oldconn = pset.db; ! const char *dbparam = NULL; ! const char *userparam = NULL; ! const char *hostparam = NULL; ! const char *portparam = NULL; ! const char *pwparam = NULL; ! char *password_prompt = NULL; ! char *prompted_password = NULL; ! bool need_pass; ! bool success = false; /* Delete variables (in case we fail before setting them anew) */ UnsyncVariables(); --- 975,984 ---- * Returns true if all ok, false if the new connection couldn't be established. * The old connection will be kept if the session is interactive. */ ! static char * ! prompt_for_password(const char *username) { ! char *result; if (username == NULL) result = simple_prompt("Password: ", 100, false); *************** *** 1011,1051 **** bool need_pass; bool success = false; ! /* Delete variables (in case we fail before setting them anew) */ ! UnsyncVariables(); ! /* If dbname is "" then use old name, else new one (even if NULL) */ ! if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0) ! dbparam = PQdb(oldconn); ! else ! dbparam = new_dbname; ! /* If user is "" then use the old one */ ! if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0) ! userparam = PQuser(oldconn); ! else ! userparam = new_user; ! /* If host is "" then use the old one */ ! if (new_host && PQhost(oldconn) && strcmp(new_host, "") == 0) ! hostparam = PQhost(oldconn); ! else ! hostparam = new_host; ! /* If port is "" then use the old one */ ! if (new_port && PQport(oldconn) && strcmp(new_port, "") == 0) ! portparam = PQport(oldconn); ! else ! portparam = new_port; ! if (userparam == NULL) ! password_prompt = strdup("Password: "); ! else ! { ! password_prompt = malloc(strlen(_("Password for user %s: ")) - 2 + ! strlen(userparam) + 1); ! sprintf(password_prompt, _("Password for user %s: "), userparam); ! } /* need to prompt for password? */ if (pset.getPassword) --- 980,1011 ---- bool need_pass; bool success = false; ! if (username == NULL) ! result = simple_prompt("Password: ", 100, false); ! else ! { ! char *prompt_text; ! prompt_text = malloc(strlen(username) + 32); ! sprintf(prompt_text, "Password for user \"%s\": ", username); ! result = simple_prompt(prompt_text, 100, false); ! free(prompt_text); ! } ! return result; ! } ! static bool ! param_is_newly_set(const char *old_val, const char *new_val) ! { ! if (new_val == NULL) ! return false; ! if (old_val == NULL || strcmp(old_val, new_val) != 0) ! return true; ! return false; ! } /* * do_connect -- handler for \connect *************** *** 1047,1091 **** sprintf(password_prompt, _("Password for user %s: "), userparam); } ! /* need to prompt for password? */ ! if (pset.getPassword) ! pwparam = prompted_password = simple_prompt(password_prompt, 100, false); /* ! * Use old password (if any) if no new one given and we are reconnecting ! * as same user */ ! if (!pwparam && oldconn && PQuser(oldconn) && userparam && ! strcmp(PQuser(oldconn), userparam) == 0) ! pwparam = PQpass(oldconn); ! do { ! need_pass = false; ! pset.db = PQsetdbLogin(hostparam, portparam, ! NULL, NULL, dbparam, userparam, pwparam); ! if (PQstatus(pset.db) == CONNECTION_BAD && ! strcmp(PQerrorMessage(pset.db), PQnoPasswordSupplied) == 0 && ! !feof(stdin)) { ! PQfinish(pset.db); ! need_pass = true; ! free(prompted_password); ! prompted_password = NULL; ! pwparam = prompted_password = simple_prompt(password_prompt, 100, false); } - } while (need_pass); ! free(prompted_password); ! free(password_prompt); ! ! /* ! * If connection failed, try at least keep the old one. That's probably ! * more convenient than just kicking you out of the program. ! */ ! if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD) ! { if (pset.cur_cmd_interactive) { psql_error("%s", PQerrorMessage(pset.db)); --- 1007,1087 ---- sprintf(password_prompt, _("Password for user %s: "), userparam); } ! /* ! * do_connect -- handler for \connect ! * ! * 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. ! * ! * In interactive mode, if connection fails with the given parameters, ! * the old connection will be kept. ! */ ! static bool ! do_connect(char *dbname, char *user, char *host, char *port) ! { ! PGconn *o_conn = pset.db, ! *n_conn; ! char *password = NULL; + if (!dbname) + dbname = PQdb(o_conn); + if (!user) + user = PQuser(o_conn); + if (!host) + host = PQhost(o_conn); + if (!port) + port = PQport(o_conn); + /* ! * If the user asked to be prompted for a password, ask for one ! * now. If not, use the password from the old connection, provided ! * the username has not changed. Otherwise, try to connect without ! * a password first, and then ask for a password if we got the ! * appropriate error message. ! * ! * XXX: this behavior is broken. It leads to spurious connection ! * attempts in the postmaster's log, and doing a string comparison ! * against the returned error message is pretty fragile. */ ! if (pset.getPassword) ! { ! password = prompt_for_password(user); ! } ! else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0) ! { ! password = strdup(PQpass(o_conn)); ! } ! while (true) { ! n_conn = PQsetdbLogin(host, port, NULL, NULL, ! dbname, user, password); ! /* We can immediately discard the password -- no longer needed */ ! if (password) ! free(password); ! ! if (PQstatus(n_conn) == CONNECTION_OK) ! break; ! ! /* ! * Connection attempt failed; either retry the connection ! * attempt with a new password, or give up. ! */ ! if (strcmp(PQerrorMessage(n_conn), PQnoPasswordSupplied) == 0) { ! PQfinish(n_conn); ! password = prompt_for_password(user); ! continue; } ! /* ! * Failed to connect to the database. In interactive mode, ! * keep the previous connection to the DB; in scripting mode, ! * close our previous connection as well. ! */ if (pset.cur_cmd_interactive) { psql_error("%s", PQerrorMessage(n_conn)); *************** *** 1088,1102 **** { if (pset.cur_cmd_interactive) { ! psql_error("%s", PQerrorMessage(pset.db)); ! PQfinish(pset.db); ! if (oldconn) ! { ! fputs(_("Previous connection kept\n"), stderr); ! pset.db = oldconn; ! } ! else ! pset.db = NULL; } else { --- 1084,1094 ---- { if (pset.cur_cmd_interactive) { ! psql_error("%s", PQerrorMessage(n_conn)); ! ! /* pset.db is left unmodified */ ! if (o_conn) ! fputs(_("Previous connection kept.\n"), stderr); } else { *************** *** 1100,1113 **** } else { ! /* ! * we don't want unpredictable things to happen in scripting mode ! */ ! psql_error("\\connect: %s", PQerrorMessage(pset.db)); ! PQfinish(pset.db); ! if (oldconn) ! PQfinish(oldconn); ! pset.db = NULL; } } else --- 1092,1103 ---- } else { ! psql_error("\\connect: %s", PQerrorMessage(n_conn)); ! if (o_conn) ! { ! PQfinish(o_conn); ! pset.db = NULL; ! } } PQfinish(n_conn); *************** *** 1109,1138 **** PQfinish(oldconn); pset.db = NULL; } } ! else { ! if (!QUIET()) ! { ! if ((hostparam == new_host) && (portparam == new_port)) /* no new host or port */ ! { ! if (userparam != new_user) /* no new user */ ! printf(_("You are now connected to database \"%s\".\n"), dbparam); ! else if (dbparam != new_dbname) /* no new db */ ! printf(_("You are now connected as new user \"%s\".\n"), new_user); ! else ! /* both new */ ! printf(_("You are now connected to database \"%s\" as user \"%s\".\n"), ! PQdb(pset.db), PQuser(pset.db)); ! } ! else /* At least one of host and port are new */ ! { ! printf( ! _("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port %s.\n"), ! PQdb(pset.db), PQuser(pset.db), PQhost(pset.db), ! PQport(pset.db)); ! } ! } if (oldconn) PQfinish(oldconn); --- 1099,1121 ---- PQfinish(oldconn); pset.db = NULL; } + + PQfinish(n_conn); + return false; } ! ! /* ! * Replace the old connection with the new one, and update ! * connection-dependent variables. ! */ ! PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL); ! pset.db = n_conn; ! SyncVariables(); ! ! /* Tell the user about the new connection */ ! if (!QUIET()) { ! printf(_("You are now connected to database \"%s\""), PQdb(pset.db)); if (param_is_newly_set(PQuser(o_conn), PQuser(pset.db))) printf(_(" as user \"%s\""), PQuser(pset.db)); *************** *** 1134,1151 **** } } ! if (oldconn) ! PQfinish(oldconn); ! success = true; ! } ! PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL); ! /* Update variables */ ! SyncVariables(); ! return success; } --- 1117,1137 ---- } } ! if (param_is_newly_set(PQuser(o_conn), PQuser(pset.db))) ! printf(_(" as user \"%s\""), PQuser(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)); ! printf(".\n"); ! } ! if (o_conn) ! PQfinish(o_conn); ! return true; }
---------------------------(end of broadcast)--------------------------- TIP 3: Have you checked our extensive FAQ? http://www.postgresql.org/docs/faq