diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index d3220849f6a..db0e2a5f74c 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -65,7 +65,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, bool check_rights) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -176,6 +176,21 @@ CreateStatistics(CreateStatsStmt *stmt) } namestrcpy(&stxname, namestr); + /* + * Check we have creation rights in target namespace. Skip check if + * caller doesn't want it. + */ + if (check_rights) + { + AclResult aclresult; + + aclresult = object_aclcheck(NamespaceRelationId, namespaceId, + GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_SCHEMA, + get_namespace_name(namespaceId)); + } + /* * Deal with the possibility that the statistics object already exists. */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3548786fe41..b8b69336b89 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11038,7 +11038,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, Assert(stmt->transformed); HOLD_DISPATCH(); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, !is_rebuild); RESUME_DISPATCH(); return address; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 08756e59c2a..021c69ad031 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -2538,7 +2538,7 @@ ProcessUtilitySlow(ParseState *pstate, /* Run parse analysis ... */ stmt = transformStatsStmt(relid, stmt, queryString); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, true); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 4b00ff7cdc7..7af15a37f52 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -87,7 +87,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, bool check_rights); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 135af368f84..54858d15689 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -1075,7 +1076,7 @@ parse_comma_separated_list(char **startptr, bool *more) char *p; char *s = *startptr; char *e; - int len; + size_t len; /* * Search for the end of the current element; a comma or end-of-string @@ -5594,7 +5595,21 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options, /* concatenate values into a single string with newline terminators */ size = 1; /* for the trailing null */ for (i = 0; values[i] != NULL; i++) + { + if (values[i]->bv_len >= INT_MAX || + size > (INT_MAX - (values[i]->bv_len + 1))) + { + libpq_append_error(errorMessage, + "connection info string size exceeds the maximum allowed (%d)", + INT_MAX); + ldap_value_free_len(values); + ldap_unbind(ld); + return 3; + } + size += values[i]->bv_len + 1; + } + if ((result = malloc(size)) == NULL) { libpq_append_error(errorMessage, "out of memory"); diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 2cf897cef04..e723bdf925c 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -532,7 +532,7 @@ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) } else { - attval->value = (char *) pqResultAlloc(res, len + 1, true); + attval->value = (char *) pqResultAlloc(res, (size_t) len + 1, true); if (!attval->value) goto fail; attval->len = len; @@ -624,8 +624,13 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary) */ if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD) { - size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD; + size_t alloc_size; + /* Don't wrap around with overly large requests. */ + if (nBytes > SIZE_MAX - PGRESULT_BLOCK_OVERHEAD) + return NULL; + + alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD; block = (PGresult_data *) malloc(alloc_size); if (!block) return NULL; @@ -1292,7 +1297,7 @@ pqRowProcessor(PGconn *conn, const char **errmsgp) bool isbinary = (res->attDescs[i].format != 0); char *val; - val = (char *) pqResultAlloc(res, clen + 1, isbinary); + val = (char *) pqResultAlloc(res, (size_t) clen + 1, isbinary); if (val == NULL) goto fail; @@ -4166,6 +4171,27 @@ PQescapeString(char *to, const char *from, size_t length) } +/* + * Frontend version of the backend's add_size(), intended to be API-compatible + * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. + * Returns true instead if the addition overflows. + * + * TODO: move to common/int.h + */ +static bool +add_size_overflow(size_t s1, size_t s2, size_t *dst) +{ + size_t result; + + result = s1 + s2; + if (result < s1 || result < s2) + return true; + + *dst = result; + return false; +} + + /* * Escape arbitrary strings. If as_ident is true, we escape the result * as an identifier; if false, as a literal. The result is returned in @@ -4178,9 +4204,9 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) const char *s; char *result; char *rp; - int num_quotes = 0; /* single or double, depending on as_ident */ - int num_backslashes = 0; - size_t input_len = strlen(str); + size_t num_quotes = 0; /* single or double, depending on as_ident */ + size_t num_backslashes = 0; + size_t input_len = strnlen(str, len); size_t result_size; char quote_char = as_ident ? '"' : '\''; bool validated_mb = false; @@ -4245,10 +4271,21 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) } } - /* Allocate output buffer. */ - result_size = input_len + num_quotes + 3; /* two quotes, plus a NUL */ + /* + * Allocate output buffer. Protect against overflow, in case the caller + * has allocated a large fraction of the available size_t. + */ + if (add_size_overflow(input_len, num_quotes, &result_size) || + add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */ + goto overflow; + if (!as_ident && num_backslashes > 0) - result_size += num_backslashes + 2; + { + if (add_size_overflow(result_size, num_backslashes, &result_size) || + add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */ + goto overflow; + } + result = rp = (char *) malloc(result_size); if (rp == NULL) { @@ -4321,6 +4358,12 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident) *rp = '\0'; return result; + +overflow: + libpq_append_conn_error(conn, + "escaped string size exceeds the maximum allowed (%zu)", + SIZE_MAX); + return NULL; } char * @@ -4386,16 +4429,25 @@ PQescapeByteaInternal(PGconn *conn, unsigned char *result; size_t i; size_t len; - size_t bslash_len = (std_strings ? 1 : 2); + const size_t bslash_len = (std_strings ? 1 : 2); /* - * empty string has 1 char ('\0') + * Calculate the escaped length, watching for overflow as we do with + * PQescapeInternal(). The following code relies on a small constant + * bslash_len so that small additions and multiplications don't need their + * own overflow checks. + * + * Start with the empty string, which has 1 char ('\0'). */ len = 1; if (use_hex) { - len += bslash_len + 1 + 2 * from_length; + /* We prepend "\x" and double each input character. */ + if (add_size_overflow(len, bslash_len + 1, &len) || + add_size_overflow(len, from_length, &len) || + add_size_overflow(len, from_length, &len)) + goto overflow; } else { @@ -4403,13 +4455,25 @@ PQescapeByteaInternal(PGconn *conn, for (i = from_length; i > 0; i--, vp++) { if (*vp < 0x20 || *vp > 0x7e) - len += bslash_len + 3; + { + if (add_size_overflow(len, bslash_len + 3, &len)) /* octal "\ooo" */ + goto overflow; + } else if (*vp == '\'') - len += 2; + { + if (add_size_overflow(len, 2, &len)) /* double each quote */ + goto overflow; + } else if (*vp == '\\') - len += bslash_len + bslash_len; + { + if (add_size_overflow(len, bslash_len * 2, &len)) /* double each backslash */ + goto overflow; + } else - len++; + { + if (add_size_overflow(len, 1, &len)) + goto overflow; + } } } @@ -4470,6 +4534,13 @@ PQescapeByteaInternal(PGconn *conn, *rp = '\0'; return result; + +overflow: + if (conn) + libpq_append_conn_error(conn, + "escaped bytea size exceeds the maximum allowed (%zu)", + SIZE_MAX); + return NULL; } unsigned char * diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c index 40620b47e94..4b9dd7da087 100644 --- a/src/interfaces/libpq/fe-print.c +++ b/src/interfaces/libpq/fe-print.c @@ -107,6 +107,16 @@ PQprint(FILE *fout, const PGresult *res, const PQprintOpt *po) } screen_size; #endif + /* + * Quick sanity check on po->fieldSep, since we make heavy use of int + * math throughout. + */ + if (fs_len < strlen(po->fieldSep)) + { + fprintf(stderr, libpq_gettext("overlong field separator\n")); + goto exit; + } + nTups = PQntuples(res); fieldNames = (const char **) calloc(nFields, sizeof(char *)); fieldNotNum = (unsigned char *) calloc(nFields, 1); @@ -402,7 +412,7 @@ do_field(const PQprintOpt *po, const PGresult *res, { if (plen > fieldMax[j]) fieldMax[j] = plen; - if (!(fields[i * nFields + j] = (char *) malloc(plen + 1))) + if (!(fields[i * nFields + j] = (char *) malloc((size_t) plen + 1))) { fprintf(stderr, libpq_gettext("out of memory\n")); return false; @@ -452,6 +462,27 @@ do_field(const PQprintOpt *po, const PGresult *res, } +/* + * Frontend version of the backend's add_size(), intended to be API-compatible + * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. + * Returns true instead if the addition overflows. + * + * TODO: move to common/int.h + */ +static bool +add_size_overflow(size_t s1, size_t s2, size_t *dst) +{ + size_t result; + + result = s1 + s2; + if (result < s1 || result < s2) + return true; + + *dst = result; + return false; +} + + static char * do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, const char **fieldNames, unsigned char *fieldNotNum, @@ -464,15 +495,31 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, fputs("", fout); else { - int tot = 0; + size_t tot = 0; int n = 0; char *p = NULL; + /* Calculate the border size, checking for overflow. */ for (; n < nFields; n++) - tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0); + { + /* Field plus separator, plus 2 extra '-' in standard format. */ + if (add_size_overflow(tot, fieldMax[n], &tot) || + add_size_overflow(tot, fs_len, &tot) || + (po->standard && add_size_overflow(tot, 2, &tot))) + goto overflow; + } if (po->standard) - tot += fs_len * 2 + 2; - border = malloc(tot + 1); + { + /* An extra separator at the front and back. */ + if (add_size_overflow(tot, fs_len, &tot) || + add_size_overflow(tot, fs_len, &tot) || + add_size_overflow(tot, 2, &tot)) + goto overflow; + } + if (add_size_overflow(tot, 1, &tot)) /* terminator */ + goto overflow; + + border = malloc(tot); if (!border) { fprintf(stderr, libpq_gettext("out of memory\n")); @@ -535,6 +582,10 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax, else fprintf(fout, "\n%s\n", border); return border; + +overflow: + fprintf(stderr, libpq_gettext("header size exceeds the maximum allowed\n")); + return NULL; } diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index dbb64161a33..82d0516e138 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -25,6 +25,7 @@ #include #include +#include #ifdef WIN32 #include "win32.h" @@ -64,8 +65,8 @@ static int getReadyForQuery(PGconn *conn); static void saveCdbStatMsg(PGresult *result, char *data, int len); static void reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding); -static int build_startup_packet(const PGconn *conn, char *packet, - const PQEnvironmentOption *options); +static size_t build_startup_packet(const PGconn *conn, char *packet, + const PQEnvironmentOption *options); /* @@ -1385,8 +1386,21 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding) * scridx[] respectively. */ - /* we need a safe allocation size... */ + /* + * We need a safe allocation size. + * + * The only caller of reportErrorPosition() is pqBuildErrorMessage3(); it + * gets its query from either a PQresultErrorField() or a PGcmdQueueEntry, + * both of which must have fit into conn->inBuffer/outBuffer. So slen fits + * inside an int, but we can't assume that (slen * sizeof(int)) fits + * inside a size_t. + */ slen = strlen(wquery) + 1; + if (slen > SIZE_MAX / sizeof(int)) + { + free(wquery); + return; + } qidx = (int *) malloc(slen * sizeof(int)); if (qidx == NULL) @@ -2443,15 +2457,43 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen, const PQEnvironmentOption *options) { char *startpacket; + size_t len; + + len = build_startup_packet(conn, NULL, options); + if (len == 0 || len > INT_MAX) + return NULL; - *packetlen = build_startup_packet(conn, NULL, options); + *packetlen = len; startpacket = (char *) malloc(*packetlen); if (!startpacket) return NULL; - *packetlen = build_startup_packet(conn, startpacket, options); + + len = build_startup_packet(conn, startpacket, options); + Assert(*packetlen == len); + return startpacket; } +/* + * Frontend version of the backend's add_size(), intended to be API-compatible + * with the pg_add_*_overflow() helpers. Stores the result into *dst on success. + * Returns true instead if the addition overflows. + * + * TODO: move to common/int.h + */ +static bool +add_size_overflow(size_t s1, size_t s2, size_t *dst) +{ + size_t result; + + result = s1 + s2; + if (result < s1 || result < s2) + return true; + + *dst = result; + return false; +} + /* * Build a startup packet given a filled-in PGconn structure. * @@ -2459,13 +2501,13 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen, * To avoid duplicate logic, this routine is called twice: the first time * (with packet == NULL) just counts the space needed, the second time * (with packet == allocated space) fills it in. Return value is the number - * of bytes used. + * of bytes used, or zero in the unlikely event of size_t overflow. */ -static int +static size_t build_startup_packet(const PGconn *conn, char *packet, const PQEnvironmentOption *options) { - int packet_len = 0; + size_t packet_len = 0; const PQEnvironmentOption *next_eo; const char *val; @@ -2484,10 +2526,12 @@ build_startup_packet(const PGconn *conn, char *packet, do { \ if (packet) \ strcpy(packet + packet_len, optname); \ - packet_len += strlen(optname) + 1; \ + if (add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \ + return 0; \ if (packet) \ strcpy(packet + packet_len, optval); \ - packet_len += strlen(optval) + 1; \ + if (add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \ + return 0; \ } while(0) if (conn->pguser && conn->pguser[0]) @@ -2535,7 +2579,8 @@ build_startup_packet(const PGconn *conn, char *packet, /* Add trailing terminator */ if (packet) packet[packet_len] = '\0'; - packet_len++; + if (add_size_overflow(packet_len, 1, &packet_len)) + return 0; return packet_len; } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index fabf6bab94d..908cc5da0b9 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -558,7 +558,16 @@ struct pg_conn pg_prng_state prng_state; /* prng state for load balancing connections */ - /* Buffer for data received from backend and not yet processed */ + /* + * Buffer for data received from backend and not yet processed. + * + * NB: We rely on a maximum inBufSize/outBufSize of INT_MAX (and therefore + * an INT_MAX upper bound on the size of any and all packet contents) to + * avoid overflow; for example in reportErrorPosition(). Changing the type + * would require not only an adjustment to the overflow protection in + * pqCheck{In,Out}BufferSpace(), but also a careful audit of all libpq + * code that uses ints during size calculations. + */ char *inBuffer; /* currently allocated buffer */ int inBufSize; /* allocated size of buffer */ int inStart; /* offset to first unconsumed data in buffer */ diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index cd6a5e61e67..80bf1733e89 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -3336,6 +3336,40 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x s_expr | {1} (2 rows) +-- CREATE STATISTICS checks for CREATE on the schema +RESET SESSION AUTHORIZATION; +CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT); +CREATE SCHEMA sts_sch2; +GRANT USAGE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch1 +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch2 +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch2 +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1; +GRANT CREATE ON SCHEMA sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch1 +CREATE STATISTICS sts_sch2.pass1 ON a, b FROM sts_sch1.tbl; +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.pass2 ON a, b FROM sts_sch1.tbl; +-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT; -- Tidy up DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); @@ -3345,6 +3379,8 @@ DROP SCHEMA tststats CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table tststats.priv_test_tbl drop cascades to view tststats.priv_test_view +DROP SCHEMA sts_sch1, sts_sch2 CASCADE; +NOTICE: drop cascades to table sts_sch1.tbl DROP USER regress_stats_user1; -- test analyze with extended statistics CREATE TABLE tbl_issue1293 (col1 int, col2 int); diff --git a/src/test/regress/expected/stats_ext_optimizer.out b/src/test/regress/expected/stats_ext_optimizer.out index 97513665ae1..cf187147fb9 100644 --- a/src/test/regress/expected/stats_ext_optimizer.out +++ b/src/test/regress/expected/stats_ext_optimizer.out @@ -3371,6 +3371,40 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x s_expr | {1} (2 rows) +-- CREATE STATISTICS checks for CREATE on the schema +RESET SESSION AUTHORIZATION; +CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT); +CREATE SCHEMA sts_sch2; +GRANT USAGE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch1 +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch2 +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch2 +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1; +GRANT CREATE ON SCHEMA sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +ERROR: permission denied for schema sts_sch1 +CREATE STATISTICS sts_sch2.pass1 ON a, b FROM sts_sch1.tbl; +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.pass2 ON a, b FROM sts_sch1.tbl; +-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT; -- Tidy up DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); @@ -3380,6 +3414,8 @@ DROP SCHEMA tststats CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table tststats.priv_test_tbl drop cascades to view tststats.priv_test_view +DROP SCHEMA sts_sch1, sts_sch2 CASCADE; +NOTICE: drop cascades to table sts_sch1.tbl DROP USER regress_stats_user1; -- test analyze with extended statistics CREATE TABLE tbl_issue1293 (col1 int, col2 int); diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index db9196b3918..5ac9587c8fa 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -1698,12 +1698,45 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext x SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*); +-- CREATE STATISTICS checks for CREATE on the schema +RESET SESSION AUTHORIZATION; +CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT); +CREATE SCHEMA sts_sch2; +GRANT USAGE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl; +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1; +GRANT CREATE ON SCHEMA sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.pass1 ON a, b FROM sts_sch1.tbl; +RESET SESSION AUTHORIZATION; +GRANT CREATE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +CREATE STATISTICS ON a, b FROM sts_sch1.tbl; +CREATE STATISTICS sts_sch2.pass2 ON a, b FROM sts_sch1.tbl; + +-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema +RESET SESSION AUTHORIZATION; +REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1; +SET SESSION AUTHORIZATION regress_stats_user1; +ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT; + -- Tidy up DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); RESET SESSION AUTHORIZATION; DROP TABLE stats_ext_tbl; DROP SCHEMA tststats CASCADE; +DROP SCHEMA sts_sch1, sts_sch2 CASCADE; DROP USER regress_stats_user1; -- test analyze with extended statistics