aboutsummaryrefslogtreecommitdiff
path: root/src/bin/psql
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql')
-rw-r--r--src/bin/psql/command.c16
-rw-r--r--src/bin/psql/describe.c292
-rw-r--r--src/bin/psql/describe.h9
-rw-r--r--src/bin/psql/help.c2
-rw-r--r--src/bin/psql/tab-complete.c53
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) &&