/* * Transfer a user or group name starting at *input into the output buffer, * dequoting if needed. Returns a pointer to just past the input name. * The name is taken to end at an unquoted '=' or end of string. */ static char * copyAclUserName(PQExpBuffer output, char *input) { resetPQExpBuffer(output); while (*input && *input != '=') { /* * If user name isn't quoted, then just add it to the output buffer */ if (*input != '"') appendPQExpBufferChar(output, *input++); else { /* Otherwise, it's a quoted username */ input++; /* Loop until we come across an unescaped quote */ while (!(*input == '"' && *(input + 1) != '"')) { if (*input == '\0') return input; /* really a syntax error... */ /* * Quoting convention is to escape " as "". Keep this code in * sync with putid() in backend's acl.c. */ if (*input == '"' && *(input + 1) == '"') input++; appendPQExpBufferChar(output, *input++); } input++; } } return input; }
/* * pqGets[_append]: * get a null-terminated string from the connection, * and store it in an expansible PQExpBuffer. * If we run out of memory, all of the string is still read, * but the excess characters are silently discarded. */ static int pqGets_internal(PQExpBuffer buf, PGconn *conn, bool resetbuffer) { /* Copy conn data to locals for faster search loop */ char *inBuffer = conn->inBuffer; int inCursor = conn->inCursor; int inEnd = conn->inEnd; int slen; while (inCursor < inEnd && inBuffer[inCursor]) inCursor++; if (inCursor >= inEnd) return EOF; slen = inCursor - conn->inCursor; if (resetbuffer) resetPQExpBuffer(buf); appendBinaryPQExpBuffer(buf, inBuffer + conn->inCursor, slen); conn->inCursor = ++inCursor; if (conn->Pfdebug) fprintf(conn->Pfdebug, "From backend> \"%s\"\n", buf->data); return 0; }
/* * Attempt to negotiate secure session. */ PostgresPollingStatusType pqsecure_open_client(PGconn *conn) { #ifdef USE_SSL /* First time through? */ if (conn->ssl == NULL) { if (!(conn->ssl = SSL_new(SSL_context)) || !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock)) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not establish SSL connection: %s\n"), err); SSLerrfree(err); close_SSL(conn); return PGRES_POLLING_FAILED; } /* * Initialize errorMessage to empty. This allows open_client_SSL() to * detect whether client_cert_cb() has stored a message. */ resetPQExpBuffer(&conn->errorMessage); } /* Begin or continue the actual handshake */ return open_client_SSL(conn); #else /* shouldn't get here */ return PGRES_POLLING_FAILED; #endif }
/* * gets_fromFile * * Gets a line of noninteractive input from a file (which could be stdin). * The result is a malloc'd string, or NULL on EOF or input error. * * Caller *must* have set up sigint_interrupt_jmp before calling. * * Note: we re-use a static PQExpBuffer for each call. This is to avoid * leaking memory if interrupted by SIGINT. */ char * gets_fromFile(FILE *source) { static PQExpBuffer buffer = NULL; char line[1024]; if (buffer == NULL) /* first time through? */ buffer = createPQExpBuffer(); else resetPQExpBuffer(buffer); for (;;) { char *result; /* Enable SIGINT to longjmp to sigint_interrupt_jmp */ sigint_interrupt_enabled = true; /* Get some data */ result = fgets(line, sizeof(line), source); /* Disable SIGINT again */ sigint_interrupt_enabled = false; /* EOF or error? */ if (result == NULL) { if (ferror(source)) { psql_error("could not read from input file: %s\n", strerror(errno)); return NULL; } break; } appendPQExpBufferStr(buffer, line); if (PQExpBufferBroken(buffer)) { psql_error("out of memory\n"); return NULL; } /* EOL? */ if (buffer->data[buffer->len - 1] == '\n') { buffer->data[buffer->len - 1] = '\0'; return pg_strdup(buffer->data); } } if (buffer->len > 0) /* EOF after reading some bufferload(s) */ return pg_strdup(buffer->data); /* EOF, so return null */ return NULL; }
/* * Issue a query on a catalog table, and produce calls to a preassign support * function from the result set. * * The output is a string, containing SQL calls like: * * SELECT binary_upgrade.preassign_*_oid(<oid>, <other args); * * 'funcname' is the "preassign_*_oid" function to use. * 'sql' is the query to issue. The columns of the result set are passed as * arguments to the preassign-support function. * */ static void dump_rows(migratorContext *ctx, PQExpBuffer buf, FILE *file, PGconn *conn, const char *sql, const char *funcname) { int ntups; int ncols; int row; int col; PGresult *res; if (file != NULL) buf = createPQExpBuffer(); /* * Add a WHERE or AND clause to filter out built-in objects. * * If the query contains "UNION ALL", then it's the caller's * responsibility to do the filtering. This special case is for the * one more complicated query in get_old_oids() function; all the * other queries are very simple ones. */ if (strstr(sql, "WHERE ") == NULL) res = executeQueryOrDie(ctx, conn, "%s WHERE oid >= %u", sql, FirstNormalObjectId); else if (strstr(sql, "UNION ALL") == NULL) res = executeQueryOrDie(ctx, conn, "%s AND oid >= %u", sql, FirstNormalObjectId); else res = executeQueryOrDie(ctx, conn, "%s", sql); ntups = PQntuples(res); ncols = PQnfields(res); for (row = 0; row < ntups; row++) { appendPQExpBuffer(buf, "SELECT binary_upgrade.%s('%s'", funcname, simple_escape_literal(ctx, conn, PQgetvalue(res, row, 0))); for (col = 1; col < ncols; col++) appendPQExpBuffer(buf, ", '%s'", simple_escape_literal(ctx, conn, PQgetvalue(res, row, col))); appendPQExpBuffer(buf, ");\n"); if (file) { fwrite(buf->data, buf->len, 1, file); resetPQExpBuffer(buf); } } PQclear(res); if (file != NULL) destroyPQExpBuffer(buf); }
/* * GSSAPI errors contain two parts; put both into conn->errorMessage. */ static void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat, OM_uint32 min_stat) { resetPQExpBuffer(&conn->errorMessage); /* Fetch major error codes */ pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE); /* Add the minor codes as well */ pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE); }
/* * Reset a CdbDispatchResult object for possible reuse. */ void cdbdisp_resetResult(CdbDispatchResult *dispatchResult) { PQExpBuffer buf = dispatchResult->resultbuf; PGresult **begp = (PGresult **) buf->data; PGresult **endp = (PGresult **) (buf->data + buf->len); PGresult **p; /* * Free the PGresult objects. */ for (p = begp; p < endp; ++p) { Assert(*p != NULL); PQclear(*p); } /* * Reset summary indicators. */ dispatchResult->errcode = 0; dispatchResult->errindex = -1; dispatchResult->okindex = -1; /* * Reset progress indicators. */ dispatchResult->hasDispatched = false; dispatchResult->stillRunning = false; dispatchResult->sentSignal = DISPATCH_WAIT_NONE; dispatchResult->wasCanceled = false; /* * Empty (but don't free) the error message buffer and result buffer. */ resetPQExpBuffer(dispatchResult->resultbuf); resetPQExpBuffer(dispatchResult->error_message); }
/* * gets_fromFile * * Gets a line of noninteractive input from a file (which could be stdin). * The result is a malloc'd string. * * Caller *must* have set up sigint_interrupt_jmp before calling. * * Note: we re-use a static PQExpBuffer for each call. This is to avoid * leaking memory if interrupted by SIGINT. */ char * gets_fromFile(FILE *source) { static PQExpBuffer buffer = NULL; char line[1024]; if (buffer == NULL) /* first time through? */ buffer = createPQExpBuffer(); else resetPQExpBuffer(buffer); for (;;) { char *result; /* Enable SIGINT to longjmp to sigint_interrupt_jmp */ sigint_interrupt_enabled = true; /* Get some data */ result = fgets(line, sizeof(line), source); /* Disable SIGINT again */ sigint_interrupt_enabled = false; /* EOF? */ if (result == NULL) break; appendPQExpBufferStr(buffer, line); /* EOL? */ if (buffer->data[buffer->len - 1] == '\n') { buffer->data[buffer->len - 1] = '\0'; return pg_strdup(buffer->data); } } if (buffer->len > 0) /* EOF after reading some bufferload(s) */ return pg_strdup(buffer->data); /* EOF, so return null */ return NULL; }
/* * Returns a temporary PQExpBuffer, valid until the next call to the function. * This is used by fmtId and fmtQualifiedId. * * Non-reentrant and non-thread-safe but reduces memory leakage. You can * replace this with a custom version by setting the getLocalPQExpBuffer * function pointer. */ static PQExpBuffer defaultGetLocalPQExpBuffer(void) { static PQExpBuffer id_return = NULL; if (id_return) /* first time through? */ { /* same buffer, just wipe contents */ resetPQExpBuffer(id_return); } else { /* new buffer */ id_return = createPQExpBuffer(); } return id_return; }
/* * printfPQExpBuffer * Format text data under the control of fmt (an sprintf-like format string) * and insert it into str. More space is allocated to str if necessary. * This is a convenience routine that does the same thing as * resetPQExpBuffer() followed by appendPQExpBuffer(). */ void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) { va_list args; size_t avail; int nprinted; resetPQExpBuffer(str); if (PQExpBufferBroken(str)) return; /* already failed */ for (;;) { /* * Try to format the given string into the available space; but if * there's hardly any space, don't bother trying, just fall through to * enlarge the buffer first. */ if (str->maxlen > str->len + 16) { avail = str->maxlen - str->len - 1; va_start(args, fmt); nprinted = vsnprintf(str->data + str->len, avail, fmt, args); va_end(args); /* * Note: some versions of vsnprintf return the number of chars * actually stored, but at least one returns -1 on failure. Be * conservative about believing whether the print worked. */ if (nprinted >= 0 && nprinted < (int) avail - 1) { /* Success. Note nprinted does not include trailing null. */ str->len += nprinted; break; } } /* Double the buffer size and try again. */ if (!enlargePQExpBuffer(str, str->maxlen)) return; /* oops, out of memory */ } }
/* * printfPQExpBuffer * Format text data under the control of fmt (an sprintf-like format string) * and insert it into str. More space is allocated to str if necessary. * This is a convenience routine that does the same thing as * resetPQExpBuffer() followed by appendPQExpBuffer(). */ void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) { va_list args; bool done; resetPQExpBuffer(str); if (PQExpBufferBroken(str)) return; /* already failed */ /* Loop in case we have to retry after enlarging the buffer. */ do { va_start(args, fmt); done = appendPQExpBufferVA(str, fmt, args); va_end(args); } while (!done); }
/* * cluster_conn_opts() * * Return standard command-line options for connecting to this cluster when * using psql, pg_dump, etc. Ideally this would match what get_db_conn() * sets, but the utilities we need aren't very consistent about the treatment * of database name options, so we leave that out. * * Result is valid until the next call to this function. */ char * cluster_conn_opts(ClusterInfo *cluster) { static PQExpBuffer buf; if (buf == NULL) buf = createPQExpBuffer(); else resetPQExpBuffer(buf); if (cluster->sockdir) { appendPQExpBufferStr(buf, "--host "); appendShellString(buf, cluster->sockdir); appendPQExpBufferChar(buf, ' '); } appendPQExpBuffer(buf, "--port %d --username ", cluster->port); appendShellString(buf, os_info.user); return buf->data; }
static void cluster_all_databases(bool verbose, const char *maintenance_db, const char *host, const char *port, const char *username, enum trivalue prompt_password, const char *progname, bool echo, bool quiet) { PGconn *conn; PGresult *result; PQExpBufferData connstr; int i; conn = connectMaintenanceDatabase(maintenance_db, host, port, username, prompt_password, progname, echo); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); PQfinish(conn); initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); if (!quiet) { printf(_("%s: clustering database \"%s\"\n"), progname, dbname); fflush(stdout); } resetPQExpBuffer(&connstr); appendPQExpBuffer(&connstr, "dbname="); appendConnStrVal(&connstr, dbname); cluster_one_database(connstr.data, verbose, NULL, host, port, username, prompt_password, progname, echo); } termPQExpBuffer(&connstr); PQclear(result); }
/* * Emit accumulated history entry to readline's history mechanism, * then reset the buffer to empty. * * Note: we write nothing if history_buf is empty, so extra calls to this * function don't hurt. There must have been at least one line added by * pg_append_history before we'll do anything. */ void pg_send_history(PQExpBuffer history_buf) { #ifdef USE_READLINE static char *prev_hist = NULL; char *s = history_buf->data; int i; /* Trim any trailing \n's (OK to scribble on history_buf) */ for (i = strlen(s) - 1; i >= 0 && s[i] == '\n'; i--) ; s[i + 1] = '\0'; if (useHistory && s[0]) { if (((pset.histcontrol & hctl_ignorespace) && s[0] == ' ') || ((pset.histcontrol & hctl_ignoredups) && prev_hist && strcmp(s, prev_hist) == 0)) { /* Ignore this line as far as history is concerned */ } else { /* Save each previous line for ignoredups processing */ if (prev_hist) free(prev_hist); prev_hist = pg_strdup(s); /* And send it to readline */ add_history(s); /* Count lines added to history for use later */ history_lines_added++; } } resetPQExpBuffer(history_buf); #endif }
/* * processSQLNamePattern * * Scan a wildcard-pattern string and generate appropriate WHERE clauses * to limit the set of objects returned. The WHERE clauses are appended * to the already-partially-constructed query in buf. Returns whether * any clause was added. * * conn: connection query will be sent to (consulted for escaping rules). * buf: output parameter. * pattern: user-specified pattern option, or NULL if none ("*" is implied). * have_where: true if caller already emitted "WHERE" (clauses will be ANDed * onto the existing WHERE clause). * force_escape: always quote regexp special characters, even outside * double quotes (else they are quoted only between double quotes). * schemavar: name of query variable to match against a schema-name pattern. * Can be NULL if no schema. * namevar: name of query variable to match against an object-name pattern. * altnamevar: NULL, or name of an alternative variable to match against name. * visibilityrule: clause to use if we want to restrict to visible objects * (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL. * * Formatting note: the text already present in buf should end with a newline. * The appended text, if any, will end with one too. */ bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern, bool have_where, bool force_escape, const char *schemavar, const char *namevar, const char *altnamevar, const char *visibilityrule) { PQExpBufferData schemabuf; PQExpBufferData namebuf; int encoding = PQclientEncoding(conn); bool inquotes; const char *cp; int i; bool added_clause = false; #define WHEREAND() \ (appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \ have_where = true, added_clause = true) if (pattern == NULL) { /* Default: select all visible objects */ if (visibilityrule) { WHEREAND(); appendPQExpBuffer(buf, "%s\n", visibilityrule); } return added_clause; } initPQExpBuffer(&schemabuf); initPQExpBuffer(&namebuf); /* * Parse the pattern, converting quotes and lower-casing unquoted letters. * Also, adjust shell-style wildcard characters into regexp notation. * * We surround the pattern with "^(...)$" to force it to match the whole * string, as per SQL practice. We have to have parens in case the string * contains "|", else the "^" and "$" will be bound into the first and * last alternatives which is not what we want. * * Note: the result of this pass is the actual regexp pattern(s) we want * to execute. Quoting/escaping into SQL literal format will be done * below using appendStringLiteralConn(). */ appendPQExpBufferStr(&namebuf, "^("); inquotes = false; cp = pattern; while (*cp) { char ch = *cp; if (ch == '"') { if (inquotes && cp[1] == '"') { /* emit one quote, stay in inquotes mode */ appendPQExpBufferChar(&namebuf, '"'); cp++; } else inquotes = !inquotes; cp++; } else if (!inquotes && isupper((unsigned char) ch)) { appendPQExpBufferChar(&namebuf, pg_tolower((unsigned char) ch)); cp++; } else if (!inquotes && ch == '*') { appendPQExpBufferStr(&namebuf, ".*"); cp++; } else if (!inquotes && ch == '?') { appendPQExpBufferChar(&namebuf, '.'); cp++; } else if (!inquotes && ch == '.') { /* Found schema/name separator, move current pattern to schema */ resetPQExpBuffer(&schemabuf); appendPQExpBufferStr(&schemabuf, namebuf.data); resetPQExpBuffer(&namebuf); appendPQExpBufferStr(&namebuf, "^("); cp++; } else if (ch == '$') { /* * Dollar is always quoted, whether inside quotes or not. The * reason is that it's allowed in SQL identifiers, so there's a * significant use-case for treating it literally, while because * we anchor the pattern automatically there is no use-case for * having it possess its regexp meaning. */ appendPQExpBufferStr(&namebuf, "\\$"); cp++; } else { /* * Ordinary data character, transfer to pattern * * Inside double quotes, or at all times if force_escape is true, * quote regexp special characters with a backslash to avoid * regexp errors. Outside quotes, however, let them pass through * as-is; this lets knowledgeable users build regexp expressions * that are more powerful than shell-style patterns. */ if ((inquotes || force_escape) && strchr("|*+?()[]{}.^$\\", ch)) appendPQExpBufferChar(&namebuf, '\\'); i = PQmblen(cp, encoding); while (i-- && *cp) { appendPQExpBufferChar(&namebuf, *cp); cp++; } } } /* * Now decide what we need to emit. Note there will be a leading "^(" in * the patterns in any case. */ if (namebuf.len > 2) { /* We have a name pattern, so constrain the namevar(s) */ appendPQExpBufferStr(&namebuf, ")$"); /* Optimize away a "*" pattern */ if (strcmp(namebuf.data, "^(.*)$") != 0) { WHEREAND(); if (altnamevar) { appendPQExpBuffer(buf, "(%s ~ ", namevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBuffer(buf, "\n OR %s ~ ", altnamevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBufferStr(buf, ")\n"); } else { appendPQExpBuffer(buf, "%s ~ ", namevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBufferChar(buf, '\n'); } } } if (schemabuf.len > 2) { /* We have a schema pattern, so constrain the schemavar */ appendPQExpBufferStr(&schemabuf, ")$"); /* Optimize away a "*" pattern */ if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar) { WHEREAND(); appendPQExpBuffer(buf, "%s ~ ", schemavar); appendStringLiteralConn(buf, schemabuf.data, conn); appendPQExpBufferChar(buf, '\n'); } } else { /* No schema pattern given, so select only visible objects */ if (visibilityrule) { WHEREAND(); appendPQExpBuffer(buf, "%s\n", visibilityrule); } } termPQExpBuffer(&schemabuf); termPQExpBuffer(&namebuf); return added_clause; #undef WHEREAND }
/* * This will parse an aclitem string, having the general form * username=privilegecodes/grantor * or * group groupname=privilegecodes/grantor * (the /grantor part will not be present if pre-7.4 database). * * The returned grantee string will be the dequoted username or groupname * (preceded with "group " in the latter case). The returned grantor is * the dequoted grantor name or empty. Privilege characters are decoded * and split between privileges with grant option (privswgo) and without * (privs). * * Note: for cross-version compatibility, it's important to use ALL when * appropriate. */ static bool parseAclItem(const char *item, const char *type, const char *name, const char *subname, int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo) { char *buf; bool all_with_go = true; bool all_without_go = true; char *eqpos; char *slpos; char *pos; buf = strdup(item); if (!buf) return false; /* user or group name is string up to = */ eqpos = copyAclUserName(grantee, buf); if (*eqpos != '=') return false; /* grantor may be listed after / */ slpos = strchr(eqpos + 1, '/'); if (slpos) { *slpos++ = '\0'; slpos = copyAclUserName(grantor, slpos); if (*slpos != '\0') return false; } else resetPQExpBuffer(grantor); /* privilege codes */ #define CONVERT_PRIV(code, keywd) \ do { \ if ((pos = strchr(eqpos + 1, code))) \ { \ if (*(pos + 1) == '*') \ { \ AddAcl(privswgo, keywd, subname); \ all_without_go = false; \ } \ else \ { \ AddAcl(privs, keywd, subname); \ all_with_go = false; \ } \ } \ else \ all_with_go = all_without_go = false; \ } while (0) resetPQExpBuffer(privs); resetPQExpBuffer(privswgo); if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0) { CONVERT_PRIV('r', "SELECT"); if (strcmp(type, "SEQUENCE") == 0 || strcmp(type, "SEQUENCES") == 0) /* sequence only */ CONVERT_PRIV('U', "USAGE"); else { /* table only */ CONVERT_PRIV('a', "INSERT"); if (remoteVersion >= 70200) CONVERT_PRIV('x', "REFERENCES"); /* rest are not applicable to columns */ if (subname == NULL) { if (remoteVersion >= 70200) { CONVERT_PRIV('d', "DELETE"); CONVERT_PRIV('t', "TRIGGER"); } if (remoteVersion >= 80400) CONVERT_PRIV('D', "TRUNCATE"); } } /* UPDATE */ if (remoteVersion >= 70200 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "SEQUENCES") == 0) CONVERT_PRIV('w', "UPDATE"); else /* 7.0 and 7.1 have a simpler worldview */ CONVERT_PRIV('w', "UPDATE,DELETE"); } else if (strcmp(type, "FUNCTION") == 0 || strcmp(type, "FUNCTIONS") == 0) CONVERT_PRIV('X', "EXECUTE"); else if (strcmp(type, "LANGUAGE") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "SCHEMA") == 0) { CONVERT_PRIV('C', "CREATE"); CONVERT_PRIV('U', "USAGE"); } else if (strcmp(type, "DATABASE") == 0) { CONVERT_PRIV('C', "CREATE"); CONVERT_PRIV('c', "CONNECT"); CONVERT_PRIV('T', "TEMPORARY"); } else if (strcmp(type, "TABLESPACE") == 0) CONVERT_PRIV('C', "CREATE"); else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "FOREIGN SERVER") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "FOREIGN TABLE") == 0) CONVERT_PRIV('r', "SELECT"); else if (strcmp(type, "LARGE OBJECT") == 0) { CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('w', "UPDATE"); } else abort(); #undef CONVERT_PRIV if (all_with_go) { resetPQExpBuffer(privs); printfPQExpBuffer(privswgo, "ALL"); if (subname) appendPQExpBuffer(privswgo, "(%s)", subname); } else if (all_without_go) { resetPQExpBuffer(privswgo); printfPQExpBuffer(privs, "ALL"); if (subname) appendPQExpBuffer(privs, "(%s)", subname); } free(buf); return true; }
/* * Quotes input string if it's not a legitimate SQL identifier as-is. * * Note that the returned string must be used before calling fmtId again, * since we re-use the same return buffer each time. Non-reentrant but * reduces memory leakage. (On Windows the memory leakage will be one buffer * per thread, which is at least better than one per call). */ const char * fmtId(const char *rawid) { /* * The Tls code goes awry if we use a static var, so we provide for both * static and auto, and omit any use of the static var when using Tls. */ static PQExpBuffer s_id_return = NULL; PQExpBuffer id_return; const char *cp; bool need_quotes = false; #ifdef WIN32 if (parallel_init_done) id_return = (PQExpBuffer) TlsGetValue(tls_index); /* 0 when not set */ else id_return = s_id_return; #else id_return = s_id_return; #endif if (id_return) /* first time through? */ { /* same buffer, just wipe contents */ resetPQExpBuffer(id_return); } else { /* new buffer */ id_return = createPQExpBuffer(); #ifdef WIN32 if (parallel_init_done) TlsSetValue(tls_index, id_return); else s_id_return = id_return; #else s_id_return = id_return; #endif } /* * These checks need to match the identifier production in scan.l. Don't * use islower() etc. */ if (quote_all_identifiers) need_quotes = true; /* slightly different rules for first character */ else if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || rawid[0] == '_')) need_quotes = true; else { /* otherwise check the entire string */ for (cp = rawid; *cp; cp++) { if (!((*cp >= 'a' && *cp <= 'z') || (*cp >= '0' && *cp <= '9') || (*cp == '_'))) { need_quotes = true; break; } } } if (!need_quotes) { /* * Check for keyword. We quote keywords except for unreserved ones. * (In some cases we could avoid quoting a col_name or type_func_name * keyword, but it seems much harder than it's worth to tell that.) * * Note: ScanKeywordLookup() does case-insensitive comparison, but * that's fine, since we already know we have all-lower-case. */ const ScanKeyword *keyword = ScanKeywordLookup(rawid, ScanKeywords, NumScanKeywords); if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD) need_quotes = true; } if (!need_quotes) { /* no quoting needed */ appendPQExpBufferStr(id_return, rawid); } else { appendPQExpBufferChar(id_return, '\"'); for (cp = rawid; *cp; cp++) { /* * Did we find a double-quote in the string? Then make this a * double double-quote per SQL99. Before, we put in a * backslash/double-quote pair. - thomas 2000-08-05 */ if (*cp == '\"') appendPQExpBufferChar(id_return, '\"'); appendPQExpBufferChar(id_return, *cp); } appendPQExpBufferChar(id_return, '\"'); } return id_return->data; }
/* * PQendcopy * * See fe-exec.c for documentation. */ int pqEndcopy3(PGconn *conn) { PGresult *result; if (conn->asyncStatus != PGASYNC_COPY_IN && conn->asyncStatus != PGASYNC_COPY_OUT) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("no COPY in progress\n")); return 1; } /* Send the CopyDone message if needed */ if (conn->asyncStatus == PGASYNC_COPY_IN) { if (pqPutMsgStart('c', false, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; /* * If we sent the COPY command in extended-query mode, we must issue a * Sync as well. */ if (conn->queryclass != PGQUERY_SIMPLE) { if (pqPutMsgStart('S', false, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; } } /* * make sure no data is waiting to be sent, abort if we are non-blocking * and the flush fails */ if (pqFlush(conn) && pqIsnonblocking(conn)) return 1; /* Return to active duty */ conn->asyncStatus = PGASYNC_BUSY; resetPQExpBuffer(&conn->errorMessage); /* * Non blocking connections may have to abort at this point. If everyone * played the game there should be no problem, but in error scenarios the * expected messages may not have arrived yet. (We are assuming that the * backend's packetizing will ensure that CommandComplete arrives along * with the CopyDone; are there corner cases where that doesn't happen?) */ if (pqIsnonblocking(conn) && PQisBusy(conn)) return 1; /* Wait for the completion response */ result = PQgetResult(conn); /* Expecting a successful result */ if (result && result->resultStatus == PGRES_COMMAND_OK) { PQclear(result); return 0; } /* * Trouble. For backwards-compatibility reasons, we issue the error * message as if it were a notice (would be nice to get rid of this * silliness, but too many apps probably don't handle errors from * PQendcopy reasonably). Note that the app can still obtain the error * status from the PGconn object. */ if (conn->errorMessage.len > 0) { /* We have to strip the trailing newline ... pain in neck... */ char svLast = conn->errorMessage.data[conn->errorMessage.len - 1]; if (svLast == '\n') conn->errorMessage.data[conn->errorMessage.len - 1] = '\0'; pqInternalNotice(&conn->noticeHooks, "%s", conn->errorMessage.data); conn->errorMessage.data[conn->errorMessage.len - 1] = svLast; } PQclear(result); return 1; }
/* * Attempt to read an Error or Notice response message. * This is possible in several places, so we break it out as a subroutine. * Entry: 'E' or 'N' message type and length have already been consumed. * Exit: returns 0 if successfully consumed message. * returns EOF if not enough data. */ int pqGetErrorNotice3(PGconn *conn, bool isError) { PGresult *res = NULL; PQExpBufferData workBuf; char id; const char *val; const char *querytext = NULL; int querypos = 0; /* * Since the fields might be pretty long, we create a temporary * PQExpBuffer rather than using conn->workBuffer. workBuffer is intended * for stuff that is expected to be short. We shouldn't use * conn->errorMessage either, since this might be only a notice. */ initPQExpBuffer(&workBuf); /* * Make a PGresult to hold the accumulated fields. We temporarily lie * about the result status, so that PQmakeEmptyPGresult doesn't uselessly * copy conn->errorMessage. */ res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); if (!res) goto fail; res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR; /* * Read the fields and save into res. */ for (;;) { if (pqGetc(&id, conn)) goto fail; if (id == '\0') break; /* terminator found */ if (pqGets(&workBuf, conn)) goto fail; pqSaveMessageField(res, id, workBuf.data); } /* * Now build the "overall" error message for PQresultErrorMessage. */ resetPQExpBuffer(&workBuf); val = PQresultErrorField(res, PG_DIAG_SEVERITY); if (val) appendPQExpBuffer(&workBuf, "%s: ", val); if (conn->verbosity == PQERRORS_VERBOSE) { val = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (val) appendPQExpBuffer(&workBuf, "%s: ", val); } val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); if (val) appendPQExpBufferStr(&workBuf, val); val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); if (val) { if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL) { /* emit position as a syntax cursor display */ querytext = conn->last_query; querypos = atoi(val); } else { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), val); } } else { val = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION); if (val) { querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); if (conn->verbosity != PQERRORS_TERSE && querytext != NULL) { /* emit position as a syntax cursor display */ querypos = atoi(val); } else { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), val); } } } appendPQExpBufferChar(&workBuf, '\n'); if (conn->verbosity != PQERRORS_TERSE) { if (querytext && querypos > 0) reportErrorPosition(&workBuf, querytext, querypos, conn->client_encoding); val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_CONTEXT); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), val); } if (conn->verbosity == PQERRORS_VERBOSE) { const char *valf; const char *vall; valf = PQresultErrorField(res, PG_DIAG_SOURCE_FILE); vall = PQresultErrorField(res, PG_DIAG_SOURCE_LINE); val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION); if (val || valf || vall) { appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: ")); if (val) appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val); if (valf && vall) /* unlikely we'd have just one */ appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"), valf, vall); appendPQExpBufferChar(&workBuf, '\n'); } } /* * Either save error as current async result, or just emit the notice. */ if (isError) { res->errMsg = pqResultStrdup(res, workBuf.data); if (!res->errMsg) goto fail; pqClearAsyncResult(conn); conn->result = res; resetPQExpBuffer(&conn->errorMessage); appendPQExpBufferStr(&conn->errorMessage, workBuf.data); } else { /* We can cheat a little here and not copy the message. */ res->errMsg = workBuf.data; if (res->noticeHooks.noticeRec != NULL) (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); PQclear(res); } termPQExpBuffer(&workBuf); return 0; fail: PQclear(res); termPQExpBuffer(&workBuf); return EOF; }
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, * etc. */ PQExpBuffer history_buf; /* earlier lines of a multi-line command, not * yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; /* Save old settings */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; /* Create working state */ scan_state = psql_scan_create(); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); if (PQExpBufferBroken(query_buf) || PQExpBufferBroken(previous_buf) || PQExpBufferBroken(history_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Clean up after a previous Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C. */ successResult = EXIT_USER; break; } cancel_pressed = false; } /* * Establish longjmp destination for exiting from wait-for-input. We * must re-do this each time through the loop for safety, since the * jmpbuf might get changed during command execution. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ psql_scan_finish(scan_state); psql_scan_reset(scan_state); resetPQExpBuffer(query_buf); resetPQExpBuffer(history_buf); count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; cancel_pressed = false; if (pset.cur_cmd_interactive) putc('\n', stdout); else { successResult = EXIT_USER; break; } } fflush(stdout); /* * get another line */ if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status)); } else { line = gets_fromFile(source); if (!line && ferror(source)) successResult = EXIT_FAILURE; } /* * query_buf holds query already accumulated. line is the malloc'd * new line of input (note it must be freed before looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < GetVariableNum(pset.vars, "IGNOREEOF", 0, 10, false)) { if (!pset.quiet) printf(_("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(pset.quiet ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* ignore UTF-8 Unicode byte-order mark */ if (pset.lineno == 1 && pset.encoding == PG_UTF8 && strncmp(line, "\xef\xbb\xbf", 3) == 0) memmove(line, line + 3, strlen(line + 3) + 1); /* nothing left on line? then ignore */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* A request for help? Be friendly and give them some guidance */ if (pset.cur_cmd_interactive && query_buf->len == 0 && pg_strncasecmp(line, "help", 4) == 0 && (line[4] == '\0' || line[4] == ';' || isspace((unsigned char) line[4]))) { free(line); puts(_("You are using psql, the command-line interface to PostgreSQL.")); printf(_("Type: \\copyright for distribution terms\n" " \\h for help with SQL commands\n" " \\? for help with psql commands\n" " \\g or terminate with semicolon to execute query\n" " \\q to quit\n")); fflush(stdout); continue; } /* echo back if flag is set */ if (pset.echo == PSQL_ECHO_ALL && !pset.cur_cmd_interactive) puts(line); fflush(stdout); /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = pset.on_error_stop; /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line)); success = true; line_saved_in_history = false; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; if (PQExpBufferBroken(query_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* * Send command if semicolon found, or if end of line and we're in * single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && pset.singleline)) { /* * Save query in history. We use history_buf to accumulate * multi-line queries into a single history entry. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute query */ success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a * line contains only a backslash command. Also, in this * situation we force out any previous lines as a separate * history entry; we don't want SQL and backslash commands * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) { query_buf->data[--query_buf->len] = '\0'; pg_send_history(history_buf); } added_nl_pos = -1; /* save backslash command in history */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, query_buf->len > 0 ? query_buf : previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) && query_buf->len == 0) { /* copy previous buffer to current for handling */ appendPQExpBufferStr(query_buf, previous_buf->data); } if (slashCmdStatus == PSQL_CMD_SEND) { success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); line = pg_strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); psql_scan_setup(scan_state, line, strlen(line)); line_saved_in_history = false; prompt_status = PROMPT_READY; } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } /* Add line to pending history if we didn't execute anything yet */ if (pset.cur_cmd_interactive && !line_saved_in_history) pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); if (slashCmdStatus == PSQL_CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * Process query at the end of file without a semicolon */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { /* save query in history */ if (pset.cur_cmd_interactive) pg_send_history(history_buf); /* execute query */ success = SendQuery(query_buf->data); if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to * itself at the top of its loop, before any further interactive input * happens. */ sigint_interrupt_enabled = false; destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */
/* * Vacuum/analyze all connectable databases. * * In analyze-in-stages mode, we process all databases in one stage before * moving on to the next stage. That ensure minimal stats are available * quickly everywhere before generating more detailed ones. */ static void vacuum_all_databases(vacuumingOptions *vacopts, bool analyze_in_stages, const char *maintenance_db, const char *host, const char *port, const char *username, enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet) { PGconn *conn; PGresult *result; PQExpBufferData connstr; int stage; int i; conn = connectMaintenanceDatabase(maintenance_db, host, port, username, prompt_password, progname, echo); result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); PQfinish(conn); initPQExpBuffer(&connstr); if (analyze_in_stages) { /* * When analyzing all databases in stages, we analyze them all in the * fastest stage first, so that initial statistics become available * for all of them as soon as possible. * * This means we establish several times as many connections, but * that's a secondary consideration. */ for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++) { for (i = 0; i < PQntuples(result); i++) { resetPQExpBuffer(&connstr); appendPQExpBuffer(&connstr, "dbname="); appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); vacuum_one_database(connstr.data, vacopts, stage, NULL, host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } } } else { for (i = 0; i < PQntuples(result); i++) { resetPQExpBuffer(&connstr); appendPQExpBuffer(&connstr, "dbname="); appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); vacuum_one_database(connstr.data, vacopts, ANALYZE_NO_STAGE, NULL, host, port, username, prompt_password, concurrentCons, progname, echo, quiet); } } termPQExpBuffer(&connstr); PQclear(result); }
/* * Quotes input string if it's not a legitimate SQL identifier as-is. * * Note that the returned string must be used before calling fmtId again, * since we re-use the same return buffer each time. Non-reentrant but * reduces memory leakage. (On Windows the memory leakage will be one buffer * per thread, which is at least better than one per call). */ const char * fmtId(const char *rawid) { static PQExpBuffer id_return = NULL; const char *cp; bool need_quotes = false; if (id_return) /* first time through? */ resetPQExpBuffer(id_return); else id_return = createPQExpBuffer(); /* * These checks need to match the identifier production in scan.l. Don't * use islower() etc. */ /* slightly different rules for first character */ if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || (rawid[0] == '_'))) need_quotes = true; else { /* otherwise check the entire string */ for (cp = rawid; *cp; cp++) { if (!((*cp >= 'a' && *cp <= 'z') || (*cp >= '0' && *cp <= '9') || (*cp == '_'))) { need_quotes = true; break; } } } if (!need_quotes) { /* * Check for keyword. We quote keywords except for unreserved ones. * (In some cases we could avoid quoting a col_name or type_func_name * keyword, but it seems much harder than it's worth to tell that.) * * Note: ScanKeywordLookup() does case-insensitive comparison, but * that's fine, since we already know we have all-lower-case. */ const ScanKeyword *keyword = ScanKeywordLookup(rawid); if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD) need_quotes = true; } if (!need_quotes) { /* no quoting needed */ appendPQExpBufferStr(id_return, rawid); } else { appendPQExpBufferChar(id_return, '\"'); for (cp = rawid; *cp; cp++) { /* * Did we find a double-quote in the string? Then make this a * double double-quote per SQL99. Before, we put in a * backslash/double-quote pair. - thomas 2000-08-05 */ if (*cp == '\"') appendPQExpBufferChar(id_return, '\"'); appendPQExpBufferChar(id_return, *cp); } appendPQExpBufferChar(id_return, '\"'); } return id_return->data; }
/* * vacuum_one_database * * Process tables in the given database. If the 'tables' list is empty, * process all tables in the database. * * Note that this function is only concerned with running exactly one stage * when in analyze-in-stages mode; caller must iterate on us if necessary. * * If concurrentCons is > 1, multiple connections are used to vacuum tables * in parallel. In this case and if the table list is empty, we first obtain * a list of tables from the database. */ static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, int stage, SimpleStringList *tables, const char *host, const char *port, const char *username, enum trivalue prompt_password, int concurrentCons, const char *progname, bool echo, bool quiet) { PQExpBufferData sql; PGconn *conn; SimpleStringListCell *cell; ParallelSlot *slots = NULL; SimpleStringList dbtables = {NULL, NULL}; int i; bool result = 0; bool parallel = concurrentCons > 1; const char *stage_commands[] = { "SET default_statistics_target=1; SET vacuum_cost_delay=0;", "SET default_statistics_target=10; RESET vacuum_cost_delay;", "RESET default_statistics_target;" }; const char *stage_messages[] = { gettext_noop("Generating minimal optimizer statistics (1 target)"), gettext_noop("Generating medium optimizer statistics (10 targets)"), gettext_noop("Generating default (full) optimizer statistics") }; Assert(stage == ANALYZE_NO_STAGE || (stage >= 0 && stage < ANALYZE_NUM_STAGES)); if (!quiet) { if (stage != ANALYZE_NO_STAGE) printf(_("%s: processing database \"%s\": %s\n"), progname, dbname, stage_messages[stage]); else printf(_("%s: vacuuming database \"%s\"\n"), progname, dbname); fflush(stdout); } conn = connectDatabase(dbname, host, port, username, prompt_password, progname, false); initPQExpBuffer(&sql); /* * If a table list is not provided and we're using multiple connections, * prepare the list of tables by querying the catalogs. */ if (parallel && (!tables || !tables->head)) { PQExpBufferData buf; PGresult *res; int ntups; int i; initPQExpBuffer(&buf); res = executeQuery(conn, "SELECT c.relname, ns.nspname FROM pg_class c, pg_namespace ns\n" " WHERE relkind IN (\'r\', \'m\') AND c.relnamespace = ns.oid\n" " ORDER BY c.relpages DESC;", progname, echo); ntups = PQntuples(res); for (i = 0; i < ntups; i++) { appendPQExpBuffer(&buf, "%s", fmtQualifiedId(PQserverVersion(conn), PQgetvalue(res, i, 1), PQgetvalue(res, i, 0))); simple_string_list_append(&dbtables, buf.data); resetPQExpBuffer(&buf); } termPQExpBuffer(&buf); tables = &dbtables; /* * If there are more connections than vacuumable relations, we don't * need to use them all. */ if (concurrentCons > ntups) concurrentCons = ntups; if (concurrentCons <= 1) parallel = false; } /* * Setup the database connections. We reuse the connection we already have * for the first slot. If not in parallel mode, the first slot in the * array contains the connection. */ slots = (ParallelSlot *) pg_malloc(sizeof(ParallelSlot) * concurrentCons); init_slot(slots, conn); if (parallel) { for (i = 1; i < concurrentCons; i++) { conn = connectDatabase(dbname, host, port, username, prompt_password, progname, false); init_slot(slots + i, conn); } } /* * Prepare all the connections to run the appropriate analyze stage, if * caller requested that mode. */ if (stage != ANALYZE_NO_STAGE) { int j; /* We already emitted the message above */ for (j = 0; j < concurrentCons; j++) executeCommand((slots + j)->connection, stage_commands[stage], progname, echo); } cell = tables ? tables->head : NULL; do { ParallelSlot *free_slot; const char *tabname = cell ? cell->val : NULL; prepare_vacuum_command(&sql, conn, vacopts, tabname); if (CancelRequested) { result = -1; goto finish; } /* * Get the connection slot to use. If in parallel mode, here we wait * for one connection to become available if none already is. In * non-parallel mode we simply use the only slot we have, which we * know to be free. */ if (parallel) { /* * Get a free slot, waiting until one becomes free if none * currently is. */ free_slot = GetIdleSlot(slots, concurrentCons, dbname, progname); if (!free_slot) { result = -1; goto finish; } free_slot->isFree = false; } else free_slot = slots; run_vacuum_command(free_slot->connection, sql.data, echo, dbname, tabname, progname, parallel); if (cell) cell = cell->next; } while (cell != NULL); if (parallel) { int j; for (j = 0; j < concurrentCons; j++) { /* wait for all connection to return the results */ if (!GetQueryResult((slots + j)->connection, dbname, progname)) goto finish; (slots + j)->isFree = true; } } finish: for (i = 0; i < concurrentCons; i++) DisconnectDatabase(slots + i); pfree(slots); termPQExpBuffer(&sql); if (result == -1) exit(1); }
/* * Dump roles */ static void dumpRoles(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); PGresult *res; int i_rolname, i_rolsuper, i_rolinherit, i_rolcreaterole, i_rolcreatedb, i_rolcatupdate, i_rolcanlogin, i_rolconnlimit, i_rolpassword, i_rolvaliduntil; int i; /* note: rolconfig is dumped later */ if (server_version >= 80100) printfPQExpBuffer(buf, "SELECT rolname, rolsuper, rolinherit, " "rolcreaterole, rolcreatedb, rolcatupdate, " "rolcanlogin, rolconnlimit, rolpassword, " "rolvaliduntil " "FROM pg_authid " "ORDER BY 1"); else printfPQExpBuffer(buf, "SELECT usename as rolname, " "usesuper as rolsuper, " "true as rolinherit, " "usesuper as rolcreaterole, " "usecreatedb as rolcreatedb, " "usecatupd as rolcatupdate, " "true as rolcanlogin, " "-1 as rolconnlimit, " "passwd as rolpassword, " "valuntil as rolvaliduntil " "FROM pg_shadow " "UNION ALL " "SELECT groname as rolname, " "false as rolsuper, " "true as rolinherit, " "false as rolcreaterole, " "false as rolcreatedb, " "false as rolcatupdate, " "false as rolcanlogin, " "-1 as rolconnlimit, " "null::text as rolpassword, " "null::abstime as rolvaliduntil " "FROM pg_group " "WHERE NOT EXISTS (SELECT 1 FROM pg_shadow " " WHERE usename = groname) " "ORDER BY 1"); res = executeQuery(conn, buf->data); i_rolname = PQfnumber(res, "rolname"); i_rolsuper = PQfnumber(res, "rolsuper"); i_rolinherit = PQfnumber(res, "rolinherit"); i_rolcreaterole = PQfnumber(res, "rolcreaterole"); i_rolcreatedb = PQfnumber(res, "rolcreatedb"); i_rolcatupdate = PQfnumber(res, "rolcatupdate"); i_rolcanlogin = PQfnumber(res, "rolcanlogin"); i_rolconnlimit = PQfnumber(res, "rolconnlimit"); i_rolpassword = PQfnumber(res, "rolpassword"); i_rolvaliduntil = PQfnumber(res, "rolvaliduntil"); if (PQntuples(res) > 0) printf("--\n-- Roles\n--\n\n"); for (i = 0; i < PQntuples(res); i++) { const char *rolename; rolename = PQgetvalue(res, i, i_rolname); resetPQExpBuffer(buf); if (output_clean) appendPQExpBuffer(buf, "DROP ROLE %s;\n", fmtId(rolename)); /* * We dump CREATE ROLE followed by ALTER ROLE to ensure that the role * will acquire the right properties even if it already exists. (The * above DROP may therefore seem redundant, but it isn't really, * because this technique doesn't get rid of role memberships.) */ appendPQExpBuffer(buf, "CREATE ROLE %s;\n", fmtId(rolename)); appendPQExpBuffer(buf, "ALTER ROLE %s WITH", fmtId(rolename)); if (strcmp(PQgetvalue(res, i, i_rolsuper), "t") == 0) appendPQExpBuffer(buf, " SUPERUSER"); else appendPQExpBuffer(buf, " NOSUPERUSER"); if (strcmp(PQgetvalue(res, i, i_rolinherit), "t") == 0) appendPQExpBuffer(buf, " INHERIT"); else appendPQExpBuffer(buf, " NOINHERIT"); if (strcmp(PQgetvalue(res, i, i_rolcreaterole), "t") == 0) appendPQExpBuffer(buf, " CREATEROLE"); else appendPQExpBuffer(buf, " NOCREATEROLE"); if (strcmp(PQgetvalue(res, i, i_rolcreatedb), "t") == 0) appendPQExpBuffer(buf, " CREATEDB"); else appendPQExpBuffer(buf, " NOCREATEDB"); if (strcmp(PQgetvalue(res, i, i_rolcanlogin), "t") == 0) appendPQExpBuffer(buf, " LOGIN"); else appendPQExpBuffer(buf, " NOLOGIN"); if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0) appendPQExpBuffer(buf, " CONNECTION LIMIT %s", PQgetvalue(res, i, i_rolconnlimit)); if (!PQgetisnull(res, i, i_rolpassword)) { appendPQExpBuffer(buf, " PASSWORD "); appendStringLiteral(buf, PQgetvalue(res, i, i_rolpassword), true); } if (!PQgetisnull(res, i, i_rolvaliduntil)) appendPQExpBuffer(buf, " VALID UNTIL '%s'", PQgetvalue(res, i, i_rolvaliduntil)); appendPQExpBuffer(buf, ";\n"); printf("%s", buf->data); if (server_version >= 70300) dumpUserConfig(conn, rolename); } PQclear(res); printf("\n\n"); destroyPQExpBuffer(buf); }
/* * 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; }
/* call this one */ static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited) { char fnametmp[MAXPGPATH]; FILE *stream = NULL; const char *fname; bool error = false; int fd; struct stat before, after; if (filename_arg) fname = filename_arg; else { /* make a temp file to edit */ #ifndef WIN32 const char *tmpdir = getenv("TMPDIR"); if (!tmpdir) tmpdir = "/tmp"; #else char tmpdir[MAXPGPATH]; int ret; ret = GetTempPath(MAXPGPATH, tmpdir); if (ret == 0 || ret > MAXPGPATH) { psql_error("cannot locate temporary directory: %s\n", !ret ? strerror(errno) : ""); return false; } /* * No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the * current directory to the supplied path unless we use only * backslashes, so we do that. */ #endif #ifndef WIN32 snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d", tmpdir, "/", (int) getpid()); #else snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d", tmpdir, "" /* trailing separator already present */ , (int) getpid()); #endif fname = (const char *) fnametmp; fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd != -1) stream = fdopen(fd, "w"); if (fd == -1 || !stream) { psql_error("could not open temporary file \"%s\": %s\n", fname, strerror(errno)); error = true; } else { unsigned int ql = query_buf->len; if (ql == 0 || query_buf->data[ql - 1] != '\n') { appendPQExpBufferChar(query_buf, '\n'); ql++; } if (fwrite(query_buf->data, 1, ql, stream) != ql) { psql_error("%s: %s\n", fname, strerror(errno)); fclose(stream); remove(fname); error = true; } else if (fclose(stream) != 0) { psql_error("%s: %s\n", fname, strerror(errno)); remove(fname); error = true; } } } if (!error && stat(fname, &before) != 0) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } /* call editor */ if (!error) error = !editFile(fname); if (!error && stat(fname, &after) != 0) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } if (!error && before.st_mtime != after.st_mtime) { stream = fopen(fname, PG_BINARY_R); if (!stream) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } else { /* read file back into query_buf */ char line[1024]; resetPQExpBuffer(query_buf); while (fgets(line, sizeof(line), stream) != NULL) appendPQExpBufferStr(query_buf, line); if (ferror(stream)) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } else if (edited) { *edited = true; } fclose(stream); } } /* remove temp file */ if (!filename_arg) { if (remove(fname) == -1) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } } return !error; }
/* * Dump commands to create each database. * * To minimize the number of reconnections (and possibly ensuing * password prompts) required by the output script, we emit all CREATE * DATABASE commands during the initial phase of the script, and then * run pg_dump for each database to dump the contents of that * database. We skip databases marked not datallowconn, since we'd be * unable to connect to them anyway (and besides, we don't want to * dump template0). */ static void dumpCreateDB(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); PGresult *res; int i; printf("--\n-- Database creation\n--\n\n"); if (server_version >= 80100) res = executeQuery(conn, "SELECT datname, " "coalesce(rolname, (select rolname from pg_authid where oid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, datacl, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace " "FROM pg_database d LEFT JOIN pg_authid u ON (datdba = u.oid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 80000) res = executeQuery(conn, "SELECT datname, " "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, datacl, -1 as datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace " "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 70300) res = executeQuery(conn, "SELECT datname, " "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, datacl, -1 as datconnlimit, " "'pg_default' AS dattablespace " "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 70100) res = executeQuery(conn, "SELECT datname, " "coalesce(" "(select usename from pg_shadow where usesysid=datdba), " "(select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, '' as datacl, -1 as datconnlimit, " "'pg_default' AS dattablespace " "FROM pg_database d " "WHERE datallowconn ORDER BY 1"); else { /* * Note: 7.0 fails to cope with sub-select in COALESCE, so just deal * with getting a NULL by not printing any OWNER clause. */ res = executeQuery(conn, "SELECT datname, " "(select usename from pg_shadow where usesysid=datdba), " "pg_encoding_to_char(d.encoding), " "'f' as datistemplate, " "'' as datacl, -1 as datconnlimit, " "'pg_default' AS dattablespace " "FROM pg_database d " "ORDER BY 1"); } for (i = 0; i < PQntuples(res); i++) { char *dbname = PQgetvalue(res, i, 0); char *dbowner = PQgetvalue(res, i, 1); char *dbencoding = PQgetvalue(res, i, 2); char *dbistemplate = PQgetvalue(res, i, 3); char *dbacl = PQgetvalue(res, i, 4); char *dbconnlimit = PQgetvalue(res, i, 5); char *dbtablespace = PQgetvalue(res, i, 6); char *fdbname; fdbname = strdup(fmtId(dbname)); resetPQExpBuffer(buf); /* * Skip the CREATE DATABASE commands for "template1" and "postgres", * since they are presumably already there in the destination cluster. * We do want to emit their ACLs and config options if any, however. */ if (strcmp(dbname, "template1") != 0 && strcmp(dbname, "postgres") != 0) { if (output_clean) appendPQExpBuffer(buf, "DROP DATABASE %s;\n", fdbname); appendPQExpBuffer(buf, "CREATE DATABASE %s", fdbname); appendPQExpBuffer(buf, " WITH TEMPLATE = template0"); if (strlen(dbowner) != 0) appendPQExpBuffer(buf, " OWNER = %s", fmtId(dbowner)); appendPQExpBuffer(buf, " ENCODING = "); appendStringLiteral(buf, dbencoding, true); /* Output tablespace if it isn't default */ if (strcmp(dbtablespace, "pg_default") != 0) appendPQExpBuffer(buf, " TABLESPACE = %s", fmtId(dbtablespace)); if (strcmp(dbconnlimit, "-1") != 0) appendPQExpBuffer(buf, " CONNECTION LIMIT = %s", dbconnlimit); appendPQExpBuffer(buf, ";\n"); if (strcmp(dbistemplate, "t") == 0) { appendPQExpBuffer(buf, "UPDATE pg_database SET datistemplate = 't' WHERE datname = "); appendStringLiteral(buf, dbname, true); appendPQExpBuffer(buf, ";\n"); } } if (!skip_acls && !buildACLCommands(fdbname, "DATABASE", dbacl, dbowner, server_version, buf)) { fprintf(stderr, _("%s: could not parse ACL list (%s) for database \"%s\"\n"), progname, dbacl, fdbname); PQfinish(conn); exit(1); } printf("%s", buf->data); if (server_version >= 70300) dumpDatabaseConfig(conn, dbname); free(fdbname); } PQclear(res); destroyPQExpBuffer(buf); printf("\n\n"); }
/* * PQendcopy * * See fe-exec.c for documentation. */ int pqEndcopy2(PGconn *conn) { PGresult *result; if (conn->asyncStatus != PGASYNC_COPY_IN && conn->asyncStatus != PGASYNC_COPY_OUT) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("no COPY in progress\n")); return 1; } /* * make sure no data is waiting to be sent, abort if we are non-blocking * and the flush fails */ if (pqFlush(conn) && pqIsnonblocking(conn)) return 1; /* non blocking connections may have to abort at this point. */ if (pqIsnonblocking(conn) && PQisBusy(conn)) return 1; /* Return to active duty */ conn->asyncStatus = PGASYNC_BUSY; resetPQExpBuffer(&conn->errorMessage); /* Wait for the completion response */ result = PQgetResult(conn); /* Expecting a successful result */ if (result && result->resultStatus == PGRES_COMMAND_OK) { PQclear(result); return 0; } /* * Trouble. For backwards-compatibility reasons, we issue the error * message as if it were a notice (would be nice to get rid of this * silliness, but too many apps probably don't handle errors from * PQendcopy reasonably). Note that the app can still obtain the error * status from the PGconn object. */ if (conn->errorMessage.len > 0) { /* We have to strip the trailing newline ... pain in neck... */ char svLast = conn->errorMessage.data[conn->errorMessage.len - 1]; if (svLast == '\n') conn->errorMessage.data[conn->errorMessage.len - 1] = '\0'; pqInternalNotice(&conn->noticeHooks, "%s", conn->errorMessage.data); conn->errorMessage.data[conn->errorMessage.len - 1] = svLast; } PQclear(result); /* * The worst case is that we've lost sync with the backend entirely due to * application screwup of the copy in/out protocol. To recover, reset the * connection (talk about using a sledgehammer...) */ pqInternalNotice(&conn->noticeHooks, "lost synchronization with server, resetting connection"); /* * Users doing non-blocking connections need to handle the reset * themselves, they'll need to check the connection status if we return an * error. */ if (pqIsnonblocking(conn)) PQresetStart(conn); else PQreset(conn); return 1; }
/* * Construct a vacuum/analyze command to run based on the given options, in the * given string buffer, which may contain previous garbage. * * An optional table name can be passed; this must be already be properly * quoted. The command is semicolon-terminated. */ static void prepare_vacuum_command(PQExpBuffer sql, PGconn *conn, vacuumingOptions *vacopts, const char *table) { resetPQExpBuffer(sql); if (vacopts->analyze_only) { appendPQExpBufferStr(sql, "ANALYZE"); if (vacopts->verbose) appendPQExpBufferStr(sql, " VERBOSE"); } else { appendPQExpBufferStr(sql, "VACUUM"); if (PQserverVersion(conn) >= 90000) { const char *paren = " ("; const char *comma = ", "; const char *sep = paren; if (vacopts->full) { appendPQExpBuffer(sql, "%sFULL", sep); sep = comma; } if (vacopts->freeze) { appendPQExpBuffer(sql, "%sFREEZE", sep); sep = comma; } if (vacopts->verbose) { appendPQExpBuffer(sql, "%sVERBOSE", sep); sep = comma; } if (vacopts->and_analyze) { appendPQExpBuffer(sql, "%sANALYZE", sep); sep = comma; } if (sep != paren) appendPQExpBufferStr(sql, ")"); } else { if (vacopts->full) appendPQExpBufferStr(sql, " FULL"); if (vacopts->freeze) appendPQExpBufferStr(sql, " FREEZE"); if (vacopts->verbose) appendPQExpBufferStr(sql, " VERBOSE"); if (vacopts->and_analyze) appendPQExpBufferStr(sql, " ANALYZE"); } } if (table) appendPQExpBuffer(sql, " %s", table); appendPQExpBufferChar(sql, ';'); }
/* * Attempt to read an Error or Notice response message. * This is possible in several places, so we break it out as a subroutine. * Entry: 'E' or 'N' message type has already been consumed. * Exit: returns 0 if successfully consumed message. * returns EOF if not enough data. */ static int pqGetErrorNotice2(PGconn *conn, bool isError) { PGresult *res = NULL; PQExpBufferData workBuf; char *startp; char *splitp; /* * Since the message might be pretty long, we create a temporary * PQExpBuffer rather than using conn->workBuffer. workBuffer is intended * for stuff that is expected to be short. */ initPQExpBuffer(&workBuf); if (pqGets(&workBuf, conn)) goto failure; /* * Make a PGresult to hold the message. We temporarily lie about the * result status, so that PQmakeEmptyPGresult doesn't uselessly copy * conn->errorMessage. * * NB: This allocation can fail, if you run out of memory. The rest of the * function handles that gracefully, and we still try to set the error * message as the connection's error message. */ res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); if (res) { res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR; res->errMsg = pqResultStrdup(res, workBuf.data); } /* * Break the message into fields. We can't do very much here, but we can * split the severity code off, and remove trailing newlines. Also, we use * the heuristic that the primary message extends only to the first * newline --- anything after that is detail message. (In some cases it'd * be better classed as hint, but we can hardly be expected to guess that * here.) */ while (workBuf.len > 0 && workBuf.data[workBuf.len - 1] == '\n') workBuf.data[--workBuf.len] = '\0'; splitp = strstr(workBuf.data, ": "); if (splitp) { /* what comes before the colon is severity */ *splitp = '\0'; pqSaveMessageField(res, PG_DIAG_SEVERITY, workBuf.data); startp = splitp + 3; } else { /* can't find a colon? oh well... */ startp = workBuf.data; } splitp = strchr(startp, '\n'); if (splitp) { /* what comes before the newline is primary message */ *splitp++ = '\0'; pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp); /* the rest is detail; strip any leading whitespace */ while (*splitp && isspace((unsigned char) *splitp)) splitp++; pqSaveMessageField(res, PG_DIAG_MESSAGE_DETAIL, splitp); } else { /* single-line message, so all primary */ pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp); } /* * Either save error as current async result, or just emit the notice. * Also, if it's an error and we were in a transaction block, assume the * server has now gone to error-in-transaction state. */ if (isError) { pqClearAsyncResult(conn); conn->result = res; resetPQExpBuffer(&conn->errorMessage); if (res && !PQExpBufferDataBroken(workBuf) && res->errMsg) appendPQExpBufferStr(&conn->errorMessage, res->errMsg); else printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory")); if (conn->xactStatus == PQTRANS_INTRANS) conn->xactStatus = PQTRANS_INERROR; } else { if (res) { if (res->noticeHooks.noticeRec != NULL) (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); PQclear(res); } } termPQExpBuffer(&workBuf); return 0; failure: if (res) PQclear(res); termPQExpBuffer(&workBuf); return EOF; }