/* * 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 * MainLoop() error code. */ int process_file(char *filename, bool single_txn) { FILE *fd; int result; char *oldfilename; PGresult *res; if (!filename) return EXIT_FAILURE; if (strcmp(filename, "-") != 0) { canonicalize_path(filename); fd = fopen(filename, PG_BINARY_R); } else fd = stdin; if (!fd) { psql_error("%s: %s\n", filename, strerror(errno)); return EXIT_FAILURE; } oldfilename = pset.inputfile; pset.inputfile = filename; if (single_txn) { if ((res = PSQLexec("BEGIN", false)) == NULL) { if (pset.on_error_stop) return EXIT_USER; } else PQclear(res); } result = MainLoop(fd); if (single_txn) { if ((res = PSQLexec("COMMIT", false)) == NULL) { if (pset.on_error_stop) return EXIT_USER; } else PQclear(res); } fclose(fd); pset.inputfile = oldfilename; return result; }
/* * do_lo_list() * * Show all large objects in database with comments */ bool do_lo_list(void) { PGresult *res; char buf[1024]; printQueryOpt myopt = pset.popt; snprintf(buf, sizeof(buf), "SELECT loid as \"ID\",\n" " pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n" "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n" "ORDER BY 1", _("Description")); res = PSQLexec(buf, false); if (!res) return false; myopt.topt.tuples_only = false; myopt.nullPrint = NULL; myopt.title = _("Large objects"); printQuery(res, &myopt, pset.queryFout, pset.logfile); PQclear(res); return true; }
/* * do_lo_import() * * Copy large object from file to database */ bool do_lo_import(const char *filename_arg, const char *comment_arg) { PGresult *res; Oid loid; char oidbuf[32]; bool own_transaction; if (!start_lo_xact("\\lo_import", &own_transaction)) return false; SetCancelConn(); loid = lo_import(pset.db, filename_arg); ResetCancelConn(); if (loid == InvalidOid) { psql_error("%s", PQerrorMessage(pset.db)); return fail_lo_xact("\\lo_import", own_transaction); } /* insert description if given */ if (comment_arg) { char *cmdbuf; char *bufptr; size_t slen = strlen(comment_arg); cmdbuf = malloc(slen * 2 + 256); if (!cmdbuf) return fail_lo_xact("\\lo_import", own_transaction); sprintf(cmdbuf, "COMMENT ON LARGE OBJECT %u IS '", loid); bufptr = cmdbuf + strlen(cmdbuf); bufptr += PQescapeStringConn(pset.db, bufptr, comment_arg, slen, NULL); strcpy(bufptr, "'"); if (!(res = PSQLexec(cmdbuf, false))) { free(cmdbuf); return fail_lo_xact("\\lo_import", own_transaction); } PQclear(res); free(cmdbuf); } if (!finish_lo_xact("\\lo_import", own_transaction)) return false; print_lo_result("lo_import %u", loid); sprintf(oidbuf, "%u", loid); SetVariable(pset.vars, "LASTOID", oidbuf); return true; }
/* * Clean up after a successful LO operation */ static bool finish_lo_xact(const char *operation, bool own_transaction) { PGresult *res; if (own_transaction && pset.autocommit) { /* close out our own xact */ if (!(res = PSQLexec("COMMIT", false))) { res = PSQLexec("ROLLBACK", false); PQclear(res); return false; } PQclear(res); } return true; }
/* * Clean up after a failed LO operation */ static bool fail_lo_xact(const char *operation, bool own_transaction) { PGresult *res; if (own_transaction && pset.autocommit) { /* close out our own xact */ res = PSQLexec("ROLLBACK", false); PQclear(res); } return false; /* always */ }
/* * do_lo_list() * * Show all large objects in database with comments */ bool do_lo_list(void) { PGresult *res; char buf[1024]; printQueryOpt myopt = pset.popt; if (pset.sversion >= 90000) { snprintf(buf, sizeof(buf), "SELECT oid as \"%s\",\n" " pg_catalog.pg_get_userbyid(lomowner) as \"%s\",\n" " pg_catalog.obj_description(oid, 'pg_largeobject') as \"%s\"\n" " FROM pg_catalog.pg_largeobject_metadata " " ORDER BY oid", gettext_noop("ID"), gettext_noop("Owner"), gettext_noop("Description")); } else { snprintf(buf, sizeof(buf), "SELECT loid as \"%s\",\n" " pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n" "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n" "ORDER BY 1", gettext_noop("ID"), gettext_noop("Description")); } res = PSQLexec(buf, false); if (!res) return false; myopt.topt.tuples_only = false; myopt.nullPrint = NULL; myopt.title = _("Large objects"); myopt.translate_header = true; printQuery(res, &myopt, pset.queryFout, pset.logfile); PQclear(res); return true; }
/* * Prepare to do a large-object operation. We *must* be inside a transaction * block for all these operations, so start one if needed. * * Returns TRUE if okay, FALSE if failed. *own_transaction is set to indicate * if we started our own transaction or not. */ static bool start_lo_xact(const char *operation, bool *own_transaction) { PGTransactionStatusType tstatus; PGresult *res; *own_transaction = false; if (!pset.db) { psql_error("%s: not connected to a database\n", operation); return false; } tstatus = PQtransactionStatus(pset.db); switch (tstatus) { case PQTRANS_IDLE: /* need to start our own xact */ if (!(res = PSQLexec("BEGIN", false))) return false; PQclear(res); *own_transaction = true; break; case PQTRANS_INTRANS: /* use the existing xact */ break; case PQTRANS_INERROR: psql_error("%s: current transaction is aborted\n", operation); return false; default: psql_error("%s: unknown transaction status\n", operation); return false; } return true; }
/* * Execute a \copy command (frontend copy). We have to open a file, then * submit a COPY query to the backend and either feed it data from the * file or route its response into the file. */ bool do_copy(const char *args) { struct pqbuf query; FILE* copystream; struct copy_options *options; PGresult* result; bool success; struct stat st; /* parse options */ options = parse_slash_copy(args); if (!options) return false; /* prepare to read or write the target file */ if (options->file) canonicalize_path(options->file); if (options->from) { if (options->file) copystream = fopen(options->file, PG_BINARY_R); else if (!options->psql_inout) copystream = pset.cur_cmd_source; else copystream = stdin; } else { if (options->file) copystream = fopen(options->file, PG_BINARY_W); else if (!options->psql_inout) copystream = pset.queryFout; else copystream = stdout; } if (!copystream) { psql_error("%s: %s\n", options->file, strerror(errno)); free_copy_options(options); return false; } /* make sure the specified file is not a directory */ fstat(fileno(copystream), &st); if (S_ISDIR(st.st_mode)) { fclose(copystream); psql_error("%s: cannot copy from/to a directory\n", options->file); free_copy_options(options); return false; } /* build the command we will send to the backend */ init_pqbuf(&query); print_pqbuf(&query, "COPY "); append_pqbuf_str(&query, options->before_tofrom); if (options->from) append_pqbuf(&query, " FROM STDIN "); else append_pqbuf(&query, " TO STDOUT "); if (options->after_tofrom) append_pqbuf_str(&query, options->after_tofrom); result = PSQLexec(query.data, true); term_pqbuf(&query); switch (PQresultStatus(result)) { case PGRES_COPY_OUT: SetCancelConn(); success = handleCopyOut(pset.db, copystream); ResetCancelConn(); break; case PGRES_COPY_IN: SetCancelConn(); success = handleCopyIn(pset.db, copystream, PQbinaryTuples(result)); ResetCancelConn(); break; case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: success = false; psql_error("\\copy: %s", PQerrorMessage(pset.db)); break; default: success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); break; } PQclear(result); /* * Make sure we have pumped libpq dry of results; else it may still be in * ASYNC_BUSY state, leading to false readings in, eg, get_prompt(). */ while ((result = PQgetResult(pset.db)) != NULL) { success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); /* if still in COPY IN state, try to get out of it */ if (PQresultStatus(result) == PGRES_COPY_IN) PQputCopyEnd(pset.db, _("trying to exit copy mode")); PQclear(result); } if (options->file != NULL) { if (fclose(copystream) != 0) { psql_error("%s: %s\n", options->file, strerror(errno)); success = false; } } free_copy_options(options); return success; }
/* * Subroutine to actually try to execute a backslash command. */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, PQExpBuffer query_buf) { bool success = true; /* indicate here if the command ran ok or * failed */ backslashResult status = PSQL_CMD_SKIP_LINE; /* * \a -- toggle field alignment This makes little sense but we keep it * around. */ if (strcmp(cmd, "a") == 0) { if (pset.popt.topt.format != PRINT_ALIGNED) success = do_pset("format", "aligned", &pset.popt, pset.quiet); else success = do_pset("format", "unaligned", &pset.popt, pset.quiet); } /* \C -- override table title (formerly change HTML caption) */ else if (strcmp(cmd, "C") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("title", opt, &pset.popt, pset.quiet); 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) { char *opt1, *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); free(opt1); free(opt2); free(opt3); free(opt4); } /* \cd */ else if (strcmp(cmd, "cd") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); char *dir; if (opt) dir = opt; else { #ifndef WIN32 struct passwd *pw; pw = getpwuid(geteuid()); if (!pw) { psql_error("could not get home directory: %s\n", strerror(errno)); exit(EXIT_FAILURE); } dir = pw->pw_dir; #else /* WIN32 */ /* * On Windows, 'cd' without arguments prints the current * directory, so if someone wants to code this here instead... */ dir = "/"; #endif /* WIN32 */ } if (chdir(dir) == -1) { psql_error("\\%s: could not change directory to \"%s\": %s\n", cmd, dir, strerror(errno)); success = false; } if (pset.dirname) free(pset.dirname); pset.dirname = pg_strdup(dir); canonicalize_path(pset.dirname); if (opt) 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 not connected.\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) { /* Default fetch-it-all-and-print mode */ instr_time before, after; char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); if (pset.timing) INSTR_TIME_SET_CURRENT(before); success = do_copy(opt); if (pset.timing && success) { INSTR_TIME_SET_CURRENT(after); INSTR_TIME_SUBTRACT(after, before); printf(_("Time: %.3f ms\n"), INSTR_TIME_GET_MILLISEC(after)); } free(opt); } /* \copyright */ else if (strcmp(cmd, "copyright") == 0) print_copyright(); /* \d* commands */ else if (cmd[0] == 'd') { char *pattern; bool show_verbose, show_system; /* We don't do SQLID reduction on the pattern yet */ pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); show_verbose = strchr(cmd, '+') ? true : false; show_system = strchr(cmd, 'S') ? true : false; switch (cmd[1]) { case '\0': case '+': case 'S': /* GPDB: This is a change from old behavior: We used to show just system tables */ if (pattern) success = describeTableDetails(pattern, show_verbose, show_system); else /* standard listing of interesting things */ success = listTables("tvsxr", NULL, show_verbose, show_system); break; case 'a': success = describeAggregates(pattern, show_verbose, show_system); break; case 'b': success = describeTablespaces(pattern, show_verbose); break; case 'c': success = listConversions(pattern, show_system); break; case 'C': success = listCasts(pattern); break; case 'd': if (strncmp(cmd, "ddp", 3) == 0) success = listDefaultACLs(pattern); else success = objectDescription(pattern, show_system); break; case 'D': success = listDomains(pattern, show_system); break; case 'f': /* function subsystem */ switch (cmd[2]) { case '\0': case '+': case 'S': case 'a': case 'n': case 't': case 'w': success = describeFunctions(&cmd[2], pattern, show_verbose, show_system); break; default: status = PSQL_CMD_UNKNOWN; break; } break; case 'g': /* no longer distinct from \du */ success = describeRoles(pattern, show_verbose); break; case 'l': success = do_lo_list(); break; case 'n': success = listSchemas(pattern, show_verbose); break; case 'o': success = describeOperators(pattern, show_system); break; case 'p': success = permissionsList(pattern); break; case 'T': success = describeTypes(pattern, show_verbose, show_system); break; case 't': case 'v': case 'i': case 's': case 'E': /* PostgreSQL use dx for extension, change to dE for foreign table */ /* case 'S': // GPDB: We used to show just system tables for this */ case 'P': /* GPDB: Parent-only tables, no children */ success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'r': if (cmd[2] == 'd' && cmd[3] == 's') { char *pattern2 = NULL; if (pattern) pattern2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = listDbRoleSettings(pattern, pattern2); } else //success = PSQL_CMD_UNKNOWN; /* GPDB uses \dr for foreign tables ? */ success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'u': success = describeRoles(pattern, show_verbose); break; case 'F': /* text search subsystem */ switch (cmd[2]) { case '\0': case '+': success = listTSConfigs(pattern, show_verbose); break; case 'p': success = listTSParsers(pattern, show_verbose); break; case 'd': success = listTSDictionaries(pattern, show_verbose); break; case 't': success = listTSTemplates(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; } if (pattern) free(pattern); } /* * \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) { if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { char *fname; 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); } } /* * \ef -- edit the named function, or present a blank CREATE FUNCTION * template if no argument is given */ else if (strcmp(cmd, "ef") == 0) { if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { char *func; Oid foid = InvalidOid; func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); if (!func) { /* set up an empty command to fill in */ printfPQExpBuffer(query_buf, "CREATE FUNCTION ( )\n" " RETURNS \n" " LANGUAGE \n" " -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n" "AS $function$\n" "\n$function$\n"); } 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, query_buf)) { /* error already reported */ status = PSQL_CMD_ERROR; } if (func) free(func); } if (status != PSQL_CMD_ERROR) { bool edited = false; if (!do_edit(0, query_buf, &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) { char *value; char quoted; bool no_newline = false; bool first = true; FILE *fout; if (strcmp(cmd, "qecho") == 0) fout = pset.queryFout; else fout = stdout; while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, "ed, false))) { if (!quoted && strcmp(value, "-n") == 0) no_newline = true; else { if (first) first = false; else fputc(' ', fout); fputs(value, fout); } free(value); } if (!no_newline) fputs("\n", fout); } /* \encoding -- set/show client side encoding */ else if (strcmp(cmd, "encoding") == 0) { char *encoding = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!encoding) { /* show encoding */ puts(pg_encoding_to_char(pset.encoding)); } else { /* set encoding */ if (PQsetClientEncoding(pset.db, encoding) == -1) psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding); else { /* save encoding info into psql internal data */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); } free(encoding); } } /* \f -- change field separator */ else if (strcmp(cmd, "f") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } /* \g means send query */ else if (strcmp(cmd, "g") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); if (!fname) pset.gfname = NULL; else { expand_tilde(&fname); pset.gfname = pg_strdup(fname); } free(fname); status = PSQL_CMD_SEND; } /* help */ else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) { 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); } /* HTML mode */ else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) { if (pset.popt.topt.format != PRINT_HTML) success = do_pset("format", "html", &pset.popt, pset.quiet); else success = do_pset("format", "aligned", &pset.popt, pset.quiet); } /* \i is include file */ else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { expand_tilde(&fname); success = (process_file(fname, false) == EXIT_SUCCESS); free(fname); } } /* \l is list databases */ else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0) success = listAllDbs(false); else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) success = listAllDbs(true); /* * large object things */ else if (strncmp(cmd, "lo_", 3) == 0) { char *opt1, *opt2; opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); opt2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (strcmp(cmd + 3, "export") == 0) { if (!opt2) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { expand_tilde(&opt2); success = do_lo_export(opt1, opt2); } } else if (strcmp(cmd + 3, "import") == 0) { if (!opt1) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { expand_tilde(&opt1); success = do_lo_import(opt1, opt2); } } else if (strcmp(cmd + 3, "list") == 0) success = do_lo_list(); else if (strcmp(cmd + 3, "unlink") == 0) { if (!opt1) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_lo_unlink(opt1); } else status = PSQL_CMD_UNKNOWN; free(opt1); free(opt2); } /* \o -- set query output */ else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); success = setQFout(fname); free(fname); } /* \p prints the current query buffer */ else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) { if (query_buf && query_buf->len > 0) puts(query_buf->data); else if (!pset.quiet) puts(_("Query buffer is empty.")); fflush(stdout); } /* \password -- set user password */ else if (strcmp(cmd, "password") == 0) { char *pw1; char *pw2; pw1 = simple_prompt("Enter new password: "******"Enter it again: ", 100, false); if (strcmp(pw1, pw2) != 0) { fprintf(stderr, _("Passwords didn't match.\n")); success = false; } else { char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true); char *user; char *encrypted_password; if (opt0) user = opt0; else user = PQuser(pset.db); encrypted_password = PQencryptPassword(pw1, user); if (!encrypted_password) { fprintf(stderr, _("Password encryption failed.\n")); success = false; } else { PQExpBufferData buf; PGresult *res; initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ", fmtId(user)); appendStringLiteralConn(&buf, encrypted_password, pset.db); res = PSQLexec(buf.data, false); termPQExpBuffer(&buf); if (!res) success = false; else PQclear(res); PQfreemem(encrypted_password); } } free(pw1); free(pw2); } /* \prompt -- prompt and set variable */ else if (strcmp(cmd, "prompt") == 0) { char *opt, *prompt_text = NULL; char *arg1, *arg2; arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!arg1) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { char *result; if (arg2) { prompt_text = arg1; opt = arg2; } else opt = arg1; if (!pset.inputfile) result = simple_prompt(prompt_text, 4096, true); else { if (prompt_text) { fputs(prompt_text, stdout); fflush(stdout); } result = gets_fromFile(stdin); } if (!SetVariable(pset.vars, opt, result)) { psql_error("\\%s: error\n", cmd); success = false; } free(result); if (prompt_text) free(prompt_text); free(opt); } } /* \pset -- set printing parameters */ else if (strcmp(cmd, "pset") == 0) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); char *opt1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_pset(opt0, opt1, &pset.popt, pset.quiet); free(opt0); free(opt1); } /* \q or \quit */ else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) status = PSQL_CMD_TERMINATE; /* reset(clear) the buffer */ else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); if (!pset.quiet) puts(_("Query buffer reset (cleared).")); } /* \s save history in a file or show it on the screen */ else if (strcmp(cmd, "s") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); #if defined(WIN32) && !defined(__CYGWIN__) /* * XXX This does not work for all terminal environments or for output * containing non-ASCII characters; see comments in simple_prompt(). */ #define DEVTTY "con" #else #define DEVTTY "/dev/tty" #endif expand_tilde(&fname); /* This scrolls off the screen when using /dev/tty */ success = saveHistory(fname ? fname : DEVTTY, -1, false, false); if (success && !pset.quiet && fname) printf(gettext("Wrote history to file \"%s/%s\".\n"), pset.dirname ? pset.dirname : ".", fname); if (!fname) putchar('\n'); free(fname); } /* \set -- generalized set variable/option command */ else if (strcmp(cmd, "set") == 0) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0) { /* list all variables */ PrintVariables(pset.vars); success = true; } else { /* * Set variable to the concatenation of the arguments. */ char *newval; char *opt; opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); newval = pg_strdup(opt ? opt : ""); free(opt); while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false))) { newval = realloc(newval, strlen(newval) + strlen(opt) + 1); if (!newval) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } strcat(newval, opt); free(opt); } if (!SetVariable(pset.vars, opt0, newval)) { psql_error("\\%s: error\n", cmd); success = false; } free(newval); } free(opt0); } /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } /* \T -- define html <table ...> attributes */ else if (strcmp(cmd, "T") == 0) { char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } /* \timing -- toggle timing of queries */ else if (strcmp(cmd, "timing") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (opt) pset.timing = ParseVariableBool(opt); else pset.timing = !pset.timing; if (!pset.quiet) { if (pset.timing) puts(_("Timing is on.")); else puts(_("Timing is off.")); } free(opt); } /* \unset */ else if (strcmp(cmd, "unset") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else if (!SetVariable(pset.vars, opt, NULL)) { psql_error("\\%s: error\n", cmd); success = false; } free(opt); } /* \w -- write query buffer to file */ else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) { FILE *fd = NULL; bool is_pipe = false; char *fname = NULL; if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { if (fname[0] == '|') { is_pipe = true; fd = popen(&fname[1], "w"); } else { canonicalize_path(fname); fd = fopen(fname, "w"); } if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } } if (fd) { int result; if (query_buf && query_buf->len > 0) fprintf(fd, "%s\n", query_buf->data); if (is_pipe) result = pclose(fd); else result = fclose(fd); if (result == EOF) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } free(fname); } /* \x -- toggle expanded table representation */ else if (strcmp(cmd, "x") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } /* \z -- list table rights (equivalent to \dp) */ else if (strcmp(cmd, "z") == 0) { char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = permissionsList(pattern); if (pattern) free(pattern); } /* \! -- shell escape */ else if (strcmp(cmd, "!") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); success = do_shell(opt); free(opt); } /* \? -- slash command help */ else if (strcmp(cmd, "?") == 0) slashUsage(pset.popt.topt.pager); #if 0 /* * These commands don't do anything. I just use them to test the parser. */ else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0) { int i = 0; char *value; while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true))) { fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value); free(value); } } #endif else status = PSQL_CMD_UNKNOWN; if (!success) status = PSQL_CMD_ERROR; return status; }
/* * Execute a \copy command (frontend copy). We have to open a file, then * submit a COPY query to the backend and either feed it data from the * file or route its response into the file. */ bool do_copy(const char *args) { PQExpBufferData query; FILE *copystream; struct copy_options *options; PGresult *result; bool success; struct stat st; /* parse options */ options = parse_slash_copy(args); if (!options) return false; initPQExpBuffer(&query); printfPQExpBuffer(&query, "COPY "); appendPQExpBuffer(&query, "%s ", options->table); if (options->column_list) appendPQExpBuffer(&query, "%s ", options->column_list); if (options->from) appendPQExpBuffer(&query, "FROM STDIN"); else appendPQExpBuffer(&query, "TO STDOUT"); if (options->binary) appendPQExpBuffer(&query, " BINARY "); if (options->oids) appendPQExpBuffer(&query, " OIDS "); if (options->delim) emit_copy_option(&query, " DELIMITER ", options->delim); if (options->null) emit_copy_option(&query, " NULL AS ", options->null); if (options->csv_mode) appendPQExpBuffer(&query, " CSV"); if (options->header) appendPQExpBuffer(&query, " HEADER"); if (options->quote) emit_copy_option(&query, " QUOTE AS ", options->quote); if (options->escape) emit_copy_option(&query, " ESCAPE AS ", options->escape); if (options->force_quote_list) appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list); if (options->force_notnull_list) appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list); if (options->file) canonicalize_path(options->file); if (options->from) { if (options->file) copystream = fopen(options->file, PG_BINARY_R); else if (!options->psql_inout) copystream = pset.cur_cmd_source; else copystream = stdin; } else { if (options->file) copystream = fopen(options->file, options->binary ? PG_BINARY_W : "w"); else if (!options->psql_inout) copystream = pset.queryFout; else copystream = stdout; } if (!copystream) { psql_error("%s: %s\n", options->file, strerror(errno)); free_copy_options(options); return false; } /* make sure the specified file is not a directory */ fstat(fileno(copystream), &st); if (S_ISDIR(st.st_mode)) { fclose(copystream); psql_error("%s: cannot copy from/to a directory\n", options->file); free_copy_options(options); return false; } result = PSQLexec(query.data, true); termPQExpBuffer(&query); switch (PQresultStatus(result)) { case PGRES_COPY_OUT: SetCancelConn(); success = handleCopyOut(pset.db, copystream); ResetCancelConn(); break; case PGRES_COPY_IN: SetCancelConn(); success = handleCopyIn(pset.db, copystream, PQbinaryTuples(result)); ResetCancelConn(); break; case PGRES_NONFATAL_ERROR: case PGRES_FATAL_ERROR: case PGRES_BAD_RESPONSE: success = false; psql_error("\\copy: %s", PQerrorMessage(pset.db)); break; default: success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); break; } PQclear(result); /* * Make sure we have pumped libpq dry of results; else it may still be * in ASYNC_BUSY state, leading to false readings in, eg, get_prompt(). */ while ((result = PQgetResult(pset.db)) != NULL) { success = false; psql_error("\\copy: unexpected response (%d)\n", PQresultStatus(result)); /* if still in COPY IN state, try to get out of it */ if (PQresultStatus(result) == PGRES_COPY_IN) PQputCopyEnd(pset.db, _("trying to exit copy mode")); PQclear(result); } if (options->file != NULL) { if (fclose(copystream) != 0) { psql_error("%s: %s\n", options->file, strerror(errno)); success = false; } } free_copy_options(options); return success; }
/* * * main * */ int main(int argc, char *argv[]) { struct adhoc_opts options; int successResult; char *password = NULL; char *password_prompt = NULL; bool new_pass; set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql")); if (argc > 1) { if ((strcmp(argv[1], "-?") == 0) || (argc == 2 && (strcmp(argv[1], "--help") == 0))) { usage(NOPAGER); exit(EXIT_SUCCESS); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { showVersion(); exit(EXIT_SUCCESS); } } #ifdef WIN32 setvbuf(stderr, NULL, _IONBF, 0); #endif pset.progname = get_progname(argv[0]); pset.db = NULL; setDecimalLocale(); pset.encoding = PQenv2encoding(); pset.queryFout = stdout; pset.queryFoutPipe = false; pset.copyStream = NULL; pset.last_error_result = NULL; pset.cur_cmd_source = stdin; pset.cur_cmd_interactive = false; /* We rely on unmentioned fields of pset.popt to start out 0/false/NULL */ pset.popt.topt.format = PRINT_ALIGNED; pset.popt.topt.border = 1; pset.popt.topt.pager = 1; pset.popt.topt.pager_min_lines = 0; pset.popt.topt.start_table = true; pset.popt.topt.stop_table = true; pset.popt.topt.default_footer = true; pset.popt.topt.unicode_border_linestyle = UNICODE_LINESTYLE_SINGLE; pset.popt.topt.unicode_column_linestyle = UNICODE_LINESTYLE_SINGLE; pset.popt.topt.unicode_header_linestyle = UNICODE_LINESTYLE_SINGLE; refresh_utf8format(&(pset.popt.topt)); /* We must get COLUMNS here before readline() sets it */ pset.popt.topt.env_columns = getenv("COLUMNS") ? atoi(getenv("COLUMNS")) : 0; pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout))); pset.getPassword = TRI_DEFAULT; EstablishVariableSpace(); SetVariable(pset.vars, "VERSION", PG_VERSION_STR); /* Default values for variables */ SetVariableBool(pset.vars, "AUTOCOMMIT"); SetVariable(pset.vars, "VERBOSITY", "default"); SetVariable(pset.vars, "SHOW_CONTEXT", "errors"); SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1); SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); parse_psql_options(argc, argv, &options); /* * If no action was specified and we're in non-interactive mode, treat it * as if the user had specified "-f -". This lets single-transaction mode * work in this case. */ if (options.actions.head == NULL && pset.notty) simple_action_list_append(&options.actions, ACT_FILE, NULL); /* Bail out if -1 was specified but will be ignored. */ if (options.single_txn && options.actions.head == NULL) { fprintf(stderr, _("%s: -1 can only be used in non-interactive mode\n"), pset.progname); exit(EXIT_FAILURE); } if (!pset.popt.topt.fieldSep.separator && !pset.popt.topt.fieldSep.separator_zero) { pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP); pset.popt.topt.fieldSep.separator_zero = false; } if (!pset.popt.topt.recordSep.separator && !pset.popt.topt.recordSep.separator_zero) { pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP); pset.popt.topt.recordSep.separator_zero = false; } if (options.username == NULL) password_prompt = pg_strdup(_("Password: "******"Password for user %s: "), options.username); if (pset.getPassword == TRI_YES) password = simple_prompt(password_prompt, 100, false); /* loop until we have a password if requested by backend */ do { #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] = options.host; keywords[1] = "port"; values[1] = options.port; keywords[2] = "user"; values[2] = options.username; keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; values[4] = (options.list_dbs && options.dbname == NULL) ? "postgres" : options.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; new_pass = false; pset.db = PQconnectdbParams(keywords, values, true); free(keywords); free(values); if (PQstatus(pset.db) == CONNECTION_BAD && PQconnectionNeedsPassword(pset.db) && password == NULL && pset.getPassword != TRI_NO) { PQfinish(pset.db); password = simple_prompt(password_prompt, 100, false); new_pass = true; } } while (new_pass); free(password); free(password_prompt); if (PQstatus(pset.db) == CONNECTION_BAD) { fprintf(stderr, "%s: %s", pset.progname, PQerrorMessage(pset.db)); PQfinish(pset.db); exit(EXIT_BADCONN); } setup_cancel_handler(); PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL); SyncVariables(); if (options.list_dbs) { int success; if (!options.no_psqlrc) process_psqlrc(argv[0]); success = listAllDbs(NULL, false); PQfinish(pset.db); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); } if (options.logfilename) { pset.logfile = fopen(options.logfilename, "a"); if (!pset.logfile) { fprintf(stderr, _("%s: could not open log file \"%s\": %s\n"), pset.progname, options.logfilename, strerror(errno)); exit(EXIT_FAILURE); } } if (!options.no_psqlrc) process_psqlrc(argv[0]); /* * If any actions were given by user, process them in the order in which * they were specified. Note single_txn is only effective in this mode. */ if (options.actions.head != NULL) { PGresult *res; SimpleActionListCell *cell; successResult = EXIT_SUCCESS; /* silence compiler */ if (options.single_txn) { if ((res = PSQLexec("BEGIN")) == NULL) { if (pset.on_error_stop) { successResult = EXIT_USER; goto error; } } else PQclear(res); } for (cell = options.actions.head; cell; cell = cell->next) { if (cell->action == ACT_SINGLE_QUERY) { if (pset.echo == PSQL_ECHO_ALL) puts(cell->val); successResult = SendQuery(cell->val) ? EXIT_SUCCESS : EXIT_FAILURE; } else if (cell->action == ACT_SINGLE_SLASH) { PsqlScanState scan_state; if (pset.echo == PSQL_ECHO_ALL) puts(cell->val); scan_state = psql_scan_create(&psqlscan_callbacks); psql_scan_setup(scan_state, cell->val, strlen(cell->val), pset.encoding, standard_strings()); successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; psql_scan_destroy(scan_state); } else if (cell->action == ACT_FILE) { successResult = process_file(cell->val, false); } else { /* should never come here */ Assert(false); } if (successResult != EXIT_SUCCESS && pset.on_error_stop) break; } if (options.single_txn) { if ((res = PSQLexec("COMMIT")) == NULL) { if (pset.on_error_stop) { successResult = EXIT_USER; goto error; } } else PQclear(res); } error: ; } /* * or otherwise enter interactive main loop */ else { connection_warnings(true); if (!pset.quiet) printf(_("Type \"help\" for help.\n\n")); initializeInput(options.no_readline ? 0 : 1); successResult = MainLoop(stdin); } /* clean up */ if (pset.logfile) fclose(pset.logfile); PQfinish(pset.db); setQFout(NULL); return successResult; }