diff options
Diffstat (limited to 'src/bin/psql')
-rw-r--r-- | src/bin/psql/command.c | 16 | ||||
-rw-r--r-- | src/bin/psql/describe.c | 292 | ||||
-rw-r--r-- | src/bin/psql/describe.h | 9 | ||||
-rw-r--r-- | src/bin/psql/help.c | 2 | ||||
-rw-r--r-- | src/bin/psql/tab-complete.c | 53 |
5 files changed, 368 insertions, 4 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 4139b7763fb..0c164a339c1 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -501,6 +501,22 @@ exec_command(const char *cmd, else success = PSQL_CMD_UNKNOWN; break; + case 'R': + switch (cmd[2]) + { + case 'p': + if (show_verbose) + success = describePublications(pattern); + else + success = listPublications(pattern); + break; + case 's': + success = describeSubscriptions(pattern, show_verbose); + break; + default: + status = PSQL_CMD_UNKNOWN; + } + break; case 'u': success = describeRoles(pattern, show_verbose, show_system); break; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index ce198779f49..c501168d8c7 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2387,6 +2387,38 @@ describeOneTableDetails(const char *schemaname, } PQclear(result); } + + /* print any publications */ + if (pset.sversion >= 100000) + { + printfPQExpBuffer(&buf, + "SELECT pub.pubname\n" + " FROM pg_catalog.pg_publication pub\n" + " LEFT JOIN pg_publication_rel pr\n" + " ON (pr.prpubid = pub.oid)\n" + "WHERE pr.prrelid = '%s' OR pub.puballtables\n" + "ORDER BY 1;", + oid); + + result = PSQLexec(buf.data); + if (!result) + goto error_return; + else + tuples = PQntuples(result); + + if (tuples > 0) + printTableAddFooter(&cont, _("Publications:")); + + /* Might be an empty set - that's ok */ + for (i = 0; i < tuples; i++) + { + printfPQExpBuffer(&buf, " \"%s\"", + PQgetvalue(result, i, 0)); + + printTableAddFooter(&cont, buf.data); + } + PQclear(result); + } } if (view_def) @@ -4846,6 +4878,266 @@ listOneExtensionContents(const char *extname, const char *oid) return true; } +/* \dRp + * Lists publications. + * + * Takes an optional regexp to select particular publications + */ +bool +listPublications(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = {false, false, false, false, false}; + + if (pset.sversion < 100000) + { + char sverbuf[32]; + psql_error("The server (version %s) does not support publications.\n", + formatPGVersionNumber(pset.sversion, false, + sverbuf, sizeof(sverbuf))); + return true; + } + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT pubname AS \"%s\",\n" + " pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n" + " pubinsert AS \"%s\",\n" + " pubupdate AS \"%s\",\n" + " pubdelete AS \"%s\"\n", + gettext_noop("Name"), + gettext_noop("Owner"), + gettext_noop("Inserts"), + gettext_noop("Updates"), + gettext_noop("Deletes")); + + appendPQExpBufferStr(&buf, + "\nFROM pg_catalog.pg_publication\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "pubname", NULL, + NULL); + + appendPQExpBufferStr(&buf, "ORDER BY 1;"); + + res = PSQLexec(buf.data); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of publications"); + myopt.translate_header = true; + myopt.translate_columns = translate_columns; + myopt.n_translate_columns = lengthof(translate_columns); + + printQuery(res, &myopt, pset.queryFout, false, pset.logfile); + + PQclear(res); + + return true; +} + +/* \dRp+ + * Describes publications including the contents. + * + * Takes an optional regexp to select particular publications + */ +bool +describePublications(const char *pattern) +{ + PQExpBufferData buf; + int i; + PGresult *res; + + if (pset.sversion < 100000) + { + char sverbuf[32]; + psql_error("The server (version %s) does not support publications.\n", + formatPGVersionNumber(pset.sversion, false, + sverbuf, sizeof(sverbuf))); + return true; + } + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT oid, pubname, puballtables, pubinsert,\n" + " pubupdate, pubdelete\n" + "FROM pg_catalog.pg_publication\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "pubname", NULL, + NULL); + + appendPQExpBufferStr(&buf, "ORDER BY 2;"); + + res = PSQLexec(buf.data); + if (!res) + { + termPQExpBuffer(&buf); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + const char align = 'l'; + int ncols = 3; + int nrows = 1; + int tables = 0; + PGresult *tabres; + char *pubid = PQgetvalue(res, i, 0); + char *pubname = PQgetvalue(res, i, 1); + bool puballtables = strcmp(PQgetvalue(res, i, 2), "t") == 0; + int j; + PQExpBufferData title; + printTableOpt myopt = pset.popt.topt; + printTableContent cont; + + initPQExpBuffer(&title); + printfPQExpBuffer(&title, _("Publication %s"), pubname); + printTableInit(&cont, &myopt, title.data, ncols, nrows); + + printTableAddHeader(&cont, gettext_noop("Inserts"), true, align); + printTableAddHeader(&cont, gettext_noop("Updates"), true, align); + printTableAddHeader(&cont, gettext_noop("Deletes"), true, align); + + printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); + + if (puballtables) + printfPQExpBuffer(&buf, + "SELECT n.nspname, c.relname\n" + "FROM pg_catalog.pg_class c,\n" + " pg_catalog.pg_namespace n\n" + "WHERE c.relnamespace = n.oid\n" + " AND c.relkind = 'r'\n" + " AND n.nspname <> 'pg_catalog'\n" + " AND n.nspname <> 'information_schema'\n" + "ORDER BY 1,2"); + else + printfPQExpBuffer(&buf, + "SELECT n.nspname, c.relname\n" + "FROM pg_catalog.pg_class c,\n" + " pg_catalog.pg_namespace n,\n" + " pg_catalog.pg_publication_rel pr\n" + "WHERE c.relnamespace = n.oid\n" + " AND c.oid = pr.prrelid\n" + " AND pr.prpubid = '%s'\n" + "ORDER BY 1,2", pubid); + + tabres = PSQLexec(buf.data); + if (!tabres) + { + printTableCleanup(&cont); + PQclear(res); + termPQExpBuffer(&buf); + termPQExpBuffer(&title); + return false; + } + else + tables = PQntuples(tabres); + + if (tables > 0) + printTableAddFooter(&cont, _("Tables:")); + + for (j = 0; j < tables; j++) + { + printfPQExpBuffer(&buf, " \"%s.%s\"", + PQgetvalue(tabres, j, 0), + PQgetvalue(tabres, j, 1)); + + printTableAddFooter(&cont, buf.data); + } + PQclear(tabres); + + printTable(&cont, pset.queryFout, false, pset.logfile); + printTableCleanup(&cont); + + termPQExpBuffer(&title); + } + + termPQExpBuffer(&buf); + PQclear(res); + + return true; +} + +/* \dRs + * Describes subscriptions. + * + * Takes an optional regexp to select particular subscriptions + */ +bool +describeSubscriptions(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = {false, false, false, false, false}; + + if (pset.sversion < 100000) + { + char sverbuf[32]; + psql_error("The server (version %s) does not support subscriptions.\n", + formatPGVersionNumber(pset.sversion, false, + sverbuf, sizeof(sverbuf))); + return true; + } + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT subname AS \"%s\"\n" + ", pg_catalog.pg_get_userbyid(subowner) AS \"%s\"\n" + ", subenabled AS \"%s\"\n" + ", subpublications AS \"%s\"\n", + gettext_noop("Name"), + gettext_noop("Owner"), + gettext_noop("Enabled"), + gettext_noop("Publication")); + + if (verbose) + { + appendPQExpBuffer(&buf, + ", subconninfo AS \"%s\"\n", + gettext_noop("Conninfo")); + } + + /* Only display subscritpions in current database. */ + appendPQExpBufferStr(&buf, + "FROM pg_catalog.pg_subscription\n" + "WHERE subdbid = (SELECT oid\n" + " FROM pg_catalog.pg_database\n" + " WHERE datname = current_database())"); + + processSQLNamePattern(pset.db, &buf, pattern, true, false, + NULL, "subname", NULL, + NULL); + + appendPQExpBufferStr(&buf, "ORDER BY 1;"); + + res = PSQLexec(buf.data); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of subscriptions"); + myopt.translate_header = true; + myopt.translate_columns = translate_columns; + myopt.n_translate_columns = lengthof(translate_columns); + + printQuery(res, &myopt, pset.queryFout, false, pset.logfile); + + PQclear(res); + return true; +} + /* * printACLColumn * diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 4600182e4c9..074553e1334 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -102,4 +102,13 @@ extern bool listExtensionContents(const char *pattern); /* \dy */ extern bool listEventTriggers(const char *pattern, bool verbose); +/* \dRp */ +bool listPublications(const char *pattern); + +/* \dRp+ */ +bool describePublications(const char *pattern); + +/* \dRs */ +bool describeSubscriptions(const char *pattern, bool verbose); + #endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 09baf871dda..53656294da4 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -241,6 +241,8 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\dO[S+] [PATTERN] list collations\n")); fprintf(output, _(" \\dp [PATTERN] list table, view, and sequence access privileges\n")); fprintf(output, _(" \\drds [PATRN1 [PATRN2]] list per-database role settings\n")); + fprintf(output, _(" \\dRp[+] [PATTERN] list replication publications\n")); + fprintf(output, _(" \\dRs[+] [PATTERN] list replication subscriptions\n")); fprintf(output, _(" \\ds[S+] [PATTERN] list sequences\n")); fprintf(output, _(" \\dt[S+] [PATTERN] list tables\n")); fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 7709112f494..d6fffcf42f1 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -960,11 +960,13 @@ static const pgsql_thing_t words_after_create[] = { {"OWNED", NULL, NULL, THING_NO_CREATE}, /* for DROP OWNED BY ... */ {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW}, {"POLICY", NULL, NULL}, + {"PUBLICATION", NULL, NULL}, {"ROLE", Query_for_list_of_roles}, {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, &Query_for_list_of_sequences}, {"SERVER", Query_for_list_of_servers}, + {"SUBSCRIPTION", NULL, NULL}, {"TABLE", NULL, &Query_for_list_of_tables}, {"TABLESPACE", Query_for_list_of_tablespaces}, {"TEMP", NULL, NULL, THING_NO_DROP}, /* for CREATE TEMP TABLE ... */ @@ -1407,8 +1409,8 @@ psql_completion(const char *text, int start, int end) {"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR", - "POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE", - "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", + "POLICY", "PUBLICATION", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", + "SUBSCRIPTION", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL}; COMPLETE_WITH_LIST(list_ALTER); @@ -1433,7 +1435,26 @@ psql_completion(const char *text, int start, int end) else COMPLETE_WITH_FUNCTION_ARG(prev2_wd); } - + /* ALTER PUBLICATION <name> ...*/ + else if (Matches3("ALTER","PUBLICATION",MatchAny)) + { + COMPLETE_WITH_LIST5("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE", "OWNER TO"); + } + /* ALTER PUBLICATION <name> .. WITH ( ... */ + else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "(")) + { + COMPLETE_WITH_LIST6("PUBLISH INSERT", "NOPUBLISH INSERT", "PUBLISH UPDATE", + "NOPUBLISH UPDATE", "PUBLISH DELETE", "NOPUBLISH DELETE"); + } + /* ALTER SUBSCRIPTION <name> ... */ + else if (Matches3("ALTER","SUBSCRIPTION",MatchAny)) + { + COMPLETE_WITH_LIST6("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE", "OWNER TO"); + } + else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "(")) + { + COMPLETE_WITH_CONST("SLOT NAME"); + } /* ALTER SCHEMA <name> */ else if (Matches3("ALTER", "SCHEMA", MatchAny)) COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO"); @@ -2227,6 +2248,20 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_CONST("("); +/* CREATE PUBLICATION */ + else if (Matches3("CREATE", "PUBLICATION", MatchAny)) + COMPLETE_WITH_LIST3("FOR TABLE", "FOR ALL TABLES", "WITH ("); + else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR")) + COMPLETE_WITH_LIST2("TABLE", "ALL TABLES"); + /* Complete "CREATE PUBLICATION <name> FOR TABLE <table>" */ + else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* Complete "CREATE PUBLICATION <name> [...] WITH" */ + else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches2("WITH", "(")) + COMPLETE_WITH_LIST2("PUBLISH", "NOPUBLISH"); + else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches3("WITH", "(", MatchAny)) + COMPLETE_WITH_LIST3("INSERT", "UPDATE", "DELETE"); + /* CREATE RULE */ /* Complete "CREATE RULE <sth>" with "AS ON" */ else if (Matches3("CREATE", "RULE", MatchAny)) @@ -2278,6 +2313,16 @@ psql_completion(const char *text, int start, int end) else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny)) COMPLETE_WITH_CONST("("); +/* CREATE SUBSCRIPTION */ + else if (Matches3("CREATE", "SUBSCRIPTION", MatchAny)) + COMPLETE_WITH_CONST("CONNECTION"); + else if (Matches5("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",MatchAny)) + COMPLETE_WITH_CONST("PUBLICATION"); + /* Complete "CREATE SUBSCRIPTION <name> ... WITH ( <opt>" */ + else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("WITH", "(")) + COMPLETE_WITH_LIST5("ENABLED", "DISABLED", "CREATE SLOT", + "NOCREATE SLOT", "SLOT NAME"); + /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */ else if (TailMatches3("CREATE", "TRIGGER", MatchAny)) @@ -2438,7 +2483,7 @@ psql_completion(const char *text, int start, int end) /* DROP */ /* Complete DROP object with CASCADE / RESTRICT */ else if (Matches3("DROP", - "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW", + "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW", MatchAny) || Matches4("DROP", "ACCESS", "METHOD", MatchAny) || (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) && |